@Override
  @DB
  public RemoteAccessVpnVO startRemoteAccessVpn(long vpnId, boolean openFirewall)
      throws ResourceUnavailableException {
    Account caller = UserContext.current().getCaller();

    RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findById(vpnId);
    if (vpn == null) {
      throw new InvalidParameterValueException("Unable to find your vpn: " + vpnId);
    }

    _accountMgr.checkAccess(caller, null, true, vpn);

    Network network = _networkMgr.getNetwork(vpn.getNetworkId());

    boolean started = false;
    try {
      boolean firewallOpened = true;
      if (openFirewall) {
        firewallOpened = _firewallMgr.applyIngressFirewallRules(vpn.getServerAddressId(), caller);
      }

      if (firewallOpened) {
        for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) {
          if (element.startVpn(network, vpn)) {
            started = true;
            break;
          }
        }
      }

      return vpn;
    } finally {
      if (started) {
        Transaction txn = Transaction.currentTxn();
        txn.start();
        vpn.setState(RemoteAccessVpn.State.Running);
        _remoteAccessVpnDao.update(vpn.getServerAddressId(), vpn);

        // Start billing of existing VPN users in ADD and Active state
        List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId());
        for (VpnUserVO user : vpnUsers) {
          if (user.getState() != VpnUser.State.Revoke) {
            UsageEventUtils.publishUsageEvent(
                EventTypes.EVENT_VPN_USER_ADD,
                user.getAccountId(),
                0,
                user.getId(),
                user.getUsername(),
                user.getClass().getName(),
                user.getUuid());
          }
        }
        txn.commit();
      }
    }
  }
  @Override
  public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
    Map<String, String> configs = _configDao.getConfiguration(params);

    _userLimit = NumbersUtil.parseInt(configs.get(Config.RemoteAccessVpnUserLimit.key()), 8);

    _clientIpRange = configs.get(Config.RemoteAccessVpnClientIpRange.key());

    _pskLength = NumbersUtil.parseInt(configs.get(Config.RemoteAccessVpnPskLength.key()), 24);

    validateRemoteAccessVpnConfiguration();

    VpnSearch = _remoteAccessVpnDao.createSearchBuilder();
    VpnSearch.and("accountId", VpnSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
    SearchBuilder<DomainVO> domainSearch = _domainDao.createSearchBuilder();
    domainSearch.and("path", domainSearch.entity().getPath(), SearchCriteria.Op.LIKE);
    VpnSearch.join(
        "domainSearch",
        domainSearch,
        VpnSearch.entity().getDomainId(),
        domainSearch.entity().getId(),
        JoinBuilder.JoinType.INNER);
    VpnSearch.done();

    return true;
  }
  @Override
  @ActionEvent(
      eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_UPDATE,
      eventDescription = "updating remote access vpn",
      async = true)
  public RemoteAccessVpn updateRemoteAccessVpn(long id, String customId, Boolean forDisplay) {
    final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findById(id);
    if (vpn == null) {
      throw new InvalidParameterValueException("Can't find remote access vpn by id " + id);
    }

    _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, vpn);
    if (customId != null) {
      vpn.setUuid(customId);
    }
    if (forDisplay != null) {
      vpn.setDisplay(forDisplay);
    }

    _remoteAccessVpnDao.update(vpn.getId(), vpn);
    return _remoteAccessVpnDao.findById(id);
  }
 @Override
 public RemoteAccessVpn getRemoteAccessVpnById(long vpnId) {
   return _remoteAccessVpnDao.findById(vpnId);
 }
 @Override
 public RemoteAccessVpn getRemoteAccessVpn(long vpnAddrId) {
   return _remoteAccessVpnDao.findByPublicIpAddress(vpnAddrId);
 }
 @Override
 public List<? extends RemoteAccessVpn> listRemoteAccessVpns(long networkId) {
   return _remoteAccessVpnDao.listByNetworkId(networkId);
 }
  @Override
  public Pair<List<? extends RemoteAccessVpn>, Integer> searchForRemoteAccessVpns(
      ListRemoteAccessVpnsCmd cmd) {
    // do some parameter validation
    Account caller = CallContext.current().getCallingAccount();
    Long ipAddressId = cmd.getPublicIpId();
    List<Long> permittedAccounts = new ArrayList<Long>();

    Long vpnId = cmd.getId();
    Long networkId = cmd.getNetworkId();

    if (ipAddressId != null) {
      PublicIpAddress publicIp = _networkMgr.getPublicIpAddress(ipAddressId);
      if (publicIp == null) {
        throw new InvalidParameterValueException(
            "Unable to list remote access vpns, IP address " + ipAddressId + " not found.");
      } else {
        Long ipAddrAcctId = publicIp.getAccountId();
        if (ipAddrAcctId == null) {
          throw new InvalidParameterValueException(
              "Unable to list remote access vpns, IP address "
                  + ipAddressId
                  + " is not associated with an account.");
        }
      }
      _accountMgr.checkAccess(caller, null, true, publicIp);
    }

    Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject =
        new Ternary<Long, Boolean, ListProjectResourcesCriteria>(
            cmd.getDomainId(), cmd.isRecursive(), null);
    _accountMgr.buildACLSearchParameters(
        caller,
        null,
        cmd.getAccountName(),
        cmd.getProjectId(),
        permittedAccounts,
        domainIdRecursiveListProject,
        cmd.listAll(),
        false);
    Long domainId = domainIdRecursiveListProject.first();
    Boolean isRecursive = domainIdRecursiveListProject.second();
    ListProjectResourcesCriteria listProjectResourcesCriteria =
        domainIdRecursiveListProject.third();

    Filter filter =
        new Filter(
            RemoteAccessVpnVO.class,
            "serverAddressId",
            false,
            cmd.getStartIndex(),
            cmd.getPageSizeVal());
    SearchBuilder<RemoteAccessVpnVO> sb = _remoteAccessVpnDao.createSearchBuilder();
    _accountMgr.buildACLSearchBuilder(
        sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);

    sb.and("serverAddressId", sb.entity().getServerAddressId(), Op.EQ);
    sb.and("id", sb.entity().getId(), Op.EQ);
    sb.and("networkId", sb.entity().getNetworkId(), Op.EQ);
    sb.and("state", sb.entity().getState(), Op.EQ);
    sb.and("display", sb.entity().isDisplay(), Op.EQ);

    SearchCriteria<RemoteAccessVpnVO> sc = sb.create();
    _accountMgr.buildACLSearchCriteria(
        sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);

    sc.setParameters("state", RemoteAccessVpn.State.Running);

    if (ipAddressId != null) {
      sc.setParameters("serverAddressId", ipAddressId);
    }

    if (vpnId != null) {
      sc.setParameters("id", vpnId);
    }

    if (networkId != null) {
      sc.setParameters("networkId", networkId);
    }

    Pair<List<RemoteAccessVpnVO>, Integer> result = _remoteAccessVpnDao.searchAndCount(sc, filter);
    return new Pair<List<? extends RemoteAccessVpn>, Integer>(result.first(), result.second());
  }
  @DB
  @Override
  public boolean applyVpnUsers(long vpnOwnerId, String userName) {
    Account caller = CallContext.current().getCallingAccount();
    Account owner = _accountDao.findById(vpnOwnerId);
    _accountMgr.checkAccess(caller, null, true, owner);

    s_logger.debug("Applying vpn users for " + owner);
    List<RemoteAccessVpnVO> vpns = _remoteAccessVpnDao.findByAccount(vpnOwnerId);

    List<VpnUserVO> users = _vpnUsersDao.listByAccount(vpnOwnerId);

    // If user is in Active state, we still have to resend them therefore their status has to be Add
    for (VpnUserVO user : users) {
      if (user.getState() == State.Active) {
        user.setState(State.Add);
        _vpnUsersDao.update(user.getId(), user);
      }
    }

    boolean success = true;

    boolean[] finals = new boolean[users.size()];
    for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) {
      s_logger.debug("Applying vpn access to " + element.getName());
      for (RemoteAccessVpnVO vpn : vpns) {
        try {
          String[] results = element.applyVpnUsers(vpn, users);
          if (results != null) {
            for (int i = 0; i < results.length; i++) {
              s_logger.debug(
                  "VPN User "
                      + users.get(i)
                      + (results[i] == null
                          ? " is set on "
                          : (" couldn't be set due to " + results[i]) + " on ")
                      + vpn);
              if (results[i] == null) {
                if (!finals[i]) {
                  finals[i] = true;
                }
              } else {
                finals[i] = false;
                success = false;
              }
            }
          }
        } catch (Exception e) {
          s_logger.warn("Unable to apply vpn users ", e);
          success = false;

          for (int i = 0; i < finals.length; i++) {
            finals[i] = false;
          }
        }
      }
    }

    for (int i = 0; i < finals.length; i++) {
      final VpnUserVO user = users.get(i);
      if (finals[i]) {
        if (user.getState() == State.Add) {
          user.setState(State.Active);
          _vpnUsersDao.update(user.getId(), user);
        } else if (user.getState() == State.Revoke) {
          _vpnUsersDao.remove(user.getId());
        }
      } else {
        if (user.getState() == State.Add && (user.getUsername()).equals(userName)) {
          Transaction.execute(
              new TransactionCallbackNoReturn() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) {
                  _vpnUsersDao.remove(user.getId());
                  UsageEventUtils.publishUsageEvent(
                      EventTypes.EVENT_VPN_USER_REMOVE,
                      user.getAccountId(),
                      0,
                      user.getId(),
                      user.getUsername(),
                      user.getClass().getName(),
                      user.getUuid());
                }
              });
        }
        s_logger.warn(
            "Failed to apply vpn for user "
                + user.getUsername()
                + ", accountId="
                + user.getAccountId());
      }
    }

    return success;
  }
  @Override
  @DB
  @ActionEvent(
      eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_CREATE,
      eventDescription = "creating remote access vpn",
      async = true)
  public RemoteAccessVpnVO startRemoteAccessVpn(long ipAddressId, boolean openFirewall)
      throws ResourceUnavailableException {
    Account caller = CallContext.current().getCallingAccount();

    final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddress(ipAddressId);
    if (vpn == null) {
      throw new InvalidParameterValueException("Unable to find your vpn: " + ipAddressId);
    }

    if (vpn.getVpcId() != null) {
      openFirewall = false;
    }

    _accountMgr.checkAccess(caller, null, true, vpn);

    boolean started = false;
    try {
      boolean firewallOpened = true;
      if (openFirewall) {
        firewallOpened = _firewallMgr.applyIngressFirewallRules(vpn.getServerAddressId(), caller);
      }

      if (firewallOpened) {
        for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) {
          if (element.startVpn(vpn)) {
            started = true;
            break;
          }
        }
      }

      return vpn;
    } finally {
      if (started) {
        Transaction.execute(
            new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                vpn.setState(RemoteAccessVpn.State.Running);
                _remoteAccessVpnDao.update(vpn.getId(), vpn);

                // Start billing of existing VPN users in ADD and Active state
                List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId());
                for (VpnUserVO user : vpnUsers) {
                  if (user.getState() != VpnUser.State.Revoke) {
                    UsageEventUtils.publishUsageEvent(
                        EventTypes.EVENT_VPN_USER_ADD,
                        user.getAccountId(),
                        0,
                        user.getId(),
                        user.getUsername(),
                        user.getClass().getName(),
                        user.getUuid());
                  }
                }
              }
            });
      }
    }
  }
  @Override
  @DB
  @ActionEvent(
      eventType = EventTypes.EVENT_REMOTE_ACCESS_VPN_DESTROY,
      eventDescription = "removing remote access vpn",
      async = true)
  public boolean destroyRemoteAccessVpnForIp(long ipId, Account caller, final boolean forceCleanup)
      throws ResourceUnavailableException {
    final RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddress(ipId);
    if (vpn == null) {
      s_logger.debug("there are no Remote access vpns for public ip address id=" + ipId);
      return true;
    }

    _accountMgr.checkAccess(caller, AccessType.OperateEntry, true, vpn);

    RemoteAccessVpn.State prevState = vpn.getState();
    vpn.setState(RemoteAccessVpn.State.Removed);
    _remoteAccessVpnDao.update(vpn.getId(), vpn);

    boolean success = false;
    try {
      for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) {
        if (element.stopVpn(vpn)) {
          success = true;
          break;
        }
      }
    } catch (ResourceUnavailableException ex) {
      vpn.setState(prevState);
      _remoteAccessVpnDao.update(vpn.getId(), vpn);
      s_logger.debug(
          "Failed to stop the vpn "
              + vpn.getId()
              + " , so reverted state to "
              + RemoteAccessVpn.State.Running);
      success = false;
    } finally {
      if (success || forceCleanup) {
        // Cleanup corresponding ports
        final List<? extends FirewallRule> vpnFwRules =
            _rulesDao.listByIpAndPurpose(ipId, Purpose.Vpn);

        boolean applyFirewall = false;
        final List<FirewallRuleVO> fwRules = new ArrayList<FirewallRuleVO>();
        // if related firewall rule is created for the first vpn port, it would be created for the 2
        // other ports as well, so need to cleanup the backend
        if (vpnFwRules.size() != 0
            && _rulesDao.findByRelatedId(vpnFwRules.get(0).getId()) != null) {
          applyFirewall = true;
        }

        if (applyFirewall) {
          Transaction.execute(
              new TransactionCallbackNoReturn() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) {
                  for (FirewallRule vpnFwRule : vpnFwRules) {
                    // don't apply on the backend yet; send all 3 rules in a banch
                    _firewallMgr.revokeRelatedFirewallRule(vpnFwRule.getId(), false);
                    fwRules.add(_rulesDao.findByRelatedId(vpnFwRule.getId()));
                  }

                  s_logger.debug(
                      "Marked "
                          + fwRules.size()
                          + " firewall rules as Revoked as a part of disable remote access vpn");
                }
              });

          // now apply vpn rules on the backend
          s_logger.debug(
              "Reapplying firewall rules for ip id="
                  + ipId
                  + " as a part of disable remote access vpn");
          success = _firewallMgr.applyIngressFirewallRules(ipId, caller);
        }

        if (success || forceCleanup) {
          try {
            Transaction.execute(
                new TransactionCallbackNoReturn() {
                  @Override
                  public void doInTransactionWithoutResult(TransactionStatus status) {
                    _remoteAccessVpnDao.remove(vpn.getId());
                    // Stop billing of VPN users when VPN is removed. VPN_User_ADD events will be
                    // generated when VPN is created again
                    List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId());
                    for (VpnUserVO user : vpnUsers) {
                      // VPN_USER_REMOVE event is already generated for users in Revoke state
                      if (user.getState() != VpnUser.State.Revoke) {
                        UsageEventUtils.publishUsageEvent(
                            EventTypes.EVENT_VPN_USER_REMOVE,
                            user.getAccountId(),
                            0,
                            user.getId(),
                            user.getUsername(),
                            user.getClass().getName(),
                            user.getUuid());
                      }
                    }
                    if (vpnFwRules != null) {
                      for (FirewallRule vpnFwRule : vpnFwRules) {
                        _rulesDao.remove(vpnFwRule.getId());
                        s_logger.debug(
                            "Successfully removed firewall rule with ip id="
                                + vpnFwRule.getSourceIpAddressId()
                                + " and port "
                                + vpnFwRule.getSourcePortStart().intValue()
                                + " as a part of vpn cleanup");
                      }
                    }
                  }
                });
          } catch (Exception ex) {
            s_logger.warn("Unable to release the three vpn ports from the firewall rules", ex);
          }
        }
      }
    }
    return success;
  }
  @Override
  @DB
  public RemoteAccessVpn createRemoteAccessVpn(
      final long publicIpId, String ipRange, boolean openFirewall, final Boolean forDisplay)
      throws NetworkRuleConflictException {
    CallContext ctx = CallContext.current();
    final Account caller = ctx.getCallingAccount();

    Long networkId = null;

    // make sure ip address exists
    final PublicIpAddress ipAddr = _networkMgr.getPublicIpAddress(publicIpId);
    if (ipAddr == null) {
      throw new InvalidParameterValueException(
          "Unable to create remote access vpn, invalid public IP address id" + publicIpId);
    }

    _accountMgr.checkAccess(caller, null, true, ipAddr);

    if (!ipAddr.readyToUse()) {
      throw new InvalidParameterValueException(
          "The Ip address is not ready to be used yet: " + ipAddr.getAddress());
    }

    IPAddressVO ipAddress = _ipAddressDao.findById(publicIpId);

    networkId = ipAddress.getAssociatedWithNetworkId();
    if (networkId != null) {
      _networkMgr.checkIpForService(ipAddress, Service.Vpn, null);
    }

    final Long vpcId = ipAddress.getVpcId();
    /* IP Address used for VPC must be the source NAT IP of whole VPC */
    if (vpcId != null && ipAddress.isSourceNat()) {
      assert networkId == null;
      // No firewall setting for VPC, it would be open internally
      openFirewall = false;
    }

    final boolean openFirewallFinal = openFirewall;

    if (networkId == null && vpcId == null) {
      throw new InvalidParameterValueException(
          "Unable to create remote access vpn for the ipAddress: "
              + ipAddr.getAddress().addr()
              + " as ip is not associated with any network or VPC");
    }

    RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIpId);

    if (vpnVO != null) {
      // if vpn is in Added state, return it to the api
      if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
        return vpnVO;
      }
      throw new InvalidParameterValueException(
          "A Remote Access VPN already exists for this public Ip address");
    }

    if (ipRange == null) {
      ipRange = RemoteAccessVpnClientIpRange.valueIn(ipAddr.getAccountId());
    }
    final String[] range = ipRange.split("-");
    if (range.length != 2) {
      throw new InvalidParameterValueException("Invalid ip range");
    }
    if (!NetUtils.isValidIp(range[0]) || !NetUtils.isValidIp(range[1])) {
      throw new InvalidParameterValueException("Invalid ip in range specification " + ipRange);
    }
    if (!NetUtils.validIpRange(range[0], range[1])) {
      throw new InvalidParameterValueException("Invalid ip range " + ipRange);
    }

    Pair<String, Integer> cidr = null;

    // TODO: assumes one virtual network / domr per account per zone
    if (networkId != null) {
      vpnVO = _remoteAccessVpnDao.findByAccountAndNetwork(ipAddr.getAccountId(), networkId);
      if (vpnVO != null) {
        // if vpn is in Added state, return it to the api
        if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
          return vpnVO;
        }
        throw new InvalidParameterValueException(
            "A Remote Access VPN already exists for this account");
      }
      // Verify that vpn service is enabled for the network
      Network network = _networkMgr.getNetwork(networkId);
      if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Service.Vpn)) {
        throw new InvalidParameterValueException(
            "Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId());
      }
      cidr = NetUtils.getCidr(network.getCidr());
    } else { // Don't need to check VPC because there is only one IP(source NAT IP) available for
             // VPN
      Vpc vpc = _vpcDao.findById(vpcId);
      cidr = NetUtils.getCidr(vpc.getCidr());
    }

    // FIXME: This check won't work for the case where the guest ip range
    // changes depending on the vlan allocated.
    String[] guestIpRange = NetUtils.getIpRangeFromCidr(cidr.first(), cidr.second());
    if (NetUtils.ipRangesOverlap(range[0], range[1], guestIpRange[0], guestIpRange[1])) {
      throw new InvalidParameterValueException(
          "Invalid ip range: "
              + ipRange
              + " overlaps with guest ip range "
              + guestIpRange[0]
              + "-"
              + guestIpRange[1]);
    }
    // TODO: check sufficient range
    // TODO: check overlap with private and public ip ranges in datacenter

    long startIp = NetUtils.ip2Long(range[0]);
    final String newIpRange = NetUtils.long2Ip(++startIp) + "-" + range[1];
    final String sharedSecret = PasswordGenerator.generatePresharedKey(_pskLength);

    return Transaction.execute(
        new TransactionCallbackWithException<RemoteAccessVpn, NetworkRuleConflictException>() {
          @Override
          public RemoteAccessVpn doInTransaction(TransactionStatus status)
              throws NetworkRuleConflictException {
            if (vpcId == null) {
              _rulesMgr.reservePorts(
                  ipAddr,
                  NetUtils.UDP_PROTO,
                  Purpose.Vpn,
                  openFirewallFinal,
                  caller,
                  NetUtils.VPN_PORT,
                  NetUtils.VPN_L2TP_PORT,
                  NetUtils.VPN_NATT_PORT);
            }
            RemoteAccessVpnVO vpnVO =
                new RemoteAccessVpnVO(
                    ipAddr.getAccountId(),
                    ipAddr.getDomainId(),
                    ipAddr.getAssociatedWithNetworkId(),
                    publicIpId,
                    vpcId,
                    range[0],
                    newIpRange,
                    sharedSecret);

            if (forDisplay != null) {
              vpnVO.setDisplay(forDisplay);
            }
            return _remoteAccessVpnDao.persist(vpnVO);
          }
        });
  }
  @Override
  @DB
  public void destroyRemoteAccessVpn(long ipId, Account caller)
      throws ResourceUnavailableException {
    RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findById(ipId);
    if (vpn == null) {
      s_logger.debug("vpn id=" + ipId + " does not exists ");
      return;
    }

    _accountMgr.checkAccess(caller, null, true, vpn);

    Network network = _networkMgr.getNetwork(vpn.getNetworkId());

    vpn.setState(RemoteAccessVpn.State.Removed);
    _remoteAccessVpnDao.update(vpn.getServerAddressId(), vpn);

    boolean success = false;
    try {
      for (RemoteAccessVPNServiceProvider element : _vpnServiceProviders) {
        if (element.stopVpn(network, vpn)) {
          success = true;
          break;
        }
      }
    } finally {
      if (success) {
        // Cleanup corresponding ports
        List<? extends FirewallRule> vpnFwRules = _rulesDao.listByIpAndPurpose(ipId, Purpose.Vpn);
        Transaction txn = Transaction.currentTxn();

        boolean applyFirewall = false;
        List<FirewallRuleVO> fwRules = new ArrayList<FirewallRuleVO>();
        // if related firewall rule is created for the first vpn port, it would be created for the 2
        // other ports as well, so need to cleanup the backend
        if (_rulesDao.findByRelatedId(vpnFwRules.get(0).getId()) != null) {
          applyFirewall = true;
        }

        if (applyFirewall) {
          txn.start();

          for (FirewallRule vpnFwRule : vpnFwRules) {
            // don't apply on the backend yet; send all 3 rules in a banch
            _firewallMgr.revokeRelatedFirewallRule(vpnFwRule.getId(), false);
            fwRules.add(_rulesDao.findByRelatedId(vpnFwRule.getId()));
          }

          s_logger.debug(
              "Marked "
                  + fwRules.size()
                  + " firewall rules as Revoked as a part of disable remote access vpn");

          txn.commit();

          // now apply vpn rules on the backend
          s_logger.debug(
              "Reapplying firewall rules for ip id="
                  + ipId
                  + " as a part of disable remote access vpn");
          success = _firewallMgr.applyIngressFirewallRules(ipId, caller);
        }

        if (success) {
          try {
            txn.start();
            _remoteAccessVpnDao.remove(ipId);
            // Stop billing of VPN users when VPN is removed. VPN_User_ADD events will be generated
            // when VPN is created again
            List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId());
            for (VpnUserVO user : vpnUsers) {
              // VPN_USER_REMOVE event is already generated for users in Revoke state
              if (user.getState() != VpnUser.State.Revoke) {
                UsageEventUtils.publishUsageEvent(
                    EventTypes.EVENT_VPN_USER_REMOVE,
                    user.getAccountId(),
                    0,
                    user.getId(),
                    user.getUsername(),
                    user.getClass().getName(),
                    user.getUuid());
              }
            }
            if (vpnFwRules != null) {
              for (FirewallRule vpnFwRule : vpnFwRules) {
                _rulesDao.remove(vpnFwRule.getId());
                s_logger.debug(
                    "Successfully removed firewall rule with ip id="
                        + vpnFwRule.getSourceIpAddressId()
                        + " and port "
                        + vpnFwRule.getSourcePortStart()
                        + " as a part of vpn cleanup");
              }
            }
            txn.commit();
          } catch (Exception ex) {
            txn.rollback();
            s_logger.warn("Unable to release the three vpn ports from the firewall rules", ex);
          }
        }
      }
    }
  }
  @Override
  public RemoteAccessVpn createRemoteAccessVpn(
      long publicIpId, String ipRange, boolean openFirewall, long networkId)
      throws NetworkRuleConflictException {
    UserContext ctx = UserContext.current();
    Account caller = ctx.getCaller();

    // make sure ip address exists
    PublicIpAddress ipAddr = _networkMgr.getPublicIpAddress(publicIpId);
    if (ipAddr == null) {
      throw new InvalidParameterValueException(
          "Unable to create remote access vpn, invalid public IP address id" + publicIpId);
    }

    _accountMgr.checkAccess(caller, null, true, ipAddr);

    if (!ipAddr.readyToUse()) {
      throw new InvalidParameterValueException(
          "The Ip address is not ready to be used yet: " + ipAddr.getAddress());
    }

    IPAddressVO ipAddress = _ipAddressDao.findById(publicIpId);
    _networkMgr.checkIpForService(ipAddress, Service.Vpn, null);

    RemoteAccessVpnVO vpnVO = _remoteAccessVpnDao.findByPublicIpAddress(publicIpId);

    if (vpnVO != null) {
      // if vpn is in Added state, return it to the api
      if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
        return vpnVO;
      }
      throw new InvalidParameterValueException(
          "A Remote Access VPN already exists for this public Ip address");
    }

    // TODO: assumes one virtual network / domr per account per zone
    vpnVO = _remoteAccessVpnDao.findByAccountAndNetwork(ipAddr.getAccountId(), networkId);
    if (vpnVO != null) {
      // if vpn is in Added state, return it to the api
      if (vpnVO.getState() == RemoteAccessVpn.State.Added) {
        return vpnVO;
      }
      throw new InvalidParameterValueException(
          "A Remote Access VPN already exists for this account");
    }

    // Verify that vpn service is enabled for the network
    Network network = _networkMgr.getNetwork(networkId);
    if (!_networkMgr.areServicesSupportedInNetwork(network.getId(), Service.Vpn)) {
      throw new InvalidParameterValueException(
          "Vpn service is not supported in network id=" + ipAddr.getAssociatedWithNetworkId());
    }

    if (ipRange == null) {
      ipRange = _clientIpRange;
    }
    String[] range = ipRange.split("-");
    if (range.length != 2) {
      throw new InvalidParameterValueException("Invalid ip range");
    }
    if (!NetUtils.isValidIp(range[0]) || !NetUtils.isValidIp(range[1])) {
      throw new InvalidParameterValueException("Invalid ip in range specification " + ipRange);
    }
    if (!NetUtils.validIpRange(range[0], range[1])) {
      throw new InvalidParameterValueException("Invalid ip range " + ipRange);
    }

    Pair<String, Integer> cidr = NetUtils.getCidr(network.getCidr());

    // FIXME: This check won't work for the case where the guest ip range
    // changes depending on the vlan allocated.
    String[] guestIpRange = NetUtils.getIpRangeFromCidr(cidr.first(), cidr.second());
    if (NetUtils.ipRangesOverlap(range[0], range[1], guestIpRange[0], guestIpRange[1])) {
      throw new InvalidParameterValueException(
          "Invalid ip range: "
              + ipRange
              + " overlaps with guest ip range "
              + guestIpRange[0]
              + "-"
              + guestIpRange[1]);
    }
    // TODO: check sufficient range
    // TODO: check overlap with private and public ip ranges in datacenter

    long startIp = NetUtils.ip2Long(range[0]);
    String newIpRange = NetUtils.long2Ip(++startIp) + "-" + range[1];
    String sharedSecret = PasswordGenerator.generatePresharedKey(_pskLength);
    _rulesMgr.reservePorts(
        ipAddr,
        NetUtils.UDP_PROTO,
        Purpose.Vpn,
        openFirewall,
        caller,
        NetUtils.VPN_PORT,
        NetUtils.VPN_L2TP_PORT,
        NetUtils.VPN_NATT_PORT);
    vpnVO =
        new RemoteAccessVpnVO(
            ipAddr.getAccountId(),
            ipAddr.getDomainId(),
            ipAddr.getAssociatedWithNetworkId(),
            publicIpId,
            range[0],
            newIpRange,
            sharedSecret);
    return _remoteAccessVpnDao.persist(vpnVO);
  }