diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index f74c46161180..11ca219041fc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -61,6 +61,7 @@ public class ApiConstants { public static final String BACKUP_AVAILABLE = "backupavailable"; public static final String BACKUP_ID = "backupid"; public static final String BACKUP_LIMIT = "backuplimit"; + public static final String BACKUP_PATHS = "backuppaths"; public static final String BACKUP_OFFERING_NAME = "backupofferingname"; public static final String BACKUP_OFFERING_ID = "backupofferingid"; public static final String BACKUP_OFFERING_DETAILS = "backupofferingdetails"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java index 45173f8668ee..d0ddd0ec75f6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java @@ -79,7 +79,9 @@ public void execute() { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup"); } - BackupResponse response = backupManager.createBackupResponse(backup, null); + boolean isCallerRootAdmin = _accountService.isRootAdmin(CallContext.current().getCallingAccountId()); + + BackupResponse response = backupManager.createBackupResponse(backup, null, isCallerRootAdmin); response.setResponseName(getCommandName()); setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java index 1bf6d45db049..3fd41180b900 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java @@ -82,7 +82,8 @@ public String getDescription() { public void execute() { try { Backup backup = kvmBackupExportService.startBackup(this); - BackupResponse response = backupManager.createBackupResponse(backup, null); + boolean isCallerRootAdmin = _accountService.isRootAdmin(CallContext.current().getCallingAccountId()); + BackupResponse response = backupManager.createBackupResponse(backup, null, isCallerRootAdmin); response.setResponseName(getCommandName()); setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java index 703fd9cd4358..a94151b9344a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java @@ -148,12 +148,13 @@ public Boolean getListVmDetails() { ///////////////////////////////////////////////////// protected void setupResponseBackupList(final List backups, final Integer count) { + boolean isCallerRootAdmin = _accountService.isRootAdmin(CallContext.current().getCallingAccountId()); final List responses = new ArrayList<>(); for (Backup backup : backups) { if (backup == null) { continue; } - BackupResponse backupResponse = backupManager.createBackupResponse(backup, this.getListVmDetails()); + BackupResponse backupResponse = backupManager.createBackupResponse(backup, this.getListVmDetails(), isCallerRootAdmin); responses.add(backupResponse); } final ListResponse response = new ListResponse<>(); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java index 70db01445edd..4fccdb176e14 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -20,6 +20,7 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.api.response.helper.BackupVolumeNameAndPath; import org.apache.cloudstack.backup.Backup; import com.cloud.serializer.Param; @@ -99,6 +100,10 @@ public class BackupResponse extends BaseResponse { @Param(description = "Backup offering name") private String backupOfferingName; + @SerializedName(ApiConstants.BACKUP_PATHS) + @Param(description = "The backups paths in the secondary storage") + private Map backupPaths; + @SerializedName(ApiConstants.ACCOUNT_ID) @Param(description = "Account id") private String accountId; @@ -386,4 +391,12 @@ public void setHostId(String hostId) { public String getHostId() { return this.hostId; } + + public Map getBackupPaths() { + return backupPaths; + } + + public void setBackupPaths(Map backupPaths) { + this.backupPaths = backupPaths; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/helper/BackupVolumeNameAndPath.java b/api/src/main/java/org/apache/cloudstack/api/response/helper/BackupVolumeNameAndPath.java new file mode 100644 index 000000000000..00a7b97564b8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/helper/BackupVolumeNameAndPath.java @@ -0,0 +1,45 @@ +// 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.response.helper; + +public class BackupVolumeNameAndPath { + private String volumeName; + private String backupPath; + + public BackupVolumeNameAndPath(String volumeName, String backupPath) { + this.volumeName = volumeName; + this.backupPath = backupPath; + } + + public String getVolumeName() { + return volumeName; + } + + public void setVolumeName(String volumeName) { + this.volumeName = volumeName; + } + + public String getBackupPath() { + return backupPath; + } + + public void setBackupPath(String backupPath) { + this.backupPath = backupPath; + } +} + diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 30fc2bbec40d..b5dfe0d8e818 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -259,7 +259,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer String getBackupNameFromVM(VirtualMachine vm); - BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails); + BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails, boolean isCallerRootAdmin); Capacity getBackupStorageUsedStats(Long zoneId); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java index acc54ccd41fa..b79ac477cee8 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java @@ -98,6 +98,12 @@ public class InternalBackupJoinVO { @Column(name = "volume_id") private long volumeId; + @Column(name = "volume_uuid") + private String volumeUuid; + + @Column(name = "volume_name") + private String volumeName; + @Column(name = "isolated") private Boolean isolated; @@ -175,6 +181,10 @@ public long getVolumeId() { return volumeId; } + public String getVolumeName() { + return volumeName; + } + public Boolean getIsolated() { return BooleanUtils.isTrue(isolated); } @@ -183,6 +193,11 @@ public Backup.CompressionStatus getCompressionStatus() { return compressionStatus; } + + public String getVolumeUuid() { + return volumeUuid; + } + @Override public String toString() { return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql index a1fbd102630b..fb8bed4478cb 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql @@ -37,11 +37,14 @@ SELECT b.id, MAX(CASE WHEN bd.name = 'current' THEN bd.value END) current, COALESCE(MAX(CASE WHEN bd.name = 'isolated' THEN bd.value END), 'false') isolated, nbpr.volume_id, + v.name as volume_name, + v.uuid as volume_uuid, nbsr.path image_store_path FROM backups b LEFT JOIN backup_details bd ON b.id = bd.backup_id LEFT JOIN backup_offering bo ON b.backup_offering_id = bo.id LEFT JOIN internal_backup_store_ref nbsr ON b.id = nbsr.backup_id LEFT JOIN internal_backup_pool_ref nbpr ON nbpr.volume_id = nbsr.volume_id +LEFT JOIN volumes v ON v.id = nbsr.volume_id WHERE bo.provider='kboss' GROUP BY b.id, nbsr.volume_id; diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index 782862df6d83..da359b3ef11e 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -80,11 +80,13 @@ import org.apache.cloudstack.api.command.user.backup.repository.UpdateBackupRepositoryCmd; import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.helper.BackupVolumeNameAndPath; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; import org.apache.cloudstack.backup.dao.BackupScheduleDao; +import org.apache.cloudstack.backup.dao.InternalBackupJoinDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; @@ -255,6 +257,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager { private DomainHelper domainHelper; @Inject ReservationDao reservationDao; + @Inject + private InternalBackupJoinDao internalBackupJoinDao; private AsyncJobDispatcher asyncJobDispatcher; private Timer backupTimer; @@ -2595,7 +2599,7 @@ Map getDetailsFromBackupDetails(Long backupId) { } @Override - public BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails) { + public BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails, boolean isCallerRootAdmin) { VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); AccountVO account = accountDao.findByIdIncludingRemoved(backup.getAccountId()); DomainVO domain = domainDao.findByIdIncludingRemoved(backup.getDomainId()); @@ -2677,6 +2681,17 @@ public BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails) response.setToCheckpointId(backup.getToCheckpointId()); } + if (offering != null && KBOSS_BACKUP_PROVIDER.equals(offering.getProvider()) && isCallerRootAdmin) { + List backupJoins = internalBackupJoinDao.listById(backup.getId()); + Map backupVolumePaths = new HashMap<>(); + + backupJoins.forEach(b -> { + backupVolumePaths.put(b.getVolumeUuid(), new BackupVolumeNameAndPath(b.getVolumeName(), b.getImageStorePath())); + }); + + response.setBackupPaths(backupVolumePaths); + } + response.setObjectName("backup"); return response; } diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerResponsesTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerResponsesTest.java new file mode 100644 index 000000000000..daac8469eab8 --- /dev/null +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerResponsesTest.java @@ -0,0 +1,287 @@ +// 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.backup; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.backup.dao.BackupDetailsDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.InternalBackupJoinDao; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class BackupManagerResponsesTest { + @Spy + @InjectMocks + private BackupManagerImpl backupManager = new BackupManagerImpl(); + + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private AccountDao accountDao; + @Mock + private DomainDao domainDao; + @Mock + private DataCenterDao dataCenterDao; + @Mock + private BackupOfferingDao backupOfferingDao; + @Mock + private VMTemplateDao vmTemplateDao; + @Mock + private ServiceOfferingDao serviceOfferingDao; + @Mock + private NetworkDao networkDao; + @Mock + private BackupDetailsDao backupDetailsDao; + @Mock + private InternalBackupJoinDao internalBackupJoinDaoMock; + + // Entities + private BackupVO backup; + private VMInstanceVO virtualMachine; + private AccountVO account; + private DomainVO domain; + private DataCenterVO zone; + private BackupOfferingVO backupOffering; + private VMTemplateVO template; + private ServiceOfferingVO serviceOffering; + private NetworkVO network; + + @Before + public void setup() { + Long vmId = 1L; + Long accountId = 2L; + Long domainId = 3L; + Long zoneId = 4L; + Long vmOfferingId = 5L; + Long backupOfferingId = 6L; + Long backupId = 7L; + Long templateId = 8L; + String templateUuid = "template-uuid1"; + String serviceOfferingUuid = "service-offering-uuid1"; + + backup = new BackupVO(); + ReflectionTestUtils.setField(backup, "id", backupId); + ReflectionTestUtils.setField(backup, "uuid", "backup-uuid"); + backup.setVmId(vmId); + backup.setAccountId(accountId); + backup.setDomainId(domainId); + backup.setZoneId(zoneId); + backup.setBackupOfferingId(backupOfferingId); + backup.setType("Full"); + backup.setBackupScheduleId(null); + + virtualMachine = new VMInstanceVO(vmId, 0L, "test-vm", "test-vm", VirtualMachine.Type.User, + 0L, Hypervisor.HypervisorType.Simulator, 0L, domainId, accountId, 0L, false); + virtualMachine.setDataCenterId(zoneId); + virtualMachine.setBackupOfferingId(vmOfferingId); + virtualMachine.setTemplateId(templateId); + + account = new AccountVO(); + account.setUuid("account-uuid"); + account.setAccountName("test-account"); + + domain = new DomainVO(); + domain.setUuid("domain-uuid"); + domain.setName("test-domain"); + + zone = new DataCenterVO(1L, "test-zone", null, null, null, null, null, null, null, null, DataCenter.NetworkType.Advanced, null, null); + zone.setUuid("zone-uuid"); + + backupOffering = new BackupOfferingVO(); + ReflectionTestUtils.setField(backupOffering, "id", backupOfferingId); + ReflectionTestUtils.setField(backupOffering, "uuid", "offering-uuid"); + backupOffering.setName("test-offering"); + + Mockito.when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(virtualMachine); + Mockito.when(accountDao.findByIdIncludingRemoved(accountId)).thenReturn(account); + Mockito.when(domainDao.findByIdIncludingRemoved(domainId)).thenReturn(domain); + Mockito.when(dataCenterDao.findByIdIncludingRemoved(zoneId)).thenReturn(zone); + Mockito.when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(backupOffering); + + template = new VMTemplateVO(); + template.setFormat(Storage.ImageFormat.QCOW2); + template.setName("template1"); + when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template); + + + serviceOffering = mock(ServiceOfferingVO.class); + when(serviceOffering.getName()).thenReturn("service-offering1"); + when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); + + network = new NetworkVO(); + network.setName("network1"); + when(networkDao.findByUuid("network-uuid1")).thenReturn(network); + + + Map details = new HashMap<>(); + details.put(ApiConstants.TEMPLATE_ID, templateUuid); + details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); + details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]"); + Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details); + } + + @Test + public void createBackupResponseTestBasicResponse() { + BackupResponse response = backupManager.createBackupResponse(backup, true, true); + + assertFullBackupResponseFields(response); + Assert.assertNull(response.getBackupPaths()); + } + + @Test + public void createBackupResponseTestKBOSSResponseAdmin() { + ReflectionTestUtils.setField(backupOffering, "provider", BackupManagerImpl.KBOSS_BACKUP_PROVIDER); + + InternalBackupJoinVO backupJoin = createBackupJoin(); + Mockito.when(internalBackupJoinDaoMock.listById(Mockito.anyLong())).thenReturn(Collections.singletonList(backupJoin)); + + BackupResponse response = backupManager.createBackupResponse(backup, true, true); + + assertFullBackupResponseFields(response); + Assert.assertFalse(response.getBackupPaths().isEmpty()); + Assert.assertTrue(response.getBackupPaths().containsKey(backupJoin.getVolumeUuid())); + Assert.assertEquals(backupJoin.getImageStorePath(), response.getBackupPaths().get(backupJoin.getVolumeUuid()).getBackupPath()); + Assert.assertEquals(backupJoin.getVolumeName(), response.getBackupPaths().get(backupJoin.getVolumeUuid()).getVolumeName()); + } + + @Test + public void createBackupResponseTestKBOSSResponseMultipleEqualNamedVolumes() { + ReflectionTestUtils.setField(backupOffering, "provider", BackupManagerImpl.KBOSS_BACKUP_PROVIDER); + + int joinsAmount = 3; + List backupJoins = getListOfBackupJoins(joinsAmount); + Mockito.when(internalBackupJoinDaoMock.listById(Mockito.anyLong())).thenReturn(backupJoins); + + BackupResponse response = backupManager.createBackupResponse(backup, true, true); + + assertFullBackupResponseFields(response); + Assert.assertFalse(response.getBackupPaths().isEmpty()); + Assert.assertEquals(joinsAmount, response.getBackupPaths().size()); + + for (InternalBackupJoinVO backupJoin : backupJoins) { + Assert.assertTrue(response.getBackupPaths().containsKey(backupJoin.getVolumeUuid())); + Assert.assertEquals(backupJoin.getImageStorePath(), response.getBackupPaths().get(backupJoin.getVolumeUuid()).getBackupPath()); + Assert.assertEquals(backupJoin.getVolumeName(), response.getBackupPaths().get(backupJoin.getVolumeUuid()).getVolumeName()); + } + } + + @Test + public void createBackupResponseTestKBOSSResponseNormalUser() { + ReflectionTestUtils.setField(backupOffering, "provider", BackupManagerImpl.KBOSS_BACKUP_PROVIDER); + + BackupResponse response = backupManager.createBackupResponse(backup, true, false); + + assertFullBackupResponseFields(response); + Assert.assertNull(response.getBackupPaths()); + } + + @Test + public void createBackupResponseTestKBOSSResponseNullOffering() { + Mockito.when(backupOfferingDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(null); + + BackupResponse response = backupManager.createBackupResponse(backup, true, true); + + assertBasicBackupResponseFields(response); + Assert.assertNull(response.getBackupPaths()); + } + + private void assertFullBackupResponseFields(BackupResponse response) { + assertBasicBackupResponseFields(response); + assertBackupResponseOfferingFields(response); + } + + private void assertBasicBackupResponseFields(BackupResponse response) { + Assert.assertEquals("backup-uuid", response.getId()); + Assert.assertEquals("test-vm", response.getVmName()); + Assert.assertEquals("account-uuid", response.getAccountId()); + Assert.assertEquals("test-account", response.getAccount()); + Assert.assertEquals("domain-uuid", response.getDomainId()); + Assert.assertEquals("test-domain", response.getDomain()); + Assert.assertEquals("zone-uuid", response.getZoneId()); + Assert.assertEquals("test-zone", response.getZone()); + Assert.assertEquals("MANUAL", response.getIntervalType()); + Assert.assertEquals("{serviceofferingid=service-offering-uuid1, isiso=false, hypervisor=Simulator, " + + "nics=[{\"networkid\":\"network-uuid1\",\"networkname\":\"network1\"}], serviceofferingname=service-offering1, " + + "templatename=template1, templateid=template-uuid1}", response.getVmDetails().toString()); + Assert.assertEquals(true, response.getVmOfferingRemoved()); + } + + private void assertBackupResponseOfferingFields(BackupResponse response) { + Assert.assertEquals("offering-uuid", response.getBackupOfferingId()); + Assert.assertEquals("test-offering", response.getBackupOffering()); + } + + private List getListOfBackupJoins(int listSize) { + List backupJoins = new java.util.ArrayList<>(); + + for (int i = 0; i < listSize; i++) { + backupJoins.add(createBackupJoin()); + } + + return backupJoins; + } + + private InternalBackupJoinVO createBackupJoin() { + String backupPath = "/backup/path"; + String volumeName = "ROOT"; + String volumeUuid = UUID.randomUUID().toString(); + InternalBackupJoinVO backupJoin = new InternalBackupJoinVO(); + ReflectionTestUtils.setField(backupJoin, "imageStorePath", backupPath); + ReflectionTestUtils.setField(backupJoin, "volumeName", volumeName); + ReflectionTestUtils.setField(backupJoin, "volumeUuid", volumeUuid); + + return backupJoin; + } +} diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 04bd03670079..b770bdd452ae 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -53,7 +53,6 @@ import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.backup.dao.BackupOfferingDao; @@ -91,7 +90,6 @@ import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.capacity.CapacityVO; import com.cloud.configuration.Resource; -import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.domain.Domain; @@ -113,7 +111,6 @@ import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; @@ -1643,95 +1640,6 @@ public void testDeleteBackupBlockedByPendingJobs() throws ResourceAllocationExce backupManager.deleteBackup(backupId, false); } - @Test - public void testNewBackupResponse() { - Long vmId = 1L; - Long accountId = 2L; - Long domainId = 3L; - Long zoneId = 4L; - Long vmOfferingId = 5L; - Long backupOfferingId = 6L; - Long backupId = 7L; - Long templateId = 8L; - String templateUuid = "template-uuid1"; - String serviceOfferingUuid = "service-offering-uuid1"; - - BackupVO backup = new BackupVO(); - ReflectionTestUtils.setField(backup, "id", backupId); - ReflectionTestUtils.setField(backup, "uuid", "backup-uuid"); - backup.setVmId(vmId); - backup.setAccountId(accountId); - backup.setDomainId(domainId); - backup.setZoneId(zoneId); - backup.setBackupOfferingId(backupOfferingId); - backup.setType("Full"); - backup.setBackupScheduleId(null); - - VMInstanceVO vm = new VMInstanceVO(vmId, 0L, "test-vm", "test-vm", VirtualMachine.Type.User, - 0L, Hypervisor.HypervisorType.Simulator, 0L, domainId, accountId, 0L, false); - vm.setDataCenterId(zoneId); - vm.setBackupOfferingId(vmOfferingId); - vm.setTemplateId(templateId); - - AccountVO account = new AccountVO(); - account.setUuid("account-uuid"); - account.setAccountName("test-account"); - - DomainVO domain = new DomainVO(); - domain.setUuid("domain-uuid"); - domain.setName("test-domain"); - - DataCenterVO zone = new DataCenterVO(1L, "test-zone", null, null, null, null, null, null, null, null, DataCenter.NetworkType.Advanced, null, null); - zone.setUuid("zone-uuid"); - - BackupOfferingVO offering = Mockito.mock(BackupOfferingVO.class); - Mockito.when(offering.getUuid()).thenReturn("offering-uuid"); - Mockito.when(offering.getName()).thenReturn("test-offering"); - - Mockito.when(vmInstanceDao.findByIdIncludingRemoved(vmId)).thenReturn(vm); - Mockito.when(accountDao.findByIdIncludingRemoved(accountId)).thenReturn(account); - Mockito.when(domainDao.findByIdIncludingRemoved(domainId)).thenReturn(domain); - Mockito.when(dataCenterDao.findByIdIncludingRemoved(zoneId)).thenReturn(zone); - Mockito.when(backupOfferingDao.findByIdIncludingRemoved(backupOfferingId)).thenReturn(offering); - - VMTemplateVO template = mock(VMTemplateVO.class); - when(template.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); - when(template.getName()).thenReturn("template1"); - when(vmTemplateDao.findByUuid(templateUuid)).thenReturn(template); - Map details = new HashMap<>(); - details.put(ApiConstants.TEMPLATE_ID, templateUuid); - - ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); - when(serviceOffering.getName()).thenReturn("service-offering1"); - when(serviceOfferingDao.findByUuid(serviceOfferingUuid)).thenReturn(serviceOffering); - details.put(ApiConstants.SERVICE_OFFERING_ID, serviceOfferingUuid); - - NetworkVO network = mock(NetworkVO.class); - when(network.getName()).thenReturn("network1"); - when(networkDao.findByUuid("network-uuid1")).thenReturn(network); - details.put(ApiConstants.NICS, "[{\"networkid\":\"network-uuid1\"}]"); - - Mockito.when(backupDetailsDao.listDetailsKeyPairs(backup.getId(), true)).thenReturn(details); - - BackupResponse response = backupManager.createBackupResponse(backup, true); - - Assert.assertEquals("backup-uuid", response.getId()); - Assert.assertEquals("test-vm", response.getVmName()); - Assert.assertEquals("account-uuid", response.getAccountId()); - Assert.assertEquals("test-account", response.getAccount()); - Assert.assertEquals("domain-uuid", response.getDomainId()); - Assert.assertEquals("test-domain", response.getDomain()); - Assert.assertEquals("zone-uuid", response.getZoneId()); - Assert.assertEquals("test-zone", response.getZone()); - Assert.assertEquals("offering-uuid", response.getBackupOfferingId()); - Assert.assertEquals("test-offering", response.getBackupOffering()); - Assert.assertEquals("MANUAL", response.getIntervalType()); - Assert.assertEquals("{serviceofferingid=service-offering-uuid1, isiso=false, hypervisor=Simulator, " + - "nics=[{\"networkid\":\"network-uuid1\",\"networkname\":\"network1\"}], serviceofferingname=service-offering1, " + - "templatename=template1, templateid=template-uuid1}", response.getVmDetails().toString()); - Assert.assertEquals(true, response.getVmOfferingRemoved()); - } - @Test public void validateAndGetDefaultBackupRetentionIfRequiredTestReturnZeroAsDefaultValue() { int retention = backupManager.validateAndGetDefaultBackupRetentionIfRequired(null, backupOfferingVOMock, null); diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 99bf2cf7aef9..47ee6fe569c8 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -489,6 +489,7 @@ "label.backup.restore": "Restore Instance backup", "label.backup.schedule.create.failed": "Failed to create Backup Schedule", "label.backuplimit": "Backup Limits", +"label.backuppaths": "Backup secondary storage paths", "label.backup.schedules": "Backup Schedules", "label.backup.storage": "Backup Storage", "label.backupstoragelimit": "Backup Storage Limits (GiB)", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 8fba779b1d12..f180518074fa 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -442,6 +442,7 @@ "label.backingup": "Salvando", "label.backup.attach.restore": "Restaurar e anexar volume de backup", "label.backuplimit": "Limite de backups", +"label.backuppaths": "Caminho dos backups no armazenamento secund\u00e1rio", "label.backup.storage": "Armazenamento de backup", "label.backupstoragelimit": "Limite de armazenamento de backup (GiB)", "label.backup.configure.schedule": "Configurar Agendamento de Backup", diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index d2aabacb10c9..ec21a94949d2 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -68,6 +68,11 @@ {{ volume.type }} - {{ volume.path }} ({{ parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GiB) +
+
+ {{ backupVolumeInfo.volumeName }} - {{ backupVolumeInfo.backupPath }} +
+
{{ dataResource.rootdisksize }} GB diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index 75bdfd4d5fa6..5b8625a2c6ce 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -494,7 +494,7 @@ export default { permission: ['listBackups'], params: { listvmdetails: 'true' }, columns: ['name', 'status', 'compressionstatus', 'validationstatus', 'size', 'virtualsize', 'virtualmachinename', 'backupofferingname', 'intervaltype', 'type', 'created', 'account', 'domain', 'zone'], - details: ['name', 'description', 'virtualmachinename', 'id', 'intervaltype', 'type', 'externalid', 'size', 'virtualsize', 'volumes', 'backupofferingname', 'zone', 'account', 'domain', 'created'], + details: ['name', 'description', 'virtualmachinename', 'id', 'intervaltype', 'type', 'externalid', 'size', 'virtualsize', 'volumes', 'backupofferingname', 'backuppaths', 'zone', 'account', 'domain', 'created'], searchFilters: ['name', 'zoneid', 'domainid', 'account', 'backupofferingid', 'status', 'type', 'virtualmachineid'], tabs: [ {