content: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  package com.cloud.network.element;
  
  import java.net.URI;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.UUID;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.api.ApiConstants;
  import org.apache.cloudstack.api.ApiErrorCode;
  import org.apache.cloudstack.api.ServerApiException;
  import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice;
  import org.apache.cloudstack.region.gslb.GslbServiceProvider;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.NetScalerImplementNetworkCommand;
  import com.cloud.agent.api.routing.GlobalLoadBalancerConfigCommand;
  import com.cloud.agent.api.routing.HealthCheckLBConfigAnswer;
  import com.cloud.agent.api.routing.HealthCheckLBConfigCommand;
  import com.cloud.agent.api.routing.LoadBalancerConfigCommand;
  import com.cloud.agent.api.routing.SetStaticNatRulesAnswer;
  import com.cloud.agent.api.routing.SetStaticNatRulesCommand;
  import com.cloud.agent.api.to.LoadBalancerTO;
  import com.cloud.agent.api.to.StaticNatRuleTO;
  import com.cloud.api.ApiDBUtils;
  import com.cloud.api.commands.AddNetscalerLoadBalancerCmd;
  import com.cloud.api.commands.ConfigureNetscalerLoadBalancerCmd;
  import com.cloud.api.commands.DeleteNetscalerControlCenterCmd;
  import com.cloud.api.commands.DeleteNetscalerLoadBalancerCmd;
  import com.cloud.api.commands.DeleteServicePackageOfferingCmd;
  import com.cloud.api.commands.DeployNetscalerVpxCmd;
  import com.cloud.api.commands.ListNetscalerControlCenterCmd;
  import com.cloud.api.commands.ListNetscalerLoadBalancerNetworksCmd;
  import com.cloud.api.commands.ListNetscalerLoadBalancersCmd;
  import com.cloud.api.commands.ListRegisteredServicePackageCmd;
  import com.cloud.api.commands.RegisterNetscalerControlCenterCmd;
  import com.cloud.api.commands.RegisterServicePackageCmd;
  import com.cloud.api.commands.StopNetScalerVMCmd;
  import com.cloud.api.response.NetScalerServicePackageResponse;
  import com.cloud.api.response.NetscalerControlCenterResponse;
  import com.cloud.api.response.NetscalerLoadBalancerResponse;
  import com.cloud.configuration.Config;
  import com.cloud.configuration.ConfigurationManager;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenter.NetworkType;
  import com.cloud.dc.DataCenterIpAddressVO;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.HostPodVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.DataCenterIpAddressDao;
  import com.cloud.deploy.DataCenterDeployment;
  import com.cloud.deploy.DeployDestination;
  import com.cloud.deploy.DeploymentPlan;
  import com.cloud.event.ActionEvent;
  import com.cloud.event.EventTypes;
  import com.cloud.exception.AgentUnavailableException;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InsufficientCapacityException;
  import com.cloud.exception.InsufficientNetworkCapacityException;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.host.Host;
  import com.cloud.host.Host.Type;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.host.dao.HostDetailsDao;
  import com.cloud.network.ExternalLoadBalancerDeviceManager;
  import com.cloud.network.ExternalLoadBalancerDeviceManagerImpl;
  import com.cloud.network.IpAddress;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.NetScalerControlCenterVO;
  import com.cloud.network.NetScalerPodVO;
  import com.cloud.network.NetScalerServicePackageVO;
  import com.cloud.network.Network;
  import com.cloud.network.Network.Capability;
  import com.cloud.network.Network.Provider;
  import com.cloud.network.Network.Service;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.network.PhysicalNetwork;
  import com.cloud.network.PhysicalNetworkServiceProvider;
  import com.cloud.network.PublicIpAddress;
  import com.cloud.network.as.AutoScaleCounter;
  import com.cloud.network.as.AutoScaleCounter.AutoScaleCounterType;
  import com.cloud.network.dao.ExternalLoadBalancerDeviceDao;
  import com.cloud.network.dao.ExternalLoadBalancerDeviceVO;
  import com.cloud.network.dao.ExternalLoadBalancerDeviceVO.LBDeviceState;
  import com.cloud.network.dao.NetScalerControlCenterDao;
  import com.cloud.network.dao.NetScalerPodDao;
  import com.cloud.network.dao.NetScalerServicePackageDao;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkExternalLoadBalancerDao;
  import com.cloud.network.dao.NetworkExternalLoadBalancerVO;
  import com.cloud.network.dao.NetworkServiceMapDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkVO;
  import com.cloud.network.lb.LoadBalancingRule;
  import com.cloud.network.lb.LoadBalancingRulesManager;
  import com.cloud.network.lb.LoadBalancingRule.LbDestination;
  import com.cloud.network.resource.NetScalerControlCenterResource;
  import com.cloud.network.resource.NetscalerResource;
  import com.cloud.network.router.VirtualRouter;
  import com.cloud.network.rules.FirewallRule;
  import com.cloud.network.rules.LbStickinessMethod;
  import com.cloud.network.rules.LbStickinessMethod.StickinessMethodType;
  import com.cloud.network.rules.LoadBalancerContainer;
  import com.cloud.network.rules.StaticNat;
  import com.cloud.network.vm.NetScalerVMManager;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.offerings.dao.NetworkOfferingDao;
  import com.cloud.resource.ResourceManager;
  import com.cloud.resource.ResourceState;
  import com.cloud.resource.ServerResource;
  import com.cloud.user.Account;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.crypt.DBEncryptionUtil;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallback;
  import com.cloud.utils.db.TransactionCallbackNoReturn;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.net.UrlUtil;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.ReservationContext;
  import com.cloud.vm.VirtualMachineProfile;
  import com.google.gson.Gson;
  
  public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl
  implements LoadBalancingServiceProvider, NetscalerLoadBalancerElementService, ExternalLoadBalancerDeviceManager,
  IpDeployer, StaticNatServiceProvider, GslbServiceProvider {
  
  
      @Inject
      NetworkModel _networkManager;
      @Inject
      ConfigurationManager _configMgr;
      @Inject
      NetworkServiceMapDao _ntwkSrvcDao;
      @Inject
      AgentManager _agentMgr;
      @Inject
      NetworkModel _networkMgr;
      @Inject
      HostDao _hostDao;
      @Inject
      DataCenterDao _dcDao;
      @Inject
      ExternalLoadBalancerDeviceDao _lbDeviceDao;
      @Inject
      NetScalerControlCenterDao _netscalerControlCenterDao;
      @Inject
      NetworkExternalLoadBalancerDao _networkLBDao;
      @Inject
      PhysicalNetworkDao _physicalNetworkDao;
      @Inject
      NetworkDao _networkDao;
      @Inject
      HostDetailsDao _detailsDao;
      @Inject
      ConfigurationDao _configDao;
      @Inject
      NetScalerPodDao _netscalerPodDao;
      @Inject
      DataCenterIpAddressDao _privateIpAddressDao;
      @Inject
      ExternalLoadBalancerDeviceDao _externalLoadBalancerDeviceDao;
      @Inject
      NetScalerServicePackageDao _netscalerServicePackageDao;
      @Inject
      ResourceManager _resourceMgr;
      @Inject
      HostDetailsDao _hostDetailDao;
      @Inject
      IpAddressManager _ipAddrMgr;
      @Inject
      NetworkOrchestrationService _networkService;
      @Inject
      NetworkOfferingDao _networkOfferingDao = null;
      @Inject
      NetScalerVMManager _netScalerVMManager;
      @Inject
      LoadBalancingRulesManager lbRulesManager;
  
      private boolean canHandle(Network config, Service service) {
          DataCenter zone = _dcDao.findById(config.getDataCenterId());
          // Create a NCC Resource on Demand for the zone.
  
          boolean handleInAdvanceZone = (zone.getNetworkType() == NetworkType.Advanced
                  && (config.getGuestType() == Network.GuestType.Isolated
                  || config.getGuestType() == Network.GuestType.Shared)
                  && config.getTrafficType() == TrafficType.Guest);
          boolean handleInBasicZone = (zone.getNetworkType() == NetworkType.Basic
                  && config.getGuestType() == Network.GuestType.Shared && config.getTrafficType() == TrafficType.Guest);
  
          if (!(handleInAdvanceZone || handleInBasicZone)) {
              logger.trace("Not handling network with Type  " + config.getGuestType() + " and traffic type "
                      + config.getTrafficType() + " in zone of type " + zone.getNetworkType());
              return false;
          }
  
          return (_networkManager.isProviderForNetwork(getProvider(), config.getId()) && _ntwkSrvcDao
                  .canProviderSupportServiceInNetwork(config.getId(), service, Network.Provider.Netscaler));
      }
  
      private boolean isBasicZoneNetwok(Network config) {
          DataCenter zone = _dcDao.findById(config.getDataCenterId());
          return (zone.getNetworkType() == NetworkType.Basic && config.getGuestType() == Network.GuestType.Shared
                  && config.getTrafficType() == TrafficType.Guest);
      }
  
      @Override
      public boolean implement(Network guestConfig, NetworkOffering offering, DeployDestination dest,
              ReservationContext context) throws ResourceUnavailableException, ConcurrentOperationException,
      InsufficientNetworkCapacityException {
  
          if (!canHandle(guestConfig, Service.Lb)) {
              return false;
          }
  
          if (_ntwkSrvcDao.canProviderSupportServiceInNetwork(guestConfig.getId(), Service.StaticNat,
                  Network.Provider.Netscaler) && !isBasicZoneNetwok(guestConfig)) {
              logger.error("NetScaler provider can not be Static Nat service provider for the network "
                      + guestConfig.getGuestType() + " and traffic type " + guestConfig.getTrafficType());
              return false;
          }
  
          try {
              if (offering.getServicePackage() == null) {
                  return manageGuestNetworkWithExternalLoadBalancer(true, guestConfig);
              } else {
                  // if the network offering has service package implement it with
                  // Netscaler Control Center
                  return manageGuestNetworkWithNetscalerControlCenter(true, guestConfig, offering);
              }
          } catch (InsufficientCapacityException capacityException) {
              throw new ResourceUnavailableException(
                      "There are no NetScaler load balancer devices with the free capacity for implementing this network",
                      DataCenter.class, guestConfig.getDataCenterId());
          } catch (ConfigurationException e) {
              throw new ResourceUnavailableException(
                      "There are no NetScaler load balancer devices with the free capacity for implementing this network : "
                              + e.getMessage(),
                              DataCenter.class, guestConfig.getDataCenterId());
          }
      }
  
      @Override
      public HostVO getNetScalerControlCenterForNetwork(Network guestConfig) {
          long zoneId = guestConfig.getDataCenterId();
          return _hostDao.findByTypeNameAndZoneId(zoneId, "NetscalerControlCenter", Type.NetScalerControlCenter);
      }
  
      public HostVO allocateNCCResourceForNetwork(Network guestConfig) throws ConfigurationException {
  
          Map<String, String> _configs;
          List<NetScalerControlCenterVO> ncc = _netscalerControlCenterDao.listAll();
          HostVO hostVO = null;
          if (ncc.size() > 0) {
              NetScalerControlCenterVO nccVO = ncc.get(0);
              String ipAddress = nccVO.getNccip();
              Map hostDetails = new HashMap<String, String>();
              String hostName = "NetscalerControlCenter";
              hostDetails.put("name", hostName);
              hostDetails.put("guid", UUID.randomUUID().toString());
              hostDetails.put("zoneId", Long.toString(guestConfig.getDataCenterId()));
              hostDetails.put("ip", ipAddress);
              hostDetails.put("username", nccVO.getUsername());
              hostDetails.put("password", DBEncryptionUtil.decrypt(nccVO.getPassword()));
              hostDetails.put("deviceName", "netscaler control center");
              hostDetails.put("cmdTimeOut", Long.toString(NumbersUtil.parseInt(_configDao.getValue(Config.NCCCmdTimeOut.key()), 600000)));
              ServerResource resource = new NetScalerControlCenterResource();
              resource.configure(hostName, hostDetails);
              final Host host = _resourceMgr.addHost(guestConfig.getDataCenterId(), resource, Host.Type.NetScalerControlCenter, hostDetails);
              hostVO = _hostDao.findById(host.getId());
          }
          return hostVO;
      }
  
      public boolean manageGuestNetworkWithNetscalerControlCenter(boolean add, Network guestConfig,
              NetworkOffering offering)
                      throws ResourceUnavailableException, InsufficientCapacityException, ConfigurationException {
  
          if (guestConfig.getTrafficType() != TrafficType.Guest) {
              logger.trace("External load balancer can only be used for guest networks.");
              return false;
          }
  
          long zoneId = guestConfig.getDataCenterId();
          DataCenterVO zone = _dcDao.findById(zoneId);
          HostVO netscalerControlCenter = null;
  
          if (add) {
              HostVO lbDeviceVO = null;
              // on restart network, device could have been allocated already,
              // skip allocation if a device is assigned
              lbDeviceVO = getNetScalerControlCenterForNetwork(guestConfig);
              if (lbDeviceVO == null) {
                  // allocate a load balancer device for the network
                  lbDeviceVO = allocateNCCResourceForNetwork(guestConfig);
                  if (lbDeviceVO == null) {
                      String msg = String.format("failed to allocate Netscaler ControlCenter Resource for the zone in the network %s", guestConfig);
                      logger.error(msg);
                      throw new InsufficientNetworkCapacityException(msg, DataCenter.class,
                              guestConfig.getDataCenterId());
                  }
              }
              netscalerControlCenter = _hostDao.findById(lbDeviceVO.getId());
              logger.debug("Allocated Netscaler Control Center device: {} for the network: {}", lbDeviceVO, guestConfig);
          } else {
              // find the load balancer device allocated for the network
  
              HostVO lbDeviceVO = null;
              // on restart network, device could have been allocated already, skip allocation if a device is assigned
              lbDeviceVO = getNetScalerControlCenterForNetwork(guestConfig);
              if (lbDeviceVO == null) {
                  logger.warn(
                          "Network shutdwon requested on external load balancer element, which did not implement the network."
                                  + " Either network implement failed half way through or already network shutdown is completed. So just returning.");
                  return true;
              }
  
              netscalerControlCenter = _hostDao.findById(lbDeviceVO.getId());
              assert(netscalerControlCenter != null) : "There is no device assigned to this network how did shutdown network ended up here??";
          }
          JSONObject networkDetails = new JSONObject();
          JSONObject networkPayload = new JSONObject();
  
          String selfIp = null;
          try {
              networkDetails.put("id", guestConfig.getId());
              networkDetails.put("vlan", guestConfig.getBroadcastUri());
              networkDetails.put("cidr", guestConfig.getCidr());
              networkDetails.put("gateway", guestConfig.getGateway());
              networkDetails.put("servicepackage_id", offering.getServicePackage());
              networkDetails.put("zone_id", zone.getUuid());
              networkDetails.put("account_id", guestConfig.getAccountId());
              networkDetails.put("add", Boolean.toString(add));
              selfIp = _ipAddrMgr.acquireGuestIpAddress(guestConfig, null);
              if (selfIp == null) {
                  String msg = "failed to acquire guest IP address so not implementing the network on the NetscalerControlCenter";
                  logger.error(msg);
                  throw new InsufficientNetworkCapacityException(msg, Network.class, guestConfig.getId());
              }
              networkDetails.put("snip", selfIp);
              // TODO region is hardcoded make it dynamic
              networkDetails.put("region_id", 1);
              networkDetails.put("name", guestConfig.getName());
              networkPayload.put("network", networkDetails);
          } catch (JSONException e) {
              e.printStackTrace();
          }
  
          NetScalerImplementNetworkCommand cmd = new NetScalerImplementNetworkCommand(zone.getUuid(), netscalerControlCenter.getId(), networkPayload.toString());
          Answer answer = _agentMgr.easySend(netscalerControlCenter.getId(), cmd);
          if (add) {
              //TODO After getting the answer check with the job id and do poll on the job and then save the selfip or acquired guest ip to the Nics table
              if (answer != null) {
                  if (answer.getResult() == true) {
                      if (add) {
                          // Insert a new NIC for this guest network to reserve the self IP
                          _networkService.savePlaceholderNic(guestConfig, selfIp, null, null);
                      }
                  } else {
                      return false;
                  }
              }
          } else {
              if (answer != null) {
                  if (answer.getResult() == true) {
                      return true;
                  } else {
                      return false;
                  }
              }
              return false;
          }
          return true;
      }
  
      @Override
      public boolean prepare(Network config, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest,
              ReservationContext context) throws ConcurrentOperationException, InsufficientNetworkCapacityException,
      ResourceUnavailableException {
          return true;
      }
  
      @Override
      public boolean release(Network config, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) {
          return true;
      }
  
      @Override
      public boolean shutdown(Network guestConfig, ReservationContext context, boolean cleanup)
              throws ResourceUnavailableException, ConcurrentOperationException {
          if (!canHandle(guestConfig, Service.Lb)) {
              return false;
          }
  
          try {
  
              NetworkOffering networkOffering = _networkOfferingDao.findById(guestConfig.getNetworkOfferingId());
              if (networkOffering.getServicePackage() == null) {
                  return manageGuestNetworkWithExternalLoadBalancer(true, guestConfig);
              } else {
                  // if the network offering has service package implement it with Netscaler Control Center
                  return manageGuestNetworkWithNetscalerControlCenter(false, guestConfig, networkOffering);
              }
          } catch (InsufficientCapacityException capacityException) {
              // TODO: handle out of capacity exception gracefully in case of
              // multple providers available
              return false;
          } catch (ConfigurationException e) {
              e.printStackTrace();
          }
          return false;
      }
  
      @Override
      public boolean destroy(Network config, ReservationContext context) {
          return true;
      }
  
      @Override
      public boolean validateLBRule(Network network, LoadBalancingRule rule) {
          if (canHandle(network, Service.Lb)) {
              String algo = rule.getAlgorithm();
              return (algo.equals("roundrobin") || algo.equals("leastconn") || algo.equals("source"));
          }
          return true;
      }
  
      @Override
      public boolean applyLBRules(Network config, List<LoadBalancingRule> rules) throws ResourceUnavailableException {
          if (!canHandle(config, Service.Lb)) {
              return false;
          }
  
          if (!canHandleLbRules(rules)) {
              return false;
          }
  
          if (isBasicZoneNetwok(config)) {
              return applyElasticLoadBalancerRules(config, rules);
          } else {
              return applyLoadBalancerRules(config, rules);
          }
      }
  
      @Override
      public Map<Service, Map<Capability, String>> getCapabilities() {
          Map<Service, Map<Capability, String>> capabilities = new HashMap<Service, Map<Capability, String>>();
  
          // Set capabilities for LB service
          Map<Capability, String> lbCapabilities = new HashMap<Capability, String>();
  
          // Specifies that the RoundRobin and Leastconn algorithms are supported for load balancing rules
          lbCapabilities.put(Capability.SupportedLBAlgorithms, "roundrobin, leastconn, source");
  
          // specifies that Netscaler network element can provided both shared and
          // isolation modes
          lbCapabilities.put(Capability.SupportedLBIsolation, "dedicated, shared");
  
          // Specifies that load balancing rules can be made for either TCP or UDP
          // traffic
          lbCapabilities.put(Capability.SupportedProtocols, "tcp,udp,http");
  
          // Specifies that this element can measure network usage on a per public
          // IP basis
          lbCapabilities.put(Capability.TrafficStatistics, "per public ip");
  
          // Specifies that load balancing rules can only be made with public IPs
          // that aren't source NAT IPs
          lbCapabilities.put(Capability.LoadBalancingSupportedIps, "additional");
  
          // Supports only Public load balancing
          lbCapabilities.put(Capability.LbSchemes, LoadBalancerContainer.Scheme.Public.toString());
  
          // Support inline mode with firewall
          lbCapabilities.put(Capability.InlineMode, "true");
  
          // Specifies that load balancing rules can support autoscaling and the list of counters it supports
          // list of counters it supports
          AutoScaleCounter counter;
          List<AutoScaleCounter> counterList = new ArrayList<AutoScaleCounter>();
          counter = new AutoScaleCounter(AutoScaleCounterType.Snmp);
          counterList.add(counter);
          counter.addParam("snmpcommunity", true,
                  "the community string that has to be used to do a SNMP GET on the AutoScaled Vm", false);
          counter.addParam("snmpport", false, "the port at which SNMP agent is running on the AutoScaled Vm", false);
  
          counter = new AutoScaleCounter(AutoScaleCounterType.Netscaler);
          counterList.add(counter);
  
          Gson gson = new Gson();
          String autoScaleCounterList = gson.toJson(counterList);
          lbCapabilities.put(Capability.AutoScaleCounters, autoScaleCounterList);
          lbCapabilities.put(Capability.VmAutoScaling, "true");
  
          LbStickinessMethod method;
          List<LbStickinessMethod> methodList = new ArrayList<LbStickinessMethod>();
          method = new LbStickinessMethod(StickinessMethodType.LBCookieBased,
                  "This is cookie based sticky method, can be used only for http");
          methodList.add(method);
          method.addParam("holdtime", false, "time period in minutes for which persistence is in effect.", false);
  
          method = new LbStickinessMethod(StickinessMethodType.AppCookieBased,
                  "This is app session based sticky method, can be used only for http");
          methodList.add(method);
          method.addParam("name", true, "cookie name passed in http header by apllication to the client", false);
  
          method = new LbStickinessMethod(StickinessMethodType.SourceBased,
                  "This is source based sticky method, can be used for any type of protocol.");
          methodList.add(method);
          method.addParam("holdtime", false, "time period for which persistence is in effect.", false);
  
          String stickyMethodList = gson.toJson(methodList);
          lbCapabilities.put(Capability.SupportedStickinessMethods, stickyMethodList);
  
          lbCapabilities.put(Capability.ElasticLb, "true");
          // Setting HealthCheck Capability to True for Netscaler element
          lbCapabilities.put(Capability.HealthCheckPolicy, "true");
          capabilities.put(Service.Lb, lbCapabilities);
  
          Map<Capability, String> staticNatCapabilities = new HashMap<Capability, String>();
          staticNatCapabilities.put(Capability.ElasticIp, "true");
          capabilities.put(Service.StaticNat, staticNatCapabilities);
  
          // Supports SSL offloading
          lbCapabilities.put(Capability.SslTermination, "true");
  
          // TODO - Murali, please put correct capabilities here
          Map<Capability, String> firewallCapabilities = new HashMap<Capability, String>();
          firewallCapabilities.put(Capability.TrafficStatistics, "per public ip");
          firewallCapabilities.put(Capability.SupportedProtocols, "tcp,udp,icmp");
          firewallCapabilities.put(Capability.MultipleIps, "true");
          capabilities.put(Service.Firewall, firewallCapabilities);
  
          return capabilities;
      }
  
      @Override
      public ExternalLoadBalancerDeviceVO addNetscalerLoadBalancer(AddNetscalerLoadBalancerCmd cmd) {
          String deviceName = cmd.getDeviceType();
  
          if (!isNetscalerDevice(deviceName)) {
              throw new InvalidParameterValueException("Invalid Netscaler device type");
          }
  
          URI uri;
          try {
              uri = new URI(cmd.getUrl());
          } catch (Exception e) {
              String msg = "Error parsing the url parameter specified in addNetscalerLoadBalancer command due to "
                      + e.getMessage();
              logger.debug(msg);
              throw new InvalidParameterValueException(msg);
          }
          Map<String, String> configParams = new HashMap<String, String>();
          UrlUtil.parseQueryParameters(uri.getQuery(), false, configParams);
          boolean dedicatedUse = (configParams.get(ApiConstants.LOAD_BALANCER_DEVICE_DEDICATED) != null)
                  ? Boolean.parseBoolean(configParams.get(ApiConstants.LOAD_BALANCER_DEVICE_DEDICATED)) : false;
  
                  if (dedicatedUse && !deviceName.equals(NetworkDevice.NetscalerVPXLoadBalancer.getName())) {
                      String msg = "Only Netscaler VPX load balancers can be specified for dedicated use";
                      logger.debug(msg);
                      throw new InvalidParameterValueException(msg);
                  }
  
                  if (cmd.isGslbProvider()) {
  
                      if (!deviceName.equals(NetworkDevice.NetscalerVPXLoadBalancer.getName())
                              && !deviceName.equals(NetworkDevice.NetscalerMPXLoadBalancer.getName())) {
                          String msg = "Only Netscaler VPX or MPX load balancers can be specified as GSLB service provider";
                          logger.debug(msg);
                          throw new InvalidParameterValueException(msg);
                      }
  
                      if (cmd.getSitePublicIp() == null || cmd.getSitePrivateIp() == null) {
                          String msg = "Public and Privae IP needs to provided for NetScaler that will be GSLB provider";
                          logger.debug(msg);
                          throw new InvalidParameterValueException(msg);
                      }
  
                      if (dedicatedUse) {
                          throw new InvalidParameterValueException(
                                  "NetScaler provisioned to be GSLB service provider can only be configured for shared usage.");
                      }
  
                  }
  
                  if (cmd.isExclusiveGslbProvider() && !cmd.isGslbProvider()) {
                      throw new InvalidParameterValueException(
                              "NetScaler can be provisioned to be exclusive GSLB service provider"
                                      + " only if its being configured as GSLB service provider also.");
                  }
  
                  ExternalLoadBalancerDeviceVO lbDeviceVO = addExternalLoadBalancer(cmd.getPhysicalNetworkId(), cmd.getUrl(),
                          cmd.getUsername(), cmd.getPassword(), deviceName, new NetscalerResource(), cmd.isGslbProvider(),
                          cmd.isExclusiveGslbProvider(), cmd.getSitePublicIp(), cmd.getSitePrivateIp());
  
                  return lbDeviceVO;
      }
  
      @Override
      public boolean deleteNetscalerLoadBalancer(DeleteNetscalerLoadBalancerCmd cmd) {
          Long lbDeviceId = cmd.getLoadBalancerDeviceId();
  
          ExternalLoadBalancerDeviceVO lbDeviceVo = _lbDeviceDao.findById(lbDeviceId);
          if ((lbDeviceVo == null) || !isNetscalerDevice(lbDeviceVo.getDeviceName())) {
              throw new InvalidParameterValueException("No netscaler device found with ID: " + lbDeviceId);
          }
  
          return deleteExternalLoadBalancer(lbDeviceVo.getHostId());
      }
  
      @Override
      public ExternalLoadBalancerDeviceVO configureNetscalerLoadBalancer(ConfigureNetscalerLoadBalancerCmd cmd) {
          Long lbDeviceId = cmd.getLoadBalancerDeviceId();
          Boolean dedicatedUse = cmd.getLoadBalancerDedicated();
          Long capacity = cmd.getLoadBalancerCapacity();
          List<Long> podIds = cmd.getPodIds();
          try {
              return configureNetscalerLoadBalancer(lbDeviceId, capacity, dedicatedUse, podIds);
          } catch (Exception e) {
              throw new CloudRuntimeException("failed to configure netscaler device due to " + e.getMessage());
          }
      }
  
      @DB
      private ExternalLoadBalancerDeviceVO configureNetscalerLoadBalancer(final long lbDeviceId, Long capacity,
              Boolean dedicatedUse, List<Long> newPodsConfig) {
          final ExternalLoadBalancerDeviceVO lbDeviceVo = _lbDeviceDao.findById(lbDeviceId);
          final Map<String, String> lbDetails = _detailsDao.findDetails(lbDeviceVo.getHostId());
  
          if ((lbDeviceVo == null) || !isNetscalerDevice(lbDeviceVo.getDeviceName())) {
              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());
              }
          }
  
          final 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);
                  }
              }
          }
  
          final 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) {
              if (NetworkDevice.NetscalerSDXLoadBalancer.getName().equalsIgnoreCase(deviceName)
                      || NetworkDevice.NetscalerMPXLoadBalancer.getName().equalsIgnoreCase(deviceName)) {
                  if (dedicatedUse != null && dedicatedUse == true) {
                      throw new InvalidParameterValueException(
                              "Netscaler MPX and SDX device should be shared and can not be dedicated to a single account.");
                  }
              }
  
              // check if any networks are using this netscaler device
              List<NetworkExternalLoadBalancerVO> networks = _networkLBDao.listByLoadBalancerDeviceId(lbDeviceId);
              if ((networks != null) && !networks.isEmpty()) {
                  if (capacity != null && capacity < networks.size()) {
                      throw new CloudRuntimeException(
                              "There are more number of networks already using this netscaler device than configured capacity");
                  }
  
                  if (dedicatedUse != null && dedicatedUse == true) {
                      throw new CloudRuntimeException(
                              "There are networks already using this netscaler device to make device dedicated");
                  }
              }
          }
  
          if (!NetworkDevice.NetscalerSDXLoadBalancer.getName().equalsIgnoreCase(deviceName)) {
              if (capacity != null) {
                  lbDeviceVo.setCapacity(capacity);
              }
          } else {
              // FIXME how to interpret configured capacity of the SDX device
          }
  
          if (dedicatedUse != null) {
              lbDeviceVo.setIsDedicatedDevice(dedicatedUse);
          }
  
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                  _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());
  
          try {
              _agentMgr.reconnect(host.getId());
          } catch (AgentUnavailableException e) {
              logger.warn("failed to reconnect host " + host, e);
          }
          return lbDeviceVo;
      }
  
      @Override
      public List<Class<?>> getCommands() {
          List<Class<?>> cmdList = new ArrayList<Class<?>>();
          cmdList.add(AddNetscalerLoadBalancerCmd.class);
          cmdList.add(ConfigureNetscalerLoadBalancerCmd.class);
          cmdList.add(DeleteNetscalerLoadBalancerCmd.class);
          cmdList.add(ListNetscalerLoadBalancerNetworksCmd.class);
          cmdList.add(ListNetscalerLoadBalancersCmd.class);
          cmdList.add(RegisterServicePackageCmd.class);
          cmdList.add(RegisterNetscalerControlCenterCmd.class);
          cmdList.add(ListRegisteredServicePackageCmd.class);
          cmdList.add(ListNetscalerControlCenterCmd.class);
          cmdList.add(DeleteServicePackageOfferingCmd.class);
          cmdList.add(DeleteNetscalerControlCenterCmd.class);
          cmdList.add(DeployNetscalerVpxCmd.class);
          cmdList.add(StopNetScalerVMCmd.class);
          return cmdList;
      }
  
      @Override
      public List<? extends Network> listNetworks(ListNetscalerLoadBalancerNetworksCmd cmd) {
          Long lbDeviceId = cmd.getLoadBalancerDeviceId();
          List<NetworkVO> networks = new ArrayList<NetworkVO>();
  
          ExternalLoadBalancerDeviceVO lbDeviceVo = _lbDeviceDao.findById(lbDeviceId);
          if (lbDeviceVo == null || !isNetscalerDevice(lbDeviceVo.getDeviceName())) {
              throw new InvalidParameterValueException(
                      "Could not find Netscaler load balancer device with ID " + lbDeviceId);
          }
  
          List<NetworkExternalLoadBalancerVO> networkLbMaps = _networkLBDao.listByLoadBalancerDeviceId(lbDeviceId);
          if (networkLbMaps != null && !networkLbMaps.isEmpty()) {
              for (NetworkExternalLoadBalancerVO networkLbMap : networkLbMaps) {
                  NetworkVO network = _networkDao.findById(networkLbMap.getNetworkId());
                  networks.add(network);
              }
          }
  
          return networks;
      }
  
      @Override
      public List<ExternalLoadBalancerDeviceVO> listNetscalerLoadBalancers(ListNetscalerLoadBalancersCmd cmd) {
          Long physcialNetworkId = cmd.getPhysicalNetworkId();
          Long lbDeviceId = cmd.getLoadBalancerDeviceId();
          PhysicalNetworkVO pNetwork = null;
          List<ExternalLoadBalancerDeviceVO> lbDevices = new ArrayList<ExternalLoadBalancerDeviceVO>();
  
          if (physcialNetworkId == null && lbDeviceId == null) {
              throw new InvalidParameterValueException(
                      "Either physical network Id or load balancer device Id must be specified");
          }
  
          if (lbDeviceId != null) {
              ExternalLoadBalancerDeviceVO lbDeviceVo = _lbDeviceDao.findById(lbDeviceId);
              if (lbDeviceVo == null || !isNetscalerDevice(lbDeviceVo.getDeviceName())) {
                  throw new InvalidParameterValueException(
                          "Could not find Netscaler load balancer device with ID: " + lbDeviceId);
              }
              lbDevices.add(lbDeviceVo);
              return lbDevices;
          }
  
          if (physcialNetworkId != null) {
              pNetwork = _physicalNetworkDao.findById(physcialNetworkId);
              if (pNetwork == null) {
                  throw new InvalidParameterValueException(
                          "Could not find phyical network with ID: " + physcialNetworkId);
              }
              lbDevices = _lbDeviceDao.listByPhysicalNetworkAndProvider(physcialNetworkId, Provider.Netscaler.getName());
              return lbDevices;
          }
  
          return null;
      }
  
      @Override
      public List<NetScalerServicePackageVO> listRegisteredServicePackages(ListRegisteredServicePackageCmd cmd) {
  
          List<NetScalerServicePackageVO> lrsPackages = new ArrayList<NetScalerServicePackageVO>();
          lrsPackages = _netscalerServicePackageDao.listAll();
          return lrsPackages;
      }
  
      @Override
      public List<NetScalerControlCenterVO> listNetscalerControlCenter(ListNetscalerControlCenterCmd cmd) {
          return _netscalerControlCenterDao.listAll();
      }
  
      @Override
      public boolean deleteServicePackageOffering(DeleteServicePackageOfferingCmd cmd) throws CloudRuntimeException {
          NetScalerServicePackageVO result = null;
          boolean flag=false;
          try {
              result = _netscalerServicePackageDao.findByUuid(cmd.getId());
              if (result == null) {
                  throw new CloudRuntimeException("Record does not Exists in the Table");
              }
  
              if(_networkOfferingDao.isUsingServicePackage(result.getUuid()))
              {
                  throw new CloudRuntimeException("Network offering is using the service package. First delete the NeworkOffering and then delete ServicePackage");
              }
  
              flag = _netscalerServicePackageDao.remove(result.getId());
  
          } catch (Exception e) {
              if (e instanceof InvalidParameterValueException) {
                  throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage());
              } else {
                  throw e;
              }
  
          }
          return flag;
  
      }
  
      @DB
      @Override
      public boolean deleteNetscalerControlCenter(DeleteNetscalerControlCenterCmd cmd) throws CloudRuntimeException {
  
          NetScalerControlCenterVO result = _netscalerControlCenterDao.findByUuid(cmd.getId());
          if (result == null) {
              throw new CloudRuntimeException("External Netscaler Control Center Table does not contain record with this ID");
          } else {
              //ID list of Network Offering which are not removed and have service Package Uuid field not null.
              List<Long> servicePackageId_list = _networkOfferingDao.listNetworkOfferingID();
  
              if (servicePackageId_list.size() != 0) {
                  //VO list of Networks  which are using Network Offering.
                  List<NetworkVO> networkVO_list = _networkDao.listNetworkVO(servicePackageId_list);
                  if (networkVO_list != null && networkVO_list.size() != 0) {
                      throw new CloudRuntimeException(
                              "ServicePackages published by NetScalerControlCenter are being used by NetworkOfferings. Try deleting NetworkOffering with ServicePackages and then delete NetScalerControlCenter.");
                  }
              }
          }
          try {
              _netscalerServicePackageDao.removeAll();
          } catch (CloudRuntimeException ce) {
              throw new CloudRuntimeException("Service Package is being used by Network Offering, Try deleting Network Offering and then delete Service Package.");
          }
  
          // delete Netscaler Control Center
          _netscalerControlCenterDao.remove(result.getId());
  
          //Removal of  NCC from Host Table
          List<HostVO> ncc_list = _hostDao.listAll();
          if (ncc_list == null) {
              throw new CloudRuntimeException("Could not find Netscaler Control Center in Database");
          }
          for (HostVO ncc : ncc_list) {
              if (ncc.getType().equals(Host.Type.NetScalerControlCenter)) {
                  try {
                      // put the host in maintenance state in order for it to be deleted
                      ncc.setResourceState(ResourceState.Maintenance);
                      _hostDao.update(ncc.getId(), ncc);
                      _resourceMgr.deleteHost(ncc.getId(), false, false);
                  } catch (Exception e) {
                      logger.debug(e);
                      return false;
                  }
              }
          }
          return true;
      }
  
      @Override
      public NetscalerLoadBalancerResponse createNetscalerLoadBalancerResponse(ExternalLoadBalancerDeviceVO lbDeviceVO) {
          NetscalerLoadBalancerResponse response = new NetscalerLoadBalancerResponse();
          Host lbHost = _hostDao.findById(lbDeviceVO.getHostId());
          Map<String, String> lbDetails = _detailsDao.findDetails(lbDeviceVO.getHostId());
          response.setId(lbDeviceVO.getUuid());
          response.setIpAddress(lbHost.getPrivateIpAddress());
          PhysicalNetwork pnw = ApiDBUtils.findPhysicalNetworkById(lbDeviceVO.getPhysicalNetworkId());
          if (pnw != null) {
              response.setPhysicalNetworkId(pnw.getUuid());
          }
          response.setPublicInterface(lbDetails.get("publicInterface"));
          response.setPrivateInterface(lbDetails.get("privateInterface"));
          response.setDeviceName(lbDeviceVO.getDeviceName());
          if (lbDeviceVO.getCapacity() == 0) {
              long defaultLbCapacity = NumbersUtil
                      .parseLong(_configDao.getValue(Config.DefaultExternalLoadBalancerCapacity.key()), 50);
              response.setDeviceCapacity(defaultLbCapacity);
          } else {
              response.setDeviceCapacity(lbDeviceVO.getCapacity());
          }
          response.setDedicatedLoadBalancer(lbDeviceVO.getIsDedicatedDevice());
          response.setProvider(lbDeviceVO.getProviderName());
          response.setDeviceState(lbDeviceVO.getState().name());
          response.setObjectName("netscalerloadbalancer");
  
          response.setGslbProvider(lbDeviceVO.getGslbProvider());
          response.setExclusiveGslbProvider(lbDeviceVO.getExclusiveGslbProvider());
          response.setGslbSitePublicIp(lbDeviceVO.getGslbSitePublicIP());
          response.setGslbSitePrivateIp(lbDeviceVO.getGslbSitePrivateIP());
  
          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;
      }
  
      @Override
      public NetscalerControlCenterResponse createNetscalerControlCenterResponse(NetScalerControlCenterVO lncCentersVO) {
          NetscalerControlCenterResponse response = new NetscalerControlCenterResponse(lncCentersVO);
          response.setObjectName("netscalercontrolcenter");
          return response;
      }
  
      @Override
      public NetScalerServicePackageResponse createRegisteredServicePackageResponse(
              NetScalerServicePackageVO lrsPackageVO) {
          NetScalerServicePackageResponse response = new NetScalerServicePackageResponse();
          response.setId(lrsPackageVO.getUuid());
          response.setName(lrsPackageVO.getName());
          response.setDescription(lrsPackageVO.getDescription());
          response.setObjectName("registeredServicepackage");
          return response;
      }
  
      @Override
      public Provider getProvider() {
          return Provider.Netscaler;
      }
  
      @Override
      public boolean isReady(PhysicalNetworkServiceProvider provider) {
          List<ExternalLoadBalancerDeviceVO> lbDevices = _lbDeviceDao
                  .listByPhysicalNetworkAndProvider(provider.getPhysicalNetworkId(), Provider.Netscaler.getName());
  
          // true if at-least one Netscaler device is added in to physical network
          // and is in configured (in enabled state)
          // state
          if (lbDevices != null && !lbDevices.isEmpty()) {
              for (ExternalLoadBalancerDeviceVO lbDevice : lbDevices) {
                  if (lbDevice.getState() == LBDeviceState.Enabled) {
                      return true;
                  }
              }
          }
          return true;
      }
  
      @Override
      public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context)
              throws ConcurrentOperationException, ResourceUnavailableException {
          // TODO reset the configuration on all of the netscaler devices in this
          // physical network
          return true;
      }
  
      @Override
      public boolean canEnableIndividualServices() {
          return true;
      }
  
      private boolean isNetscalerDevice(String deviceName) {
          if ((deviceName == null) || ((!deviceName.equalsIgnoreCase(NetworkDevice.NetscalerMPXLoadBalancer.getName()))
                  && (!deviceName.equalsIgnoreCase(NetworkDevice.NetscalerSDXLoadBalancer.getName()))
                  && (!deviceName.equalsIgnoreCase(NetworkDevice.NetscalerVPXLoadBalancer.getName())))) {
              return false;
          } else {
              return true;
          }
      }
  
      @Override
      public boolean verifyServicesCombination(Set<Service> services) {
          Set<Service> netscalerServices = new HashSet<Service>();
          netscalerServices.add(Service.Lb);
          netscalerServices.add(Service.StaticNat);
  
          // NetScaler can only act as Lb and Static Nat service provider
          if (services != null && !services.isEmpty() && !netscalerServices.containsAll(services)) {
              logger.warn(
                      "NetScaler network element can only support LB and Static NAT services and service combination "
                              + services + " is not supported.");
  
              StringBuffer buff = new StringBuffer();
              for (Service service : services) {
                  buff.append(service.getName());
                  buff.append(" ");
              }
              logger.warn(
                      "NetScaler network element can only support LB and Static NAT services and service combination "
                              + buff.toString() + " is not supported.");
              logger.warn(
                      "NetScaler network element can only support LB and Static NAT services and service combination "
                              + services + " is not supported.");
              return false;
          }
  
          return true;
      }
  
      @Override
      public boolean applyIps(Network network, List<? extends PublicIpAddress> ipAddress, Set<Service> service)
              throws ResourceUnavailableException {
          // return true, as IP will be associated as part of LB rule
          // configuration
          return true;
      }
  
      @Override
      public IpDeployer getIpDeployer(Network network) {
  
          if (_networkMgr.isNetworkInlineMode(network)) {
              return getIpDeployerForInlineMode(network);
          }
  
          return this;
      }
  
      public boolean applyElasticLoadBalancerRules(Network network, List<LoadBalancingRule> loadBalancingRules)
              throws ResourceUnavailableException {
  
          if (loadBalancingRules == null || loadBalancingRules.isEmpty()) {
              return true;
          }
  
          String errMsg = null;
          ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network);
          if (lbDeviceVO == null) {
              try {
                  lbDeviceVO = allocateLoadBalancerForNetwork(network);
              } catch (Exception e) {
                  errMsg = "Could not allocate a NetSclaer load balancer for configuring elastic load balancer rules due to "
                          + e.getMessage();
                  logger.error(errMsg);
                  throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
              }
          }
  
          if (!isNetscalerDevice(lbDeviceVO.getDeviceName())) {
              errMsg = "There are no NetScaler load balancer assigned for this network. So NetScaler element can not be handle elastic load balancer rules.";
              logger.error(errMsg);
              throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
          }
  
          List<LoadBalancerTO> loadBalancersToApply = new ArrayList<LoadBalancerTO>();
          for (int i = 0; i < loadBalancingRules.size(); i++) {
              LoadBalancingRule rule = loadBalancingRules.get(i);
              boolean revoked = (rule.getState().equals(FirewallRule.State.Revoke));
              String protocol = rule.getProtocol();
              String algorithm = rule.getAlgorithm();
              String lbUuid = rule.getUuid();
              String srcIp = rule.getSourceIp().addr();
              int srcPort = rule.getSourcePortStart();
              List<LbDestination> destinations = rule.getDestinations();
  
              if ((destinations != null && !destinations.isEmpty()) || rule.isAutoScaleConfig()) {
                  LoadBalancerTO loadBalancer = new LoadBalancerTO(lbUuid, srcIp, srcPort, protocol, algorithm, revoked,
                          false, false, destinations, rule.getStickinessPolicies(), rule.getHealthCheckPolicies(),
                          rule.getLbSslCert(), rule.getLbProtocol());
                  if (rule.isAutoScaleConfig()) {
                      loadBalancer.setAutoScaleVmGroupTO(lbRulesManager.toAutoScaleVmGroupTO(rule.getAutoScaleVmGroup()));
                  }
                  loadBalancersToApply.add(loadBalancer);
              }
          }
  
          if (loadBalancersToApply.size() > 0) {
              int numLoadBalancersForCommand = loadBalancersToApply.size();
              LoadBalancerTO[] loadBalancersForCommand = loadBalancersToApply
                      .toArray(new LoadBalancerTO[numLoadBalancersForCommand]);
              LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(loadBalancersForCommand, null);
  
              HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId());
              Answer answer = _agentMgr.easySend(externalLoadBalancer.getId(), cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "Unable to apply elastic load balancer rules to the external load balancer appliance in zone "
                          + network.getDataCenterId() + " due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, network.getDataCenterId());
              }
          }
  
          return true;
      }
  
      @Override
      public boolean applyStaticNats(Network config, List<? extends StaticNat> rules)
              throws ResourceUnavailableException {
  
          if (!canHandle(config, Service.StaticNat)) {
              return false;
          }
  
          boolean multiNetScalerDeployment = Boolean
                  .valueOf(_configDao.getValue(Config.EIPWithMultipleNetScalersEnabled.key()));
  
          try {
              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();
                          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.";
                      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, null);
                  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();
                              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, null);
  
                          // 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();
                              logger.error(errMsg);
                              throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
                          }
                      }
                      return true;
                  }
              }
              return true;
          } catch (Exception e) {
              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;
      }
  
      public List<LoadBalancerTO> getElasticLBRulesHealthCheck(Network network,
              List<LoadBalancingRule> loadBalancingRules) throws ResourceUnavailableException {
  
          HealthCheckLBConfigAnswer answer = null;
  
          if (loadBalancingRules == null || loadBalancingRules.isEmpty()) {
              return null;
          }
  
          String errMsg = null;
          ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network);
  
          if (lbDeviceVO == null) {
              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 null;
          }
  
          if (!isNetscalerDevice(lbDeviceVO.getDeviceName())) {
              errMsg = "There are no NetScaler load balancer assigned for this network. So NetScaler element can not be handle elastic load balancer rules.";
              logger.error(errMsg);
              throw new ResourceUnavailableException(errMsg, this.getClass(), 0);
          }
  
          List<LoadBalancerTO> loadBalancersToApply = new ArrayList<LoadBalancerTO>();
          for (int i = 0; i < loadBalancingRules.size(); i++) {
              LoadBalancingRule rule = loadBalancingRules.get(i);
              boolean revoked = (rule.getState().equals(FirewallRule.State.Revoke));
              String protocol = rule.getProtocol();
              String algorithm = rule.getAlgorithm();
              String lbUuid = rule.getUuid();
              String srcIp = rule.getSourceIp().addr();
              int srcPort = rule.getSourcePortStart();
              List<LbDestination> destinations = rule.getDestinations();
  
              if ((destinations != null && !destinations.isEmpty()) || rule.isAutoScaleConfig()) {
                  LoadBalancerTO loadBalancer = new LoadBalancerTO(lbUuid, srcIp, srcPort, protocol, algorithm, revoked,
                          false, false, destinations, null, rule.getHealthCheckPolicies(), rule.getLbSslCert(),
                          rule.getLbProtocol());
                  loadBalancersToApply.add(loadBalancer);
              }
          }
  
          if (loadBalancersToApply.size() > 0) {
              int numLoadBalancersForCommand = loadBalancersToApply.size();
              LoadBalancerTO[] loadBalancersForCommand = loadBalancersToApply
                      .toArray(new LoadBalancerTO[numLoadBalancersForCommand]);
              HealthCheckLBConfigCommand cmd = new HealthCheckLBConfigCommand(loadBalancersForCommand, network.getId());
              HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId());
              answer = (HealthCheckLBConfigAnswer)_agentMgr.easySend(externalLoadBalancer.getId(), cmd);
              return answer.getLoadBalancers();
          }
          return null;
      }
  
      @Override
      public List<LoadBalancerTO> updateHealthChecks(Network network, List<LoadBalancingRule> lbrules) {
  
          if (canHandle(network, Service.Lb) && canHandleLbRules(lbrules)) {
              try {
  
                  if (isBasicZoneNetwok(network)) {
                      return getElasticLBRulesHealthCheck(network, lbrules);
                  } else {
                      return getLBHealthChecks(network, lbrules);
                  }
              } catch (ResourceUnavailableException e) {
                  logger.error("Error in getting the LB Rules from NetScaler " + e);
              }
          } else {
              logger.error("Network cannot handle to LB service ");
          }
          return null;
      }
  
      @Override
      public boolean handlesOnlyRulesInTransitionState() {
          return true;
      }
  
      @Override
      public List<LoadBalancerTO> getLBHealthChecks(Network network, List<LoadBalancingRule> rules)
              throws ResourceUnavailableException {
          return super.getLBHealthChecks(network, rules);
      }
  
      @Override
      public NetScalerServicePackageResponse listNetscalerServicePackage(RegisterServicePackageCmd cmd) {
          return null;
      }
  
      @Override
      public NetScalerServicePackageResponse deleteNetscalerServicePackage(RegisterServicePackageCmd cmd) {
          return null;
      }
  
      @Override
      public NetScalerServicePackageResponse createNetscalerServicePackageResponse(NetScalerServicePackageVO servicePackageVO) {
          return null;
      }
  
      @Override
      public boolean applyGlobalLoadBalancerRule(long zoneId, long physicalNetworkId,
              GlobalLoadBalancerConfigCommand gslbConfigCmd) throws ResourceUnavailableException {
  
          long zoneGslbProviderHosId = 0;
  
          // find the NetScaler device configured as gslb service provider in the
          // zone
          ExternalLoadBalancerDeviceVO nsGslbProvider = findGslbProvider(zoneId, physicalNetworkId);
          if (nsGslbProvider == null) {
              String msg = "Unable to find a NetScaler configured as gslb service provider in zone " + zoneId;
              logger.debug(msg);
              throw new ResourceUnavailableException(msg, DataCenter.class, zoneId);
          }
  
          // get the host Id corresponding to NetScaler acting as GSLB service
          // provider in the zone
          zoneGslbProviderHosId = nsGslbProvider.getHostId();
  
          // send gslb configuration to NetScaler device
          Answer answer = _agentMgr.easySend(zoneGslbProviderHosId, gslbConfigCmd);
          if (answer == null || !answer.getResult()) {
              String msg = "Unable to apply global load balancer rule to the gslb service provider in zone " + zoneId;
              logger.debug(msg);
              throw new ResourceUnavailableException(msg, DataCenter.class, zoneId);
          }
  
          return true;
      }
  
      private ExternalLoadBalancerDeviceVO findGslbProvider(long zoneId, long physicalNetworkId) {
          List<PhysicalNetworkVO> pNtwks = _physicalNetworkDao.listByZoneAndTrafficType(zoneId, TrafficType.Guest);
  
          if (pNtwks == null || pNtwks.isEmpty()) {
              throw new InvalidParameterValueException(
                      "Unable to get physical network: " + physicalNetworkId + " in zone id = " + zoneId);
          } else {
              for (PhysicalNetwork physicalNetwork : pNtwks) {
                  if (physicalNetwork.getId() == physicalNetworkId) {
                      PhysicalNetworkVO physNetwork = pNtwks.get(0);
                      ExternalLoadBalancerDeviceVO nsGslbProvider = _externalLoadBalancerDeviceDao
                              .findGslbServiceProvider(physNetwork.getId(), Provider.Netscaler.getName());
                      return nsGslbProvider;
                  }
              }
          }
  
          return null;
      }
  
      @Override
      public boolean isServiceEnabledInZone(long zoneId, long physicalNetworkId) {
  
          ExternalLoadBalancerDeviceVO nsGslbProvider = findGslbProvider(zoneId, physicalNetworkId);
          // return true if a NetScaler device is configured in the zone
          return (nsGslbProvider != null);
      }
  
      @Override
      public String getZoneGslbProviderPublicIp(long zoneId, long physicalNetworkId) {
          ExternalLoadBalancerDeviceVO nsGslbProvider = findGslbProvider(zoneId, physicalNetworkId);
          if (nsGslbProvider != null) {
              return nsGslbProvider.getGslbSitePublicIP();
          }
          return null;
      }
  
      @Override
      public String getZoneGslbProviderPrivateIp(long zoneId, long physicalNetworkId) {
          ExternalLoadBalancerDeviceVO nsGslbProvider = findGslbProvider(zoneId, physicalNetworkId);
          if (nsGslbProvider != null) {
              return nsGslbProvider.getGslbSitePrivateIP();
          }
          return null;
      }
  
      private boolean canHandleLbRules(List<LoadBalancingRule> rules) {
          Map<Capability, String> lbCaps = getCapabilities().get(Service.Lb);
          if (!lbCaps.isEmpty()) {
              String schemeCaps = lbCaps.get(Capability.LbSchemes);
              if (schemeCaps != null) {
                  for (LoadBalancingRule rule : rules) {
                      if (!schemeCaps.contains(rule.getScheme().toString())) {
                          logger.debug("Scheme " + rules.get(0).getScheme() + " is not supported by the provider "
                                  + getName());
                          return false;
                      }
                  }
              }
          }
          return true;
      }
  
      @Override
      @ActionEvent(eventType = EventTypes.EVENT_NETSCALER_SERVICEPACKAGE_ADD, eventDescription = "Registering NetScaler Service Package")
      public NetScalerServicePackageResponse registerNetscalerServicePackage(RegisterServicePackageCmd cmd) {
          NetScalerServicePackageVO servicePackage = new NetScalerServicePackageVO(cmd);
          NetScalerServicePackageResponse response = null;
          _netscalerServicePackageDao.persist(servicePackage);
          response = new NetScalerServicePackageResponse(servicePackage);
          return response;
      }
  
      @Override
      @DB
      public NetScalerControlCenterVO registerNetscalerControlCenter(RegisterNetscalerControlCenterCmd cmd) {
  
          if (_netscalerControlCenterDao.listAll() != null && _netscalerControlCenterDao.listAll().size() != 0) {
              throw new CloudRuntimeException("One Netscaler Control Center already exist in the DataBase. At a time only one Netscaler Control Center is allowed");
          }
  
          final RegisterNetscalerControlCenterCmd cmdinfo = cmd;
          String ipAddress = cmd.getIpaddress();
          Map hostDetails = new HashMap<String, String>();
          String hostName = "NetscalerControlCenter";
          hostDetails.put("name", hostName);
          hostDetails.put("guid", UUID.randomUUID().toString());
          List<DataCenterVO> dcVO = _dcDao.listEnabledZones();
          if (dcVO.size() == 0) {
              throw new CloudRuntimeException("There is no single enabled zone. Please add a zone, enable it and then add Netscaler ControlCenter");
          }
          hostDetails.put("zoneId", "1");
          hostDetails.put("ip", ipAddress);
          hostDetails.put("username", cmd.getUsername());
          hostDetails.put("password", cmd.getPassword());
          hostDetails.put("deviceName", "Netscaler ControlCenter");
          ServerResource resource = new NetScalerControlCenterResource();
          try {
              resource.configure(hostName, hostDetails);
              return Transaction.execute(new TransactionCallback<NetScalerControlCenterVO>() {
                  @Override
                  public NetScalerControlCenterVO doInTransaction(TransactionStatus status) {
                      NetScalerControlCenterVO nccVO = new NetScalerControlCenterVO(cmdinfo.getUsername(), DBEncryptionUtil.encrypt(cmdinfo.getPassword()),
                              cmdinfo.getIpaddress(), cmdinfo.getNumretries());
                      _netscalerControlCenterDao.persist(nccVO);
                      return nccVO;
                  }
              });
          } catch (ConfigurationException e) {
              resource = null;
              throw new CloudRuntimeException(e.getMessage());
          }
      }
  
      @Override
      public Map<String,Object> deployNetscalerServiceVm(DeployNetscalerVpxCmd cmd) {
          DataCenter zone = _dcDao.findById(cmd.getZoneId());
          DeployDestination dest = new DeployDestination(zone, null, null, null);
          Map<String,Object> resp = new HashMap<String, Object>();
          Long templateId = cmd.getTemplateId();
          Long serviceOfferingId = cmd.getServiceOfferingId();
          DeploymentPlan plan = new DataCenterDeployment(dest.getDataCenter().getId());
          try {
              resp =  _netScalerVMManager.deployNsVpx(cmd.getAccount(), dest, plan, serviceOfferingId, templateId);
          } catch (InsufficientCapacityException e) {
              e.printStackTrace();
          }
          return resp;
      }
  
      @Override
      public VirtualRouter stopNetscalerServiceVm(Long id, boolean forced, Account callingAccount, long callingUserId) throws ConcurrentOperationException,
      ResourceUnavailableException {
          return _netScalerVMManager.stopNetScalerVm(id, forced, callingAccount, callingUserId);
      }
  }
  