@Transactional(readOnly = true)
  private Collection<PortForwardingRuleTO> calculateAllRules(
      Map<String, PortForwardingRuleVO> ruleMap, String vrUuid) {
    String sql =
        "select rule.uuid, nic.ip, vip.ip from PortForwardingRuleVO rule, VmNicVO nic, VipVO vip where rule.vmNicUuid = nic.uuid and rule.uuid in (:ruleUuids) and vip.uuid = rule.vipUuid";
    TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
    q.setParameter("ruleUuids", ruleMap.keySet());
    List<Tuple> privateIps = q.getResultList();

    Map<String, PortForwardingRuleTO> tos = new HashMap<String, PortForwardingRuleTO>();
    for (Tuple t : privateIps) {
      String ruleUuid = t.get(0, String.class);
      PortForwardingRuleTO to = new PortForwardingRuleTO();
      to.setPrivateIp(t.get(1, String.class));

      PortForwardingRuleVO ruleVO = ruleMap.get(ruleUuid);
      to.setAllowedCidr(ruleVO.getAllowedCidr());
      to.setPrivatePortEnd(ruleVO.getPrivatePortEnd());
      to.setPrivatePortStart(ruleVO.getPrivatePortStart());
      to.setVipPortEnd(ruleVO.getVipPortEnd());
      to.setSnatInboundTraffic(
          PortForwardingGlobalConfig.SNAT_INBOUND_TRAFFIC.value(Boolean.class));
      to.setVipPortStart(ruleVO.getVipPortStart());
      to.setVipIp(t.get(2, String.class));
      to.setProtocolType(ruleVO.getProtocolType().toString());
      tos.put(ruleUuid, to);
    }

    assert tos.size() == ruleMap.size();

    sql =
        "select rule.uuid, vrnic.mac from PortForwardingRuleVO rule, VmNicVO vrnic, VmNicVO nic2, ApplianceVmVO vr where vr.uuid = vrnic.vmInstanceUuid and vrnic.l3NetworkUuid = nic2.l3NetworkUuid and nic2.uuid = rule.vmNicUuid and rule.uuid in (:ruleUuids) and vr.uuid = :vrUuid";
    TypedQuery<Tuple> privateMacQuery = dbf.getEntityManager().createQuery(sql, Tuple.class);
    privateMacQuery.setParameter("ruleUuids", ruleMap.keySet());
    privateMacQuery.setParameter("vrUuid", vrUuid);
    List<Tuple> privateMacs = privateMacQuery.getResultList();
    for (Tuple t : privateMacs) {
      String ruleUuid = t.get(0, String.class);
      PortForwardingRuleTO to = tos.get(ruleUuid);
      to.setPrivateMac(t.get(1, String.class));
    }

    return tos.values();
  }
  @Override
  public void rollback(FlowTrigger chain, Map data) {
    List<VirtualRouterPortForwardingRuleRefVO> refs =
        (List<VirtualRouterPortForwardingRuleRefVO>)
            data.get(VirtualRouterSyncPortForwardingRulesOnStartFlow.class.getName());
    if (refs != null) {
      dbf.removeCollection(refs, VirtualRouterPortForwardingRuleRefVO.class);
    }

    chain.rollback();
  }
  @Override
  public void run(final FlowTrigger chain, final Map data) {
    final VirtualRouterVmInventory vr =
        (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());

    List<String> nwServed = vr.getGuestL3Networks();
    List<String> l3Uuids =
        vrMgr.selectL3NetworksNeedingSpecificNetworkService(nwServed, NetworkServiceType.DNS);
    if (l3Uuids.isEmpty()) {
      chain.next();
      return;
    }

    if (VirtualRouterSystemTags.DEDICATED_ROLE_VR.hasTag(vr.getUuid())
        && !VirtualRouterSystemTags.VR_DNS_ROLE.hasTag(vr.getUuid())) {
      chain.next();
      return;
    }

    new VirtualRouterRoleManager().makeDnsRole(vr.getUuid());

    SimpleQuery<L3NetworkDnsVO> query = dbf.createQuery(L3NetworkDnsVO.class);
    query.select(L3NetworkDnsVO_.dns);
    query.add(L3NetworkDnsVO_.l3NetworkUuid, Op.IN, l3Uuids);
    List<String> lst = query.listValue();
    if (lst.isEmpty()) {
      chain.next();
      return;
    }

    Set<String> dnsAddresses = new HashSet<String>(lst.size());
    dnsAddresses.addAll(lst);

    final List<DnsInfo> dns = new ArrayList<DnsInfo>(dnsAddresses.size());
    for (String d : dnsAddresses) {
      DnsInfo dinfo = new DnsInfo();
      dinfo.setDnsAddress(d);
      dns.add(dinfo);
    }

    SetDnsCmd cmd = new SetDnsCmd();
    cmd.setDns(dns);

    VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg();
    msg.setVmInstanceUuid(vr.getUuid());
    msg.setPath(VirtualRouterConstant.VR_SET_DNS_PATH);
    msg.setCommand(cmd);
    msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "5m"));
    bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid());
    bus.send(
        msg,
        new CloudBusCallBack(chain) {
          @Override
          public void run(MessageReply reply) {
            if (!reply.isSuccess()) {
              chain.fail(reply.getError());
              return;
            }

            VirtualRouterAsyncHttpCallReply re = reply.castReply();
            SetDnsRsp ret = re.toResponse(SetDnsRsp.class);
            if (ret.isSuccess()) {
              chain.next();
            } else {
              String err =
                  String.format(
                      "virtual router[name: %s, uuid: %s] failed to configure dns%s, %s ",
                      vr.getName(), vr.getUuid(), JSONObjectUtil.toJsonString(dns), ret.getError());
              logger.warn(err);
              chain.fail(errf.stringToOperationError(err));
            }
          }
        });
  }
  @Override
  public void run(final FlowTrigger chain, Map data) {
    final VirtualRouterVmInventory vr =
        (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());
    VmNicInventory guestNic = vr.getGuestNic();
    if (!vrMgr.isL3NetworkNeedingNetworkServiceByVirtualRouter(
        guestNic.getL3NetworkUuid(), PortForwardingConstant.PORTFORWARDING_NETWORK_SERVICE_TYPE)) {
      chain.next();
      return;
    }

    boolean isNewCreated = data.containsKey(Param.IS_NEW_CREATED.toString());

    List<PortForwardingRuleVO> ruleVOs = findRulesForThisRouter(vr, data, isNewCreated);
    if (ruleVOs.isEmpty()) {
      chain.next();
      return;
    }

    Map<String, PortForwardingRuleVO> ruleMap =
        new HashMap<String, PortForwardingRuleVO>(ruleVOs.size());
    for (PortForwardingRuleVO rvo : ruleVOs) {
      ruleMap.put(rvo.getUuid(), rvo);
    }

    Collection<PortForwardingRuleTO> tos = calculateAllRules(ruleMap, vr.getUuid());
    List<PortForwardingRuleTO> toList = new ArrayList<PortForwardingRuleTO>(tos.size());
    toList.addAll(tos);

    SyncPortForwardingRuleCmd cmd = new SyncPortForwardingRuleCmd();
    cmd.setRules(toList);

    VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg();
    msg.setCommand(cmd);
    msg.setPath(VirtualRouterConstant.VR_SYNC_PORT_FORWARDING);
    msg.setVmInstanceUuid(vr.getUuid());
    bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid());
    bus.send(
        msg,
        new CloudBusCallBack(chain) {
          @Override
          public void run(MessageReply reply) {
            if (!reply.isSuccess()) {
              chain.fail(reply.getError());
              return;
            }

            VirtualRouterAsyncHttpCallReply re = reply.castReply();
            SyncPortForwardingRuleRsp ret = re.toResponse(SyncPortForwardingRuleRsp.class);
            if (ret.isSuccess()) {
              String info =
                  String.format(
                      "successfully sync port forwarding rules served by virtual router[name: %s uuid: %s]",
                      vr.getName(), vr.getUuid());
              logger.debug(info);
              chain.next();
            } else {
              String err =
                  String.format(
                      "failed to sync port forwarding rules served by virtual router[name: %s, uuid: %s], because %s",
                      vr.getName(), vr.getUuid(), ret.getError());
              logger.warn(err);
              chain.fail(errf.stringToOperationError(err));
            }
          }
        });
  }