This is an automated email from the ASF dual-hosted git repository. shwstppr pushed a commit to branch 4.22 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 02da97d172c422f5e9519e5e0a9ad16fb5302e12 Merge: 17098c15eb3 b93f603d315 Author: Abhishek Kumar <[email protected]> AuthorDate: Thu Sep 10 17:04:00 2026 +0530 Merge remote-tracking branch 'apache/4.20' into 4.22 .../api/response/ImageStoreDetailResponse.java | 4 +- .../api/response/NetworkOfferingResponse.java | 12 +++ .../cloudstack/api/response/ProjectResponse.java | 2 +- .../api/response/ImageStoreDetailResponseTest.java | 45 ++++++++ .../api/response/ProjectResponseTest.java | 34 ++++++ .../java/com/cloud/agent/manager/AgentAttache.java | 3 +- .../agent/manager/AgentAttacheSendNextTest.java | 85 +++++++++++++++ .../framework/config/impl/ConfigDepotImpl.java | 3 +- .../framework/config/impl/ConfigDepotImplTest.java | 25 +++++ .../framework/jobs/impl/AsyncJobManagerImpl.java | 80 ++++++++++++++ .../main/java/com/cloud/api/ApiResponseHelper.java | 3 +- .../network/lb/LoadBalancingRulesManagerImpl.java | 2 +- .../network/vpn/RemoteAccessVpnManagerImpl.java | 2 +- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 24 ++--- .../lb/LoadBalancingRulesManagerImplTest.java | 74 ++++++++----- .../vpn/RemoteAccessVpnManagerImplTest.java | 50 +++++++++ .../java/com/cloud/vm/UserVmManagerImplTest.java | 79 +++++++------- tools/checkstyle/src/main/resources/cloud-pmd.xml | 117 +++++++++++++-------- ui/public/locales/en.json | 1 + ui/public/locales/pt_BR.json | 1 + ui/src/components/view/ListView.vue | 10 +- ui/src/config/section/offering.js | 8 +- ui/src/utils/util.js | 9 ++ ui/src/views/compute/AutoScaleDownPolicyTab.vue | 19 ++-- ui/src/views/compute/AutoScaleLoadBalancing.vue | 20 ++-- ui/src/views/compute/AutoScaleUpPolicyTab.vue | 19 ++-- ui/src/views/compute/AutoScaleVmProfile.vue | 57 +++++++--- ui/src/views/dashboard/SetupTwoFaAtLogin.vue | 4 +- ui/src/views/dashboard/VerifyTwoFa.vue | 6 +- ui/src/views/iam/SetupTwoFaAtUserProfile.vue | 4 +- 30 files changed, 620 insertions(+), 182 deletions(-) diff --cc engine/orchestration/src/test/java/com/cloud/agent/manager/AgentAttacheSendNextTest.java index 00000000000,967cbd9d60c..5b504b57133 mode 000000,100644..100644 --- a/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentAttacheSendNextTest.java +++ b/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentAttacheSendNextTest.java @@@ -1,0 -1,84 +1,85 @@@ + // 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 org.junit.Assert; + import org.junit.Test; + import org.mockito.Mockito; + + import com.cloud.agent.transport.Request; + import com.cloud.exception.AgentUnavailableException; + import com.cloud.host.Status; ++import com.cloud.hypervisor.Hypervisor; + + public class AgentAttacheSendNextTest { + + /** + * Minimal concrete AgentAttache: send() fails for one designated sequence and succeeds otherwise, + * recording the sequence that was actually dispatched. + */ + static class TestAgentAttache extends AgentAttache { + Long sentSeq; + final long failSeq; + + TestAgentAttache(long failSeq) { - super(null, 1L, "uuid-1", "host-1", false); ++ super(null, 1L, "uuid-1", "host-1", Hypervisor.HypervisorType.KVM, false); + this.failSeq = failSeq; + } + + @Override + public void send(Request req) throws AgentUnavailableException { + if (req.getSequence() == failSeq) { + throw new AgentUnavailableException("simulated transient link failure", _id); + } + sentSeq = req.getSequence(); + } + + @Override + public void disconnect(Status state) { + } + + @Override + protected boolean isClosed() { + return false; + } + } + + @Test + public void sendNextAdvancesPastAFailedCommandToTheNextQueued() { + long failSeq = 100L; + long goodSeq = 200L; + + Request failing = Mockito.mock(Request.class); + Mockito.when(failing.getSequence()).thenReturn(failSeq); + Request good = Mockito.mock(Request.class); + Mockito.when(good.getSequence()).thenReturn(goodSeq); + + TestAgentAttache attache = new TestAgentAttache(failSeq); + attache._requests.add(failing); + attache._requests.add(good); + + attache.sendNext(1L); + + // A command whose send() failed (and was cancelled) must NOT become _currentSequence: no answer + // will ever arrive for it, so every later in-sequence command to this host would queue behind it + // and time out. sendNext must move on and dispatch the next queued command instead. + Assert.assertEquals("the next queued command should have been dispatched", Long.valueOf(goodSeq), attache.sentSeq); + Assert.assertEquals("current sequence must be the successfully sent command, not the failed one", + Long.valueOf(goodSeq), attache._currentSequence); + Assert.assertTrue("the request queue should be drained", attache._requests.isEmpty()); + } + } diff --cc framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java index f4012b43fa8,a0b9287acdc..97f9b1765a7 --- a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java +++ b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java @@@ -19,8 -19,7 +19,9 @@@ package org.apache.cloudstack.framework.config.impl; import java.util.Collections; ++import java.util.Date; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.apache.cloudstack.framework.config.ConfigKey; @@@ -44,12 -44,34 +46,35 @@@ public class ConfigDepotImplTest @Mock ConfigurationDao _configDao; + @Mock + ConfigurationSubGroupDao configSubGroupDao; + @Mock + ConfigurationSubGroupDao _configSubGroupDao; + @InjectMocks private ConfigDepotImpl configDepotImpl = new ConfigDepotImpl(); + @Test + public void createConfigObjectPersistsSubGroupWithNameAndGroupId() { + ConfigKey<?> key = Mockito.mock(ConfigKey.class); + Mockito.when(key.group()).thenReturn(null); + Mockito.when(key.subGroup()).thenReturn(new Pair<>("ConsoleProxy VM", 5L)); + Mockito.when(key.key()).thenReturn("consoleproxy.capacity.standby"); - Mockito.when(key.scope()).thenReturn(ConfigKey.Scope.Global); + Mockito.when(_configSubGroupDao.findByNameAndGroup("ConsoleProxy VM", 1L)).thenReturn(null); + Mockito.when(_configSubGroupDao.persist(Mockito.any(ConfigurationSubGroupVO.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + Mockito.when(_configDao.findById("consoleproxy.capacity.standby")).thenReturn(Mockito.mock(ConfigurationVO.class)); + + ArgumentCaptor<ConfigurationSubGroupVO> captor = ArgumentCaptor.forClass(ConfigurationSubGroupVO.class); + ReflectionTestUtils.invokeMethod(configDepotImpl, "createOrupdateConfigObject", + new Date(), "components", key, "someValue"); + + Mockito.verify(_configSubGroupDao).persist(captor.capture()); + Assert.assertEquals("ConsoleProxy VM", captor.getValue().getName()); + Assert.assertEquals(Long.valueOf(1L), captor.getValue().getGroupId()); + } + @Test public void createEmptyScopeLevelMappingsTest() { configDepotImpl.createEmptyScopeLevelMappings(); diff --cc server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java index 78655ba9a05,055d3fc663b..040f050fd45 --- a/server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java @@@ -17,50 -17,33 +17,53 @@@ package com.cloud.network.lb; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.network.Network; -import com.cloud.network.NetworkModel; -import com.cloud.network.dao.LoadBalancerVO; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.offering.NetworkOffering; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.utils.db.EntityManager; -import com.cloud.utils.exception.CloudRuntimeException; ++import static org.mockito.ArgumentMatchers.anyLong; ++import static org.mockito.Mockito.never; ++import static org.mockito.Mockito.times; ++import static org.mockito.Mockito.when; ++ ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.UUID; ++ ++import org.apache.cloudstack.acl.SecurityChecker; ++import org.apache.cloudstack.api.ApiConstants; + import org.apache.cloudstack.api.ServerApiException; ++import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd; ++import org.apache.cloudstack.context.CallContext; + import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; + import org.junit.Assert; + import org.junit.Test; + import org.junit.runner.RunWith; + import org.mockito.InjectMocks; + import org.mockito.Mock; + import org.mockito.Mockito; + import org.mockito.Spy; + import org.mockito.junit.MockitoJUnitRunner; ++import org.springframework.test.util.ReflectionTestUtils; + -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.mockito.Mockito.when; ++import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.LoadBalancerCertMapDao; +import com.cloud.network.dao.LoadBalancerCertMapVO; +import com.cloud.network.dao.LoadBalancerDao; +import com.cloud.network.dao.LoadBalancerVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.SslCertVO; ++import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; - import org.apache.cloudstack.acl.SecurityChecker; - import org.apache.cloudstack.api.ApiConstants; - import org.apache.cloudstack.api.ServerApiException; - import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd; - import org.apache.cloudstack.context.CallContext; - import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; - import org.junit.Assert; - import org.junit.Test; - import org.junit.runner.RunWith; - import org.mockito.InjectMocks; - import org.mockito.Mock; - import org.mockito.Mockito; - import org.mockito.Spy; - import org.mockito.junit.MockitoJUnitRunner; - import org.springframework.test.util.ReflectionTestUtils; - - import java.util.ArrayList; - import java.util.Arrays; - import java.util.List; - import java.util.UUID; - - import static org.mockito.ArgumentMatchers.anyLong; - import static org.mockito.Mockito.never; - import static org.mockito.Mockito.times; - import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class LoadBalancingRulesManagerImplTest{ @@@ -84,26 -64,15 +87,26 @@@ NetworkModel _networkModel; @Mock - NetworkVO networkMock; + LoadBalancerCertMapDao _lbCertMapDao; - private long accountId = 10L; - private long networkId = 4L; + @Mock + NetworkOfferingServiceMapDao _networkOfferingServiceDao; + ++ @Mock ++ NetworkVO networkMock; + @Spy @InjectMocks LoadBalancingRulesManagerImpl lbr = new LoadBalancingRulesManagerImpl(); - @Mock - NetworkVO networkMock; - + @Mock + LoadBalancerVO loadBalancerMock; + + private long accountId = 10L; + private long lbRuleId = 2L; + private long certMapRuleId = 3L; + private long networkId = 4L; + @Test public void generateCidrStringTestNullCidrList() { String result = lbr.generateCidrString(null); @@@ -154,158 -123,18 +157,173 @@@ Network.Provider provider = lbr.getLoadBalancerServiceProvider(loadBalancerMock); } + @Test + public void testAssignCertToLoadBalancer() throws Exception { + long accountId = 10L; + long lbRuleId = 2L; + long certId = 3L; + long networkId = 4L; + + AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid"); + account.setId(accountId); + UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + UUID.randomUUID().toString(), User.Source.UNKNOWN); + CallContext.register(user, account); + + LoadBalancerVO loadBalancerMock = Mockito.mock(LoadBalancerVO.class); + when(_lbDao.findById(lbRuleId)).thenReturn(loadBalancerMock); + when(loadBalancerMock.getId()).thenReturn(lbRuleId); + when(loadBalancerMock.getAccountId()).thenReturn(accountId); + when(loadBalancerMock.getNetworkId()).thenReturn(networkId); + when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.SSL_PROTO); + + SslCertVO certVO = Mockito.mock(SslCertVO.class); + when(_entityMgr.findById(SslCertVO.class, certId)).thenReturn(certVO); + when(certVO.getAccountId()).thenReturn(accountId); + + LoadBalancerCertMapVO certMapRule = Mockito.mock(LoadBalancerCertMapVO.class); + when(_lbCertMapDao.findByLbRuleId(lbRuleId)).thenReturn(certMapRule); + + Mockito.doNothing().when(_accountMgr).checkAccess(Mockito.any(Account.class), Mockito.isNull(SecurityChecker.AccessType.class), Mockito.eq(true), Mockito.any(LoadBalancerVO.class)); + + Mockito.doReturn("LB").when(lbr).getLBCapability(networkId, Network.Capability.SslTermination.getName()); + Mockito.doReturn(true).when(lbr).applyLoadBalancerConfig(lbRuleId); + + lbr.assignCertToLoadBalancer(lbRuleId, certId, true); + + Mockito.verify(lbr, times(2)).applyLoadBalancerConfig(lbRuleId); + } + + private void setupUpdateLoadBalancerRule() throws Exception{ + AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid"); + account.setId(accountId); + UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + UUID.randomUUID().toString(), User.Source.UNKNOWN); + CallContext.register(user, account); + + when(_lbDao.findById(lbRuleId)).thenReturn(loadBalancerMock); + when(loadBalancerMock.getId()).thenReturn(lbRuleId); + when(loadBalancerMock.getNetworkId()).thenReturn(networkId); + + when(_networkDao.findById(networkId)).thenReturn(networkMock); + + Mockito.doNothing().when(_accountMgr).checkAccess(Mockito.any(Account.class), Mockito.isNull(SecurityChecker.AccessType.class), Mockito.eq(true), Mockito.any(LoadBalancerVO.class)); + + LoadBalancingRule loadBalancingRule = Mockito.mock(LoadBalancingRule.class); + Mockito.doReturn(loadBalancingRule).when(lbr).getLoadBalancerRuleToApply(loadBalancerMock); + Mockito.doReturn(true).when(lbr).validateLbRule(loadBalancingRule); + Mockito.doReturn(true).when(lbr).applyLoadBalancerConfig(lbRuleId); + + when(_lbDao.update(lbRuleId, loadBalancerMock)).thenReturn(true); + + LoadBalancerCertMapVO certMapRule = Mockito.mock(LoadBalancerCertMapVO.class); + when(_lbCertMapDao.findByLbRuleId(lbRuleId)).thenReturn(certMapRule); + when(certMapRule.getId()).thenReturn(certMapRuleId); + } + + @Test + public void testUpdateLoadBalancerRule1() throws Exception { + setupUpdateLoadBalancerRule(); + + // Update protocol from TCP to SSL + UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd(); + ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId); + ReflectionTestUtils.setField(cmd, "lbProtocol", NetUtils.SSL_PROTO); + when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.TCP_PROTO).thenReturn(NetUtils.SSL_PROTO); + + lbr.updateLoadBalancerRule(cmd); + + Mockito.verify(lbr, times(1)).applyLoadBalancerConfig(lbRuleId); + Mockito.verify(_lbCertMapDao, never()).remove(anyLong()); + } + + @Test + public void testUpdateLoadBalancerRule2() throws Exception { + setupUpdateLoadBalancerRule(); + + // Update protocol from SSL to TCP + UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd(); + ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId); + ReflectionTestUtils.setField(cmd, "lbProtocol", NetUtils.TCP_PROTO); + when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.SSL_PROTO).thenReturn(NetUtils.TCP_PROTO); + + lbr.updateLoadBalancerRule(cmd); + + Mockito.verify(_lbCertMapDao, times(1)).remove(anyLong()); + Mockito.verify(lbr, times(1)).applyLoadBalancerConfig(lbRuleId); + } + + @Test + public void testUpdateLoadBalancerRule3() throws Exception { + setupUpdateLoadBalancerRule(); + + // Update algorithm from source to roundrobin, lb protocol is same + UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd(); + ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId); + ReflectionTestUtils.setField(cmd, "algorithm", "roundrobin"); + ReflectionTestUtils.setField(cmd, "lbProtocol", NetUtils.SSL_PROTO); + when(loadBalancerMock.getAlgorithm()).thenReturn("source"); + when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.SSL_PROTO); + + lbr.updateLoadBalancerRule(cmd); + + Mockito.verify(lbr, times(1)).applyLoadBalancerConfig(lbRuleId); + Mockito.verify(_lbCertMapDao, never()).remove(anyLong()); + } + + @Test + public void testUpdateLoadBalancerRule4() throws Exception { + setupUpdateLoadBalancerRule(); + + // Update with same algorithm and protocol + UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd(); + ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId); + ReflectionTestUtils.setField(cmd, "algorithm", "roundrobin"); + ReflectionTestUtils.setField(cmd, "lbProtocol", NetUtils.SSL_PROTO); + when(loadBalancerMock.getAlgorithm()).thenReturn("roundrobin"); + when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.SSL_PROTO); + + lbr.updateLoadBalancerRule(cmd); + + Mockito.verify(lbr, never()).applyLoadBalancerConfig(lbRuleId); + Mockito.verify(_lbCertMapDao, never()).remove(anyLong()); + } + + @Test(expected = CloudRuntimeException.class) + public void testUpdateLoadBalancerRule5() throws Exception { + setupUpdateLoadBalancerRule(); + + // Update protocol from SSL to TCP, throws an exception + UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd(); + ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId); + ReflectionTestUtils.setField(cmd, "lbProtocol", NetUtils.TCP_PROTO); + when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.SSL_PROTO).thenReturn(NetUtils.TCP_PROTO); + Mockito.doThrow(ResourceUnavailableException.class).when(lbr).applyLoadBalancerConfig(lbRuleId); + + List<Network.Provider> providers = Arrays.asList(Network.Provider.VirtualRouter); + when(_networkDao.findById(anyLong())).thenReturn(networkMock); + when(_networkMgr.getProvidersForServiceInNetwork(networkMock, Network.Service.Lb)).thenReturn(providers); + + lbr.updateLoadBalancerRule(cmd); + + Mockito.verify(_lbCertMapDao, never()).remove(anyLong()); + Mockito.verify(lbr, times(1)).applyLoadBalancerConfig(lbRuleId); + Mockito.verify(loadBalancerMock, times(1)).setLbProtocol(NetUtils.TCP_PROTO); + Mockito.verify(loadBalancerMock, times(1)).setLbProtocol(NetUtils.SSL_PROTO); + } ++ + @Test(expected = InvalidParameterValueException.class) + public void createPublicLoadBalancerRuleWithDnsPortAndNoIpDoesNotNpe() throws Exception { + long lbOwnerId = accountId; + long networkOfferingId = 7L; + when(_accountMgr.getAccount(lbOwnerId)).thenReturn(Mockito.mock(Account.class)); + when(_networkModel.getNetwork(networkId)).thenReturn(networkMock); + when(networkMock.getNetworkOfferingId()).thenReturn(networkOfferingId); + NetworkOffering off = Mockito.mock(NetworkOffering.class); + when(_entityMgr.findById(NetworkOffering.class, networkOfferingId)).thenReturn(off); + when(off.isElasticLb()).thenReturn(false); + + lbr.createPublicLoadBalancerRule("xid", "name", "desc", 53, 53, 53, 53, + null, "tcp", "roundrobin", networkId, lbOwnerId, false, "tcp", null, null); + } } diff --cc server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 4b26402ffbe,68c3fbedf03..cc2dc1cc9ef --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@@ -57,50 -44,25 +57,47 @@@ import java.util.LinkedHashSet import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.TimeZone; +import java.util.UUID; - import com.cloud.event.dao.UsageEventDao; - import com.cloud.host.Status; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; +import org.apache.cloudstack.api.command.admin.vm.ExpungeVMCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd; +import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; +import org.apache.cloudstack.api.command.user.vm.ResetVMSSHKeyCmd; import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd; import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupVO; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; - import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; - import org.apache.cloudstack.resourcelimit.Reserver; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; ++import org.apache.cloudstack.framework.config.ConfigKey; + import org.apache.cloudstack.resourcelimit.Reserver; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.userdata.UserDataManager; +import org.apache.cloudstack.vm.UnmanagedVMsManager; +import org.apache.cloudstack.vm.lease.VMLeaseManager; - import org.junit.After; import org.junit.Assert; import org.junit.Before; @@@ -127,8 -89,7 +124,9 @@@ import com.cloud.deploy.DeploymentPlann import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEventUtils; import com.cloud.event.UsageEventUtils; ++import com.cloud.event.dao.UsageEventDao; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; @@@ -138,12 -99,11 +136,13 @@@ import com.cloud.exception.ResourceAllo import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.Host; import com.cloud.host.HostVO; ++import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.network.Network; import com.cloud.network.NetworkModel; -import com.cloud.network.NetworkService; +import com.cloud.network.as.AutoScaleManager; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; @@@ -3190,15 -2913,15 +3189,15 @@@ public class UserVmManagerImplTest } @Test - public void moveVmToUserTestProjectIdProvidedAndDomainIdIsNullThrowsInvalidParameterValueException() throws ResourceUnavailableException, InsufficientCapacityException, + public void moveVmToUserTestMovesVmWhenProjectIdIsProvidedAndDomainIdIsNull() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - - String expectedMessage = "Please provide a valid domain ID; cannot assign VM to a project if domain ID is NULL."; - Mockito.doReturn(true).when(accountManager).isRootAdmin(Mockito.anyLong()); Mockito.doReturn(userVmVoMock).when(userVmDao).findById(Mockito.anyLong()); - Mockito.doReturn(1l).when(assignVmCmdMock).getProjectId(); + Mockito.doReturn(1L).when(assignVmCmdMock).getProjectId(); Mockito.doReturn(null).when(assignVmCmdMock).getDomainId(); + Mockito.doReturn(null).when(userVmManagerImpl).ensureDestinationNetwork(Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.doNothing().when(userVmManagerImpl).executeStepsToChangeOwnershipOfVm(Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); configureDoNothingForMethodsThatWeDoNotWantToTest(); diff --cc ui/src/components/view/ListView.vue index 7fd344da209,002411db87b..ceb5ad7884c --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@@ -786,14 -427,13 +787,15 @@@ <status :text="record.enabled ? record.enabled.toString() : 'false'" /> {{ record.enabled ? 'Enabled' : 'Disabled' }} </template> - <template - v-if="['created', 'sent', 'removed', 'effectiveDate', 'endDate', 'allocated'].includes(column.key) || (['startdate'].includes(column.key) && ['webhook'].includes($route.path.split('/')[1])) || (column.key === 'allocated' && ['asnumbers', 'publicip', 'ipv4subnets'].includes($route.meta.name) && text)" - > + <template v-if="column.key === 'egressdefaultpolicy'"> + <span> {{ record.egressdefaultpolicy ? 'Allow' : 'Deny' }} </span> + </template> + <template v-if="['created', 'sent', 'removed', 'effectiveDate', 'endDate', 'allocated'].includes(column.key) || (['startdate'].includes(column.key) && ['webhook'].includes($route.path.split('/')[1])) || (column.key === 'allocated' && ['asnumbers', 'publicip', 'ipv4subnets'].includes($route.meta.name) && text)"> {{ text && $toLocaleDate(text) }} </template> - <template v-if="['startdate', 'enddate'].includes(column.key) && ['vm', 'vnfapp'].includes($route.path.split('/')[1])"> + <template + v-if="['startdate', 'enddate'].includes(column.key) && ['vm', 'vnfapp'].includes($route.path.split('/')[1])" + > {{ getDateAtTimeZone(text, record.timezone) }} </template> <template v-if="column.key === 'payloadurl'"> diff --cc ui/src/config/section/offering.js index 4a32619b8c2,62e3fe4d346..99ef3462373 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@@ -394,22 -388,16 +394,28 @@@ export default icon: 'wifi-outlined', docHelp: 'adminguide/networking.html#network-offerings', permission: ['listNetworkOfferings'], - searchFilters: ['name', 'zoneid', 'domainid', 'tags'], + filters: ['all', 'forvpc', 'guestnetwork'], + searchFilters: ['name', 'zoneid', 'domainid', 'guestiptype', 'tags'], - columns: ['name', 'state', 'guestiptype', 'traffictype', 'networkrate', 'domain', 'zone', 'order'], + columns: () => { + const fields = ['name', 'state', 'guestiptype', 'traffictype', 'networkrate', 'domain', 'zone', 'egressdefaultpolicy', 'order'] + if (store.getters.userInfo.roletype === 'Admin') { + fields.splice(fields.length - 1, 0, { field: 'serviceofferingname', customTitle: 'virtual.routers.system.offering' }) + } + return fields + }, details: ['name', 'id', 'displaytext', 'guestiptype', 'traffictype', 'internetprotocol', 'networkrate', 'ispersistent', 'egressdefaultpolicy', 'availability', 'conservemode', 'specifyvlan', 'routingmode', 'specifyasnumber', 'specifyipranges', 'supportspublicaccess', 'supportsstrechedl2subnet', 'forvpc', 'fornsx', 'networkmode', 'service', 'tags', 'domain', 'zone'], resourceType: 'NetworkOffering', + customParamHandler: (params, query) => { + const { filter } = query + if (!filter) { + return params + } + params.forvpc = filter === 'forvpc' + if (filter === 'all') { + delete params.forvpc + } + return params + }, tabs: [ { name: 'details', diff --cc ui/src/utils/util.js index a6dd2bd689c,ec18e0b5d5f..bd9e0d7cdad --- a/ui/src/utils/util.js +++ b/ui/src/utils/util.js @@@ -112,23 -112,11 +112,32 @@@ export function toCsv ({ keys = null, d return result } +export function isValidIPv4Cidr (rule, value) { + return new Promise((resolve, reject) => { + if (!value) { + reject(new Error()) + return + } + const cidrRegex = /^(\d{1,3}\.){3}\d{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/ + if (!cidrRegex.test(value)) { + reject(new Error('Invalid CIDR format')) + return + } + const ip = value.split('/')[0] + const octets = ip.split('.').map(Number) + if (octets.some(octet => octet < 0 || octet > 255)) { + reject(new Error('Invalid CIDR format')) + return + } + resolve() + }) +} ++ + // Adds the projectid of a project-scoped resource to the given API params, so + // that listing calls are correctly scoped to the resource's project. + export function addProjectFilter (params, resource) { + if (resource?.projectid) { + params.projectid = resource.projectid + } + return params + } diff --cc ui/src/views/compute/AutoScaleDownPolicyTab.vue index 4cd32168cae,e3af0da3370..69aedadb6b1 --- a/ui/src/views/compute/AutoScaleDownPolicyTab.vue +++ b/ui/src/views/compute/AutoScaleDownPolicyTab.vue @@@ -323,7 -323,8 +323,8 @@@ </template> <script> -import { api } from '@/api' +import { getAPI, postAPI } from '@/api' + import { addProjectFilter } from '@/utils/util' import Status from '@/components/widgets/Status' import TooltipButton from '@/components/widgets/TooltipButton' import TooltipLabel from '@/components/widgets/TooltipLabel' @@@ -425,10 -426,12 +426,12 @@@ export default methods: { fetchInitData () { this.loading = true - getAPI('listAutoScaleVmGroups', { + const params = { listAll: true, id: this.resource.id - }).then(response => { + } + addProjectFilter(params, this.resource) - api('listAutoScaleVmGroups', params).then(response => { ++ getAPI('listAutoScaleVmGroups', params).then(response => { const lbruleid = response.listautoscalevmgroupsresponse?.autoscalevmgroup?.[0]?.lbruleid this.policies = response.listautoscalevmgroupsresponse?.autoscalevmgroup?.[0]?.scaledownpolicies if (this.selectedPolicyId) { @@@ -437,12 -440,14 +440,14 @@@ this.policy = this.policies?.[0] this.selectedPolicyId = this.policy.id } - getAPI('listLoadBalancerRules', { + const lbParams = { listAll: true, id: lbruleid - }).then(response => { + } + addProjectFilter(lbParams, this.resource) - api('listLoadBalancerRules', lbParams).then(response => { ++ getAPI('listLoadBalancerRules', lbParams).then(response => { const networkid = response.listloadbalancerrulesresponse?.loadbalancerrule?.[0]?.networkid - api('listNetworks', { + getAPI('listNetworks', { listAll: true, projectid: this.resource.projectid, id: networkid @@@ -464,10 -469,12 +469,12 @@@ }, fetchData () { this.loading = true - getAPI('listAutoScalePolicies', { + const params = { listAll: true, id: this.selectedPolicyId - }).then(response => { + } + addProjectFilter(params, this.resource) - api('listAutoScalePolicies', params).then(response => { ++ getAPI('listAutoScalePolicies', params).then(response => { this.policy = response.listautoscalepoliciesresponse?.autoscalepolicy[0] }).finally(() => { this.loading = false diff --cc ui/src/views/compute/AutoScaleLoadBalancing.vue index 6c04ce1c250,f5377297c2e..498f157e28d --- a/ui/src/views/compute/AutoScaleLoadBalancing.vue +++ b/ui/src/views/compute/AutoScaleLoadBalancing.vue @@@ -298,7 -297,8 +298,8 @@@ <script> import { ref, reactive, toRaw, nextTick } from 'vue' -import { api } from '@/api' +import { getAPI, postAPI } from '@/api' + import { addProjectFilter } from '@/utils/util' import { mixinForm } from '@/utils/mixin' import Status from '@/components/widgets/Status' import TooltipButton from '@/components/widgets/TooltipButton' @@@ -468,7 -468,9 +469,9 @@@ export default id: this.resource.lbruleid, page: this.page, pageSize: this.pageSize - }).then(response => { + } + addProjectFilter(params, this.resource) - api('listLoadBalancerRules', params).then(response => { ++ getAPI('listLoadBalancerRules', params).then(response => { this.lbRules = response.listloadbalancerrulesresponse.loadbalancerrule || [] this.totalCount = response.listloadbalancerrulesresponse.count || 0 }).then(() => { @@@ -519,16 -521,19 +522,19 @@@ }, fetchAutoScaleVMgroups () { this.loading = true - this.lbRules.forEach(rule => { - getAPI('listAutoScaleVmGroups', { + const requests = this.lbRules.map(rule => { + const params = { listAll: true, lbruleid: rule.id - }).then(response => { + } + addProjectFilter(params, this.resource) - return api('listAutoScaleVmGroups', params).then(response => { ++ return getAPI('listAutoScaleVmGroups', params).then(response => { rule.autoscalevmgroup = response.listautoscalevmgroupsresponse?.autoscalevmgroup?.[0] - }).finally(() => { - this.loading = false }) }) + Promise.all(requests).finally(() => { + this.loading = false + }) }, returnAlgorithmName (name) { switch (name) { diff --cc ui/src/views/compute/AutoScaleUpPolicyTab.vue index 6b5ccf69c45,3d0fe3375c0..d166e9a20f9 --- a/ui/src/views/compute/AutoScaleUpPolicyTab.vue +++ b/ui/src/views/compute/AutoScaleUpPolicyTab.vue @@@ -323,7 -323,8 +323,8 @@@ </template> <script> -import { api } from '@/api' +import { getAPI, postAPI } from '@/api' + import { addProjectFilter } from '@/utils/util' import Status from '@/components/widgets/Status' import TooltipButton from '@/components/widgets/TooltipButton' import TooltipLabel from '@/components/widgets/TooltipLabel' @@@ -425,10 -426,12 +426,12 @@@ export default methods: { fetchInitData () { this.loading = true - getAPI('listAutoScaleVmGroups', { + const params = { listAll: true, id: this.resource.id - }).then(response => { + } + addProjectFilter(params, this.resource) - api('listAutoScaleVmGroups', params).then(response => { ++ getAPI('listAutoScaleVmGroups', params).then(response => { const lbruleid = response.listautoscalevmgroupsresponse?.autoscalevmgroup?.[0]?.lbruleid this.policies = response.listautoscalevmgroupsresponse?.autoscalevmgroup?.[0]?.scaleuppolicies if (this.selectedPolicyId) { @@@ -437,12 -440,14 +440,14 @@@ this.policy = this.policies?.[0] this.selectedPolicyId = this.policy.id } - getAPI('listLoadBalancerRules', { + const lbParams = { listAll: true, id: lbruleid - }).then(response => { + } + addProjectFilter(lbParams, this.resource) - api('listLoadBalancerRules', lbParams).then(response => { ++ getAPI('listLoadBalancerRules', lbParams).then(response => { const networkid = response.listloadbalancerrulesresponse?.loadbalancerrule?.[0]?.networkid - api('listNetworks', { + getAPI('listNetworks', { listAll: true, projectid: this.resource.projectid, id: networkid @@@ -464,10 -469,12 +469,12 @@@ }, fetchData () { this.loading = true - getAPI('listAutoScalePolicies', { + const params = { listAll: true, id: this.selectedPolicyId - }).then(response => { + } + addProjectFilter(params, this.resource) - api('listAutoScalePolicies', params).then(response => { ++ getAPI('listAutoScalePolicies', params).then(response => { this.policy = response.listautoscalepoliciesresponse?.autoscalepolicy[0] }).finally(() => { this.loading = false diff --cc ui/src/views/compute/AutoScaleVmProfile.vue index 9d944c59898,bdb2ec012b5..af014c517d5 --- a/ui/src/views/compute/AutoScaleVmProfile.vue +++ b/ui/src/views/compute/AutoScaleVmProfile.vue @@@ -307,7 -307,8 +307,8 @@@ </template> <script> -import { api } from '@/api' +import { getAPI, postAPI } from '@/api' + import { addProjectFilter } from '@/utils/util' import { isAdmin, isAdminOrDomainAdmin } from '@/role' import Status from '@/components/widgets/Status' import TooltipButton from '@/components/widgets/TooltipButton' @@@ -422,9 -424,9 +424,10 @@@ export default domainid: this.resource.domainid, account: this.resource.account } + addProjectFilter(params, this.resource) if (isAdmin()) { params.templatefilter = 'all' + params.isready = true } else { params.templatefilter = 'executable' } @@@ -447,10 -450,12 +451,12 @@@ }, fetchData () { this.loading = true - getAPI('listAutoScaleVmProfiles', { + const params = { listAll: true, id: this.resource.vmprofileid - }).then(response => { + } + addProjectFilter(params, this.resource) - api('listAutoScaleVmProfiles', params).then(response => { ++ getAPI('listAutoScaleVmProfiles', params).then(response => { this.profileid = response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.id this.autoscaleuserid = response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.autoscaleuserid this.expungevmgraceperiod = response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.expungevmgraceperiod
