username= password= \
+ clustername= host= \
+ name= displayname= serviceofferingid= \
+ usevddk=true forceconverttopool=true \
+ convertinstancepoolid= \
+ nicnetworklist[0].nic="Network adapter 1" nicnetworklist[0].network= \
+ datadiskofferinglist[0].disk= datadiskofferinglist[0].diskOffering=
+
+Warm CBT into Linstor: `startVmwareCbtMigration` with the Linstor pool as
+`storagepoolid`, let the delta cycles run, power off the source, then
+`cutoverVmwareCbtMigration`.
+
+(The web import wizard fills the NIC/disk mappings from the discovered VM, so
+the UI is the easiest way to drive it.)
+
+## Host prerequisites
+
+For any Linstor destination the conversion host must be a LINSTOR satellite
+connected to the pool (its `hostname` must match its LINSTOR node name).
+Direct-VDDK and warm-CBT additionally need VDDK, an nbdkit VDDK plugin, and
+in-place virt-v2v (`virt-v2v-in-place` or `virt-v2v --in-place`). No extra
+qemu block-driver is needed (writes go to a local device). Prefer a diskful
+satellite as the conversion host so writes are local rather than traversing
+the DRBD replication network.
+
+## Backwards compatibility
+
+- No behavior change for existing NFS or RBD destinations; Linstor is added
+ to the same gate lists and dispatch branches.
+- The CBT-delta optimization only affects `RAW_BLOCK_DEVICE` targets; qcow2
+ and RBD keep their existing code paths.
+- One host-capability check flag was added to `CheckConvertInstanceCommand`
+ (old constructor preserved, null-tolerant).
+
+## Testing
+
+- **Unit tests**: full server + KVM-plugin suites pass, including new
+ Linstor-specific tests across the gate lists, host selection, the
+ convert/import/CBT wrappers, `listPhysicalDisks`, and the block-device
+ delta script.
+- **Data plane, real 3-node LINSTOR/DRBD lab, real VMware source**:
+ - Cold direct VDDK: a real VM's disks read over VDDK into DRBD devices +
+ in-place finalized; `qemu-img compare` against the source = identical.
+ - Warm CBT: baseline + delta + power-off + final delta + finalize;
+ integrity verified (file checksum inside the migrated volume matched the
+ source, and `qemu-img compare` of a delta-applied device against a full
+ read of the snapshot = identical).
+ - CBT-delta optimization: 144 real changed extents applied via the new
+ direct-copy script; `qemu-img compare` = images identical.
+- **Full end-to-end through CloudStack**: `importVm` (usevddk +
+ forceconverttopool) against a real vCenter produced a Stopped CloudStack VM
+ with its root and data disks as Linstor volumes on the pool, each backed by
+ a DRBD resource UpToDate on all three nodes.
+
+## Known limitations / possible follow-ups
+
+- Full-disk copies into block devices use nbdcopy when present (see above).
+ RBD URI and qcow2 file targets still use qemu-img convert; extending
+ nbdcopy to RBD would need a qemu-nbd bridge (nbdcopy is raw-device/file
+ only), which is a possible follow-up.
+- The conversion host is usually not ESXi-adjacent, so VDDK typically uses
+ network (NBD/NBDSSL) transport rather than the faster HotAdd; largely
+ inherent to the KVM conversion-host model.
+- A diskful conversion host is recommended but not enforced; a diskless
+ satellite makes every write traverse the replication network.
+- The staged path keeps its extra NFS hop (needed for running-VM clones and
+ hosts without in-place support).
+
+## Relationship to the RBD series
+
+The Linstor support is layered on the VMware-to-KVM RBD series in this same
+change set (direct VDDK-to-RBD imports, the CBT warm-migration framework,
+importVm RBD adoption, and the consolidated host-capability probe names).
+The Linstor-specific commits, in order:
+
+- KVM: allow staged VMware instance imports into Linstor primary storage
+- KVM: support direct VDDK VMware imports into Linstor primary storage
+- VMware CBT warm migration to KVM with Linstor destination storage
+- Document Linstor destination support for VMware-to-KVM imports
+- Drop unsupported -O option from virt-v2v-in-place invocations
+- KVM: stream VMware CBT block-device deltas directly, without a temp file
+- KVM: use nbdcopy for full-disk copies into Linstor block devices when available
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
index d908ef6af508..9788b6c2dd14 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
@@ -31,6 +31,13 @@
import com.cloud.utils.Pair;
public interface PrimaryDataStoreDriver extends DataStoreDriver {
+ /** Validate backend ownership before adopting an existing volume, without modifying it. */
+ default void validateVolumeForImport(StoragePool pool, String path) {
+ if (pool.getPoolType() == StoragePoolType.Linstor) {
+ throw new UnsupportedOperationException("The storage driver cannot validate LINSTOR volume imports");
+ }
+ }
+
enum QualityOfServiceState { MIGRATION, NO_MIGRATION }
String BASIC_CREATE = "basicCreate";
diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
index d3225f2fda5e..9eab15214aeb 100644
--- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
+++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
@@ -817,8 +817,21 @@ protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, fi
String vddkSupport = detailsMap.get(Host.HOST_VDDK_SUPPORT);
String vddkLibDir = detailsMap.get(Host.HOST_VDDK_LIB_DIR);
String vddkVersion = detailsMap.get(Host.HOST_VDDK_VERSION);
+ String vmwareCbtSupport = detailsMap.get(Host.HOST_VDDK_BLOCKCOPY_SUPPORT);
+ String vmwareCbtInPlaceFinalizationSupport = detailsMap.get(Host.HOST_VDDK_BLOCKCOPY_INPLACE_FINALIZATION_SUPPORT);
+ String vmwareCbtRbdSupport = detailsMap.get(Host.HOST_VDDK_BLOCKCOPY_RBD_SUPPORT);
+ String qemuImgVersion = detailsMap.get(Host.HOST_QEMU_IMG_VERSION);
+ String qemuNbdVersion = detailsMap.get(Host.HOST_QEMU_NBD_VERSION);
+ String qemuIoVersion = detailsMap.get(Host.HOST_QEMU_IO_VERSION);
+ String virtV2vInPlaceVersion = detailsMap.get(Host.HOST_VIRTV2V_INPLACE_VERSION);
+ String virtv2vInPlaceSupport = detailsMap.get(Host.HOST_VIRTV2V_INPLACE_SUPPORT);
+ String qemuImgRbdSupport = detailsMap.get(Host.HOST_QEMU_RBD_SUPPORT);
+ String vddkRbdDirectImportSupport = detailsMap.get(Host.HOST_VDDK_RBD_DIRECT_IMPORT_SUPPORT);
logger.debug("Got HOST_UEFI_ENABLE [{}] for host [{}]:", uefiEnabled, host);
- if (ObjectUtils.anyNotNull(uefiEnabled, diskOnlyVmSnapshotNvramSupport, virtv2vVersion, ovftoolVersion, vddkSupport, vddkLibDir, vddkVersion)) {
+ if (ObjectUtils.anyNotNull(uefiEnabled, diskOnlyVmSnapshotNvramSupport, virtv2vVersion, ovftoolVersion, vddkSupport, vddkLibDir, vddkVersion,
+ vmwareCbtSupport, vmwareCbtInPlaceFinalizationSupport, vmwareCbtRbdSupport, qemuImgVersion, qemuNbdVersion, qemuIoVersion, virtV2vInPlaceVersion,
+ virtv2vInPlaceSupport, qemuImgRbdSupport, vddkRbdDirectImportSupport)) {
+ _hostDao.loadDetails(host);
boolean updateNeeded = false;
if (syncBooleanHostCapability(host, Host.HOST_UEFI_ENABLE, uefiEnabled)) {
updateNeeded = true;
@@ -854,6 +867,64 @@ protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, fi
}
updateNeeded = true;
}
+ if (StringUtils.isNotBlank(vmwareCbtSupport) && !vmwareCbtSupport.equals(host.getDetails().get(Host.HOST_VDDK_BLOCKCOPY_SUPPORT))) {
+ host.getDetails().put(Host.HOST_VDDK_BLOCKCOPY_SUPPORT, vmwareCbtSupport);
+ updateNeeded = true;
+ }
+ if (StringUtils.isNotBlank(vmwareCbtInPlaceFinalizationSupport) &&
+ !vmwareCbtInPlaceFinalizationSupport.equals(host.getDetails().get(Host.HOST_VDDK_BLOCKCOPY_INPLACE_FINALIZATION_SUPPORT))) {
+ host.getDetails().put(Host.HOST_VDDK_BLOCKCOPY_INPLACE_FINALIZATION_SUPPORT, vmwareCbtInPlaceFinalizationSupport);
+ updateNeeded = true;
+ }
+ if (StringUtils.isNotBlank(vmwareCbtRbdSupport) &&
+ !vmwareCbtRbdSupport.equals(host.getDetails().get(Host.HOST_VDDK_BLOCKCOPY_RBD_SUPPORT))) {
+ host.getDetails().put(Host.HOST_VDDK_BLOCKCOPY_RBD_SUPPORT, vmwareCbtRbdSupport);
+ updateNeeded = true;
+ }
+ if (!StringUtils.defaultString(qemuImgVersion).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_QEMU_IMG_VERSION)))) {
+ if (StringUtils.isBlank(qemuImgVersion)) {
+ host.getDetails().remove(Host.HOST_QEMU_IMG_VERSION);
+ } else {
+ host.getDetails().put(Host.HOST_QEMU_IMG_VERSION, qemuImgVersion);
+ }
+ updateNeeded = true;
+ }
+ if (!StringUtils.defaultString(qemuNbdVersion).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_QEMU_NBD_VERSION)))) {
+ if (StringUtils.isBlank(qemuNbdVersion)) {
+ host.getDetails().remove(Host.HOST_QEMU_NBD_VERSION);
+ } else {
+ host.getDetails().put(Host.HOST_QEMU_NBD_VERSION, qemuNbdVersion);
+ }
+ updateNeeded = true;
+ }
+ if (!StringUtils.defaultString(qemuIoVersion).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_QEMU_IO_VERSION)))) {
+ if (StringUtils.isBlank(qemuIoVersion)) {
+ host.getDetails().remove(Host.HOST_QEMU_IO_VERSION);
+ } else {
+ host.getDetails().put(Host.HOST_QEMU_IO_VERSION, qemuIoVersion);
+ }
+ updateNeeded = true;
+ }
+ if (!StringUtils.defaultString(virtV2vInPlaceVersion).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_VIRTV2V_INPLACE_VERSION)))) {
+ if (StringUtils.isBlank(virtV2vInPlaceVersion)) {
+ host.getDetails().remove(Host.HOST_VIRTV2V_INPLACE_VERSION);
+ } else {
+ host.getDetails().put(Host.HOST_VIRTV2V_INPLACE_VERSION, virtV2vInPlaceVersion);
+ }
+ updateNeeded = true;
+ }
+ if (StringUtils.isNotBlank(virtv2vInPlaceSupport) && !virtv2vInPlaceSupport.equals(host.getDetails().get(Host.HOST_VIRTV2V_INPLACE_SUPPORT))) {
+ host.getDetails().put(Host.HOST_VIRTV2V_INPLACE_SUPPORT, virtv2vInPlaceSupport);
+ updateNeeded = true;
+ }
+ if (StringUtils.isNotBlank(qemuImgRbdSupport) && !qemuImgRbdSupport.equals(host.getDetails().get(Host.HOST_QEMU_RBD_SUPPORT))) {
+ host.getDetails().put(Host.HOST_QEMU_RBD_SUPPORT, qemuImgRbdSupport);
+ updateNeeded = true;
+ }
+ if (StringUtils.isNotBlank(vddkRbdDirectImportSupport) && !vddkRbdDirectImportSupport.equals(host.getDetails().get(Host.HOST_VDDK_RBD_DIRECT_IMPORT_SUPPORT))) {
+ host.getDetails().put(Host.HOST_VDDK_RBD_DIRECT_IMPORT_SUPPORT, vddkRbdDirectImportSupport);
+ updateNeeded = true;
+ }
if (updateNeeded) {
_hostDao.saveDetails(host);
}
diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
index 2fad96ec1da2..c4fadc552857 100644
--- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
+++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java
@@ -1335,6 +1335,20 @@ private ImageFormat getSupportedImageFormatForCluster(HypervisorType hyperType)
}
}
+ /**
+ * Returns the image format for a volume taking the underlying storage pool type into account.
+ * On KVM the default cluster format is QCOW2, but block-based pools such as RBD (Ceph) and
+ * Linstor (DRBD) store volumes as RAW images. This is relevant when importing/adopting an
+ * existing volume so the recorded format matches what is actually on the pool.
+ */
+ protected ImageFormat getSupportedImageFormatForCluster(HypervisorType hyperType, Storage.StoragePoolType poolType) {
+ if (hyperType == HypervisorType.KVM
+ && (poolType == Storage.StoragePoolType.RBD || poolType == Storage.StoragePoolType.Linstor)) {
+ return ImageFormat.RAW;
+ }
+ return getSupportedImageFormatForCluster(hyperType);
+ }
+
private boolean isSupportedImageFormatForCluster(VolumeInfo volume, HypervisorType rootDiskHyperType) {
ImageFormat volumeFormat = volume.getFormat();
if (rootDiskHyperType == HypervisorType.Hyperv) {
@@ -2665,7 +2679,7 @@ public DiskProfile importVolume(Type type, String name, DiskOffering offering, L
vol.setDisplayVolume(userVm.isDisplayVm());
}
- vol.setFormat(getSupportedImageFormatForCluster(hypervisorType));
+ vol.setFormat(getSupportedImageFormatForCluster(hypervisorType, poolType));
vol.setPoolId(poolId);
vol.setPoolType(poolType);
vol.setPath(path);
@@ -2709,7 +2723,7 @@ public DiskProfile updateImportedVolume(Type type, DiskOffering offering, Virtua
vol.setDisplayVolume(userVm.isDisplayVm());
}
- vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType()));
+ vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType(), poolType));
vol.setPoolId(poolId);
vol.setPoolType(poolType);
vol.setPath(path);
diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java
index d61caf16ae5d..41f71b1b4773 100644
--- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java
+++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java
@@ -178,6 +178,31 @@ public void testCheckAndUpdateVolumeAccountResourceCountLessSize() {
runCheckAndUpdateVolumeAccountResourceCountTest(20L, 10L);
}
+ @Test
+ public void testGetSupportedImageFormatForClusterKvmRbdIsRaw() {
+ Assert.assertEquals(Storage.ImageFormat.RAW,
+ volumeOrchestrator.getSupportedImageFormatForCluster(Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.RBD));
+ // Linstor (DRBD) volumes are also raw block devices.
+ Assert.assertEquals(Storage.ImageFormat.RAW,
+ volumeOrchestrator.getSupportedImageFormatForCluster(Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.Linstor));
+ }
+
+ @Test
+ public void testGetSupportedImageFormatForClusterKvmNonRbdIsQcow2() {
+ Assert.assertEquals(Storage.ImageFormat.QCOW2,
+ volumeOrchestrator.getSupportedImageFormatForCluster(Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.NetworkFilesystem));
+ Assert.assertEquals(Storage.ImageFormat.QCOW2,
+ volumeOrchestrator.getSupportedImageFormatForCluster(Hypervisor.HypervisorType.KVM, Storage.StoragePoolType.Filesystem));
+ }
+
+ @Test
+ public void testGetSupportedImageFormatForClusterNonKvmIgnoresPoolType() {
+ Assert.assertEquals(Storage.ImageFormat.VHD,
+ volumeOrchestrator.getSupportedImageFormatForCluster(Hypervisor.HypervisorType.XenServer, Storage.StoragePoolType.RBD));
+ Assert.assertEquals(Storage.ImageFormat.OVA,
+ volumeOrchestrator.getSupportedImageFormatForCluster(Hypervisor.HypervisorType.VMware, Storage.StoragePoolType.RBD));
+ }
+
@Test
public void testGrantVolumeAccessToHostIfNeededDriverNoNeed() {
PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class);
diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml
index 6e078d7ec9f8..a92845184e95 100644
--- a/engine/schema/pom.xml
+++ b/engine/schema/pom.xml
@@ -88,8 +88,15 @@
diff --git a/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationCycleVO.java b/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationCycleVO.java
new file mode 100644
index 000000000000..78e492a434d9
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationCycleVO.java
@@ -0,0 +1,171 @@
+// 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 com.cloud.vm;
+
+import org.apache.cloudstack.vm.VmwareCbtMigrationCycle;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "vmware_cbt_migration_cycle")
+public class VmwareCbtMigrationCycleVO implements VmwareCbtMigrationCycle {
+
+ public VmwareCbtMigrationCycleVO() {
+ uuid = UUID.randomUUID().toString();
+ }
+
+ public VmwareCbtMigrationCycleVO(long migrationId, int cycleNumber) {
+ this();
+ this.migrationId = migrationId;
+ this.cycleNumber = cycleNumber;
+ this.state = State.Created;
+ }
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "migration_id")
+ private long migrationId;
+
+ @Column(name = "cycle_number")
+ private int cycleNumber;
+
+ @Column(name = "snapshot_moref")
+ private String snapshotMor;
+
+ @Column(name = "changed_bytes")
+ private Long changedBytes;
+
+ @Column(name = "dirty_rate")
+ private Long dirtyRate;
+
+ @Column(name = "duration")
+ private Long duration;
+
+ @Column(name = "state")
+ @Enumerated(value = EnumType.STRING)
+ private State state;
+
+ @Column(name = "description")
+ private String description;
+
+ @Column(name = "created")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date created;
+
+ @Column(name = "updated")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date updated;
+
+ @Column(name = "removed")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ public long getMigrationId() {
+ return migrationId;
+ }
+
+ public int getCycleNumber() {
+ return cycleNumber;
+ }
+
+ public String getSnapshotMor() {
+ return snapshotMor;
+ }
+
+ public void setSnapshotMor(String snapshotMor) {
+ this.snapshotMor = snapshotMor;
+ }
+
+ public Long getChangedBytes() {
+ return changedBytes;
+ }
+
+ public void setChangedBytes(Long changedBytes) {
+ this.changedBytes = changedBytes;
+ }
+
+ public Long getDirtyRate() {
+ return dirtyRate;
+ }
+
+ public void setDirtyRate(Long dirtyRate) {
+ this.dirtyRate = dirtyRate;
+ }
+
+ public Long getDuration() {
+ return duration;
+ }
+
+ public void setDuration(Long duration) {
+ this.duration = duration;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public Date getUpdated() {
+ return updated;
+ }
+
+ public void setUpdated(Date updated) {
+ this.updated = updated;
+ }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationDiskVO.java b/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationDiskVO.java
new file mode 100644
index 000000000000..62069cfbcd97
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationDiskVO.java
@@ -0,0 +1,197 @@
+// 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 com.cloud.vm;
+
+import org.apache.cloudstack.vm.VmwareCbtMigrationDisk;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "vmware_cbt_migration_disk")
+public class VmwareCbtMigrationDiskVO implements VmwareCbtMigrationDisk {
+
+ public VmwareCbtMigrationDiskVO() {
+ uuid = UUID.randomUUID().toString();
+ }
+
+ public VmwareCbtMigrationDiskVO(long migrationId, String sourceDiskId, Integer sourceDiskDeviceKey,
+ String sourceDiskPath, String datastoreName, Long capacityBytes) {
+ this();
+ this.migrationId = migrationId;
+ this.sourceDiskId = sourceDiskId;
+ this.sourceDiskDeviceKey = sourceDiskDeviceKey;
+ this.sourceDiskPath = sourceDiskPath;
+ this.datastoreName = datastoreName;
+ this.capacityBytes = capacityBytes;
+ this.state = State.Created;
+ }
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "migration_id")
+ private long migrationId;
+
+ @Column(name = "source_disk_id")
+ private String sourceDiskId;
+
+ @Column(name = "source_disk_device_key")
+ private Integer sourceDiskDeviceKey;
+
+ @Column(name = "source_disk_path")
+ private String sourceDiskPath;
+
+ @Column(name = "datastore_name")
+ private String datastoreName;
+
+ @Column(name = "capacity_bytes")
+ private Long capacityBytes;
+
+ @Column(name = "target_path")
+ private String targetPath;
+
+ @Column(name = "target_format")
+ private String targetFormat;
+
+ @Column(name = "change_id")
+ private String changeId;
+
+ @Column(name = "snapshot_moref")
+ private String snapshotMor;
+
+ @Column(name = "state")
+ @Enumerated(value = EnumType.STRING)
+ private State state;
+
+ @Column(name = "created")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date created;
+
+ @Column(name = "updated")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date updated;
+
+ @Column(name = "removed")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ public long getMigrationId() {
+ return migrationId;
+ }
+
+ public String getSourceDiskId() {
+ return sourceDiskId;
+ }
+
+ public Integer getSourceDiskDeviceKey() {
+ return sourceDiskDeviceKey;
+ }
+
+ public void setSourceDiskDeviceKey(Integer sourceDiskDeviceKey) {
+ this.sourceDiskDeviceKey = sourceDiskDeviceKey;
+ }
+
+ public String getSourceDiskPath() {
+ return sourceDiskPath;
+ }
+
+ public String getDatastoreName() {
+ return datastoreName;
+ }
+
+ public Long getCapacityBytes() {
+ return capacityBytes;
+ }
+
+ public String getTargetPath() {
+ return targetPath;
+ }
+
+ public void setTargetPath(String targetPath) {
+ this.targetPath = targetPath;
+ }
+
+ public String getTargetFormat() {
+ return targetFormat;
+ }
+
+ public void setTargetFormat(String targetFormat) {
+ this.targetFormat = targetFormat;
+ }
+
+ public String getChangeId() {
+ return changeId;
+ }
+
+ public void setChangeId(String changeId) {
+ this.changeId = changeId;
+ }
+
+ public String getSnapshotMor() {
+ return snapshotMor;
+ }
+
+ public void setSnapshotMor(String snapshotMor) {
+ this.snapshotMor = snapshotMor;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public Date getUpdated() {
+ return updated;
+ }
+
+ public void setUpdated(Date updated) {
+ this.updated = updated;
+ }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationVO.java b/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationVO.java
new file mode 100644
index 000000000000..24ad5b1dd552
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/VmwareCbtMigrationVO.java
@@ -0,0 +1,465 @@
+// 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 com.cloud.vm;
+
+import org.apache.cloudstack.vm.VmwareCbtMigration;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+import java.util.UUID;
+
+import com.cloud.utils.db.Encrypt;
+
+@Entity
+@Table(name = "vmware_cbt_migration")
+public class VmwareCbtMigrationVO implements VmwareCbtMigration {
+
+ public VmwareCbtMigrationVO() {
+ uuid = UUID.randomUUID().toString();
+ }
+
+ public VmwareCbtMigrationVO(long zoneId, long accountId, long userId, long destinationClusterId,
+ String displayName, String vcenter, String datacenter, String sourceHost,
+ String sourceCluster, String sourceVmName) {
+ this();
+ this.zoneId = zoneId;
+ this.accountId = accountId;
+ this.userId = userId;
+ this.destinationClusterId = destinationClusterId;
+ this.displayName = displayName;
+ this.vcenter = vcenter;
+ this.datacenter = datacenter;
+ this.sourceHost = sourceHost;
+ this.sourceCluster = sourceCluster;
+ this.sourceVmName = sourceVmName;
+ this.state = State.Created;
+ this.currentStep = "Created";
+ }
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "zone_id")
+ private long zoneId;
+
+ @Column(name = "account_id")
+ private long accountId;
+
+ @Column(name = "user_id")
+ private long userId;
+
+ @Column(name = "vm_id")
+ private Long vmId;
+
+ @Column(name = "existing_vcenter_id")
+ private Long existingVcenterId;
+
+ @Column(name = "source_username")
+ private String sourceUsername;
+
+ @Encrypt
+ @Column(name = "source_password")
+ private String sourcePassword;
+
+ @Column(name = "destination_cluster_id")
+ private long destinationClusterId;
+
+ @Column(name = "convert_host_id")
+ private Long convertHostId;
+
+ @Column(name = "storage_pool_id")
+ private Long storagePoolId;
+
+ @Column(name = "display_name")
+ private String displayName;
+
+ @Column(name = "host_name")
+ private String hostName;
+
+ @Column(name = "template_id")
+ private Long templateId;
+
+ @Column(name = "service_offering_id")
+ private Long serviceOfferingId;
+
+ @Column(name = "guest_os_id")
+ private Long guestOsId;
+
+ @Column(name = "data_disk_offering_map")
+ private String dataDiskOfferingMap;
+
+ @Column(name = "nic_network_map")
+ private String nicNetworkMap;
+
+ @Column(name = "nic_ip_address_map")
+ private String nicIpAddressMap;
+
+ @Column(name = "import_details")
+ private String importDetails;
+
+ @Column(name = "forced")
+ private boolean forced;
+
+ @Column(name = "vcenter")
+ private String vcenter;
+
+ @Column(name = "datacenter")
+ private String datacenter;
+
+ @Column(name = "source_host")
+ private String sourceHost;
+
+ @Column(name = "source_cluster")
+ private String sourceCluster;
+
+ @Column(name = "source_vm_name")
+ private String sourceVmName;
+
+ @Column(name = "vddk_lib_dir")
+ private String vddkLibDir;
+
+ @Column(name = "vddk_transports")
+ private String vddkTransports;
+
+ @Column(name = "vddk_thumbprint")
+ private String vddkThumbprint;
+
+ @Column(name = "state")
+ @Enumerated(value = EnumType.STRING)
+ private State state;
+
+ @Column(name = "current_step")
+ private String currentStep;
+
+ @Column(name = "last_error")
+ private String lastError;
+
+ @Column(name = "completed_cycles")
+ private int completedCycles;
+
+ @Column(name = "quiet_cycles")
+ private int quietCycles;
+
+ @Column(name = "total_changed_bytes")
+ private long totalChangedBytes;
+
+ @Column(name = "last_changed_bytes")
+ private Long lastChangedBytes;
+
+ @Column(name = "last_dirty_rate")
+ private Long lastDirtyRate;
+
+ @Column(name = "created")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date created;
+
+ @Column(name = "updated")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date updated;
+
+ @Column(name = "removed")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date removed;
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ public long getZoneId() {
+ return zoneId;
+ }
+
+ public long getAccountId() {
+ return accountId;
+ }
+
+ public long getUserId() {
+ return userId;
+ }
+
+ public Long getVmId() {
+ return vmId;
+ }
+
+ public void setVmId(Long vmId) {
+ this.vmId = vmId;
+ }
+
+ public Long getExistingVcenterId() {
+ return existingVcenterId;
+ }
+
+ public void setExistingVcenterId(Long existingVcenterId) {
+ this.existingVcenterId = existingVcenterId;
+ }
+
+ public String getSourceUsername() {
+ return sourceUsername;
+ }
+
+ public void setSourceUsername(String sourceUsername) {
+ this.sourceUsername = sourceUsername;
+ }
+
+ public String getSourcePassword() {
+ return sourcePassword;
+ }
+
+ public void setSourcePassword(String sourcePassword) {
+ this.sourcePassword = sourcePassword;
+ }
+
+ public long getDestinationClusterId() {
+ return destinationClusterId;
+ }
+
+ public Long getConvertHostId() {
+ return convertHostId;
+ }
+
+ public void setConvertHostId(Long convertHostId) {
+ this.convertHostId = convertHostId;
+ }
+
+ public Long getStoragePoolId() {
+ return storagePoolId;
+ }
+
+ public void setStoragePoolId(Long storagePoolId) {
+ this.storagePoolId = storagePoolId;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public String getHostName() {
+ return hostName;
+ }
+
+ public void setHostName(String hostName) {
+ this.hostName = hostName;
+ }
+
+ public Long getTemplateId() {
+ return templateId;
+ }
+
+ public void setTemplateId(Long templateId) {
+ this.templateId = templateId;
+ }
+
+ public Long getServiceOfferingId() {
+ return serviceOfferingId;
+ }
+
+ public void setServiceOfferingId(Long serviceOfferingId) {
+ this.serviceOfferingId = serviceOfferingId;
+ }
+
+ public Long getGuestOsId() {
+ return guestOsId;
+ }
+
+ public void setGuestOsId(Long guestOsId) {
+ this.guestOsId = guestOsId;
+ }
+
+ public String getDataDiskOfferingMap() {
+ return dataDiskOfferingMap;
+ }
+
+ public void setDataDiskOfferingMap(String dataDiskOfferingMap) {
+ this.dataDiskOfferingMap = dataDiskOfferingMap;
+ }
+
+ public String getNicNetworkMap() {
+ return nicNetworkMap;
+ }
+
+ public void setNicNetworkMap(String nicNetworkMap) {
+ this.nicNetworkMap = nicNetworkMap;
+ }
+
+ public String getNicIpAddressMap() {
+ return nicIpAddressMap;
+ }
+
+ public void setNicIpAddressMap(String nicIpAddressMap) {
+ this.nicIpAddressMap = nicIpAddressMap;
+ }
+
+ public String getImportDetails() {
+ return importDetails;
+ }
+
+ public void setImportDetails(String importDetails) {
+ this.importDetails = importDetails;
+ }
+
+ public boolean isForced() {
+ return forced;
+ }
+
+ public void setForced(boolean forced) {
+ this.forced = forced;
+ }
+
+ public String getVcenter() {
+ return vcenter;
+ }
+
+ public String getDatacenter() {
+ return datacenter;
+ }
+
+ public String getSourceHost() {
+ return sourceHost;
+ }
+
+ public String getSourceCluster() {
+ return sourceCluster;
+ }
+
+ public String getSourceVmName() {
+ return sourceVmName;
+ }
+
+ public String getVddkLibDir() {
+ return vddkLibDir;
+ }
+
+ public void setVddkLibDir(String vddkLibDir) {
+ this.vddkLibDir = vddkLibDir;
+ }
+
+ public String getVddkTransports() {
+ return vddkTransports;
+ }
+
+ public void setVddkTransports(String vddkTransports) {
+ this.vddkTransports = vddkTransports;
+ }
+
+ public String getVddkThumbprint() {
+ return vddkThumbprint;
+ }
+
+ public void setVddkThumbprint(String vddkThumbprint) {
+ this.vddkThumbprint = vddkThumbprint;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
+
+ public String getCurrentStep() {
+ return currentStep;
+ }
+
+ public void setCurrentStep(String currentStep) {
+ this.currentStep = currentStep;
+ }
+
+ public String getLastError() {
+ return lastError;
+ }
+
+ public void setLastError(String lastError) {
+ this.lastError = lastError;
+ }
+
+ public int getCompletedCycles() {
+ return completedCycles;
+ }
+
+ public void setCompletedCycles(int completedCycles) {
+ this.completedCycles = completedCycles;
+ }
+
+ public int getQuietCycles() {
+ return quietCycles;
+ }
+
+ public void setQuietCycles(int quietCycles) {
+ this.quietCycles = quietCycles;
+ }
+
+ public long getTotalChangedBytes() {
+ return totalChangedBytes;
+ }
+
+ public void setTotalChangedBytes(long totalChangedBytes) {
+ this.totalChangedBytes = totalChangedBytes;
+ }
+
+ public Long getLastChangedBytes() {
+ return lastChangedBytes;
+ }
+
+ public void setLastChangedBytes(Long lastChangedBytes) {
+ this.lastChangedBytes = lastChangedBytes;
+ }
+
+ public Long getLastDirtyRate() {
+ return lastDirtyRate;
+ }
+
+ public void setLastDirtyRate(Long lastDirtyRate) {
+ this.lastDirtyRate = lastDirtyRate;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public Date getUpdated() {
+ return updated;
+ }
+
+ public void setUpdated(Date updated) {
+ this.updated = updated;
+ }
+
+ public Date getRemoved() {
+ return removed;
+ }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDao.java
new file mode 100644
index 000000000000..e7ce4deedfcf
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDao.java
@@ -0,0 +1,30 @@
+// 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 com.cloud.vm.dao;
+
+import java.util.List;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.vm.bootgroup.InstanceBootGroupVO;
+
+public interface InstanceBootGroupDao extends GenericDao {
+
+ List listByAccountId(long accountId);
+
+ boolean isNameInUse(long accountId, String name);
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDaoImpl.java
new file mode 100644
index 000000000000..3a80e7eabeed
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDaoImpl.java
@@ -0,0 +1,60 @@
+// 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 com.cloud.vm.dao;
+
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.vm.bootgroup.InstanceBootGroupVO;
+
+@Component
+public class InstanceBootGroupDaoImpl extends GenericDaoBase implements InstanceBootGroupDao {
+
+ private final SearchBuilder accountSearch;
+ private final SearchBuilder accountNameSearch;
+
+ public InstanceBootGroupDaoImpl() {
+ accountSearch = createSearchBuilder();
+ accountSearch.and("accountId", accountSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+ accountSearch.done();
+
+ accountNameSearch = createSearchBuilder();
+ accountNameSearch.and("accountId", accountNameSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
+ accountNameSearch.and("name", accountNameSearch.entity().getName(), SearchCriteria.Op.EQ);
+ accountNameSearch.done();
+ }
+
+ @Override
+ public List listByAccountId(long accountId) {
+ SearchCriteria sc = accountSearch.create();
+ sc.setParameters("accountId", accountId);
+ return listBy(sc);
+ }
+
+ @Override
+ public boolean isNameInUse(long accountId, String name) {
+ SearchCriteria sc = accountNameSearch.create();
+ sc.setParameters("accountId", accountId);
+ sc.setParameters("name", name);
+ return !listBy(sc).isEmpty();
+ }
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDetailsDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDetailsDao.java
new file mode 100644
index 000000000000..6b1c241cddb0
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDetailsDao.java
@@ -0,0 +1,28 @@
+// 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 com.cloud.vm.dao;
+
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
+import org.apache.cloudstack.vm.bootgroup.InstanceBootGroupDetailsVO;
+
+public interface InstanceBootGroupDetailsDao extends ResourceDetailsDao {
+
+ String getDetail(long bootGroupId, String name);
+
+ void setDetail(long bootGroupId, String name, String value);
+}
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDetailsDaoImpl.java
new file mode 100644
index 000000000000..065b09571a96
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/InstanceBootGroupDetailsDaoImpl.java
@@ -0,0 +1,71 @@
+// 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 com.cloud.vm.dao;
+
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
+import org.apache.cloudstack.vm.bootgroup.InstanceBootGroupDetailsVO;
+import org.springframework.stereotype.Component;
+
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+
+@Component
+public class InstanceBootGroupDetailsDaoImpl extends ResourceDetailsDaoBase implements InstanceBootGroupDetailsDao {
+
+ private final SearchBuilder bootGroupSearch;
+ private final SearchBuilder bootGroupNameSearch;
+
+ public InstanceBootGroupDetailsDaoImpl() {
+ bootGroupSearch = createSearchBuilder();
+ bootGroupSearch.and("bootGroupId", bootGroupSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
+ bootGroupSearch.done();
+
+ bootGroupNameSearch = createSearchBuilder();
+ bootGroupNameSearch.and("bootGroupId", bootGroupNameSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
+ bootGroupNameSearch.and("name", bootGroupNameSearch.entity().getName(), SearchCriteria.Op.EQ);
+ bootGroupNameSearch.done();
+ }
+
+ @Override
+ public void addDetail(long resourceId, String key, String value, boolean display) {
+ super.addDetail(new InstanceBootGroupDetailsVO(resourceId, key, value, display));
+ }
+
+ @Override
+ public String getDetail(long bootGroupId, String name) {
+ SearchCriteria