@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));
            }
          }
        });
  }
  @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 applyEip(final EipStruct struct, final Completion completion) {
    L3NetworkVO l3vo = dbf.findByUuid(struct.getNic().getL3NetworkUuid(), L3NetworkVO.class);
    final L3NetworkInventory l3inv = L3NetworkInventory.valueOf(l3vo);
    vrMgr.acquireVirtualRouterVm(
        l3inv,
        new VirtualRouterOfferingValidator() {
          @Override
          public void validate(VirtualRouterOfferingInventory offering)
              throws OperationFailureException {
            if (!offering.getPublicNetworkUuid().equals(struct.getVip().getL3NetworkUuid())) {
              throw new OperationFailureException(
                  errf.stringToOperationError(
                      String.format(
                          "found a virtual router offering[uuid:%s] for L3Network[uuid:%s] in zone[uuid:%s]; however, the network's public network[uuid:%s] is not the same to EIP[uuid:%s]'s; you may need to use system tag"
                              + " guestL3Network::l3NetworkUuid to specify a particular virtual router offering for the L3Network",
                          offering.getUuid(),
                          l3inv.getUuid(),
                          l3inv.getZoneUuid(),
                          struct.getVip().getL3NetworkUuid(),
                          struct.getEip().getUuid())));
            }
          }
        },
        new ReturnValueCompletion<VirtualRouterVmInventory>(completion) {
          @Override
          public void success(final VirtualRouterVmInventory vr) {
            applyEip(
                vr,
                struct,
                new Completion() {
                  @Override
                  public void success() {
                    SimpleQuery<VirtualRouterEipRefVO> q =
                        dbf.createQuery(VirtualRouterEipRefVO.class);
                    q.add(VirtualRouterEipRefVO_.eipUuid, Op.EQ, struct.getEip().getUuid());
                    if (!q.isExists()) {
                      // if virtual router is stopped outside zstack (e.g. the host rebbot)
                      // database will still have VirtualRouterEipRefVO for this EIP.
                      // in this case, don't create the record again
                      VirtualRouterEipRefVO ref = new VirtualRouterEipRefVO();
                      ref.setEipUuid(struct.getEip().getUuid());
                      ref.setVirtualRouterVmUuid(vr.getUuid());
                      dbf.persist(ref);
                    }
                    completion.success();
                  }

                  @Override
                  public void fail(ErrorCode errorCode) {
                    completion.fail(errorCode);
                  }
                });
          }

          @Override
          public void fail(ErrorCode errorCode) {
            completion.fail(errorCode);
          }
        });
  }