bug CS-14291: support EIP with multiple NetScalers in basic zone

This fix will enable support for multiple NetScaler devices providing EIP service in same zone.

    - Introduced global setting "eip.use.multiple.netscalers" to turn multiple netscaler support
    - Enhanced configureNetscalerLoadBalancer API to take the PBR setup between the POD's subnet
      and NetScaler device
    - logic to pick a NetScaler (based on the guest IP and corresponding pod) while configuring INAT rule
This commit is contained in:
Murali reddy 2012-05-15 14:00:32 +05:30
parent ca5683e29d
commit 3b1aca19b3
10 changed files with 327 additions and 37 deletions

View File

@ -127,6 +127,7 @@ public class ApiConstants {
public static final String SSHKEY_ENABLED = "sshkeyenabled";
public static final String PATH = "path";
public static final String POD_ID = "podid";
public static final String POD_IDS = "podids";
public static final String POLICY_ID = "policyid";
public static final String PORT = "port";
public static final String PORTAL = "portal";

View File

@ -12,11 +12,16 @@
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.api.response;
import java.util.List;
import com.cloud.api.ApiConstants;
import com.cloud.api.Parameter;
import com.cloud.api.BaseCmd.CommandType;
import com.cloud.utils.IdentityProxy;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
@SuppressWarnings("unused")
public class NetscalerLoadBalancerResponse extends BaseResponse {
@SerializedName(ApiConstants.LOAD_BALANCER_DEVICE_ID) @Param(description="device id of the netscaler load balancer")
@ -52,6 +57,11 @@ public class NetscalerLoadBalancerResponse extends BaseResponse {
@SerializedName(ApiConstants.IP_ADDRESS) @Param(description="the management IP address of the external load balancer")
private String ipAddress;
@SerializedName(ApiConstants.POD_IDS) @Param(description="Used when NetScaler device is provider of EIP service." +
" This parameter represents the list of pod's, for which there exists a policy based route on datacenter L3 router to " +
"route pod's subnet IP to a NetScaler device.")
private List<Long> podIds;
public void setId(long lbDeviceId) {
this.id.setValue(lbDeviceId);
}
@ -95,4 +105,8 @@ public class NetscalerLoadBalancerResponse extends BaseResponse {
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public void setAssociatedPods(List<Long> pods) {
this.podIds = pods;
}
}

View File

@ -12,6 +12,8 @@
// Automatically generated by addcopyright.py at 04/03/2012
package com.cloud.api.commands;
import java.util.List;
import org.apache.log4j.Logger;
import com.cloud.api.ApiConstants;
@ -22,6 +24,7 @@ import com.cloud.api.Implementation;
import com.cloud.api.Parameter;
import com.cloud.api.PlugService;
import com.cloud.api.ServerApiException;
import com.cloud.api.BaseCmd.CommandType;
import com.cloud.api.response.NetscalerLoadBalancerResponse;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
@ -58,6 +61,12 @@ public class ConfigureNetscalerLoadBalancerCmd extends BaseAsyncCmd {
@Parameter (name=ApiConstants.INLINE, type=CommandType.BOOLEAN, required=false, description="true if netscaler load balancer is intended to be used in in-line with firewall, false if netscaler load balancer will side-by-side with firewall")
private Boolean inline;
@IdentityMapper(entityTableName="host_pod_ref")
@Parameter(name=ApiConstants.POD_IDS, type=CommandType.LIST, required=false, description="Used when NetScaler device is provider of EIP service." +
" This parameter represents the list of pod's, for which there exists a policy based route on datacenter L3 router to " +
"route pod's subnet IP to a NetScaler device.")
private List<Long> podIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -78,6 +87,10 @@ public class ConfigureNetscalerLoadBalancerCmd extends BaseAsyncCmd {
return inline;
}
public List<Long> getPodIds() {
return podIds;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -328,6 +328,7 @@ public enum Config {
DefaultExternalLoadBalancerCapacity("Advanced", ManagementServer.class, String.class, "external.lb.default.capacity", "50", "default number of networks permitted per external load balancer device", null),
DefaultExternalFirewallCapacity("Advanced", ManagementServer.class, String.class, "external.firewall.default.capacity", "50", "default number of networks permitted per external load firewall device", null),
EIPWithMultipleNetScalersEnabled("Advanced", ManagementServer.class, Boolean.class, "eip.use.multiple.netscalers", "false", "Should be set to true, if there will be multiple NetScaler devices providing EIP service in a zone", null),
CustomDiskOfferingMinSize("Advanced", ManagementServer.class, Long.class, "custom.diskoffering.size.min", "1", "Minimum size in GB for custom disk offering", null),
CustomDiskOfferingMaxSize("Advanced", ManagementServer.class, Long.class, "custom.diskoffering.size.max", "1024", "Maximum size in GB for custom disk offering", null),
ConsoleProxyServiceOffering("Advanced", ManagementServer.class, Long.class, "consoleproxy.service.offering", null, "Service offering used by console proxy; if NULL - system offering will be used", null),

View File

@ -79,6 +79,7 @@ import com.cloud.network.dao.InlineLoadBalancerNicMapDaoImpl;
import com.cloud.network.dao.LBStickinessPolicyDaoImpl;
import com.cloud.network.dao.LoadBalancerDaoImpl;
import com.cloud.network.dao.LoadBalancerVMMapDaoImpl;
import com.cloud.network.dao.NetScalerPodDaoImpl;
import com.cloud.network.dao.NetworkDaoImpl;
import com.cloud.network.dao.NetworkDomainDaoImpl;
import com.cloud.network.dao.NetworkExternalFirewallDaoImpl;
@ -315,6 +316,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com
addDao("ExternalFirewallDeviceDao", ExternalFirewallDeviceDaoImpl.class);
addDao("NetworkExternalLoadBalancerDao", NetworkExternalLoadBalancerDaoImpl.class);
addDao("NetworkExternalFirewallDao", NetworkExternalFirewallDaoImpl.class);
addDao("NetScalerPodDao", NetScalerPodDaoImpl.class);
addDao("PhysicalNetworkTrafficTypeDao", PhysicalNetworkTrafficTypeDaoImpl.class);
addDao("NetworkServiceMapDao", NetworkServiceMapDaoImpl.class);
addDao("StorageNetworkIpAddressDao", StorageNetworkIpAddressDaoImpl.class);

View File

@ -0,0 +1,61 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.cloud.network;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* NetScalerPodVO contains information about a EIP deployment where on datacenter L3 router a PBR (policy
* based routing) is setup between a POD's subnet IP range to a NetScaler device. This VO object
* represents a mapping between a POD and NetScaler device where PBR is setup.
*
*/
@Entity
@Table(name="netscaler_pod_ref")
public class NetScalerPodVO {
@Column(name="external_load_balancer_device_id")
private long netscalerDeviceId;
@Id
@Column(name="id")
private long id;
@Column(name="pod_id")
private long podId;
public NetScalerPodVO() {
}
public NetScalerPodVO(long netscalerDeviceId, long podId) {
this.netscalerDeviceId = netscalerDeviceId;
this.podId = podId;
}
public long getId() {
return id;
}
public long getPodId() {
return podId;
}
public long getNetscalerDeviceId() {
return netscalerDeviceId;
}
}

View File

@ -0,0 +1,24 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.cloud.network.dao;
import java.util.List;
import com.cloud.network.NetScalerPodVO;
import com.cloud.utils.db.GenericDao;
public interface NetScalerPodDao extends GenericDao<NetScalerPodVO, Long> {
NetScalerPodVO findByPodId(long podId);
List<NetScalerPodVO> listByNetScalerDeviceId(long netscalerDeviceId);
}

View File

@ -0,0 +1,57 @@
// Copyright 2012 Citrix Systems, Inc. Licensed under the
// Apache License, Version 2.0 (the "License"); you may not use this
// file except in compliance with the License. Citrix Systems, Inc.
// reserves all rights not expressly granted by the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.cloud.network.dao;
import java.util.List;
import javax.ejb.Local;
import com.cloud.network.NetScalerPodVO;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
@Local(value=NetScalerPodDao.class) @DB(txn=false)
public class NetScalerPodDaoImpl extends GenericDaoBase<NetScalerPodVO, Long> implements NetScalerPodDao {
final SearchBuilder<NetScalerPodVO> podIdSearch;
final SearchBuilder<NetScalerPodVO> deviceIdSearch;
protected NetScalerPodDaoImpl() {
super();
podIdSearch = createSearchBuilder();
podIdSearch.and("pod_id", podIdSearch.entity().getPodId(), Op.EQ);
podIdSearch.done();
deviceIdSearch = createSearchBuilder();
deviceIdSearch.and("netscalerDeviceId", deviceIdSearch.entity().getNetscalerDeviceId(), Op.EQ);
deviceIdSearch.done();
}
@Override
public NetScalerPodVO findByPodId(long podId) {
SearchCriteria<NetScalerPodVO> sc = podIdSearch.create();
sc.setParameters("pod_id", podId);
return findOneBy(sc);
}
@Override
public List<NetScalerPodVO> listByNetScalerDeviceId(long netscalerDeviceId) {
SearchCriteria<NetScalerPodVO> sc = deviceIdSearch.create();
sc.setParameters("netscalerDeviceId", netscalerDeviceId);
return search(sc, null);
}
}

View File

@ -41,8 +41,11 @@ import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.configuration.dao.ConfigurationDao;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterIpAddressVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.DataCenterIpAddressDao;
import com.cloud.deploy.DeployDestination;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
@ -59,6 +62,7 @@ import com.cloud.network.ExternalLoadBalancerDeviceVO;
import com.cloud.network.ExternalLoadBalancerDeviceVO.LBDeviceState;
import com.cloud.network.ExternalNetworkDeviceManager.NetworkDevice;
import com.cloud.network.IpAddress;
import com.cloud.network.NetScalerPodVO;
import com.cloud.network.Network;
import com.cloud.network.Network.Capability;
import com.cloud.network.Network.Provider;
@ -71,6 +75,7 @@ import com.cloud.network.PhysicalNetworkServiceProvider;
import com.cloud.network.PhysicalNetworkVO;
import com.cloud.network.PublicIpAddress;
import com.cloud.network.dao.ExternalLoadBalancerDeviceDao;
import com.cloud.network.dao.NetScalerPodDao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkExternalLoadBalancerDao;
import com.cloud.network.dao.NetworkServiceMapDao;
@ -129,7 +134,11 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl
HostDetailsDao _detailsDao;
@Inject
ConfigurationDao _configDao;
@Inject
NetScalerPodDao _netscalerPodDao;
@Inject
DataCenterIpAddressDao _privateIpAddressDao;
private boolean canHandle(Network config, Service service) {
DataCenter zone = _dcDao.findById(config.getDataCenterId());
boolean handleInAdvanceZone = (zone.getNetworkType() == NetworkType.Advanced && config.getGuestType() == Network.GuestType.Isolated && config.getTrafficType() == TrafficType.Guest);
@ -324,16 +333,16 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl
Boolean dedicatedUse = cmd.getLoadBalancerDedicated();
Boolean inline = cmd.getLoadBalancerInline();
Long capacity = cmd.getLoadBalancerCapacity();
List<Long> podIds = cmd.getPodIds();
try {
return configureNetscalerLoadBalancer(lbDeviceId, capacity, inline, dedicatedUse);
return configureNetscalerLoadBalancer(lbDeviceId, capacity, inline, dedicatedUse, podIds);
} catch (Exception e) {
throw new CloudRuntimeException("failed to configure netscaler device due to " + e.getMessage());
}
}
@DB
private ExternalLoadBalancerDeviceVO configureNetscalerLoadBalancer(long lbDeviceId, Long capacity, Boolean inline, Boolean dedicatedUse) {
private ExternalLoadBalancerDeviceVO configureNetscalerLoadBalancer(long lbDeviceId, Long capacity, Boolean inline, Boolean dedicatedUse, List<Long> newPodsConfig) {
ExternalLoadBalancerDeviceVO lbDeviceVo = _lbDeviceDao.findById(lbDeviceId);
Map<String, String> lbDetails = _detailsDao.findDetails(lbDeviceVo.getHostId());
@ -341,6 +350,37 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl
throw new InvalidParameterValueException("No netscaler device found with ID: " + lbDeviceId);
}
List<Long> currentPodsConfig = new ArrayList<Long>();
List<NetScalerPodVO> currentPodVOs = _netscalerPodDao.listByNetScalerDeviceId(lbDeviceVo.getId());
if (currentPodVOs != null && currentPodVOs.size() > 0) {
for (NetScalerPodVO nsPodVo: currentPodVOs) {
currentPodsConfig.add(nsPodVo.getPodId());
}
}
List<Long> podsToAssociate = new ArrayList<Long>();
if (newPodsConfig != null && newPodsConfig.size() > 0) {
for (Long podId: newPodsConfig) {
HostPodVO pod = _podDao.findById(podId);
if (pod == null) {
throw new InvalidParameterValueException("Can't find pod by id " + podId);
}
}
for (Long podId: newPodsConfig) {
if (!currentPodsConfig.contains(podId)) {
podsToAssociate.add(podId);
}
}
}
List<Long> podsToDeassociate = new ArrayList<Long>();
for (Long podId: currentPodsConfig) {
if (!newPodsConfig.contains(podId)) {
podsToDeassociate.add(podId);
}
}
String deviceName = lbDeviceVo.getDeviceName();
if (dedicatedUse != null || capacity != null || inline != null) {
if (NetworkDevice.NetscalerSDXLoadBalancer.getName().equalsIgnoreCase(deviceName) ||
@ -395,6 +435,16 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl
_lbDeviceDao.update(lbDeviceId, lbDeviceVo);
for (Long podId: podsToAssociate) {
NetScalerPodVO nsPodVo = new NetScalerPodVO(lbDeviceId, podId);
_netscalerPodDao.persist(nsPodVo);
}
for (Long podId: podsToDeassociate) {
NetScalerPodVO nsPodVo = _netscalerPodDao.findByPodId(podId);
_netscalerPodDao.remove(nsPodVo.getId());
}
// FIXME get the row lock to avoid race condition
_detailsDao.persist(lbDeviceVo.getHostId(), lbDetails);
HostVO host = _hostDao.findById(lbDeviceVo.getHostId());
@ -485,6 +535,15 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl
response.setProvider(lbDeviceVO.getProviderName());
response.setDeviceState(lbDeviceVO.getState().name());
response.setObjectName("netscalerloadbalancer");
List<Long> associatedPods = new ArrayList<Long>();
List<NetScalerPodVO> currentPodVOs = _netscalerPodDao.listByNetScalerDeviceId(lbDeviceVO.getId());
if (currentPodVOs != null && currentPodVOs.size() > 0) {
for (NetScalerPodVO nsPodVo: currentPodVOs) {
associatedPods.add(nsPodVo.getPodId());
}
}
response.setAssociatedPods(associatedPods);
return response;
}
@ -630,47 +689,96 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl
return false;
}
String errMsg;
ExternalLoadBalancerDeviceVO lbDevice = getExternalLoadBalancerForNetwork(config);
if (lbDevice == null) {
try {
lbDevice = allocateLoadBalancerForNetwork(config);
} catch (Exception e) {
errMsg = "Could not allocate a NetSclaer load balancer for configuring static NAT rules due to" + e.getMessage();
s_logger.error(errMsg);
throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
}
}
boolean multiNetScalerDeployment = Boolean.valueOf(_configDao.getValue(Config.EIPWithMultipleNetScalersEnabled.key()));
if (!isNetscalerDevice(lbDevice.getDeviceName())) {
errMsg = "There are no NetScaler load balancer assigned for this network. So NetScaler element will not be handling the static nat rules.";
s_logger.error(errMsg);
throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
}
SetStaticNatRulesAnswer answer = null;
try {
List<StaticNatRuleTO> rulesTO = null;
if (rules != null) {
rulesTO = new ArrayList<StaticNatRuleTO>();
for (StaticNat rule : rules) {
IpAddress sourceIp = _networkMgr.getIp(rule.getSourceIpAddressId());
StaticNatRuleTO ruleTO = new StaticNatRuleTO(0, sourceIp.getAddress().addr(), null, null, rule.getDestIpAddress(), null, null, null, rule.isForRevoke(), false);
rulesTO.add(ruleTO);
if (!multiNetScalerDeployment) {
String errMsg;
ExternalLoadBalancerDeviceVO lbDevice = getExternalLoadBalancerForNetwork(config);
if (lbDevice == null) {
try {
lbDevice = allocateLoadBalancerForNetwork(config);
} catch (Exception e) {
errMsg = "Could not allocate a NetSclaer load balancer for configuring static NAT rules due to" + e.getMessage();
s_logger.error(errMsg);
throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
}
}
if (!isNetscalerDevice(lbDevice.getDeviceName())) {
errMsg = "There are no NetScaler load balancer assigned for this network. So NetScaler element will not be handling the static nat rules.";
s_logger.error(errMsg);
throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
}
SetStaticNatRulesAnswer answer = null;
List<StaticNatRuleTO> rulesTO = null;
if (rules != null) {
rulesTO = new ArrayList<StaticNatRuleTO>();
for (StaticNat rule : rules) {
IpAddress sourceIp = _networkMgr.getIp(rule.getSourceIpAddressId());
StaticNatRuleTO ruleTO = new StaticNatRuleTO(0, sourceIp.getAddress().addr(), null, null, rule.getDestIpAddress(), null, null, null, rule.isForRevoke(), false);
rulesTO.add(ruleTO);
}
}
SetStaticNatRulesCommand cmd = new SetStaticNatRulesCommand(rulesTO);
answer = (SetStaticNatRulesAnswer) _agentMgr.send(lbDevice.getHostId(), cmd);
if (answer == null) {
return false;
} else {
return answer.getResult();
}
} else {
if (rules != null) {
for (StaticNat rule : rules) {
// validate if EIP rule can be configured.
ExternalLoadBalancerDeviceVO lbDevice = getNetScalerForEIP(rule);
if (lbDevice == null) {
String errMsg = "There is no NetScaler device configured to perform EIP to guest IP address: " + rule.getDestIpAddress();
s_logger.error(errMsg);
throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
}
List<StaticNatRuleTO> rulesTO = new ArrayList<StaticNatRuleTO>();
IpAddress sourceIp = _networkMgr.getIp(rule.getSourceIpAddressId());
StaticNatRuleTO ruleTO = new StaticNatRuleTO(0, sourceIp.getAddress().addr(), null, null, rule.getDestIpAddress(), null, null, null, rule.isForRevoke(), false);
rulesTO.add(ruleTO);
SetStaticNatRulesCommand cmd = new SetStaticNatRulesCommand(rulesTO);
// send commands to configure INAT rule on the NetScaler device
SetStaticNatRulesAnswer answer = (SetStaticNatRulesAnswer) _agentMgr.send(lbDevice.getHostId(), cmd);
if (answer == null) {
String errMsg = "Failed to configure INAT rule on NetScaler device " + lbDevice.getHostId();
s_logger.error(errMsg);
throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
}
}
return true;
}
}
SetStaticNatRulesCommand cmd = new SetStaticNatRulesCommand(rulesTO);
answer = (SetStaticNatRulesAnswer) _agentMgr.send(lbDevice.getHostId(), cmd);
if (answer == null) {
return false;
} else {
return answer.getResult();
}
return true;
} catch (Exception e) {
s_logger.error("Failed to configure StaticNat rule due to " + e.getMessage());
return false;
}
}
// returns configured NetScaler device that is associated with the pod that owns guest IP
private ExternalLoadBalancerDeviceVO getNetScalerForEIP(StaticNat rule) {
String guestIP = rule.getDestIpAddress();
List <DataCenterIpAddressVO> dcGuestIps = _privateIpAddressDao.listAll();
if (dcGuestIps != null) {
for (DataCenterIpAddressVO dcGuestIp: dcGuestIps) {
if (dcGuestIp.getIpAddress().equalsIgnoreCase(guestIP)) {
long podId = dcGuestIp.getPodId();
NetScalerPodVO nsPodVO = _netscalerPodDao.findByPodId(podId);
if (nsPodVO != null) {
ExternalLoadBalancerDeviceVO lbDeviceVO = _lbDeviceDao.findById(nsPodVO.getNetscalerDeviceId());
return lbDeviceVO;
}
}
}
}
return null;
}
}

View File

@ -2079,4 +2079,13 @@ CREATE TABLE `cloud`.`op_user_stats_log` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `cloud`.`netscaler_pod_ref` (
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
`external_load_balancer_device_id` bigint unsigned NOT NULL COMMENT 'id of external load balancer device',
`pod_id` bigint unsigned NOT NULL COMMENT 'pod id',
PRIMARY KEY (`id`),
CONSTRAINT `fk_ns_pod_ref__pod_id` FOREIGN KEY (`pod_id`) REFERENCES `cloud`.`host_pod_ref`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_ns_pod_ref__device_id` FOREIGN KEY (`external_load_balancer_device_id`) REFERENCES `external_load_balancer_devices`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET foreign_key_checks = 1;