This is an automated email from the ASF dual-hosted git repository. pearl11594 pushed a commit to branch netris-integration-upstream in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit e02ffed25e10a1b15e825532e429b16ef6e6be0a Author: Pearl Dsilva <pearl1...@gmail.com> AuthorDate: Wed Feb 12 11:29:52 2025 -0500 Phase5 - Support for LB - create, delete and Update operations (#49) * Add support for Netris ACLs * acl support * Make acl api call to netris to create the rule * refactor add acl rule to populate the right fields * support icmp type acl rule * acl rule creation - move netrisnetworkRule * Update ACL naming on Netris * Add support for Deletion of netris acls * Add support to delete and re-order ACL rules * support creation of default acl rules and replacing acl rules * fix NSXNetworkRule * Fix naming convention for NAT subnets to follow other resources * Use vpc ID for nat subnets * Phase5 - Support for LB - create, delete and Update operations * Use new nat subnet name for deletion of static nat rule * add support to add netris lb rule * support deletion of LB rule on Netris * add checks when editing unsupported fields of LB rule for Netris and hide columns on the UI * fix test failure * fix imports * add license * address comments --- .../com/cloud/network/netris/NetrisLbBackend.java | 41 ++++ .../cloud/network/netris/NetrisNetworkRule.java | 14 ++ .../com/cloud/network/netris/NetrisService.java | 3 + ...reateOrUpdateNetrisLoadBalancerRuleCommand.java | 72 ++++++ .../api/DeleteNetrisLoadBalancerRuleCommand.java | 34 +++ .../apache/cloudstack/resource/NetrisResource.java | 31 +++ .../resource/NetrisResourceObjectUtils.java | 7 +- .../apache/cloudstack/service/NetrisApiClient.java | 4 + .../cloudstack/service/NetrisApiClientImpl.java | 269 +++++++++++++++++++-- .../apache/cloudstack/service/NetrisElement.java | 92 ++++++- .../cloudstack/service/NetrisServiceImpl.java | 23 ++ .../network/lb/LoadBalancingRulesManagerImpl.java | 14 ++ .../cloud/network/lb/UpdateLoadBalancerTest.java | 7 +- .../cloudstack/service/NetrisServiceMockTest.java | 10 + ui/src/views/network/LoadBalancing.vue | 33 ++- 15 files changed, 617 insertions(+), 37 deletions(-) diff --git a/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java b/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java new file mode 100644 index 00000000000..e2dfd6b59c1 --- /dev/null +++ b/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java @@ -0,0 +1,41 @@ +// 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.network.netris; + +public class NetrisLbBackend { + private long vmId; + private String vmIp; + private int port; + + public NetrisLbBackend(long vmId, String vmIp, int port) { + this.vmId = vmId; + this.vmIp = vmIp; + this.port = port; + } + + public long getVmId() { + return vmId; + } + + public String getVmIp() { + return vmIp; + } + + public int getPort() { + return port; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java b/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java index 67612141f65..dcd7bd22acb 100644 --- a/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java +++ b/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java @@ -19,6 +19,8 @@ package com.cloud.network.netris; import com.cloud.network.SDNProviderNetworkRule; +import java.util.List; + public class NetrisNetworkRule { public enum NetrisRuleAction { PERMIT, DENY @@ -26,11 +28,13 @@ public class NetrisNetworkRule { private SDNProviderNetworkRule baseRule; private NetrisRuleAction aclAction; + private List<NetrisLbBackend> lbBackends; private String reason; public NetrisNetworkRule(Builder builder) { this.baseRule = builder.baseRule; this.aclAction = builder.aclAction; + this.lbBackends = builder.lbBackends; this.reason = builder.reason; } @@ -38,6 +42,10 @@ public class NetrisNetworkRule { return aclAction; } + public List<NetrisLbBackend> getLbBackends() { + return lbBackends; + } + public String getReason() { return reason; } @@ -50,6 +58,7 @@ public class NetrisNetworkRule { public static class Builder { private SDNProviderNetworkRule baseRule; private NetrisRuleAction aclAction; + private List<NetrisLbBackend> lbBackends; private String reason; public Builder baseRule(SDNProviderNetworkRule baseRule) { @@ -62,6 +71,11 @@ public class NetrisNetworkRule { return this; } + public Builder lbBackends(List<NetrisLbBackend> lbBackends) { + this.lbBackends = lbBackends; + return this; + } + public Builder reason(String reason) { this.reason = reason; return this; diff --git a/api/src/main/java/com/cloud/network/netris/NetrisService.java b/api/src/main/java/com/cloud/network/netris/NetrisService.java index 76a239422e0..d386d4f8d58 100644 --- a/api/src/main/java/com/cloud/network/netris/NetrisService.java +++ b/api/src/main/java/com/cloud/network/netris/NetrisService.java @@ -54,4 +54,7 @@ public interface NetrisService { boolean deleteStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId); boolean releaseNatIp(long zoneId, String publicIp); + + boolean createLbRule(NetrisNetworkRule rule); + boolean deleteLbRule(NetrisNetworkRule rule); } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisLoadBalancerRuleCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisLoadBalancerRuleCommand.java new file mode 100644 index 00000000000..34aeb4ca5e0 --- /dev/null +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisLoadBalancerRuleCommand.java @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.agent.api; + +import com.cloud.network.netris.NetrisLbBackend; + +import java.util.List; + +public class CreateOrUpdateNetrisLoadBalancerRuleCommand extends NetrisCommand { + private final String publicPort; + private final String privatePort; + private final String algorithm; + private final String protocol; + List<NetrisLbBackend> lbBackends; + private String publicIp; + private final Long lbId; + + public CreateOrUpdateNetrisLoadBalancerRuleCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, + List<NetrisLbBackend> lbBackends, long lbId, String publicIp, String publicPort, + String privatePort, String algorithm, String protocol) { + super(zoneId, accountId, domainId, name, id, isVpc); + this.lbId = lbId; + this.publicIp = publicIp; + this.publicPort = publicPort; + this.privatePort = privatePort; + this.algorithm = algorithm; + this.protocol = protocol; + this.lbBackends = lbBackends; + } + + public String getPublicPort() { + return publicPort; + } + + public String getPrivatePort() { + return privatePort; + } + + public String getAlgorithm() { + return algorithm; + } + + public String getProtocol() { + return protocol; + } + + public List<NetrisLbBackend> getLbBackends() { + return lbBackends; + } + + public Long getLbId() { + return lbId; + } + + public String getPublicIp() { + return publicIp; + } +} diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisLoadBalancerRuleCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisLoadBalancerRuleCommand.java new file mode 100644 index 00000000000..c114466d729 --- /dev/null +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisLoadBalancerRuleCommand.java @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.agent.api; + +public class DeleteNetrisLoadBalancerRuleCommand extends NetrisCommand { + private Long lbId; + + public DeleteNetrisLoadBalancerRuleCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, Long lbId) { + super(zoneId, accountId, domainId, name, id, isVpc); + this.lbId = lbId; + } + + public Long getLbId() { + return lbId; + } + + public void setLbId(Long lbId) { + this.lbId = lbId; + } +} diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java index 989987b9a41..5d4777d610c 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java @@ -32,8 +32,10 @@ import org.apache.cloudstack.agent.api.CreateNetrisACLCommand; import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; @@ -118,6 +120,10 @@ public class NetrisResource implements ServerResource { return executeRequest((AddOrUpdateNetrisStaticRouteCommand) cmd); } else if (cmd instanceof ReleaseNatIpCommand) { return executeRequest((ReleaseNatIpCommand) cmd); + } else if (cmd instanceof CreateOrUpdateNetrisLoadBalancerRuleCommand) { + return executeRequest((CreateOrUpdateNetrisLoadBalancerRuleCommand) cmd); + } else if (cmd instanceof DeleteNetrisLoadBalancerRuleCommand) { + return executeRequest((DeleteNetrisLoadBalancerRuleCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -352,6 +358,31 @@ public class NetrisResource implements ServerResource { return new NetrisAnswer(cmd, true, "OK"); } + private Answer executeRequest(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd) { + boolean result = netrisApiClient.createLbRule(cmd); + if (!result) { + return new NetrisAnswer(cmd, false, String.format("Failed to create Netris LB rule for %s: %s, " + + "for private port: %s and public port: %s", getNetworkType(cmd.isVpc()), cmd.getName(), cmd.getPrivatePort(), cmd.getPublicPort(), cmd.getPublicPort())); + } + return new NetrisAnswer(cmd, true, "OK"); + } + + private Answer executeRequest(DeleteNetrisLoadBalancerRuleCommand cmd) { + boolean result = netrisApiClient.deleteLbRule(cmd); + if (!result) { + return new NetrisAnswer(cmd, false, String.format("Failed to delete Netris LB rule for %s: %s, " + + "for private port: %s and public port: %s", getNetworkType(cmd.isVpc()), cmd.getName())); + } + return new NetrisAnswer(cmd, true, "OK"); + } + + private String getNetworkType(Boolean isVpc) { + if (isVpc) { + return "VPC"; + } + return "Network"; + } + @Override public boolean start() { return true; diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java index 9caff9793ff..296f74b1c29 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java @@ -24,7 +24,7 @@ import java.util.Objects; public class NetrisResourceObjectUtils { public enum NetrisObjectType { - VPC, IPAM_ALLOCATION, IPAM_SUBNET, VNET, SNAT, STATICNAT, DNAT, STATICROUTE, ACL + VPC, IPAM_ALLOCATION, IPAM_SUBNET, VNET, SNAT, STATICNAT, DNAT, STATICROUTE, ACL, LB } public static String retrieveNetrisResourceObjectName(NetrisCommand cmd, NetrisObjectType netrisObjectType, String... suffixes) { @@ -59,7 +59,7 @@ public class NetrisResourceObjectUtils { break; case IPAM_SUBNET: if (!isZoneLevel) { - if (Objects.nonNull(objectId) && Objects.nonNull(objectName)) { + if (Objects.nonNull(objectId) && Objects.nonNull(objectName) && !isVpc) { stringBuilder.append(String.format("-N%s", objectId)); } else { stringBuilder.append(String.format("-V%s-%s", suffixes[0], suffixes[1])); @@ -86,6 +86,9 @@ public class NetrisResourceObjectUtils { case VNET: case ACL: break; + case LB: + stringBuilder.append(String.format("%s%s", prefix, objectId)); + break; default: stringBuilder.append(String.format("-%s", objectName)); break; diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java index f5efb4b55ea..91351bf21f7 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java @@ -24,8 +24,10 @@ import org.apache.cloudstack.agent.api.CreateNetrisACLCommand; import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; @@ -84,4 +86,6 @@ public interface NetrisApiClient { boolean addOrUpdateStaticRoute(AddOrUpdateNetrisStaticRouteCommand cmd); boolean deleteStaticRoute(DeleteNetrisStaticRouteCommand cmd); boolean releaseNatIp(ReleaseNatIpCommand cmd); + boolean createLbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd); + boolean deleteLbRule(DeleteNetrisLoadBalancerRuleCommand cmd); } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java index e61482a3564..e49c7c5882a 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java @@ -16,8 +16,10 @@ // under the License. package org.apache.cloudstack.service; +import com.cloud.network.netris.NetrisLbBackend; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressString; import io.netris.ApiClient; @@ -29,6 +31,7 @@ import io.netris.api.v1.RoutesApi; import io.netris.api.v1.SitesApi; import io.netris.api.v1.TenantsApi; import io.netris.api.v2.IpamApi; +import io.netris.api.v2.L4LoadBalancerApi; import io.netris.api.v2.NatApi; import io.netris.api.v2.VNetApi; import io.netris.api.v2.VpcApi; @@ -52,12 +55,20 @@ import io.netris.model.IpTreeAllocation; import io.netris.model.IpTreeAllocationTenant; import io.netris.model.IpTreeSubnet; import io.netris.model.IpTreeSubnetSites; +import io.netris.model.L4LBSite; +import io.netris.model.L4LbTenant; +import io.netris.model.L4LbVpc; +import io.netris.model.L4LoadBalancerBackendItem; +import io.netris.model.L4LoadBalancerItem; +import io.netris.model.L4lbAddItem; +import io.netris.model.L4lbresBody; import io.netris.model.NatBodySiteSite; import io.netris.model.NatBodyVpcVpc; import io.netris.model.NatGetBody; import io.netris.model.NatPostBody; import io.netris.model.NatPutBody; import io.netris.model.NatResponseGetOk; +import io.netris.model.ResAddEditBody; import io.netris.model.RoutesBody; import io.netris.model.RoutesBodyId; import io.netris.model.RoutesBodyVpcVpc; @@ -90,10 +101,12 @@ import io.netris.model.response.TenantResponse; import io.netris.model.response.TenantsResponse; import org.apache.cloudstack.agent.api.CreateNetrisACLCommand; import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; @@ -598,6 +611,169 @@ public class NetrisApiClientImpl implements NetrisApiClient { return true; } + @Override + public boolean createLbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd) { + boolean isVpc = cmd.isVpc(); + Long networkResourceId = cmd.getId(); + String networkResourceName = cmd.getName(); + Long domainId = cmd.getDomainId(); + Long accountId = cmd.getAccountId(); + Long zoneId = cmd.getZoneId(); + Long lbId = cmd.getLbId(); + String publicIp = cmd.getPublicIp(); + List<NetrisLbBackend> lbBackends = cmd.getLbBackends(); + + try { + String resourcePrefix = isVpc ? "V" : "N"; + String netrisResourceName = String.format("D%s-A%s-Z%s-%s%s-%s", domainId, accountId, zoneId, resourcePrefix, networkResourceId, networkResourceName); + VPCListing vpcResource = getNetrisVpcResource(netrisResourceName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisResourceName, tenantId); + return false; + } + createLBSubnet(cmd, publicIp + "/32", vpcResource.getId()); + + String suffix = String.format("LB%s", lbId); + String lbName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.LB, suffix); + Pair<Boolean, List<BigDecimal>> resultAndMatchingLbId = getMatchingLbRule(lbName, vpcResource.getName()); + Boolean result = resultAndMatchingLbId.first(); + List<BigDecimal> matchingLbId = resultAndMatchingLbId.second(); + if (Boolean.FALSE.equals(result)) { + logger.warn("Could not find the Netris LB rule with name {}", lbName); + } + if (!matchingLbId.isEmpty()) { + logger.warn("LB rule by name: {} already exists", lbName); + return true; + } + + L4lbAddItem l4lbAddItem = getL4LbRule(cmd, vpcResource, lbName, publicIp, lbBackends); + L4LoadBalancerApi loadBalancerApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class); + ResAddEditBody response = loadBalancerApi.apiV2L4lbPost(l4lbAddItem); + if (Objects.isNull(response) || Boolean.FALSE.equals(response.isIsSuccess())) { + throw new CloudRuntimeException("Failed to create Netris LB rule"); + } + } catch (ApiException e) { + logAndThrowException("Failed to create Netris load balancer rule", e); + } + return true; + } + + private L4lbAddItem getL4LbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, VPCListing vpcResource, String lbName, + String publicIp, List<NetrisLbBackend> lbBackends) { + L4lbAddItem l4lbAddItem = new L4lbAddItem(); + try { + l4lbAddItem.setName(lbName); + + String protocol = cmd.getProtocol().toUpperCase(Locale.ROOT); + if (!Arrays.asList("TCP", "UDP").contains(protocol)) { + throw new CloudRuntimeException("Invalid protocol " + protocol); + } + l4lbAddItem.setProtocol(cmd.getProtocol().toUpperCase(Locale.ROOT)); + L4LBSite site = new L4LBSite(); + site.setId(siteId); + site.setName(siteName); + l4lbAddItem.setSite(site); + l4lbAddItem.setSiteID(new BigDecimal(siteId)); + + L4LbTenant tenant = new L4LbTenant(); + tenant.setId(tenantId); + tenant.setName(tenantName); + l4lbAddItem.setTenant(tenant); + + L4LbVpc vpc = new L4LbVpc(); + vpc.setId(vpcResource.getId()); + l4lbAddItem.setVpc(vpc); + + l4lbAddItem.setAutomatic(false); + l4lbAddItem.setIpFamily(NetUtils.isIpv4(publicIp) ? L4lbAddItem.IpFamilyEnum.IPv4 : L4lbAddItem.IpFamilyEnum.IPv6); + l4lbAddItem.setIp(publicIp); + l4lbAddItem.setStatus("enable"); + + List<L4LoadBalancerBackendItem> backends = new ArrayList<>(); + for (NetrisLbBackend backend : lbBackends) { + L4LoadBalancerBackendItem backendItem = new L4LoadBalancerBackendItem(); + backendItem.setIp(backend.getVmIp()); + backendItem.setPort(backend.getPort()); + backends.add(backendItem); + } + l4lbAddItem.setBackend(backends); + l4lbAddItem.setPort(Integer.valueOf(cmd.getPublicPort())); + l4lbAddItem.setHealthCheck(L4lbAddItem.HealthCheckEnum.NONE); + } catch (Exception e) { + throw new CloudRuntimeException("Failed to create Netris load balancer rule", e); + } + return l4lbAddItem; + } + + @Override + public boolean deleteLbRule(DeleteNetrisLoadBalancerRuleCommand cmd) { + boolean isVpc = cmd.isVpc(); + String vpcName = null; + String networkName = null; + Long vpcId = null; + Long networkId = null; + if (isVpc) { + vpcName = cmd.getName(); + vpcId = cmd.getId(); + } else { + networkName = cmd.getName(); + networkId = cmd.getId(); + } + Long lbId = cmd.getLbId(); + try { + String suffix = getNetrisVpcNameSuffix(vpcId, vpcName, networkId, networkName, isVpc); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix); + suffix = String.format("LB%s", lbId); + String lbName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.LB, suffix); + Pair<Boolean, List<BigDecimal>> resultAndMatchingLbId = getMatchingLbRule(lbName, netrisVpcName); + Boolean result = resultAndMatchingLbId.first(); + List<BigDecimal> matchingLbId = resultAndMatchingLbId.second(); + if (!result) { + logger.error("Could not find the Netris LB rule with name {}", lbName); + return false; + } + if (matchingLbId.isEmpty()) { + logger.warn("There doesn't seem to be any LB rule on Netris matching {}", lbName); + return true; + } + + L4LoadBalancerApi lbApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class); + lbApi.apiV2L4lbIdDelete(matchingLbId.get(0).intValue()); + } catch (ApiException e) { + logAndThrowException("Failed to delete Netris load balancer rule", e); + } + return true; + } + + private Pair<Boolean, List<BigDecimal>> getMatchingLbRule(String lbName, String vpcName) { + try { + VPCListing vpcResource = getVpcByNameAndTenant(vpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId); + return new Pair<>(false, Collections.emptyList()); + } + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(vpcResource.getId()); + FilterBySites siteFilter = new FilterBySites(); + siteFilter.add(siteId); + L4LoadBalancerApi lbApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class); + L4lbresBody lbGetResponse = lbApi.apiV2L4lbGet(siteFilter, vpcFilter); + if (lbGetResponse == null || !lbGetResponse.isIsSuccess()) { + logger.warn("No LB rules were found to be present for the specific Netris VPC resource {}." + + " Netris LB rules may have been deleted out of band.", vpcName); + return new Pair<>(true, Collections.emptyList()); + } + List<L4LoadBalancerItem> lbList = lbGetResponse.getData(); + return new Pair<>(true, lbList.stream() + .filter(lb -> lbName.equals(lb.getName())) + .map(acl -> BigDecimal.valueOf(acl.getId())) + .collect(Collectors.toList())); + } catch (ApiException e) { + logAndThrowException("Failed to retrieve Netris LB rules", e); + } + return new Pair<>(true, Collections.emptyList()); + } + private Pair<Boolean, RoutesGetBody> staticRouteExists(Integer netrisVpcId, String prefix, String nextHop, String description) { try { FilterByVpc vpcFilter = new FilterByVpc(); @@ -904,7 +1080,7 @@ public class NetrisApiClientImpl implements NetrisApiClient { } if (StringUtils.isNotBlank(targetIpSubnet) && existsDestinationSubnet(targetIpSubnet)) { - logger.debug(String.format("Creating subnet with NAT purpose for %s", targetIpSubnet)); + logger.debug("Creating subnet with NAT purpose for {}}", targetIpSubnet); createNatSubnet(cmd, targetIpSubnet, vpcResource.getId()); } @@ -1022,6 +1198,27 @@ public class NetrisApiClientImpl implements NetrisApiClient { } } + private void createLBSubnet(NetrisCommand cmd, String lbIp, Integer netrisVpcId) { + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(netrisVpcId); + String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, + NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, + String.valueOf(cmd.getId()), lbIp); + List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName); + VPCListing systemVpc = getSystemVpc(); + if (matchedSubnets.isEmpty()) { + createIpamSubnetInternal(netrisSubnetName, lbIp, SubnetBody.PurposeEnum.LOAD_BALANCER, systemVpc, null); + } else if (IpTreeSubnet.PurposeEnum.LOAD_BALANCER != matchedSubnets.get(0).getPurpose()){ + logger.debug("Updating existing NAT subnet {} to have load balancer purpose", netrisSubnetName); + updateIpamSubnetInternal(matchedSubnets.get(0).getId().intValue(), netrisSubnetName, lbIp, SubnetBody.PurposeEnum.LOAD_BALANCER, systemVpc, null); + } + logger.debug("LB subnet: {} already exists", netrisSubnetName); + } catch (ApiException e) { + throw new CloudRuntimeException(String.format("Failed to create subnet for %s with LB purpose", lbIp)); + } + } + private NatPostBody.ProtocolEnum getProtocolFromString(String protocol) { return NatPostBody.ProtocolEnum.fromValue(protocol); } @@ -1204,33 +1401,38 @@ public class NetrisApiClientImpl implements NetrisApiClient { } } + private SubnetBody getIpamSubnetBody(VPCListing vpc, SubnetBody.PurposeEnum purpose, String subnetName, String subnetPrefix, Boolean isGlobalRouting) { + SubnetBody subnetBody = new SubnetBody(); + subnetBody.setName(subnetName); + + AllocationBodyVpc vpcAllocationBody = new AllocationBodyVpc(); + vpcAllocationBody.setName(vpc.getName()); + vpcAllocationBody.setId(vpc.getId()); + subnetBody.setVpc(vpcAllocationBody); + + IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); + allocationTenant.setId(new BigDecimal(tenantId)); + allocationTenant.setName(tenantName); + subnetBody.setTenant(allocationTenant); + + IpTreeSubnetSites subnetSites = new IpTreeSubnetSites(); + subnetSites.setId(new BigDecimal(siteId)); + subnetSites.setName(siteName); + subnetBody.setSites(List.of(subnetSites)); + + subnetBody.setPurpose(purpose); + subnetBody.setPrefix(subnetPrefix); + if (isGlobalRouting != null) { + subnetBody.setGlobalRouting(isGlobalRouting); + } + return subnetBody; + } + private InlineResponse2004Data createIpamSubnetInternal(String subnetName, String subnetPrefix, SubnetBody.PurposeEnum purpose, VPCListing vpc, Boolean isGlobalRouting) { logger.debug("Creating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName()); try { - SubnetBody subnetBody = new SubnetBody(); - subnetBody.setName(subnetName); - - AllocationBodyVpc vpcAllocationBody = new AllocationBodyVpc(); - vpcAllocationBody.setName(vpc.getName()); - vpcAllocationBody.setId(vpc.getId()); - subnetBody.setVpc(vpcAllocationBody); - - IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant(); - allocationTenant.setId(new BigDecimal(tenantId)); - allocationTenant.setName(tenantName); - subnetBody.setTenant(allocationTenant); - - IpTreeSubnetSites subnetSites = new IpTreeSubnetSites(); - subnetSites.setId(new BigDecimal(siteId)); - subnetSites.setName(siteName); - subnetBody.setSites(List.of(subnetSites)); - - subnetBody.setPurpose(purpose); - subnetBody.setPrefix(subnetPrefix); - if (isGlobalRouting != null) { - subnetBody.setGlobalRouting(isGlobalRouting); - } + SubnetBody subnetBody = getIpamSubnetBody(vpc, purpose, subnetName, subnetPrefix, isGlobalRouting); IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); InlineResponse2004 subnetResponse = ipamApi.apiV2IpamSubnetPost(subnetBody); if (subnetResponse == null || !subnetResponse.isIsSuccess()) { @@ -1245,6 +1447,25 @@ public class NetrisApiClientImpl implements NetrisApiClient { } } + private InlineResponse2004Data updateIpamSubnetInternal(Integer netrisSubnetId, String subnetName, String subnetPrefix, SubnetBody.PurposeEnum purpose, VPCListing vpc, Boolean isGlobalRouting) { + logger.debug("Updating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName()); + try { + + SubnetBody subnetBody = getIpamSubnetBody(vpc, purpose, subnetName, subnetPrefix, isGlobalRouting); + IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class); + InlineResponse2004 subnetResponse = ipamApi.apiV2IpamSubnetIdPut(subnetBody, netrisSubnetId); + if (subnetResponse == null || !subnetResponse.isIsSuccess()) { + String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris IPAM Subnet {} update failed: {}", subnetName, reason); + throw new CloudRuntimeException(reason); + } + return subnetResponse.getData(); + } catch (ApiException e) { + logAndThrowException(String.format("Error Updating Netris IPAM Subnet %s for VPC %s", subnetPrefix, vpc.getName()), e); + return null; + } + } + VnetResAddBody createVnetInternal(VPCListing associatedVpc, String netrisVnetName, String netrisGateway, String netrisV6Gateway, Integer vxlanId, String netrisTag) { logger.debug("Creating Netris VPC vNet {} for CIDR {}", netrisVnetName, netrisGateway); try { diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java index 0f25d216719..12e7239a8ee 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java @@ -23,6 +23,7 @@ import com.cloud.agent.api.AgentControlCommand; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.to.LoadBalancerTO; import com.cloud.api.ApiDBUtils; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; @@ -45,16 +46,21 @@ import com.cloud.network.SDNProviderNetworkRule; import com.cloud.network.SDNProviderOpObject; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.LoadBalancerVMMapDao; +import com.cloud.network.dao.LoadBalancerVMMapVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.element.DhcpServiceProvider; import com.cloud.network.element.DnsServiceProvider; import com.cloud.network.element.IpDeployer; +import com.cloud.network.element.LoadBalancingServiceProvider; import com.cloud.network.element.NetworkACLServiceProvider; import com.cloud.network.element.PortForwardingServiceProvider; import com.cloud.network.element.StaticNatServiceProvider; import com.cloud.network.element.VirtualRouterElement; import com.cloud.network.element.VpcProvider; +import com.cloud.network.lb.LoadBalancingRule; +import com.cloud.network.netris.NetrisLbBackend; import com.cloud.network.netris.NetrisService; import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.LoadBalancerContainer; @@ -107,7 +113,8 @@ import java.util.Set; @Component public class NetrisElement extends AdapterBase implements DhcpServiceProvider, DnsServiceProvider, VpcProvider, - StaticNatServiceProvider, IpDeployer, PortForwardingServiceProvider, NetworkACLServiceProvider, ResourceStateAdapter, Listener { + StaticNatServiceProvider, IpDeployer, PortForwardingServiceProvider, NetworkACLServiceProvider, + LoadBalancingServiceProvider, ResourceStateAdapter, Listener { @Inject NetworkModel networkModel; @@ -133,6 +140,8 @@ public class NetrisElement extends AdapterBase implements DhcpServiceProvider, D private IPAddressDao ipAddressDao; @Inject private VMInstanceDao vmInstanceDao; + @Inject + LoadBalancerVMMapDao lbVmMapDao; protected Logger logger = LogManager.getLogger(getClass()); @@ -693,4 +702,85 @@ public class NetrisElement extends AdapterBase implements DhcpServiceProvider, D return 0; } } + + private SDNProviderOpObject getNetrisObject(Network network) { + Pair<VpcVO, NetworkVO> vpcOrNetwork = getVpcOrNetwork(network.getVpcId(), network.getId()); + VpcVO vpc = vpcOrNetwork.first(); + NetworkVO networkVO = vpcOrNetwork.second(); + long domainId = getResourceId("domain", vpc, networkVO); + long accountId = getResourceId("account", vpc, networkVO); + long zoneId = getResourceId("zone", vpc, networkVO); + + return new SDNProviderOpObject.Builder() + .vpcVO(vpc) + .networkVO(networkVO) + .domainId(domainId) + .accountId(accountId) + .zoneId(zoneId) + .build(); + } + + @Override + public boolean applyLBRules(Network network, List<LoadBalancingRule> rules) throws ResourceUnavailableException { + boolean result = true; + for (LoadBalancingRule loadBalancingRule : rules) { + IPAddressVO publicIp = ipAddressDao.findByIpAndDcId(network.getDataCenterId(), + loadBalancingRule.getSourceIp().addr()); + + List<NetrisLbBackend> lbBackends = getLoadBalancerBackends(loadBalancingRule); + SDNProviderOpObject netrisObject = getNetrisObject(network); + SDNProviderNetworkRule baseNetworkRule = new SDNProviderNetworkRule.Builder() + .setDomainId(netrisObject.getDomainId()) + .setAccountId(netrisObject.getAccountId()) + .setZoneId(netrisObject.getZoneId()) + .setNetworkResourceId(netrisObject.getNetworkResourceId()) + .setNetworkResourceName(netrisObject.getNetworkResourceName()) + .setVpcResource(netrisObject.isVpcResource()) + .setPublicIp(LoadBalancerContainer.Scheme.Public == loadBalancingRule.getScheme() ? + publicIp.getAddress().addr() : loadBalancingRule.getSourceIp().addr()) + .setPrivatePort(String.valueOf(loadBalancingRule.getDefaultPortStart())) + .setPublicPort(String.valueOf(loadBalancingRule.getSourcePortStart())) + .setRuleId(loadBalancingRule.getId()) + .setProtocol(loadBalancingRule.getProtocol().toUpperCase(Locale.ROOT)) + .setAlgorithm(loadBalancingRule.getAlgorithm()) + .build(); + + NetrisNetworkRule networkRule = new NetrisNetworkRule.Builder() + .baseRule(baseNetworkRule) + .lbBackends(lbBackends) + .build(); + if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(loadBalancingRule.getState())) { + result &= netrisService.createLbRule(networkRule); + } else if (loadBalancingRule.getState() == FirewallRule.State.Revoke) { + result &= netrisService.deleteLbRule(networkRule); + } + } + return result; + } + + private List<NetrisLbBackend> getLoadBalancerBackends(LoadBalancingRule lbRule) { + List<LoadBalancerVMMapVO> lbVms = lbVmMapDao.listByLoadBalancerId(lbRule.getId(), false); + List<NetrisLbBackend> lbMembers = new ArrayList<>(); + + for (LoadBalancerVMMapVO lbVm : lbVms) { + NetrisLbBackend member = new NetrisLbBackend(lbVm.getInstanceId(), lbVm.getInstanceIp(), lbRule.getDefaultPortStart()); + lbMembers.add(member); + } + return lbMembers; + } + + @Override + public boolean validateLBRule(Network network, LoadBalancingRule rule) { + return true; + } + + @Override + public List<LoadBalancerTO> updateHealthChecks(Network network, List<LoadBalancingRule> lbrules) { + return List.of(); + } + + @Override + public boolean handlesOnlyRulesInTransitionState() { + return false; + } } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisServiceImpl.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisServiceImpl.java index 337ea849dcb..62064c1ac07 100644 --- a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisServiceImpl.java +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisServiceImpl.java @@ -44,10 +44,12 @@ import com.cloud.utils.net.NetUtils; import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressString; import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand; +import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand; import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand; import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; @@ -455,6 +457,27 @@ public class NetrisServiceImpl implements NetrisService, Configurable { return answer.getResult(); } + @Override + public boolean createLbRule(NetrisNetworkRule rule) { + SDNProviderNetworkRule baseRule = rule.getBaseRule(); + CreateOrUpdateNetrisLoadBalancerRuleCommand cmd = new CreateOrUpdateNetrisLoadBalancerRuleCommand(baseRule.getZoneId(), baseRule.getAccountId(), + baseRule.getDomainId(), baseRule.getNetworkResourceName(), baseRule.getNetworkResourceId(), baseRule.isVpcResource(), + rule.getLbBackends(), baseRule.getRuleId(), baseRule.getPublicIp(), baseRule.getPublicPort(), + baseRule.getPrivatePort(), baseRule.getAlgorithm(), baseRule.getProtocol()); + NetrisAnswer answer = sendNetrisCommand(cmd, baseRule.getZoneId()); + return answer.getResult(); + } + + @Override + public boolean deleteLbRule(NetrisNetworkRule rule) { + SDNProviderNetworkRule baseRule = rule.getBaseRule(); + DeleteNetrisLoadBalancerRuleCommand cmd = new DeleteNetrisLoadBalancerRuleCommand(baseRule.getZoneId(), baseRule.getAccountId(), + baseRule.getDomainId(), baseRule.getNetworkResourceName(), baseRule.getNetworkResourceId(), baseRule.isVpcResource(), + baseRule.getRuleId()); + NetrisAnswer answer = sendNetrisCommand(cmd, baseRule.getZoneId()); + return answer.getResult(); + } + private String getResourceSuffix(Long vpcId, Long networkId, boolean isForVpc) { String suffix; if (isForVpc) { diff --git a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java index 015cbe49049..1615eec4c5c 100644 --- a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java +++ b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; @@ -2245,6 +2246,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements lb.setLbProtocol(lbProtocol); } + validateInputsForExternalNetworkProvider(lb, algorithm, lbProtocol); // Validate rule in LB provider LoadBalancingRule rule = getLoadBalancerRuleToApply(lb); if (!validateLbRule(rule)) { @@ -2294,6 +2296,18 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements return lb; } + private void validateInputsForExternalNetworkProvider(LoadBalancerVO lb, String algorithm, String protocol) { + Network network = _networkDao.findById(lb.getNetworkId()); + if (_networkOfferingServiceDao.canProviderSupportServiceInNetworkOffering(network.getNetworkOfferingId(), Service.Lb, Provider.Netris)) { + if (Objects.nonNull(algorithm)) { + throw new InvalidParameterValueException(String.format("Algorithm: %s specified for Netris Provider is not supported.", algorithm)); + } + if (Objects.nonNull(protocol) && "tcp-proxy".equalsIgnoreCase(protocol)) { + throw new InvalidParameterValueException("TCP Proxy protocol is not supported for Netris Provider."); + } + } + } + @Override public Pair<List<? extends UserVm>, List<String>> listLoadBalancerInstances(ListLoadBalancerRuleInstancesCmd cmd) throws PermissionDeniedException { Account caller = CallContext.current().getCallingAccount(); diff --git a/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java b/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java index c3e8923b943..729d2ea8ff5 100644 --- a/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java +++ b/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java @@ -33,6 +33,7 @@ import com.cloud.network.dao.LoadBalancerVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.MockAccountManagerImpl; @@ -52,6 +53,7 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -63,6 +65,7 @@ public class UpdateLoadBalancerTest { private LoadBalancerDao lbDao = Mockito.mock(LoadBalancerDao.class); private NetworkDao netDao = Mockito.mock(NetworkDao.class); private NetworkModel netModel = Mockito.mock(NetworkModel.class); + private NetworkOfferingServiceMapDao ntwkOffServiceMapDao = Mockito.mock(NetworkOfferingServiceMapDao.class); private LoadBalancingServiceProvider lbServiceProvider= Mockito.mock(LoadBalancingServiceProvider.class); private static long domainId = 5L; @@ -73,6 +76,7 @@ public class UpdateLoadBalancerTest { _lbMgr._accountMgr = new MockAccountManagerImpl(); _lbMgr._autoScaleVmGroupDao = Mockito.mock(AutoScaleVmGroupDao.class); _lbMgr._networkDao = netDao; + _lbMgr._networkOfferingServiceDao = ntwkOffServiceMapDao; _lbMgr._networkModel = netModel; _lbMgr._lb2healthcheckDao = Mockito.mock(LBHealthCheckPolicyDao.class); _lbMgr._lb2stickinesspoliciesDao = Mockito.mock(LBStickinessPolicyDao.class); @@ -99,7 +103,8 @@ public class UpdateLoadBalancerTest { when(netDao.findById(anyLong())).thenReturn(Mockito.mock(NetworkVO.class)); when(lbServiceProvider.validateLBRule(any(Network.class), any(LoadBalancingRule.class))).thenReturn(true); when(lbDao.update(isNull(), eq(lb))).thenReturn(true); - + when(netDao.findById(nullable(Long.class))).thenReturn(Mockito.mock(NetworkVO.class)); + when(ntwkOffServiceMapDao.canProviderSupportServiceInNetworkOffering(nullable(Long.class), any(Network.Service.class), any(Network.Provider.class))).thenReturn(false); _lbMgr.updateLoadBalancerRule(updateLbRuleCmd); InOrder inOrder = Mockito.inOrder(lbServiceProvider, lbDao); diff --git a/server/src/test/java/org/apache/cloudstack/service/NetrisServiceMockTest.java b/server/src/test/java/org/apache/cloudstack/service/NetrisServiceMockTest.java index b8e4835f589..99f319d6de7 100644 --- a/server/src/test/java/org/apache/cloudstack/service/NetrisServiceMockTest.java +++ b/server/src/test/java/org/apache/cloudstack/service/NetrisServiceMockTest.java @@ -105,4 +105,14 @@ public class NetrisServiceMockTest implements NetrisService { public boolean releaseNatIp(long zoneId, String publicIp) { return true; } + + @Override + public boolean createLbRule(NetrisNetworkRule rule) { + return true; + } + + @Override + public boolean deleteLbRule(NetrisNetworkRule rule) { + return true; + } } diff --git a/ui/src/views/network/LoadBalancing.vue b/ui/src/views/network/LoadBalancing.vue index 09f9a39c4cf..8e3d1e4c90f 100644 --- a/ui/src/views/network/LoadBalancing.vue +++ b/ui/src/views/network/LoadBalancing.vue @@ -40,7 +40,7 @@ <tooltip-label :title="$t('label.sourcecidrlist')" bold :tooltip="createLoadBalancerRuleParams.cidrlist.description" :tooltip-placement="'right'"/> <a-input v-model:value="newRule.cidrlist"></a-input> </div> - <div class="form__item"> + <div class="form__item" v-if="lbProvider !== 'Netris'"> <div class="form__label">{{ $t('label.algorithm') }}</div> <a-select v-model:value="newRule.algorithm" @@ -64,7 +64,7 @@ :filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > - <a-select-option value="tcp-proxy" :label="$t('label.tcp.proxy')">{{ $t('label.tcp.proxy') }}</a-select-option> + <a-select-option v-if="lbProvider !== 'Netris'" value="tcp-proxy" :label="$t('label.tcp.proxy')">{{ $t('label.tcp.proxy') }}</a-select-option> <a-select-option value="tcp" :label="$t('label.tcp')">{{ $t('label.tcp') }}</a-select-option> <a-select-option value="udp" :label="$t('label.udp')">{{ $t('label.udp') }}</a-select-option> </a-select> @@ -409,7 +409,7 @@ <p class="edit-rule__label">{{ $t('label.name') }}</p> <a-input v-focus="true" v-model:value="editRuleDetails.name" /> </div> - <div class="edit-rule__item"> + <div v-if="lbProvider !== 'Netris'" class="edit-rule__item"> <p class="edit-rule__label">{{ $t('label.algorithm') }}</p> <a-select v-model:value="editRuleDetails.algorithm" @@ -423,7 +423,7 @@ <a-select-option value="source" :label="$t('label.lb.algorithm.source')">{{ $t('label.lb.algorithm.source') }}</a-select-option> </a-select> </div> - <div class="edit-rule__item"> + <div v-if="lbProvider !== 'Netris'" class="edit-rule__item"> <p class="edit-rule__label">{{ $t('label.protocol') }}</p> <a-select v-model:value="editRuleDetails.protocol" @@ -781,9 +781,11 @@ export default { vmguestip: [], cidrlist: '' }, + lbProvider: null, addVmModalVisible: false, addVmModalLoading: false, addVmModalNicLoading: false, + zoneloading: false, vms: [], nics: [], totalCount: 0, @@ -802,10 +804,6 @@ export default { title: this.$t('label.privateport'), dataIndex: 'privateport' }, - { - key: 'algorithm', - title: this.$t('label.algorithm') - }, { key: 'cidrlist', title: this.$t('label.sourcecidrlist') @@ -979,6 +977,7 @@ export default { fetchData () { this.fetchListTiers() this.fetchLBRules() + this.fetchZone() }, fetchListTiers () { this.tiers.loading = true @@ -1073,6 +1072,22 @@ export default { }) }) }, + fetchZone () { + this.zoneloading = true + api('listZones', { + id: this.resource.zoneid + }).then(response => { + this.lbProvider = response?.listzonesresponse?.zone?.[0]?.provider || null + }).finally(() => { + this.zoneloading = false + if (this.lbProvider !== 'Netris') { + this.column.push({ + key: 'algorithm', + title: this.$t('label.algorithm') + }) + } + }) + }, returnAlgorithmName (name) { switch (name) { case 'leastconn': @@ -1377,7 +1392,7 @@ export default { this.selectedRule = rule this.editRuleModalVisible = true this.editRuleDetails.name = this.selectedRule.name - this.editRuleDetails.algorithm = this.selectedRule.algorithm + this.editRuleDetails.algorithm = this.lbProvider !== 'Netris' ? this.selectedRule.algorithm : undefined this.editRuleDetails.protocol = this.selectedRule.protocol }, handleSubmitEditForm () {