// 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.ConfigurationException;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.cloud.agent.IAgentControl;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.AssociateAsaWithLogicalEdgeFirewallCommand;
import com.cloud.agent.api.CleanupLogicalEdgeFirewallCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.ConfigureNexusVsmForAsaCommand;
import com.cloud.agent.api.CreateLogicalEdgeFirewallCommand;
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.StartupExternalFirewallCommand;
import com.cloud.agent.api.routing.IpAssocAnswer;
import com.cloud.agent.api.routing.IpAssocCommand;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.agent.api.routing.SetFirewallRulesCommand;
import com.cloud.agent.api.routing.SetPortForwardingRulesCommand;
import com.cloud.agent.api.routing.SetSourceNatCommand;
import com.cloud.agent.api.routing.SetStaticNatRulesCommand;
import com.cloud.agent.api.to.FirewallRuleTO;
import com.cloud.agent.api.to.PortForwardingRuleTO;
import com.cloud.agent.api.to.StaticNatRuleTO;
import com.cloud.host.Host;
import com.cloud.network.cisco.CiscoVnmcConnectionImpl;
import com.cloud.network.rules.FirewallRule.TrafficType;
import com.cloud.resource.ServerResource;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.cisco.n1kv.vsm.NetconfHelper;
import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.OperationType;
import com.cloud.utils.cisco.n1kv.vsm.VsmCommand.SwitchPortMode;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.exception.ExecutionException;
import com.cloud.utils.net.NetUtils;

public class CiscoVnmcResource implements ServerResource {

    private String _name;
    private String _zoneId;
    private String _physicalNetworkId;
    private String _ip;
    private String _username;
    private String _password;
    private String _guid;
    private Integer _numRetries = 1;

    private CiscoVnmcConnectionImpl _connection;

    public void setConnection(CiscoVnmcConnectionImpl connection) {
        _connection = connection;
    }

    protected Logger logger = LogManager.getLogger(getClass());

    @Override
    public Answer executeRequest(Command cmd) {
        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);
        } else if (cmd instanceof SetSourceNatCommand) {
            return execute((SetSourceNatCommand)cmd);
        } else if (cmd instanceof SetFirewallRulesCommand) {
            return execute((SetFirewallRulesCommand)cmd);
        } else if (cmd instanceof SetStaticNatRulesCommand) {
            return execute((SetStaticNatRulesCommand)cmd);
        } else if (cmd instanceof SetPortForwardingRulesCommand) {
            return execute((SetPortForwardingRulesCommand)cmd);
        } else if (cmd instanceof ExternalNetworkResourceUsageCommand) {
            return execute((ExternalNetworkResourceUsageCommand)cmd);
        } else if (cmd instanceof CreateLogicalEdgeFirewallCommand) {
            return execute((CreateLogicalEdgeFirewallCommand)cmd);
        } else if (cmd instanceof CleanupLogicalEdgeFirewallCommand) {
            return execute((CleanupLogicalEdgeFirewallCommand)cmd);
        } else if (cmd instanceof ConfigureNexusVsmForAsaCommand) {
            return execute((ConfigureNexusVsmForAsaCommand)cmd);
        } else if (cmd instanceof AssociateAsaWithLogicalEdgeFirewallCommand) {
            return execute((AssociateAsaWithLogicalEdgeFirewallCommand)cmd);
        } else {
            return Answer.createUnsupportedCommandAnswer(cmd);
        }
    }

    @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");
            }

            _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");
            }

            _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");
            }

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

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

            NumbersUtil.parseInt((String)params.get("timeout"), 300);

            // Open a socket and login
            _connection = new CiscoVnmcConnectionImpl(_ip, _username, _password);
            if (!refreshVnmcConnection()) {
                throw new ConfigurationException("Unable to connect to VNMC, check if ip/username/password is valid.");
            }

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

    }

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

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

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

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

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

    @Override
    public PingCommand getCurrentStatus(final long id) {
        if (!refreshVnmcConnection()) {
            return null;
        }
        return new PingCommand(Host.Type.ExternalFirewall, id);
    }

    @Override
    public void disconnected() {
    }

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

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

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

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

    private ExternalNetworkResourceUsageAnswer execute(ExternalNetworkResourceUsageCommand cmd) {
        return new ExternalNetworkResourceUsageAnswer(cmd);
    }

    /*
     * Login
     */
    private boolean refreshVnmcConnection() {
        boolean ret = false;
        try {
            ret = _connection.login();
        } catch (ExecutionException ex) {
            logger.error("Login to Vnmc failed", ex);
        }
        return ret;
    }

    private synchronized Answer execute(IpAssocCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private Answer execute(IpAssocCommand cmd, int numRetries) {
        String[] results = new String[cmd.getIpAddresses().length];
        return new IpAssocAnswer(cmd, results);
    }

    private String[] getIpRangeFromCidr(String cidr) {
        String[] result = new String[2];
        String[] cidrData = cidr.split("\\/");
        assert (cidrData.length == 2) : "Something is wrong with source cidr " + cidr;
        long size = Long.parseLong(cidrData[1]);
        result[0] = cidrData[0];
        result[1] = cidrData[0];
        if (size < 32) {
            result[0] = NetUtils.getIpRangeStartIpFromCidr(cidrData[0], size);
            result[1] = NetUtils.getIpRangeEndIpFromCidr(cidrData[0], size);
        }
        return result;
    }

    /*
     * Source NAT
     */
    private synchronized Answer execute(SetSourceNatCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private Answer execute(SetSourceNatCommand cmd, int numRetries) {
        String vlanId = cmd.getContextParam(NetworkElementCommand.GUEST_VLAN_TAG);
        String tenant = "vlan-" + vlanId;
        String policyIdentifier = cmd.getIpAddress().getPublicIp().replace('.', '-');
        try {
            if (!_connection.createTenantVDCNatPolicySet(tenant)) {
                throw new ExecutionException("Failed to create NAT policy set in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCSourceNatPolicy(tenant, policyIdentifier)) {
                throw new ExecutionException("Failed to create source NAT policy in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCSourceNatPolicyRef(tenant, policyIdentifier)) {
                throw new ExecutionException("Failed to associate source NAT policy with NAT policy set in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCSourceNatIpPool(tenant, policyIdentifier, cmd.getIpAddress().getPublicIp())) {
                throw new ExecutionException("Failed to create source NAT ip pool in VNMC for guest network with vlan " + vlanId);
            }

            String[] ipRange = getIpRangeFromCidr(cmd.getContextParam(NetworkElementCommand.GUEST_NETWORK_CIDR));
            if (!_connection.createTenantVDCSourceNatRule(tenant, policyIdentifier, ipRange[0], ipRange[1])) {
                throw new ExecutionException("Failed to create source NAT rule in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.associateNatPolicySet(tenant)) {
                throw new ExecutionException("Failed to associate source NAT policy set with edge security profile in VNMC for guest network with vlan " + vlanId);
            }
        } catch (ExecutionException e) {
            String msg = "SetSourceNatCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    /*
     * Firewall rule
     */
    private synchronized Answer execute(SetFirewallRulesCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private Answer execute(SetFirewallRulesCommand cmd, int numRetries) {
        String vlanId = cmd.getContextParam(NetworkElementCommand.GUEST_VLAN_TAG);
        String tenant = "vlan-" + vlanId;

        FirewallRuleTO[] rules = cmd.getRules();
        Map<String, List<FirewallRuleTO>> publicIpRulesMap = new HashMap<String, List<FirewallRuleTO>>();
        for (FirewallRuleTO rule : rules) {
            String publicIp = rule.getSrcIp();
            if (!publicIpRulesMap.containsKey(publicIp)) {
                List<FirewallRuleTO> publicIpRulesList = new ArrayList<FirewallRuleTO>();
                publicIpRulesMap.put(publicIp, publicIpRulesList);
            }
            publicIpRulesMap.get(publicIp).add(rule);
        }

        try {
            if (!_connection.createTenantVDCAclPolicySet(tenant, true)) {
                throw new ExecutionException("Failed to create ACL ingress policy set in VNMC for guest network with vlan " + vlanId);
            }
            if (!_connection.createTenantVDCAclPolicySet(tenant, false)) {
                throw new ExecutionException("Failed to create ACL egress policy set in VNMC for guest network with vlan " + vlanId);
            }

            for (String publicIp : publicIpRulesMap.keySet()) {
                String policyIdentifier = publicIp.replace('.', '-');

                if (!_connection.createTenantVDCAclPolicy(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to create ACL policy in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCAclPolicyRef(tenant, policyIdentifier, true)) {
                    throw new ExecutionException("Failed to associate ACL policy with ACL ingress policy set in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCAclPolicyRef(tenant, policyIdentifier, false)) {
                    throw new ExecutionException("Failed to associate ACL policy with ACL egress policy set in VNMC for guest network with vlan " + vlanId);
                }

                for (FirewallRuleTO rule : publicIpRulesMap.get(publicIp)) {
                    if (rule.revoked()) {
                        if (!_connection.deleteTenantVDCAclRule(tenant, rule.getId(), policyIdentifier)) {
                            throw new ExecutionException("Failed to delete ACL rule in VNMC for guest network with vlan " + vlanId);
                        }
                    } else {
                        String[] externalIpRange = getIpRangeFromCidr(rule.getSourceCidrList().get(0));
                        if (rule.getTrafficType() == TrafficType.Ingress) {
                            if (!rule.getProtocol().equalsIgnoreCase("icmp") && rule.getSrcPortRange() != null) {
                                if (!_connection.createTenantVDCIngressAclRule(tenant, rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(),
                                    externalIpRange[0], externalIpRange[1], Integer.toString(rule.getSrcPortRange()[0]), Integer.toString(rule.getSrcPortRange()[1]))) {
                                    throw new ExecutionException("Failed to create ACL ingress rule in VNMC for guest network with vlan " + vlanId);
                                }
                            } else {
                                if (!_connection.createTenantVDCIngressAclRule(tenant, rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(),
                                    externalIpRange[0], externalIpRange[1])) {
                                    throw new ExecutionException("Failed to create ACL ingress rule in VNMC for guest network with vlan " + vlanId);
                                }
                            }
                        } else {
                            if ((rule.getProtocol().equalsIgnoreCase("tcp") || rule.getProtocol().equalsIgnoreCase("udp")) && rule.getSrcPortRange() != null) {
                                if (!_connection.createTenantVDCEgressAclRule(tenant, rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(),
                                    externalIpRange[0], externalIpRange[1], Integer.toString(rule.getSrcPortRange()[0]), Integer.toString(rule.getSrcPortRange()[1]))) {
                                    throw new ExecutionException("Failed to create ACL egress rule in VNMC for guest network with vlan " + vlanId);
                                }
                            } else {
                                if (!_connection.createTenantVDCEgressAclRule(tenant, rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(),
                                    externalIpRange[0], externalIpRange[1])) {
                                    throw new ExecutionException("Failed to create ACL egress rule in VNMC for guest network with vlan " + vlanId);
                                }
                            }
                        }
                    }
                }
            }

            if (!_connection.associateAclPolicySet(tenant)) {
                throw new ExecutionException("Failed to associate ACL policy set with edge security profile in VNMC for guest network with vlan " + vlanId);
            }
        } catch (ExecutionException e) {
            String msg = "SetFirewallRulesCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    /*
     * Static NAT
     */
    private synchronized Answer execute(SetStaticNatRulesCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private Answer execute(SetStaticNatRulesCommand cmd, int numRetries) {
        String vlanId = cmd.getContextParam(NetworkElementCommand.GUEST_VLAN_TAG);
        String tenant = "vlan-" + vlanId;

        StaticNatRuleTO[] rules = cmd.getRules();
        Map<String, List<StaticNatRuleTO>> publicIpRulesMap = new HashMap<String, List<StaticNatRuleTO>>();
        for (StaticNatRuleTO rule : rules) {
            String publicIp = rule.getSrcIp();
            if (!publicIpRulesMap.containsKey(publicIp)) {
                List<StaticNatRuleTO> publicIpRulesList = new ArrayList<StaticNatRuleTO>();
                publicIpRulesMap.put(publicIp, publicIpRulesList);
            }
            publicIpRulesMap.get(publicIp).add(rule);
        }

        try {
            if (!_connection.createTenantVDCNatPolicySet(tenant)) {
                throw new ExecutionException("Failed to create NAT policy set in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCAclPolicySet(tenant, true)) {
                throw new ExecutionException("Failed to create ACL ingress policy set in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCAclPolicySet(tenant, false)) {
                throw new ExecutionException("Failed to create ACL egress policy set in VNMC for guest network with vlan " + vlanId);
            }

            for (String publicIp : publicIpRulesMap.keySet()) {
                String policyIdentifier = publicIp.replace('.', '-');

                if (!_connection.createTenantVDCDNatPolicy(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to create DNAT policy in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCDNatPolicyRef(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to associate DNAT policy with NAT policy set in VNMC for guest network with vlan " + vlanId);
                }

                if (!_connection.createTenantVDCAclPolicy(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to create ACL policy in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCAclPolicyRef(tenant, policyIdentifier, true)) {
                    throw new ExecutionException("Failed to associate ACL policy with ACL ingress policy set in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCAclPolicyRef(tenant, policyIdentifier, false)) {
                    throw new ExecutionException("Failed to associate ACL policy with ACL egress policy set in VNMC for guest network with vlan " + vlanId);
                }

                for (StaticNatRuleTO rule : publicIpRulesMap.get(publicIp)) {
                    if (rule.revoked()) {
                        if (!_connection.deleteTenantVDCDNatRule(tenant, rule.getId(), policyIdentifier)) {
                            throw new ExecutionException("Failed to delete DNAT rule in VNMC for guest network with vlan " + vlanId);
                        }
                    } else {
                        if (!_connection.createTenantVDCDNatIpPool(tenant, Long.toString(rule.getId()), rule.getDstIp())) {
                            throw new ExecutionException("Failed to create DNAT ip pool in VNMC for guest network with vlan " + vlanId);
                        }

                        if (!_connection.createTenantVDCDNatRule(tenant, rule.getId(), policyIdentifier, rule.getSrcIp())) {
                            throw new ExecutionException("Failed to create DNAT rule in VNMC for guest network with vlan " + vlanId);
                        }
                    }
                }
            }

            if (!_connection.associateAclPolicySet(tenant)) {
                throw new ExecutionException("Failed to associate source NAT policy set with edge security profile in VNMC for guest network with vlan " + vlanId);
            }
        } catch (ExecutionException e) {
            String msg = "SetStaticNatRulesCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    /*
     * PF
     */
    private synchronized Answer execute(SetPortForwardingRulesCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private Answer execute(SetPortForwardingRulesCommand cmd, int numRetries) {
        String vlanId = cmd.getContextParam(NetworkElementCommand.GUEST_VLAN_TAG);
        String tenant = "vlan-" + vlanId;

        PortForwardingRuleTO[] rules = cmd.getRules();
        Map<String, List<PortForwardingRuleTO>> publicIpRulesMap = new HashMap<String, List<PortForwardingRuleTO>>();
        for (PortForwardingRuleTO rule : rules) {
            String publicIp = rule.getSrcIp();
            if (!publicIpRulesMap.containsKey(publicIp)) {
                List<PortForwardingRuleTO> publicIpRulesList = new ArrayList<PortForwardingRuleTO>();
                publicIpRulesMap.put(publicIp, publicIpRulesList);
            }
            publicIpRulesMap.get(publicIp).add(rule);
        }

        try {
            if (!_connection.createTenantVDCNatPolicySet(tenant)) {
                throw new ExecutionException("Failed to create NAT policy set in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCAclPolicySet(tenant, true)) {
                throw new ExecutionException("Failed to create ACL ingress policy set in VNMC for guest network with vlan " + vlanId);
            }

            if (!_connection.createTenantVDCAclPolicySet(tenant, false)) {
                throw new ExecutionException("Failed to create ACL egress policy set in VNMC for guest network with vlan " + vlanId);
            }

            for (String publicIp : publicIpRulesMap.keySet()) {
                String policyIdentifier = publicIp.replace('.', '-');

                if (!_connection.createTenantVDCPFPolicy(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to create PF policy in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCPFPolicyRef(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to associate PF policy with NAT policy set in VNMC for guest network with vlan " + vlanId);
                }

                if (!_connection.createTenantVDCAclPolicy(tenant, policyIdentifier)) {
                    throw new ExecutionException("Failed to create ACL policy in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCAclPolicyRef(tenant, policyIdentifier, true)) {
                    throw new ExecutionException("Failed to associate ACL policy with ACL ingress policy set in VNMC for guest network with vlan " + vlanId);
                }
                if (!_connection.createTenantVDCAclPolicyRef(tenant, policyIdentifier, false)) {
                    throw new ExecutionException("Failed to associate ACL policy with ACL egress policy set in VNMC for guest network with vlan " + vlanId);
                }

                for (PortForwardingRuleTO rule : publicIpRulesMap.get(publicIp)) {
                    if (rule.revoked()) {
                        if (!_connection.deleteTenantVDCPFRule(tenant, rule.getId(), policyIdentifier)) {
                            throw new ExecutionException("Failed to delete PF rule in VNMC for guest network with vlan " + vlanId);
                        }
                    } else {
                        if (!_connection.createTenantVDCPFIpPool(tenant, Long.toString(rule.getId()), rule.getDstIp())) {
                            throw new ExecutionException("Failed to create PF ip pool in VNMC for guest network with vlan " + vlanId);
                        }
                        if (!_connection.createTenantVDCPFPortPool(tenant, Long.toString(rule.getId()), Integer.toString(rule.getDstPortRange()[0]),
                            Integer.toString(rule.getDstPortRange()[1]))) {
                            throw new ExecutionException("Failed to create PF port pool in VNMC for guest network with vlan " + vlanId);
                        }

                        if (!_connection.createTenantVDCPFRule(tenant, rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(), rule.getSrcIp(),
                            Integer.toString(rule.getSrcPortRange()[0]), Integer.toString(rule.getSrcPortRange()[1]))) {
                            throw new ExecutionException("Failed to create PF rule in VNMC for guest network with vlan " + vlanId);
                        }
                    }
                }
            }

            if (!_connection.associateAclPolicySet(tenant)) {
                throw new ExecutionException("Failed to associate source NAT policy set with edge security profile in VNMC for guest network with vlan " + vlanId);
            }
        } catch (ExecutionException e) {
            String msg = "SetPortForwardingRulesCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    /*
     * Logical edge firewall
     */
    private synchronized Answer execute(CreateLogicalEdgeFirewallCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private void createEdgeDeviceProfile(String tenant, List<String> gateways, Long vlanId) throws ExecutionException {
        // create edge device profile
        if (!_connection.createTenantVDCEdgeDeviceProfile(tenant))
            throw new ExecutionException("Failed to create tenant edge device profile in VNMC for guest network with vlan " + vlanId);

        // create edge static route policy
        if (!_connection.createTenantVDCEdgeStaticRoutePolicy(tenant))
            throw new ExecutionException("Failed to create tenant edge static route policy in VNMC for guest network with vlan " + vlanId);

        // create edge static route for all gateways
        for (String gateway : gateways) {
            if (!_connection.createTenantVDCEdgeStaticRoute(tenant, gateway, "0.0.0.0", "0.0.0.0"))
                throw new ExecutionException("Failed to create tenant edge static route in VNMC for guest network with vlan " + vlanId);
        }

        // associate edge
        if (!_connection.associateTenantVDCEdgeStaticRoutePolicy(tenant))
            throw new ExecutionException("Failed to associate edge static route policy with edge device profile in VNMC for guest network with vlan " + vlanId);
    }

    private Answer execute(CreateLogicalEdgeFirewallCommand cmd, int numRetries) {
        String tenant = "vlan-" + cmd.getVlanId();
        try {
            // create tenant
            if (!_connection.createTenant(tenant))
                throw new ExecutionException("Failed to create tenant in VNMC for guest network with vlan " + cmd.getVlanId());

            // create tenant VDC
            if (!_connection.createTenantVDC(tenant))
                throw new ExecutionException("Failed to create tenant VDC in VNMC for guest network with vlan " + cmd.getVlanId());

            // create edge security profile
            if (!_connection.createTenantVDCEdgeSecurityProfile(tenant))
                throw new ExecutionException("Failed to create tenant edge security profile in VNMC for guest network with vlan " + cmd.getVlanId());

            // create edge device profile and associated route
            createEdgeDeviceProfile(tenant, cmd.getPublicGateways(), cmd.getVlanId());

            // create logical edge firewall
            if (!_connection.createEdgeFirewall(tenant, cmd.getPublicIp(), cmd.getInternalIp(), cmd.getPublicSubnet(), cmd.getInternalSubnet()))
                throw new ExecutionException("Failed to create edge firewall in VNMC for guest network with vlan " + cmd.getVlanId());
        } catch (ExecutionException e) {
            String msg = "CreateLogicalEdgeFirewallCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    /*
     * Create vservice node and update inside port profile for ASA appliance in VSM
     */
    private synchronized Answer execute(ConfigureNexusVsmForAsaCommand cmd) {
        return execute(cmd, _numRetries);
    }

    private Answer execute(ConfigureNexusVsmForAsaCommand cmd, int numRetries) {
        String vlanId = Long.toString(cmd.getVlanId());
        NetconfHelper helper = null;
        List<Pair<OperationType, String>> params = new ArrayList<Pair<OperationType, String>>();
        params.add(new Pair<OperationType, String>(OperationType.addvlanid, vlanId));
        try {
            helper = new NetconfHelper(cmd.getVsmIp(), cmd.getVsmUsername(), cmd.getVsmPassword());
            logger.debug("Connected to Cisco VSM " + cmd.getVsmIp());
            helper.addVServiceNode(vlanId, cmd.getIpAddress());
            logger.debug("Created vservice node for ASA appliance in Cisco VSM for vlan " + vlanId);
            helper.updatePortProfile(cmd.getAsaInPortProfile(), SwitchPortMode.access, params);
            logger.debug("Updated inside port profile for ASA appliance in Cisco VSM with new vlan " + vlanId);
        } catch (CloudRuntimeException e) {
            String msg = "ConfigureVSMForASACommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        } finally {
            if( helper != null) {
                helper.disconnect();
            }
        }
        return new Answer(cmd, true, "Success");
    }

    /*
     * Associates ASA 1000v with logical edge firewall in VNMC
     */
    private synchronized Answer execute(AssociateAsaWithLogicalEdgeFirewallCommand cmd) {
        return execute(cmd, _numRetries);
    }

    private Answer execute(AssociateAsaWithLogicalEdgeFirewallCommand cmd, int numRetries) {
        String tenant = "vlan-" + cmd.getVlanId();
        try {
            Map<String, String> availableAsaAppliances = _connection.listUnAssocAsa1000v();
            if (availableAsaAppliances.isEmpty()) {
                throw new ExecutionException("No ASA 1000v available to associate with logical edge firewall for guest vlan " + cmd.getVlanId());
            }

            String asaInstanceDn = availableAsaAppliances.get(cmd.getAsaMgmtIp());
            if (asaInstanceDn == null) {
                throw new ExecutionException("Requested ASA 1000v (" + cmd.getAsaMgmtIp() + ") is not available");
            }

            if (!_connection.assignAsa1000v(tenant, asaInstanceDn)) {
                throw new ExecutionException("Failed to associate ASA 1000v (" + cmd.getAsaMgmtIp() + ") with logical edge firewall for guest vlan " + cmd.getVlanId());
            }
        } catch (ExecutionException e) {
            String msg = "AssociateAsaWithLogicalEdgeFirewallCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    /*
     * Cleanup
     */
    private synchronized Answer execute(CleanupLogicalEdgeFirewallCommand cmd) {
        refreshVnmcConnection();
        return execute(cmd, _numRetries);
    }

    private Answer execute(CleanupLogicalEdgeFirewallCommand cmd, int numRetries) {
        String tenant = "vlan-" + cmd.getVlanId();
        try {
            _connection.deleteTenant(tenant);
        } catch (ExecutionException e) {
            String msg = "CleanupLogicalEdgeFirewallCommand failed due to " + e.getMessage();
            logger.error(msg, e);
            return new Answer(cmd, false, msg);
        }

        return new Answer(cmd, true, "Success");
    }

    @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
    }

}
