@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
  @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;
  }
  @DB
  public LoadBalancer createLoadBalancer(CreateLoadBalancerRuleCmd lb, boolean openFirewall)
      throws NetworkRuleConflictException {
    long ipId = lb.getSourceIpAddressId();
    UserContext caller = UserContext.current();
    int srcPortStart = lb.getSourcePortStart();
    int defPortStart = lb.getDefaultPortStart();
    int srcPortEnd = lb.getSourcePortEnd();

    IPAddressVO ipAddr = _ipAddressDao.findById(lb.getSourceIpAddressId());
    Long networkId = ipAddr.getSourceNetworkId();
    // make sure ip address exists
    if (ipAddr == null || !ipAddr.readyToUse()) {
      throw new InvalidParameterValueException(
          "Unable to create load balancer rule, invalid IP address id" + ipId);
    }

    _firewallMgr.validateFirewallRule(
        caller.getCaller(),
        ipAddr,
        srcPortStart,
        srcPortEnd,
        lb.getProtocol(),
        Purpose.LoadBalancing);

    networkId = ipAddr.getAssociatedWithNetworkId();
    if (networkId == null) {
      throw new InvalidParameterValueException(
          "Unable to create load balancer rule ; ip id="
              + ipId
              + " is not associated with any network");
    }
    NetworkVO network = _networkDao.findById(networkId);

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

    // verify that lb service is supported by the network
    if (!_networkMgr.isServiceSupported(network.getNetworkOfferingId(), Service.Lb)) {
      throw new InvalidParameterValueException(
          "LB service is not supported in network id= " + networkId);
    }

    Transaction txn = Transaction.currentTxn();
    txn.start();

    LoadBalancerVO newRule =
        new LoadBalancerVO(
            lb.getXid(),
            lb.getName(),
            lb.getDescription(),
            lb.getSourceIpAddressId(),
            lb.getSourcePortEnd(),
            lb.getDefaultPortStart(),
            lb.getAlgorithm(),
            network.getId(),
            ipAddr.getAccountId(),
            ipAddr.getDomainId());

    newRule = _lbDao.persist(newRule);

    if (openFirewall) {
      _firewallMgr.createRuleForAllCidrs(
          ipId,
          caller.getCaller(),
          lb.getSourcePortStart(),
          lb.getSourcePortEnd(),
          lb.getProtocol(),
          null,
          null,
          newRule.getId());
    }

    boolean success = true;

    try {
      _firewallMgr.detectRulesConflict(newRule, ipAddr);
      if (!_firewallDao.setStateToAdd(newRule)) {
        throw new CloudRuntimeException("Unable to update the state to add for " + newRule);
      }
      s_logger.debug(
          "Load balancer "
              + newRule.getId()
              + " for Ip address id="
              + ipId
              + ", public port "
              + srcPortStart
              + ", private port "
              + defPortStart
              + " is added successfully.");
      UserContext.current().setEventDetails("Load balancer Id: " + newRule.getId());
      UsageEventVO usageEvent =
          new UsageEventVO(
              EventTypes.EVENT_LOAD_BALANCER_CREATE,
              ipAddr.getAllocatedToAccountId(),
              ipAddr.getDataCenterId(),
              newRule.getId(),
              null);
      _usageEventDao.persist(usageEvent);
      txn.commit();

      return newRule;
    } catch (Exception e) {
      success = false;
      if (e instanceof NetworkRuleConflictException) {
        throw (NetworkRuleConflictException) e;
      }
      throw new CloudRuntimeException(
          "Unable to add rule for ip address id=" + newRule.getSourceIpAddressId(), e);
    } finally {
      if (!success && newRule != null) {

        txn.start();
        _firewallMgr.revokeRelatedFirewallRule(newRule.getId(), false);
        _lbDao.remove(newRule.getId());

        txn.commit();
      }
    }
  }
  @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);
          }
        }
      }
    }
  }