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 465965a6daa4da88ec5ee598e5df7180586337ba Author: Pearl Dsilva <pearl1...@gmail.com> AuthorDate: Fri Dec 20 07:22:04 2024 -0500 Add support to add and delete and update static routes on Netris (#37) * Add support to add static routes in Netris * support to delete static routes on netris * add defensive check for nextHop * Add support to update static routes * add state * pass empty list for switched to avoid timeout * Netris: search static route by name and next hop if exists --------- Co-authored-by: Wei Zhou <weiz...@apache.org> --- .../com/cloud/network/netris/NetrisService.java | 14 ++ .../java/com/cloud/network/vpc/StaticRoute.java | 1 + .../api/AddOrUpdateNetrisStaticRouteCommand.java | 48 ++++++ .../agent/api/DeleteNetrisStaticRouteCommand.java | 23 +++ .../apache/cloudstack/resource/NetrisResource.java | 24 ++- .../resource/NetrisResourceObjectUtils.java | 6 +- .../apache/cloudstack/service/NetrisApiClient.java | 4 + .../cloudstack/service/NetrisApiClientImpl.java | 181 +++++++++++++++++++++ .../apache/cloudstack/service/NetrisElement.java | 10 ++ .../cloudstack/service/NetrisServiceImpl.java | 16 ++ .../cloudstack/service/NetrisServiceMockTest.java | 10 ++ 11 files changed, 335 insertions(+), 2 deletions(-) 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 d989a53fcdb..355586f7d91 100644 --- a/api/src/main/java/com/cloud/network/netris/NetrisService.java +++ b/api/src/main/java/com/cloud/network/netris/NetrisService.java @@ -22,14 +22,28 @@ import com.cloud.network.vpc.Vpc; public interface NetrisService { boolean createIPAMAllocationsForZoneLevelPublicRanges(long zoneId); + boolean createVpcResource(long zoneId, long accountId, long domainId, Long vpcId, String vpcName, boolean sourceNatEnabled, String cidr, boolean isVpcNetwork); + boolean deleteVpcResource(long zoneId, long accountId, long domainId, Vpc vpc); + boolean createVnetResource(Long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String cidr); + boolean deleteVnetResource(long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String cidr); + boolean createSnatRule(long zoneId, long accountId, long domainId, String vpcName, long vpcId, String networkName, long networkId, boolean isForVpc, String vpcCidr, String sourceNatIp); + boolean createPortForwardingRule(long zoneId, long accountId, long domainId, String vpcName, long vpcId, String networkName, Long networkId, boolean isForVpc, String vpcCidr, SDNProviderNetworkRule networkRule); + boolean deletePortForwardingRule(long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, boolean isForVpc, String vpcCidr, SDNProviderNetworkRule networkRule); + boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address); + boolean createStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String vpcCidr, String staticNatIp, String vmIp); + boolean deleteStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String staticNatIp); + + boolean addOrUpdateStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId, boolean updateRoute); + + boolean deleteStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId); } diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java index 5707ca14024..b52ed980893 100644 --- a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java +++ b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java @@ -25,6 +25,7 @@ public interface StaticRoute extends ControlledEntity, Identity, InternalIdentit Staged, // route been created but has never got through network rule conflict detection. Routes in this state can not be sent to VPC virtual router. Add, // Add means the route has been created and has gone through network rule conflict detection. Active, // Route has been sent to the VPC router and reported to be active. + Update, Revoke, // Revoke means this route has been revoked. If this route has been sent to the VPC router, the route will be deleted from database. Deleting // rule has been revoked and is scheduled for deletion } diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/AddOrUpdateNetrisStaticRouteCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/AddOrUpdateNetrisStaticRouteCommand.java new file mode 100644 index 00000000000..392016fc238 --- /dev/null +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/AddOrUpdateNetrisStaticRouteCommand.java @@ -0,0 +1,48 @@ +// 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 AddOrUpdateNetrisStaticRouteCommand extends NetrisCommand { + private String prefix; + private String nextHop; + private Long routeId; + private boolean updateRoute; + + public AddOrUpdateNetrisStaticRouteCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, String prefix, String nextHop, Long routeId, boolean updateRoute) { + super(zoneId, accountId, domainId, name, id, isVpc); + this.prefix = prefix; + this.nextHop = nextHop; + this.routeId = routeId; + this.updateRoute = updateRoute; + } + + public String getPrefix() { + return prefix; + } + + public String getNextHop() { + return nextHop; + } + + public Long getRouteId() { + return routeId; + } + + public boolean isUpdateRoute() { + return updateRoute; + } +} diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisStaticRouteCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisStaticRouteCommand.java new file mode 100644 index 00000000000..b531a049b0f --- /dev/null +++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisStaticRouteCommand.java @@ -0,0 +1,23 @@ +// 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 DeleteNetrisStaticRouteCommand extends AddOrUpdateNetrisStaticRouteCommand { + public DeleteNetrisStaticRouteCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, String prefix, String nextHop, Long routeId) { + super(zoneId, accountId, domainId, name, id, isVpc, prefix, nextHop, routeId, false); + } +} 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 6f5e01b6392..4507eb7d82b 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 @@ -28,10 +28,12 @@ import com.cloud.agent.api.StartupCommand; import com.cloud.host.Host; import com.cloud.resource.ServerResource; import com.cloud.utils.exception.CloudRuntimeException; +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.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; import org.apache.cloudstack.agent.api.NetrisAnswer; @@ -102,7 +104,11 @@ public class NetrisResource implements ServerResource { } else if (cmd instanceof DeleteNetrisNatRuleCommand) { return executeRequest((DeleteNetrisNatRuleCommand) cmd); } else if (cmd instanceof CreateOrUpdateNetrisNatCommand) { - return executeRequest((CreateOrUpdateNetrisNatCommand) cmd); + return executeRequest((CreateOrUpdateNetrisNatCommand) cmd); + } else if (cmd instanceof DeleteNetrisStaticRouteCommand) { + return executeRequest((DeleteNetrisStaticRouteCommand) cmd); + } else if (cmd instanceof AddOrUpdateNetrisStaticRouteCommand) { + return executeRequest((AddOrUpdateNetrisStaticRouteCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -297,6 +303,22 @@ public class NetrisResource implements ServerResource { return new NetrisAnswer(cmd, true, "OK"); } + private Answer executeRequest(AddOrUpdateNetrisStaticRouteCommand cmd) { + boolean result = netrisApiClient.addOrUpdateStaticRoute(cmd); + if (!result) { + return new NetrisAnswer(cmd, false, String.format("Failed to add static route for VPC: %s, prefix: %s, nextHop: %s ", cmd.getName(), cmd.getPrefix(), cmd.getNextHop())); + } + return new NetrisAnswer(cmd, true, "OK"); + } + + private Answer executeRequest(DeleteNetrisStaticRouteCommand cmd) { + boolean result = netrisApiClient.deleteStaticRoute(cmd); + if (!result) { + return new NetrisAnswer(cmd, false, String.format("Failed to add static route for VPC: %s, prefix: %s, nextHop: %s ", cmd.getName(), cmd.getPrefix(), cmd.getNextHop())); + } + return new NetrisAnswer(cmd, true, "OK"); + } + @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 fd6851696c8..f5f6072b604 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 @@ -22,7 +22,7 @@ import org.apache.commons.lang3.ArrayUtils; public class NetrisResourceObjectUtils { public enum NetrisObjectType { - VPC, IPAM_ALLOCATION, IPAM_SUBNET, VNET, SNAT, STATICNAT, DNAT + VPC, IPAM_ALLOCATION, IPAM_SUBNET, VNET, SNAT, STATICNAT, DNAT, STATICROUTE } public static String retrieveNetrisResourceObjectName(NetrisCommand cmd, NetrisObjectType netrisObjectType, String... suffixes) { @@ -72,6 +72,10 @@ public class NetrisResourceObjectUtils { stringBuilder.append(String.format("%s%s-%s", prefix, suffixes[0], "DNAT")); suffixes = ArrayUtils.subarray(suffixes, 1, suffixes.length); break; + case STATICROUTE: + stringBuilder.append(String.format("%s%s-%s%s", prefix, suffixes[0], "ROUTE", suffixes[1])); + suffixes = new String[0]; + break; case VNET: break; default: 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 cf214bb4664..59c2c9342a9 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 @@ -20,10 +20,12 @@ import io.netris.ApiException; import io.netris.model.GetSiteBody; import io.netris.model.VPCListing; import io.netris.model.response.TenantResponse; +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.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; @@ -74,4 +76,6 @@ public interface NetrisApiClient { boolean createOrUpdateDNATRule(CreateOrUpdateNetrisNatCommand cmd); boolean createStaticNatRule(CreateOrUpdateNetrisNatCommand cmd); boolean deleteNatRule(DeleteNetrisNatRuleCommand cmd); + boolean addOrUpdateStaticRoute(AddOrUpdateNetrisStaticRouteCommand cmd); + boolean deleteStaticRoute(DeleteNetrisStaticRouteCommand 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 9b7783c2eaa..e2d13e47daa 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 @@ -22,6 +22,7 @@ import io.netris.ApiClient; import io.netris.ApiException; import io.netris.ApiResponse; import io.netris.api.v1.AuthenticationApi; +import io.netris.api.v1.RoutesApi; import io.netris.api.v1.SitesApi; import io.netris.api.v1.TenantsApi; import io.netris.api.v2.IpamApi; @@ -35,6 +36,7 @@ import io.netris.model.FilterByVpc; import io.netris.model.GetSiteBody; import io.netris.model.InlineResponse20015; import io.netris.model.InlineResponse20016; +import io.netris.model.InlineResponse2003; import io.netris.model.InlineResponse2004; import io.netris.model.InlineResponse2004Data; import io.netris.model.IpTree; @@ -48,6 +50,13 @@ import io.netris.model.NatGetBody; import io.netris.model.NatPostBody; import io.netris.model.NatPutBody; import io.netris.model.NatResponseGetOk; +import io.netris.model.RoutesBody; +import io.netris.model.RoutesBodyId; +import io.netris.model.RoutesBodyVpcVpc; +import io.netris.model.RoutesGetBody; +import io.netris.model.RoutesPostBody; +import io.netris.model.RoutesPutBody; +import io.netris.model.RoutesResponseGetOk; import io.netris.model.SitesResponseOK; import io.netris.model.SubnetBody; import io.netris.model.SubnetResBody; @@ -71,10 +80,12 @@ import io.netris.model.VnetsBody; import io.netris.model.response.AuthResponse; import io.netris.model.response.TenantResponse; import io.netris.model.response.TenantsResponse; +import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand; 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.DeleteNetrisNatRuleCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand; @@ -283,6 +294,176 @@ public class NetrisApiClientImpl implements NetrisApiClient { return true; } + @Override + public boolean addOrUpdateStaticRoute(AddOrUpdateNetrisStaticRouteCommand cmd) { + String prefix = cmd.getPrefix(); + String nextHop = cmd.getNextHop(); + Long vpcId = cmd.getId(); + String vpcName = cmd.getName(); + boolean updateRoute = cmd.isUpdateRoute(); + try { + String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix); + VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return false; + } + + String[] suffixes = new String[2]; + suffixes[0] = vpcId.toString(); + suffixes[1] = cmd.getRouteId().toString(); + String staticRouteId = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffixes); + + Pair<Boolean, RoutesGetBody> existingStaticRoute = staticRouteExists(vpcResource.getId(), prefix, null, staticRouteId); + if (updateRoute) { + if (!existingStaticRoute.first()) { + logger.error("The Netris static route {} does not exist for VPC {}", prefix, netrisVpcName); + return false; + } + return updateStaticRouteInternal(existingStaticRoute.second().getId(), netrisVpcName, prefix, nextHop, staticRouteId); + } else { + if (existingStaticRoute.first()) { + String existingNextHop = existingStaticRoute.second().getNextHop(); + if (existingNextHop != null && existingNextHop.equals(nextHop)) { + logger.debug("The Netris static route {} already exists for VPC {}", prefix, netrisVpcName); + return true; + } else { + logger.debug("The Netris static route {} already exists but has different next hop {} for VPC {}", prefix, nextHop, netrisVpcName); + return false; + } + } + return addStaticRouteInternal(vpcResource, netrisVpcName, prefix, nextHop, staticRouteId); + } + } catch (Exception e) { + throw new CloudRuntimeException("Error adding Netris static route", e); + } + } + + private boolean addStaticRouteInternal(VPCListing vpcResource, String netrisVpcName, String prefix, String nextHop, String staticRouteId) { + try { + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesPostBody routesPostBody = new RoutesPostBody(); + routesPostBody.setPrefix(prefix); + routesPostBody.setNextHop(nextHop); + routesPostBody.setSiteId(new BigDecimal(siteId)); + routesPostBody.setStateStatus(RoutesBody.StateStatusEnum.ACTIVE); + + RoutesBodyVpcVpc vpcBody = new RoutesBodyVpcVpc(); + vpcBody.setId(vpcResource.getId()); + vpcBody.setName(vpcResource.getName()); + vpcBody.setIsDefault(vpcResource.isIsDefault()); + vpcBody.setIsSystem(vpcResource.isIsSystem()); + routesPostBody.setVpc(vpcBody); + + routesPostBody.setDescription(staticRouteId); + routesPostBody.setStateStatus(RoutesBody.StateStatusEnum.ACTIVE); + routesPostBody.setSwitches(Collections.emptyList()); + + InlineResponse2004 routeResponse = routesApi.apiRoutesPost(routesPostBody); + if (routeResponse == null || !routeResponse.isIsSuccess()) { + String reason = routeResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris static route creation failed for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException("Error adding Netris static route", e); + return false; + } + return true; + } + + private boolean updateStaticRouteInternal(Integer id, String netrisVpcName, String prefix, String nextHop, String staticRouteId) { + try { + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesPutBody routesPutBody = new RoutesPutBody(); + routesPutBody.setId(id); + routesPutBody.setPrefix(prefix); + routesPutBody.setNextHop(nextHop); + routesPutBody.setSiteId(new BigDecimal(siteId)); + + routesPutBody.setDescription(staticRouteId); + routesPutBody.setSwitches(Collections.emptyList()); + + InlineResponse2003 routeResponse = routesApi.apiRoutesPut(routesPutBody); + if (routeResponse == null || !routeResponse.isIsSuccess()) { + String reason = routeResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("Failed to update Netris static route for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + } catch (ApiException e) { + logAndThrowException("Error updating Netris static route", e); + return false; + } + return true; + } + + @Override + public boolean deleteStaticRoute(DeleteNetrisStaticRouteCommand cmd) { + Long vpcId = cmd.getId(); + String vpcName = cmd.getName(); + String prefix = cmd.getPrefix(); + String nextHop = cmd.getNextHop(); + try { + String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true); + String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix); + VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName); + if (vpcResource == null) { + logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId); + return false; + } + + String[] suffixes = new String[2]; + suffixes[0] = vpcId.toString(); + suffixes[1] = cmd.getRouteId().toString(); + String staticRouteId = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffixes); + Pair<Boolean, RoutesGetBody> existingStaticRoute = staticRouteExists(vpcResource.getId(), prefix, nextHop, staticRouteId); + + if (Boolean.FALSE.equals(existingStaticRoute.first())) { + logger.debug("The Netris static route {} does not exist for VPC {}", prefix, netrisVpcName); + return true; + } + RoutesGetBody existingRoute = existingStaticRoute.second(); + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesBodyId id = new RoutesBodyId(); + id.setId(existingRoute.getId()); + InlineResponse2003 routeDeleteResponse = routesApi.apiRoutesDelete(id); + if (routeDeleteResponse == null || !routeDeleteResponse.isIsSuccess()) { + String reason = routeDeleteResponse == null ? "Empty response" : "Operation failed on Netris"; + logger.debug("The Netris static route deletion failed for netris VPC - {}: {}", netrisVpcName, reason); + throw new CloudRuntimeException(reason); + } + return true; + } catch (ApiException e) { + logAndThrowException("Error deleting Netris static route", e); + } + return false; + } + + private Pair<Boolean, RoutesGetBody> staticRouteExists(Integer netrisVpcId, String prefix, String nextHop, String description) { + try { + FilterByVpc vpcFilter = new FilterByVpc(); + vpcFilter.add(netrisVpcId); + FilterBySites sitesFilter = new FilterBySites(); + sitesFilter.add(siteId); + RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class); + RoutesResponseGetOk routesResponseGetOk = routesApi.apiRoutesGet(sitesFilter, vpcFilter); + if (Objects.isNull(routesResponseGetOk) || Boolean.FALSE.equals(routesResponseGetOk.isIsSuccess())) { + logger.warn("Failed to retrieve static routes"); + return new Pair<>(false, null); + } + List<RoutesGetBody> routesList = routesResponseGetOk.getData(); + List<RoutesGetBody> filteredList = routesList.stream() + .filter(x -> x.getName().equals(prefix) && + (Objects.isNull(nextHop) || x.getNextHop().equals(nextHop))) + .collect(Collectors.toList()); + return new Pair<>(!filteredList.isEmpty(), filteredList.isEmpty() ? null : filteredList.get(0)); + } catch (ApiException e) { + logAndThrowException("Error checking Netris static routes", e); + } + return new Pair<>(false, null); + } + private void deleteNatSubnet(Integer netrisVpcId, String natIp) { FilterByVpc vpcFilter = new FilterByVpc(); vpcFilter.add(netrisVpcId); 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 eb74e6f7080..b74ec53f9f5 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 @@ -61,6 +61,7 @@ import com.cloud.network.rules.PortForwardingRule; import com.cloud.network.rules.StaticNat; import com.cloud.network.vpc.NetworkACLItem; import com.cloud.network.vpc.PrivateGateway; +import com.cloud.network.vpc.StaticRoute; import com.cloud.network.vpc.StaticRouteProfile; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcVO; @@ -410,6 +411,15 @@ public class NetrisElement extends AdapterBase implements DhcpServiceProvider, D @Override public boolean applyStaticRoutes(Vpc vpc, List<StaticRouteProfile> routes) throws ResourceUnavailableException { + for(StaticRouteProfile staticRoute : routes) { + if (StaticRoute.State.Add == staticRoute.getState()) { + netrisService.addOrUpdateStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), staticRoute.getGateway(), staticRoute.getId(), false); + } else if (StaticRoute.State.Revoke == staticRoute.getState()) { + netrisService.deleteStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), staticRoute.getGateway(), staticRoute.getId()); + } else if (StaticRoute.State.Update == staticRoute.getState()) { + netrisService.addOrUpdateStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), staticRoute.getGateway(), staticRoute.getId(), true); + } + } return true; } 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 a72b50fc8ed..6d97f850243 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 @@ -42,10 +42,12 @@ import com.cloud.utils.net.NetUtils; import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressString; import io.netris.model.NatPostBody; +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.CreateOrUpdateNetrisNatCommand; import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand; +import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand; import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand; import org.apache.cloudstack.agent.api.NetrisAnswer; @@ -338,6 +340,20 @@ public class NetrisServiceImpl implements NetrisService, Configurable { return answer.getResult(); } + @Override + public boolean addOrUpdateStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId, boolean updateRoute) { + AddOrUpdateNetrisStaticRouteCommand cmd = new AddOrUpdateNetrisStaticRouteCommand(zoneId, accountId, domainId, networkResourceName, networkResourceId, isForVpc, prefix, nextHop, routeId, updateRoute); + NetrisAnswer answer = sendNetrisCommand(cmd, zoneId); + return answer.getResult(); + } + + @Override + public boolean deleteStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId) { + DeleteNetrisStaticRouteCommand cmd = new DeleteNetrisStaticRouteCommand(zoneId, accountId, domainId, networkResourceName, networkResourceId, isForVpc, prefix, nextHop, routeId); + NetrisAnswer answer = sendNetrisCommand(cmd, zoneId); + return answer.getResult(); + } + private String getResourceSuffix(Long vpcId, Long networkId, boolean isForVpc) { String suffix; if (isForVpc) { 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 e0abcef63ba..b4e4509dc3a 100644 --- a/server/src/test/java/org/apache/cloudstack/service/NetrisServiceMockTest.java +++ b/server/src/test/java/org/apache/cloudstack/service/NetrisServiceMockTest.java @@ -76,4 +76,14 @@ public class NetrisServiceMockTest implements NetrisService { public boolean deleteStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String staticNatIp) { return true; } + + @Override + public boolean addOrUpdateStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId, boolean updateRoute) { + return true; + } + + @Override + public boolean deleteStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId) { + return true; + } }