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.resource;
  
  import java.io.IOException;
  import java.io.StringWriter;
  import java.security.cert.Certificate;
  import java.util.ArrayList;
  import java.util.Formatter;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.api.ApiConstants;
  import org.apache.commons.io.output.ByteArrayOutputStream;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  import org.bouncycastle.util.io.pem.PemObject;
  import org.bouncycastle.util.io.pem.PemWriter;
  
  import com.citrix.netscaler.nitro.exception.nitro_exception;
  import com.citrix.netscaler.nitro.resource.base.base_response;
  import com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction;
  import com.citrix.netscaler.nitro.resource.config.autoscale.autoscalepolicy;
  import com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleprofile;
  import com.citrix.netscaler.nitro.resource.config.basic.server_service_binding;
  import com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding;
  import com.citrix.netscaler.nitro.resource.config.basic.servicegroup;
  import com.citrix.netscaler.nitro.resource.config.basic.servicegroup_lbmonitor_binding;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbservice;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbsite;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbsite_gslbservice_binding;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_domain_binding;
  import com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_gslbservice_binding;
  import com.citrix.netscaler.nitro.resource.config.lb.lbmetrictable;
  import com.citrix.netscaler.nitro.resource.config.lb.lbmetrictable_metric_binding;
  import com.citrix.netscaler.nitro.resource.config.lb.lbmonitor;
  import com.citrix.netscaler.nitro.resource.config.lb.lbmonitor_metric_binding;
  import com.citrix.netscaler.nitro.resource.config.lb.lbvserver;
  import com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding;
  import com.citrix.netscaler.nitro.resource.config.lb.lbvserver_servicegroup_binding;
  import com.citrix.netscaler.nitro.resource.config.network.Interface;
  import com.citrix.netscaler.nitro.resource.config.network.inat;
  import com.citrix.netscaler.nitro.resource.config.network.rnat;
  import com.citrix.netscaler.nitro.resource.config.network.vlan;
  import com.citrix.netscaler.nitro.resource.config.network.vlan_interface_binding;
  import com.citrix.netscaler.nitro.resource.config.network.vlan_nsip_binding;
  import com.citrix.netscaler.nitro.resource.config.ns.nsconfig;
  import com.citrix.netscaler.nitro.resource.config.ns.nshardware;
  import com.citrix.netscaler.nitro.resource.config.ns.nsip;
  import com.citrix.netscaler.nitro.resource.config.ns.nstimer;
  import com.citrix.netscaler.nitro.resource.config.ns.nstimer_autoscalepolicy_binding;
  import com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey;
  import com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey_sslvserver_binding;
  import com.citrix.netscaler.nitro.resource.config.ssl.sslcertlink;
  import com.citrix.netscaler.nitro.resource.config.ssl.sslvserver_sslcertkey_binding;
  import com.citrix.netscaler.nitro.resource.stat.lb.lbvserver_stats;
  import com.citrix.netscaler.nitro.service.nitro_service;
  import com.citrix.netscaler.nitro.util.filtervalue;
  import com.citrix.sdx.nitro.resource.config.mps.mps;
  import com.citrix.sdx.nitro.resource.config.ns.ns;
  import com.citrix.sdx.nitro.resource.config.xen.xen_nsvpx_image;
  import com.cloud.agent.IAgentControl;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.Command;
  import com.cloud.agent.api.ExternalNetworkResourceUsageAnswer;
  import com.cloud.agent.api.ExternalNetworkResourceUsageCommand;
  import com.cloud.agent.api.MaintainAnswer;
  import com.cloud.agent.api.MaintainCommand;
  import com.cloud.agent.api.PingCommand;
  import com.cloud.agent.api.ReadyAnswer;
  import com.cloud.agent.api.ReadyCommand;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.StartupExternalLoadBalancerCommand;
  import com.cloud.agent.api.routing.CreateLoadBalancerApplianceCommand;
  import com.cloud.agent.api.routing.DestroyLoadBalancerApplianceCommand;
  import com.cloud.agent.api.routing.GlobalLoadBalancerConfigAnswer;
  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.IpAssocAnswer;
  import com.cloud.agent.api.routing.IpAssocCommand;
  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.routing.SiteLoadBalancerConfig;
  import com.cloud.agent.api.to.IpAddressTO;
  import com.cloud.agent.api.to.LoadBalancerTO;
  import com.cloud.agent.api.to.LoadBalancerTO.AutoScalePolicyTO;
  import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmGroupTO;
  import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmProfileTO;
  import com.cloud.agent.api.to.LoadBalancerTO.ConditionTO;
  import com.cloud.agent.api.to.LoadBalancerTO.CounterTO;
  import com.cloud.agent.api.to.LoadBalancerTO.DestinationTO;
  import com.cloud.agent.api.to.LoadBalancerTO.HealthCheckPolicyTO;
  import com.cloud.agent.api.to.LoadBalancerTO.StickinessPolicyTO;
  import com.cloud.agent.api.to.StaticNatRuleTO;
  import com.cloud.host.Host;
  import com.cloud.host.Host.Type;
  import com.cloud.network.as.AutoScalePolicy;
  import com.cloud.network.as.AutoScaleVmGroup;
  import com.cloud.network.as.Condition;
  import com.cloud.network.as.Counter;
  import com.cloud.network.lb.LoadBalancingRule.LbSslCert;
  import com.cloud.network.rules.LbStickinessMethod.StickinessMethodType;
  import com.cloud.resource.ServerResource;
  import com.cloud.serializer.GsonHelper;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.Pair;
  import com.cloud.utils.exception.ExecutionException;
  import com.cloud.utils.net.NetUtils;
  import com.cloud.utils.security.CertificateHelper;
  import com.cloud.utils.ssh.SshHelper;
  import com.google.common.collect.Lists;
  import com.google.gson.Gson;
  
  class NitroError {
      static final int NS_RESOURCE_EXISTS = 273;
      static final int NS_RESOURCE_NOT_EXISTS = 258;
      static final int NS_NO_SERIVCE = 344;
      static final int NS_OPERATION_NOT_PERMITTED = 257;
      static final int NS_INTERFACE_ALREADY_BOUND_TO_VLAN = 2080;
      static final int NS_GSLB_DOMAIN_ALREADY_BOUND = 1842;
  }
  
  public class NetscalerResource implements ServerResource {
  
      public final static int DEFAULT_SNMP_PORT = 161;
      // deployment configuration
      private String _name;
      private String _zoneId;
      private String _physicalNetworkId;
      private String _ip;
      private String _username;
      private String _password;
      private String _publicInterface;
      private String _privateInterface;
      private Integer _numRetries;
      private String _guid;
      private boolean _isSdx;
      private boolean _cloudManaged;
      private String _deviceName;
      private String _publicIP;
      private String _publicIPNetmask;
      private String _publicIPVlan;
  
      protected static Logger LOGGER = LogManager.getLogger(NetscalerResource.class);
      protected Gson _gson;
      private final String _objectNamePathSep = "-";
  
      // interface to interact with VPX and MPX devices
      com.citrix.netscaler.nitro.service.nitro_service _netscalerService;
  
      // interface to interact with service VM of the SDX appliance
      com.citrix.sdx.nitro.service.nitro_service _netscalerSdxService;
  
      base_response apiCallResult;
  
      public NetscalerResource() {
          _gson = GsonHelper.getGsonLogger();
      }
  
      @Override
      public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
          try {
              _name = (String)params.get("name");
              if (_name == null) {
                  throw new ConfigurationException("Unable to find name in the configuration parameters");
              }
  
              _zoneId = (String)params.get("zoneId");
              if (_zoneId == null) {
                  throw new ConfigurationException("Unable to find zone Id  in the configuration parameters");
              }
  
              _physicalNetworkId = (String)params.get("physicalNetworkId");
              if (_physicalNetworkId == null) {
                  throw new ConfigurationException("Unable to find physical network id in the configuration parameters");
              }
  
              _ip = (String)params.get("ip");
              if (_ip == null) {
                  throw new ConfigurationException("Unable to find IP address in the configuration parameters");
              }
  
              _username = (String)params.get("username");
              if (_username == null) {
                  throw new ConfigurationException("Unable to find username in the configuration parameters");
              }
  
              _password = (String)params.get("password");
              if (_password == null) {
                  throw new ConfigurationException("Unable to find password in the configuration parameters");
              }
  
              _publicInterface = (String)params.get("publicinterface");
              if (_publicInterface == null) {
                  throw new ConfigurationException("Unable to find public interface in the configuration parameters");
              }
  
              _privateInterface = (String)params.get("privateinterface");
              if (_privateInterface == null) {
                  throw new ConfigurationException("Unable to find private interface in the configuration parameters");
              }
  
              _numRetries = NumbersUtil.parseInt((String)params.get("numretries"), 2);
  
              _guid = (String)params.get("guid");
              if (_guid == null) {
                  throw new ConfigurationException("Unable to find the guid in the configuration parameters");
              }
  
              _deviceName = (String)params.get("deviceName");
              if (_deviceName == null) {
                  throw new ConfigurationException("Unable to find the device name in the configuration parameters");
              }
  
              _isSdx = _deviceName.equalsIgnoreCase("NetscalerSDXLoadBalancer");
  
              if ((String)params.get("cloudmanaged") != null) {
                  _cloudManaged = Boolean.parseBoolean((String)params.get("cloudmanaged"));
              }
  
              // validate device configuration parameters
              login();
              validateDeviceType(_deviceName);
              validateInterfaces(_publicInterface, _privateInterface);
  
              //enable load balancing feature
              enableLoadBalancingFeature();
              SSL.enableSslFeature(_netscalerService, _isSdx);
  
              //if the device is cloud stack provisioned then make it part of the public network
              if (_cloudManaged) {
                  _publicIP = (String)params.get("publicip");
                  _publicIPNetmask = (String)params.get("publicipnetmask");
                  _publicIPVlan = (String)params.get("publicipvlan");
                  if ("untagged".equalsIgnoreCase(_publicIPVlan)) {
                      // if public network is un-tagged just add subnet IP
                      addSubnetIP(_publicIP, _publicIPNetmask);
                  } else {
                      // if public network is tagged then add vlan and bind subnet IP to the vlan
                      addGuestVlanAndSubnet(Long.parseLong(_publicIPVlan), _publicIP, _publicIPNetmask, false);
                  }
              }
  
              return true;
          } catch (final Exception e) {
              throw new ConfigurationException(e.getMessage());
          }
      }
  
      private void logout() throws ExecutionException {
          try {
              if (!_isSdx) {
                  if (_netscalerService != null) {
                      _netscalerService.logout();
                  }
              } else {
                  if (_netscalerSdxService != null) {
                      _netscalerSdxService.logout();
                  }
              }
          } catch (final Exception e) {
              // Ignore logout exceptions
          }
      }
  
      private void login() throws ExecutionException {
          try {
              // If a previous session was open, log it out.
              logout();
              if (!_isSdx) {
                  _netscalerService = new nitro_service(_ip, "https");
                  _netscalerService.set_credential(_username, _password);
                  _netscalerService.set_certvalidation(false);
                  _netscalerService.set_hostnameverification(false);
                  apiCallResult = _netscalerService.login();
                  if (apiCallResult.errorcode != 0) {
                      throw new ExecutionException("Failed to log in to Netscaler device at " + _ip + " due to error " + apiCallResult.errorcode + " and message " +
                              apiCallResult.message);
                  }
              } else {
                  _netscalerSdxService = new com.citrix.sdx.nitro.service.nitro_service(_ip, "https");
                  _netscalerSdxService.set_credential(_username, _password);
                  final com.citrix.sdx.nitro.resource.base.login login = _netscalerSdxService.login();
                  if (login == null) {
                      throw new ExecutionException("Failed to log in to Netscaler SDX device at " + _ip + " due to error " + apiCallResult.errorcode + " and message " +
                              apiCallResult.message);
                  }
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to log in to Netscaler device at " + _ip + " due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to log in to Netscaler device at " + _ip + " due to " + e.getMessage());
          }
      }
  
      private void enableLoadBalancingFeature() throws ExecutionException {
          if (_isSdx) {
              return;
          }
          try {
              final String[] features = _netscalerService.get_enabled_features();
              if (features != null) {
                  for (final String feature : features) {
                      if (feature.equalsIgnoreCase("LB")) {
                          return;
                      }
                  }
              }
  
              // enable load balancing on the device
              final String[] feature = new String[1];
              feature[0] = "LB";
              apiCallResult = _netscalerService.enable_features(feature);
              if (apiCallResult.errorcode != 0) {
                  throw new ExecutionException("Enabling load balancing feature on the device failed.");
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Enabling load balancing feature on the device failed  due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Enabling load balancing feature on the device failed due to " + e.getMessage());
          }
      }
  
      private void validateInterfaces(final String publicInterface, final String privateInterface) throws ExecutionException {
          try {
              if (!_isSdx && !_cloudManaged) {
                  final Interface publicIf = Interface.get(_netscalerService, publicInterface);
                  final Interface privateIf = Interface.get(_netscalerService, privateInterface);
                  if (publicIf != null || privateIf != null) {
                      return;
                  } else {
                      throw new ExecutionException("Invalid interface name specified for public/private interfaces.");
                  }
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  throw new ExecutionException("Invalid interface name specified for public and private interfaces.");
              } else {
                  throw new ExecutionException("Failed to verify public interface and private intefaces are valid due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify public interface and private intefaces are valid due to " + e.getMessage());
          }
      }
  
      private void validateDeviceType(final String deviceType) throws ExecutionException {
          try {
              if (!_isSdx && !_cloudManaged) {
                  final nshardware nsHw = com.citrix.netscaler.nitro.resource.config.ns.nshardware.get(_netscalerService);
                  if (nsHw == null) {
                      throw new ExecutionException("Failed to get the hardware description of the Netscaler device at " + _ip);
                  } else {
                      if (_deviceName.equalsIgnoreCase("NetscalerMPXLoadBalancer") && nsHw.get_hwdescription().contains("MPX") ||
                              _deviceName.equalsIgnoreCase("NetscalerVPXLoadBalancer") && nsHw.get_hwdescription().contains("NetScaler Virtual Appliance")) {
                          return;
                      }
                      throw new ExecutionException("Netscalar device type specified does not match with the actuall device type.");
                  }
              } else if (_isSdx) {
                  final mps serviceVM = mps.get(_netscalerSdxService);
                  if (serviceVM != null) {
                      if (serviceVM.get_platform().contains("SDX") || serviceVM.get_product().contains("SDX")) {
                          return;
                      } else {
                          throw new ExecutionException("Netscalar device type specified does not match with the actuall device type.");
                      }
                  } else {
                      throw new ExecutionException("Failed to get the hardware details of the Netscaler device at " + _ip);
                  }
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify device type specified when matching with actuall device type due to " + e.getMessage());
          }
      }
  
      @Override
      public StartupCommand[] initialize() {
          final StartupExternalLoadBalancerCommand cmd = new StartupExternalLoadBalancerCommand();
          cmd.setName(_name);
          cmd.setDataCenter(_zoneId);
          cmd.setPod("");
          cmd.setPrivateIpAddress(_ip);
          cmd.setStorageIpAddress("");
          cmd.setVersion(NetscalerResource.class.getPackage().getImplementationVersion());
          cmd.setGuid(_guid);
          return new StartupCommand[] {cmd};
      }
  
      @Override
      public Answer executeRequest(final Command cmd) {
          return executeRequest(cmd, _numRetries);
      }
  
      private Answer executeRequest(final Command cmd, final int numRetries) {
          if (cmd instanceof ReadyCommand) {
              return execute((ReadyCommand)cmd);
          } else if (cmd instanceof MaintainCommand) {
              return execute((MaintainCommand)cmd);
          } else if (cmd instanceof IpAssocCommand) {
              return execute((IpAssocCommand)cmd, numRetries);
          } else if (cmd instanceof LoadBalancerConfigCommand) {
              return execute((LoadBalancerConfigCommand)cmd, numRetries);
          } else if (cmd instanceof ExternalNetworkResourceUsageCommand) {
              return execute((ExternalNetworkResourceUsageCommand)cmd, numRetries);
          } else if (cmd instanceof CreateLoadBalancerApplianceCommand) {
              return execute((CreateLoadBalancerApplianceCommand)cmd, numRetries);
          } else if (cmd instanceof DestroyLoadBalancerApplianceCommand) {
              return execute((DestroyLoadBalancerApplianceCommand)cmd, numRetries);
          } else if (cmd instanceof SetStaticNatRulesCommand) {
              return execute((SetStaticNatRulesCommand)cmd, numRetries);
          } else if (cmd instanceof GlobalLoadBalancerConfigCommand) {
              return execute((GlobalLoadBalancerConfigCommand)cmd, numRetries);
          } else if (cmd instanceof HealthCheckLBConfigCommand) {
              return execute((HealthCheckLBConfigCommand)cmd, numRetries);
          } else {
              return Answer.createUnsupportedCommandAnswer(cmd);
          }
      }
  
      private Answer execute(final ReadyCommand cmd) {
          return new ReadyAnswer(cmd);
      }
  
      protected Answer execute(final MaintainCommand cmd) {
          return new MaintainAnswer(cmd);
      }
  
      private synchronized Answer execute(final IpAssocCommand cmd, final int numRetries) {
          if (_isSdx) {
              return Answer.createUnsupportedCommandAnswer(cmd);
          }
  
          final String[] results = new String[cmd.getIpAddresses().length];
          int i = 0;
          try {
              final IpAddressTO[] ips = cmd.getIpAddresses();
              for (final IpAddressTO ip : ips) {
                  final long guestVlanTag = Long.parseLong(ip.getBroadcastUri());
                  final String vlanSelfIp = ip.getVlanGateway();
                  final String vlanNetmask = ip.getVlanNetmask();
  
                  if (ip.isAdd()) {
                      // Add a new guest VLAN and its subnet and bind it to private interface
                      addGuestVlanAndSubnet(guestVlanTag, vlanSelfIp, vlanNetmask, true);
                  } else {
                      // Check and delete guest VLAN with this tag, self IP, and netmask
                      deleteGuestVlan(guestVlanTag, vlanSelfIp, vlanNetmask);
                  }
  
                  saveConfiguration();
                  results[i++] = ip.getPublicIp() + " - success";
                  final String action = ip.isAdd() ? "associate" : "remove";
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Netscaler load balancer " + _ip + " successfully executed IPAssocCommand to " + action + " IP " + ip);
                  }
              }
          } catch (final ExecutionException e) {
              LOGGER.error("Netscaler loadbalancer " + _ip + " failed to execute IPAssocCommand due to " + e.getMessage());
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              } else {
                  results[i++] = IpAssocAnswer.errorResult;
              }
          }
  
          return new IpAssocAnswer(cmd, results);
      }
  
      private Answer execute(final HealthCheckLBConfigCommand cmd, final int numRetries) {
  
          final List<LoadBalancerTO> hcLB = new ArrayList<LoadBalancerTO>();
          try {
  
              if (_isSdx) {
                  return Answer.createUnsupportedCommandAnswer(cmd);
              }
  
              final LoadBalancerTO[] loadBalancers = cmd.getLoadBalancers();
  
              if (loadBalancers == null) {
                  return new HealthCheckLBConfigAnswer(hcLB);
              }
  
              for (final LoadBalancerTO loadBalancer : loadBalancers) {
                  final HealthCheckPolicyTO[] healthCheckPolicies = loadBalancer.getHealthCheckPolicies();
                  if (healthCheckPolicies != null && healthCheckPolicies.length > 0 && healthCheckPolicies[0] != null) {
                      final String nsVirtualServerName = generateNSVirtualServerName(loadBalancer.getSrcIp(), loadBalancer.getSrcPort());
  
                      final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding[] serviceBindings =
                              com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.get(_netscalerService, nsVirtualServerName);
  
                      if (serviceBindings != null) {
                          for (final DestinationTO destination : loadBalancer.getDestinations()) {
                              final String nsServiceName = generateNSServiceName(destination.getDestIp(), destination.getDestPort());
                              for (final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding binding : serviceBindings) {
                                  if (nsServiceName.equalsIgnoreCase(binding.get_servicename())) {
                                      destination.setMonitorState(binding.get_curstate());
                                      break;
                                  }
                              }
                          }
                          hcLB.add(loadBalancer);
                      }
                  }
              }
  
          } catch (final ExecutionException e) {
              LOGGER.error("Failed to execute HealthCheckLBConfigCommand due to ", e);
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              } else {
                  return new HealthCheckLBConfigAnswer(hcLB);
              }
          } catch (final Exception e) {
              LOGGER.error("Failed to execute HealthCheckLBConfigCommand due to ", e);
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              } else {
                  return new HealthCheckLBConfigAnswer(hcLB);
              }
          }
          return new HealthCheckLBConfigAnswer(hcLB);
      }
  
      private synchronized Answer execute(final LoadBalancerConfigCommand cmd, final int numRetries) {
          try {
              if (_isSdx) {
                  return Answer.createUnsupportedCommandAnswer(cmd);
              }
  
              final LoadBalancerTO[] loadBalancers = cmd.getLoadBalancers();
              if (loadBalancers == null) {
                  return new Answer(cmd);
              }
  
              for (final LoadBalancerTO loadBalancer : loadBalancers) {
                  final String srcIp = loadBalancer.getSrcIp();
                  final int srcPort = loadBalancer.getSrcPort();
                  final String lbProtocol = getNetScalerProtocol(loadBalancer);
                  final String lbAlgorithm = loadBalancer.getAlgorithm();
                  final String nsVirtualServerName = generateNSVirtualServerName(srcIp, srcPort);
                  final String nsMonitorName = generateNSMonitorName(srcIp, srcPort);
                  final LbSslCert sslCert = loadBalancer.getSslCert();
  
                  if (loadBalancer.isAutoScaleVmGroupTO()) {
                      applyAutoScaleConfig(loadBalancer);
                      // Continue to process all the rules.
                      continue;
                  }
                  boolean hasMonitor = false;
                  boolean deleteMonitor = false;
                  boolean destinationsToAdd = false;
                  boolean deleteCert = false;
                  for (final DestinationTO destination : loadBalancer.getDestinations()) {
                      if (!destination.isRevoked()) {
                          destinationsToAdd = true;
                          break;
                      }
                  }
  
                  if (!loadBalancer.isRevoked() && destinationsToAdd) {
  
                      // create a load balancing virtual server
                      addLBVirtualServer(nsVirtualServerName, srcIp, srcPort, lbAlgorithm, lbProtocol, loadBalancer.getStickinessPolicies(), null);
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.debug("Created load balancing virtual server " + nsVirtualServerName + " on the Netscaler device");
                      }
  
                      // create a new monitor
                      final HealthCheckPolicyTO[] healthCheckPolicies = loadBalancer.getHealthCheckPolicies();
                      if (healthCheckPolicies != null && healthCheckPolicies.length > 0 && healthCheckPolicies[0] != null) {
  
                          for (final HealthCheckPolicyTO healthCheckPolicyTO : healthCheckPolicies) {
                              if (!healthCheckPolicyTO.isRevoked()) {
                                  addLBMonitor(nsMonitorName, lbProtocol, healthCheckPolicyTO);
                                  hasMonitor = true;
                              } else {
                                  deleteMonitor = true;
                                  hasMonitor = false;
                              }
                          }
  
                      }
  
                      for (final DestinationTO destination : loadBalancer.getDestinations()) {
  
                          final String nsServerName = generateNSServerName(destination.getDestIp());
                          final String nsServiceName = generateNSServiceName(destination.getDestIp(), destination.getDestPort());
                          if (!destination.isRevoked()) {
                              // add a new destination to deployed load balancing rule
  
                              // add a new server
                              if (!nsServerExists(nsServerName)) {
                                  final com.citrix.netscaler.nitro.resource.config.basic.server nsServer = new com.citrix.netscaler.nitro.resource.config.basic.server();
                                  nsServer.set_name(nsServerName);
                                  nsServer.set_ipaddress(destination.getDestIp());
                                  apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.server.add(_netscalerService, nsServer);
                                  if (apiCallResult.errorcode != 0 && apiCallResult.errorcode != NitroError.NS_RESOURCE_EXISTS) {
                                      throw new ExecutionException("Failed to add server " + destination.getDestIp() + " due to" + apiCallResult.message);
                                  }
                              }
  
                              // create a new service using the server added
                              if (!nsServiceExists(nsServiceName)) {
                                  final com.citrix.netscaler.nitro.resource.config.basic.service newService = new com.citrix.netscaler.nitro.resource.config.basic.service();
                                  newService.set_name(nsServiceName);
                                  newService.set_port(destination.getDestPort());
                                  newService.set_servername(nsServerName);
                                  newService.set_state("ENABLED");
                                  if(lbProtocol.equalsIgnoreCase(NetUtils.SSL_PROTO)) {
                                      newService.set_servicetype(NetUtils.HTTP_PROTO);
                                  } else {
                                      newService.set_servicetype(lbProtocol);
                                  }
  
                                  apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.service.add(_netscalerService, newService);
                                  if (apiCallResult.errorcode != 0) {
                                      throw new ExecutionException("Failed to create service " + nsServiceName + " using server " + nsServerName + " due to" +
                                              apiCallResult.message);
                                  }
                              }
  
                              //bind service to load balancing virtual server
                              if (!nsServiceBindingExists(nsVirtualServerName, nsServiceName)) {
                                  final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding svcBinding =
                                          new com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding();
                                  svcBinding.set_name(nsVirtualServerName);
                                  svcBinding.set_servicename(nsServiceName);
                                  apiCallResult = com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.add(_netscalerService, svcBinding);
  
                                  if (apiCallResult.errorcode != 0) {
                                      throw new ExecutionException("Failed to bind service: " + nsServiceName + " to the lb virtual server: " + nsVirtualServerName +
                                              " on Netscaler device");
                                  }
                              }
  
                              // After binding the service to the LB Vserver
                              // successfully, bind the created monitor to the
                              // service.
                              if (hasMonitor) {
                                  if (!isServiceBoundToMonitor(nsServiceName, nsMonitorName)) {
                                      bindServiceToMonitor(nsServiceName, nsMonitorName);
                                  }
                              } else {
                                  // check if any monitor created by CS is already
                                  // existing, if yes, unbind it from services and
                                  // delete it.
                                  if (nsMonitorExist(nsMonitorName)) {
                                      // unbind the service from the monitor and
                                      // delete the monitor
                                      unBindServiceToMonitor(nsServiceName, nsMonitorName);
                                      deleteMonitor = true;
                                  }
  
                              }
  
                              if (sslCert != null && lbProtocol.equalsIgnoreCase(NetUtils.SSL_PROTO)) {
                                  if (sslCert.isRevoked()) {
                                      deleteCert = true;
                                  } else {
  
                                      // If there is a chain, that should go first to the NS
  
                                      String previousCertKeyName = null;
  
                                      if (sslCert.getChain() != null) {
                                          final List<Certificate> chainList = CertificateHelper.parseChain(sslCert.getChain());
                                          // go from ROOT to intermediate CAs
                                          for (final Certificate intermediateCert : Lists.reverse(chainList)) {
  
                                              final String fingerPrint = CertificateHelper.generateFingerPrint(intermediateCert);
                                              final String intermediateCertKeyName = generateSslCertKeyName(fingerPrint);
                                              final String intermediateCertFileName = intermediateCertKeyName + ".pem";
  
                                              if (!SSL.isSslCertKeyPresent(_netscalerService, intermediateCertKeyName)) {
                                                  final PemObject pemObject = new PemObject(intermediateCert.getType(), intermediateCert.getEncoded());
                                                  final StringWriter textWriter = new StringWriter();
                                                  try (final PemWriter pemWriter = new PemWriter(textWriter);) {
                                                      pemWriter.writeObject(pemObject);
                                                      pemWriter.flush();
                                                  } catch (final IOException e) {
                                                      if (LOGGER.isDebugEnabled())
                                                      {
                                                          LOGGER.debug("couldn't write PEM to a string", e);
                                                      } // else just close the certDataStream
                                                  }
  
                                                  SSL.uploadCert(_ip, _username, _password, intermediateCertFileName, textWriter.toString().getBytes());
                                                  SSL.createSslCertKey(_netscalerService, intermediateCertFileName, null, intermediateCertKeyName, null);
                                              }
  
                                              if (previousCertKeyName != null && !SSL.certLinkExists(_netscalerService, intermediateCertKeyName, previousCertKeyName)) {
                                                  SSL.linkCerts(_netscalerService, intermediateCertKeyName, previousCertKeyName);
                                              }
  
                                              previousCertKeyName = intermediateCertKeyName;
                                          }
                                      }
  
                                      final String certFilename = generateSslCertName(sslCert.getFingerprint()) + ".pem"; //netscaler uses ".pem" format for "bundle" files
                                      final String keyFilename = generateSslKeyName(sslCert.getFingerprint()) + ".pem"; //netscaler uses ".pem" format for "bundle" files
                                      final String certKeyName = generateSslCertKeyName(sslCert.getFingerprint());
  
                                      try (final ByteArrayOutputStream certDataStream = new ByteArrayOutputStream();) {
                                          certDataStream.write(sslCert.getCert().getBytes());
  
                                          if (!SSL.isSslCertKeyPresent(_netscalerService, certKeyName)) {
  
                                              SSL.uploadCert(_ip, _username, _password, certFilename, certDataStream.toByteArray());
                                              SSL.uploadKey(_ip, _username, _password, keyFilename, sslCert.getKey().getBytes());
                                              SSL.createSslCertKey(_netscalerService, certFilename, keyFilename, certKeyName, sslCert.getPassword());
                                          }
                                      } catch (final IOException e) {
                                          if (LOGGER.isDebugEnabled())
                                          {
                                              LOGGER.debug("couldn't open buffer for certificate", e);
                                          } // else just close the certDataStream
                                      }
  
                                      if (previousCertKeyName != null && !SSL.certLinkExists(_netscalerService, certKeyName, previousCertKeyName)) {
                                          SSL.linkCerts(_netscalerService, certKeyName, previousCertKeyName);
                                      }
  
                                      SSL.bindCertKeyToVserver(_netscalerService, certKeyName, nsVirtualServerName);
                                  }
  
                              }
  
                              if (LOGGER.isDebugEnabled()) {
                                  LOGGER.debug("Successfully added LB destination: " + destination.getDestIp() + ":" + destination.getDestPort() + " to load balancer " +
                                          srcIp + ":" + srcPort);
                              }
  
                          } else {
                              // remove a destination from the deployed load balancing rule
                              final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding[] serviceBindings =
                                      com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.get(_netscalerService, nsVirtualServerName);
                              if (serviceBindings != null) {
                                  for (final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding binding : serviceBindings) {
                                      if (nsServiceName.equalsIgnoreCase(binding.get_servicename())) {
                                          // delete the binding
                                          apiCallResult = com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.delete(_netscalerService, binding);
                                          if (apiCallResult.errorcode != 0) {
                                              throw new ExecutionException("Failed to delete the binding between the virtual server: " + nsVirtualServerName +
                                                      " and service:" + nsServiceName + " due to" + apiCallResult.message);
                                          }
  
                                          // check if service is bound to any other virtual server
                                          if (!isServiceBoundToVirtualServer(nsServiceName)) {
                                              // no lb virtual servers are bound to this service so delete it
                                              apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.service.delete(_netscalerService, nsServiceName);
                                              if (apiCallResult.errorcode != 0) {
                                                  throw new ExecutionException("Failed to delete service: " + nsServiceName + " due to " + apiCallResult.message);
                                              }
                                          }
  
                                          // delete the server if there is no associated services
                                          final server_service_binding[] services = server_service_binding.get(_netscalerService, nsServerName);
                                          if (services == null || services.length == 0) {
                                              apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.server.delete(_netscalerService, nsServerName);
                                              if (apiCallResult.errorcode != 0) {
                                                  throw new ExecutionException("Failed to remove server:" + nsServerName + " due to " + apiCallResult.message);
                                              }
                                          }
                                      }
                                  }
                              }
                          }
                      }
                  } else {
                      // delete the implemented load balancing rule and its destinations
                      final lbvserver lbserver = getVirtualServerIfExisits(nsVirtualServerName);
                      if (lbserver != null) {
                          //unbind the all services associated with this virtual server
                          final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding[] serviceBindings =
                                  com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.get(_netscalerService, nsVirtualServerName);
  
                          if (serviceBindings != null) {
                              for (final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding binding : serviceBindings) {
                                  final String serviceName = binding.get_servicename();
                                  apiCallResult = com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.delete(_netscalerService, binding);
                                  if (apiCallResult.errorcode != 0) {
                                      throw new ExecutionException("Failed to unbind service from the lb virtual server: " + nsVirtualServerName + " due to " +
                                              apiCallResult.message);
                                  }
  
                                  final com.citrix.netscaler.nitro.resource.config.basic.service svc =
                                          com.citrix.netscaler.nitro.resource.config.basic.service.get(_netscalerService, serviceName);
                                  final String nsServerName = svc.get_servername();
  
                                  // check if service is bound to any other virtual server
                                  if (!isServiceBoundToVirtualServer(serviceName)) {
                                      // no lb virtual servers are bound to this service so delete it
                                      apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.service.delete(_netscalerService, serviceName);
                                      if (apiCallResult.errorcode != 0) {
                                          throw new ExecutionException("Failed to delete service: " + serviceName + " due to " + apiCallResult.message);
                                      }
                                  }
  
                                  //delete the server if no more services attached
                                  final server_service_binding[] services = server_service_binding.get(_netscalerService, nsServerName);
                                  if (services == null || services.length == 0) {
                                      apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.server.delete(_netscalerService, nsServerName);
                                      if (apiCallResult.errorcode != 0) {
                                          throw new ExecutionException("Failed to remove server:" + nsServerName + " due to " + apiCallResult.message);
                                      }
                                  }
                              }
                          }
                          removeLBVirtualServer(nsVirtualServerName);
                          deleteMonitor = true;
                          deleteCert = true;
                      }
                  }
                  if (deleteMonitor) {
                      removeLBMonitor(nsMonitorName);
                  }
                  if (sslCert != null && deleteCert) {
  
                      final String certFilename = generateSslCertName(sslCert.getFingerprint()) + ".pem"; //netscaler uses ".pem" format for "bundle" files
                      final String keyFilename = generateSslKeyName(sslCert.getFingerprint()) + ".pem"; //netscaler uses ".pem" format for "bundle" files
                      final String certKeyName = generateSslCertKeyName(sslCert.getFingerprint());
  
                      // unbind before deleting
                      if (nsVirtualServerExists(nsVirtualServerName) &&
                              SSL.isSslCertKeyPresent(_netscalerService, certKeyName) &&
                              SSL.isBoundToVserver(_netscalerService, certKeyName, nsVirtualServerName)) {
                          SSL.unbindCertKeyFromVserver(_netscalerService, certKeyName, nsVirtualServerName);
                      }
  
                      if (SSL.isSslCertKeyPresent(_netscalerService, certKeyName)) {
  
                          SSL.deleteSslCertKey(_netscalerService, certKeyName);
                          SSL.deleteCertFile(_ip, _username, _password, certFilename);
                          SSL.deleteKeyFile(_ip, _username, _password, keyFilename);
                      }
  
                      /*
                       * Check and delete intermediate certs:
                       * we can delete an intermediate cert if no other
                       * cert references it as the athority
                       */
  
                      if (sslCert.getChain() != null) {
                          final List<Certificate> chainList = CertificateHelper.parseChain(sslCert.getChain());
                          //go from intermediate CAs to ROOT
                          for (final Certificate intermediateCert : chainList) {
  
                              final String fingerPrint = CertificateHelper.generateFingerPrint(intermediateCert);
                              final String intermediateCertKeyName = generateSslCertKeyName(fingerPrint);
                              final String intermediateCertFileName = intermediateCertKeyName + ".pem";
  
                              if (SSL.isSslCertKeyPresent(_netscalerService, intermediateCertKeyName) &&
                                      !SSL.isCaforCerts(_netscalerService, intermediateCertKeyName)) {
                                  SSL.deleteSslCertKey(_netscalerService, intermediateCertKeyName);
                                  SSL.deleteCertFile(_ip, _username, _password, intermediateCertFileName);
                              } else {
                                  break;// if this cert has another certificate as a child then stop at this point because we need the whole chain
                              }
  
                          }
                      }
                  }
  
              }
  
              if (LOGGER.isInfoEnabled()) {
                  LOGGER.info("Successfully executed resource LoadBalancerConfigCommand: " + _gson.toJson(cmd));
              }
  
              saveConfiguration();
              return new Answer(cmd);
          } catch (final ExecutionException e) {
              LOGGER.error("Failed to execute LoadBalancerConfigCommand due to ", e);
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              } else {
                  return new Answer(cmd, e);
              }
          } catch (final Exception e) {
              LOGGER.error("Failed to execute LoadBalancerConfigCommand due to ", e);
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              } else {
                  return new Answer(cmd, e);
              }
          }
      }
  
      private synchronized Answer execute(final CreateLoadBalancerApplianceCommand cmd, final int numRetries) {
  
          if (!_isSdx) {
              return Answer.createUnsupportedCommandAnswer(cmd);
          }
  
          try {
              final String vpxName = "Cloud-VPX-" + cmd.getLoadBalancerIP();
              final String username = "admin";
              final String password = "admin";
  
              final ns ns_obj = new ns();
              ns_obj.set_name(vpxName);
              ns_obj.set_ip_address(cmd.getLoadBalancerIP());
              ns_obj.set_netmask(cmd.getNetmask());
              ns_obj.set_gateway(cmd.getGateway());
              ns_obj.set_username(username);
              ns_obj.set_password(password);
  
              // configure VPX instances with defaults
              ns_obj.set_license("Standard");
              ns_obj.set_vm_memory_total(new Double(2048));
              ns_obj.set_throughput(new Double(1000));
              ns_obj.set_pps(new Double(1000000));
              ns_obj.set_number_of_ssl_cores(0);
              ns_obj.set_profile_name("ns_nsroot_profile");
  
              // use the first VPX image of the available VPX images on the SDX to create an instance of VPX
              // TODO: should enable the option to choose the template while adding the SDX device in to CloudStack
              final xen_nsvpx_image[] vpxImages = xen_nsvpx_image.get(_netscalerSdxService);
              if (!(vpxImages != null && vpxImages.length >= 1)) {
                  new Answer(cmd, new ExecutionException("Failed to create VPX instance on the netscaler SDX device " + _ip +
                          " as there are no VPX images on SDX to use for creating VPX."));
              }
              final String imageName = vpxImages[0].get_file_name();
              ns_obj.set_image_name(imageName);
  
              String publicIf = _publicInterface;
              String privateIf = _privateInterface;
  
              // enable only the interfaces that will be used by VPX
              enableVPXInterfaces(_publicInterface, _privateInterface, ns_obj);
  
              // create new VPX instance
              ns newVpx = ns.add(_netscalerSdxService, ns_obj);
  
              if (newVpx == null) {
                  return new Answer(cmd, new ExecutionException("Failed to create VPX instance on the netscaler SDX device " + _ip));
              }
  
              // wait for VPX instance to start-up
              long startTick = System.currentTimeMillis();
              final long startWaitMilliSeconds = 600000;
              while (!newVpx.get_instance_state().equalsIgnoreCase("up") && System.currentTimeMillis() - startTick < startWaitMilliSeconds) {
                  try {
                      Thread.sleep(10000);
                  } catch (final InterruptedException e) {
                      LOGGER.debug("[ignored] interrupted while waiting for netscaler to be 'up'.");
                  }
                  final ns refreshNsObj = new ns();
                  refreshNsObj.set_id(newVpx.get_id());
                  newVpx = ns.get(_netscalerSdxService, refreshNsObj);
              }
  
              // if vpx instance never came up then error out
              if (!newVpx.get_instance_state().equalsIgnoreCase("up")) {
                  return new Answer(cmd, new ExecutionException("Failed to start VPX instance " + vpxName + " created on the netscaler SDX device " + _ip));
              }
  
              // wait till NS service in side VPX is actually ready
              startTick = System.currentTimeMillis();
              boolean nsServiceUp = false;
              final long nsServiceWaitMilliSeconds = 60000;
              while (System.currentTimeMillis() - startTick < nsServiceWaitMilliSeconds) {
                  try {
                      final nitro_service netscalerService = new nitro_service(cmd.getLoadBalancerIP(), "https");
                      netscalerService.set_certvalidation(false);
                      netscalerService.set_hostnameverification(false);
                      netscalerService.set_credential(username, password);
                      apiCallResult = netscalerService.login();
                      if (apiCallResult.errorcode == 0) {
                          nsServiceUp = true;
                          break;
                      }
                  } catch (final Exception e) {
                      Thread.sleep(10000);
                      continue;
                  }
              }
  
              if (!nsServiceUp) {
                  return new Answer(cmd, new ExecutionException("Failed to create VPX instance " + vpxName + " on the netscaler SDX device " + _ip));
              }
  
              if (LOGGER.isInfoEnabled()) {
                  LOGGER.info("Successfully provisioned VPX instance " + vpxName + " on the Netscaler SDX device " + _ip);
              }
  
              // physical interfaces on the SDX range from 10/1 to 10/8 & 1/1 to 1/8 of which two different port or same port can be used for public and private interfaces
              // However the VPX instances created will have interface range start from 10/1 but will only have as many interfaces enabled while creating the VPX instance
              // So due to this, we need to map public & private interface on SDX to correct public & private interface of VPX
  
              final int publicIfnum = Integer.parseInt(_publicInterface.substring(_publicInterface.lastIndexOf("/") + 1));
              final int privateIfnum = Integer.parseInt(_privateInterface.substring(_privateInterface.lastIndexOf("/") + 1));
  
              if (_publicInterface.startsWith("10/") && _privateInterface.startsWith("10/")) {
                  if (publicIfnum == privateIfnum) {
                      publicIf = "10/1";
                      privateIf = "10/1";
                  } else if (publicIfnum > privateIfnum) {
                      privateIf = "10/1";
                      publicIf = "10/2";
                  } else {
                      publicIf = "10/1";
                      privateIf = "10/2";
                  }
              } else if (_publicInterface.startsWith("1/") && _privateInterface.startsWith("1/")) {
                  if (publicIfnum == privateIfnum) {
                      publicIf = "1/1";
                      privateIf = "1/1";
                  } else if (publicIfnum > privateIfnum) {
                      privateIf = "1/1";
                      publicIf = "1/2";
                  } else {
                      publicIf = "1/1";
                      privateIf = "1/2";
                  }
              } else if (_publicInterface.startsWith("1/") && _privateInterface.startsWith("10/")) {
                  publicIf = "1/1";
                  privateIf = "10/1";
              } else if (_publicInterface.startsWith("10/") && _privateInterface.startsWith("1/")) {
                  publicIf = "10/1";
                  privateIf = "1/1";
              }
  
              return new CreateLoadBalancerApplianceAnswer(cmd, true, "provisioned VPX instance", "NetscalerVPXLoadBalancer", "Netscaler", new NetscalerResource(),
                      publicIf, privateIf, _username, _password);
          } catch (final Exception e) {
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              }
              return new CreateLoadBalancerApplianceAnswer(cmd, false, "failed to provisioned VPX instance due to " + e.getMessage(), null, null, null, null, null, null,
                      null);
          }
      }
  
      private Answer execute(final GlobalLoadBalancerConfigCommand gslbCmd, final int numRetries) {
  
          final String lbMethod = gslbCmd.getLoadBalancerMethod();
          final String persistenceType = gslbCmd.getPersistenceType();
          final String serviceType = gslbCmd.getServiceType();
          final boolean forRevoke = gslbCmd.isForRevoke();
          final long gslbId = gslbCmd.getGslbId();
          final List<SiteLoadBalancerConfig> sites = gslbCmd.getSiteDetails();
  
          final String domainName = gslbCmd.getDomainName();
          final String vserverName = GSLB.generateVirtualServerName(domainName);
  
          try {
  
              if (!forRevoke) { //check if the global load balancer rule is being added
  
                  // Add a GSLB virtual server
                  GSLB.createVirtualServer(_netscalerService, vserverName, lbMethod, persistenceType, gslbId, serviceType);
  
                  if (sites != null) { // check if there are any sites that are participating in global load balancing
                      for (final SiteLoadBalancerConfig site : sites) {
  
                          final String sitePrivateIP = site.getGslbProviderPrivateIp();
                          final String sitePublicIP = site.getGslbProviderPublicIp();
                          final String servicePublicIp = site.getServicePublicIp();
                          final String servicePublicPort = site.getServicePort();
                          final String siteName = GSLB.generateUniqueSiteName(sitePrivateIP, sitePublicIP, site.getDataCenterId());
  
                          // Add/Delete GSLB local and remote sites that are part of GSLB virtual server
                          if (!site.forRevoke()) {
                              final String siteType = site.isLocal() ? "LOCAL" : "REMOTE";
                              if (GSLB.getSiteObject(_netscalerService, siteName) != null) {
                                  GSLB.updateSite(_netscalerService, siteType, siteName, site.getGslbProviderPrivateIp(), site.getGslbProviderPublicIp());
                              } else {
                                  GSLB.createSite(_netscalerService, siteName, siteType, site.getGslbProviderPrivateIp(), site.getGslbProviderPublicIp());
                              }
                          }
  
                          // Add/Delete GSLB service corresponding the service running on each site
                          final String serviceName = GSLB.generateUniqueServiceName(siteName, servicePublicIp, servicePublicPort);
                          final String monitorName = GSLB.generateGslbServiceMonitorName(servicePublicIp);
                          if (!site.forRevoke()) {
                              // create a 'gslbservice' object
                              GSLB.createService(_netscalerService, serviceName, site.getServiceType(), servicePublicIp, servicePublicPort, siteName);
  
                              // Bind 'gslbservice' service object to GSLB virtual server
                              GSLB.createVserverServiceBinding(_netscalerService, serviceName, vserverName, site.getWeight());
  
                              // create a monitor for the service running on the site
                              GSLB.createGslbServiceMonitor(_netscalerService, servicePublicIp, serviceName);
  
                              // bind the monitor to the GSLB service
                              GSLB.createGslbServiceGslbMonitorBinding(_netscalerService, monitorName, serviceName);
  
                          } else {
  
                              // delete GSLB service and GSLB monitor binding
                              GSLB.deleteGslbServiceGslbMonitorBinding(_netscalerService, monitorName, serviceName);
  
                              // Unbind GSLB service with GSLB virtual server
                              GSLB.deleteVserverServiceBinding(_netscalerService, serviceName, vserverName);
  
                              GSLB.getServiceObject(_netscalerService, serviceName);
                              GSLB.deleteService(_netscalerService, serviceName);
  
                              // delete the GSLB service monitor
                              GSLB.deleteGslbServiceMonitor(_netscalerService, monitorName);
                          }
  
                          if (site.forRevoke()) { // delete the site if its for revoke
                              GSLB.deleteSite(_netscalerService, siteName);
                          }
                      }
                  }
  
                  // Bind GSLB vserver to domain
                  GSLB.createVserverDomainBinding(_netscalerService, vserverName, domainName);
  
              } else {  // global load balancer rule is being deleted, so clean up all objects created
  
                  // remove binding between virtual server and the domain name
                  GSLB.deleteVserverDomainBinding(_netscalerService, vserverName, domainName);
  
                  if (sites != null) {
                      for (final SiteLoadBalancerConfig site : sites) {
  
                          final String sitePrivateIP = site.getGslbProviderPrivateIp();
                          final String sitePublicIP = site.getGslbProviderPublicIp();
                          final String servicePublicIp = site.getServicePublicIp();
                          final String servicePublicPort = site.getServicePort();
                          final String siteName = GSLB.generateUniqueSiteName(sitePrivateIP, sitePublicIP, site.getDataCenterId());
                          final String serviceName = GSLB.generateUniqueServiceName(siteName, servicePublicIp, servicePublicPort);
                          final String monitorName = GSLB.generateGslbServiceMonitorName(servicePublicIp);
  
                          // delete GSLB service and GSLB monitor binding
                          GSLB.deleteGslbServiceGslbMonitorBinding(_netscalerService, monitorName, serviceName);
  
                          // remove binding between virtual server and services
                          GSLB.deleteVserverServiceBinding(_netscalerService, serviceName, vserverName);
  
                          // delete service object
                          GSLB.deleteService(_netscalerService, serviceName);
  
                          // delete GSLB site object
                          GSLB.deleteSite(_netscalerService, siteName);
  
                          // delete the GSLB service monitor
                          GSLB.deleteGslbServiceMonitor(_netscalerService, monitorName);
                      }
                  }
  
                  // delete GSLB virtual server
                  GSLB.deleteVirtualServer(_netscalerService, vserverName);
              }
  
              saveConfiguration();
  
          } catch (final Exception e) {
              final String errMsg = "Failed to apply GSLB configuration due to " + e.getMessage();
              if (shouldRetry(numRetries)) {
                  return retry(gslbCmd, numRetries);
              }
              return new GlobalLoadBalancerConfigAnswer(false, errMsg);
          }
  
          return new GlobalLoadBalancerConfigAnswer(true, "Successfully applied GSLB configuration.");
      }
  
      /*
       * convenience class to create/update/delete/get the GSLB specific NetScaler objects
       *     - gslbsite
       *     - gslbvserver
       *     - gslbservice
       *     - vserver-service binding
       *     - vserver-domain bindings
       */
      private static class GSLB {
  
          // create a 'gslbsite' object representing a site
          private static void createSite(final nitro_service client, final String siteName, final String siteType, final String siteIP, final String sitePublicIP) throws ExecutionException {
              try {
                  gslbsite site;
                  site = getSiteObject(client, siteName);
  
                  boolean isUpdateSite = false;
                  if (site == null) {
                      site = new gslbsite();
                  } else {
                      isUpdateSite = true;
                  }
  
                  assert "LOCAL".equalsIgnoreCase(siteType) || "REMOTE".equalsIgnoreCase(siteType);
                  site.set_sitetype(siteType);
                  site.set_sitename(siteName);
                  site.set_siteipaddress(siteIP);
                  site.set_publicip(sitePublicIP);
                  site.set_metricexchange("ENABLED");
                  site.set_nwmetricexchange("ENABLED");
                  site.set_sessionexchange("ENABLED");
                  if (isUpdateSite) {
                      gslbsite.update(client, site);
                  } else {
                      gslbsite.add(client, site);
                  }
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Successfully created GSLB site: " + siteName);
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to create GSLB site: " + siteName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          // delete 'gslbsite' object representing a site
          private static void deleteSite(final nitro_service client, final String siteName) throws ExecutionException {
              try {
                  final gslbsite site = getSiteObject(client, siteName);
                  if (site != null) {
                      final gslbsite_gslbservice_binding[] serviceBindings = gslbsite_gslbservice_binding.get(client, siteName);
                      if (serviceBindings != null && serviceBindings.length > 0) {
                          if (LOGGER.isDebugEnabled()) {
                              LOGGER.debug("There are services associated with GSLB site: " + siteName + " so ignoring site deletion");
                          }
                      }
                      gslbsite.delete(client, siteName);
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.debug("Successfully deleted GSLB site: " + siteName);
                      }
                  } else {
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.warn("Ignoring delete request for non existing  GSLB site: " + siteName);
                      }
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to delete GSLB site: " + siteName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          // update 'gslbsite' object representing a site
          private static void updateSite(final nitro_service client, final String siteType, final String siteName, final String siteIP, final String sitePublicIP) throws ExecutionException {
              try {
                  gslbsite site;
                  site = getSiteObject(client, siteName);
                  if (site == null) {
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.warn("Ignoring update request for non existing  GSLB site: " + siteName);
                      }
                      return;
                  }
                  assert "LOCAL".equalsIgnoreCase(siteType) || "REMOTE".equalsIgnoreCase(siteType);
                  site.set_sitetype(siteType);
                  site.set_sitename(siteName);
                  site.set_siteipaddress(siteIP);
                  site.set_publicip(sitePublicIP);
                  site.set_metricexchange("ENABLED");
                  site.set_nwmetricexchange("ENABLED");
                  site.set_sessionexchange("ENABLED");
                  gslbsite.update(client, site);
  
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Successfully updated GSLB site: " + siteName);
                  }
  
              } catch (final Exception e) {
                  final String errMsg = "Failed to update GSLB site: " + siteName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          // create a 'gslbvserver' object representing a globally load balanced service
          private static void
          createVirtualServer(final nitro_service client, final String vserverName, final String lbMethod, final String persistenceType, final long persistenceId, final String serviceType)
                  throws ExecutionException {
              try {
                  gslbvserver vserver;
                  vserver = getVserverObject(client, vserverName);
  
                  boolean isUpdateSite = false;
                  if (vserver == null) {
                      vserver = new gslbvserver();
                  } else {
                      isUpdateSite = true;
                  }
  
                  vserver.set_name(vserverName);
                  if ("RoundRobin".equalsIgnoreCase(lbMethod)) {
                      vserver.set_lbmethod("ROUNDROBIN");
                  } else if ("LeastConn".equalsIgnoreCase(lbMethod)) {
                      vserver.set_lbmethod("LEASTCONNECTION");
                  } else if ("Proximity".equalsIgnoreCase(lbMethod)) {
                      vserver.set_lbmethod("RTT");
                  } else {
                      throw new ExecutionException("Unsupported LB method");
                  }
                  vserver.set_persistencetype(persistenceType);
                  if ("SOURCEIP".equalsIgnoreCase(persistenceType)) {
                      vserver.set_persistenceid(persistenceId);
                  }
                  vserver.set_servicetype(serviceType);
                  vserver.set_state("ENABLED");
                  vserver.set_cookietimeout(null);
                  vserver.set_domainname(null);
                  if (isUpdateSite) {
                      // both netmask and LB method can not be specified while update so set to null
                      vserver.set_netmask(null);
                      vserver.set_v6netmasklen(null);
                      gslbvserver.update(client, vserver);
                  } else {
                      gslbvserver.add(client, vserver);
                  }
  
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Successfully added GSLB virtual server: " + vserverName);
                  }
  
              } catch (final Exception e) {
                  final String errMsg = "Failed to add GSLB virtual server: " + vserverName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          // delete 'gslbvserver' object representing a globally load balanced service
          private static void deleteVirtualServer(final nitro_service client, final String vserverName) throws ExecutionException {
              try {
                  final gslbvserver vserver = getVserverObject(client, vserverName);
                  if (vserver != null) {
                      gslbvserver.delete(client, vserver);
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.debug("Successfully deleted GSLB virtual server: " + vserverName);
                      }
                  } else {
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.warn("Ignoring delete request for non existing  GSLB virtual server: " + vserverName);
                      }
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to delete GSLB virtual server: " + vserverName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          // create, delete, update, get the GSLB services
          private static void createService(final nitro_service client, final String serviceName, final String serviceType, final String serviceIp, final String servicePort, final String siteName)
                  throws ExecutionException {
              try {
                  gslbservice service;
                  service = getServiceObject(client, serviceName);
                  final String gslbServerName = generateGslbServerName(serviceIp);
  
                  if (!gslbServerExists(client, gslbServerName)) {
                      base_response apiCallResult;
                      final com.citrix.netscaler.nitro.resource.config.basic.server nsServer = new com.citrix.netscaler.nitro.resource.config.basic.server();
                      nsServer.set_name(gslbServerName);
                      nsServer.set_ipaddress(serviceIp);
                      apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.server.add(client, nsServer);
                      if (apiCallResult.errorcode != 0 && apiCallResult.errorcode != NitroError.NS_RESOURCE_EXISTS) {
                          throw new ExecutionException("Failed to add server " + gslbServerName + " due to" + apiCallResult.message);
                      }
                  }
  
                  boolean isUpdateSite = false;
                  if (service == null) {
                      service = new gslbservice();
                  } else {
                      isUpdateSite = true;
                  }
  
                  service.set_sitename(siteName);
                  service.set_servername(gslbServerName);
                  final int port = Integer.parseInt(servicePort);
                  service.set_port(port);
                  service.set_servicename(serviceName);
                  service.set_servicetype(serviceType);
                  if (isUpdateSite) {
                      service.set_viewip(null);
                      service.set_viewname(null);
                      gslbservice.update(client, service);
                  } else {
                      gslbservice.add(client, service);
                  }
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Successfully created service: " + serviceName + " at site: " + siteName);
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to created service: " + serviceName + " at site: " + siteName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          private static void deleteService(final nitro_service client, final String serviceName) throws ExecutionException {
              try {
                  final gslbservice service = getServiceObject(client, serviceName);
                  if (service != null) {
                      gslbservice.delete(client, serviceName);
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.debug("Successfully deleted service: " + serviceName);
                      }
                  } else {
                      if (LOGGER.isDebugEnabled()) {
                          LOGGER.warn("Ignoring delete request for non existing  service: " + serviceName);
                      }
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to delete service: " + serviceName + " due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          private static void createVserverServiceBinding(final nitro_service client, final String serviceName, final String vserverName, final long weight) throws ExecutionException {
              String errMsg;
              try {
                  assert weight >= 1 && weight <= 100;
                  final gslbvserver_gslbservice_binding binding = new gslbvserver_gslbservice_binding();
                  binding.set_name(vserverName);
                  binding.set_servicename(serviceName);
                  binding.set_weight(weight);
                  gslbvserver_gslbservice_binding.add(client, binding);
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Successfully created service: " + serviceName + " and virtual server: " + vserverName + " binding");
                  }
              } catch (final nitro_exception ne) {
                  if (ne.getErrorCode() == 273) {
                      return;
                  }
                  errMsg = "Failed to create service: " + serviceName + " and virtual server: " + vserverName + " binding due to " + ne.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              } catch (final Exception e) {
                  errMsg = "Failed to create service: " + serviceName + " and virtual server: " + vserverName + " binding due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          private static void deleteVserverServiceBinding(final nitro_service client, final String serviceName, final String vserverName) throws ExecutionException {
              try {
                  final gslbvserver_gslbservice_binding[] bindings = gslbvserver_gslbservice_binding.get(client, vserverName);
                  if (bindings != null) {
                      for (final gslbvserver_gslbservice_binding binding : bindings) {
                          if (binding.get_servicename().equalsIgnoreCase(serviceName) && binding.get_name().equals(vserverName)) {
                              gslbvserver_gslbservice_binding.delete(client, binding);
                              if (LOGGER.isDebugEnabled()) {
                                  LOGGER.debug("Successfully deleted service: " + serviceName + " and virtual server: " + vserverName + " binding");
                              }
                              break;
                          }
                      }
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to create service: " + serviceName + " and virtual server: " + vserverName + " binding due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          // create, delete GSLB virtual server and domain bindings
          private static void createVserverDomainBinding(final nitro_service client, final String vserverName, final String domainName) throws ExecutionException {
              String errMsg;
              try {
                  final gslbvserver_domain_binding binding = new gslbvserver_domain_binding();
                  binding.set_domainname(domainName);
                  binding.set_name(vserverName);
                  gslbvserver_domain_binding.add(client, binding);
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Successfully added virtual server: " + vserverName + " domain name: " + domainName + " binding");
                  }
                  return;
              } catch (final nitro_exception e) {
                  if (e.getErrorCode() == NitroError.NS_GSLB_DOMAIN_ALREADY_BOUND) {
                      return;
                  }
                  errMsg = e.getMessage();
              } catch (final Exception e) {
                  errMsg = e.getMessage();
              }
              errMsg = "Failed to create virtual server: " + vserverName + " domain name: " + domainName + " binding" + errMsg;
              if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug(errMsg);
              }
              throw new ExecutionException(errMsg);
          }
  
          private static void deleteVserverDomainBinding(final nitro_service client, final String vserverName, final String domainName) throws ExecutionException {
              try {
                  final gslbvserver_domain_binding[] bindings = gslbvserver_domain_binding.get(client, vserverName);
                  if (bindings != null) {
                      for (final gslbvserver_domain_binding binding : bindings) {
                          if (binding.get_domainname().equalsIgnoreCase(domainName)) {
                              gslbvserver_domain_binding.delete(client, binding);
                              if (LOGGER.isDebugEnabled()) {
                                  LOGGER.debug("Successfully deleted virtual server: " + vserverName + " and " + " domain: " + domainName + " binding");
                              }
                              break;
                          }
                      }
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to delete virtual server: " + vserverName + " and domain " + domainName + " binding due to " + e.getMessage();
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          private static void createGslbServiceMonitor(final nitro_service nsService, final String servicePublicIp, final String serviceName) throws ExecutionException {
              try {
                  final lbmonitor newmonitor = new lbmonitor();
                  final String monitorName = generateGslbServiceMonitorName(servicePublicIp);
                  newmonitor.set_type("TCP");
                  newmonitor.set_servicename(serviceName);
                  newmonitor.set_monitorname(monitorName);
                  newmonitor.set_state("ENABLED");
                  lbmonitor.add(nsService, newmonitor);
              } catch (final nitro_exception ne) {
                  if (ne.getErrorCode() == NitroError.NS_RESOURCE_EXISTS) {
                      return;
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to create GSLB monitor for service public ip" + servicePublicIp;
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug(errMsg);
                  }
                  throw new ExecutionException(errMsg);
              }
          }
  
          private static void deleteGslbServiceMonitor(final nitro_service nsService, final String monitorName) throws ExecutionException {
              try {
                  final lbmonitor serviceMonitor = lbmonitor.get(nsService, monitorName);
                  if (serviceMonitor != null) {
                      lbmonitor.delete(nsService, serviceMonitor);
                  }
              } catch (final nitro_exception ne) {
                  if (ne.getErrorCode() != NitroError.NS_RESOURCE_NOT_EXISTS) {
                      final String errMsg = "Failed to delete monitor " + monitorName + " for GSLB service due to " + ne.getMessage();
                      LOGGER.debug(errMsg);
                      throw new com.cloud.utils.exception.ExecutionException(errMsg);
                  }
              } catch (final Exception e) {
                  final String errMsg = "Failed to delete monitor " + monitorName + " for GSLB service due to " + e.getMessage();
                  LOGGER.debug(errMsg);
                  throw new com.cloud.utils.exception.ExecutionException(errMsg);
              }
          }
  
          private static void createGslbServiceGslbMonitorBinding(final nitro_service nsService, final String monitorName, final String serviceName) {
              try {
                  final gslbservice_lbmonitor_binding monitorBinding = new gslbservice_lbmonitor_binding();
                  monitorBinding.set_monitor_name(monitorName);
                  monitorBinding.set_servicename(serviceName);
                  gslbservice_lbmonitor_binding.add(nsService, monitorBinding);
              } catch (final Exception e) {
                  // TODO: Nitro API version 10.* is not compatible for NetScalers 9.*, so may fail
                  // against NetScaler version lesser than 10 hence ignore the exception
                  LOGGER.warn("Failed to bind monitor to GSLB service due to " + e.getMessage());
              }
          }
  
          private static void deleteGslbServiceGslbMonitorBinding(final nitro_service nsService, final String monitorName, final String serviceName) {
              try {
                  final gslbservice_lbmonitor_binding[] monitorBindings = gslbservice_lbmonitor_binding.get(nsService, serviceName);
                  if (monitorBindings != null && monitorBindings.length > 0) {
                      for (final gslbservice_lbmonitor_binding binding : monitorBindings) {
                          if (binding.get_monitor_name().equalsIgnoreCase(monitorName)) {
                              LOGGER.info("Found a binding between monitor " + binding.get_monitor_name() + " and " + binding.get_servicename());
                              gslbservice_lbmonitor_binding.delete(nsService, binding);
                          }
                      }
                  }
              } catch (final Exception e) {
                  LOGGER.debug("Failed to delete GSLB monitor " + monitorName + " and GSLB service " + serviceName + " binding due to " + e.getMessage() +
                          " but moving on ..., will be cleaned up as part of GSLB " + " service delete any way..");
              }
          }
  
          // get 'gslbsite' object corresponding to a site name
          private static gslbsite getSiteObject(final nitro_service client, final String siteName) {
              try {
                  final gslbsite site = gslbsite.get(client, siteName);
                  if (site != null) {
                      return site;
                  }
              } catch (final Exception e) {
                  LOGGER.info("[ignored]"
                          + "error getting site: " + e.getLocalizedMessage());
              }
              return null;
          }
  
          private static gslbvserver getVserverObject(final nitro_service client, final String vserverName) {
              try {
                  final gslbvserver vserver = gslbvserver.get(client, vserverName);
                  return vserver;
              } catch (final Exception e) {
                  return null;
              }
          }
  
          private static gslbservice getServiceObject(final nitro_service client, final String serviceName) {
              try {
                  final gslbservice service = gslbservice.get(client, serviceName);
                  return service;
              } catch (final Exception e) {
                  return null;
              }
          }
  
          private static String generateUniqueSiteName(final String sitePrivateIp, final String sitePublicIP, final long dataCenterId) {
              return "cloudsite" + String.valueOf(dataCenterId);
          }
  
          private static String generateVirtualServerName(final String domainName) {
              return "cloud-gslb-vserver-" + domainName;
          }
  
          private static String generateUniqueServiceName(final String siteName, final String publicIp, final String publicPort) {
              return "cloud-gslb-service-" + siteName + "-" + publicIp + "-" + publicPort;
          }
  
          private static String generateGslbServiceMonitorName(final String publicIp) {
              return "cloud-monitor-" + publicIp;
          }
  
          private static boolean gslbServerExists(final nitro_service client, final String serverName) throws ExecutionException {
              try {
                  if (com.citrix.netscaler.nitro.resource.config.basic.server.get(client, serverName) != null) {
                      return true;
                  } else {
                      return false;
                  }
              } catch (final nitro_exception e) {
                  if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                      return false;
                  } else {
                      throw new ExecutionException("Failed to verify Server " + serverName + " exists on the NetScaler device due to " + e.getMessage());
                  }
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to verify Server " + serverName + " exists on the NetScaler device due to " + e.getMessage());
              }
          }
  
          private static String generateGslbServerName(final String serverIP) {
              return genGslbObjectName("Cloud-Server-", serverIP);
          }
  
          private static String genGslbObjectName(final Object... args) {
              final StringBuffer buff = new StringBuffer();
              for (int i = 0; i < args.length; i++) {
                  buff.append(args[i]);
                  if (i != args.length - 1) {
                      buff.append("-");
                  }
              }
              return buff.toString();
          }
      }
  
      /* SSL Termination */
      private static class SSL {
  
          private static final String SSL_CERT_PATH = "/nsconfig/ssl/";
          private static final int SSH_PORT = 22;
  
          private static boolean isSslCertKeyPresent(final nitro_service ns, final String certKeyName) throws ExecutionException {
  
              final String filter = "certkey:" + certKeyName;
  
              try {
                  if (sslcertkey.count_filtered(ns, filter) > 0) {
                      return true;
                  }
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to get certkey " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to get certkey " + e.getMessage());
              }
  
              return false;
          }
  
          private static void deleteSslCertKey(final nitro_service ns, final String certKeyName) throws ExecutionException {
              try {
  
                  final sslcertkey certkey = new sslcertkey();
                  certkey.set_certkey(certKeyName);
                  sslcertkey.delete(ns, certkey);
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to delete certkey " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to delete certkey " + e.getMessage());
              }
  
          }
  
          private static void deleteCertFile(final String nsIp, final String username, final String password, final String certFilename) throws Exception {
              SshHelper.sshExecute(nsIp, SSH_PORT, username, null, password, "shell rm " + SSL_CERT_PATH + certFilename);
          }
  
          private static void deleteKeyFile(final String nsIp, final String username, final String password, final String keyFilename) throws Exception {
              SshHelper.sshExecute(nsIp, SSH_PORT, username, null, password, "shell rm " + SSL_CERT_PATH + keyFilename);
          }
  
          private static void createSslCertKey(final nitro_service ns, final String certFilename, final String keyFilename, final String certKeyName, final String password) throws ExecutionException {
              LOGGER.debug("Adding cert to netscaler");
              try {
                  final sslcertkey certkey = new sslcertkey();
                  certkey.set_certkey(certKeyName);
                  certkey.set_cert(SSL_CERT_PATH + certFilename);
  
                  if (keyFilename != null) {
                      certkey.set_key(SSL_CERT_PATH + keyFilename);
                  }
  
                  if (password != null) {
                      certkey.set_passplain(password);
                  }
  
                  certkey.perform_operation(ns);
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to add certkey binding " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to add certkey binding " + e.getMessage());
              }
  
          }
  
          private static void bindCertKeyToVserver(final nitro_service ns, final String certKeyName, final String vserver) throws ExecutionException {
              LOGGER.debug("Adding cert to netscaler");
  
              try {
                  final sslvserver_sslcertkey_binding cert_binding = new sslvserver_sslcertkey_binding();
                  cert_binding.set_certkeyname(certKeyName);
                  cert_binding.set_vservername(vserver);
                  cert_binding.perform_operation(ns);
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to bind certkey to vserver due to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to bind certkey to vserver due to " + e.getMessage());
              }
          }
  
          private static void unbindCertKeyFromVserver(final nitro_service ns, final String certKeyName, final String vserver) throws ExecutionException {
              try {
  
                  final sslvserver_sslcertkey_binding cert_binding = new sslvserver_sslcertkey_binding();
                  cert_binding.set_certkeyname(certKeyName);
                  cert_binding.set_vservername(vserver);
                  sslvserver_sslcertkey_binding.delete(ns, cert_binding);
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to unbind certkey to vserver due to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to unbind certkey to vserver due to " + e.getMessage());
              }
  
          }
  
          private static void uploadCert(final String nsIp, final String user, final String password, final String certFilename, final byte[] certData) throws ExecutionException {
              try {
                  SshHelper.scpTo(nsIp, SSH_PORT, user, null, password, SSL_CERT_PATH, certData, certFilename, null);
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to copy private key to device " + e.getMessage());
              }
          }
  
          private static void uploadKey(final String nsIp, final String user, final String password, final String keyFilename, final byte[] keyData) throws ExecutionException {
              try {
                  SshHelper.scpTo(nsIp, SSH_PORT, user, null, password, SSL_CERT_PATH, keyData, keyFilename, null);
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to copy private key to device " + e.getMessage());
              }
          }
  
          private static void enableSslFeature(final nitro_service ns, final boolean isSdx) throws ExecutionException {
              if (isSdx) {
                  return;
              }
              try {
                  final base_response result = ns.enable_features(new String[] {"SSL"});
                  if (result.errorcode != 0) {
                      throw new ExecutionException("Unable to enable SSL on LB");
                  }
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to enable ssl feature on load balancer due to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to enable ssl feature on load balancer due to " + e.getMessage());
              }
          }
  
          public static boolean certLinkExists(final nitro_service ns, final String userCertName, final String caCertName) throws ExecutionException {
              try {
                  // check if there is a link from userCertName to caCertName
  
                  final sslcertkey userCert = sslcertkey.get(ns, userCertName);
                  final String nsCaCert = userCert.get_linkcertkeyname();
  
                  if (nsCaCert != null && nsCaCert.equals(caCertName)) {
                      return true;
                  }
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer due to " + e.getMessage());
              }
              return false;
          }
  
          public static void linkCerts(final nitro_service ns, final String userCertName, final String caCertName) throws ExecutionException {
              try {
  
                  // the assumption is that both userCertName and caCertName are present on NS
  
                  final sslcertkey caCert = sslcertkey.get(ns, caCertName);
                  final sslcertkey userCert = sslcertkey.get(ns, userCertName);
  
                  final sslcertkey linkResource = new sslcertkey();
  
                  // link user cert to CA cert
                  linkResource.set_certkey(userCert.get_certkey());
                  linkResource.set_linkcertkeyname(caCert.get_certkey());
                  sslcertkey.link(ns, linkResource);
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer due to " + e.getMessage());
              }
  
          }
  
          public static boolean isCaforCerts(final nitro_service ns, final String caCertName) throws ExecutionException {
              // check if this certificate  serves as a CA for other certificates
              try {
                  final sslcertlink[] childLinks = sslcertlink.get_filtered(ns, "linkcertkeyname:" + caCertName);
                  if (childLinks != null && childLinks.length > 0) {
                      return true;
                  }
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer due to " + e.getMessage());
              }
              return false;
  
          }
  
          public static boolean isBoundToVserver(final nitro_service ns, final String certKeyName, final String nsVirtualServerName) throws ExecutionException {
              try {
  
                  final sslcertkey_sslvserver_binding[] cert_vs_binding = sslcertkey_sslvserver_binding.get_filtered(ns, certKeyName, "vservername:" + nsVirtualServerName);
                  if (cert_vs_binding != null && cert_vs_binding.length > 0) {
                      return true;
                  }
  
              } catch (final nitro_exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer to " + e.getMessage());
              } catch (final Exception e) {
                  throw new ExecutionException("Failed to check cert link on load balancer due to " + e.getMessage());
              }
              return false;
  
          }
      }
  
      private void enableVPXInterfaces(final String publicIf, final String privateIf, final ns nsObj) {
          // enable VPX to use 10 gigabit Ethernet interfaces if public/private interface
          // on SDX is a 10Gig interface
          if (publicIf.equals("10/1") || privateIf.equals("10/1")) {
              nsObj.set_if_10_1(new Boolean(true));
          }
  
          if (publicIf.equals("10/2") || privateIf.equals("10/2")) {
              nsObj.set_if_10_2(new Boolean(true));
          }
  
          if (publicIf.equals("10/3") || privateIf.equals("10/3")) {
              nsObj.set_if_10_3(new Boolean(true));
          }
  
          if (publicIf.equals("10/4") || privateIf.equals("10/4")) {
              nsObj.set_if_10_4(new Boolean(true));
          }
  
          if (publicIf.equals("10/5") || privateIf.equals("10/5")) {
              nsObj.set_if_10_5(new Boolean(true));
          }
  
          if (publicIf.equals("10/6") || privateIf.equals("10/6")) {
              nsObj.set_if_10_6(new Boolean(true));
          }
  
          if (publicIf.equals("10/7") || privateIf.equals("10/7")) {
              nsObj.set_if_10_7(new Boolean(true));
          }
  
          if (publicIf.equals("10/8") || privateIf.equals("10/8")) {
              nsObj.set_if_10_8(new Boolean(true));
          }
  
          // enable VPX to use 1 gigabit Ethernet interfaces if public/private interface
          // on SDX is a 1Gig interface
          if (publicIf.equals("1/1") || privateIf.equals("1/1")) {
              nsObj.set_if_1_1(new Boolean(true));
          }
  
          if (publicIf.equals("1/2") || privateIf.equals("1/2")) {
              nsObj.set_if_1_2(new Boolean(true));
          }
  
          if (publicIf.equals("1/3") || privateIf.equals("1/3")) {
              nsObj.set_if_1_3(new Boolean(true));
          }
  
          if (publicIf.equals("1/4") || privateIf.equals("1/4")) {
              nsObj.set_if_1_4(new Boolean(true));
          }
  
          if (publicIf.equals("1/5") || privateIf.equals("1/5")) {
              nsObj.set_if_1_5(new Boolean(true));
          }
  
          if (publicIf.equals("1/6") || privateIf.equals("1/6")) {
              nsObj.set_if_1_6(new Boolean(true));
          }
  
          if (publicIf.equals("1/7") || privateIf.equals("1/7")) {
              nsObj.set_if_1_7(new Boolean(true));
          }
  
          if (publicIf.equals("1/8") || privateIf.equals("1/8")) {
              nsObj.set_if_1_8(new Boolean(true));
          }
      }
  
      private synchronized Answer execute(final DestroyLoadBalancerApplianceCommand cmd, final int numRetries) {
          final String vpxName = "Cloud-VPX-" + cmd.getLoadBalancerIP();
          if (!_isSdx) {
              return Answer.createUnsupportedCommandAnswer(cmd);
          }
  
          try {
              ns vpxToDelete = null;
              final ns[] vpxInstances = ns.get(_netscalerSdxService);
              for (final ns vpx : vpxInstances) {
                  if (vpx.get_name().equals(vpxName)) {
                      vpxToDelete = vpx;
                      break;
                  }
              }
  
              if (vpxToDelete == null) {
                  final String msg = "There is no VPX instance " + vpxName + " on the Netscaler SDX device " + _ip + " to delete";
                  LOGGER.warn(msg);
                  return new DestroyLoadBalancerApplianceAnswer(cmd, true, msg);
              }
  
              // destroy the VPX instance
              final ns nsDelObj = new ns();
              nsDelObj.set_id(vpxToDelete.get_id());
              vpxToDelete = ns.delete(_netscalerSdxService, nsDelObj);
              final String msg = "Deleted VPX instance " + vpxName + " on Netscaler SDX " + _ip + " successfully.";
              LOGGER.info(msg);
              return new DestroyLoadBalancerApplianceAnswer(cmd, true, msg);
          } catch (final Exception e) {
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              }
              return new DestroyLoadBalancerApplianceAnswer(cmd, false, "Failed to delete VPX instance " + vpxName + " on Netscaler SDX " + _ip);
          }
      }
  
      private synchronized Answer execute(final SetStaticNatRulesCommand cmd, final int numRetries) {
  
          if (_isSdx) {
              return Answer.createUnsupportedCommandAnswer(cmd);
          }
  
          final String[] results = new String[cmd.getRules().length];
          int i = 0;
          boolean endResult = true;
  
          try {
              for (final StaticNatRuleTO rule : cmd.getRules()) {
                  final String srcIp = rule.getSrcIp();
                  final String dstIP = rule.getDstIp();
                  final String iNatRuleName = generateInatRuleName(srcIp, dstIP);
                  final String rNatRuleName = generateRnatRuleName(srcIp, dstIP);
                  inat iNatRule = null;
                  rnat rnatRule = null;
  
                  if (!rule.revoked()) {
                      try {
                          iNatRule = inat.get(_netscalerService, iNatRuleName);
                      } catch (final nitro_exception e) {
                          if (e.getErrorCode() != NitroError.NS_RESOURCE_NOT_EXISTS) {
                              throw e;
                          }
                      }
  
                      if (iNatRule == null) {
                          iNatRule = new inat();
                          iNatRule.set_name(iNatRuleName);
                          iNatRule.set_publicip(srcIp);
                          iNatRule.set_privateip(dstIP);
                          iNatRule.set_usnip("OFF");
                          iNatRule.set_usip("ON");
                          try {
                              apiCallResult = inat.add(_netscalerService, iNatRule);
                          } catch (final nitro_exception e) {
                              if (e.getErrorCode() != NitroError.NS_RESOURCE_EXISTS) {
                                  throw e;
                              }
                          }
                          LOGGER.debug("Created Inat rule on the Netscaler device " + _ip + " to enable static NAT from " + srcIp + " to " + dstIP);
                      }
                      try {
                          final rnat[] rnatRules = rnat.get(_netscalerService);
                          if (rnatRules != null) {
                              for (final rnat rantrule : rnatRules) {
                                  if (rantrule.get_network().equalsIgnoreCase(rNatRuleName)) {
                                      rnatRule = rantrule;
                                      break;
                                  }
                              }
                          }
                      } catch (final nitro_exception e) {
                          throw e;
                      }
  
                      if (rnatRule == null) {
                          rnatRule = new rnat();
                          rnatRule.set_natip(srcIp);
                          rnatRule.set_network(dstIP);
                          rnatRule.set_netmask("255.255.255.255");
                          try {
                              apiCallResult = rnat.update(_netscalerService, rnatRule);
                          } catch (final nitro_exception e) {
                              if (e.getErrorCode() != NitroError.NS_RESOURCE_EXISTS) {
                                  throw e;
                              }
                          }
                          LOGGER.debug("Created Rnat rule on the Netscaler device " + _ip + " to enable revese static NAT from " + dstIP + " to " + srcIp);
                      }
                  } else {
                      try {
                          inat.delete(_netscalerService, iNatRuleName);
                          final rnat[] rnatRules = rnat.get(_netscalerService);
                          if (rnatRules != null) {
                              for (final rnat rantrule : rnatRules) {
                                  if (rantrule.get_network().equalsIgnoreCase(dstIP)) {
                                      rnatRule = rantrule;
                                      rnat.clear(_netscalerService, rnatRule);
                                      break;
                                  }
                              }
                          }
                      } catch (final nitro_exception e) {
                          if (e.getErrorCode() != NitroError.NS_RESOURCE_NOT_EXISTS) {
                              throw e;
                          }
                      }
                      LOGGER.debug("Deleted Inat rule on the Netscaler device " + _ip + " to remove static NAT from " + srcIp + " to " + dstIP);
                  }
  
                  saveConfiguration();
                  results[i++] = "Static nat rule from " + srcIp + " to " + dstIP + " successfully " + (rule.revoked() ? " revoked." : " created.");
              }
          } catch (final Exception e) {
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              }
              results[i++] = "Configuring static nat rule failed due to " + e.getMessage();
              endResult = false;
              return new SetStaticNatRulesAnswer(cmd, results, endResult);
          }
  
          return new SetStaticNatRulesAnswer(cmd, results, endResult);
      }
  
      private synchronized Answer execute(final ExternalNetworkResourceUsageCommand cmd, final int numRetries) {
          try {
              if (!_isSdx) {
                  return getPublicIpBytesSentAndReceived(cmd);
              } else {
                  return Answer.createUnsupportedCommandAnswer(cmd);
              }
          } catch (final ExecutionException e) {
              if (shouldRetry(numRetries)) {
                  return retry(cmd, numRetries);
              } else {
                  return new ExternalNetworkResourceUsageAnswer(cmd, e);
              }
          }
      }
  
      private void addSubnetIP(final String snip, final String netmask) throws ExecutionException {
          try {
              final nsip selfIp = new nsip();
              selfIp.set_ipaddress(snip);
              selfIp.set_netmask(netmask);
              selfIp.set_type("SNIP");
              apiCallResult = nsip.add(_netscalerService, selfIp);
              if (apiCallResult.errorcode != 0) {
                  throw new ExecutionException("Failed to add SNIP object on the Netscaler device due to " + apiCallResult.message);
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to add SNIP object on the Netscaler device due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to add SNIP object on the Netscaler device due to " + e.getMessage());
          }
      }
  
      private void addGuestVlanAndSubnet(final long vlanTag, final String vlanSelfIp, final String vlanNetmask, final boolean guestVlan) throws ExecutionException {
          try {
              // add vlan object for guest VLAN
              if (!nsVlanExists(vlanTag)) {
                  try {
                      final vlan vlanObj = new vlan();
                      vlanObj.set_id(vlanTag);
                      apiCallResult = vlan.add(_netscalerService, vlanObj);
                      if (apiCallResult.errorcode != 0) {
                          throw new ExecutionException("Failed to add new vlan with tag:" + vlanTag + "on the NetScaler device due to " + apiCallResult.message);
                      }
                  } catch (final nitro_exception e) {
                      throw new ExecutionException("Failed to add new vlan with tag:" + vlanTag + "on the NetScaler device due to " + e.getMessage());
                  }
              }
  
              // add subnet IP object for this guest network
              if (!nsSnipExists(vlanSelfIp)) {
                  try {
                      final nsip selfIp = new nsip();
                      selfIp.set_ipaddress(vlanSelfIp);
                      selfIp.set_netmask(vlanNetmask);
                      selfIp.set_type("SNIP");
                      apiCallResult = nsip.add(_netscalerService, selfIp);
                      if (apiCallResult.errorcode != 0) {
                          throw new ExecutionException("Failed to add SNIP object for the guest network on the Netscaler device due to " + apiCallResult.message);
                      }
                  } catch (final nitro_exception e) {
                      throw new ExecutionException("Failed to add SNIP object for the guest network on the Netscaler device due to " + e.getMessage());
                  }
              }
  
              // bind the vlan object to subnet IP object
              if (!nsVlanNsipBindingExists(vlanTag, vlanSelfIp)) {
                  try {
                      final vlan_nsip_binding ipVlanBinding = new vlan_nsip_binding();
                      ipVlanBinding.set_id(vlanTag);
                      ipVlanBinding.set_ipaddress(vlanSelfIp);
                      ipVlanBinding.set_netmask(vlanNetmask);
                      apiCallResult = vlan_nsip_binding.add(_netscalerService, ipVlanBinding);
                      if (apiCallResult.errorcode != 0) {
                          throw new ExecutionException("Failed to bind VLAN with tag:" + vlanTag + " to the subnet due to " + apiCallResult.message);
                      }
                  } catch (final nitro_exception e) {
                      throw new ExecutionException("Failed to bind VLAN with tage:" + vlanTag + " to the subnet due to " + e.getMessage());
                  }
              }
  
              // bind vlan object to the private interface
              try {
                  final vlan_interface_binding vlanBinding = new vlan_interface_binding();
                  if (guestVlan) {
                      vlanBinding.set_ifnum(_privateInterface);
                  } else {
                      vlanBinding.set_ifnum(_publicInterface);
                  }
                  vlanBinding.set_tagged(true);
                  vlanBinding.set_id(vlanTag);
                  apiCallResult = vlan_interface_binding.add(_netscalerService, vlanBinding);
                  if (apiCallResult.errorcode != 0) {
                      final String vlanInterface = guestVlan ? _privateInterface : _publicInterface;
                      throw new ExecutionException("Failed to bind vlan with tag:" + vlanTag + " with the interface " + vlanInterface + " due to " + apiCallResult.message);
                  }
              } catch (final nitro_exception e) {
                  if (!(e.getErrorCode() == NitroError.NS_INTERFACE_ALREADY_BOUND_TO_VLAN)) {
                      throw new ExecutionException("Failed to bind VLAN " + vlanTag + " with interface on the Netscaler device due to " + e.getMessage());
                  }
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to implement guest network on the Netscaler device due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to implement guest network on the Netscaler device due to " + e.getMessage());
          }
      }
  
      private void deleteGuestVlan(final long vlanTag, final String vlanSelfIp, final String vlanNetmask) throws ExecutionException {
          try {
  
              // Delete all servers and associated services from this guest VLAN
              deleteServersInGuestVlan(vlanTag, vlanSelfIp, vlanNetmask);
  
              // unbind vlan to the private interface
              try {
                  final vlan_interface_binding vlanIfBinding = new vlan_interface_binding();
                  vlanIfBinding.set_id(vlanTag);
                  vlanIfBinding.set_ifnum(_privateInterface);
                  vlanIfBinding.set_tagged(true);
                  apiCallResult = vlan_interface_binding.delete(_netscalerService, vlanIfBinding);
                  if (apiCallResult.errorcode != 0) {
                      throw new ExecutionException("Failed to unbind vlan:" + vlanTag + " with the private interface due to " + apiCallResult.message);
                  }
              } catch (final nitro_exception e) {
                  // if Vlan to interface binding does not exist then ignore the exception and proceed
                  if (!(e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS)) {
                      throw new ExecutionException("Failed to unbind vlan from the interface while shutdown of guest network on the Netscaler device due to " +
                              e.getMessage());
                  }
              }
  
              // unbind the vlan to subnet
              try {
                  final vlan_nsip_binding vlanSnipBinding = new vlan_nsip_binding();
                  vlanSnipBinding.set_netmask(vlanNetmask);
                  vlanSnipBinding.set_ipaddress(vlanSelfIp);
                  vlanSnipBinding.set_id(vlanTag);
                  apiCallResult = vlan_nsip_binding.delete(_netscalerService, vlanSnipBinding);
                  if (apiCallResult.errorcode != 0) {
                      throw new ExecutionException("Failed to unbind vlan:" + vlanTag + " with the subnet due to " + apiCallResult.message);
                  }
              } catch (final nitro_exception e) {
                  // if Vlan to subnet binding does not exist then ignore the exception and proceed
                  if (!(e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS)) {
                      throw new ExecutionException("Failed to unbind vlan:" + vlanTag + " with the subnet due to " + e.getMessage());
                  }
              }
  
              // remove subnet IP
              try {
                  final nsip tmpVlanSelfIp = new nsip();
                  tmpVlanSelfIp.set_ipaddress(vlanSelfIp);
  
                  final nsip subnetIp = nsip.get(_netscalerService, tmpVlanSelfIp);
                  apiCallResult = nsip.delete(_netscalerService, subnetIp);
                  if (apiCallResult.errorcode != 0) {
                      throw new ExecutionException("Failed to remove subnet ip:" + vlanSelfIp + " from the NetScaler device due to" + apiCallResult.message);
                  }
              } catch (final nitro_exception e) {
                  // if subnet SNIP does not exist then ignore the exception and proceed
                  if (!(e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS)) {
                      throw new ExecutionException("Failed to remove subnet ip:" + vlanSelfIp + " from the NetScaler device due to" + e.getMessage());
                  }
              }
  
              // remove the vlan from the NetScaler device
              if (nsVlanExists(vlanTag)) {
                  // remove vlan
                  apiCallResult = com.citrix.netscaler.nitro.resource.config.network.vlan.delete(_netscalerService, vlanTag);
                  if (apiCallResult.errorcode != 0) {
                      throw new ExecutionException("Failed to remove vlan with tag:" + vlanTag + "due to" + apiCallResult.message);
                  }
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to delete guest vlan network on the Netscaler device due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to delete guest vlan network on the Netscaler device due to " + e.getMessage());
          }
      }
  
      private boolean nsVlanExists(final long vlanTag) throws ExecutionException {
          try {
              if (vlan.get(_netscalerService, new Long(vlanTag)) != null) {
                  return true;
              } else {
                  return false;
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return false;
              } else {
                  throw new ExecutionException("Failed to verify  VLAN exists on the NetScaler device due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify  VLAN exists on the NetScaler device due to " + e.getMessage());
          }
      }
  
      private boolean nsSnipExists(final String subnetIp) throws ExecutionException {
          try {
              final nsip tmpSubnetIp = new nsip();
              tmpSubnetIp.set_ipaddress(subnetIp);
  
              final nsip snip = nsip.get(_netscalerService, tmpSubnetIp);
              return snip != null;
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return false;
              } else {
                  throw new ExecutionException("Failed to verify if SNIP exists on the NetScaler device due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify if SNIP exists on the NetScaler device due to " + e.getMessage());
          }
      }
  
      private boolean nsServerExists(final String serverName) throws ExecutionException {
          try {
              if (com.citrix.netscaler.nitro.resource.config.basic.server.get(_netscalerService, serverName) != null) {
                  return true;
              } else {
                  return false;
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return false;
              } else {
                  throw new ExecutionException("Failed to verify Server " + serverName + " exists on the NetScaler device due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify Server " + serverName + " exists on the NetScaler device due to " + e.getMessage());
          }
      }
  
      private boolean nsVirtualServerExists(final String vserverName) throws ExecutionException {
          try {
              if (com.citrix.netscaler.nitro.resource.config.lb.lbvserver.get(_netscalerService, vserverName) != null) {
                  return true;
              } else {
                  return false;
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return false;
              } else {
                  throw new ExecutionException("Failed to verify VServer " + vserverName + " exists on the NetScaler device due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify VServer " + vserverName + " exists on the NetScaler device due to " + e.getMessage());
          }
      }
  
      private boolean nsVlanNsipBindingExists(final long vlanTag, final String vlanSelfIp) throws ExecutionException {
          try {
              final vlan_nsip_binding[] vlanNsipBindings = vlan_nsip_binding.get(_netscalerService, vlanTag);
              if (vlanNsipBindings != null && vlanNsipBindings[0] != null && vlanNsipBindings[0].get_ipaddress().equalsIgnoreCase(vlanSelfIp)) {
                  return true;
              } else {
                  return false;
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return false;
              } else {
                  throw new ExecutionException("Failed to verify Vlan " + vlanTag + " to SNIP " + vlanSelfIp + " binding exists due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify Vlan " + vlanTag + " to SNIP " + vlanSelfIp + " binding exists due to " + e.getMessage());
          }
      }
  
      private lbvserver getVirtualServerIfExisits(final String lbVServerName) throws ExecutionException {
          try {
              return lbvserver.get(_netscalerService, lbVServerName);
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return null;
              } else {
                  throw new ExecutionException(e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException(e.getMessage());
          }
      }
  
      private lbmonitor getMonitorIfExisits(final String lbMonitorName) throws ExecutionException {
          try {
              return lbmonitor.get(_netscalerService, lbMonitorName);
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return null;
              } else {
                  throw new ExecutionException(e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException(e.getMessage());
          }
      }
  
      private boolean isServiceBoundToVirtualServer(final String serviceName) throws ExecutionException {
          try {
              final lbvserver[] lbservers = lbvserver.get(_netscalerService);
              for (final lbvserver vserver : lbservers) {
                  final filtervalue[] filter = new filtervalue[1];
                  filter[0] = new filtervalue("servicename", serviceName);
                  final lbvserver_service_binding[] result = lbvserver_service_binding.get_filtered(_netscalerService, vserver.get_name(), filter);
                  if (result != null && result.length > 0) {
                      return true;
                  }
              }
              return false;
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify service " + serviceName + " is bound to any virtual server due to " + e.getMessage());
          }
      }
  
      private boolean isServiceBoundToMonitor(final String nsServiceName, final String nsMonitorName) throws ExecutionException {
  
          final filtervalue[] filter = new filtervalue[1];
          filter[0] = new filtervalue("monitor_name", nsMonitorName);
          service_lbmonitor_binding[] result;
          try {
              result = service_lbmonitor_binding.get_filtered(_netscalerService, nsServiceName, filter);
              if (result != null && result.length > 0) {
                  return true;
              }
  
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify service " + nsServiceName + " is bound to any monitor due to " + e.getMessage());
          }
          return false;
      }
  
      private boolean nsMonitorExist(final String nsMonitorname) throws ExecutionException {
          if (getMonitorIfExisits(nsMonitorname) != null) {
              return true;
          } else {
              return false;
          }
      }
  
      private boolean nsServiceExists(final String serviceName) throws ExecutionException {
          try {
              if (com.citrix.netscaler.nitro.resource.config.basic.service.get(_netscalerService, serviceName) != null) {
                  return true;
              } else {
                  return false;
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_NO_SERIVCE) {
                  return false;
              } else {
                  throw new ExecutionException("Failed to verify service " + serviceName + " exists due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify service " + serviceName + " exists due to " + e.getMessage());
          }
      }
  
      private boolean nsServiceBindingExists(final String lbVirtualServer, final String serviceName) throws ExecutionException {
          try {
              final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding[] serviceBindings =
                      com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding.get(_netscalerService, lbVirtualServer);
              if (serviceBindings != null) {
                  for (final com.citrix.netscaler.nitro.resource.config.lb.lbvserver_service_binding binding : serviceBindings) {
                      if (serviceName.equalsIgnoreCase(binding.get_servicename())) {
                          return true;
                      }
                  }
              }
              return false;
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to verify lb vserver " + lbVirtualServer + "and service " + serviceName + " binding exists due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify lb vserver " + lbVirtualServer + "and service " + serviceName + " binding exists due to " + e.getMessage());
          }
      }
  
      private boolean isServiceGroupBoundToVirtualServer(final String nsVirtualServerName, final String serviceGroupName) throws ExecutionException {
  
          new lbvserver_servicegroup_binding();
  
          try {
              final lbvserver_servicegroup_binding[] result =
                      lbvserver_servicegroup_binding.get_filtered(_netscalerService, nsVirtualServerName, "servicegroupname:" + serviceGroupName);
              if (result != null && result.length > 0) {
                  return true;
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to verify lb vserver " + nsVirtualServerName + "and servicegrop " + serviceGroupName + " binding exists due to " +
                      e.getMessage());
          }
          return false;
  
      }
  
      private boolean nsServiceGroupExists(final String lbVServerName) throws ExecutionException {
          try {
              return servicegroup.get(_netscalerService, lbVServerName) != null;
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return false; // service group does not exist
              } else {
                  throw new ExecutionException(e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException(e.getMessage());
          }
      }
  
      private void deleteServersInGuestVlan(final long vlanTag, final String vlanSelfIp, final String vlanNetmask) throws ExecutionException {
          try {
              final com.citrix.netscaler.nitro.resource.config.basic.server[] serverList = com.citrix.netscaler.nitro.resource.config.basic.server.get(_netscalerService);
  
              if (serverList == null) {
                  return;
              }
  
              // remove the server and services associated with guest vlan
              for (final com.citrix.netscaler.nitro.resource.config.basic.server server : serverList) {
                  // check if server belong to same subnet as one associated with vlan
                  if (NetUtils.sameSubnet(vlanSelfIp, server.get_ipaddress(), vlanNetmask)) {
                      // first remove services associated with this server
                      final com.citrix.netscaler.nitro.resource.config.basic.service serveicesList[] =
                              com.citrix.netscaler.nitro.resource.config.basic.service.get(_netscalerService);
                      if (serveicesList != null) {
                          for (final com.citrix.netscaler.nitro.resource.config.basic.service svc : serveicesList) {
                              if (svc.get_servername().equals(server.get_ipaddress())) {
                                  apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.service.delete(_netscalerService, svc.get_name());
                                  if (apiCallResult.errorcode != 0) {
                                      throw new ExecutionException("Failed to remove service:" + svc.get_name());
                                  }
                              }
                          }
                      }
                      // remove the server
                      // don't delete server which has no ip address (these servers are created by NS for autoscale
                      if (server.get_ipaddress() != null) {
                          apiCallResult = com.citrix.netscaler.nitro.resource.config.basic.server.delete(_netscalerService, server.get_name());
                          if (apiCallResult.errorcode != 0) {
                              throw new ExecutionException("Failed to remove server:" + server.get_name());
                          }
                      }
                  }
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to delete server and services in the guest vlan:" + vlanTag + " on the Netscaler device due to: " + e.getMessage());
          }
      }
  
      private String getNetScalerProtocol(final LoadBalancerTO loadBalancer) throws ExecutionException {
          final int port = loadBalancer.getSrcPort();
          String lbProtocol = loadBalancer.getLbProtocol();
          final StickinessPolicyTO[] stickyPolicies = loadBalancer.getStickinessPolicies();
          String nsProtocol = "TCP";
  
          if (lbProtocol == null) {
              lbProtocol = loadBalancer.getProtocol();
          }
  
          if (stickyPolicies != null && stickyPolicies.length > 0 && stickyPolicies[0] != null) {
              final StickinessPolicyTO stickinessPolicy = stickyPolicies[0];
              if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName()) ||
                      StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) {
                  nsProtocol = "HTTP";
                  return nsProtocol;
              }
          }
  
          if (lbProtocol.equalsIgnoreCase(NetUtils.SSL_PROTO) || lbProtocol.equalsIgnoreCase(NetUtils.HTTP_PROTO)) {
              return lbProtocol.toUpperCase();
          }
  
          if (port == NetUtils.HTTP_PORT) {
              nsProtocol = "HTTP";
          } else if (NetUtils.TCP_PROTO.equalsIgnoreCase(lbProtocol)) {
              nsProtocol = "TCP";
          } else if (NetUtils.UDP_PROTO.equalsIgnoreCase(lbProtocol)) {
              nsProtocol = "UDP";
          }
  
          return nsProtocol;
      }
  
      private void addLBVirtualServer(final String virtualServerName, final String publicIp, final int publicPort, final String lbAlgorithm, final String protocol, final StickinessPolicyTO[] stickyPolicies,
              final AutoScaleVmGroupTO vmGroupTO) throws ExecutionException {
          try {
              String lbMethod;
              if ("roundrobin".equalsIgnoreCase(lbAlgorithm)) {
                  lbMethod = "ROUNDROBIN";
              } else if ("leastconn".equalsIgnoreCase(lbAlgorithm)) {
                  lbMethod = "LEASTCONNECTION";
              } else if ("source".equalsIgnoreCase(lbAlgorithm)) {
                  lbMethod = "SOURCEIPHASH";
              } else {
                  throw new ExecutionException("Got invalid load balancing algorithm: " + lbAlgorithm + " in the load balancing rule");
              }
  
              boolean vserverExisis = false;
              lbvserver vserver = getVirtualServerIfExisits(virtualServerName);
              if (vserver != null) {
                  if (!vserver.get_servicetype().equalsIgnoreCase(protocol)) {
                      throw new ExecutionException("Can not update virtual server:" + virtualServerName + " as current protocol:" + vserver.get_servicetype() +
                              " of virtual server is different from the " + " intended protocol:" + protocol);
                  }
                  vserverExisis = true;
              }
              // Use new vserver always for configuration
              vserver = new lbvserver();
              vserver.set_name(virtualServerName);
              vserver.set_ipv46(publicIp);
              vserver.set_port(publicPort);
              vserver.set_servicetype(protocol);
              vserver.set_lbmethod(lbMethod);
  
              // netmask can only be set for source IP load balancer algorithm
              if (!lbMethod.equalsIgnoreCase("SOURCEIPHASH")) {
                  vserver.set_netmask(null);
                  vserver.set_v6netmasklen(null);
              }
  
              if (stickyPolicies != null && stickyPolicies.length > 0 && stickyPolicies[0] != null) {
                  long timeout = 2;// netscaler default 2 min
                  String cookieName = null;
                  final StickinessPolicyTO stickinessPolicy = stickyPolicies[0];
  
                  // get the session persistence parameters
                  final List<Pair<String, String>> paramsList = stickinessPolicy.getParams();
                  for (final Pair<String, String> param : paramsList) {
                      if ("holdtime".equalsIgnoreCase(param.first())) {
                          timeout = Long.parseLong(param.second());
                      } else if ("name".equalsIgnoreCase(param.first())) {
                          cookieName = param.second();
                      }
                  }
  
                  // configure virtual server based on the persistence method
                  if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) {
                      vserver.set_persistencetype("COOKIEINSERT");
                  } else if (StickinessMethodType.SourceBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) {
                      vserver.set_persistencetype("SOURCEIP");
                  } else if (StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) {
                      vserver.set_persistencetype("RULE");
                      vserver.set_rule("HTTP.REQ.HEADER(\"COOKIE\").VALUE(0).typecast_nvlist_t('=',';').value(\"" + cookieName + "\")");
                      vserver.set_resrule("HTTP.RES.HEADER(\"SET-COOKIE\").VALUE(0).typecast_nvlist_t('=',';').value(\"" + cookieName + "\")");
                  } else {
                      throw new ExecutionException("Got invalid session persistence method: " + stickinessPolicy.getMethodName() + " in the load balancing rule");
                  }
  
                  // set session persistence timeout
                  vserver.set_timeout(timeout);
              } else {
                  // delete the LB stickyness policy
                  vserver.set_persistencetype("NONE");
              }
  
              if (vserverExisis) {
                  apiCallResult = lbvserver.update(_netscalerService, vserver);
              } else {
                  apiCallResult = lbvserver.add(_netscalerService, vserver);
              }
              if (apiCallResult.errorcode != 0) {
                  throw new ExecutionException("Failed to create new load balancing virtual server:" + virtualServerName + " due to " + apiCallResult.message);
              }
  
              if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug("Created load balancing virtual server " + virtualServerName + " on the Netscaler device");
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to create new virtual server:" + virtualServerName + " due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to create new virtual server:" + virtualServerName + " due to " + e.getMessage());
          }
      }
  
      private void removeLBVirtualServer(final String virtualServerName) throws ExecutionException {
          try {
              final lbvserver vserver = lbvserver.get(_netscalerService, virtualServerName);
              if (vserver == null) {
                  return;
              }
              apiCallResult = lbvserver.delete(_netscalerService, vserver);
              if (apiCallResult.errorcode != 0) {
                  throw new ExecutionException("Failed to delete virtual server:" + virtualServerName + " due to " + apiCallResult.message);
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return;
              } else {
                  throw new ExecutionException("Failed remove virtual server:" + virtualServerName + " due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to remove virtual server:" + virtualServerName + " due to " + e.getMessage());
          }
      }
  
      // Monitor related methods
      private void addLBMonitor(final String nsMonitorName, final String lbProtocol, final HealthCheckPolicyTO hcp) throws ExecutionException {
          try {
              // check if the monitor exists
              boolean csMonitorExisis = false;
              final lbmonitor csMonitor = getMonitorIfExisits(nsMonitorName);
              if (csMonitor != null) {
                  if (!csMonitor.get_type().equalsIgnoreCase(lbProtocol)) {
                      throw new ExecutionException("Can not update monitor :" + nsMonitorName + " as current protocol:" + csMonitor.get_type() +
                              " of monitor is different from the " + " intended protocol:" + lbProtocol);
                  }
                  csMonitorExisis = true;
              }
              if (!csMonitorExisis) {
                  final lbmonitor csMon = new lbmonitor();
                  csMon.set_monitorname(nsMonitorName);
                  csMon.set_type(lbProtocol);
                  if (lbProtocol.equalsIgnoreCase("HTTP")) {
                      csMon.set_httprequest(hcp.getpingPath());
                      LOGGER.trace("LB Protocol is HTTP,  Applying  ping path on HealthCheck Policy");
                  } else {
                      LOGGER.debug("LB Protocol is not HTTP, Skipping to apply  ping path on HealthCheck Policy");
                  }
  
                  csMon.set_interval(hcp.getHealthcheckInterval());
                  csMon.set_retries(Math.max(hcp.getHealthcheckThresshold(), hcp.getUnhealthThresshold()) + 1);
                  csMon.set_resptimeout(hcp.getResponseTime());
                  csMon.set_failureretries(hcp.getUnhealthThresshold());
                  csMon.set_successretries(hcp.getHealthcheckThresshold());
                  LOGGER.debug("Monitor properites going to get created :interval :: " + csMon.get_interval() + "respTimeOUt:: " + csMon.get_resptimeout() +
                          "failure retires(unhealththresshold) :: " + csMon.get_failureretries() + "successtries(healththresshold) ::" + csMon.get_successretries());
                  lbmonitor.add(_netscalerService, csMon);
              } else {
                  LOGGER.debug("Monitor :" + nsMonitorName + " is already existing. Skipping to delete and create it");
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to create new monitor :" + nsMonitorName + " due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to create new monitor :" + nsMonitorName + " due to " + e.getMessage());
          }
      }
  
      private void bindServiceToMonitor(final String nsServiceName, final String nsMonitorName) throws ExecutionException {
  
          try {
              com.citrix.netscaler.nitro.resource.config.basic.service serviceObject = new com.citrix.netscaler.nitro.resource.config.basic.service();
              serviceObject = com.citrix.netscaler.nitro.resource.config.basic.service.get(_netscalerService, nsServiceName);
              if (serviceObject != null) {
                  final com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding serviceMonitor =
                          new com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding();
                  serviceMonitor.set_monitor_name(nsMonitorName);
                  serviceMonitor.set_name(nsServiceName);
                  serviceMonitor.set_monstate("ENABLED");
                  LOGGER.debug("Trying to bind  the monitor :" + nsMonitorName + " to the service :" + nsServiceName);
                  com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding.add(_netscalerService, serviceMonitor);
                  LOGGER.debug("Successfully binded the monitor :" + nsMonitorName + " to the service :" + nsServiceName);
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to create new monitor :" + nsMonitorName + " due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to create new monitor :" + nsMonitorName + " due to " + e.getMessage());
          }
      }
  
      private void unBindServiceToMonitor(final String nsServiceName, final String nsMonitorName) throws ExecutionException {
  
          try {
              com.citrix.netscaler.nitro.resource.config.basic.service serviceObject = new com.citrix.netscaler.nitro.resource.config.basic.service();
              serviceObject = com.citrix.netscaler.nitro.resource.config.basic.service.get(_netscalerService, nsServiceName);
  
              if (serviceObject != null) {
                  final com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding serviceMonitor =
                          new com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding();
                  serviceMonitor.set_monitor_name(nsMonitorName);
                  serviceMonitor.set_name(nsServiceName);
                  LOGGER.debug("Trying to unbind  the monitor :" + nsMonitorName + " from the service :" + nsServiceName);
                  service_lbmonitor_binding.delete(_netscalerService, serviceMonitor);
                  LOGGER.debug("Successfully unbinded the monitor :" + nsMonitorName + " from the service :" + nsServiceName);
              }
  
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return;
              } else {
                  throw new ExecutionException("Failed to unbind monitor :" + nsMonitorName + "from the service :" + nsServiceName + "due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to unbind monitor :" + nsMonitorName + "from the service :" + nsServiceName + "due to " + e.getMessage());
          }
  
      }
  
      private void removeLBMonitor(final String nsMonitorName) throws ExecutionException {
  
          try {
              if (nsMonitorExist(nsMonitorName)) {
                  final lbmonitor monitorObj = lbmonitor.get(_netscalerService, nsMonitorName);
                  monitorObj.set_respcode(null);
                  lbmonitor.delete(_netscalerService, monitorObj);
                  LOGGER.info("Successfully deleted monitor : " + nsMonitorName);
              }
          } catch (final nitro_exception e) {
              if (e.getErrorCode() == NitroError.NS_RESOURCE_NOT_EXISTS) {
                  return;
              } else {
                  throw new ExecutionException("Failed to delete monitor :" + nsMonitorName + " due to " + e.getMessage());
              }
          } catch (final Exception e) {
              throw new ExecutionException("Failed to delete monitor :" + nsMonitorName + " due to " + e.getMessage());
          }
  
      }
  
      public synchronized void applyAutoScaleConfig(final LoadBalancerTO loadBalancer) throws Exception, ExecutionException {
  
          final AutoScaleVmGroupTO vmGroupTO = loadBalancer.getAutoScaleVmGroupTO();
          if (!isAutoScaleSupportedInNetScaler()) {
              throw new ExecutionException("AutoScale not supported in this version of NetScaler");
          }
          if (loadBalancer.isRevoked() || vmGroupTO.getState().equals(AutoScaleVmGroup.State.REVOKE)) {
              removeAutoScaleConfig(loadBalancer);
          } else {
              createAutoScaleConfig(loadBalancer);
          }
          // AutoScale APIs are successful executed, now save the configuration.
          saveConfiguration();
          if (LOGGER.isInfoEnabled()) {
              LOGGER.info("Successfully executed resource AutoScaleConfig");
          }
      }
  
      private synchronized boolean createAutoScaleConfig(final LoadBalancerTO loadBalancerTO) throws ExecutionException, Exception {
  
          final String srcIp = loadBalancerTO.getSrcIp();
          final int srcPort = loadBalancerTO.getSrcPort();
          final String lbProtocol = getNetScalerProtocol(loadBalancerTO);
          final String lbAlgorithm = loadBalancerTO.getAlgorithm();
          generateAutoScaleVmGroupIdentifier(loadBalancerTO);
          final String nsVirtualServerName = generateNSVirtualServerName(srcIp, srcPort);
          final AutoScaleVmGroupTO vmGroupTO = loadBalancerTO.getAutoScaleVmGroupTO();
          if (LOGGER.isDebugEnabled()) {
              LOGGER.debug("Created load balancing virtual server " + nsVirtualServerName + " on the Netscaler device");
          }
          addLBVirtualServer(nsVirtualServerName, srcIp, srcPort, lbAlgorithm, lbProtocol, loadBalancerTO.getStickinessPolicies(), vmGroupTO);
  
          final String serviceGroupName = generateAutoScaleServiceGroupName(loadBalancerTO);
          if (!nsServiceGroupExists(serviceGroupName)) {
              // add servicegroup lb_autoscaleGroup -autoscale POLICY -memberPort 80
              final int memberPort = vmGroupTO.getMemberPort();
              try {
                  final servicegroup serviceGroup = new servicegroup();
                  serviceGroup.set_servicegroupname(serviceGroupName);
                  serviceGroup.set_servicetype(lbProtocol);
                  serviceGroup.set_autoscale("POLICY");
                  serviceGroup.set_memberport(memberPort);
                  servicegroup.add(_netscalerService, serviceGroup);
              } catch (final Exception e) {
                  throw e;
              }
          }
  
          if (!isServiceGroupBoundToVirtualServer(nsVirtualServerName, serviceGroupName)) {
              // Bind autoscale service group
              // bind lb vserver lb lb_autoscaleGroup
              final lbvserver_servicegroup_binding vserver_servicegroup_binding = new lbvserver_servicegroup_binding();
  
              try {
                  vserver_servicegroup_binding.set_name(nsVirtualServerName);
                  vserver_servicegroup_binding.set_servicegroupname(serviceGroupName);
                  lbvserver_servicegroup_binding.add(_netscalerService, vserver_servicegroup_binding);
              } catch (final Exception e) {
                  throw e;
              }
          }
  
          // Create the autoscale config
          if (!loadBalancerTO.getAutoScaleVmGroupTO().getState().equals(AutoScaleVmGroup.State.DISABLED)) {
              // on restart of network, there might be vmgrps in disabled state, no need to create autoscale config for them
              enableAutoScaleConfig(loadBalancerTO, false);
          } else if (loadBalancerTO.getAutoScaleVmGroupTO().getState().equals(AutoScaleVmGroup.State.DISABLED)) {
              disableAutoScaleConfig(loadBalancerTO, false);
          }
  
          return true;
      }
  
      private synchronized boolean removeAutoScaleConfig(final LoadBalancerTO loadBalancerTO) throws Exception, ExecutionException {
          final String srcIp = loadBalancerTO.getSrcIp();
          final int srcPort = loadBalancerTO.getSrcPort();
          generateAutoScaleVmGroupIdentifier(loadBalancerTO);
  
          final String nsVirtualServerName = generateNSVirtualServerName(srcIp, srcPort);
          final String serviceGroupName = generateAutoScaleServiceGroupName(loadBalancerTO);
  
          if (loadBalancerTO.getAutoScaleVmGroupTO().getCurrentState().equals(AutoScaleVmGroup.State.ENABLED)) {
              disableAutoScaleConfig(loadBalancerTO, false);
          }
  
          if (isServiceGroupBoundToVirtualServer(nsVirtualServerName, serviceGroupName)) {
              // UnBind autoscale service group
              // unbind lb vserver lb lb_autoscaleGroup
              final lbvserver_servicegroup_binding vserver_servicegroup_binding = new lbvserver_servicegroup_binding();
              try {
                  vserver_servicegroup_binding.set_name(nsVirtualServerName);
                  vserver_servicegroup_binding.set_servicegroupname(serviceGroupName);
                  lbvserver_servicegroup_binding.delete(_netscalerService, vserver_servicegroup_binding);
              } catch (final Exception e) {
                  throw e;
              }
          }
  
          if (nsServiceGroupExists(serviceGroupName)) {
              // Remove autoscale service group
              final com.citrix.netscaler.nitro.resource.config.basic.servicegroup serviceGroup = new com.citrix.netscaler.nitro.resource.config.basic.servicegroup();
              try {
                  serviceGroup.set_servicegroupname(serviceGroupName);
                  servicegroup.delete(_netscalerService, serviceGroup);
              } catch (final Exception e) {
                  throw e;
              }
          }
  
          removeLBVirtualServer(nsVirtualServerName);
  
          return true;
      }
  
      private synchronized boolean enableAutoScaleConfig(final LoadBalancerTO loadBalancerTO, final boolean isCleanUp) throws Exception {
          final String vmGroupIdentifier = generateAutoScaleVmGroupIdentifier(loadBalancerTO);
          final String srcIp = loadBalancerTO.getSrcIp();
          final int srcPort = loadBalancerTO.getSrcPort();
  
          final String nsVirtualServerName = generateNSVirtualServerName(srcIp, srcPort);
          final String serviceGroupName = generateAutoScaleServiceGroupName(loadBalancerTO);
          final String profileName = generateAutoScaleProfileName(vmGroupIdentifier);
          final String timerName = generateAutoScaleTimerName(vmGroupIdentifier);
          final String scaleDownActionName = generateAutoScaleScaleDownActionName(vmGroupIdentifier);
          final String scaleUpActionName = generateAutoScaleScaleUpActionName(vmGroupIdentifier);
          final String mtName = generateSnmpMetricTableName(vmGroupIdentifier);
          final String monitorName = generateSnmpMonitorName(vmGroupIdentifier);
          final AutoScaleVmGroupTO vmGroupTO = loadBalancerTO.getAutoScaleVmGroupTO();
          final AutoScaleVmProfileTO profileTO = vmGroupTO.getProfile();
          final List<AutoScalePolicyTO> policies = vmGroupTO.getPolicies();
          final int interval = vmGroupTO.getInterval();
          String snmpCommunity = null;
          int snmpPort = DEFAULT_SNMP_PORT;
          long cur_prirotiy = 1;
  
          // get the session persistence parameters
          final List<Pair<String, String>> paramsList = profileTO.getCounterParamList();
          for (final Pair<String, String> param : paramsList) {
              if ("snmpcommunity".equalsIgnoreCase(param.first())) {
                  snmpCommunity = param.second();
              } else if ("snmpport".equalsIgnoreCase(param.first())) {
                  snmpPort = Integer.parseInt(param.second());
              }
          }
  
          try {
              // Set min and max autoscale members;
              // add lb vserver lb  http 10.102.31.100 80 -minAutoscaleMinMembers 3 -maxAutoscaleMembers 10
              final int minAutoScaleMembers = vmGroupTO.getMinMembers();
              final int maxAutoScaleMembers = vmGroupTO.getMaxMembers();
              final lbvserver vserver = new lbvserver();
              try {
                  vserver.set_name(nsVirtualServerName);
                  vserver.set_minautoscalemembers(minAutoScaleMembers);
                  vserver.set_maxautoscalemembers(maxAutoScaleMembers);
                  lbvserver.update(_netscalerService, vserver);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              /* AutoScale Config */
              // Add AutoScale Profile
              // add autoscale profile lb_asprofile CLOUDSTACK -url -http:// 10.102.31.34:8080/client/api- -apiKey abcdef
              // -sharedSecret xyzabc
              final String apiKey = profileTO.getAutoScaleUserApiKey();
              final String secretKey = profileTO.getAutoScaleUserSecretKey();
              final String url = profileTO.getCloudStackApiUrl();
  
              final autoscaleprofile autoscaleProfile = new autoscaleprofile();
              try {
                  autoscaleProfile.set_name(profileName);
                  autoscaleProfile.set_type("CLOUDSTACK");
                  autoscaleProfile.set_apikey(apiKey);
                  autoscaleProfile.set_sharedsecret(secretKey);
                  autoscaleProfile.set_url(url);
                  autoscaleprofile.add(_netscalerService, autoscaleProfile);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              // Add Timer
              final nstimer timer = new nstimer();
              try {
                  timer.set_name(timerName);
                  timer.set_interval(interval);
                  nstimer.add(_netscalerService, timer);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              // AutoScale Actions
              Integer scaleUpQuietTime = null;
              Integer scaleDownQuietTime = null;
              for (final AutoScalePolicyTO autoScalePolicyTO : policies) {
                  if (scaleUpQuietTime == null) {
                      if (isScaleUpPolicy(autoScalePolicyTO)) {
                          scaleUpQuietTime = autoScalePolicyTO.getQuietTime();
                          if (scaleDownQuietTime != null) {
                              break;
                          }
                      }
                  }
                  if (scaleDownQuietTime == null) {
                      if (isScaleDownPolicy(autoScalePolicyTO)) {
                          scaleDownQuietTime = autoScalePolicyTO.getQuietTime();
                          if (scaleUpQuietTime != null) {
                              break;
                          }
                      }
                  }
              }
  
              // Add AutoScale ScaleUp action
              // add autoscale action lb_scaleUpAction provision -vserver lb -profilename lb_asprofile -params
              // -lbruleid=1234&command=deployvm&zoneid=10&templateid=5&serviceofferingid=3- -quiettime 300
              final com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction scaleUpAction =
                      new com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction();
              try {
                  scaleUpAction.set_name(scaleUpActionName);
                  scaleUpAction.set_type("SCALE_UP"); // TODO: will this be called provision?
                  scaleUpAction.set_vserver(nsVirtualServerName); // Actions Vserver, the one that is autoscaled, with CS
                  // now both are same. Not exposed in API.
                  scaleUpAction.set_profilename(profileName);
                  if(scaleUpQuietTime != null) {
                      scaleUpAction.set_quiettime(scaleUpQuietTime);
                  }
                  final String scaleUpParameters =
                          "command=deployVirtualMachine" + "&" + ApiConstants.ZONE_ID + "=" + profileTO.getZoneId() + "&" + ApiConstants.SERVICE_OFFERING_ID + "=" +
                                  profileTO.getServiceOfferingId() + "&" + ApiConstants.TEMPLATE_ID + "=" + profileTO.getTemplateId() + "&" + ApiConstants.DISPLAY_NAME + "=" +
                                  profileTO.getVmName() + "&" + (profileTO.getNetworkId() == null ? "" : ApiConstants.NETWORK_IDS + "=" + profileTO.getNetworkId() + "&") +
                                  (profileTO.getOtherDeployParams() == null ? "" : profileTO.getOtherDeployParams() + "&") + "lbruleid=" + loadBalancerTO.getUuid();
                  scaleUpAction.set_parameters(scaleUpParameters);
                  autoscaleaction.add(_netscalerService, scaleUpAction);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              final com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction scaleDownAction =
                      new com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction();
              final Integer expungeVmGracePeriod = profileTO.getExpungeVmGracePeriod();
              try {
                  scaleDownAction.set_name(scaleDownActionName);
                  scaleDownAction.set_type("SCALE_DOWN"); // TODO: will this be called de-provision?
                  scaleDownAction.set_vserver(nsVirtualServerName); // TODO: no global option as of now through Nitro.
                  // Testing cannot be done.
                  scaleDownAction.set_profilename(profileName);
                  scaleDownAction.set_quiettime(scaleDownQuietTime);
                  final String scaleDownParameters = "command=destroyVirtualMachine" + "&" + "lbruleid=" + loadBalancerTO.getUuid();
                  scaleDownAction.set_parameters(scaleDownParameters);
                  scaleDownAction.set_vmdestroygraceperiod(expungeVmGracePeriod);
                  autoscaleaction.add(_netscalerService, scaleDownAction);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              /* Create min member policy */
              final String minMemberPolicyName = generateAutoScaleMinPolicyName(vmGroupIdentifier);
              final String minMemberPolicyExp =
                      "SYS.VSERVER(\"" + nsVirtualServerName + "\").ACTIVESERVICES.LT(SYS.VSERVER(\"" + nsVirtualServerName + "\").MINAUTOSCALEMEMBERS)";
              addAutoScalePolicy(timerName, minMemberPolicyName, cur_prirotiy++, minMemberPolicyExp, scaleUpActionName, interval, interval, isCleanUp);
  
              /* Create max member policy */
              final String maxMemberPolicyName = generateAutoScaleMaxPolicyName(vmGroupIdentifier);
              final String maxMemberPolicyExp =
                      "SYS.VSERVER(\"" + nsVirtualServerName + "\").ACTIVESERVICES.GT(SYS.VSERVER(\"" + nsVirtualServerName + "\").MAXAUTOSCALEMEMBERS)";
              addAutoScalePolicy(timerName, maxMemberPolicyName, cur_prirotiy++, maxMemberPolicyExp, scaleDownActionName, interval, interval, isCleanUp);
  
              /* Create Counters */
              final HashMap<String, Integer> snmpMetrics = new HashMap<String, Integer>();
              for (final AutoScalePolicyTO autoScalePolicyTO : policies) {
                  final List<ConditionTO> conditions = autoScalePolicyTO.getConditions();
                  String policyExpression = "";
                  int snmpCounterNumber = 0;
                  for (final ConditionTO conditionTO : conditions) {
                      final CounterTO counterTO = conditionTO.getCounter();
                      String counterName = counterTO.getName();
                      final Condition.Operator operator = conditionTO.getRelationalOperator();
                      final long threshold = conditionTO.getThreshold();
  
                      final StringBuilder conditionExpression = new StringBuilder();
                      try(Formatter formatter = new Formatter(conditionExpression, Locale.US);) {
  
                          if (counterTO.getSource().equals(Counter.Source.SNMP)) {
                              counterName = generateSnmpMetricName(counterName);
                              if (snmpMetrics.size() == 0) {
                                  // Create Metric Table
                                  //add lb metricTable lb_metric_table
                                  final lbmetrictable metricTable = new lbmetrictable();
                                  try {
                                      metricTable.set_metrictable(mtName);
                                      lbmetrictable.add(_netscalerService, metricTable);
                                  } catch (final Exception e) {
                                      // Ignore Exception on cleanup
                                      if (!isCleanUp) {
                                          throw e;
                                      }
                                  }
  
                                  // Create Monitor
                                  // add lb monitor lb_metric_table_mon LOAD -destPort 161 -snmpCommunity public -metricTable
                                  // lb_metric_table -interval <policy_interval == 80% >
                                  final lbmonitor monitor = new lbmonitor();
                                  try {
                                      monitor.set_monitorname(monitorName);
                                      monitor.set_type("LOAD");
                                      monitor.set_destport(snmpPort);
                                      monitor.set_snmpcommunity(snmpCommunity);
                                      monitor.set_metrictable(mtName);
                                      monitor.set_interval((int)(interval * 0.8));
                                      lbmonitor.add(_netscalerService, monitor);
                                  } catch (final Exception e) {
                                      // Ignore Exception on cleanup
                                      if (!isCleanUp) {
                                          throw e;
                                      }
                                  }
  
                                  // Bind monitor to servicegroup.
                                  // bind lb monitor lb_metric_table_mon lb_autoscaleGroup -passive
                                  final servicegroup_lbmonitor_binding servicegroup_monitor_binding = new servicegroup_lbmonitor_binding();
                                  try {
                                      servicegroup_monitor_binding.set_servicegroupname(serviceGroupName);
                                      servicegroup_monitor_binding.set_monitor_name(monitorName);
  
                                      // Use the monitor for autoscaling purpose only.
                                      // Don't mark service members down when metric breaches threshold
                                      servicegroup_monitor_binding.set_passive(true);
  
                                      servicegroup_lbmonitor_binding.add(_netscalerService, servicegroup_monitor_binding);
                                  } catch (final Exception e) {
                                      // Ignore Exception on cleanup
                                      if (!isCleanUp) {
                                          throw e;
                                      }
                                  }
                              }
  
                              final boolean newMetric = !snmpMetrics.containsKey(counterName);
                              if (newMetric) {
                                  snmpMetrics.put(counterName, snmpCounterNumber++);
                              }
  
                              if (newMetric) {
                                  // bind lb metricTable lb_metric_table mem 1.3.6.1.4.1.2021.11.9.0
                                  final String counterOid = counterTO.getValue();
                                  final lbmetrictable_metric_binding metrictable_metric_binding = new lbmetrictable_metric_binding();
                                  try {
                                      metrictable_metric_binding.set_metrictable(mtName);
                                      metrictable_metric_binding.set_metric(counterName);
                                      metrictable_metric_binding.set_Snmpoid(counterOid);
                                      lbmetrictable_metric_binding.add(_netscalerService, metrictable_metric_binding);
                                  } catch (final Exception e) {
                                      // Ignore Exception on cleanup
                                      if (!isCleanUp) {
                                          throw e;
                                      }
                                  }
  
                                  // bind lb monitor lb_metric_table_mon -metric cpu -metricThreshold 1
                                  final lbmonitor_metric_binding monitor_metric_binding = new lbmonitor_metric_binding();
  
                                  try {
                                      monitor_metric_binding.set_monitorname(monitorName);
                                      monitor_metric_binding.set_metric(counterName);
                                      /*
                                       * Setting it to max to make sure traffic is not affected due to 'LOAD' monitoring.
                                       * For Ex. if CPU is tracked and CPU is greater than 80, it is still < than Integer.MAX_VALUE
                                       * so traffic will continue to flow.
                                       */
                                      monitor_metric_binding.set_metricthreshold(Integer.MAX_VALUE);
                                      lbmonitor_metric_binding.add(_netscalerService, monitor_metric_binding);
                                  } catch (final Exception e) {
                                      // Ignore Exception on cleanup
                                      if (!isCleanUp) {
                                          throw e;
                                      }
                                  }
                              }
                              // SYS.VSERVER("abcd").SNMP_TABLE(0).AVERAGE_VALUE.GT(80)
                              final int counterIndex = snmpMetrics.get(counterName); // TODO: temporary fix. later on counter name
                              // will be added as a param to SNMP_TABLE.
                              formatter.format("SYS.VSERVER(\"%s\").SNMP_TABLE(%d).AVERAGE_VALUE.%s(%d)", nsVirtualServerName, counterIndex, operator, threshold);
                          } else if (counterTO.getSource().equals(Counter.Source.NETSCALER)) {
                              //SYS.VSERVER("abcd").RESPTIME.GT(10)
                              formatter.format("SYS.VSERVER(\"%s\").%s.%s(%d)", nsVirtualServerName, counterTO.getValue(), operator, threshold);
                          }
                      } finally {
                          // closing formatter
                      }
                      if (policyExpression.length() != 0) {
                          policyExpression += " && ";
                      }
                      policyExpression += conditionExpression;
                  }
                  policyExpression = "(" + policyExpression + ")";
  
                  final String policyId = Long.toString(autoScalePolicyTO.getId());
                  final String policyName = generateAutoScalePolicyName(vmGroupIdentifier, policyId);
                  String action = null;
                  if (isScaleUpPolicy(autoScalePolicyTO)) {
                      action = scaleUpActionName;
                      final String scaleUpCondition =
                              "SYS.VSERVER(\"" + nsVirtualServerName + "\").ACTIVESERVICES.LT(SYS.VSERVER(\"" + nsVirtualServerName + "\").MAXAUTOSCALEMEMBERS)";
                      policyExpression = scaleUpCondition + " && " + policyExpression;
                  } else {
                      action = scaleDownActionName;
                      final String scaleDownCondition =
                              "SYS.VSERVER(\"" + nsVirtualServerName + "\").ACTIVESERVICES.GT(SYS.VSERVER(\"" + nsVirtualServerName + "\").MINAUTOSCALEMEMBERS)";
                      policyExpression = scaleDownCondition + " && " + policyExpression;
                  }
  
                  addAutoScalePolicy(timerName, policyName, cur_prirotiy++, policyExpression, action, autoScalePolicyTO.getDuration(), interval, isCleanUp);
  
              }
          } catch (final Exception ex) {
              if (!isCleanUp) {
                  // Normal course, exception has occurred
                  disableAutoScaleConfig(loadBalancerTO, true);
                  throw ex;
  
              } else {
                  // Programming error. Exception should never be thrown afterall.
                  throw ex;
              }
          }
  
          return true;
      }
  
      private synchronized boolean disableAutoScaleConfig(final LoadBalancerTO loadBalancerTO, final boolean isCleanUp) throws Exception {
  
          final String vmGroupIdentifier = generateAutoScaleVmGroupIdentifier(loadBalancerTO);
  
          final String profileName = generateAutoScaleProfileName(vmGroupIdentifier);
          final String timerName = generateAutoScaleTimerName(vmGroupIdentifier);
          final String scaleDownActionName = generateAutoScaleScaleDownActionName(vmGroupIdentifier);
          final String scaleUpActionName = generateAutoScaleScaleUpActionName(vmGroupIdentifier);
          final String mtName = generateSnmpMetricTableName(vmGroupIdentifier);
          final String monitorName = generateSnmpMonitorName(vmGroupIdentifier);
          final String serviceGroupName = generateAutoScaleServiceGroupName(loadBalancerTO);
          final AutoScaleVmGroupTO vmGroupTO = loadBalancerTO.getAutoScaleVmGroupTO();
          final List<AutoScalePolicyTO> policies = vmGroupTO.getPolicies();
          final String minMemberPolicyName = generateAutoScaleMinPolicyName(vmGroupIdentifier);
          final String maxMemberPolicyName = generateAutoScaleMaxPolicyName(vmGroupIdentifier);
  
          try {
  
              /* Delete min/max member policies */
  
              removeAutoScalePolicy(timerName, minMemberPolicyName, isCleanUp);
  
              removeAutoScalePolicy(timerName, maxMemberPolicyName, isCleanUp);
  
              boolean isSnmp = false;
              /* Create Counters */
              for (final AutoScalePolicyTO autoScalePolicyTO : policies) {
                  final List<ConditionTO> conditions = autoScalePolicyTO.getConditions();
                  for (final ConditionTO conditionTO : conditions) {
                      final CounterTO counterTO = conditionTO.getCounter();
                      if (counterTO.getSource().equals(Counter.Source.SNMP)) {
                          isSnmp = true;
                          break;
                      }
                  }
                  final String policyId = Long.toString(autoScalePolicyTO.getId());
                  final String policyName = generateAutoScalePolicyName(vmGroupIdentifier, policyId);
  
                  // Removing Timer policy
                  removeAutoScalePolicy(timerName, policyName, isCleanUp);
              }
  
              /* Delete AutoScale Config */
              // Delete AutoScale ScaleDown action
              final com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction scaleDownAction =
                      new com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction();
              try {
                  scaleDownAction.set_name(scaleDownActionName);
                  autoscaleaction.delete(_netscalerService, scaleDownAction);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              // Delete AutoScale ScaleUp action
              final com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction scaleUpAction =
                      new com.citrix.netscaler.nitro.resource.config.autoscale.autoscaleaction();
              try {
                  scaleUpAction.set_name(scaleUpActionName);
                  autoscaleaction.delete(_netscalerService, scaleUpAction);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              // Delete Timer
              final nstimer timer = new nstimer();
              try {
                  timer.set_name(timerName);
                  nstimer.delete(_netscalerService, timer);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              // Delete AutoScale Profile
              final autoscaleprofile autoscaleProfile = new autoscaleprofile();
              try {
                  autoscaleProfile.set_name(profileName);
                  autoscaleprofile.delete(_netscalerService, autoscaleProfile);
              } catch (final Exception e) {
                  // Ignore Exception on cleanup
                  if (!isCleanUp) {
                      throw e;
                  }
              }
  
              if (isSnmp) {
                  final servicegroup_lbmonitor_binding servicegroup_monitor_binding = new servicegroup_lbmonitor_binding();
                  try {
                      servicegroup_monitor_binding.set_monitor_name(monitorName);
                      servicegroup_monitor_binding.set_servicegroupname(serviceGroupName);
                      servicegroup_lbmonitor_binding.delete(_netscalerService, servicegroup_monitor_binding);
                  } catch (final Exception e) {
                      // Ignore Exception on cleanup
                      if (!isCleanUp) {
                          throw e;
                      }
                  }
  
                  // Delete Monitor
                  // rm lb monitor lb_metric_table_mon
                  final com.citrix.netscaler.nitro.resource.config.lb.lbmonitor monitor = new com.citrix.netscaler.nitro.resource.config.lb.lbmonitor();
                  try {
                      monitor.set_monitorname(monitorName);
                      monitor.set_type("LOAD");
                      lbmonitor.delete(_netscalerService, monitor);
                  } catch (final Exception e) {
                      // Ignore Exception on cleanup
                      if (!isCleanUp) {
                          throw e;
                      }
                  }
  
                  // Delete Metric Table
                  final com.citrix.netscaler.nitro.resource.config.lb.lbmetrictable metricTable = new com.citrix.netscaler.nitro.resource.config.lb.lbmetrictable();
                  try {
                      metricTable.set_metrictable(mtName);
                      lbmetrictable.delete(_netscalerService, metricTable);
                  } catch (final Exception e) {
                      // Ignore Exception on cleanup
                      if (!isCleanUp) {
                          throw e;
                      }
                  }
              }
          } catch (final Exception ex) {
              if (!isCleanUp) {
                  // Normal course, exception has occurred
                  enableAutoScaleConfig(loadBalancerTO, true);
                  throw ex;
              } else {
                  // Programming error
                  throw ex;
              }
          }
  
          return true;
      }
  
      private synchronized void addAutoScalePolicy(final String timerName, final String policyName, final long priority, final String policyExpression, final String action, final int duration, final int interval,
              final boolean isCleanUp) throws Exception {
          // Adding a autoscale policy
          // add timer policy lb_policy_scaleUp_cpu_mem -rule - (SYS.CUR_VSERVER.METRIC_TABLE(cpu).AVG_VAL.GT(80)-
          // -action lb_scaleUpAction
          final autoscalepolicy timerPolicy = new autoscalepolicy();
          try {
              timerPolicy.set_name(policyName);
              timerPolicy.set_action(action);
              timerPolicy.set_rule(policyExpression);
              autoscalepolicy.add(_netscalerService, timerPolicy);
          } catch (final Exception e) {
              // Ignore Exception on cleanup
              if (!isCleanUp) {
                  throw e;
              }
          }
  
          // bind timer policy
          // For now it is bound globally.
          // bind timer trigger lb_astimer -policyName lb_policy_scaleUp -vserver lb -priority 1 -samplesize 5
          // TODO: later bind to lbvserver. bind timer trigger lb_astimer -policyName lb_policy_scaleUp -vserver lb
          // -priority 1 -samplesize 5
          // -thresholdsize 5
          final nstimer_autoscalepolicy_binding timer_policy_binding = new nstimer_autoscalepolicy_binding();
          final int sampleSize = duration / interval;
          try {
              timer_policy_binding.set_name(timerName);
              timer_policy_binding.set_policyname(policyName);
              timer_policy_binding.set_samplesize(sampleSize);
              timer_policy_binding.set_threshold(sampleSize); // We are not exposing this parameter as of now.
              // i.e. n(m) is not exposed to CS user. So thresholdSize == sampleSize
              timer_policy_binding.set_priority(priority);
              nstimer_autoscalepolicy_binding.add(_netscalerService, timer_policy_binding);
          } catch (final Exception e) {
              // Ignore Exception on cleanup
              if (!isCleanUp) {
                  throw e;
              }
          }
      }
  
      private void removeAutoScalePolicy(final String timerName, final String policyName, final boolean isCleanUp) throws Exception {
          // unbind timer policy
          // unbbind timer trigger lb_astimer -policyName lb_policy_scaleUp
          final nstimer_autoscalepolicy_binding timer_policy_binding = new nstimer_autoscalepolicy_binding();
          try {
              timer_policy_binding.set_name(timerName);
              timer_policy_binding.set_policyname(policyName);
              nstimer_autoscalepolicy_binding.delete(_netscalerService, timer_policy_binding);
          } catch (final Exception e) {
              // Ignore Exception on cleanup
              if (!isCleanUp) {
                  throw e;
              }
          }
  
          // Removing Timer policy
          // rm timer policy lb_policy_scaleUp_cpu_mem
          final autoscalepolicy timerPolicy = new autoscalepolicy();
          try {
              timerPolicy.set_name(policyName);
              autoscalepolicy.delete(_netscalerService, timerPolicy);
          } catch (final Exception e) {
              // Ignore Exception on cleanup
              if (!isCleanUp) {
                  throw e;
              }
          }
  
      }
  
      private boolean isAutoScaleSupportedInNetScaler() throws ExecutionException {
          new autoscaleprofile();
          try {
              autoscaleprofile.get(_netscalerService);
          } catch (final Exception ex) {
              // Looks like autoscale is not supported in this netscaler.
              // TODO: Config team has introduce a new command to check
              // the list of entities supported in a NetScaler. Can use that
              // once it is present in AutoScale branch.
              LOGGER.warn("AutoScale is not supported in NetScaler");
              return false;
          }
          return true;
      }
  
      private boolean isScaleUpPolicy(final AutoScalePolicyTO autoScalePolicyTO) {
          return autoScalePolicyTO.getAction().equals(AutoScalePolicy.Action.SCALEUP);
      }
  
      private boolean isScaleDownPolicy(final AutoScalePolicyTO autoScalePolicyTO) {
          return autoScalePolicyTO.getAction().equals(AutoScalePolicy.Action.SCALEDOWN);
      }
  
      private void saveConfiguration() throws ExecutionException {
          try {
              apiCallResult = nsconfig.save(_netscalerService);
              if (apiCallResult.errorcode != 0) {
                  throw new ExecutionException("Error occurred while saving configuration changes to Netscaler device due to " + apiCallResult.message);
              }
          } catch (final nitro_exception e) {
              throw new ExecutionException("Failed to save configuration changes to Netscaler device due to " + e.getMessage());
          } catch (final Exception e) {
              throw new ExecutionException("Failed to save configuration changes to Netscaler device due to " + e.getMessage());
          }
      }
  
      private ExternalNetworkResourceUsageAnswer getPublicIpBytesSentAndReceived(final ExternalNetworkResourceUsageCommand cmd) throws ExecutionException {
          final ExternalNetworkResourceUsageAnswer answer = new ExternalNetworkResourceUsageAnswer(cmd);
  
          try {
              final lbvserver_stats[] stats = lbvserver_stats.get(_netscalerService);
  
              if (stats == null || stats.length == 0) {
                  return answer;
              }
  
              for (final lbvserver_stats stat_entry : stats) {
                  final String lbvserverName = stat_entry.get_name();
                  final lbvserver vserver = lbvserver.get(_netscalerService, lbvserverName);
                  if (vserver != null) {
                      final String lbVirtualServerIp = vserver.get_ipv46();
  
                      long[] bytesSentAndReceived = answer.ipBytes.get(lbVirtualServerIp);
                      if (bytesSentAndReceived == null) {
                          bytesSentAndReceived = new long[] {0, 0};
                      }
                      bytesSentAndReceived[0] += stat_entry.get_totalrequestbytes();
                      bytesSentAndReceived[1] += stat_entry.get_totalresponsebytes();
  
                      if (bytesSentAndReceived[0] >= 0 && bytesSentAndReceived[1] >= 0) {
                          answer.ipBytes.put(lbVirtualServerIp, bytesSentAndReceived);
                      }
                  }
              }
          } catch (final Exception e) {
              LOGGER.error("Failed to get bytes sent and received statistics due to " + e);
              throw new ExecutionException(e.getMessage());
          }
  
          return answer;
      }
  
      private Answer retry(final Command cmd, final int numRetries) {
          final int numRetriesRemaining = numRetries - 1;
          LOGGER.warn("Retrying " + cmd.getClass().getSimpleName() + ". Number of retries remaining: " + numRetriesRemaining);
          return executeRequest(cmd, numRetriesRemaining);
      }
  
      private boolean shouldRetry(final int numRetries) {
          try {
              if (numRetries > 0) {
                  login();
                  return true;
              }
          } catch (final Exception e) {
              LOGGER.error("Failed to log in to Netscaler device at " + _ip + " due to " + e.getMessage());
          }
          return false;
      }
  
      private String generateInatRuleName(final String srcIp, final String dstIP) {
          return genObjectName("Cloud-Inat", srcIp);
      }
  
      private String generateRnatRuleName(final String srcIp, final String dstIP) {
          return genObjectName("Cloud-Rnat", srcIp);
      }
  
      private String generateNSVirtualServerName(final String srcIp, final long srcPort) {
          return genObjectName("Cloud-VirtualServer", srcIp, srcPort);
      }
  
      private String generateNSMonitorName(final String srcIp, final long srcPort) {
          // maximum length supported by NS is 31
          return genObjectName("Cloud-Hc", srcIp, srcPort);
      }
  
      private String generateNSServerName(final String serverIP) {
          return genObjectName("Cloud-Server-", serverIP);
      }
  
      private String generateNSServiceName(final String ip, final long port) {
          return genObjectName("Cloud-Service", ip, port);
      }
  
      private String generateAutoScaleVmGroupIdentifier(final LoadBalancerTO lbTO) {
          return lbTO.getSrcIp() + "-" + lbTO.getSrcPort();
      }
  
      private String generateAutoScaleServiceGroupName(final LoadBalancerTO lbTO) {
          /*
           *  ServiceGroup name in NetScaler won't support long names. Providing special name.
           *  Need for introducing uuid because every vmgroup creation should be distinguished.
           *  Ex. (1) create a vm group, delete a vmgroup, create a vmgroup on same lb ip and port
           *  This will reuse all vms from the original vm group in step (1)
           */
  
          return "Cloud" + lbTO.getAutoScaleVmGroupTO().getUuid().replace("-", "");
      }
  
      private String generateAutoScaleTimerName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-AutoScale-Timer", vmGroupIdentifier);
      }
  
      private String generateAutoScaleProfileName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-AutoScale-Profile", vmGroupIdentifier);
      }
  
      private String generateAutoScaleScaleUpActionName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-AutoScale-ScaleUpAction", vmGroupIdentifier);
      }
  
      private String generateAutoScaleScaleDownActionName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-AutoScale-ScaleDownAction", vmGroupIdentifier);
      }
  
      private String generateAutoScalePolicyName(final String vmGroupIdentifier, final String poilcyId) {
          return genObjectName("Cloud-AutoScale-Policy", vmGroupIdentifier, poilcyId);
      }
  
      private String generateAutoScaleMinPolicyName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-AutoScale-Policy-Min", vmGroupIdentifier);
      }
  
      private String generateAutoScaleMaxPolicyName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-AutoScale-Policy-Max", vmGroupIdentifier);
      }
  
      private String generateSnmpMetricTableName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-MTbl", vmGroupIdentifier);
      }
  
      private String generateSnmpMonitorName(final String vmGroupIdentifier) {
          return genObjectName("Cloud-Mon", vmGroupIdentifier);
      }
  
      private String generateSnmpMetricName(final String counterName) {
          return counterName.replace(' ', '_');
      }
  
      private String generateSslCertName(final String fingerPrint) {
          // maximum length supported by NS is 31
          // the first 20 characters of the SHA-1 checksum are the unique id
          final String uniqueId = fingerPrint.replace(":", "").substring(0, 20);
  
          return genObjectName("Cloud-Cert", uniqueId);
      }
  
      private String generateSslKeyName(final String fingerPrint) {
          final String uniqueId = fingerPrint.replace(":", "").substring(0, 20);
          return genObjectName("Cloud-Key", uniqueId);
      }
  
      private String generateSslCertKeyName(final String fingerPrint) {
          final String uniqueId = fingerPrint.replace(":", "").substring(0, 20);
          return genObjectName("Cloud-Cert", uniqueId);
      }
  
      private String genObjectName(final Object... args) {
          final StringBuffer buff = new StringBuffer();
          for (int i = 0; i < args.length; i++) {
              buff.append(args[i]);
              if (i != args.length - 1) {
                  buff.append(_objectNamePathSep);
              }
          }
          return buff.toString();
      }
  
      @Override
      public IAgentControl getAgentControl() {
          return null;
      }
  
      @Override
      public PingCommand getCurrentStatus(final long id) {
          return new PingCommand(Host.Type.ExternalLoadBalancer, id);
      }
  
      @Override
      public Type getType() {
          return Host.Type.ExternalLoadBalancer;
      }
  
      @Override
      public void setAgentControl(final IAgentControl agentControl) {
          return;
      }
  
      @Override
      public String getName() {
          return _name;
      }
  
      @Override
      public boolean start() {
          return true;
      }
  
      @Override
      public boolean stop() {
          return true;
      }
  
      @Override
      public void disconnected() {
          return;
      }
  
      @Override
      public void setName(final String name) {
          // TODO Auto-generated method stub
  
      }
  
      @Override
      public void setConfigParams(final Map<String, Object> params) {
          // TODO Auto-generated method stub
  
      }
  
      @Override
      public Map<String, Object> getConfigParams() {
          // TODO Auto-generated method stub
          return null;
      }
  
      @Override
      public int getRunLevel() {
          // TODO Auto-generated method stub
          return 0;
      }
  
      @Override
      public void setRunLevel(final int level) {
          // TODO Auto-generated method stub
  
      }
  }
  