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

Reply via email to