From 95489b8bddf30aae041ee229f04271fd0d4f5eac Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 13 May 2025 17:47:46 +0530 Subject: [PATCH 01/19] Direct agents rebalance improvements with multiple management server nodes (#10674) Sometimes hypervisor hosts (direct agents) stuck with Disconnect state during agent rebalancing activity across multiple management server nodes. This issue was noticed during frequent restart of the management server nodes in the cluster. When there are multiple management server nodes in a cluster, if one or more nodes are shutdown/start/restart, CloudStack will rebalance the hosts among the remaining nodes or move the nodes to the newly joined management server nodes. During the rebalancing period multiple operations could happen including: - DirectAgentScan at interval of configured direct.agent.scan.interval - AgentRebalanceScan to identify and schedule rebalance agents - TransferAgentScan to transfer the host from original owner to future owner **Current Rebalance behavior** 1. For hosts that have AgentAttache && not forForward but in Disconnect state, CloudStack simply ignore these hosts without trying to ping again or update the status of the host. 2. For hosts that have AgentAttache && forForward, CloudStack removes the agent but still try to loadDirectlyConnectedHost. **Improved Rebalance behavior** During DirectAgentScan: scanDirectAgentToLoad(), identify hosts that for self-managed hosts that are in Disconnect state (disconnected after pingtimeout). 1. For hosts that have AgentAttache and is forForward, CloudStack should remove the agent 2. For hosts that have AgentAttache and is not forForward but in Disconnect state, CloudStack should try to investigate and update the status to Up if host is pingable. 3. For hosts that don't have AgentAttache, CloudStack should try to loadDirectlyConnectedHost. --- .../manager/ClusteredAgentManagerImpl.java | 56 ++++--- .../ClusteredAgentManagerImplTest.java | 150 ++++++++++++++++++ 2 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 engine/orchestration/src/test/java/com/cloud/agent/manager/ClusteredAgentManagerImplTest.java diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java index dd3666e5561..724b824942b 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java @@ -191,7 +191,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust scanDirectAgentToLoad(); } - private void scanDirectAgentToLoad() { + protected void scanDirectAgentToLoad() { logger.trace("Begin scanning directly connected hosts"); // for agents that are self-managed, threshold to be considered as disconnected after pingtimeout @@ -212,11 +212,21 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust logger.info("{} is detected down, but we have a forward attache running, disconnect this one before launching the host", host); removeAgent(agentattache, Status.Disconnected); } else { - continue; + logger.debug("Host {} status is {} but has an AgentAttache which is not forForward, try to load directly", host, host.getStatus()); + Status hostStatus = investigate(agentattache); + if (Status.Up == hostStatus) { + /* Got ping response from host, bring it back */ + logger.info("After investigation, Agent for host {} is determined to be up and running", host); + agentStatusTransitTo(host, Event.Ping, _nodeId); + } else { + logger.debug("After investigation, AgentAttache is not null but host status is {}, try to load directly {}", hostStatus, host); + loadDirectlyConnectedHost(host, false); + } } + } else { + logger.debug("AgentAttache is null, loading directly connected {}", host); + loadDirectlyConnectedHost(host, false); } - logger.debug("Loading directly connected {}", host); - loadDirectlyConnectedHost(host, false); } catch (final Throwable e) { logger.warn(" can not load directly connected {} due to ", host, e); } @@ -362,20 +372,20 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust return; } if (!result) { - throw new CloudRuntimeException("Failed to propagate agent change request event:" + Event.ShutdownRequested + " to host:" + hostId); + throw new CloudRuntimeException(String.format("Failed to propagate agent change request event: %s to host: %s", Event.ShutdownRequested, hostId)); } } public void notifyNodesInCluster(final AgentAttache attache) { logger.debug("Notifying other nodes of to disconnect"); - final Command[] cmds = new Command[] {new ChangeAgentCommand(attache.getId(), Event.AgentDisconnected)}; + final Command[] cmds = new Command[]{new ChangeAgentCommand(attache.getId(), Event.AgentDisconnected)}; _clusterMgr.broadcast(attache.getId(), _gson.toJson(cmds)); } // notifies MS peers to schedule a host scan task immediately, triggered during addHost operation public void notifyNodesInClusterToScheduleHostScanTask() { logger.debug("Notifying other MS nodes to run host scan task"); - final Command[] cmds = new Command[] {new ScheduleHostScanTaskCommand()}; + final Command[] cmds = new Command[]{new ScheduleHostScanTaskCommand()}; _clusterMgr.broadcast(0, _gson.toJson(cmds)); } @@ -416,7 +426,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust } try { logD(bytes, "Routing to peer"); - Link.write(ch, new ByteBuffer[] {ByteBuffer.wrap(bytes)}, sslEngine); + Link.write(ch, new ByteBuffer[]{ByteBuffer.wrap(bytes)}, sslEngine); return true; } catch (final IOException e) { try { @@ -625,7 +635,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust } final Request req = Request.parse(data); final Command[] cmds = req.getCommands(); - final CancelCommand cancel = (CancelCommand)cmds[0]; + final CancelCommand cancel = (CancelCommand) cmds[0]; logD(data, "Cancel request received"); agent.cancel(cancel.getSequence()); final Long current = agent._currentSequence; @@ -652,7 +662,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust return; } else { if (agent instanceof Routable) { - final Routable cluster = (Routable)agent; + final Routable cluster = (Routable) agent; cluster.routeToAgent(data); } else { agent.send(Request.parse(data)); @@ -669,7 +679,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust if (mgmtId != -1 && mgmtId != _nodeId) { routeToPeer(Long.toString(mgmtId), data); if (Request.requiresSequentialExecution(data)) { - final AgentAttache attache = (AgentAttache)link.attachment(); + final AgentAttache attache = (AgentAttache) link.attachment(); if (attache != null) { attache.sendNext(Request.getSequence(data)); } @@ -933,7 +943,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust if (_agentToTransferIds.size() > 0) { logger.debug("Found {} agents to transfer", _agentToTransferIds.size()); // for (Long hostId : _agentToTransferIds) { - for (final Iterator iterator = _agentToTransferIds.iterator(); iterator.hasNext();) { + for (final Iterator iterator = _agentToTransferIds.iterator(); iterator.hasNext(); ) { final Long hostId = iterator.next(); final AgentAttache attache = findAttache(hostId); @@ -1074,7 +1084,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust return; } - final ClusteredAgentAttache forwardAttache = (ClusteredAgentAttache)attache; + final ClusteredAgentAttache forwardAttache = (ClusteredAgentAttache) attache; if (success) { @@ -1125,10 +1135,10 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust } synchronized (_agents) { - final ClusteredDirectAgentAttache attache = (ClusteredDirectAgentAttache)_agents.get(hostId); + final ClusteredDirectAgentAttache attache = (ClusteredDirectAgentAttache) _agents.get(hostId); if (attache != null && attache.getQueueSize() == 0 && attache.getNonRecurringListenersSize() == 0) { handleDisconnectWithoutInvestigation(attache, Event.StartAgentRebalance, true, true); - final ClusteredAgentAttache forwardAttache = (ClusteredAgentAttache)createAttache(host); + final ClusteredAgentAttache forwardAttache = (ClusteredAgentAttache) createAttache(host); if (forwardAttache == null) { logger.warn("Unable to create a forward attache for the host {} as a part of rebalance process", host); return false; @@ -1232,7 +1242,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust } if (cmds.length == 1 && cmds[0] instanceof ChangeAgentCommand) { // intercepted - final ChangeAgentCommand cmd = (ChangeAgentCommand)cmds[0]; + final ChangeAgentCommand cmd = (ChangeAgentCommand) cmds[0]; logger.debug("Intercepting command for agent change: agent {} event: {}", cmd.getAgentId(), cmd.getEvent()); boolean result = false; @@ -1249,7 +1259,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust answers[0] = new ChangeAgentAnswer(cmd, result); return _gson.toJson(answers); } else if (cmds.length == 1 && cmds[0] instanceof TransferAgentCommand) { - final TransferAgentCommand cmd = (TransferAgentCommand)cmds[0]; + final TransferAgentCommand cmd = (TransferAgentCommand) cmds[0]; logger.debug("Intercepting command for agent rebalancing: agent {} event: {}", cmd.getAgentId(), cmd.getEvent()); boolean result = false; @@ -1268,7 +1278,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust answers[0] = new Answer(cmd, result, null); return _gson.toJson(answers); } else if (cmds.length == 1 && cmds[0] instanceof PropagateResourceEventCommand) { - final PropagateResourceEventCommand cmd = (PropagateResourceEventCommand)cmds[0]; + final PropagateResourceEventCommand cmd = (PropagateResourceEventCommand) cmds[0]; logger.debug("Intercepting command to propagate event {} for host {} ({})", () -> cmd.getEvent().name(), cmd::getHostId, () -> _hostDao.findById(cmd.getHostId())); @@ -1285,10 +1295,10 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust answers[0] = new Answer(cmd, result, null); return _gson.toJson(answers); } else if (cmds.length == 1 && cmds[0] instanceof ScheduleHostScanTaskCommand) { - final ScheduleHostScanTaskCommand cmd = (ScheduleHostScanTaskCommand)cmds[0]; + final ScheduleHostScanTaskCommand cmd = (ScheduleHostScanTaskCommand) cmds[0]; return handleScheduleHostScanTaskCommand(cmd); } else if (cmds.length == 1 && cmds[0] instanceof BaseShutdownManagementServerHostCommand) { - final BaseShutdownManagementServerHostCommand cmd = (BaseShutdownManagementServerHostCommand)cmds[0]; + final BaseShutdownManagementServerHostCommand cmd = (BaseShutdownManagementServerHostCommand) cmds[0]; return handleShutdownManagementServerHostCommand(cmd); } @@ -1323,7 +1333,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust try { shutdownManager.prepareForShutdown(); return "Successfully prepared for shutdown"; - } catch(CloudRuntimeException e) { + } catch (CloudRuntimeException e) { return e.getMessage(); } } @@ -1332,7 +1342,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust try { shutdownManager.triggerShutdown(); return "Successfully triggered shutdown"; - } catch(CloudRuntimeException e) { + } catch (CloudRuntimeException e) { return e.getMessage(); } } @@ -1341,7 +1351,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust try { shutdownManager.cancelShutdown(); return "Successfully prepared for shutdown"; - } catch(CloudRuntimeException e) { + } catch (CloudRuntimeException e) { return e.getMessage(); } } diff --git a/engine/orchestration/src/test/java/com/cloud/agent/manager/ClusteredAgentManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/agent/manager/ClusteredAgentManagerImplTest.java new file mode 100644 index 00000000000..5e4678f6222 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/agent/manager/ClusteredAgentManagerImplTest.java @@ -0,0 +1,150 @@ +// 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.agent.manager; + +import com.cloud.configuration.ManagementServiceConfiguration; +import com.cloud.ha.HighAvailabilityManagerImpl; +import com.cloud.host.HostVO; +import com.cloud.host.Status; +import com.cloud.host.dao.HostDao; +import com.cloud.resource.ResourceManagerImpl; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ClusteredAgentManagerImplTest { + + private HostDao _hostDao; + @Mock + ManagementServiceConfiguration _mgmtServiceConf; + + @Before + public void setUp() throws Exception { + _hostDao = mock(HostDao.class); + } + + @Test + public void scanDirectAgentToLoadNoHostsTest() { + ClusteredAgentManagerImpl clusteredAgentManagerImpl = mock(ClusteredAgentManagerImpl.class); + clusteredAgentManagerImpl._hostDao = _hostDao; + clusteredAgentManagerImpl.scanDirectAgentToLoad(); + verify(clusteredAgentManagerImpl, never()).findAttache(anyLong()); + verify(clusteredAgentManagerImpl, never()).loadDirectlyConnectedHost(any(), anyBoolean()); + } + + @Test + public void scanDirectAgentToLoadHostWithoutAttacheTest() { + // Arrange + ClusteredAgentManagerImpl clusteredAgentManagerImpl = Mockito.spy(ClusteredAgentManagerImpl.class); + HostVO hostVO = mock(HostVO.class); + clusteredAgentManagerImpl._hostDao = _hostDao; + clusteredAgentManagerImpl.mgmtServiceConf = _mgmtServiceConf; + clusteredAgentManagerImpl._resourceMgr = mock(ResourceManagerImpl.class); + when(_mgmtServiceConf.getTimeout()).thenReturn(16000L); + when(hostVO.getId()).thenReturn(1L); + List hosts = new ArrayList<>(); + hosts.add(hostVO); + when(_hostDao.findAndUpdateDirectAgentToLoad(anyLong(), anyLong(), anyLong())).thenReturn(hosts); + AgentAttache agentAttache = mock(AgentAttache.class); + doReturn(Boolean.TRUE).when(clusteredAgentManagerImpl).loadDirectlyConnectedHost(hostVO, false); + clusteredAgentManagerImpl.scanDirectAgentToLoad(); + verify(clusteredAgentManagerImpl).loadDirectlyConnectedHost(hostVO, false); + } + + @Test + public void scanDirectAgentToLoadHostWithForwardAttacheTest() { + ClusteredAgentManagerImpl clusteredAgentManagerImpl = Mockito.spy(ClusteredAgentManagerImpl.class); + HostVO hostVO = mock(HostVO.class); + clusteredAgentManagerImpl._hostDao = _hostDao; + clusteredAgentManagerImpl.mgmtServiceConf = _mgmtServiceConf; + when(_mgmtServiceConf.getTimeout()).thenReturn(16000L); + when(hostVO.getId()).thenReturn(1L); + List hosts = new ArrayList<>(); + hosts.add(hostVO); + when(_hostDao.findAndUpdateDirectAgentToLoad(anyLong(), anyLong(), anyLong())).thenReturn(hosts); + AgentAttache agentAttache = mock(AgentAttache.class); + when(agentAttache.forForward()).thenReturn(Boolean.TRUE); + when(clusteredAgentManagerImpl.findAttache(1L)).thenReturn(agentAttache); + + clusteredAgentManagerImpl.scanDirectAgentToLoad(); + verify(clusteredAgentManagerImpl).removeAgent(agentAttache, Status.Disconnected); + } + + @Test + public void scanDirectAgentToLoadHostWithNonForwardAttacheTest() { + // Arrange + ClusteredAgentManagerImpl clusteredAgentManagerImpl = Mockito.spy(new ClusteredAgentManagerImpl()); + HostVO hostVO = mock(HostVO.class); + clusteredAgentManagerImpl._hostDao = _hostDao; + clusteredAgentManagerImpl.mgmtServiceConf = _mgmtServiceConf; + clusteredAgentManagerImpl._haMgr = mock(HighAvailabilityManagerImpl.class); + when(_mgmtServiceConf.getTimeout()).thenReturn(16000L); + when(hostVO.getId()).thenReturn(0L); + List hosts = new ArrayList<>(); + hosts.add(hostVO); + when(_hostDao.findAndUpdateDirectAgentToLoad(anyLong(), anyLong(), anyLong())).thenReturn(hosts); + + AgentAttache agentAttache = mock(AgentAttache.class); + when(agentAttache.forForward()).thenReturn(Boolean.FALSE); + when(clusteredAgentManagerImpl.findAttache(0L)).thenReturn(agentAttache); + doReturn(Boolean.TRUE).when(clusteredAgentManagerImpl).agentStatusTransitTo(hostVO, Status.Event.Ping, clusteredAgentManagerImpl._nodeId); + doReturn(Status.Up).when(clusteredAgentManagerImpl).investigate(agentAttache); + + clusteredAgentManagerImpl.scanDirectAgentToLoad(); + verify(clusteredAgentManagerImpl).investigate(agentAttache); + verify(clusteredAgentManagerImpl).agentStatusTransitTo(hostVO, Status.Event.Ping, clusteredAgentManagerImpl._nodeId); + } + + @Test + public void scanDirectAgentToLoadHostWithNonForwardAttacheAndDisconnectedTest() { + ClusteredAgentManagerImpl clusteredAgentManagerImpl = Mockito.spy(ClusteredAgentManagerImpl.class); + HostVO hostVO = mock(HostVO.class); + clusteredAgentManagerImpl._hostDao = _hostDao; + clusteredAgentManagerImpl.mgmtServiceConf = _mgmtServiceConf; + clusteredAgentManagerImpl._haMgr = mock(HighAvailabilityManagerImpl.class); + clusteredAgentManagerImpl._resourceMgr = mock(ResourceManagerImpl.class); + when(_mgmtServiceConf.getTimeout()).thenReturn(16000L); + when(hostVO.getId()).thenReturn(0L); + List hosts = new ArrayList<>(); + hosts.add(hostVO); + when(_hostDao.findAndUpdateDirectAgentToLoad(anyLong(), anyLong(), anyLong())).thenReturn(hosts); + AgentAttache agentAttache = mock(AgentAttache.class); + when(agentAttache.forForward()).thenReturn(Boolean.FALSE); + when(clusteredAgentManagerImpl.findAttache(0L)).thenReturn(agentAttache); + doReturn(Boolean.TRUE).when(clusteredAgentManagerImpl).loadDirectlyConnectedHost(hostVO, false); + clusteredAgentManagerImpl.scanDirectAgentToLoad(); + verify(clusteredAgentManagerImpl).investigate(agentAttache); + verify(clusteredAgentManagerImpl).loadDirectlyConnectedHost(hostVO, false); + } +} From 52d986081b39116b282b61456b4427225dd787b8 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 13 May 2025 17:48:49 +0530 Subject: [PATCH 02/19] Updated Endpoint Selector to pick the Cluster in Enabled state (in addition to Host state) (#10757) * Consider the clusters with allocation state 'Enabled' for EndPoint selection (in addition to Host state) * Reset the pool id when create volume fails on the allocated pool - the pool id is persisted while creating the volume, when it fails the pool id is not reverted. On next create volume attempt, CloudStack couldn't find any suitable primary storage even there are pools available with enough capacity as the pool is already assigned to volume which is in Allocated state (and storage pool compatibility check fails). Ensure volume is not assigned to any pool if create volume fails (so the next creation job would pick the suitable pool). * endpoint check for resize * update the resize error through callback result instead of exception * logger fix --- .../endpoint/DefaultEndPointSelector.java | 2 +- .../storage/volume/VolumeServiceImpl.java | 24 ++++++++++--------- .../CloudStackPrimaryDataStoreDriverImpl.java | 11 +++++++-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java index 79be6588899..7e9f65f43b3 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java @@ -80,7 +80,7 @@ public class DefaultEndPointSelector implements EndPointSelector { private final String findOneHostOnPrimaryStorage = "select t.id from " + "(select h.id, cd.value, hd.value as " + VOL_ENCRYPT_COLUMN_NAME + " " + "from host h join storage_pool_host_ref s on h.id = s.host_id " - + "join cluster c on c.id=h.cluster_id " + + "join cluster c on c.id=h.cluster_id and c.allocation_state = 'Enabled'" + "left join cluster_details cd on c.id=cd.cluster_id and cd.name='" + CapacityManager.StorageOperationsExcludeCluster.key() + "' " + "left join host_details hd on h.id=hd.host_id and hd.name='" + HOST_VOLUME_ENCRYPTION + "' " + "where h.status = 'Up' and h.type = 'Routing' and h.resource_state = 'Enabled' and s.pool_id = ? "; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 26bef607c9b..1a352f7351f 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -328,6 +328,11 @@ public class VolumeServiceImpl implements VolumeService { } else { vo.processEvent(Event.OperationFailed); errMsg = result.getResult(); + VolumeVO volume = volDao.findById(vo.getId()); + if (volume != null && volume.getState() == State.Allocated && volume.getPodId() != null) { + volume.setPoolId(null); + volDao.update(volume.getId(), volume); + } } VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo); if (errMsg != null) { @@ -1237,6 +1242,10 @@ public class VolumeServiceImpl implements VolumeService { } if (volume.getState() == State.Allocated) { // Possible states here: Allocated, Ready & Creating + if (volume.getPodId() != null) { + volume.setPoolId(null); + volDao.update(volume.getId(), volume); + } return; } @@ -2476,7 +2485,7 @@ public class VolumeServiceImpl implements VolumeService { try { volume.processEvent(Event.ResizeRequested); } catch (Exception e) { - logger.debug("Failed to change state to resize", e); + logger.debug("Failed to change volume state to resize", e); result.setResult(e.toString()); future.complete(result); return future; @@ -2488,10 +2497,8 @@ public class VolumeServiceImpl implements VolumeService { try { volume.getDataStore().getDriver().resize(volume, caller); } catch (Exception e) { - logger.debug("Failed to change state to resize", e); - + logger.debug("Failed to resize volume", e); result.setResult(e.toString()); - future.complete(result); } @@ -2535,7 +2542,7 @@ public class VolumeServiceImpl implements VolumeService { try { volume.processEvent(Event.OperationFailed); } catch (Exception e) { - logger.debug("Failed to change state", e); + logger.debug("Failed to change volume state (after resize failure)", e); } VolumeApiResult res = new VolumeApiResult(volume); res.setResult(result.getResult()); @@ -2546,13 +2553,8 @@ public class VolumeServiceImpl implements VolumeService { try { volume.processEvent(Event.OperationSuccessed); } catch (Exception e) { - logger.debug("Failed to change state", e); - VolumeApiResult res = new VolumeApiResult(volume); - res.setResult(result.getResult()); - future.complete(res); - return null; + logger.debug("Failed to change volume state (after resize success)", e); } - VolumeApiResult res = new VolumeApiResult(volume); future.complete(res); diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java index 8bb9ef1ead8..3603589f4ad 100644 --- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java @@ -432,9 +432,18 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri boolean encryptionRequired = anyVolumeRequiresEncryption(vol); long [] endpointsToRunResize = resizeParameter.hosts; + CreateCmdResult result = new CreateCmdResult(null, null); + // if hosts are provided, they are where the VM last ran. We can use that. if (endpointsToRunResize == null || endpointsToRunResize.length == 0) { EndPoint ep = epSelector.select(data, encryptionRequired); + if (ep == null) { + String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired); + logger.error(errMsg); + result.setResult(errMsg); + callback.complete(result); + return; + } endpointsToRunResize = new long[] {ep.getId()}; } ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), @@ -442,7 +451,6 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri if (pool.getParent() != 0) { resizeCmd.setContextParam(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString()); } - CreateCmdResult result = new CreateCmdResult(null, null); try { ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, endpointsToRunResize, resizeCmd); if (answer != null && answer.getResult()) { @@ -459,7 +467,6 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri logger.debug("return a null answer, mark it as failed for unknown reason"); result.setResult("return a null answer, mark it as failed for unknown reason"); } - } catch (Exception e) { logger.debug("sending resize command failed", e); result.setResult(e.toString()); From 8d3ae3e0574b9fc10d1d5635ad4d96e36bb8cce0 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Wed, 14 May 2025 03:12:27 -0300 Subject: [PATCH 03/19] [Vmware] Improve listing of Vmware Datacenter VMs for migration to KVM (#10770) Co-authored-by: dahn Co-authored-by: Suresh Kumar Anaparti --- .../response/UnmanagedInstanceResponse.java | 24 ++++ .../cloudstack/vm/UnmanagedInstanceTO.java | 19 ++++ .../vmware/manager/VmwareManagerImpl.java | 86 ++++++++++---- .../admin/zone/ListVmwareDcVmsCmd.java | 19 ++++ .../java/com/cloud/api/ApiResponseHelper.java | 2 + ui/src/views/tools/ManageInstances.vue | 28 +++++ ui/src/views/tools/SelectVmwareVcenter.vue | 7 ++ .../cloud/hypervisor/vmware/mo/BaseMO.java | 107 ++++++++++++++++++ .../hypervisor/vmware/mo/DatacenterMO.java | 26 +---- .../hypervisor/vmware/util/VmwareHelper.java | 13 +++ 10 files changed, 288 insertions(+), 43 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java index 7a26b178591..c1156f5f23a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java @@ -79,6 +79,14 @@ public class UnmanagedInstanceResponse extends BaseResponse { @Param(description = "the operating system of the virtual machine") private String operatingSystem; + @SerializedName(ApiConstants.BOOT_MODE) + @Param(description = "indicates the boot mode") + private String bootMode; + + @SerializedName(ApiConstants.BOOT_TYPE) + @Param(description = "indicates the boot type") + private String bootType; + @SerializedName(ApiConstants.DISK) @Param(description = "the list of disks associated with the virtual machine", responseObject = UnmanagedInstanceDiskResponse.class) private Set disks; @@ -211,4 +219,20 @@ public class UnmanagedInstanceResponse extends BaseResponse { public void addNic(NicResponse nic) { this.nics.add(nic); } + + public String getBootMode() { + return bootMode; + } + + public void setBootMode(String bootMode) { + this.bootMode = bootMode; + } + + public String getBootType() { + return bootType; + } + + public void setBootType(String bootType) { + this.bootType = bootType; + } } diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java index 6f632853527..f1027b74eca 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java @@ -61,6 +61,9 @@ public class UnmanagedInstanceTO { private String vncPassword; + private String bootType; + private String bootMode; + public String getName() { return name; } @@ -189,6 +192,22 @@ public class UnmanagedInstanceTO { this.vncPassword = vncPassword; } + public String getBootType() { + return bootType; + } + + public void setBootType(String bootType) { + this.bootType = bootType; + } + + public String getBootMode() { + return bootMode; + } + + public void setBootMode(String bootMode) { + this.bootMode = bootMode; + } + public static class Disk { private String diskId; diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java index 61a949f42d3..2bdd496dd92 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java @@ -43,6 +43,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; +import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; import com.cloud.hypervisor.vmware.util.VmwareClient; import org.apache.cloudstack.api.command.admin.zone.AddVmwareDcCmd; import org.apache.cloudstack.api.command.admin.zone.ImportVsphereStoragePoliciesCmd; @@ -1587,14 +1588,26 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw return compatiblePools; } - @Override - public List listVMsInDatacenter(ListVmwareDcVmsCmd cmd) { + private static class VcenterData { + public final String vcenter; + public final String datacenterName; + public final String username; + public final String password; + + public VcenterData(String vcenter, String datacenterName, String username, String password) { + this.vcenter = vcenter; + this.datacenterName = datacenterName; + this.username = username; + this.password = password; + } + } + + private VcenterData getVcenterData(ListVmwareDcVmsCmd cmd) { String vcenter = cmd.getVcenter(); String datacenterName = cmd.getDatacenterName(); String username = cmd.getUsername(); String password = cmd.getPassword(); Long existingVcenterId = cmd.getExistingVcenterId(); - String keyword = cmd.getKeyword(); if ((existingVcenterId == null && StringUtils.isBlank(vcenter)) || (existingVcenterId != null && StringUtils.isNotBlank(vcenter))) { @@ -1615,34 +1628,67 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw username = vmwareDc.getUser(); password = vmwareDc.getPassword(); } + VcenterData vmwaredc = new VcenterData(vcenter, datacenterName, username, password); + return vmwaredc; + } + + private static VmwareContext getVmwareContext(String vcenter, String username, String password) throws Exception { + s_logger.debug(String.format("Connecting to the VMware vCenter %s", vcenter)); + String serviceUrl = String.format("https://%s/sdk/vimService", vcenter); + VmwareClient vimClient = new VmwareClient(vcenter); + vimClient.connect(serviceUrl, username, password); + return new VmwareContext(vimClient, vcenter); + } + + @Override + public List listVMsInDatacenter(ListVmwareDcVmsCmd cmd) { + VcenterData vmwareDC = getVcenterData(cmd); + String vcenter = vmwareDC.vcenter; + String username = vmwareDC.username; + String password = vmwareDC.password; + String datacenterName = vmwareDC.datacenterName; + String keyword = cmd.getKeyword(); + String esxiHostName = cmd.getHostName(); + String virtualMachineName = cmd.getInstanceName(); try { - s_logger.debug(String.format("Connecting to the VMware datacenter %s at vCenter %s to retrieve VMs", - datacenterName, vcenter)); - String serviceUrl = String.format("https://%s/sdk/vimService", vcenter); - VmwareClient vimClient = new VmwareClient(vcenter); - vimClient.connect(serviceUrl, username, password); - VmwareContext context = new VmwareContext(vimClient, vcenter); + VmwareContext context = getVmwareContext(vcenter, username, password); + DatacenterMO dcMo = getDatacenterMO(context, vcenter, datacenterName); - DatacenterMO dcMo = new DatacenterMO(context, datacenterName); - ManagedObjectReference dcMor = dcMo.getMor(); - if (dcMor == null) { - String msg = String.format("Unable to find VMware datacenter %s in vCenter %s", - datacenterName, vcenter); - s_logger.error(msg); - throw new InvalidParameterValueException(msg); + List instances; + if (StringUtils.isNotBlank(esxiHostName) && StringUtils.isNotBlank(virtualMachineName)) { + ManagedObjectReference hostMor = dcMo.findHost(esxiHostName); + if (hostMor == null) { + String errorMsg = String.format("Cannot find a host with name %s on vcenter %s", esxiHostName, vcenter); + s_logger.error(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + HostMO hostMO = new HostMO(context, hostMor); + VirtualMachineMO vmMo = hostMO.findVmOnHyperHost(virtualMachineName); + instances = Collections.singletonList(VmwareHelper.getUnmanagedInstance(hostMO, vmMo)); + } else { + instances = dcMo.getAllVmsOnDatacenter(keyword); } - List instances = dcMo.getAllVmsOnDatacenter(); - return StringUtils.isBlank(keyword) ? instances : - instances.stream().filter(x -> x.getName().toLowerCase().contains(keyword.toLowerCase())).collect(Collectors.toList()); + return instances; } catch (Exception e) { - String errorMsg = String.format("Error retrieving stopped VMs from the VMware VC %s datacenter %s: %s", + String errorMsg = String.format("Error retrieving VMs from the VMware VC %s datacenter %s: %s", vcenter, datacenterName, e.getMessage()); s_logger.error(errorMsg, e); throw new CloudRuntimeException(errorMsg); } } + private static DatacenterMO getDatacenterMO(VmwareContext context, String vcenter, String datacenterName) throws Exception { + DatacenterMO dcMo = new DatacenterMO(context, datacenterName); + ManagedObjectReference dcMor = dcMo.getMor(); + if (dcMor == null) { + String msg = String.format("Unable to find VMware datacenter %s in vCenter %s", datacenterName, vcenter); + s_logger.error(msg); + throw new InvalidParameterValueException(msg); + } + return dcMo; + } + @Override public boolean hasNexusVSM(Long clusterId) { ClusterVSMMapVO vsmMapVo = null; diff --git a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.java b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.java index 4dd1b4beb09..3c7b233f7c9 100644 --- a/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.java +++ b/plugins/hypervisors/vmware/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListVmwareDcVmsCmd.java @@ -70,6 +70,12 @@ public class ListVmwareDcVmsCmd extends BaseListCmd { @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, description = "The password for specified username.") private String password; + @Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, description = "Name of the host on vCenter. Must be set along with the instancename parameter") + private String hostName; + + @Parameter(name = ApiConstants.INSTANCE_NAME, type = CommandType.STRING, description = "Name of the VM on vCenter. Must be set along with the hostname parameter") + private String instanceName; + public String getVcenter() { return vcenter; } @@ -86,10 +92,18 @@ public class ListVmwareDcVmsCmd extends BaseListCmd { return datacenterName; } + public String getHostName() { + return hostName; + } + public Long getExistingVcenterId() { return existingVcenterId; } + public String getInstanceName() { + return instanceName; + } + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { checkParameters(); @@ -125,6 +139,11 @@ public class ListVmwareDcVmsCmd extends BaseListCmd { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please set all the information for a vCenter IP/Name, datacenter, username and password"); } + if ((StringUtils.isNotBlank(instanceName) && StringUtils.isBlank(hostName)) || + (StringUtils.isBlank(instanceName) && StringUtils.isNotBlank(hostName))) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, + "Please set the hostname parameter along with the instancename parameter"); + } } @Override diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index ed7a51d215c..77276090dae 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5145,6 +5145,8 @@ public class ApiResponseHelper implements ResponseGenerator { response.setMemory(instance.getMemory()); response.setOperatingSystemId(instance.getOperatingSystemId()); response.setOperatingSystem(instance.getOperatingSystem()); + response.setBootMode(instance.getBootMode()); + response.setBootType(instance.getBootType()); response.setObjectName("unmanagedinstance"); if (instance.getDisks() != null) { diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue index 2ff9052d8b9..85b1deea915 100644 --- a/ui/src/views/tools/ManageInstances.vue +++ b/ui/src/views/tools/ManageInstances.vue @@ -1302,6 +1302,31 @@ export default { this.fetchInstances() } }, + fetchVmwareInstanceForKVMMigration (vmname, hostname) { + const params = {} + if (this.isMigrateFromVmware && this.selectedVmwareVcenter) { + if (this.selectedVmwareVcenter.vcenter) { + params.datacentername = this.selectedVmwareVcenter.datacentername + params.vcenter = this.selectedVmwareVcenter.vcenter + params.username = this.selectedVmwareVcenter.username + params.password = this.selectedVmwareVcenter.password + } else { + params.existingvcenterid = this.selectedVmwareVcenter.existingvcenterid + } + params.instancename = vmname + params.hostname = hostname + } + api('listVmwareDcVms', params).then(json => { + const response = json.listvmwaredcvmsresponse + this.selectedUnmanagedInstance = response.unmanagedinstance[0] + this.selectedUnmanagedInstance.ostypename = this.selectedUnmanagedInstance.osdisplayname + this.selectedUnmanagedInstance.state = this.selectedUnmanagedInstance.powerstate + }).catch(error => { + this.$notifyError(error) + }).finally(() => { + this.loading = false + }) + }, onManageInstanceAction () { this.selectedUnmanagedInstance = {} if (this.unmanagedInstances.length > 0 && @@ -1319,6 +1344,9 @@ export default { } }) this.showUnmanageForm = false + } else if (this.isMigrateFromVmware) { + this.fetchVmwareInstanceForKVMMigration(this.selectedUnmanagedInstance.name, this.selectedUnmanagedInstance.hostname) + this.showUnmanageForm = true } else { this.showUnmanageForm = true } diff --git a/ui/src/views/tools/SelectVmwareVcenter.vue b/ui/src/views/tools/SelectVmwareVcenter.vue index 80618655719..d742e72f9e0 100644 --- a/ui/src/views/tools/SelectVmwareVcenter.vue +++ b/ui/src/views/tools/SelectVmwareVcenter.vue @@ -227,6 +227,8 @@ export default { } else { params.existingvcenterid = this.selectedExistingVcenterId } + params.page = 1 + params.pagesize = 10 api('listVmwareDcVms', params).then(json => { const obj = { params: params, @@ -265,6 +267,11 @@ export default { this.loading = false }) }, + onSelectExternalVmwareDatacenter (value) { + if (this.vcenterSelectedOption === 'new' && !(this.vcenter === '' || this.datacentername === '' || this.username === '' || this.password === '')) { + this.listVmwareDatacenterVms() + } + }, onSelectExistingVmwareDatacenter (value) { this.selectedExistingVcenterId = value }, diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java index 88f6c871fbd..0d380bd7ff7 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/BaseMO.java @@ -16,6 +16,13 @@ // under the License. package com.cloud.hypervisor.vmware.mo; +import com.cloud.utils.Pair; +import com.vmware.vim25.DynamicProperty; +import com.vmware.vim25.ObjectContent; +import com.vmware.vim25.VirtualMachineBootOptions; +import com.vmware.vim25.VirtualMachinePowerState; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.vmware.vim25.CustomFieldDef; @@ -24,12 +31,22 @@ import com.vmware.vim25.ManagedObjectReference; import com.cloud.hypervisor.vmware.util.VmwareContext; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class BaseMO { private static final Logger s_logger = Logger.getLogger(BaseMO.class); protected VmwareContext _context; protected ManagedObjectReference _mor; + protected static String[] propertyPathsForUnmanagedVmsThinListing = new String[] {"name", "config.template", + "runtime.powerState", "config.guestId", "config.guestFullName", "runtime.host", + "config.bootOptions", "config.firmware"}; + private String _name; public BaseMO(VmwareContext context, ManagedObjectReference mor) { @@ -153,4 +170,94 @@ public class BaseMO { return cfmMo.getCustomFieldKey(morType, fieldName); } + + private static UnmanagedInstanceTO.PowerState convertPowerState(VirtualMachinePowerState powerState) { + return powerState == VirtualMachinePowerState.POWERED_ON ? UnmanagedInstanceTO.PowerState.PowerOn : + powerState == VirtualMachinePowerState.POWERED_OFF ? UnmanagedInstanceTO.PowerState.PowerOff : UnmanagedInstanceTO.PowerState.PowerUnknown; + } + + protected List convertVmsObjectContentsToUnmanagedInstances(List ocs, String keyword) throws Exception { + Map> hostClusterNamesMap = new HashMap<>(); + List vms = new ArrayList<>(); + if (ocs != null) { + for (ObjectContent oc : ocs) { + List objProps = oc.getPropSet(); + if (objProps != null) { + UnmanagedInstanceTO vm = createUnmanagedInstanceTOFromThinListingDynamicProperties( + objProps, keyword, hostClusterNamesMap); + if (vm != null) { + vms.add(vm); + } + } + } + } + if (vms.size() > 0) { + vms.sort(Comparator.comparing(UnmanagedInstanceTO::getName)); + } + return vms; + } + + private UnmanagedInstanceTO createUnmanagedInstanceTOFromThinListingDynamicProperties(List objProps, + String keyword, + Map> hostClusterNamesMap) throws Exception { + UnmanagedInstanceTO vm = new UnmanagedInstanceTO(); + String vmName; + boolean isTemplate = false; + boolean excludeByKeyword = false; + + for (DynamicProperty objProp : objProps) { + if (objProp.getName().equals("name")) { + vmName = (String) objProp.getVal(); + if (StringUtils.isNotBlank(keyword) && !vmName.contains(keyword)) { + excludeByKeyword = true; + } + vm.setName(vmName); + } else if (objProp.getName().equals("config.template")) { + isTemplate = (Boolean) objProp.getVal(); + } else if (objProp.getName().equals("runtime.powerState")) { + VirtualMachinePowerState powerState = (VirtualMachinePowerState) objProp.getVal(); + vm.setPowerState(convertPowerState(powerState)); + } else if (objProp.getName().equals("config.guestFullName")) { + vm.setOperatingSystem((String) objProp.getVal()); + } else if (objProp.getName().equals("config.guestId")) { + vm.setOperatingSystemId((String) objProp.getVal()); + } else if (objProp.getName().equals("config.bootOptions")) { + VirtualMachineBootOptions bootOptions = (VirtualMachineBootOptions) objProp.getVal(); + String bootMode = "LEGACY"; + if (bootOptions != null && bootOptions.isEfiSecureBootEnabled()) { + bootMode = "SECURE"; + } + vm.setBootMode(bootMode); + } else if (objProp.getName().equals("config.firmware")) { + String firmware = (String) objProp.getVal(); + vm.setBootType(firmware.equalsIgnoreCase("efi") ? "UEFI" : "BIOS"); + } else if (objProp.getName().equals("runtime.host")) { + ManagedObjectReference hostMor = (ManagedObjectReference) objProp.getVal(); + setUnmanagedInstanceTOHostAndCluster(vm, hostMor, hostClusterNamesMap); + } + } + if (isTemplate || excludeByKeyword) { + return null; + } + return vm; + } + + private void setUnmanagedInstanceTOHostAndCluster(UnmanagedInstanceTO vm, ManagedObjectReference hostMor, + Map> hostClusterNamesMap) throws Exception { + if (hostMor != null && StringUtils.isNotBlank(hostMor.getValue())) { + String hostMorValue = hostMor.getValue(); + Pair hostClusterPair; + if (hostClusterNamesMap.containsKey(hostMorValue)) { + hostClusterPair = hostClusterNamesMap.get(hostMorValue); + } else { + HostMO hostMO = new HostMO(_context, hostMor); + ClusterMO clusterMO = new ClusterMO(_context, hostMO.getHyperHostCluster()); + hostClusterPair = new Pair<>(hostMO.getHostName(), clusterMO.getName()); + hostClusterNamesMap.put(hostMorValue, hostClusterPair); + } + vm.setHostName(hostClusterPair.first()); + vm.setClusterName(hostClusterPair.second()); + } + } + } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java index d8c7e8a61ea..7aa18276d44 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/DatacenterMO.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.cloud.hypervisor.vmware.util.VmwareHelper; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; @@ -161,28 +160,9 @@ public class DatacenterMO extends BaseMO { return null; } - public List getAllVmsOnDatacenter() throws Exception { - List vms = new ArrayList<>(); - List ocs = getVmPropertiesOnDatacenterVmFolder(new String[] {"name"}); - if (ocs != null) { - for (ObjectContent oc : ocs) { - ManagedObjectReference vmMor = oc.getObj(); - if (vmMor != null) { - VirtualMachineMO vmMo = new VirtualMachineMO(_context, vmMor); - try { - if (!vmMo.isTemplate()) { - HostMO hostMO = vmMo.getRunningHost(); - UnmanagedInstanceTO unmanagedInstance = VmwareHelper.getUnmanagedInstance(hostMO, vmMo); - vms.add(unmanagedInstance); - } - } catch (Exception e) { - s_logger.debug(String.format("Unexpected error checking unmanaged instance %s, excluding it: %s", vmMo.getVmName(), e.getMessage()), e); - } - } - } - } - - return vms; + public List getAllVmsOnDatacenter(String keyword) throws Exception { + List ocs = getVmPropertiesOnDatacenterVmFolder(propertyPathsForUnmanagedVmsThinListing); + return convertVmsObjectContentsToUnmanagedInstances(ocs, keyword); } public List getAllHostsOnDatacenter() throws Exception { diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java index b273688c162..9311360cd12 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java @@ -59,6 +59,8 @@ import com.vmware.vim25.NasDatastoreInfo; import com.vmware.vim25.VMwareDVSPortSetting; import com.vmware.vim25.VirtualDeviceFileBackingInfo; import com.vmware.vim25.VirtualIDEController; +import com.vmware.vim25.VirtualMachineBootOptions; +import com.vmware.vim25.VirtualMachineConfigInfo; import com.vmware.vim25.VirtualMachineConfigSummary; import com.vmware.vim25.VirtualMachineGuestOsIdentifier; import com.vmware.vim25.VirtualMachineToolsStatus; @@ -810,6 +812,17 @@ public class VmwareHelper { instance.setCpuSpeed(configSummary.getCpuReservation()); instance.setMemory(configSummary.getMemorySizeMB()); } + VirtualMachineConfigInfo configInfo = vmMo.getConfigInfo(); + if (configInfo != null) { + String firmware = configInfo.getFirmware(); + instance.setBootType(firmware.equalsIgnoreCase("efi") ? "UEFI" : "BIOS"); + VirtualMachineBootOptions bootOptions = configInfo.getBootOptions(); + String bootMode = "LEGACY"; + if (bootOptions != null && bootOptions.isEfiSecureBootEnabled()) { + bootMode = "SECURE"; + } + instance.setBootMode(bootMode); + } try { ClusterMO clusterMo = new ClusterMO(hyperHost.getContext(), hyperHost.getHyperHostCluster()); From a4cce70e28494b9b667a6f5a27e41cbcc1acfb0f Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Wed, 14 May 2025 13:55:11 +0530 Subject: [PATCH 04/19] List usage records for network offering (usage type 13) when offering id is specified in usage id (#10852) --- .../main/java/com/cloud/usage/UsageServiceImpl.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java index e9264554ec5..806a3f58c28 100644 --- a/server/src/main/java/com/cloud/usage/UsageServiceImpl.java +++ b/server/src/main/java/com/cloud/usage/UsageServiceImpl.java @@ -27,6 +27,8 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.domain.Domain; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; import org.apache.cloudstack.api.command.admin.usage.GenerateUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.ListUsageRecordsCmd; import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd; @@ -122,6 +124,8 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag private IPAddressDao _ipDao; @Inject private HostDao _hostDao; + @Inject + private NetworkOfferingDao _networkOfferingDao; public UsageServiceImpl() { } @@ -253,6 +257,7 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag } Long usageDbId = null; + boolean offeringExistsForNetworkOfferingType = false; switch (usageType.intValue()) { case UsageTypes.NETWORK_BYTES_RECEIVED: @@ -326,13 +331,19 @@ public class UsageServiceImpl extends ManagerBase implements UsageService, Manag usageDbId = ip.getId(); } break; + case UsageTypes.NETWORK_OFFERING: + NetworkOfferingVO networkOffering = _networkOfferingDao.findByUuidIncludingRemoved(usageId); + if (networkOffering != null) { + offeringExistsForNetworkOfferingType = true; + sc.addAnd("offeringId", SearchCriteria.Op.EQ, networkOffering.getId()); + } default: break; } if (usageDbId != null) { sc.addAnd("usageId", SearchCriteria.Op.EQ, usageDbId); - } else { + } else if (!offeringExistsForNetworkOfferingType) { // return an empty list if usageId was not found return new Pair, Integer>(new ArrayList(), new Integer(0)); } From d55aa70f7ef42aae503b2747c57f0b2cae9cb108 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Wed, 14 May 2025 18:53:07 +0530 Subject: [PATCH 05/19] Restore single backed-up volume on a live instance attaches the volume as a Raw image making it unreadable (#10844) --- .../java/org/apache/cloudstack/backup/NASBackupProvider.java | 2 ++ .../resource/wrapper/LibvirtRestoreBackupCommandWrapper.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index 4b5f724f7ef..141a0073498 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -26,6 +26,7 @@ import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; @@ -280,6 +281,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co restoredVolume.setPoolId(dataStore.getPoolId()); restoredVolume.setPath(restoredVolume.getUuid()); restoredVolume.setState(Volume.State.Copying); + restoredVolume.setFormat(Storage.ImageFormat.QCOW2); restoredVolume.setSize(backedUpVolumeSize); restoredVolume.setDiskOfferingId(volume.getDiskOfferingId()); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index 141c1d5ea19..2810f98c935 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -45,7 +45,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper Date: Wed, 14 May 2025 20:42:38 +0530 Subject: [PATCH 06/19] Get backup offering details from listVirtualMachinesMetrics in UI (#10867) --- ui/src/views/AutogenView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index 63cd60ab068..7f9f0bd619b 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -919,7 +919,7 @@ export default { if (['listVirtualMachinesMetrics'].includes(this.apiName) && this.dataView) { delete params.details delete params.isvnf - params.details = 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp' + params.details = 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff' } this.loading = true From 005afde24ec181b89b62cef7b6862cd5bcf5ca33 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 15 May 2025 15:40:38 +0530 Subject: [PATCH 07/19] ssvm: reset fields on destroy (#10253) Signed-off-by: Abhishek Kumar --- .../secondarystorage/SecondaryStorageManagerImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index 94a8f367d08..2fda8576533 100644 --- a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -1074,6 +1074,12 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar try { _itMgr.expunge(ssvm.getUuid()); + ssvm.setPublicIpAddress(null); + ssvm.setPublicMacAddress(null); + ssvm.setPublicNetmask(null); + ssvm.setPrivateMacAddress(null); + ssvm.setPrivateIpAddress(null); + _secStorageVmDao.update(ssvm.getId(), ssvm); _secStorageVmDao.remove(ssvm.getId()); HostVO host = _hostDao.findByTypeNameAndZoneId(ssvm.getDataCenterId(), ssvm.getHostName(), Host.Type.SecondaryStorageVM); if (host != null) { @@ -1358,7 +1364,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar @Override public void finalizeExpunge(VirtualMachine vm) { SecondaryStorageVmVO ssvm = _secStorageVmDao.findByUuid(vm.getUuid()); - + ssvm.setPrivateMacAddress(null); ssvm.setPublicIpAddress(null); ssvm.setPublicMacAddress(null); ssvm.setPublicNetmask(null); From f199783c7519f24ae2558f65ec0a8bf8b5e9bdbe Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Fri, 16 May 2025 12:51:19 +0530 Subject: [PATCH 08/19] VMware import - logs sanitation (#10433) --- .../LibvirtConvertInstanceCommandWrapper.java | 1 - .../java/com/cloud/utils/script/Script.java | 27 +++++++------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java index 504edb9d888..9442907f356 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapper.java @@ -57,7 +57,6 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper { boolean obscureParam = false; for (int i = 0; i < command.length; i++) { String cmd = command[i]; - if (obscureParam) { - builder.append("******").append(" "); - obscureParam = false; - } else { - builder.append(command[i]).append(" "); + if (StringUtils.isNotEmpty(cmd) && cmd.startsWith("vi://")) { + String[] tokens = cmd.split("@"); + if (tokens.length >= 2) { + builder.append("vi://").append("******@").append(tokens[1]).append(" "); + } else { + builder.append("vi://").append("******").append(" "); + } + continue; } - - if ("-y".equals(cmd) || "-z".equals(cmd)) { - obscureParam = true; - _passwordCommand = true; - } - } - return builder.toString(); - } - - protected String buildCommandLine(List command) { - StringBuilder builder = new StringBuilder(); - boolean obscureParam = false; - for (String cmd : command) { if (obscureParam) { builder.append("******").append(" "); obscureParam = false; From 1507a5633e7fe6645b7e01cb0fe874f7501acbbe Mon Sep 17 00:00:00 2001 From: Vitor Hugo Homem Marzarotto <59698484+vits-hugs@users.noreply.github.com> Date: Fri, 16 May 2025 04:23:04 -0300 Subject: [PATCH 09/19] Correct typo in exception (#10876) Co-authored-by: Vitor Hugo Homem Marzarotto --- .../java/com/cloud/storage/snapshot/SnapshotManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index ab4a71903f6..eed220836a4 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -849,7 +849,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement } else if (intervalTypeStr != null && volumeId != null) { Type type = SnapshotVO.getSnapshotType(intervalTypeStr); if (type == null) { - throw new InvalidParameterValueException("Unsupported snapstho interval type " + intervalTypeStr); + throw new InvalidParameterValueException("Unsupported snapshot interval type " + intervalTypeStr); } sc.setParameters("snapshotTypeEQ", type.ordinal()); } else { From 13ab8a04d1384db5e07d75cc27c6894866b1f316 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Fri, 16 May 2025 12:54:55 +0530 Subject: [PATCH 10/19] Fix for Vlan doesn't match issue while adding IP range for the shared network without any IP range (#10837) --- .../admin/vlan/CreateVlanIpRangeCmd.java | 3 - .../ConfigurationManagerImpl.java | 106 ++++++++++++------ .../ConfigurationManagerTest.java | 6 + 3 files changed, 76 insertions(+), 39 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java index 66aefd46966..feef18bbed1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java @@ -155,9 +155,6 @@ public class CreateVlanIpRangeCmd extends BaseCmd { } public String getVlan() { - if (vlan == null || vlan.isEmpty()) { - vlan = "untagged"; - } return vlan; } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 94a0fd2ea0d..b90d94dad5d 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -4407,14 +4407,24 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati String endIP = cmd.getEndIp(); final String newVlanGateway = cmd.getGateway(); final String newVlanNetmask = cmd.getNetmask(); + Long networkId = cmd.getNetworkID(); + Long physicalNetworkId = cmd.getPhysicalNetworkId(); + + // Verify that network exists + Network network = getNetwork(networkId); + if (network != null) { + zoneId = network.getDataCenterId(); + physicalNetworkId = network.getPhysicalNetworkId(); + } + String vlanId = cmd.getVlan(); + vlanId = verifyAndUpdateVlanId(vlanId, network); + // TODO decide if we should be forgiving or demand a valid and complete URI if (!(vlanId == null || "".equals(vlanId) || vlanId.startsWith(BroadcastDomainType.Vlan.scheme()))) { vlanId = BroadcastDomainType.Vlan.toUri(vlanId).toString(); } final Boolean forVirtualNetwork = cmd.isForVirtualNetwork(); - Long networkId = cmd.getNetworkID(); - Long physicalNetworkId = cmd.getPhysicalNetworkId(); final String accountName = cmd.getAccountName(); final Long projectId = cmd.getProjectId(); final Long domainId = cmd.getDomainId(); @@ -4487,18 +4497,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - // Verify that network exists - Network network = null; - if (networkId != null) { - network = _networkDao.findById(networkId); - if (network == null) { - throw new InvalidParameterValueException("Unable to find network by id " + networkId); - } else { - zoneId = network.getDataCenterId(); - physicalNetworkId = network.getPhysicalNetworkId(); - } - } - // Verify that zone exists final DataCenterVO zone = _zoneDao.findById(zoneId); if (zone == null) { @@ -4639,6 +4637,32 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati ip6Cidr, domain, vlanOwner, network, sameSubnet); } + private Network getNetwork(Long networkId) { + if (networkId == null) { + return null; + } + + Network network = _networkDao.findById(networkId); + if (network == null) { + throw new InvalidParameterValueException("Unable to find network by id " + networkId); + } + + return network; + } + + private String verifyAndUpdateVlanId(String vlanId, Network network) { + if (!StringUtils.isBlank(vlanId)) { + return vlanId; + } + + if (network == null || network.getTrafficType() != TrafficType.Guest) { + return Vlan.UNTAGGED; + } + + boolean connectivityWithoutVlan = isConnectivityWithoutVlan(network); + return getNetworkVlanId(network, connectivityWithoutVlan); + } + private Vlan commitVlan(final Long zoneId, final Long podId, final String startIP, final String endIP, final String newVlanGatewayFinal, final String newVlanNetmaskFinal, final String vlanId, final Boolean forVirtualNetwork, final Boolean forSystemVms, final Long networkId, final Long physicalNetworkId, final String startIPv6, final String endIPv6, final String ip6Gateway, final String ip6Cidr, final Domain domain, final Account vlanOwner, final Network network, final Pair> sameSubnet) { @@ -4847,28 +4871,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati // same as network's vlan // 2) if vlan is missing, default it to the guest network's vlan if (network.getTrafficType() == TrafficType.Guest) { - String networkVlanId = null; - boolean connectivityWithoutVlan = false; - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Connectivity)) { - Map connectivityCapabilities = _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Connectivity); - connectivityWithoutVlan = MapUtils.isNotEmpty(connectivityCapabilities) && connectivityCapabilities.containsKey(Capability.NoVlan); - } - - final URI uri = network.getBroadcastUri(); - if (connectivityWithoutVlan) { - networkVlanId = network.getBroadcastDomainType().toUri(network.getUuid()).toString(); - } else if (uri != null) { - // Do not search for the VLAN tag when the network doesn't support VLAN - if (uri.toString().startsWith("vlan")) { - final String[] vlan = uri.toString().split("vlan:\\/\\/"); - networkVlanId = vlan[1]; - // For pvlan - if (network.getBroadcastDomainType() != BroadcastDomainType.Vlan) { - networkVlanId = networkVlanId.split("-")[0]; - } - } - } - + boolean connectivityWithoutVlan = isConnectivityWithoutVlan(network); + String networkVlanId = getNetworkVlanId(network, connectivityWithoutVlan); if (vlanId != null && !connectivityWithoutVlan) { // if vlan is specified, throw an error if it's not equal to // network's vlanId @@ -5000,6 +5004,36 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati return vlan; } + private boolean isConnectivityWithoutVlan(Network network) { + boolean connectivityWithoutVlan = false; + if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Connectivity)) { + Map connectivityCapabilities = _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Connectivity); + connectivityWithoutVlan = MapUtils.isNotEmpty(connectivityCapabilities) && connectivityCapabilities.containsKey(Capability.NoVlan); + } + return connectivityWithoutVlan; + } + + private String getNetworkVlanId(Network network, boolean connectivityWithoutVlan) { + String networkVlanId = null; + if (connectivityWithoutVlan) { + return network.getBroadcastDomainType().toUri(network.getUuid()).toString(); + } + + final URI uri = network.getBroadcastUri(); + if (uri != null) { + // Do not search for the VLAN tag when the network doesn't support VLAN + if (uri.toString().startsWith("vlan")) { + final String[] vlan = uri.toString().split("vlan:\\/\\/"); + networkVlanId = vlan[1]; + // For pvlan + if (network.getBroadcastDomainType() != BroadcastDomainType.Vlan) { + networkVlanId = networkVlanId.split("-")[0]; + } + } + } + return networkVlanId; + } + private void checkZoneVlanIpOverlap(DataCenterVO zone, Network network, String newCidr, String vlanId, String vlanGateway, String vlanNetmask, String startIP, String endIP) { // Throw an exception if this subnet overlaps with subnet on other VLAN, // if this is ip range extension, gateway, network mask should be same and ip range should not overlap diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java index 4b9441dd2ea..f8ae83078c4 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java @@ -54,6 +54,8 @@ import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.offering.DiskOffering; @@ -189,6 +191,8 @@ public class ConfigurationManagerTest { @Mock HostPodDao _podDao; @Mock + NetworkDao _networkDao; + @Mock PhysicalNetworkDao _physicalNetworkDao; @Mock ImageStoreDao _imageStoreDao; @@ -1325,6 +1329,8 @@ public class ConfigurationManagerTest { public void testWrongIpv6CreateVlanAndPublicIpRange() { CreateVlanIpRangeCmd cmd = Mockito.mock(CreateVlanIpRangeCmd.class); Mockito.when(cmd.getIp6Cidr()).thenReturn("fd17:5:8a43:e2a4:c000::/66"); + NetworkVO network = Mockito.mock(NetworkVO.class); + Mockito.when(_networkDao.findById(Mockito.anyLong())).thenReturn(network); try { configurationMgr.createVlanAndPublicIpRange(cmd); } catch (InsufficientCapacityException | ResourceUnavailableException | ResourceAllocationException e) { From c183fc9859ee8447433d99c227028c94799a994f Mon Sep 17 00:00:00 2001 From: slavkap <51903378+slavkap@users.noreply.github.com> Date: Fri, 16 May 2025 11:02:33 +0300 Subject: [PATCH 11/19] Prevent data corruption for StorPool volumes (#10799) --- .../java/com/cloud/vm/VirtualMachineManagerImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index f2e90decdea..41fd631d88f 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -61,7 +61,11 @@ import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; @@ -385,6 +389,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private DomainRouterJoinDao domainRouterJoinDao; @Inject private AnnotationDao annotationDao; + @Inject + DataStoreProviderManager dataStoreProviderManager; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); @@ -1204,6 +1210,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac planChangedByVolume = true; } } + DataStoreProvider storeProvider = dataStoreProviderManager.getDataStoreProvider(pool.getStorageProviderName()); + DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); + if (storeDriver instanceof PrimaryDataStoreDriver) { + ((PrimaryDataStoreDriver)storeDriver).detachVolumeFromAllStorageNodes(vol); + } } } From 112dfddd40b5311e9bf34f78468d640b1b6aed4a Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Fri, 16 May 2025 13:56:28 +0530 Subject: [PATCH 12/19] Reset the pool id when create volume fails on the allocated pool, and update the resize error when no endpoint exists (#10777) * Reset the pool id when create volume fails on the allocated pool - the pool id is persisted while creating the volume, when it fails the pool id is not reverted. On next create volume attempt, CloudStack couldn't find any suitable primary storage even there are pools available with enough capacity as the pool is already assigned to volume which is in Allocated state (and storage pool compatibility check fails). Ensure volume is not assigned to any pool if create volume fails (so the next creation job would pick the suitable pool). * endpoint check for resize * update the resize error through callback result instead of exception --- .../storage/volume/VolumeServiceImpl.java | 24 ++++++++++--------- .../CloudStackPrimaryDataStoreDriverImpl.java | 11 +++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index aba24b6956b..76f55e1c449 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -324,6 +324,11 @@ public class VolumeServiceImpl implements VolumeService { } else { vo.processEvent(Event.OperationFailed); errMsg = result.getResult(); + VolumeVO volume = volDao.findById(vo.getId()); + if (volume != null && volume.getState() == State.Allocated && volume.getPodId() != null) { + volume.setPoolId(null); + volDao.update(volume.getId(), volume); + } } VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo); if (errMsg != null) { @@ -1238,6 +1243,10 @@ public class VolumeServiceImpl implements VolumeService { } if (volume.getState() == State.Allocated) { // Possible states here: Allocated, Ready & Creating + if (volume.getPodId() != null) { + volume.setPoolId(null); + volDao.update(volume.getId(), volume); + } return; } @@ -2482,7 +2491,7 @@ public class VolumeServiceImpl implements VolumeService { try { volume.processEvent(Event.ResizeRequested); } catch (Exception e) { - s_logger.debug("Failed to change state to resize", e); + s_logger.debug("Failed to change volume state to resize", e); result.setResult(e.toString()); future.complete(result); return future; @@ -2494,10 +2503,8 @@ public class VolumeServiceImpl implements VolumeService { try { volume.getDataStore().getDriver().resize(volume, caller); } catch (Exception e) { - s_logger.debug("Failed to change state to resize", e); - + s_logger.debug("Failed to resize volume", e); result.setResult(e.toString()); - future.complete(result); } @@ -2541,7 +2548,7 @@ public class VolumeServiceImpl implements VolumeService { try { volume.processEvent(Event.OperationFailed); } catch (Exception e) { - s_logger.debug("Failed to change state", e); + s_logger.debug("Failed to change volume state (after resize failure)", e); } VolumeApiResult res = new VolumeApiResult(volume); res.setResult(result.getResult()); @@ -2552,13 +2559,8 @@ public class VolumeServiceImpl implements VolumeService { try { volume.processEvent(Event.OperationSuccessed); } catch (Exception e) { - s_logger.debug("Failed to change state", e); - VolumeApiResult res = new VolumeApiResult(volume); - res.setResult(result.getResult()); - future.complete(res); - return null; + s_logger.debug("Failed to change volume state (after resize success)", e); } - VolumeApiResult res = new VolumeApiResult(volume); future.complete(res); diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java index a0aaab1d0aa..1e78e50301a 100644 --- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java @@ -429,9 +429,18 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri boolean encryptionRequired = anyVolumeRequiresEncryption(vol); long [] endpointsToRunResize = resizeParameter.hosts; + CreateCmdResult result = new CreateCmdResult(null, null); + // if hosts are provided, they are where the VM last ran. We can use that. if (endpointsToRunResize == null || endpointsToRunResize.length == 0) { EndPoint ep = epSelector.select(data, encryptionRequired); + if (ep == null) { + String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired); + s_logger.error(errMsg); + result.setResult(errMsg); + callback.complete(result); + return; + } endpointsToRunResize = new long[] {ep.getId()}; } ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), @@ -439,7 +448,6 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri if (pool.getParent() != 0) { resizeCmd.setContextParam(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString()); } - CreateCmdResult result = new CreateCmdResult(null, null); try { ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, endpointsToRunResize, resizeCmd); if (answer != null && answer.getResult()) { @@ -456,7 +464,6 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri s_logger.debug("return a null answer, mark it as failed for unknown reason"); result.setResult("return a null answer, mark it as failed for unknown reason"); } - } catch (Exception e) { s_logger.debug("sending resize command failed", e); result.setResult(e.toString()); From 951863c3fe9eb0cc055614dea5f99dc97c941c2e Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 16 May 2025 15:57:19 +0530 Subject: [PATCH 13/19] ui: add an infinite scroll select component (#10840) Signed-off-by: Abhishek Kumar --- ui/public/locales/en.json | 1 + ui/src/components/header/ProjectMenu.vue | 117 +++---- .../widgets/InfiniteScrollSelect.vue | 298 ++++++++++++++++++ .../views/network/CreateNetworkPermission.vue | 124 ++------ 4 files changed, 370 insertions(+), 170 deletions(-) create mode 100644 ui/src/components/widgets/InfiniteScrollSelect.vue diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index f4a8f59017e..ddfd1124813 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1925,6 +1925,7 @@ "label.sharedrouteripv6": "IPv6 address for the VR in this shared Network.", "label.sharewith": "Share with", "label.showing": "Showing", +"label.showing.results.for": "Showing results for \"%x\"", "label.shrinkok": "Shrink OK", "label.shutdown": "Shutdown", "label.shutdown.provider": "Shutdown provider", diff --git a/ui/src/components/header/ProjectMenu.vue b/ui/src/components/header/ProjectMenu.vue index 6fb0c3af350..590a8a2fbd0 100644 --- a/ui/src/components/header/ProjectMenu.vue +++ b/ui/src/components/header/ProjectMenu.vue @@ -17,112 +17,75 @@ diff --git a/ui/src/components/widgets/InfiniteScrollSelect.vue b/ui/src/components/widgets/InfiniteScrollSelect.vue new file mode 100644 index 00000000000..f97faf390f8 --- /dev/null +++ b/ui/src/components/widgets/InfiniteScrollSelect.vue @@ -0,0 +1,298 @@ +// 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. + + + + + + diff --git a/ui/src/views/network/CreateNetworkPermission.vue b/ui/src/views/network/CreateNetworkPermission.vue index 037e91eb9fd..6d73bb07ca3 100644 --- a/ui/src/views/network/CreateNetworkPermission.vue +++ b/ui/src/views/network/CreateNetworkPermission.vue @@ -29,47 +29,27 @@ - - - - - - {{ opt.name || opt.description }} - - - + api="listAccounts" + :apiParams="accountsApiParams" + resourceType="account" + defaultIcon="team-outlined" /> - - - - - - {{ opt.name || opt.description }} - - - + api="listProjects" + :apiParams="projectsApiParams" + resourceType="project" + defaultIcon="project-outlined" />