From 974dc62a2beeb7abe21aa88d9c9fb2a7fb48083f Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Mon, 5 Dec 2011 00:06:45 -0800 Subject: [PATCH] added support for dynamic load balancer provisioning when netscaler SDX is added --- ...> CreateLoadBalancerApplianceCommand.java} | 42 +- ... DestroyLoadBalancerApplianceCommand.java} | 11 +- ...=> CreateLoadBalancerApplianceAnswer.java} | 27 +- ...> DestroyLoadBalancerApplianceAnswer.java} | 8 +- .../network/resource/NetscalerResource.java | 371 ++++++++--- deps/cloud-netscaler-sdx.jar | Bin 0 -> 138839 bytes .../ConfigurationManagerImpl.java | 2 +- .../ExternalLoadBalancerDeviceManager.java | 4 +- ...ExternalLoadBalancerDeviceManagerImpl.java | 581 ++++++++++-------- 9 files changed, 690 insertions(+), 356 deletions(-) rename api/src/com/cloud/agent/api/routing/{CreateLBApplianceCommand.java => CreateLoadBalancerApplianceCommand.java} (50%) rename api/src/com/cloud/agent/api/routing/{DestroyLBApplianceCommand.java => DestroyLoadBalancerApplianceCommand.java} (79%) rename core/src/com/cloud/network/resource/{DestroyLBApplianceAnswer.java => CreateLoadBalancerApplianceAnswer.java} (53%) rename core/src/com/cloud/network/resource/{CreateLBApplianceAnswer.java => DestroyLoadBalancerApplianceAnswer.java} (80%) create mode 100755 deps/cloud-netscaler-sdx.jar diff --git a/api/src/com/cloud/agent/api/routing/CreateLBApplianceCommand.java b/api/src/com/cloud/agent/api/routing/CreateLoadBalancerApplianceCommand.java similarity index 50% rename from api/src/com/cloud/agent/api/routing/CreateLBApplianceCommand.java rename to api/src/com/cloud/agent/api/routing/CreateLoadBalancerApplianceCommand.java index 008db121201..e55b2aa0e3e 100644 --- a/api/src/com/cloud/agent/api/routing/CreateLBApplianceCommand.java +++ b/api/src/com/cloud/agent/api/routing/CreateLoadBalancerApplianceCommand.java @@ -18,19 +18,41 @@ package com.cloud.agent.api.routing; -/** NetworkElementCommand to spin a VPX instance on the Netscaler SDX load balancer appliance */ +/** NetworkElementCommand to spin a load balancer appliance */ -//TODO: fill in Nitro API parameters +public class CreateLoadBalancerApplianceCommand extends NetworkElementCommand { -public class CreateLBApplianceCommand extends NetworkElementCommand { - - String lbApplianceIP = null; + String ip; + String netmask; + String gateway; + String username; + String password; - public CreateLBApplianceCommand(String lbIp) { - this.lbApplianceIP = lbIp; + public CreateLoadBalancerApplianceCommand(String ip, String netmask, String gateway, String username, String password) { + this.ip = ip; + this.netmask = netmask; + this.gateway = gateway; + this.username = username; + this.password = password; } - String getLoadBalancerIP() { - return lbApplianceIP; + public String getLoadBalancerIP() { + return ip; } -} + + public String getNetmask() { + return netmask; + } + + public String getGateway() { + return gateway; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} \ No newline at end of file diff --git a/api/src/com/cloud/agent/api/routing/DestroyLBApplianceCommand.java b/api/src/com/cloud/agent/api/routing/DestroyLoadBalancerApplianceCommand.java similarity index 79% rename from api/src/com/cloud/agent/api/routing/DestroyLBApplianceCommand.java rename to api/src/com/cloud/agent/api/routing/DestroyLoadBalancerApplianceCommand.java index 1acd9afb674..164d14264f9 100644 --- a/api/src/com/cloud/agent/api/routing/DestroyLBApplianceCommand.java +++ b/api/src/com/cloud/agent/api/routing/DestroyLoadBalancerApplianceCommand.java @@ -22,6 +22,15 @@ package com.cloud.agent.api.routing; -public class DestroyLBApplianceCommand extends NetworkElementCommand { +public class DestroyLoadBalancerApplianceCommand extends NetworkElementCommand { + String ip; + + public DestroyLoadBalancerApplianceCommand(String ip) { + this.ip = ip; + } + + public String getLoadBalancerIP() { + return ip; + } } diff --git a/core/src/com/cloud/network/resource/DestroyLBApplianceAnswer.java b/core/src/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java similarity index 53% rename from core/src/com/cloud/network/resource/DestroyLBApplianceAnswer.java rename to core/src/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java index d7f4f31af70..66117a3eeb2 100644 --- a/core/src/com/cloud/network/resource/DestroyLBApplianceAnswer.java +++ b/core/src/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java @@ -20,9 +20,30 @@ package com.cloud.network.resource; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; +import com.cloud.resource.ServerResource; -public class DestroyLBApplianceAnswer extends Answer { - public DestroyLBApplianceAnswer(Command cmd, boolean success) { - +public class CreateLoadBalancerApplianceAnswer extends Answer { + String deviceName; + String providerName; + ServerResource serverResource; + + public CreateLoadBalancerApplianceAnswer(Command cmd, boolean success, String details, String deviceName, String providerName, ServerResource serverResource) { + this.deviceName = deviceName; + this.providerName = providerName; + this.serverResource = serverResource; + this.result = success; + this.details = details; + } + + public String getDeviceName() { + return deviceName; + } + + public String getProviderName() { + return providerName; + } + + public ServerResource getServerResource() { + return serverResource; } } diff --git a/core/src/com/cloud/network/resource/CreateLBApplianceAnswer.java b/core/src/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java similarity index 80% rename from core/src/com/cloud/network/resource/CreateLBApplianceAnswer.java rename to core/src/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java index b15aa74bdf2..bcd09cef35e 100644 --- a/core/src/com/cloud/network/resource/CreateLBApplianceAnswer.java +++ b/core/src/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java @@ -21,9 +21,9 @@ package com.cloud.network.resource; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; -public class CreateLBApplianceAnswer extends Answer { - public CreateLBApplianceAnswer(Command cmd, boolean success) { - +public class DestroyLoadBalancerApplianceAnswer extends Answer { + public DestroyLoadBalancerApplianceAnswer(Command cmd, boolean success, String details) { + this.result = success; + this.details = details; } - } diff --git a/core/src/com/cloud/network/resource/NetscalerResource.java b/core/src/com/cloud/network/resource/NetscalerResource.java index 1667b949910..496c380aa00 100644 --- a/core/src/com/cloud/network/resource/NetscalerResource.java +++ b/core/src/com/cloud/network/resource/NetscalerResource.java @@ -24,8 +24,8 @@ import javax.naming.ConfigurationException; import com.cloud.agent.IAgentControl; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; -import com.cloud.agent.api.routing.CreateLBApplianceCommand; -import com.cloud.agent.api.routing.DestroyLBApplianceCommand; +import com.cloud.agent.api.routing.CreateLoadBalancerApplianceCommand; +import com.cloud.agent.api.routing.DestroyLoadBalancerApplianceCommand; import com.cloud.agent.api.ExternalNetworkResourceUsageAnswer; import com.cloud.agent.api.ExternalNetworkResourceUsageCommand; import com.cloud.agent.api.MaintainAnswer; @@ -49,6 +49,7 @@ import com.cloud.utils.NumbersUtil; import com.cloud.utils.exception.ExecutionException; import com.cloud.utils.net.NetUtils; import com.google.gson.Gson; +import com.vmware.vim25.VirtualMachinePowerState; import com.citrix.netscaler.nitro.service.nitro_service; import com.citrix.netscaler.nitro.resource.base.base_response; @@ -59,12 +60,15 @@ import com.citrix.netscaler.nitro.resource.config.network.*; import com.citrix.netscaler.nitro.resource.config.ns.*; import com.citrix.netscaler.nitro.resource.config.basic.server_service_binding; import com.citrix.netscaler.nitro.resource.stat.lb.lbvserver_stats; +import com.citrix.sdx.nitro.resource.config.ns; +import com.citrix.sdx.nitro.resource.config.mps; import org.apache.log4j.Logger; class NitroError { static final int NS_RESOURCE_EXISTS = 273; static final int NS_RESOURCE_NOT_EXISTS=258; static final int NS_NO_SERIVCE = 344; + static final int NS_OPERATION_NOT_PERMITTED = 257; } public class NetscalerResource implements ServerResource { @@ -88,7 +92,12 @@ public class NetscalerResource implements ServerResource { protected Gson _gson; private String _objectNamePathSep = "-"; - nitro_service _netscalerService ; + // interface to interact with VPX and MPX devices + com.citrix.netscaler.nitro.service.nitro_service _netscalerService ; + + // interface to interact with service VM of the SDX appliance + com.citrix.sdx.nitro.service.nitro_service _netscalerSdxService; + Long _timeout = new Long(100000); base_response apiCallResult; @@ -133,12 +142,12 @@ public class NetscalerResource implements ServerResource { if (_publicInterface == null) { throw new ConfigurationException("Unable to find public interface in the configuration parameters"); } - + _privateInterface = (String) params.get("privateinterface"); if (_privateInterface == null) { throw new ConfigurationException("Unable to find private interface in the configuration parameters"); } - + _numRetries = NumbersUtil.parseInt((String) params.get("numretries"), 2); _guid = (String)params.get("guid"); @@ -151,17 +160,17 @@ public class NetscalerResource implements ServerResource { throw new ConfigurationException("Unable to find the device name in the configuration parameters"); } - if (_deviceName.equalsIgnoreCase("NetscalerSDXLoadBalancer")) { - _isSdx = true; - } + _isSdx = _deviceName.equalsIgnoreCase("NetscalerSDXLoadBalancer"); _inline = Boolean.parseBoolean((String) params.get("inline")); - // validate device configration parameters + // validate device configuration parameters login(); - checkLoadBalancingFeatureEnabled(); - validateInterfaces(_publicInterface, _privateInterface); validateDeviceType(_deviceName); + validateInterfaces(_publicInterface, _privateInterface); + + //enable load balancing feature + enableLoadBalancingFeature(); return true; } catch (Exception e) { @@ -171,12 +180,21 @@ public class NetscalerResource implements ServerResource { private void login() throws ExecutionException { try { - _netscalerService = new nitro_service(_ip, "https"); - _netscalerService.set_credential(_username, _password); - _netscalerService.set_timeout(_timeout); - apiCallResult = _netscalerService.login(); - if (apiCallResult.errorcode != 0) { - throw new ExecutionException ("Failed to log in to Netscaler device at " + _ip + " due to error " + apiCallResult.errorcode + " and message " + apiCallResult.message); + if (!_isSdx) { + _netscalerService = new nitro_service(_ip, "https"); + _netscalerService.set_credential(_username, _password); + _netscalerService.set_timeout(_timeout); + apiCallResult = _netscalerService.login(); + if (apiCallResult.errorcode != 0) { + throw new ExecutionException ("Failed to log in to Netscaler device at " + _ip + " due to error " + apiCallResult.errorcode + " and message " + apiCallResult.message); + } + } else { + _netscalerSdxService = new com.citrix.sdx.nitro.service.nitro_service(_ip, "https"); + _netscalerSdxService.set_credential(_username, _password); + com.citrix.sdx.nitro.resource.base.login login = _netscalerSdxService.login(); + if (login == null) { + throw new ExecutionException ("Failed to log in to Netscaler device at " + _ip + " due to error " + apiCallResult.errorcode + " and message " + apiCallResult.message); + } } } catch (nitro_exception e) { throw new ExecutionException("Failed to log in to Netscaler device at " + _ip + " due to " + e.getMessage()); @@ -185,7 +203,10 @@ public class NetscalerResource implements ServerResource { } } - private void checkLoadBalancingFeatureEnabled() throws ExecutionException { + private void enableLoadBalancingFeature() throws ExecutionException { + if (_isSdx) { + return; + } try { String[] features = _netscalerService.get_enabled_features(); if (features != null) { @@ -195,20 +216,73 @@ public class NetscalerResource implements ServerResource { } } } - throw new ExecutionException("Load balancing feature is not enabled on the device. Please enable the load balancing feature and add the device."); + + // enable load balancing on the device + String[] feature = new String[1]; + feature[0] = "LB"; + apiCallResult = _netscalerService.enable_features(feature); + if (apiCallResult.errorcode != 0) { + throw new ExecutionException("Enabling load balancing feature on the device failed."); + } } catch (nitro_exception e) { - throw new ExecutionException("Failed to verify load balancing is enalbed due to error " + apiCallResult.errorcode + " and message " + e.getMessage()); + throw new ExecutionException("Enabling load balancing feature on the device failed due to " + e.getMessage()); } catch (Exception e) { - throw new ExecutionException("Failed to verify load balancing is enalbed due to " + e.getMessage()); + throw new ExecutionException("Enabling load balancing feature on the device failed due to " + e.getMessage()); } } private void validateInterfaces(String publicInterface, String privateInterface) throws ExecutionException { - //FIXME verify the device type (VPX, MPX, SDX) specified indeed matches with actual device type + try { + if (_isSdx) { + return; + } else { + Interface publicIf = Interface.get(_netscalerService, publicInterface); + Interface privateIf = Interface.get(_netscalerService, publicInterface); + if (publicIf != null || privateIf != null) { + return; + } else { + throw new ExecutionException("Invalid interface name specified for public/private interfaces."); + } + } + } catch (nitro_exception e) { + if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) { + throw new ExecutionException("Invalid interface name specified for public and private interfaces."); + } else { + throw new ExecutionException("Failed to verify public interface and private intefaces are valid due to " + e.getMessage()); + } + } catch (Exception e) { + throw new ExecutionException("Failed to verify public interface and private intefaces are valid due to " + e.getMessage()); + } } private void validateDeviceType(String deviceType) throws ExecutionException { - //FIXME validate public and private interface strings as well + try { + if (!_isSdx) { + nshardware nsHw = com.citrix.netscaler.nitro.resource.config.ns.nshardware.get(_netscalerService); + if (nsHw == null) { + throw new ExecutionException("Failed to get the hardware description of the Netscaler device at " + _ip); + } else { + if ((_deviceName.equalsIgnoreCase("NetscalerMPXLoadBalancer") && nsHw.get_hwdescription().contains("MPX")) + || (_deviceName.equalsIgnoreCase("NetscalerVPXLoadBalancer") && nsHw.get_hwdescription().contains("NetScaler Virtual Appliance"))) { + return; + } + throw new ExecutionException("Netscalar device type specified does not match with the actuall device type."); + } + } else { + mps serviceVM = mps.get(_netscalerSdxService); + if (serviceVM != null) { + if (serviceVM.get_platform().contains("SDX") || serviceVM.get_product().contains("SDX")) { + return; + } else { + throw new ExecutionException("Netscalar device type specified does not match with the actuall device type."); + } + } else { + throw new ExecutionException("Failed to get the hardware details of the Netscaler device at " + _ip); + } + } + } catch (Exception e) { + throw new ExecutionException("Failed to verify device type specified when matching with actuall device type due to " + e.getMessage()); + } } @Override @@ -228,7 +302,7 @@ public class NetscalerResource implements ServerResource { public Answer executeRequest(Command cmd) { return executeRequest(cmd, _numRetries); } - + private Answer executeRequest(Command cmd, int numRetries) { if (cmd instanceof ReadyCommand) { return execute((ReadyCommand) cmd); @@ -239,11 +313,11 @@ public class NetscalerResource implements ServerResource { } else if (cmd instanceof LoadBalancerConfigCommand) { return execute((LoadBalancerConfigCommand) cmd, numRetries); } else if (cmd instanceof ExternalNetworkResourceUsageCommand) { - return execute((ExternalNetworkResourceUsageCommand) cmd); - } else if (cmd instanceof CreateLBApplianceCommand) { - return execute((CreateLBApplianceCommand) cmd, numRetries); - } else if (cmd instanceof DestroyLBApplianceCommand) { - return execute((DestroyLBApplianceCommand) cmd, numRetries); + return execute((ExternalNetworkResourceUsageCommand) cmd, numRetries); + } else if (cmd instanceof CreateLoadBalancerApplianceCommand) { + return execute((CreateLoadBalancerApplianceCommand) cmd, numRetries); + } else if (cmd instanceof DestroyLoadBalancerApplianceCommand) { + return execute((DestroyLoadBalancerApplianceCommand) cmd, numRetries); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -270,31 +344,34 @@ public class NetscalerResource implements ServerResource { long guestVlanTag = Long.valueOf(ip.getVlanId()); String vlanSelfIp = ip.getVlanGateway(); String vlanNetmask = ip.getVlanNetmask(); - - // Check and delete any existing guest VLAN with this tag, self IP, and netmask - deleteGuestVlan(guestVlanTag, vlanSelfIp, vlanNetmask); - + if (ip.isAdd()) { // Add a new guest VLAN and its subnet and bind it to private interface addGuestVlanAndSubnet(guestVlanTag, vlanSelfIp, vlanNetmask); + } else { + // Check and delete guest VLAN with this tag, self IP, and netmask + deleteGuestVlan(guestVlanTag, vlanSelfIp, vlanNetmask); } - + saveConfiguration(); results[i++] = ip.getPublicIp() + " - success"; + String action = ip.isAdd() ? "associate" : "remove"; + if (s_logger.isDebugEnabled()) { + s_logger.debug("Netscaler load balancer " + _ip + " successfully executed IPAssocCommand to " + action + " IP " + ip); + } } } catch (ExecutionException e) { - s_logger.error("Failed to execute IPAssocCommand due to " + e); - + s_logger.error("Netscaler loadbalancer " + _ip+ " failed to execute IPAssocCommand due to " + e.getMessage()); if (shouldRetry(numRetries)) { return retry(cmd, numRetries); } else { results[i++] = IpAssocAnswer.errorResult; } - } - + } + return new IpAssocAnswer(cmd, results); } - + private synchronized Answer execute(LoadBalancerConfigCommand cmd, int numRetries) { try { if (_isSdx) { @@ -440,7 +517,7 @@ public class NetscalerResource implements ServerResource { } } } - removeLBVirtualServer(nsVirtualServerName); + removeLBVirtualServer(nsVirtualServerName); } } @@ -467,26 +544,122 @@ public class NetscalerResource implements ServerResource { } } - private synchronized Answer execute(CreateLBApplianceCommand cmd, int numRetries) { - assert(_isSdx) : "CreateLBApplianceCommand can only be sent to SDX device"; - // FIXME: use nitro API to spin a new VPX instance on SDX - return new CreateLBApplianceAnswer(cmd, true); - } + private synchronized Answer execute(CreateLoadBalancerApplianceCommand cmd, int numRetries) { - private synchronized Answer execute(DestroyLBApplianceCommand cmd, int numRetries) { - assert(_isSdx) : "DestroyLBApplianceCommand can only be sent to SDX device"; - // FIXME: use nitro API to destroy VPX instance on SDX - return new DestroyLBApplianceAnswer(cmd, true); - } + if (!_isSdx) { + return Answer.createUnsupportedCommandAnswer(cmd); + } - private synchronized ExternalNetworkResourceUsageAnswer execute(ExternalNetworkResourceUsageCommand cmd) { try { - return getPublicIpBytesSentAndReceived(cmd); - } catch (ExecutionException e) { - return new ExternalNetworkResourceUsageAnswer(cmd, e); + String vpxName = "Cloud-VPX-"+cmd.getLoadBalancerIP(); + String ip = cmd.getLoadBalancerIP(); + ns ns_obj = new ns(); + ns_obj.set_name(vpxName); + ns_obj.set_ip_address(cmd.getLoadBalancerIP()); + ns_obj.set_netmask(cmd.getNetmask()); + ns_obj.set_gateway(cmd.getGateway()); + ns_obj.set_username(cmd.getUsername()); + ns_obj.set_password(cmd.getPassword()); + + // configure VPX instances with defaults + ns_obj.set_feature_license("Standard"); + ns_obj.set_memory_total(new Double(2048)); + ns_obj.set_throughput(new Double(1000)); + ns_obj.set_pps(new Double(1000000)); + ns_obj.set_nsroot_profile("NS_nsroot_profile"); + ns_obj.set_number_of_ssl_cores(0); + ns_obj.set_image_name("NSVPX-XEN-9.3-52.4_nc.xva"); + ns_obj.set_if_10_1(new Boolean(true)); + + // create new VPX instance + ns newVpx = ns.add(_netscalerSdxService, ns_obj); + + if (newVpx == null) { + new Answer(cmd, new ExecutionException("Failed to create VPX instance on the netscaler SDX device " + _ip)); + } + + // wait for VPX instance to start-up + long startTick = System.currentTimeMillis(); + long startWaitMins = 200000; + while(!newVpx.get_ns_state().equalsIgnoreCase("up") && System.currentTimeMillis() - startTick < startWaitMins) { + try { + Thread.sleep(1000); + } catch(InterruptedException e) { + } + ns refreshNsObj = new ns(); + refreshNsObj.set_id(newVpx.get_id()); + newVpx = ns.get(_netscalerSdxService, refreshNsObj); + } + + // if vpx instance never came up then error out + if (!newVpx.get_ns_state().equalsIgnoreCase("up")) { + new Answer(cmd, new ExecutionException("Failed to start VPX instance " + vpxName + " created on the netscaler SDX device " + _ip)); + } + if (s_logger.isInfoEnabled()) { + s_logger.info("Successfully provisioned VPX instance " + vpxName + " on the Netscaler SDX device " + _ip); + } + return new CreateLoadBalancerApplianceAnswer(cmd, true, "provisioned VPX instance", "NetscalerVPXLoadBalancer", "Netscaler", new NetscalerResource()); + } catch (Exception e) { + + if (shouldRetry(numRetries)) { + return retry(cmd, numRetries); + } + + return new CreateLoadBalancerApplianceAnswer(cmd, false, "failed to provisioned VPX instance", null, null, null); } } - + + private synchronized Answer execute(DestroyLoadBalancerApplianceCommand cmd, int numRetries) { + String vpxName = "Cloud-VPX-"+cmd.getLoadBalancerIP(); + if (!_isSdx) { + return Answer.createUnsupportedCommandAnswer(cmd); + } + + try { + ns vpxToDelete =null; + ns[] vpxInstances = ns.get(_netscalerSdxService); + for (ns vpx : vpxInstances) { + if (vpx.get_name().equals(vpxName)) { + vpxToDelete = vpx; + break; + } + } + + if (vpxToDelete == null) { + String msg = "There is no VPX instance " + vpxName + " on the Netscaler SDX device " + _ip + " to delete"; + s_logger.warn(msg); + return new DestroyLoadBalancerApplianceAnswer(cmd, true, msg); + } + + // destroy the VPX instance + vpxToDelete = ns.delete(_netscalerSdxService, vpxToDelete); + String msg = "Deleted VPX instance " + vpxName + " on Netscaler SDX " + _ip + " successfully."; + s_logger.info(msg); + return new DestroyLoadBalancerApplianceAnswer(cmd, true,msg); + } catch (Exception e) { + if (shouldRetry(numRetries)) { + return retry(cmd, numRetries); + } + return new DestroyLoadBalancerApplianceAnswer(cmd, false, "Failed to delete VPX instance " + vpxName + " on Netscaler SDX " + _ip); + } + } + + private synchronized Answer execute(ExternalNetworkResourceUsageCommand cmd, int numRetries) { + try { + if (!_isSdx) { + return getPublicIpBytesSentAndReceived(cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } catch (ExecutionException e) { + if (shouldRetry(numRetries)) { + return retry(cmd, numRetries); + } else { + return new ExternalNetworkResourceUsageAnswer(cmd, e); + } + } + } + private void addGuestVlanAndSubnet(long vlanTag, String vlanSelfIp, String vlanNetmask) throws ExecutionException { try { if (!nsVlanExists(vlanTag)) { @@ -523,7 +696,16 @@ public class NetscalerResource implements ServerResource { vlanBinding.set_ifnum(_privateInterface); vlanBinding.set_tagged(true); vlanBinding.set_id(vlanTag); - apiCallResult = vlan_interface_binding.add(_netscalerService, vlanBinding); + try { + apiCallResult = vlan_interface_binding.add(_netscalerService, vlanBinding); + } catch (nitro_exception e) { + // FIXME: Vlan binding (subsequent unbind) to an interfaces will fail on the VPX created on Xen server and on + // NetScaler SDX appliance till the VPX fix to handle VLAN's is released. Relaxing this restriction NetScaler until then + if (!(_deviceName.equalsIgnoreCase("NetscalerVPXLoadBalancer") && e.getErrorCode() == NitroError.NS_OPERATION_NOT_PERMITTED)) { + throw new ExecutionException("Failed to bind vlan to the interface while implementing guest network on the Netscaler device due to " + e.getMessage()); + } + } + if (apiCallResult.errorcode != 0) { throw new ExecutionException("Failed to bind vlan with tag:" + vlanTag + " with the interface " + _privateInterface + " due to " + apiCallResult.message); } @@ -531,9 +713,9 @@ public class NetscalerResource implements ServerResource { throw new ExecutionException("Failed to configure Netscaler device for vlan with tag " + vlanTag + " as vlan already exisits"); } } catch (nitro_exception e) { - throw new ExecutionException("Failed to implement guest network on the Netscaler device"); + throw new ExecutionException("Failed to implement guest network on the Netscaler device due to " + e.getMessage()); } catch (Exception e) { - throw new ExecutionException("Failed to implement guest network on the Netscaler device"); + throw new ExecutionException("Failed to implement guest network on the Netscaler device " + e.getMessage()); } } @@ -549,7 +731,15 @@ public class NetscalerResource implements ServerResource { vlanIfBinding.set_id(vlanTag); vlanIfBinding.set_ifnum(_privateInterface); vlanIfBinding.set_tagged(true); - apiCallResult = vlan_interface_binding.delete(_netscalerService, vlanIfBinding); + try { + apiCallResult = vlan_interface_binding.delete(_netscalerService, vlanIfBinding); + } catch (nitro_exception e) { + // FIXME: Vlan binding (subsequent unbind) to an interfaces will fail on the VPX created on Xen server and on + // NetScaler SDX appliance till the VPX fix to handle VLAN's is released. Relaxing this restriction NetScaler until then + if (!(_deviceName.equalsIgnoreCase("NetscalerVPXLoadBalancer") && e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS)) { + throw new ExecutionException("Failed to unbind vlan from the interface while shutdown of guest network on the Netscaler device due to " + e.getMessage()); + } + } if (apiCallResult.errorcode != 0) { throw new ExecutionException("Failed to unbind vlan:" + vlanTag + " with the private interface due to " + apiCallResult.message); } @@ -638,13 +828,13 @@ public class NetscalerResource implements ServerResource { if (e.getErrorCode() == NitroError.NS_NO_SERIVCE) { return false; } else { - throw new ExecutionException(e.getMessage()); + throw new ExecutionException("Failed to verify service " + serviceName + " exists due to " + e.getMessage()); } } catch (Exception e) { - throw new ExecutionException(e.getMessage()); + throw new ExecutionException("Failed to verify service " + serviceName + " exists due to " + e.getMessage()); } } - + private boolean nsServiceBindingExists(String lbVirtualServer, String serviceName) throws ExecutionException { try { com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding[] serviceBindings = com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.get(_netscalerService, lbVirtualServer); @@ -657,12 +847,12 @@ public class NetscalerResource implements ServerResource { } return false; } catch (nitro_exception e) { - throw new ExecutionException(e.getMessage()); + throw new ExecutionException("Failed to verify lb vserver " + lbVirtualServer + "and service " + serviceName + " binding exists due to " + e.getMessage()); } catch (Exception e) { - throw new ExecutionException(e.getMessage()); + throw new ExecutionException("Failed to verify lb vserver " + lbVirtualServer + "and service " + serviceName + " binding exists due to " + e.getMessage()); } } - + private void deleteServersInGuestVlan(long vlanTag, String vlanSelfIp, String vlanNetmask) throws ExecutionException { try { com.citrix.netscaler.nitro.resource.config.basic.server[] serverList = com.citrix.netscaler.nitro.resource.config.basic.server.get(_netscalerService); @@ -716,6 +906,8 @@ public class NetscalerResource implements ServerResource { lbMethod = "ROUNDROBIN"; } else if (lbMethod.equals("leastconn")) { lbMethod = "LEASTCONNECTION"; + } else if (lbMethod.equals("source")) { + lbMethod = "SOURCEIPHASH"; } else { throw new ExecutionException("Got invalid load balancing algorithm: " + lbMethod); } @@ -739,30 +931,28 @@ public class NetscalerResource implements ServerResource { apiCallResult = lbvserver.add(_netscalerService,vserver); } if (apiCallResult.errorcode != 0) { - throw new ExecutionException("Failed to create new virtual server:" + virtualServerName+ " due to " + apiCallResult.message); - } + throw new ExecutionException("Failed to create new load balancing virtual server:" + virtualServerName + " due to " + apiCallResult.message); + } if (s_logger.isDebugEnabled()) { s_logger.debug("Created load balancing virtual server " + virtualServerName + " on the Netscaler device"); } } catch (nitro_exception e) { - if (e.getErrorCode() != NitroError.NS_RESOURCE_EXISTS) { - throw new ExecutionException("Failed to create new virtual server:" + virtualServerName + " due to " + e.getMessage()); - } + throw new ExecutionException("Failed to create new virtual server:" + virtualServerName + " due to " + e.getMessage()); } catch (Exception e) { throw new ExecutionException("Failed to create new virtual server:" + virtualServerName + " due to " + e.getMessage()); } } - + private void removeLBVirtualServer (String virtualServerName) throws ExecutionException { try { lbvserver vserver = lbvserver.get(_netscalerService, virtualServerName); if (vserver == null) { - throw new ExecutionException("Failed to find virtual server with name:" + virtualServerName); + return; } apiCallResult = lbvserver.delete(_netscalerService, vserver); if (apiCallResult.errorcode != 0) { - throw new ExecutionException("Failed to remove virtual server:" + virtualServerName + " due to " + apiCallResult.message); + throw new ExecutionException("Failed to delete virtual server:" + virtualServerName + " due to " + apiCallResult.message); } } catch (nitro_exception e) { if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) { @@ -774,7 +964,7 @@ public class NetscalerResource implements ServerResource { throw new ExecutionException("Failed to remove virtual server:" + virtualServerName +" due to " + e.getMessage()); } } - + private void saveConfiguration() throws ExecutionException { try { apiCallResult = nsconfig.save(_netscalerService); @@ -790,11 +980,14 @@ public class NetscalerResource implements ServerResource { private ExternalNetworkResourceUsageAnswer getPublicIpBytesSentAndReceived(ExternalNetworkResourceUsageCommand cmd) throws ExecutionException { ExternalNetworkResourceUsageAnswer answer = new ExternalNetworkResourceUsageAnswer(cmd); - + try { - lbvserver_stats[] stats = lbvserver_stats.get(_netscalerService); + if (stats == null || stats.length == 0) { + return answer; + } + for (lbvserver_stats stat_entry : stats) { String lbvserverName = stat_entry.get_name(); lbvserver vserver = lbvserver.get(_netscalerService, lbvserverName); @@ -808,21 +1001,21 @@ public class NetscalerResource implements ServerResource { bytesSentAndReceived[1] += stat_entry.get_totalresponsebytes(); if (bytesSentAndReceived[0] >= 0 && bytesSentAndReceived[1] >= 0) { - answer.ipBytes.put(lbVirtualServerIp, bytesSentAndReceived); + answer.ipBytes.put(lbVirtualServerIp, bytesSentAndReceived); } } } catch (Exception e) { - s_logger.error(e); + s_logger.error("Failed to get bytes sent and recived statistics due to " + e); throw new ExecutionException(e.getMessage()); } - + return answer; } private Answer retry(Command cmd, int numRetries) { int numRetriesRemaining = numRetries - 1; - s_logger.error("Retrying " + cmd.getClass().getSimpleName() + ". Number of retries remaining: " + numRetriesRemaining); - return executeRequest(cmd, numRetriesRemaining); + s_logger.warn("Retrying " + cmd.getClass().getSimpleName() + ". Number of retries remaining: " + numRetriesRemaining); + return executeRequest(cmd, numRetriesRemaining); } private boolean shouldRetry(int numRetries) { @@ -838,17 +1031,17 @@ public class NetscalerResource implements ServerResource { } private String generateNSVirtualServerName(String srcIp, long srcPort, String protocol) { - return genObjectName("cloud-VirtualServer", protocol, srcIp, srcPort); + return genObjectName("Cloud-VirtualServer", protocol, srcIp, srcPort); } - + private String generateNSServerName(String serverIP) { - return genObjectName("cloud-server", serverIP); + return genObjectName("Cloud-Server-", serverIP); } private String generateNSServiceName(String ip, long port) { - return genObjectName("cloud-Service", ip, port); + return genObjectName("Cloud-Service", ip, port); } - + private String genObjectName(Object... args) { String objectName = ""; for (int i = 0; i < args.length; i++) { @@ -859,7 +1052,7 @@ public class NetscalerResource implements ServerResource { } return objectName; } - + @Override public IAgentControl getAgentControl() { return null; @@ -894,9 +1087,9 @@ public class NetscalerResource implements ServerResource { public boolean stop() { return true; } - + @Override public void disconnected() { return; } -} +} \ No newline at end of file diff --git a/deps/cloud-netscaler-sdx.jar b/deps/cloud-netscaler-sdx.jar new file mode 100755 index 0000000000000000000000000000000000000000..25a7496d2f48e5ef176a817c92666c52bbfb44aa GIT binary patch literal 138839 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 z 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 z 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%0AKi%QVBv;89Zlf3dsN_U-> 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 Wkia4fII#-+kp_hAZv*)(u>S+HlQ>)e literal 0 HcmV?d00001 diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 91eb0dbae8f..2ef081c8d21 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -3138,7 +3138,7 @@ public class ConfigurationManagerImpl implements ConfigurationManager, Configura tags = cleanupTags(tags); Map lbServiceCapabilityMap = serviceCapabilityMap.get(Service.Lb); - boolean dedicatedLb = true; + boolean dedicatedLb = false; if ((lbServiceCapabilityMap != null) && (!lbServiceCapabilityMap.isEmpty())) { String isolationCapability = lbServiceCapabilityMap.get(Capability.SupportedLBIsolation); dedicatedLb = isolationCapability.contains("dedicated"); diff --git a/server/src/com/cloud/network/ExternalLoadBalancerDeviceManager.java b/server/src/com/cloud/network/ExternalLoadBalancerDeviceManager.java index 9ce5d38aa56..02e9a5ea7c6 100644 --- a/server/src/com/cloud/network/ExternalLoadBalancerDeviceManager.java +++ b/server/src/com/cloud/network/ExternalLoadBalancerDeviceManager.java @@ -27,6 +27,7 @@ import com.cloud.host.Host; import com.cloud.network.rules.FirewallRule; import com.cloud.resource.ServerResource; import com.cloud.utils.component.Manager; +import com.cloud.utils.exception.ExecutionException; /* ExternalLoadBalancerDeviceManager provides a abstract implementation for managing a external load balancer in device agnostic manner. * Device specific managers for external load balancers (like F5 and Netscaler) should be implemented as pluggable service extending @@ -98,6 +99,7 @@ public interface ExternalLoadBalancerDeviceManager extends Manager{ * @throws ResourceUnavailableException * @throws InsufficientCapacityException */ - public boolean manageGuestNetworkWithExternalLoadBalancer(boolean add, Network guestConfig) throws ResourceUnavailableException, InsufficientCapacityException; + public boolean manageGuestNetworkWithExternalLoadBalancer(boolean add, Network guestConfig) throws ResourceUnavailableException, + InsufficientCapacityException; } diff --git a/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java b/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java index 52a1882312f..9c8cebc8b47 100644 --- a/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java +++ b/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java @@ -38,7 +38,8 @@ import com.cloud.agent.api.ExternalNetworkResourceUsageAnswer; import com.cloud.agent.api.ExternalNetworkResourceUsageCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupExternalLoadBalancerCommand; -import com.cloud.agent.api.routing.CreateLBApplianceCommand; +import com.cloud.agent.api.routing.CreateLoadBalancerApplianceCommand; +import com.cloud.agent.api.routing.DestroyLoadBalancerApplianceCommand; import com.cloud.agent.api.routing.IpAssocCommand; import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.routing.NetworkElementCommand; @@ -67,14 +68,15 @@ import com.cloud.host.dao.HostDetailsDao; import com.cloud.network.ExternalLoadBalancerDeviceVO.LBDeviceAllocationState; import com.cloud.network.ExternalLoadBalancerDeviceVO.LBDeviceState; import com.cloud.network.ExternalNetworkDeviceManager.NetworkDevice; -import com.cloud.network.Network.Capability; import com.cloud.network.Network.Service; import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.ExternalFirewallDeviceDao; import com.cloud.network.dao.ExternalLoadBalancerDeviceDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.InlineLoadBalancerNicMapDao; import com.cloud.network.dao.LoadBalancerDao; import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkExternalFirewallDao; import com.cloud.network.dao.NetworkExternalLoadBalancerDao; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.PhysicalNetworkDao; @@ -82,9 +84,11 @@ import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; import com.cloud.network.lb.LoadBalancingRule; import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.cloud.network.resource.CreateLoadBalancerApplianceAnswer; +import com.cloud.network.resource.DestroyLoadBalancerApplianceAnswer; +import com.cloud.network.router.VirtualRouter.Role; import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.FirewallRuleVO; -import com.cloud.network.rules.PortForwardingRuleVO; import com.cloud.network.rules.StaticNatRule; import com.cloud.network.rules.StaticNatRuleImpl; import com.cloud.network.rules.FirewallRule.Purpose; @@ -174,6 +178,10 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase NetworkExternalLoadBalancerDao _networkLBDao; @Inject NetworkServiceMapDao _ntwkSrvcProviderDao; + @Inject + NetworkExternalFirewallDao _networkExternalFirewallDao; + @Inject + ExternalFirewallDeviceDao _externalFirewallDeviceDao; ScheduledExecutorService _executor; private int _externalNetworkStatsInterval; @@ -337,6 +345,150 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase return physicalNetworkId + "-" + deviceName + "-" + ip; } + @Override + public ExternalLoadBalancerDeviceVO getExternalLoadBalancerForNetwork(Network network) { + NetworkExternalLoadBalancerVO lbDeviceForNetwork = _networkExternalLBDao.findByNetworkId(network.getId()); + if (lbDeviceForNetwork != null) { + long lbDeviceId = lbDeviceForNetwork.getExternalLBDeviceId(); + ExternalLoadBalancerDeviceVO lbDeviceVo = _externalLoadBalancerDeviceDao.findById(lbDeviceId); + assert(lbDeviceVo != null); + return lbDeviceVo; + } + return null; + } + + public void setExternalLoadBalancerForNetwork(Network network, long externalLBDeviceID) { + NetworkExternalLoadBalancerVO lbDeviceForNetwork = new NetworkExternalLoadBalancerVO(network.getId(), externalLBDeviceID); + _networkExternalLBDao.persist(lbDeviceForNetwork); + } + + @DB + protected ExternalLoadBalancerDeviceVO allocateLoadBalancerForNetwork(Network guestConfig) throws InsufficientCapacityException { + boolean retry = true; + boolean tryLbProvisioning = false; + ExternalLoadBalancerDeviceVO lbDevice = null; + long physicalNetworkId = guestConfig.getPhysicalNetworkId(); + NetworkOfferingVO offering = _networkOfferingDao.findById(guestConfig.getNetworkOfferingId()); + String provider = _ntwkSrvcProviderDao.getProviderForServiceInNetwork(guestConfig.getId(), Service.Lb); + + while (retry) { + GlobalLock deviceMapLock = GlobalLock.getInternLock("LoadBalancerAllocLock"); + Transaction txn = Transaction.currentTxn(); + try { + if (deviceMapLock.lock(120)) { + try { + boolean dedicatedLB = offering.getDedicatedLB(); // does network offering supports a dedicated load balancer? + long lbDeviceId; + + txn.start(); + try { + // FIXME: should the device allocation be done during network implement phase or do a + // lazy allocation when first rule for the network is configured?? + + // find a load balancer device for this network as per the network offering + lbDevice = findSuitableLoadBalancerForNetwork(guestConfig, dedicatedLB); + lbDeviceId = lbDevice.getId(); + + // persist the load balancer device id that will be used for this network. Once a network + // is implemented on a LB device then later on all rules will be programmed on to same device + NetworkExternalLoadBalancerVO networkLB = new NetworkExternalLoadBalancerVO(guestConfig.getId(), lbDeviceId); + _networkExternalLBDao.persist(networkLB); + + // mark device to be either dedicated or shared use + lbDevice.setAllocationState(dedicatedLB ? LBDeviceAllocationState.Dedicated : LBDeviceAllocationState.Shared); + _externalLoadBalancerDeviceDao.update(lbDeviceId, lbDevice); + + txn.commit(); + + // allocated load balancer for the network, so skip retry + tryLbProvisioning = false; + retry = false; + } catch (InsufficientCapacityException exception) { + // if already attempted to provision load balancer then throw out of capacity exception, + if (tryLbProvisioning) { + retry = false; + //TODO: throwing warning instead of error for now as its possible another provider can service this network + s_logger.warn("There are no load balancer device with the capacity for implementing this network"); + throw exception; + } else { + tryLbProvisioning = true; // if possible provision a LB appliance in to the physical network + } + } + } finally { + deviceMapLock.unlock(); + if (lbDevice == null) { + txn.rollback(); + } + } + } + } finally { + deviceMapLock.releaseRef(); + } + + // there are no LB devices or there is no free capacity on the devices in the physical network so provision a new LB appliance + if (tryLbProvisioning) { + // check if LB appliance can be dynamically provisioned + List providerLbDevices = _externalLoadBalancerDeviceDao.listByProviderAndDeviceAllocationState(physicalNetworkId, provider, LBDeviceAllocationState.Provider); + if ((providerLbDevices != null) && (!providerLbDevices.isEmpty())) { + for (ExternalLoadBalancerDeviceVO lbProviderDevice : providerLbDevices) { + if (lbProviderDevice.getState() == LBDeviceState.Enabled) { + + // FIXME: acquire a private IP from Pod network Guru needed for LB appliance to be provisioned + // and a public IP from the public network guru, for self-if on the public subnet + String lbIP = null; + String netmask = null; + String gateway = null; + String username = null; + String password = null; + String publicIf = null; + String privateIf = null; + + /* + String lbIP = "10.223.103.254"; + String netmask = "255.255.248.0"; + String gateway = "10.223.102.1"; + String username = "nsroot1"; + String password = "nsroot1"; + String publicIf = "0/1"; + String privateIf = "0/2"; + */ + + // send CreateLoadBalancerApplianceCommand to the host capable of provisioning + CreateLoadBalancerApplianceCommand lbProvisionCmd = new CreateLoadBalancerApplianceCommand(lbIP, netmask, gateway, username, password); + CreateLoadBalancerApplianceAnswer answer = null; + try { + answer = (CreateLoadBalancerApplianceAnswer) _agentMgr.easySend(lbProviderDevice.getHostId(), lbProvisionCmd); + if (answer == null || !answer.getResult()) { + s_logger.error("Could not provision load balancer instance on the load balancer device " + lbProviderDevice.getId()); + continue; + } + } catch (Exception agentException) { + s_logger.error("Could not provision load balancer instance on the load balancer device " + lbProviderDevice.getId() + " due to " + agentException.getMessage()); + continue; + } + + //we have provisioned load balancer so add the appliance as cloudstack provisioned external load balancer + String dedicatedLb = offering.getDedicatedLB()?"true":"false"; + String url = "https://" + lbIP + "?publicinterface="+publicIf+"&privateinterface="+privateIf+"&lbdevicededicated="+dedicatedLb;; + ExternalLoadBalancerDeviceVO lbAppliance = addExternalLoadBalancer(physicalNetworkId, url, "nsroot", "nsroot", answer.getDeviceName(), answer.getServerResource()); + + if (lbAppliance != null) { + // mark the load balancer as cloudstack managed + ExternalLoadBalancerDeviceVO managedLb = _externalLoadBalancerDeviceDao.findById(lbAppliance.getId()); + managedLb.setIsManagedDevice(true); + // set parent host id on which lb appliance is provisioned + managedLb.setParentHostId(lbProviderDevice.getHostId()); + _externalLoadBalancerDeviceDao.update(lbAppliance.getId(), managedLb); + } + } + } + } + } + } + + return lbDevice; + } + @Override public ExternalLoadBalancerDeviceVO findSuitableLoadBalancerForNetwork(Network network, boolean dedicatedLb) throws InsufficientCapacityException { long physicalNetworkId = network.getPhysicalNetworkId(); @@ -411,38 +563,91 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase } } - // there are no devices which capcity + // there are no devices which capacity throw new InsufficientNetworkCapacityException("Unable to find a load balancing provider with sufficient capcity " + " to implement the network", Network.class, network.getId()); } + @DB + protected boolean freeLoadBalancerForNetwork(Network guestConfig) { + Transaction txn = Transaction.currentTxn(); + GlobalLock deviceMapLock = GlobalLock.getInternLock("LoadBalancerAllocLock"); + + try { + if (deviceMapLock.lock(120)) { + txn.start(); + // since network is shutdown remove the network mapping to the load balancer device + NetworkExternalLoadBalancerVO networkLBDevice = _networkExternalLBDao.findByNetworkId(guestConfig.getId()); + long lbDeviceId = networkLBDevice.getExternalLBDeviceId(); + _networkExternalLBDao.remove(networkLBDevice.getId()); + + List ntwksMapped = _networkExternalLBDao.listByLoadBalancerDeviceId(networkLBDevice.getExternalLBDeviceId()); + ExternalLoadBalancerDeviceVO lbDevice = _externalLoadBalancerDeviceDao.findById(lbDeviceId); + boolean lbInUse = !(ntwksMapped == null || ntwksMapped.isEmpty()); + boolean lbCloudManaged = lbDevice.getIsManagedDevice(); + + if (!lbInUse && !lbCloudManaged) { + // this is the last network mapped to the load balancer device so set device allocation state to be free + lbDevice.setAllocationState(LBDeviceAllocationState.Free); + _externalLoadBalancerDeviceDao.update(lbDevice.getId(), lbDevice); + } + + //commit the changes before sending agent command to destroy cloudstack managed LB + txn.commit(); + + if (!lbInUse && lbCloudManaged) { + // send DestroyLoadBalancerApplianceCommand to the host where load balancer appliance + // is provisioned as this is the last network using it + Host lbHost = _hostDao.findById(lbDevice.getHostId()); + String lbIP = lbHost.getPrivateIpAddress(); + DestroyLoadBalancerApplianceCommand lbDeleteCmd = new DestroyLoadBalancerApplianceCommand(lbIP); + DestroyLoadBalancerApplianceAnswer answer = null; + try { + answer = (DestroyLoadBalancerApplianceAnswer) _agentMgr.easySend(lbDevice.getParentHostId(), lbDeleteCmd); + if (answer == null || !answer.getResult()) { + s_logger.warn("Failed to destoy load balancer appliance used by the network" + guestConfig.getId() + " due to " + answer.getDetails()); + } + } catch (Exception e) { + s_logger.warn("Failed to destroy load balancer appliance used by the network" + guestConfig.getId() + " due to " + e.getMessage()); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Successfully destroyed load balancer appliance used for the network" + guestConfig.getId()); + } + } + deviceMapLock.unlock(); + return true; + } else { + s_logger.error("Failed to release load balancer device for the network" + guestConfig.getId() + "as failed to acquire lock "); + return false; + } + } catch (Exception exception) { + txn.rollback(); + s_logger.error("Failed to release load balancer device for the network" + guestConfig.getId() + " due to " + exception.getMessage()); + }finally { + deviceMapLock.releaseRef(); + } + + return false; + } + HostVO getFirewallProviderForNetwork(Network network) { + HostVO fwHost = null; + + // get the firewall provider (could be either virtual router or external firewall device) for the network String fwProvider = _ntwkSrvcProviderDao.getProviderForServiceInNetwork(network.getId(), Service.Firewall); if (fwProvider.equalsIgnoreCase("VirtualRouter")) { - //FIXME: get the host Id of the host on which the virtual router is running + //FIXME: use network service provider container framework support to implement on virtual router } else { - //FIXME: external firewall host object + NetworkExternalFirewallVO fwDeviceForNetwork = _networkExternalFirewallDao.findByNetworkId(network.getId()); + assert (fwDeviceForNetwork != null) : "Why firewall provider is not ready for the network to apply static nat rules?"; + long fwDeviceId = fwDeviceForNetwork.getExternalFirewallDeviceId(); + ExternalFirewallDeviceVO fwDevice = _externalFirewallDeviceDao.findById(fwDeviceId); + fwHost = _hostDao.findById(fwDevice.getHostId()); } - return null; - } - - @Override - public ExternalLoadBalancerDeviceVO getExternalLoadBalancerForNetwork(Network network) { - NetworkExternalLoadBalancerVO lbDeviceForNetwork = _networkExternalLBDao.findByNetworkId(network.getId()); - if (lbDeviceForNetwork != null) { - long lbDeviceId = lbDeviceForNetwork.getExternalLBDeviceId(); - ExternalLoadBalancerDeviceVO lbDeviceVo = _externalLoadBalancerDeviceDao.findById(lbDeviceId); - assert(lbDeviceVo != null); - return lbDeviceVo; - } - return null; - } - - public void setExternalLoadBalancerForNetwork(Network network, long externalLBDeviceID) { - NetworkExternalLoadBalancerVO lbDeviceForNetwork = new NetworkExternalLoadBalancerVO(network.getId(), externalLBDeviceID); - _networkExternalLBDao.persist(lbDeviceForNetwork); + return fwHost; } private boolean externalLoadBalancerIsInline(HostVO externalLoadBalancer) { @@ -458,6 +663,18 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase return _nicDao.persist(nic); } + private NicVO getPlaceholderNic(Network network) { + List guestIps = _nicDao.listByNetworkId(network.getId()); + for (NicVO guestIp : guestIps) { + // only external firewall and external load balancer will create NicVO with PlaceHolder reservation strategy + if (guestIp.getReservationStrategy().equals(ReservationStrategy.PlaceHolder) && guestIp.getVmType() == null + && guestIp.getReserver() == null && !guestIp.getIp4Address().equals(network.getGateway())) { + return guestIp; + } + } + return null; + } + private void applyStaticNatRuleForInlineLBRule(DataCenterVO zone, Network network, HostVO firewallHost, boolean revoked, String publicIp, String privateIp) throws ResourceUnavailableException { List staticNatRules = new ArrayList(); IPAddressVO ipVO = _ipAddressDao.listByDcIdIpAddress(zone.getId(), publicIp).get(0); @@ -491,8 +708,24 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase long zoneId = network.getDataCenterId(); DataCenterVO zone = _dcDao.findById(zoneId); + List loadBalancingRules = new ArrayList(); + + for (FirewallRule rule : rules) { + if (rule.getPurpose().equals(Purpose.LoadBalancing)) { + loadBalancingRules.add((LoadBalancingRule) rule); + } + } + + if (loadBalancingRules == null || loadBalancingRules.isEmpty()) { + return true; + } + ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network); - assert(lbDeviceVO != null) : "There is no device assigned to this network how apply rules ended up here??"; + if (lbDeviceVO == null) { + s_logger.warn("There is no external load balancer device assigned to this network either network is not implement are already shutdown so just returning"); + return true; + } + HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); boolean externalLoadBalancerIsInline = externalLoadBalancerIsInline(externalLoadBalancer); @@ -502,14 +735,6 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase return true; } - List loadBalancingRules = new ArrayList(); - - for (FirewallRule rule : rules) { - if (rule.getPurpose().equals(Purpose.LoadBalancing)) { - loadBalancingRules.add((LoadBalancingRule) rule); - } - } - List loadBalancersToApply = new ArrayList(); for (int i = 0; i < loadBalancingRules.size(); i++) { LoadBalancingRule rule = loadBalancingRules.get(i); @@ -541,7 +766,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase s_logger.error(msg); throw new ResourceUnavailableException(msg, DataCenter.class, network.getDataCenterId()); } - + // If a NIC doesn't exist for the load balancing IP address, create one loadBalancingIpNic = _nicDao.findByIp4Address(loadBalancingIpAddress); if (loadBalancingIpNic == null) { @@ -551,7 +776,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase // Save a mapping between the source IP address and the load balancing IP address NIC mapping = new InlineLoadBalancerNicMapVO(rule.getId(), srcIp, loadBalancingIpNic.getId()); _inlineLoadBalancerNicMapDao.persist(mapping); - + // On the firewall provider for the network, create a static NAT rule between the source IP address and the load balancing IP address applyStaticNatRuleForInlineLBRule(zone, network, firewallProviderHost, revoked, srcIp, loadBalancingIpNic.getIp4Address()); } else { @@ -561,13 +786,13 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase if (mapping != null) { // Find the NIC that the mapping refers to loadBalancingIpNic = _nicDao.findById(mapping.getNicId()); - + // On the firewall provider for the network, delete the static NAT rule between the source IP address and the load balancing IP address applyStaticNatRuleForInlineLBRule(zone, network, firewallProviderHost, revoked, srcIp, loadBalancingIpNic.getIp4Address()); - + // Delete the mapping between the source IP address and the load balancing IP address _inlineLoadBalancerNicMapDao.expunge(mapping.getId()); - + // Delete the NIC _nicDao.expunge(loadBalancingIpNic.getId()); } else { @@ -575,11 +800,11 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase continue; } } - + // Change the source IP address for the load balancing rule to be the load balancing IP address srcIp = loadBalancingIpNic.getIp4Address(); } - + if (destinations != null && !destinations.isEmpty()) { LoadBalancerTO loadBalancer = new LoadBalancerTO(srcIp, srcPort, protocol, algorithm, revoked, false, destinations); loadBalancersToApply.add(loadBalancer); @@ -605,118 +830,27 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase } @Override - @DB public boolean manageGuestNetworkWithExternalLoadBalancer(boolean add, Network guestConfig) throws ResourceUnavailableException, InsufficientCapacityException { if (guestConfig.getTrafficType() != TrafficType.Guest) { - s_logger.trace("External load balancer can only be user for guest networks."); + s_logger.trace("External load balancer can only be used for guest networks."); return false; } long zoneId = guestConfig.getDataCenterId(); DataCenterVO zone = _dcDao.findById(zoneId); HostVO externalLoadBalancer = null; - String provider = _ntwkSrvcProviderDao.getProviderForServiceInNetwork(guestConfig.getId(), Service.Lb); - long physicalNetworkId = guestConfig.getPhysicalNetworkId(); if (add) { - boolean retry = true; - boolean provisionLB = false; - - while (retry) { - GlobalLock deviceMapLock = GlobalLock.getInternLock("LoadBalancerAllocLock"); - Transaction txn = Transaction.currentTxn(); - try { - if (deviceMapLock.lock(120)) { - try { - NetworkOfferingVO offering = _networkOfferingDao.findById(guestConfig.getNetworkOfferingId()); - long lbDeviceId; - txn.start(); - - // FIXME: should the device allocation be done during network implement phase or do a lazy allocation - // when first rule for the network is configured?? - - boolean dedicatedLB = offering.getDedicatedLB(); - try { - // find a load balancer device for this network as per the network offering - ExternalLoadBalancerDeviceVO lbDevice = findSuitableLoadBalancerForNetwork(guestConfig, dedicatedLB); - lbDeviceId = lbDevice.getId(); - - // persist the load balancer device id that will be used for this network. Once a network - // is implemented on a LB device then later on all rules will be programmed on to same device - NetworkExternalLoadBalancerVO networkLB = new NetworkExternalLoadBalancerVO(guestConfig.getId(), lbDeviceId); - _networkExternalLBDao.persist(networkLB); - - // mark device to be in use - lbDevice.setAllocationState(dedicatedLB ? LBDeviceAllocationState.Dedicated : LBDeviceAllocationState.Shared); - _externalLoadBalancerDeviceDao.update(lbDeviceId, lbDevice); - - // return the HostVO for the lb device - externalLoadBalancer = _hostDao.findById(lbDevice.getHostId()); - txn.commit(); - - // got the load balancer for the network, so skip retry - provisionLB = false; - retry = false; - } catch (InsufficientCapacityException exception) { - if (provisionLB) { - retry = false; - //TODO: throwing warning instead of error for now as its possible another provider can service this network - s_logger.warn("There are no load balancer device with the capacity for implementing this network"); - throw exception; // if already attempted once throw out of capacity exception, - } - provisionLB = true; // if possible provision a LB appliance in the physical network - } - } finally { - deviceMapLock.unlock(); - if (externalLoadBalancer == null) { - txn.rollback(); - } - } - } - } finally { - deviceMapLock.releaseRef(); - } - - // there are no LB devices or there is no free capacity on the devices in the physical network so provision a new LB appliance - if (provisionLB) { - // check if LB appliance can be dynamically provisioned - List providerLbDevices = _externalLoadBalancerDeviceDao.listByProviderAndDeviceAllocationState(physicalNetworkId, provider, LBDeviceAllocationState.Provider); - if ((providerLbDevices != null) && (!providerLbDevices.isEmpty())) { - for (ExternalLoadBalancerDeviceVO lbProviderDevice : providerLbDevices) { - if (lbProviderDevice.getState() == LBDeviceState.Enabled) { - // acquire a private IP needed for LB appliance to be provisioned - // TODO: get the ip from the pool of private IP's configured for physical network - String lbIP = null; - - //TODO: get the configuration details needed to provision LB instances - String username = null; - String password = null; - String publiInterface = null; - String privateInterface = null; - - // send CreateLBApplianceCommand to the host capable of provisioning - CreateLBApplianceCommand lbProvisionCmd = new CreateLBApplianceCommand(lbIP); - Answer answer = _agentMgr.easySend(lbProviderDevice.getHostId(), lbProvisionCmd); - - if (answer == null || !answer.getResult()) { - s_logger.trace("Could not provision load balancer instance on the load balancer device " + lbProviderDevice.getId()); - continue; - } - - //add the appliance as external load balancer - //addExternalLoadBalancer(); - //add the appliance to pool of the load balancers - } - } - } - } - } + ExternalLoadBalancerDeviceVO lbDeviceVO = allocateLoadBalancerForNetwork(guestConfig); + externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); + s_logger.debug("Allocated external load balancer device:" + lbDeviceVO.getId() + " for the network: " + guestConfig.getId()); } else { // find the load balancer device allocated for the network - ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(guestConfig); + ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(guestConfig); if (lbDeviceVO == null) { - s_logger.warn("network shutdwon requested on external load balancer, which did no implement the network. Did the network implement failed half way throug?"); - return true; //bail out nothing to do here + s_logger.warn("network shutdwon requested on external load balancer, which did not implement the network." + + " Either network implement failed half way through or already network shutdown is completed. So just returning."); + return true; } externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); @@ -725,10 +859,24 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase // Send a command to the external load balancer to implement or shutdown the guest network long guestVlanTag = Long.parseLong(guestConfig.getBroadcastUri().getHost()); - String selfIp = NetUtils.long2Ip(NetUtils.ip2Long(guestConfig.getGateway()) + 1); + String selfIp = null; String guestVlanNetmask = NetUtils.cidr2Netmask(guestConfig.getCidr()); Integer networkRate = _networkMgr.getNetworkRate(guestConfig.getId(), null); + if (add) { + // Acquire a self-ip address from the guest network IP address range + selfIp = _networkMgr.acquireGuestIpAddress(guestConfig, null); + if (selfIp == null) { + String msg = "failed to acquire guest IP address so not implementing the network on the external load balancer "; + s_logger.error(msg); + throw new InsufficientNetworkCapacityException(msg, Network.class, guestConfig.getId()); + } + } else { + // get the self-ip used by the load balancer + NicVO selfipNic = getPlaceholderNic(guestConfig); + selfIp = selfipNic.getIp4Address(); + } + IpAddressTO ip = new IpAddressTO(guestConfig.getAccountId(), null, add, false, true, String.valueOf(guestVlanTag), selfIp, guestVlanNetmask, null, null, networkRate, false); IpAddressTO[] ips = new IpAddressTO[1]; ips[0] = ip; @@ -740,38 +888,30 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase String answerDetails = (answer != null) ? answer.getDetails() : "answer was null"; String msg = "External load balancer was unable to " + action + " the guest network on the external load balancer in zone " + zone.getName() + " due to " + answerDetails; s_logger.error(msg); - throw new ResourceUnavailableException(msg, DataCenter.class, zoneId); + throw new ResourceUnavailableException(msg, Network.class, guestConfig.getId()); } - List reservedIpAddressesForGuestNetwork = _nicDao.listIpAddressInNetwork(guestConfig.getId()); - if (add && (!reservedIpAddressesForGuestNetwork.contains(selfIp))) { + if (add) { // Insert a new NIC for this guest network to reserve the self IP savePlaceholderNic(guestConfig, selfIp); - } + } else { + // release the self-ip obtained from guest network + NicVO selfipNic = getPlaceholderNic(guestConfig); + _nicDao.remove(selfipNic.getId()); - if (!add) { - Transaction txn = Transaction.currentTxn(); - txn.start(); - - // since network is shutdown remove the network mapping to the load balancer device - NetworkExternalLoadBalancerVO networkLBDevice = _networkExternalLBDao.findByNetworkId(guestConfig.getId()); - _networkExternalLBDao.remove(networkLBDevice.getId()); - - // if this is the last network mapped to the load balancer device then set device allocation state to be free - List ntwksMapped = _networkExternalLBDao.listByLoadBalancerDeviceId(networkLBDevice.getExternalLBDeviceId()); - if (ntwksMapped == null || ntwksMapped.isEmpty()) { - ExternalLoadBalancerDeviceVO lbDevice = _externalLoadBalancerDeviceDao.findById(networkLBDevice.getExternalLBDeviceId()); - lbDevice.setAllocationState(LBDeviceAllocationState.Free); - _externalLoadBalancerDeviceDao.update(lbDevice.getId(), lbDevice); - - //TODO: if device is cloud managed then take action + // release the load balancer allocated for the network + boolean releasedLB = freeLoadBalancerForNetwork(guestConfig); + if (!releasedLB) { + String msg = "Failed to release the external load balancer used for the network: " + guestConfig.getId(); + s_logger.error(msg); } - txn.commit(); } - Account account = _accountDao.findByIdIncludingRemoved(guestConfig.getAccountId()); - String action = add ? "implemented" : "shut down"; - s_logger.debug("External load balancer has " + action + " the guest network for account " + account.getAccountName() + "(id = " + account.getAccountId() + ") with VLAN tag " + guestVlanTag); + if (s_logger.isDebugEnabled()) { + Account account = _accountDao.findByIdIncludingRemoved(guestConfig.getAccountId()); + String action = add ? "implemented" : "shut down"; + s_logger.debug("External load balancer has " + action + " the guest network for account " + account.getAccountName() + "(id = " + account.getAccountId() + ") with VLAN tag " + guestVlanTag); + } return true; } @@ -820,7 +960,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase } } } catch (Exception e) { - s_logger.warn("Problems while getting external network usage", e); + s_logger.warn("Problems while getting external load balancer device usage", e); } finally { scanLock.releaseRef(); } @@ -831,38 +971,33 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase for (DataCenterVO zone : _dcDao.listAll()) { List domainRoutersInZone = _routerDao.listByDataCenter(zone.getId()); + if (domainRoutersInZone == null) { + continue; + } + for (DomainRouterVO domainRouter : domainRoutersInZone) { long accountId = domainRouter.getAccountId(); long zoneId = zone.getId(); + List networksForAccount = _networkDao.listBy(accountId, zoneId, Network.GuestType.Isolated); - + if (networksForAccount == null) { + continue; + } + for (NetworkVO network : networksForAccount) { if (!_networkMgr.networkIsConfiguredForExternalNetworking(zoneId, network.getId())) { s_logger.debug("Network " + network.getId() + " is not configured for external networking, so skipping usage check."); continue; } - HostVO externalFirewall = getFirewallProviderForNetwork(network); ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network); - assert(lbDeviceVO != null); - HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); - s_logger.debug("Collecting external network stats for network " + network.getId()); - - ExternalNetworkResourceUsageCommand cmd = new ExternalNetworkResourceUsageCommand(); - - // Get network stats from the external firewall - ExternalNetworkResourceUsageAnswer firewallAnswer = null; - if (externalFirewall != null) { - firewallAnswer = (ExternalNetworkResourceUsageAnswer) _agentMgr.easySend(externalFirewall.getId(), cmd); - if (firewallAnswer == null || !firewallAnswer.getResult()) { - String details = (firewallAnswer != null) ? firewallAnswer.getDetails() : "details unavailable"; - String msg = "Unable to get external firewall stats for " + zone.getName() + " due to: " + details + "."; - s_logger.error(msg); - continue; - } + if (lbDeviceVO == null) { + continue; } - // Get network stats from the external load balancer + // Get network stats from the external load balancer + HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); + ExternalNetworkResourceUsageCommand cmd = new ExternalNetworkResourceUsageCommand(); ExternalNetworkResourceUsageAnswer lbAnswer = null; if (externalLoadBalancer != null) { lbAnswer = (ExternalNetworkResourceUsageAnswer) _agentMgr.easySend(externalLoadBalancer.getId(), cmd); @@ -878,12 +1013,12 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase s_logger.debug("Skipping stats update for account with ID " + accountId); continue; } - - if (!manageStatsEntries(true, accountId, zoneId, network, externalFirewall, firewallAnswer, externalLoadBalancer, lbAnswer)) { + + if (!manageStatsEntries(true, accountId, zoneId, network, externalLoadBalancer, lbAnswer)) { continue; } - - manageStatsEntries(false, accountId, zoneId, network, externalFirewall, firewallAnswer, externalLoadBalancer, lbAnswer); + + manageStatsEntries(false, accountId, zoneId, network, externalLoadBalancer, lbAnswer); } } } @@ -910,10 +1045,8 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase return _userStatsDao.update(userStats.getId(), userStats); } - - /* - * Creates a new stats entry for the specified parameters, if one doesn't already exist. - */ + + //Creates a new stats entry for the specified parameters, if one doesn't already exist. private boolean createStatsEntry(long accountId, long zoneId, long networkId, String publicIp, long hostId) { HostVO host = _hostDao.findById(hostId); UserStatisticsVO userStats = _userStatsDao.findBy(accountId, zoneId, networkId, publicIp, hostId, host.getType().toString()); @@ -923,10 +1056,8 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase return true; } } - - /* - * Updates an existing stats entry with new data from the specified usage answer. - */ + + // Updates an existing stats entry with new data from the specified usage answer. private boolean updateStatsEntry(long accountId, long zoneId, long networkId, String publicIp, long hostId, ExternalNetworkResourceUsageAnswer answer) { AccountVO account = _accountDao.findById(accountId); DataCenterVO zone = _dcDao.findById(zoneId); @@ -989,7 +1120,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase s_logger.warn("Unable to find user stats entry for " + statsEntryIdentifier); return false; } - + if (updateBytes(userStats, newCurrentBytesSent, newCurrentBytesReceived)) { s_logger.debug("Successfully updated stats for " + statsEntryIdentifier); return true; @@ -997,72 +1128,28 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase s_logger.debug("Failed to update stats for " + statsEntryIdentifier); return false; } - } - + } + private boolean createOrUpdateStatsEntry(boolean create, long accountId, long zoneId, long networkId, String publicIp, long hostId, ExternalNetworkResourceUsageAnswer answer) { if (create) { return createStatsEntry(accountId, zoneId, networkId, publicIp, hostId); } else { return updateStatsEntry(accountId, zoneId, networkId, publicIp, hostId, answer); - } + } } - + /* * Creates/updates all necessary stats entries for an account and zone. * Stats entries are created for source NAT IP addresses, static NAT rules, port forwarding rules, and load balancing rules */ private boolean manageStatsEntries(boolean create, long accountId, long zoneId, Network network, - HostVO externalFirewall, ExternalNetworkResourceUsageAnswer firewallAnswer, HostVO externalLoadBalancer, ExternalNetworkResourceUsageAnswer lbAnswer) { String accountErrorMsg = "Failed to update external network stats entry. Details: account ID = " + accountId; Transaction txn = Transaction.open(Transaction.CLOUD_DB); try { txn.start(); String networkErrorMsg = accountErrorMsg + ", network ID = " + network.getId(); - - boolean sharedSourceNat = false; - Map sourceNatCapabilities = _networkMgr.getNetworkServiceCapabilities(network.getId(), Service.SourceNat); - if (sourceNatCapabilities != null) { - String supportedSourceNatTypes = sourceNatCapabilities.get(Capability.SupportedSourceNatTypes).toLowerCase(); - if (supportedSourceNatTypes.contains("zone")) { - sharedSourceNat = true; - } - } - - if (!sharedSourceNat) { - // Manage the entry for this network's source NAT IP address - List sourceNatIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), true); - if (sourceNatIps.size() == 1) { - String publicIp = sourceNatIps.get(0).getAddress().addr(); - if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalFirewall.getId(), firewallAnswer)) { - throw new ExecutionException(networkErrorMsg + ", source NAT IP = " + publicIp); - } - } - - // Manage one entry for each static NAT rule in this network - List staticNatIps = _ipAddressDao.listStaticNatPublicIps(network.getId()); - for (IPAddressVO staticNatIp : staticNatIps) { - String publicIp = staticNatIp.getAddress().addr(); - if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalFirewall.getId(), firewallAnswer)) { - throw new ExecutionException(networkErrorMsg + ", static NAT rule public IP = " + publicIp); - } - } - - // Manage one entry for each port forwarding rule in this network - List portForwardingRules = _portForwardingRulesDao.listByNetwork(network.getId()); - for (PortForwardingRuleVO portForwardingRule : portForwardingRules) { - String publicIp = _networkMgr.getIp(portForwardingRule.getSourceIpAddressId()).getAddress().addr(); - if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalFirewall.getId(), firewallAnswer)) { - throw new ExecutionException(networkErrorMsg + ", port forwarding rule public IP = " + publicIp); - } - } - } else { - // Manage the account-wide entry for the external firewall - if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), null, externalFirewall.getId(), firewallAnswer)) { - throw new ExecutionException(networkErrorMsg); - } - } - + // If an external load balancer is added, manage one entry for each load balancing rule in this network if (externalLoadBalancer != null && lbAnswer != null) { List loadBalancers = _loadBalancerDao.listByNetworkId(network.getId()); @@ -1070,7 +1157,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase String publicIp = _networkMgr.getIp(loadBalancer.getSourceIpAddressId()).getAddress().addr(); if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalLoadBalancer.getId(), lbAnswer)) { throw new ExecutionException(networkErrorMsg + ", load balancing rule public IP = " + publicIp); - } + } } } return txn.commit();