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
This commit is contained in:
Pearl Dsilva 2025-02-12 11:29:52 -05:00 committed by GitHub
parent 7f31803232
commit ca4c13c4a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 617 additions and 37 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;
@ -2248,6 +2249,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)) {
@ -2297,6 +2299,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();

View File

@ -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);

View File

@ -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;
}
}

View File

@ -40,7 +40,7 @@
<tooltip-label :title="$t('label.cidrlist')" 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.cidrlist')
@ -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 () {