@Override
  public boolean implement(
      Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context)
      throws ConcurrentOperationException, ResourceUnavailableException,
          InsufficientCapacityException {
    DataCenter zone = _configMgr.getZone(network.getDataCenterId());

    if (zone.getNetworkType() == NetworkType.Basic) {
      s_logger.debug("Not handling network implement in zone of type " + NetworkType.Basic);
      return false;
    }

    if (!canHandle(network)) {
      return false;
    }

    List<CiscoVnmcControllerVO> devices =
        _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
    if (devices.isEmpty()) {
      s_logger.error("No Cisco Vnmc device on network " + network.getName());
      return false;
    }

    List<CiscoAsa1000vDeviceVO> asaList =
        _ciscoAsa1000vDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
    if (asaList.isEmpty()) {
      s_logger.debug("No Cisco ASA 1000v device on network " + network.getName());
      return false;
    }

    NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
    if (asaForNetwork != null) {
      s_logger.debug("Cisco ASA 1000v device already associated with network " + network.getName());
      return true;
    }

    if (!_networkModel.isProviderSupportServiceInNetwork(
        network.getId(), Service.SourceNat, Provider.CiscoVnmc)) {
      s_logger.error(
          "SourceNat service is not provided by Cisco Vnmc device on network " + network.getName());
      return false;
    }

    Transaction txn = Transaction.currentTxn();
    boolean status = false;
    try {
      txn.start();

      // ensure that there is an ASA 1000v assigned to this network
      CiscoAsa1000vDevice assignedAsa = assignAsa1000vToNetwork(network);
      if (assignedAsa == null) {
        s_logger.error("Unable to assign ASA 1000v device to network " + network.getName());
        return false;
      }

      ClusterVO asaCluster = _clusterDao.findById(assignedAsa.getClusterId());
      ClusterVSMMapVO clusterVsmMap = _clusterVsmMapDao.findByClusterId(assignedAsa.getClusterId());
      if (clusterVsmMap == null) {
        s_logger.error(
            "Vmware cluster "
                + asaCluster.getName()
                + " has no Cisco Nexus VSM device associated with it");
        return false;
      }

      CiscoNexusVSMDeviceVO vsmDevice = _vsmDeviceDao.findById(clusterVsmMap.getVsmId());
      if (vsmDevice == null) {
        s_logger.error(
            "Unable to load details of Cisco Nexus VSM device associated with cluster "
                + asaCluster.getName());
        return false;
      }

      CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
      HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());
      _hostDao.loadDetails(ciscoVnmcHost);
      Account owner = context.getAccount();
      PublicIp sourceNatIp = _ipAddrMgr.assignSourceNatIpAddressToGuestNetwork(owner, network);
      String vlan = network.getBroadcastUri().getHost();
      long vlanId = Long.parseLong(vlan);

      List<VlanVO> vlanVOList =
          _vlanDao.listVlansByPhysicalNetworkId(network.getPhysicalNetworkId());
      List<String> publicGateways = new ArrayList<String>();
      for (VlanVO vlanVO : vlanVOList) {
        publicGateways.add(vlanVO.getVlanGateway());
      }

      // due to VNMC limitation of not allowing source NAT ip as the outside ip of firewall,
      // an additional public ip needs to acquired for assigning as firewall outside ip.
      // In case there are already additional ip addresses available (network restart) use one
      // of them such that it is not the source NAT ip
      IpAddress outsideIp = null;
      List<IPAddressVO> publicIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), null);
      for (IPAddressVO ip : publicIps) {
        if (!ip.isSourceNat()) {
          outsideIp = ip;
          break;
        }
      }
      if (outsideIp == null) { // none available, acquire one
        try {
          Account caller = CallContext.current().getCallingAccount();
          long callerUserId = CallContext.current().getCallingUserId();
          outsideIp = _ipAddrMgr.allocateIp(owner, false, caller, callerUserId, zone);
        } catch (ResourceAllocationException e) {
          s_logger.error("Unable to allocate additional public Ip address. Exception details " + e);
          return false;
        }

        try {
          outsideIp =
              _ipAddrMgr.associateIPToGuestNetwork(outsideIp.getId(), network.getId(), true);
        } catch (ResourceAllocationException e) {
          s_logger.error(
              "Unable to assign allocated additional public Ip "
                  + outsideIp.getAddress().addr()
                  + " to network with vlan "
                  + vlanId
                  + ". Exception details "
                  + e);
          return false;
        }
      }

      // create logical edge firewall in VNMC
      String gatewayNetmask = NetUtils.getCidrNetmask(network.getCidr());
      // due to ASA limitation of allowing single subnet to be assigned to firewall interfaces,
      // all public ip addresses must be from same subnet, this essentially means single public
      // subnet in zone
      if (!createLogicalEdgeFirewall(
          vlanId,
          network.getGateway(),
          gatewayNetmask,
          outsideIp.getAddress().addr(),
          sourceNatIp.getNetmask(),
          publicGateways,
          ciscoVnmcHost.getId())) {
        s_logger.error(
            "Failed to create logical edge firewall in Cisco VNMC device for network "
                + network.getName());
        return false;
      }

      // create stuff in VSM for ASA device
      if (!configureNexusVsmForAsa(
          vlanId,
          network.getGateway(),
          vsmDevice.getUserName(),
          vsmDevice.getPassword(),
          vsmDevice.getipaddr(),
          assignedAsa.getInPortProfile(),
          ciscoVnmcHost.getId())) {
        s_logger.error(
            "Failed to configure Cisco Nexus VSM "
                + vsmDevice.getipaddr()
                + " for ASA device for network "
                + network.getName());
        return false;
      }

      // configure source NAT
      if (!configureSourceNat(vlanId, network.getCidr(), sourceNatIp, ciscoVnmcHost.getId())) {
        s_logger.error(
            "Failed to configure source NAT in Cisco VNMC device for network " + network.getName());
        return false;
      }

      // associate Asa 1000v instance with logical edge firewall
      if (!associateAsaWithLogicalEdgeFirewall(
          vlanId, assignedAsa.getManagementIp(), ciscoVnmcHost.getId())) {
        s_logger.error(
            "Failed to associate Cisco ASA 1000v ("
                + assignedAsa.getManagementIp()
                + ") with logical edge firewall in VNMC for network "
                + network.getName());
        return false;
      }

      status = true;
      txn.commit();
    } finally {
      if (!status) {
        txn.rollback();
        // FIXME: also undo changes in VNMC, VSM if anything failed
      }
    }

    return true;
  }
  @Override
  public boolean applyPFRules(Network network, List<PortForwardingRule> rules)
      throws ResourceUnavailableException {

    if (!_networkModel.isProviderSupportServiceInNetwork(
        network.getId(), Service.PortForwarding, Provider.CiscoVnmc)) {
      s_logger.error(
          "Port forwarding service is not provided by Cisco Vnmc device on network "
              + network.getName());
      return false;
    }

    // Find VNMC host for physical network
    List<CiscoVnmcControllerVO> devices =
        _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
    if (devices.isEmpty()) {
      s_logger.error("No Cisco Vnmc device on network " + network.getName());
      return true;
    }

    // Find if ASA 1000v is associated with network
    NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
    if (asaForNetwork == null) {
      s_logger.debug("Cisco ASA 1000v device is not associated with network " + network.getName());
      return true;
    }

    if (network.getState() == Network.State.Allocated) {
      s_logger.debug(
          "External firewall was asked to apply port forwarding rules for network with ID "
              + network.getId()
              + "; this network is not implemented. Skipping backend commands.");
      return true;
    }

    CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
    HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());

    List<PortForwardingRuleTO> rulesTO = new ArrayList<PortForwardingRuleTO>();
    for (PortForwardingRule rule : rules) {
      IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
      Vlan vlan = _vlanDao.findById(sourceIp.getVlanId());
      PortForwardingRuleTO ruleTO =
          new PortForwardingRuleTO(rule, vlan.getVlanTag(), sourceIp.getAddress().addr());
      rulesTO.add(ruleTO);
    }

    if (!rulesTO.isEmpty()) {
      SetPortForwardingRulesCommand cmd = new SetPortForwardingRulesCommand(rulesTO);
      cmd.setContextParam(
          NetworkElementCommand.GUEST_VLAN_TAG, network.getBroadcastUri().getHost());
      cmd.setContextParam(NetworkElementCommand.GUEST_NETWORK_CIDR, network.getCidr());
      Answer answer = _agentMgr.easySend(ciscoVnmcHost.getId(), cmd);
      if (answer == null || !answer.getResult()) {
        String details = (answer != null) ? answer.getDetails() : "details unavailable";
        String msg =
            "Unable to apply port forwarding rules to Cisco ASA 1000v appliance due to: "
                + details
                + ".";
        s_logger.error(msg);
        throw new ResourceUnavailableException(msg, DataCenter.class, network.getDataCenterId());
      }
    }

    return true;
  }
  @PostConstruct
  public void init() {
    AllFieldsSearch = createSearchBuilder();
    AllFieldsSearch.and("id", AllFieldsSearch.entity().getId(), Op.EQ);
    AllFieldsSearch.and("dataCenterId", AllFieldsSearch.entity().getDataCenterId(), Op.EQ);
    AllFieldsSearch.and("ipAddress", AllFieldsSearch.entity().getAddress(), Op.EQ);
    AllFieldsSearch.and("vlan", AllFieldsSearch.entity().getVlanId(), Op.EQ);
    AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAllocatedToAccountId(), Op.EQ);
    AllFieldsSearch.and("sourceNat", AllFieldsSearch.entity().isSourceNat(), Op.EQ);
    AllFieldsSearch.and("network", AllFieldsSearch.entity().getAssociatedWithNetworkId(), Op.EQ);
    AllFieldsSearch.and(
        "associatedWithVmId", AllFieldsSearch.entity().getAssociatedWithVmId(), Op.EQ);
    AllFieldsSearch.and("oneToOneNat", AllFieldsSearch.entity().isOneToOneNat(), Op.EQ);
    AllFieldsSearch.and("sourcenetwork", AllFieldsSearch.entity().getSourceNetworkId(), Op.EQ);
    AllFieldsSearch.and(
        "physicalNetworkId", AllFieldsSearch.entity().getPhysicalNetworkId(), Op.EQ);
    AllFieldsSearch.and("vpcId", AllFieldsSearch.entity().getVpcId(), Op.EQ);
    AllFieldsSearch.and("associatedVmIp", AllFieldsSearch.entity().getVmIp(), Op.EQ);
    AllFieldsSearch.done();

    VlanDbIdSearchUnallocated = createSearchBuilder();
    VlanDbIdSearchUnallocated.and(
        "allocated", VlanDbIdSearchUnallocated.entity().getAllocatedTime(), Op.NULL);
    VlanDbIdSearchUnallocated.and(
        "vlanDbId", VlanDbIdSearchUnallocated.entity().getVlanId(), Op.EQ);
    VlanDbIdSearchUnallocated.done();

    AllIpCount = createSearchBuilder(Integer.class);
    AllIpCount.select(null, Func.COUNT, AllIpCount.entity().getAddress());
    AllIpCount.and("dc", AllIpCount.entity().getDataCenterId(), Op.EQ);
    AllIpCount.and("vlan", AllIpCount.entity().getVlanId(), Op.EQ);
    AllIpCount.done();

    AllocatedIpCount = createSearchBuilder(Integer.class);
    AllocatedIpCount.select(null, Func.COUNT, AllocatedIpCount.entity().getAddress());
    AllocatedIpCount.and("dc", AllocatedIpCount.entity().getDataCenterId(), Op.EQ);
    AllocatedIpCount.and("vlan", AllocatedIpCount.entity().getVlanId(), Op.EQ);
    AllocatedIpCount.and("allocated", AllocatedIpCount.entity().getAllocatedTime(), Op.NNULL);
    AllocatedIpCount.done();

    AllIpCountForDashboard = createSearchBuilder(Integer.class);
    AllIpCountForDashboard.select(null, Func.COUNT, AllIpCountForDashboard.entity().getAddress());
    AllIpCountForDashboard.and("dc", AllIpCountForDashboard.entity().getDataCenterId(), Op.EQ);
    AllIpCountForDashboard.and(
        "state", AllIpCountForDashboard.entity().getState(), SearchCriteria.Op.NEQ);

    SearchBuilder<VlanVO> virtaulNetworkVlan = _vlanDao.createSearchBuilder();
    virtaulNetworkVlan.and(
        "vlanType", virtaulNetworkVlan.entity().getVlanType(), SearchCriteria.Op.EQ);

    AllIpCountForDashboard.join(
        "vlan",
        virtaulNetworkVlan,
        virtaulNetworkVlan.entity().getId(),
        AllIpCountForDashboard.entity().getVlanId(),
        JoinBuilder.JoinType.INNER);
    virtaulNetworkVlan.done();
    AllIpCountForDashboard.done();

    AllocatedIpCountForAccount = createSearchBuilder(Long.class);
    AllocatedIpCountForAccount.select(
        null, Func.COUNT, AllocatedIpCountForAccount.entity().getAddress());
    AllocatedIpCountForAccount.and(
        "account", AllocatedIpCountForAccount.entity().getAllocatedToAccountId(), Op.EQ);
    AllocatedIpCountForAccount.and(
        "allocated", AllocatedIpCountForAccount.entity().getAllocatedTime(), Op.NNULL);
    AllocatedIpCountForAccount.and(
        "network", AllocatedIpCountForAccount.entity().getAssociatedWithNetworkId(), Op.NNULL);
    AllocatedIpCountForAccount.done();

    CountFreePublicIps = createSearchBuilder(Long.class);
    CountFreePublicIps.select(null, Func.COUNT, null);
    CountFreePublicIps.and("state", CountFreePublicIps.entity().getState(), SearchCriteria.Op.EQ);
    CountFreePublicIps.and(
        "networkId", CountFreePublicIps.entity().getSourceNetworkId(), SearchCriteria.Op.EQ);
    SearchBuilder<VlanVO> join = _vlanDao.createSearchBuilder();
    join.and("vlanType", join.entity().getVlanType(), Op.EQ);
    CountFreePublicIps.join(
        "vlans",
        join,
        CountFreePublicIps.entity().getVlanId(),
        join.entity().getId(),
        JoinBuilder.JoinType.INNER);
    CountFreePublicIps.done();

    DeleteAllExceptGivenIp = createSearchBuilder();
    DeleteAllExceptGivenIp.and("vlanDbId", DeleteAllExceptGivenIp.entity().getVlanId(), Op.EQ);
    DeleteAllExceptGivenIp.and("ip", DeleteAllExceptGivenIp.entity().getAddress(), Op.NEQ);
  }