// 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 iControl.CommonEnabledState;
import iControl.CommonIPPortDefinition;
import iControl.CommonStatistic;
import iControl.CommonStatisticType;
import iControl.CommonVirtualServerDefinition;
import iControl.Interfaces;
import iControl.LocalLBLBMethod;
import iControl.LocalLBNodeAddressBindingStub;
import iControl.LocalLBPersistenceMode;
import iControl.LocalLBPoolBindingStub;
import iControl.LocalLBProfileContextType;
import iControl.LocalLBProfilePersistenceBindingStub;
import iControl.LocalLBProfileULong;
import iControl.LocalLBVirtualServerBindingStub;
import iControl.LocalLBVirtualServerVirtualServerPersistence;
import iControl.LocalLBVirtualServerVirtualServerProfile;
import iControl.LocalLBVirtualServerVirtualServerResource;
import iControl.LocalLBVirtualServerVirtualServerStatisticEntry;
import iControl.LocalLBVirtualServerVirtualServerStatistics;
import iControl.LocalLBVirtualServerVirtualServerType;
import iControl.NetworkingMemberTagType;
import iControl.NetworkingMemberType;
import iControl.NetworkingRouteDomainBindingStub;
import iControl.NetworkingSelfIPBindingStub;
import iControl.NetworkingVLANBindingStub;
import iControl.NetworkingVLANMemberEntry;
import iControl.SystemConfigSyncBindingStub;
import iControl.SystemConfigSyncSaveMode;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.naming.ConfigurationException;

import org.apache.log4j.Logger;

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.IpAssocAnswer;
import com.cloud.agent.api.routing.IpAssocCommand;
import com.cloud.agent.api.routing.LoadBalancerConfigCommand;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.agent.api.to.IpAddressTO;
import com.cloud.agent.api.to.LoadBalancerTO;
import com.cloud.agent.api.to.LoadBalancerTO.DestinationTO;
import com.cloud.agent.api.to.LoadBalancerTO.StickinessPolicyTO;
import com.cloud.host.Host;
import com.cloud.network.rules.LbStickinessMethod.StickinessMethodType;
import com.cloud.resource.ServerResource;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.ExecutionException;
import com.cloud.utils.net.NetUtils;

public class F5BigIpResource implements ServerResource {

    private enum LbAlgorithm {
        RoundRobin(null, LocalLBLBMethod.LB_METHOD_ROUND_ROBIN), LeastConn(null, LocalLBLBMethod.LB_METHOD_LEAST_CONNECTION_MEMBER);

        String persistenceProfileName;
        LocalLBLBMethod method;

        LbAlgorithm(String persistenceProfileName, LocalLBLBMethod method) {
            this.persistenceProfileName = persistenceProfileName;
            this.method = method;
        }

        public String getPersistenceProfileName() {
            return persistenceProfileName;
        }

        public LocalLBLBMethod getMethod() {
            return method;
        }
    }

    private enum LbProtocol {
        tcp, udp;
    }

    private String _name;
    private String _zoneId;
    private String _ip;
    private String _username;
    private String _password;
    private String _publicInterface;
    private String _privateInterface;
    private Integer _numRetries;
    private String _guid;

    private Interfaces _interfaces;
    private LocalLBVirtualServerBindingStub _virtualServerApi;
    private LocalLBPoolBindingStub _loadbalancerApi;
    private LocalLBNodeAddressBindingStub _nodeApi;
    private NetworkingVLANBindingStub _vlanApi;
    private NetworkingSelfIPBindingStub _selfIpApi;
    private NetworkingRouteDomainBindingStub _routeDomainApi;
    private SystemConfigSyncBindingStub _configSyncApi;
    private LocalLBProfilePersistenceBindingStub _persistenceProfileApi;
    private String _objectNamePathSep = "-";
    private String _routeDomainIdentifier = "%";

    private static final Logger s_logger = Logger.getLogger(F5BigIpResource.class);

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        try {
            _name = (String)params.get("name");
            if (_name == null) {
                throw new ConfigurationException("Unable to find name");
            }

            _zoneId = (String)params.get("zoneId");
            if (_zoneId == null) {
                throw new ConfigurationException("Unable to find zone");
            }

            _ip = (String)params.get("ip");
            if (_ip == null) {
                throw new ConfigurationException("Unable to find IP");
            }

            _username = (String)params.get("username");
            if (_username == null) {
                throw new ConfigurationException("Unable to find username");
            }

            _password = (String)params.get("password");
            if (_password == null) {
                throw new ConfigurationException("Unable to find password");
            }

            _publicInterface = (String)params.get("publicinterface");
            if (_publicInterface == null) {
                throw new ConfigurationException("Unable to find public interface");
            }

            _privateInterface = (String)params.get("privateinterface");
            if (_privateInterface == null) {
                throw new ConfigurationException("Unable to find private interface");
            }

            _numRetries = NumbersUtil.parseInt((String)params.get("numretries"), 1);

            _guid = (String)params.get("guid");
            if (_guid == null) {
                throw new ConfigurationException("Unable to find the guid");
            }

            login();

            return true;
        } catch (Exception e) {
            throw new ConfigurationException(e.getMessage());
        }

    }

    @Override
    public StartupCommand[] initialize() {
        StartupExternalLoadBalancerCommand cmd = new StartupExternalLoadBalancerCommand();
        cmd.setName(_name);
        cmd.setDataCenter(_zoneId);
        cmd.setPod("");
        cmd.setPrivateIpAddress(_ip);
        cmd.setStorageIpAddress("");
        cmd.setVersion(F5BigIpResource.class.getPackage().getImplementationVersion());
        cmd.setGuid(_guid);
        return new StartupCommand[] {cmd};
    }

    @Override
    public Host.Type getType() {
        return Host.Type.ExternalLoadBalancer;
    }

    @Override
    public String getName() {
        return _name;
    }

    @Override
    public PingCommand getCurrentStatus(final long id) {
        return new PingCommand(Host.Type.ExternalLoadBalancer, id);
    }

    @Override
    public boolean start() {
        return true;
    }

    @Override
    public boolean stop() {
        return true;
    }

    @Override
    public void disconnected() {
        return;
    }

    @Override
    public IAgentControl getAgentControl() {
        return null;
    }

    @Override
    public void setAgentControl(IAgentControl agentControl) {
        return;
    }

    @Override
    public Answer executeRequest(Command cmd) {
        return executeRequest(cmd, _numRetries);
    }

    private Answer executeRequest(Command cmd, 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);
        } else {
            return Answer.createUnsupportedCommandAnswer(cmd);
        }
    }

    private Answer retry(Command cmd, int numRetries) {
        int numRetriesRemaining = numRetries - 1;
        s_logger.error("Retrying " + cmd.getClass().getSimpleName() + ". Number of retries remaining: " + numRetriesRemaining);
        return executeRequest(cmd, numRetriesRemaining);
    }

    private boolean shouldRetry(int numRetries) {
        try {
            if (numRetries > 0) {
                login();
                return true;
            }
        } catch (Exception e) {
            s_logger.error("Failed to log in to F5 device at " + _ip + " due to " + e.getMessage());
        }
        return false;
    }

    private Answer execute(ReadyCommand cmd) {
        return new ReadyAnswer(cmd);
    }

    private Answer execute(MaintainCommand cmd) {
        return new MaintainAnswer(cmd);
    }

    private synchronized Answer execute(IpAssocCommand cmd, int numRetries) {
        String[] results = new String[cmd.getIpAddresses().length];
        int i = 0;
        try {
            IpAddressTO[] ips = cmd.getIpAddresses();
            for (IpAddressTO ip : ips) {
                // is it saver to use Long.valueOf(BroadcastDomain.getValue(ip.getBroadcastUri())) ???
                // i.o.w. can this contain vlan:// then change !!!
                long guestVlanTag = Long.parseLong(ip.getBroadcastUri());
                // It's a hack, using isOneToOneNat field for indicate if it's inline or not
                boolean inline = ip.isOneToOneNat();
                String vlanSelfIp = inline ? tagAddressWithRouteDomain(ip.getVlanGateway(), guestVlanTag) : ip.getVlanGateway();
                String vlanNetmask = ip.getVlanNetmask();

                // Delete any existing guest VLAN with this tag, self IP, and netmask
                deleteGuestVlan(guestVlanTag, vlanSelfIp, vlanNetmask, inline);

                if (ip.isAdd()) {
                    // Add a new guest VLAN
                    addGuestVlan(guestVlanTag, vlanSelfIp, vlanNetmask, inline);
                }

                saveConfiguration();
                results[i++] = ip.getPublicIp() + " - success";
            }

        } catch (ExecutionException e) {
            s_logger.error("Failed to execute IPAssocCommand due to " + e);

            if (shouldRetry(numRetries)) {
                return retry(cmd, numRetries);
            } else {
                results[i++] = IpAssocAnswer.errorResult;
            }
        }

        return new IpAssocAnswer(cmd, results);
    }

    private synchronized Answer execute(LoadBalancerConfigCommand cmd, int numRetries) {
        try {
            long guestVlanTag = Long.parseLong(cmd.getAccessDetail(NetworkElementCommand.GUEST_VLAN_TAG));
            LoadBalancerTO[] loadBalancers = cmd.getLoadBalancers();
            for (LoadBalancerTO loadBalancer : loadBalancers) {
                boolean inline = loadBalancer.isInline();
                LbProtocol lbProtocol;
                try {
                    if (loadBalancer.getProtocol() == null) {
                        lbProtocol = LbProtocol.tcp;
                    } else {
                        lbProtocol = LbProtocol.valueOf(loadBalancer.getProtocol());
                    }
                } catch (IllegalArgumentException e) {
                    throw new ExecutionException("Got invalid protocol: " + loadBalancer.getProtocol());
                }

                LbAlgorithm lbAlgorithm;
                if (loadBalancer.getAlgorithm().equals("roundrobin")) {
                    lbAlgorithm = LbAlgorithm.RoundRobin;
                } else if (loadBalancer.getAlgorithm().equals("leastconn")) {
                    lbAlgorithm = LbAlgorithm.LeastConn;
                } else {
                    throw new ExecutionException("Got invalid algorithm: " + loadBalancer.getAlgorithm());
                }

                String srcIp = inline ? tagAddressWithRouteDomain(loadBalancer.getSrcIp(), guestVlanTag) : loadBalancer.getSrcIp();
                int srcPort = loadBalancer.getSrcPort();
                String virtualServerName = genVirtualServerName(lbProtocol, srcIp, srcPort);

                boolean destinationsToAdd = false;
                for (DestinationTO destination : loadBalancer.getDestinations()) {
                    if (!destination.isRevoked()) {
                        destinationsToAdd = true;
                        break;
                    }
                }

                // Delete the virtual server with this protocol, source IP, and source port, along with its default pool and all pool members
                deleteVirtualServerAndDefaultPool(virtualServerName);
                if (!loadBalancer.isRevoked() && destinationsToAdd) {
                    // Add the pool
                    addPool(virtualServerName, lbAlgorithm);

                    // Add pool members
                    List<String> activePoolMembers = new ArrayList<String>();
                    for (DestinationTO destination : loadBalancer.getDestinations()) {
                        if (!destination.isRevoked()) {
                            String destIp = inline ? tagAddressWithRouteDomain(destination.getDestIp(), guestVlanTag) : destination.getDestIp();
                            addPoolMember(virtualServerName, destIp, destination.getDestPort());
                            activePoolMembers.add(destIp + "-" + destination.getDestPort());
                        }
                    }

                    // Add the virtual server
                    addVirtualServer(virtualServerName, lbProtocol, srcIp, srcPort, loadBalancer.getStickinessPolicies());
                }
            }

            saveConfiguration();
            return new Answer(cmd);
        } catch (ExecutionException e) {
            s_logger.error("Failed to execute LoadBalancerConfigCommand due to " + e);

            if (shouldRetry(numRetries)) {
                return retry(cmd, numRetries);
            } else {
                return new Answer(cmd, e);
            }

        }
    }

    private synchronized ExternalNetworkResourceUsageAnswer execute(ExternalNetworkResourceUsageCommand cmd) {
        try {
            return getIpBytesSentAndReceived(cmd);
        } catch (ExecutionException e) {
            return new ExternalNetworkResourceUsageAnswer(cmd, e);
        }
    }

    private void saveConfiguration() throws ExecutionException {
        try {
            _configSyncApi.save_configuration("", SystemConfigSyncSaveMode.SAVE_BASE_LEVEL_CONFIG);
            _configSyncApi.save_configuration("", SystemConfigSyncSaveMode.SAVE_HIGH_LEVEL_CONFIG);
            s_logger.debug("Successfully saved F5 BigIp configuration.");
        } catch (RemoteException e) {
            s_logger.error("Failed to save F5 BigIp configuration due to: " + e);
            throw new ExecutionException(e.getMessage());
        }
    }

    private void addGuestVlan(long vlanTag, String vlanSelfIp, String vlanNetmask, boolean inline) throws ExecutionException {
        try {
            String vlanName = genVlanName(vlanTag);
            List<String> allVlans = getStrippedVlans();
            if (!allVlans.contains(vlanName)) {
                String[] vlanNames = genStringArray(vlanName);
                long[] vlanTags = genLongArray(vlanTag);
                CommonEnabledState[] commonEnabledState = {CommonEnabledState.STATE_DISABLED};

                // Create the interface name
                NetworkingVLANMemberEntry[][] vlanMemberEntries = {{new NetworkingVLANMemberEntry()}};
                vlanMemberEntries[0][0].setMember_type(NetworkingMemberType.MEMBER_INTERFACE);
                vlanMemberEntries[0][0].setTag_state(NetworkingMemberTagType.MEMBER_TAGGED);
                vlanMemberEntries[0][0].setMember_name(_privateInterface);

                s_logger.debug("Creating a guest VLAN with tag " + vlanTag);
                _vlanApi.create(vlanNames, vlanTags, vlanMemberEntries, commonEnabledState, new long[] {10L}, new String[] {"00:00:00:00:00:00"});
                s_logger.debug("vlanName " + vlanName);
                s_logger.debug("getStrippedVlans " + getStrippedVlans());

                if (!getStrippedVlans().contains(vlanName)) {
                    throw new ExecutionException("Failed to create vlan with tag " + vlanTag);
                }
            }

            if (inline) {
                List<Long> allRouteDomains = getRouteDomains();
                if (!allRouteDomains.contains(vlanTag)) {
                    long[] routeDomainIds = genLongArray(vlanTag);
                    String[][] vlanNames = new String[][] {genStringArray(genVlanName(vlanTag))};

                    s_logger.debug("Creating route domain " + vlanTag);
                    _routeDomainApi.create(routeDomainIds, vlanNames);

                    if (!getRouteDomains().contains(vlanTag)) {
                        throw new ExecutionException("Failed to create route domain " + vlanTag);
                    }
                }
            }

            List<String> allSelfIps = getSelfIps();
            if (!allSelfIps.contains(vlanSelfIp)) {
                String[] selfIpsToCreate = genStringArray(vlanSelfIp);
                String[] vlans = genStringArray(vlanName);
                String[] netmasks = genStringArray(vlanNetmask);
                long[] unitIds = genLongArray(0L);
                CommonEnabledState[] enabledStates = new CommonEnabledState[] {CommonEnabledState.STATE_DISABLED};

                s_logger.debug("Creating self IP " + vlanSelfIp);
                _selfIpApi.create(selfIpsToCreate, vlans, netmasks, unitIds, enabledStates);

                if (!getSelfIps().contains(vlanSelfIp)) {
                    throw new ExecutionException("Failed to create self IP " + vlanSelfIp);
                }
            }
        } catch (RemoteException e) {
            s_logger.error(e);
            throw new ExecutionException(e.getMessage());
        }

    }

    private void deleteGuestVlan(long vlanTag, String vlanSelfIp, String vlanNetmask, boolean inline) throws ExecutionException {
        try {
            // Delete all virtual servers and pools that use this guest VLAN
            deleteVirtualServersInGuestVlan(vlanSelfIp, vlanNetmask);

            List<String> allSelfIps = getSelfIps();
            if (allSelfIps.contains(vlanSelfIp)) {
                s_logger.debug("Deleting self IP " + vlanSelfIp);
                _selfIpApi.delete_self_ip(genStringArray(vlanSelfIp));

                if (getSelfIps().contains(vlanSelfIp)) {
                    throw new ExecutionException("Failed to delete self IP " + vlanSelfIp);
                }
            }

            if (inline) {
                List<Long> allRouteDomains = getRouteDomains();
                if (allRouteDomains.contains(vlanTag)) {
                    s_logger.debug("Deleting route domain " + vlanTag);
                    _routeDomainApi.delete_route_domain(genLongArray(vlanTag));

                    if (getRouteDomains().contains(vlanTag)) {
                        throw new ExecutionException("Failed to delete route domain " + vlanTag);
                    }
                }
            }

            String vlanName = genVlanName(vlanTag);
            List<String> allVlans = getStrippedVlans();
            if (allVlans.contains(vlanName)) {
                _vlanApi.delete_vlan(genStringArray(vlanName));

                if (getVlans().contains(vlanName)) {
                    throw new ExecutionException("Failed to delete VLAN with tag: " + vlanTag);
                }
            }
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private void deleteVirtualServersInGuestVlan(String vlanSelfIp, String vlanNetmask) throws ExecutionException {
        vlanSelfIp = stripRouteDomainFromAddress(vlanSelfIp);
        List<String> virtualServersToDelete = new ArrayList<String>();

        List<String> allVirtualServers = getStrippedVirtualServers();
        for (String virtualServerName : allVirtualServers) {
            // Check if the virtual server's default pool has members in this guest VLAN
            List<String> poolMembers = getMembers(virtualServerName);
            for (String poolMemberName : poolMembers) {
                String poolMemberIp = stripRouteDomainFromAddress(getIpAndPort(poolMemberName)[0]);
                if (NetUtils.sameSubnet(vlanSelfIp, poolMemberIp, vlanNetmask)) {
                    virtualServersToDelete.add(virtualServerName);
                    break;
                }
            }
        }

        for (String virtualServerName : virtualServersToDelete) {
            s_logger.debug("Found a virtual server (" + virtualServerName + ") for guest network with self IP " + vlanSelfIp +
                " that is active when the guest network is being destroyed.");
            deleteVirtualServerAndDefaultPool(virtualServerName);
        }
    }

    private String genVlanName(long vlanTag) {
        return "vlan-" + String.valueOf(vlanTag);
    }

    private List<Long> getRouteDomains() throws ExecutionException {
        try {
            List<Long> routeDomains = new ArrayList<Long>();
            long[] routeDomainsArray = _routeDomainApi.get_list();

            for (long routeDomainName : routeDomainsArray) {
                routeDomains.add(routeDomainName);
            }

            return routeDomains;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private List<String> getSelfIps() throws ExecutionException {
        try {
            List<String> selfIps = new ArrayList<String>();
            String[] selfIpsArray = _selfIpApi.get_list();

            for (String selfIp : selfIpsArray) {
                selfIps.add(selfIp);
            }

            return selfIps;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    //This was working with Big IP 10.x
    //getVlans retuns vlans with user partition information
    //ex: if vlanname is vlan-100 then the get_list() will return /Common/vlan-100
    private List<String> getVlans() throws ExecutionException {
        try {
            List<String> vlans = new ArrayList<String>();
            String[] vlansArray = _vlanApi.get_list();

            for (String vlan : vlansArray) {
                vlans.add(vlan);
            }

            return vlans;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    //getVlans retuns vlan names without user partition information
    //ex: if vlanname is vlan-100 then the get_list() will return /Common/vlan-100
    // This method will strip the partition information and only returns a list with vlan name (vlan-100)
    private List<String> getStrippedVlans() throws ExecutionException {
        try {
            List<String> vlans = new ArrayList<String>();
            String[] vlansArray = _vlanApi.get_list();

            for (String vlan : vlansArray) {
                if(vlan.contains("/")){
                    vlans.add(vlan.substring(vlan.lastIndexOf("/") + 1));
                }else{
                    vlans.add(vlan);
                }
            }

            return vlans;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }
    // Login

    private void login() throws ExecutionException {
        try {
            _interfaces = new Interfaces();

            if (!_interfaces.initialize(_ip, _username, _password)) {
                throw new ExecutionException("Failed to log in to BigIp appliance");
            }

            // iControl.Interfaces.initialize always return true so make a call to force connect to F5 to validate credentials
            _interfaces.getSystemSystemInfo().get_system_information();

            _virtualServerApi = _interfaces.getLocalLBVirtualServer();
            _loadbalancerApi = _interfaces.getLocalLBPool();
            _nodeApi = _interfaces.getLocalLBNodeAddress();
            _vlanApi = _interfaces.getNetworkingVLAN();
            _selfIpApi = _interfaces.getNetworkingSelfIP();
            _routeDomainApi = _interfaces.getNetworkingRouteDomain();
            _configSyncApi = _interfaces.getSystemConfigSync();
            _persistenceProfileApi = _interfaces.getLocalLBProfilePersistence();
        } catch (Exception e) {
            throw new ExecutionException("Failed to log in to BigIp appliance due to " + e.getMessage());
        }
    }

    // Virtual server methods

    private void addVirtualServer(String virtualServerName, LbProtocol protocol, String srcIp, int srcPort, StickinessPolicyTO[] stickyPolicies)
        throws ExecutionException {
        try {
            if (!virtualServerExists(virtualServerName)) {
                s_logger.debug("Adding virtual server " + virtualServerName);
                _virtualServerApi.create(genVirtualServerDefinition(virtualServerName, protocol, srcIp, srcPort), new String[] {"255.255.255.255"},
                    genVirtualServerResource(virtualServerName), genVirtualServerProfile(protocol));
                _virtualServerApi.set_snat_automap(genStringArray(virtualServerName));
                if (!virtualServerExists(virtualServerName)) {
                    throw new ExecutionException("Failed to add virtual server " + virtualServerName);
                }
            }

            if ((stickyPolicies != null) && (stickyPolicies.length > 0) && (stickyPolicies[0] != null)) {
                StickinessPolicyTO stickinessPolicy = stickyPolicies[0];
                if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) {

                    String[] profileNames = genStringArray("Cookie-profile-" + virtualServerName);
                    if (!persistenceProfileExists(profileNames[0])) {
                        LocalLBPersistenceMode[] lbPersistenceMode = new iControl.LocalLBPersistenceMode[1];
                        lbPersistenceMode[0] = iControl.LocalLBPersistenceMode.PERSISTENCE_MODE_COOKIE;
                        _persistenceProfileApi.create(profileNames, lbPersistenceMode);
                        _virtualServerApi.add_persistence_profile(genStringArray(virtualServerName), genPersistenceProfile(profileNames[0]));
                    }

                    List<Pair<String, String>> paramsList = stickinessPolicy.getParams();
                    for (Pair<String, String> param : paramsList) {
                        if ("holdtime".equalsIgnoreCase(param.first())) {
                            long timeout = 180; //F5 default
                            if (param.second() != null) {
                                timeout = Long.parseLong(param.second());
                            }
                            LocalLBProfileULong[] cookieTimeout = new LocalLBProfileULong[1];
                            cookieTimeout[0] = new LocalLBProfileULong();
                            cookieTimeout[0].setValue(timeout);
                            _persistenceProfileApi.set_cookie_expiration(profileNames, cookieTimeout);
                        }
                    }
                }
            } else {
                _virtualServerApi.remove_all_persistence_profiles(genStringArray(virtualServerName));
            }

        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private void deleteVirtualServerAndDefaultPool(String virtualServerName) throws ExecutionException {
        try {
            if (virtualServerExists(virtualServerName)) {
                // Delete the default pool's members
                List<String> poolMembers = getMembers(virtualServerName);
                for (String poolMember : poolMembers) {
                    String[] destIpAndPort = getIpAndPort(poolMember);
                    deletePoolMember(virtualServerName, destIpAndPort[0], Integer.parseInt(destIpAndPort[1]));
                }

                // Delete the virtual server
                s_logger.debug("Deleting virtual server " + virtualServerName);
                _virtualServerApi.delete_virtual_server(genStringArray(virtualServerName));

                if (getStrippedVirtualServers().contains(virtualServerName)) {
                    throw new ExecutionException("Failed to delete virtual server " + virtualServerName);
                }

                // Delete the default pool
                deletePool(virtualServerName);
            }
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private String genVirtualServerName(LbProtocol protocol, String srcIp, long srcPort) {
        srcIp = stripRouteDomainFromAddress(srcIp);
        return genObjectName("vs", protocol, srcIp, srcPort);
    }

    private boolean virtualServerExists(String virtualServerName) throws ExecutionException {
        return getStrippedVirtualServers().contains(virtualServerName);
    }

    //This was working with Big IP 10.x
    //getVirtualServers retuns VirtualServers with user partition information
    //ex: if VirtualServers is vs-tcp-10.147.44.8-22 then the get_list() will return /Common/vs-tcp-10.147.44.8-22
    private List<String> getVirtualServers() throws ExecutionException {
        try {
            List<String> virtualServers = new ArrayList<String>();
            String[] virtualServersArray = _virtualServerApi.get_list();

            for (String virtualServer : virtualServersArray) {
                virtualServers.add(virtualServer);
            }

            return virtualServers;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

/*    getStrippedVirtualServers retuns VirtualServers without user partition information
    ex: if VirtualServers is vs-tcp-10.147.44.8-22 then the get_list() will return /Common/vs-tcp-10.147.44.8-22
    This method will strip the partition information and only returns a list with VirtualServers (vs-tcp-10.147.44.8-22)*/
    private List<String> getStrippedVirtualServers() throws ExecutionException {
        try {
            List<String> virtualServers = new ArrayList<String>();
            String[] virtualServersArray = _virtualServerApi.get_list();

            for (String virtualServer : virtualServersArray) {
                if(virtualServer.contains("/")){
                    virtualServers.add(virtualServer.substring(virtualServer.lastIndexOf("/") + 1));
                }else{
                    virtualServers.add(virtualServer);
                }
            }

            return virtualServers;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private boolean persistenceProfileExists(String profileName) throws ExecutionException {
        try {
            String[] persistenceProfileArray = _persistenceProfileApi.get_list();
            if (persistenceProfileArray == null) {
                return false;
            }
            for (String profile : persistenceProfileArray) {
                if (profile.equalsIgnoreCase(profileName)) {
                    return true;
                }
            }
            return false;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private iControl.CommonVirtualServerDefinition[] genVirtualServerDefinition(String name, LbProtocol protocol, String srcIp, long srcPort) {
        CommonVirtualServerDefinition vsDefs[] = {new CommonVirtualServerDefinition()};
        vsDefs[0].setName(name);
        vsDefs[0].setAddress(srcIp);
        vsDefs[0].setPort(srcPort);

        if (protocol.equals(LbProtocol.tcp)) {
            vsDefs[0].setProtocol(iControl.CommonProtocolType.PROTOCOL_TCP);
        } else if (protocol.equals(LbProtocol.udp)) {
            vsDefs[0].setProtocol(iControl.CommonProtocolType.PROTOCOL_UDP);
        }

        return vsDefs;
    }

    private iControl.LocalLBVirtualServerVirtualServerResource[] genVirtualServerResource(String poolName) {
        LocalLBVirtualServerVirtualServerResource vsRes[] = {new LocalLBVirtualServerVirtualServerResource()};
        vsRes[0].setType(LocalLBVirtualServerVirtualServerType.RESOURCE_TYPE_POOL);
        vsRes[0].setDefault_pool_name(poolName);
        return vsRes;
    }

    private LocalLBVirtualServerVirtualServerProfile[][] genVirtualServerProfile(LbProtocol protocol) {
        LocalLBVirtualServerVirtualServerProfile vsProfs[][] = {{new LocalLBVirtualServerVirtualServerProfile()}};
        vsProfs[0][0].setProfile_context(LocalLBProfileContextType.PROFILE_CONTEXT_TYPE_ALL);

        if (protocol.equals(LbProtocol.tcp)) {
            vsProfs[0][0].setProfile_name("http");
        } else if (protocol.equals(LbProtocol.udp)) {
            vsProfs[0][0].setProfile_name("udp");
        }

        return vsProfs;
    }

    private LocalLBVirtualServerVirtualServerPersistence[][] genPersistenceProfile(String persistenceProfileName) {
        LocalLBVirtualServerVirtualServerPersistence[][] persistenceProfs = {{new LocalLBVirtualServerVirtualServerPersistence()}};
        persistenceProfs[0][0].setDefault_profile(true);
        persistenceProfs[0][0].setProfile_name(persistenceProfileName);
        return persistenceProfs;
    }

    // Load balancing pool methods

    private void addPool(String virtualServerName, LbAlgorithm algorithm) throws ExecutionException {
        try {
            if (!poolExists(virtualServerName)) {
                if (algorithm.getPersistenceProfileName() != null) {
                    algorithm = LbAlgorithm.RoundRobin;
                }

                s_logger.debug("Adding pool for virtual server " + virtualServerName + " with algorithm " + algorithm);
                _loadbalancerApi.create(genStringArray(virtualServerName), genLbMethod(algorithm), genEmptyMembersArray());

                if (!poolExists(virtualServerName)) {
                    throw new ExecutionException("Failed to create new pool for virtual server " + virtualServerName);
                }
            }
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private void deletePool(String virtualServerName) throws ExecutionException {
        try {
            if (poolExists(virtualServerName) && getMembers(virtualServerName).size() == 0) {
                s_logger.debug("Deleting pool for virtual server " + virtualServerName);
                _loadbalancerApi.delete_pool(genStringArray(virtualServerName));

                if (poolExists(virtualServerName)) {
                    throw new ExecutionException("Failed to delete pool for virtual server " + virtualServerName);
                }
            }
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private void addPoolMember(String virtualServerName, String destIp, int destPort) throws ExecutionException {
        try {
            String memberIdentifier = destIp + "-" + destPort;

            if (poolExists(virtualServerName) && !memberExists(virtualServerName, memberIdentifier)) {
                s_logger.debug("Adding member " + memberIdentifier + " into pool for virtual server " + virtualServerName);
                _loadbalancerApi.add_member(genStringArray(virtualServerName), genMembers(destIp, destPort));

                if (!memberExists(virtualServerName, memberIdentifier)) {
                    throw new ExecutionException("Failed to add new member " + memberIdentifier + " into pool for virtual server " + virtualServerName);
                }
            }
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private void deleteInactivePoolMembers(String virtualServerName, List<String> activePoolMembers) throws ExecutionException {
        List<String> allPoolMembers = getMembers(virtualServerName);

        for (String member : allPoolMembers) {
            if (!activePoolMembers.contains(member)) {
                String[] ipAndPort = member.split("-");
                deletePoolMember(virtualServerName, ipAndPort[0], Integer.parseInt(ipAndPort[1]));
            }
        }
    }

    private void deletePoolMember(String virtualServerName, String destIp, int destPort) throws ExecutionException {
        try {
            String memberIdentifier = destIp + "-" + destPort;
            List<String> lbPools = getAllStrippedLbPools();

            if (lbPools.contains(virtualServerName) && memberExists(virtualServerName, memberIdentifier)) {
                s_logger.debug("Deleting member " + memberIdentifier + " from pool for virtual server " + virtualServerName);
                _loadbalancerApi.remove_member(genStringArray(virtualServerName), genMembers(destIp, destPort));

                if (memberExists(virtualServerName, memberIdentifier)) {
                    throw new ExecutionException("Failed to delete member " + memberIdentifier + " from pool for virtual server " + virtualServerName);
                }

                if (nodeExists(destIp)) {
                    boolean nodeNeeded = false;
                    done: for (String poolToCheck : lbPools) {
                        for (String memberInPool : getMembers(poolToCheck)) {
                            if (getIpAndPort(memberInPool)[0].equals(destIp)) {
                                nodeNeeded = true;
                                break done;
                            }
                        }
                    }

                    if (!nodeNeeded) {
                        s_logger.debug("Deleting node " + destIp);
                        _nodeApi.delete_node_address(genStringArray(destIp));

                        if (nodeExists(destIp)) {
                            throw new ExecutionException("Failed to delete node " + destIp);
                        }
                    }
                }
            }
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private boolean poolExists(String poolName) throws ExecutionException {
        return getAllStrippedLbPools().contains(poolName);
    }

    private boolean memberExists(String poolName, String memberIdentifier) throws ExecutionException {
        return getMembers(poolName).contains(memberIdentifier);
    }

    private boolean nodeExists(String destIp) throws RemoteException {
        return getNodes().contains(destIp);
    }

    private String[] getIpAndPort(String memberIdentifier) {
        return memberIdentifier.split("-");
    }

    //This was working with Big IP 10.x
    //getAllLbPools retuns LbPools with user partition information
    //ex: if LbPools is vs-tcp-10.147.44.8-22 then the get_list() will return /Common/vs-tcp-10.147.44.8-22
    public List<String> getAllLbPools() throws ExecutionException {
        try {
            List<String> lbPools = new ArrayList<String>();
            String[] pools = _loadbalancerApi.get_list();

            for (String pool : pools) {
                lbPools.add(pool);
            }

            return lbPools;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    //Big IP 11.x
    //getAllLbPools retuns LbPools without user partition information
    //ex: if LbPools is vs-tcp-10.147.44.8-22 then the get_list() will return /Common/vs-tcp-10.147.44.8-22
    //This method will strip the partition information and only returns a list with LbPools (vs-tcp-10.147.44.8-22)
    public List<String> getAllStrippedLbPools() throws ExecutionException {
        try {
            List<String> lbPools = new ArrayList<String>();
            String[] pools = _loadbalancerApi.get_list();

            for (String pool : pools) {
                 if(pool.contains("/")){
                    lbPools.add(pool.substring(pool.lastIndexOf("/") + 1));
                }else{
                    lbPools.add(pool);
                }
             }
                return lbPools;
            } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private List<String> getMembers(String virtualServerName) throws ExecutionException {
        try {
            List<String> members = new ArrayList<String>();
            String[] virtualServerNames = genStringArray(virtualServerName);
            CommonIPPortDefinition[] membersArray = _loadbalancerApi.get_member(virtualServerNames)[0];

            for (CommonIPPortDefinition member : membersArray) {
                members.add(member.getAddress() + "-" + member.getPort());
            }

            return members;
        } catch (RemoteException e) {
            throw new ExecutionException(e.getMessage());
        }
    }

    private List<String> getNodes() throws RemoteException {
        List<String> nodes = new ArrayList<String>();
        String[] nodesArray = _nodeApi.get_list();

        for (String node : nodesArray) {
            nodes.add(node);
        }

        return nodes;
    }

    private iControl.CommonIPPortDefinition[][] genMembers(String destIp, long destPort) {
        iControl.CommonIPPortDefinition[] membersInnerArray = new iControl.CommonIPPortDefinition[1];
        membersInnerArray[0] = new iControl.CommonIPPortDefinition(destIp, destPort);
        return new iControl.CommonIPPortDefinition[][] {membersInnerArray};
    }

    private iControl.CommonIPPortDefinition[][] genEmptyMembersArray() {
        iControl.CommonIPPortDefinition[] membersInnerArray = new iControl.CommonIPPortDefinition[0];
        return new iControl.CommonIPPortDefinition[][] {membersInnerArray};
    }

    private LocalLBLBMethod[] genLbMethod(LbAlgorithm algorithm) {
        if (algorithm.getMethod() != null) {
            return new LocalLBLBMethod[] {algorithm.getMethod()};
        } else {
            return new LocalLBLBMethod[] {LbAlgorithm.RoundRobin.getMethod()};
        }
    }

    // Stats methods

    private ExternalNetworkResourceUsageAnswer getIpBytesSentAndReceived(ExternalNetworkResourceUsageCommand cmd) throws ExecutionException {
        ExternalNetworkResourceUsageAnswer answer = new ExternalNetworkResourceUsageAnswer(cmd);

        try {

            LocalLBVirtualServerVirtualServerStatistics stats = _virtualServerApi.get_all_statistics();
            for (LocalLBVirtualServerVirtualServerStatisticEntry entry : stats.getStatistics()) {
                String virtualServerIp = entry.getVirtual_server().getAddress();

                virtualServerIp = stripRouteDomainFromAddress(virtualServerIp);

                long[] bytesSentAndReceived = answer.ipBytes.get(virtualServerIp);

                if (bytesSentAndReceived == null) {
                    bytesSentAndReceived = new long[] {0, 0};
                }

                for (CommonStatistic stat : entry.getStatistics()) {
                    int index;
                    if (stat.getType().equals(CommonStatisticType.STATISTIC_CLIENT_SIDE_BYTES_OUT)) {
                        // Add to the outgoing bytes
                        index = 0;
                    } else if (stat.getType().equals(CommonStatisticType.STATISTIC_CLIENT_SIDE_BYTES_IN)) {
                        // Add to the incoming bytes
                        index = 1;
                    } else {
                        continue;
                    }

                    long high = stat.getValue().getHigh();
                    long low = stat.getValue().getLow();
                    long full = getFullUsage(high, low);

                    bytesSentAndReceived[index] += full;
                }

                if (bytesSentAndReceived[0] >= 0 && bytesSentAndReceived[1] >= 0) {
                    answer.ipBytes.put(virtualServerIp, bytesSentAndReceived);
                }
            }
        } catch (Exception e) {
            s_logger.error(e);
            throw new ExecutionException(e.getMessage());
        }

        return answer;
    }

    private long getFullUsage(long high, long low) {
        Double full;
        Double rollOver = new Double(0x7fffffff);
        rollOver = new Double(rollOver.doubleValue() + 1.0);

        if (high >= 0) {
            // shift left 32 bits and mask off new bits to 0's
            full = new Double((high << 32 & 0xffff0000));
        } else {
            // mask off sign bits + shift left by 32 bits then add the sign bit back
            full = new Double(((high & 0x7fffffff) << 32) + (0x80000000 << 32));
        }

        if (low >= 0) {
            // add low to full and we're good
            full = new Double(full.doubleValue() + low);
        } else {
            // add full to low after masking off sign bits and adding 1 to the masked off low order value
            full = new Double(full.doubleValue() + ((low & 0x7fffffff)) + rollOver.doubleValue());
        }

        return full.longValue();
    }

    // Misc methods

    private String tagAddressWithRouteDomain(String address, long vlanTag) {
        return address + _routeDomainIdentifier + vlanTag;
    }

    private String stripRouteDomainFromAddress(String address) {
        int i = address.indexOf(_routeDomainIdentifier);

        if (i > 0) {
            address = address.substring(0, i);
        }

        return address;
    }

    private String genObjectName(Object... args) {
        String objectName = "";

        for (int i = 0; i < args.length; i++) {
            objectName += args[i];
            if (i != args.length - 1) {
                objectName += _objectNamePathSep;
            }
        }

        return objectName;
    }

    private long[] genLongArray(long l) {
        return new long[] {l};
    }

    private static String[] genStringArray(String s) {
        return new String[] {s};
    }

    @Override
    public void setName(String name) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setConfigParams(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(int level) {
        // TODO Auto-generated method stub

    }

}
