content: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  package org.apache.cloudstack.network.tungsten.service;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.Listener;
  import com.cloud.agent.api.AgentControlAnswer;
  import com.cloud.agent.api.AgentControlCommand;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.Command;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.to.LoadBalancerTO;
  import com.cloud.api.ApiDBUtils;
  import com.cloud.configuration.Config;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.HostPodVO;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.HostPodDao;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.deploy.DeployDestination;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.Status;
  import com.cloud.host.dao.HostDao;
  import com.cloud.hypervisor.Hypervisor;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.Network;
  import com.cloud.network.NetworkMigrationResponder;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks;
  import com.cloud.network.PhysicalNetworkServiceProvider;
  import com.cloud.network.PublicIpAddress;
  import com.cloud.network.TungstenGuestNetworkIpAddressVO;
  import com.cloud.network.TungstenProvider;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.dao.LoadBalancerCertMapDao;
  import com.cloud.network.dao.LoadBalancerCertMapVO;
  import com.cloud.network.dao.LoadBalancerDao;
  import com.cloud.network.dao.LoadBalancerVMMapDao;
  import com.cloud.network.dao.LoadBalancerVMMapVO;
  import com.cloud.network.dao.LoadBalancerVO;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkDetailVO;
  import com.cloud.network.dao.NetworkDetailsDao;
  import com.cloud.network.dao.NetworkServiceMapDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao;
  import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO;
  import com.cloud.network.dao.TungstenGuestNetworkIpAddressDao;
  import com.cloud.network.dao.TungstenProviderDao;
  import com.cloud.network.element.DnsServiceProvider;
  import com.cloud.network.element.FirewallServiceProvider;
  import com.cloud.network.element.IpDeployer;
  import com.cloud.network.element.LoadBalancingServiceProvider;
  import com.cloud.network.element.PortForwardingServiceProvider;
  import com.cloud.network.element.StaticNatServiceProvider;
  import com.cloud.network.element.TungstenProviderVO;
  import com.cloud.network.lb.LoadBalancingRule;
  import com.cloud.network.rules.FirewallRule;
  import com.cloud.network.rules.LbStickinessMethod;
  import com.cloud.network.rules.LoadBalancerContainer;
  import com.cloud.network.rules.PortForwardingRule;
  import com.cloud.network.rules.StaticNat;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.resource.ResourceManager;
  import com.cloud.resource.ResourceStateAdapter;
  import com.cloud.resource.ServerResource;
  import com.cloud.user.AccountManager;
  import com.cloud.uservm.UserVm;
  import com.cloud.utils.Pair;
  import com.cloud.utils.TungstenUtils;
  import com.cloud.utils.component.AdapterBase;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.fsm.StateListener;
  import com.cloud.utils.fsm.StateMachine2;
  import com.cloud.utils.net.Ip;
  import com.cloud.utils.net.NetUtils;
  import com.cloud.vm.Nic;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.ReservationContext;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.VirtualMachineProfile;
  import com.cloud.vm.dao.NicDao;
  import com.cloud.vm.dao.VMInstanceDao;
  import com.google.gson.Gson;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.framework.messagebus.MessageBus;
  import org.apache.cloudstack.framework.messagebus.PublishScope;
  import org.apache.cloudstack.network.tungsten.agent.api.ApplyTungstenNetworkPolicyCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.ApplyTungstenPortForwardingCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.AssignTungstenFloatingIpCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.CreateTungstenNetworkLoadbalancerCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.CreateTungstenNetworkPolicyCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.CreateTungstenVirtualMachineCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenFloatingIpCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenLoadBalancerCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenLoadBalancerListenerCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenNetworkPolicyCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenVRouterPortCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenVmCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.DeleteTungstenVmInterfaceCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.ReleaseTungstenFloatingIpCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.SetupTfRouteCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.SetupTungstenVRouterCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.StartupTungstenCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.TungstenAnswer;
  import org.apache.cloudstack.network.tungsten.agent.api.TungstenCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerHealthMonitorCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerMemberCommand;
  import org.apache.cloudstack.network.tungsten.agent.api.UpdateTungstenLoadBalancerPoolCommand;
  import org.apache.cloudstack.network.tungsten.dao.TungstenFabricLBHealthMonitorDao;
  import org.apache.cloudstack.network.tungsten.dao.TungstenFabricLBHealthMonitorVO;
  import org.apache.cloudstack.network.tungsten.model.TungstenLoadBalancerMember;
  import org.apache.cloudstack.network.tungsten.model.TungstenRule;
  import org.apache.commons.lang3.StringUtils;
  import org.springframework.stereotype.Component;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  @Component
  public class TungstenElement extends AdapterBase
      implements StaticNatServiceProvider, IpDeployer, FirewallServiceProvider,
      LoadBalancingServiceProvider, PortForwardingServiceProvider, ResourceStateAdapter, DnsServiceProvider, Listener,
      StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualMachine>, NetworkMigrationResponder {
  
      private static final String NETWORK = "network";
  
      private final Map<Network.Service, Map<Network.Capability, String>> capabilities = initCapabilities();
      @Inject
      HostPodDao podDao;
      @Inject
      DataCenterDao dataCenterDao;
      @Inject
      NetworkModel networkModel;
      @Inject
      NetworkDao networkDao;
      @Inject
      AccountManager accountMgr;
      @Inject
      ResourceManager resourceMgr;
      @Inject
      IPAddressDao ipAddressDao;
      @Inject
      IpAddressManager ipAddressMgr;
      @Inject
      PhysicalNetworkDao physicalNetworkDao;
      @Inject
      VMInstanceDao vmInstanceDao;
      @Inject
      AgentManager agentMgr;
      @Inject
      ConfigurationDao configDao;
      @Inject
      VlanDao vlanDao;
      @Inject
      HostDao hostDao;
      @Inject
      NicDao nicDao;
      @Inject
      NetworkServiceMapDao networkServiceMapDao;
      @Inject
      NetworkDetailsDao networkDetailsDao;
      @Inject
      LoadBalancerVMMapDao lbVmMapDao;
      @Inject
      LoadBalancerDao lbDao;
      @Inject
      TungstenService tungstenService;
      @Inject
      MessageBus messageBus;
      @Inject
      TungstenProviderDao tungstenProviderDao;
      @Inject
      TungstenGuestNetworkIpAddressDao tungstenGuestNetworkIpAddressDao;
      @Inject
      TungstenFabricUtils tungstenFabricUtils;
      @Inject
      PhysicalNetworkTrafficTypeDao physicalNetworkTrafficTypeDao;
      @Inject
      TungstenFabricLBHealthMonitorDao tungstenFabricLBHealthMonitorDao;
      @Inject
      LoadBalancerCertMapDao loadBalancerCertMapDao;
  
      private static Map<Network.Service, Map<Network.Capability, String>> initCapabilities() {
          Map<Network.Service, Map<Network.Capability, String>> capabilities = new HashMap<>();
          Map<Network.Capability, String> dhcpCapabilities = new HashMap<>();
          dhcpCapabilities.put(Network.Capability.DhcpAccrossMultipleSubnets, "true");
          capabilities.put(Network.Service.Dhcp, dhcpCapabilities);
          Map<Network.Capability, String> sourceNatCapabilities = new HashMap<>();
          sourceNatCapabilities.put(Network.Capability.RedundantRouter, "true");
          sourceNatCapabilities.put(Network.Capability.SupportedSourceNatTypes, "peraccount");
          capabilities.put(Network.Service.SourceNat, sourceNatCapabilities);
          capabilities.put(Network.Service.Connectivity, null);
          capabilities.put(Network.Service.SecurityGroup, null);
          capabilities.put(Network.Service.StaticNat, null);
          Map<Network.Capability, String> dnsCapabilities = new HashMap<>();
          dnsCapabilities.put(Network.Capability.AllowDnsSuffixModification, "true");
          capabilities.put(Network.Service.Dns, dnsCapabilities);
          Map<Network.Capability, String> firewallCapabilities = new HashMap<>();
          firewallCapabilities.put(Network.Capability.SupportedProtocols, "tcp,udp,icmp");
          firewallCapabilities.put(Network.Capability.SupportedEgressProtocols, "tcp,udp,icmp,all");
          firewallCapabilities.put(Network.Capability.MultipleIps, "true");
          firewallCapabilities.put(Network.Capability.TrafficStatistics, "per public ip");
          firewallCapabilities.put(Network.Capability.SupportedTrafficDirection, "ingress, egress");
          capabilities.put(Network.Service.Firewall, firewallCapabilities);
  
          Map<Network.Capability, String> lbCapabilities = new HashMap<>();
          lbCapabilities.put(Network.Capability.SupportedLBAlgorithms, "roundrobin,leastconn,source");
          lbCapabilities.put(Network.Capability.SupportedLBIsolation, "dedicated");
          lbCapabilities.put(Network.Capability.SupportedProtocols, "tcp,udp,http,ssl");
          lbCapabilities.put(Network.Capability.TrafficStatistics, "per public ip");
          lbCapabilities.put(Network.Capability.LoadBalancingSupportedIps, "additional");
          lbCapabilities.put(Network.Capability.LbSchemes, LoadBalancerContainer.Scheme.Public.toString());
          lbCapabilities.put(Network.Capability.SslTermination, "true");
  
          LbStickinessMethod method;
          List<LbStickinessMethod> methodList = new ArrayList<>();
          method = new LbStickinessMethod(LbStickinessMethod.StickinessMethodType.LBCookieBased,
              "This is cookie based sticky method, can be used only for http");
          methodList.add(method);
  
          method = new LbStickinessMethod(LbStickinessMethod.StickinessMethodType.AppCookieBased,
              "This is app session based sticky method, can be used only for http");
          methodList.add(method);
          method.addParam("name", true, "cookie name passed in http header by apllication to the client", false);
  
          method = new LbStickinessMethod(LbStickinessMethod.StickinessMethodType.SourceBased,
              "This is source based sticky method, can be used for any type of protocol.");
          methodList.add(method);
  
          Gson gson = new Gson();
          String stickyMethodList = gson.toJson(methodList);
          lbCapabilities.put(Network.Capability.SupportedStickinessMethods, stickyMethodList);
  
          lbCapabilities.put(Network.Capability.HealthCheckPolicy, "true");
          capabilities.put(Network.Service.Lb, lbCapabilities);
          capabilities.put(Network.Service.PortForwarding, null);
  
          return capabilities;
      }
  
      protected boolean canHandle(Network network, Network.Service service) {
          logger.debug(String.format("Checking if TungstenElement can handle service %s on network %s", service.getName(), network));
  
          if (!networkModel.isProviderForNetwork(getProvider(), network.getId())) {
              logger.debug(String.format("TungstenElement is not a provider for network %s", network));
              return false;
          }
  
          return true;
      }
  
      @Override
      public boolean applyStaticNats(Network config, List<? extends StaticNat> rules) {
          for(StaticNat staticNat : rules) {
              long sourceIpAddressId = staticNat.getSourceIpAddressId();
              IPAddressVO ipAddressVO = ipAddressDao.findByIdIncludingRemoved(sourceIpAddressId);
              VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(ipAddressVO.getAssociatedWithVmId());
              // floating ip is released when nic was deleted
              if (vm == null || networkModel.getNicInNetworkIncludingRemoved(vm.getId(), config.getId()) == null) {
                  continue;
              }
              Nic nic = networkModel.getNicInNetworkIncludingRemoved(vm.getId(), config.getId());
              Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(config.getDataCenterId(), Networks.TrafficType.Public);
              if (!staticNat.isForRevoke()) {
                  TungstenCommand assignTungstenFloatingIpCommand = new AssignTungstenFloatingIpCommand(publicNetwork.getUuid(), nic.getUuid(),
                      TungstenUtils.getFloatingIpPoolName(config.getDataCenterId()), TungstenUtils.getFloatingIpName(ipAddressVO.getId()), nic.getIPv4Address());
                  TungstenAnswer assignFloatingIpAnswer = tungstenFabricUtils.sendTungstenCommand(
                      assignTungstenFloatingIpCommand, config.getDataCenterId());
                  return assignFloatingIpAnswer.getResult();
              } else {
                  TungstenCommand releaseTungstenFloatingIpCommand = new ReleaseTungstenFloatingIpCommand(publicNetwork.getUuid(), TungstenUtils.getFloatingIpPoolName(config.getDataCenterId()),
                      TungstenUtils.getFloatingIpName(ipAddressVO.getId()));
                  TungstenAnswer releaseFloatingIpAnswer = tungstenFabricUtils.sendTungstenCommand(
                      releaseTungstenFloatingIpCommand, config.getDataCenterId());
                  return releaseFloatingIpAnswer.getResult();
              }
          }
  
          return true;
      }
  
      @Override
      public boolean applyLBRules(Network network, List<LoadBalancingRule> rules) {
          Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(network.getDataCenterId(),
              Networks.TrafficType.Public);
          for (LoadBalancingRule loadBalancingRule : rules) {
              String tungstenProjectFqn = tungstenService.getTungstenProjectFqn(network);
              LoadBalancerCertMapVO loadBalancerCertMapVO = loadBalancerCertMapDao.findByLbRuleId(loadBalancingRule.getId());
              if ((loadBalancerCertMapVO == null || !loadBalancerCertMapVO.isRevoke()) && loadBalancingRule.getState() == FirewallRule.State.Add) {
                  String protocol = StringUtils.upperCase(loadBalancingRule.getLbProtocol());
                  if (loadBalancingRule.getSourcePortStart() == NetUtils.HTTP_PORT
                      || loadBalancingRule.getSourcePortStart() == NetUtils.HTTPS_PORT) {
                      protocol = "HTTP";
                  }
  
                  IPAddressVO ipAddressVO = ipAddressDao.findByIpAndDcId(network.getDataCenterId(),
                      loadBalancingRule.getSourceIp().addr());
  
                  List<TungstenLoadBalancerMember> memberIp = createTungstenLoadBalancerMember(loadBalancingRule);
  
                  createLoadBalancer(network, publicNetwork, loadBalancingRule, ipAddressVO, memberIp, tungstenProjectFqn, protocol);
  
                  updateLBHealthMonitor(network, loadBalancingRule, ipAddressVO, tungstenProjectFqn);
  
                  updateLBPool(network, loadBalancingRule, protocol, tungstenProjectFqn);
  
                  updateLoadBalancerMember(network, loadBalancingRule, memberIp, tungstenProjectFqn);
  
                  return tungstenService.updateLoadBalancer(network, loadBalancingRule);
              }
  
              if (loadBalancingRule.getState() == FirewallRule.State.Revoke) {
                  return applyRevokeRule(network, loadBalancingRule, publicNetwork, tungstenProjectFqn);
              }
          }
  
          return true;
      }
  
      private void updateLoadBalancerMember(Network network, LoadBalancingRule loadBalancingRule, List<TungstenLoadBalancerMember> memberIp, String tungstenProjectFqn) {
          TungstenCommand updateTungstenLoadBalancerMemberCommand = new UpdateTungstenLoadBalancerMemberCommand(
                  tungstenProjectFqn, network.getUuid(), TungstenUtils.getLoadBalancerPoolName(loadBalancingRule.getId()), memberIp);
          TungstenAnswer updateTungstenLoadBalancerMemberAnswer = tungstenFabricUtils.sendTungstenCommand(
                  updateTungstenLoadBalancerMemberCommand, network.getDataCenterId());
          if (!updateTungstenLoadBalancerMemberAnswer.getResult()) {
              throw new CloudRuntimeException("Can not create Tungsten Fabric Load Balancer Member");
          }
      }
  
      private void createLoadBalancer(Network network, Network publicNetwork, LoadBalancingRule loadBalancingRule, IPAddressVO ipAddressVO, List<TungstenLoadBalancerMember> memberIp, String tungstenProjectFqn, String protocol) {
          String lbIp = getLoadBalancerIpAddress(network, ipAddressVO);
          TungstenCommand createTungstenNetworkLoadbalancerCommand = new CreateTungstenNetworkLoadbalancerCommand(
                  tungstenProjectFqn, network.getUuid(), publicNetwork.getUuid(), TungstenUtils.getLoadBalancerAlgorithm(loadBalancingRule.getAlgorithm()),
                  TungstenUtils.getLoadBalancerName(ipAddressVO.getId()), TungstenUtils.getLoadBalancerListenerName(loadBalancingRule.getId()),
                  TungstenUtils.getLoadBalancerPoolName(loadBalancingRule.getId()), TungstenUtils.getLoadBalancerHealthMonitorName(ipAddressVO.getId()),
                  TungstenUtils.getLoadBalancerVmiName(ipAddressVO.getId()), TungstenUtils.getLoadBalancerIiName(ipAddressVO.getId()), loadBalancingRule.getId(), memberIp,
                  protocol, loadBalancingRule.getSourcePortStart(), loadBalancingRule.getDefaultPortStart(), lbIp,
                  TungstenUtils.getFloatingIpPoolName(network.getDataCenterId()), TungstenUtils.getFloatingIpName(ipAddressVO.getId()),
                  TungstenService.MonitorType.PING.toString(), 3, 5, 5, null, null, null);
          TungstenAnswer createTungstenNetworkLoadbalancerAnswer = tungstenFabricUtils.sendTungstenCommand(
                  createTungstenNetworkLoadbalancerCommand, network.getDataCenterId());
          if (!createTungstenNetworkLoadbalancerAnswer.getResult()) {
              throw new CloudRuntimeException("Can not create Tungsten Fabric Load Balancer");
          }
      }
  
      private List<TungstenLoadBalancerMember> createTungstenLoadBalancerMember(LoadBalancingRule loadBalancingRule) {
          List<TungstenLoadBalancerMember> memberIp = new ArrayList<>();
  
          List<LoadBalancerVMMapVO> loadBalancerVMMapVOList = lbVmMapDao.listByLoadBalancerId(
                  loadBalancingRule.getId(), false);
  
          for (LoadBalancerVMMapVO lbVMMapVO : loadBalancerVMMapVOList) {
              int port = loadBalancingRule.getDefaultPortStart();
              if (loadBalancingRule.getDefaultPortStart() == NetUtils.HTTPS_PORT) {
                  port = NetUtils.HTTP_PORT;
              }
  
              TungstenLoadBalancerMember tungstenLoadBalancerMember = new TungstenLoadBalancerMember(
                      TungstenUtils.getLoadBalancerMemberName(loadBalancingRule.getId(), lbVMMapVO.getInstanceIp()),
                      lbVMMapVO.getInstanceIp(), port, 1);
              memberIp.add(tungstenLoadBalancerMember);
          }
  
          return memberIp;
      }
  
      private String getLoadBalancerIpAddress(Network network, IPAddressVO ipAddressVO) {
          String lbIp;
          TungstenGuestNetworkIpAddressVO guestNetworkIpAddressVO = tungstenGuestNetworkIpAddressDao.findByNetworkIdAndPublicIp(
                  network.getId(), ipAddressVO.getAddress().addr());
  
          if (guestNetworkIpAddressVO == null) {
              lbIp = ipAddressMgr.acquireGuestIpAddress(network, null);
  
              if (lbIp == null) {
                  throw new CloudRuntimeException("Can not acquire ip address for loadbalancer");
              }
  
              // register tungsten guest network ip
              TungstenGuestNetworkIpAddressVO tungstenGuestNetworkIpAddressVO = new TungstenGuestNetworkIpAddressVO();
              tungstenGuestNetworkIpAddressVO.setNetworkId(network.getId());
              tungstenGuestNetworkIpAddressVO.setPublicIpAddress(ipAddressVO.getAddress());
              tungstenGuestNetworkIpAddressVO.setGuestIpAddress(new Ip(lbIp));
              tungstenGuestNetworkIpAddressDao.persist(tungstenGuestNetworkIpAddressVO);
          } else {
              lbIp = guestNetworkIpAddressVO.getGuestIpAddress().addr();
          }
  
          return lbIp;
      }
  
      private void updateLBPool(Network network, LoadBalancingRule loadBalancingRule, String protocol, String tungstenProjectFqn) {
          // update stickiness, algorithm, protocol
          List<LoadBalancingRule.LbStickinessPolicy> lbStickinessPolicyList = loadBalancingRule.getStickinessPolicies();
          String lbSessionPersistence = null;
          String lbPersistenceCookieName = null;
          if (!lbStickinessPolicyList.isEmpty()) {
              lbSessionPersistence = TungstenUtils.getLoadBalancerSession(lbStickinessPolicyList.get(0).getMethodName());
              if (lbStickinessPolicyList.get(0).getMethodName().equals("AppCookie")) {
                  lbPersistenceCookieName = lbStickinessPolicyList.get(0).getParams().get(0).second();
              }
          }
  
          // update haproxy
          TungstenCommand updateTungstenLoadBalancerPoolCommand;
          String lbStatsVisibility = configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key());
          if (!lbStatsVisibility.equals("disabled")) {
              String lbStatsUri = configDao.getValue(Config.NetworkLBHaproxyStatsUri.key());
              String lbStatsAuth = configDao.getValue(Config.NetworkLBHaproxyStatsAuth.key());
              String lbStatsPort = configDao.getValue(Config.NetworkLBHaproxyStatsPort.key());
              updateTungstenLoadBalancerPoolCommand = new UpdateTungstenLoadBalancerPoolCommand(
                      tungstenProjectFqn, TungstenUtils.getLoadBalancerPoolName(loadBalancingRule.getId()),
                      TungstenUtils.getLoadBalancerAlgorithm(loadBalancingRule.getAlgorithm()),
                      lbSessionPersistence, lbPersistenceCookieName, protocol, true, lbStatsPort, lbStatsUri,
                      lbStatsAuth);
          } else {
              updateTungstenLoadBalancerPoolCommand = new UpdateTungstenLoadBalancerPoolCommand(
                      tungstenProjectFqn, TungstenUtils.getLoadBalancerPoolName(loadBalancingRule.getId()),
                      TungstenUtils.getLoadBalancerAlgorithm(loadBalancingRule.getAlgorithm()),
                      lbSessionPersistence, lbPersistenceCookieName, protocol, false, null, null, null);
          }
          TungstenAnswer updateTungstenLoadBalancerPoolAnswer = tungstenFabricUtils.sendTungstenCommand(
                  updateTungstenLoadBalancerPoolCommand, network.getDataCenterId());
          if (!updateTungstenLoadBalancerPoolAnswer.getResult()) {
              throw new CloudRuntimeException("Can not update Tungsten Fabric Load Balancer Pool");
          }
      }
  
      private void updateLBHealthMonitor(Network network, LoadBalancingRule loadBalancingRule, IPAddressVO ipAddressVO, String tungstenProjectFqn) {
          // update load balancer health monitor
          TungstenFabricLBHealthMonitorVO tungstenFabricLBHealthMonitorVO = tungstenFabricLBHealthMonitorDao.findByLbId(
                  loadBalancingRule.getId());
          if (tungstenFabricLBHealthMonitorVO != null) {
              TungstenCommand updateTungstenHealthMonitorCommand = new UpdateTungstenLoadBalancerHealthMonitorCommand(
                      tungstenProjectFqn, TungstenUtils.getLoadBalancerHealthMonitorName(ipAddressVO.getId()),
                      tungstenFabricLBHealthMonitorVO.getType(), tungstenFabricLBHealthMonitorVO.getRetry(),
                      tungstenFabricLBHealthMonitorVO.getTimeout(), tungstenFabricLBHealthMonitorVO.getInterval(),
                      tungstenFabricLBHealthMonitorVO.getHttpMethod(), tungstenFabricLBHealthMonitorVO.getExpectedCode(),
                      tungstenFabricLBHealthMonitorVO.getUrlPath());
              TungstenAnswer updateTungstenHealthMonitorAnswer = tungstenFabricUtils.sendTungstenCommand(
                      updateTungstenHealthMonitorCommand, network.getDataCenterId());
              if (!updateTungstenHealthMonitorAnswer.getResult()) {
                  throw new CloudRuntimeException("Can not update Tungsten Fabric Health Monitor");
              }
          }
      }
  
      private boolean applyRevokeRule(Network network, LoadBalancingRule loadBalancingRule, Network publicNetwork, String tungstenProjectFqn) {
          IPAddressVO ipAddressVO = ipAddressDao.findByIpAndDcId(network.getDataCenterId(),
                  loadBalancingRule.getSourceIp().addr());
          List<LoadBalancerVO> listLoadBalancerVO = lbDao.listByIpAddress(ipAddressVO.getId());
          if (listLoadBalancerVO.size() > 1) {
              TungstenCommand deleteTungstenLoadBalancerListenerCommand =
                      new DeleteTungstenLoadBalancerListenerCommand(
                              tungstenProjectFqn, TungstenUtils.getLoadBalancerListenerName(loadBalancingRule.getId()));
              TungstenAnswer deleteTungstenLoadBalancerListenerAnswer = tungstenFabricUtils.sendTungstenCommand(
                      deleteTungstenLoadBalancerListenerCommand, network.getDataCenterId());
              if (!deleteTungstenLoadBalancerListenerAnswer.getResult()) {
                  return false;
              }
  
              return tungstenService.updateLoadBalancer(network, loadBalancingRule);
          } else {
              TungstenCommand deleteTungstenLoadBalancerCommand = new DeleteTungstenLoadBalancerCommand(
                      tungstenProjectFqn, publicNetwork.getUuid(),
                      TungstenUtils.getLoadBalancerName(ipAddressVO.getId()),
                      TungstenUtils.getLoadBalancerHealthMonitorName(ipAddressVO.getId()),
                      TungstenUtils.getLoadBalancerVmiName(ipAddressVO.getId()),
                      TungstenUtils.getFloatingIpPoolName(network.getDataCenterId()),
                      TungstenUtils.getFloatingIpName(ipAddressVO.getId()));
              TungstenAnswer deleteTungstenLoadBalancerAnswer = tungstenFabricUtils.sendTungstenCommand(
                      deleteTungstenLoadBalancerCommand, network.getDataCenterId());
              if (!deleteTungstenLoadBalancerAnswer.getResult()) {
                  return false;
              }
  
              TungstenGuestNetworkIpAddressVO guestNetworkIpAddressVO =
                      tungstenGuestNetworkIpAddressDao.findByNetworkIdAndPublicIp(
                              network.getId(), ipAddressVO.getAddress().addr());
              if (guestNetworkIpAddressVO != null) {
                  return tungstenGuestNetworkIpAddressDao.remove(guestNetworkIpAddressVO.getId());
              }
          }
  
          return true;
      }
  
      @Override
      public boolean validateLBRule(Network network, LoadBalancingRule rule) {
          return true;
      }
  
      @Override
      public List<LoadBalancerTO> updateHealthChecks(Network network, List<LoadBalancingRule> lbrules) {
          return new ArrayList<>();
      }
  
      @Override
      public boolean handlesOnlyRulesInTransitionState() {
          return false;
      }
  
      @Override
      public boolean applyPFRules(final Network network, final List<PortForwardingRule> rules) {
          Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(network.getDataCenterId(),
              Networks.TrafficType.Public);
          // tungsten port forwarding don't support multiple vm per public ip address
          // if multiple vm, only 1 vm get effect
          // frontend : don't allow to add multiple vm per public ip address
          for (PortForwardingRule rule : rules) {
              IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId());
              UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId());
              if (vm == null || networkModel.getNicInNetwork(vm.getId(), network.getId()) == null) {
                  continue;
              }
              Nic guestNic = networkModel.getNicInNetwork(vm.getId(), network.getId());
              if (rule.getState() == FirewallRule.State.Add || rule.getState() == FirewallRule.State.Revoke) {
                  TungstenCommand applyTungstenPortForwardingCommand = new ApplyTungstenPortForwardingCommand(
                      rule.getState() == FirewallRule.State.Add, publicNetwork.getUuid(),
                      TungstenUtils.getFloatingIpPoolName(network.getDataCenterId()),
                      TungstenUtils.getFloatingIpName(publicIp.getId()), guestNic.getUuid(),
                      StringUtils.upperCase(rule.getProtocol()), rule.getSourcePortStart(),
                      rule.getDestinationPortStart());
                  TungstenAnswer applyTungstenPortForwardingAnswer = tungstenFabricUtils.sendTungstenCommand(
                      applyTungstenPortForwardingCommand, network.getDataCenterId());
                  if (!applyTungstenPortForwardingAnswer.getResult()) {
                      return false;
                  }
              }
          }
  
          return true;
      }
  
      @Override
      public IpDeployer getIpDeployer(Network network) {
          return this;
      }
  
      @Override
      public Map<Network.Service, Map<Network.Capability, String>> getCapabilities() {
          return capabilities;
      }
  
      @Override
      public boolean applyIps(Network network, List<? extends PublicIpAddress> ipAddress, Set<Network.Service> services) {
          return true;
      }
  
      @Override
      public Network.Provider getProvider() {
          return Network.Provider.Tungsten;
      }
  
      @Override
      public boolean implement(Network network, NetworkOffering offering, DeployDestination dest,
          ReservationContext context)
          throws ConcurrentOperationException {
          return true;
      }
  
      @Override
      public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest,
          ReservationContext context)
          throws ConcurrentOperationException {
          if (network.getTrafficType() == Networks.TrafficType.Public) {
              TungstenAnswer createVirtualMachineAnswer = createTungstenVM(network, nic, vm, dest);
              if (!createVirtualMachineAnswer.getResult()) {
                  throw new CloudRuntimeException("can not create Tungsten-Fabric vm");
              }
  
              // get tungsten public firewall rule
              IPAddressVO ipAddressVO = ipAddressDao.findByIpAndDcId(network.getDataCenterId(), nic.getIPv4Address());
              List<TungstenRule> tungstenRuleList = createDefaultTungstenFirewallRuleList(vm.getType(),
                  nic.getIPv4Address());
  
              // create tungsten public network policy for system vm
              TungstenCommand createTungstenNetworkPolicyCommand = new CreateTungstenNetworkPolicyCommand(
                  TungstenUtils.getPublicNetworkPolicyName(ipAddressVO.getId()), null, tungstenRuleList);
              TungstenAnswer createTungstenNetworkPolicyAnswer = tungstenFabricUtils.sendTungstenCommand(
                  createTungstenNetworkPolicyCommand, network.getDataCenterId());
              if (!createTungstenNetworkPolicyAnswer.getResult()) {
                  throw new CloudRuntimeException("can not create Tungsten-Fabric public network policy");
              }
  
              messageBus.publish(_name, TungstenService.MESSAGE_APPLY_NETWORK_POLICY_EVENT, PublishScope.LOCAL, network);
  
              updateTungstenFabricService(nic);
          }
  
          if (network.getTrafficType() == Networks.TrafficType.Management) {
              TungstenAnswer createVirtualMachineAnswer = createTungstenVM(network, nic, vm, dest);
              if (!createVirtualMachineAnswer.getResult()) {
                  throw new CloudRuntimeException("can not create Tungsten-Fabric vm");
              }
  
              updateTungstenFabricService(nic);
          }
  
          return true;
      }
  
      @Override
      public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context)
          throws ConcurrentOperationException {
          VMInstanceVO vmInstanceVO = vmInstanceDao.findById(vm.getId());
          HostVO host = hostDao.findById(vmInstanceVO.getLastHostId());
  
          if (host != null) {
              if (network.getTrafficType() == Networks.TrafficType.Public) {
                  try {
                      TungstenCommand deleteTungstenVRouterPortCommand = new DeleteTungstenVRouterPortCommand(
                              host.getPublicIpAddress(), nic.getUuid());
                      tungstenFabricUtils.sendTungstenCommand(deleteTungstenVRouterPortCommand, network.getDataCenterId());
                      String nicName = TungstenUtils.getVmiName(network.getTrafficType().toString(), vm.getType().toString(),
                              vm.getInstanceName(), nic.getId());
                      String tungstenProjectFqn = tungstenService.getTungstenProjectFqn(network);
                      DeleteTungstenVmInterfaceCommand deleteVmiCmd = new DeleteTungstenVmInterfaceCommand(tungstenProjectFqn,
                              nicName);
                      tungstenFabricUtils.sendTungstenCommand(deleteVmiCmd, network.getDataCenterId());
  
                      TungstenCommand deleteVmCmd = new DeleteTungstenVmCommand(vm.getUuid());
                      tungstenFabricUtils.sendTungstenCommand(deleteVmCmd, network.getDataCenterId());
  
                      IPAddressVO ipAddressVO = ipAddressDao.findByIpAndDcId(network.getDataCenterId(), nic.getIPv4Address());
                      TungstenCommand deleteTungstenNetworkPolicyCommand = new DeleteTungstenNetworkPolicyCommand(
                              TungstenUtils.getPublicNetworkPolicyName(ipAddressVO.getId()), null, network.getUuid());
                      tungstenFabricUtils.sendTungstenCommand(deleteTungstenNetworkPolicyCommand, network.getDataCenterId());
                  } catch (IllegalArgumentException e) {
                      throw new CloudRuntimeException(String.format("Failing to expunge the vm %s from Tungsten-Fabric", vm));
                  }
              }
  
              if (network.getTrafficType() == Networks.TrafficType.Management) {
                  try {
                      TungstenCommand deleteTungstenVRouterPortCommand = new DeleteTungstenVRouterPortCommand(
                              host.getPublicIpAddress(), nic.getUuid());
                      tungstenFabricUtils.sendTungstenCommand(deleteTungstenVRouterPortCommand, network.getDataCenterId());
                      String nicName = TungstenUtils.getVmiName(network.getTrafficType().toString(), vm.getType().toString(),
                              vm.getInstanceName(), nic.getId());
                      String tungstenProjectFqn = tungstenService.getTungstenProjectFqn(network);
                      TungstenCommand deleteVmiCmd = new DeleteTungstenVmInterfaceCommand(tungstenProjectFqn, nicName);
                      tungstenFabricUtils.sendTungstenCommand(deleteVmiCmd, network.getDataCenterId());
  
                      TungstenCommand deleteVmCmd = new DeleteTungstenVmCommand(vm.getUuid());
                      tungstenFabricUtils.sendTungstenCommand(deleteVmCmd, network.getDataCenterId());
                  } catch (IllegalArgumentException e) {
                      throw new CloudRuntimeException(String.format("Failing to expunge the vm %s from Tungsten-Fabric", vm));
                  }
              }
          }
  
          return true;
      }
  
      @Override
      public boolean shutdown(Network network, ReservationContext context, boolean cleanup)
          throws ConcurrentOperationException {
          return canHandle(network, Network.Service.Connectivity);
      }
  
      @Override
      public boolean destroy(Network network, ReservationContext context)
          throws ConcurrentOperationException, ResourceUnavailableException {
          boolean result = true;
          // delete floating ips
          List<IPAddressVO> staticNatIpList = ipAddressDao.listByAssociatedNetwork(network.getId(), false);
          for (IPAddressVO ipAddressVO : staticNatIpList) {
              TungstenCommand deleteTungstenFloatingIpCommand = new DeleteTungstenFloatingIpCommand(network.getUuid(),
                  TungstenUtils.getFloatingIpPoolName(network.getDataCenterId()),
                  TungstenUtils.getFloatingIpName(ipAddressVO.getId()));
              TungstenAnswer tungstenDeleteFIPAnswer = tungstenFabricUtils.sendTungstenCommand(
                  deleteTungstenFloatingIpCommand, network.getDataCenterId());
              result = result && tungstenDeleteFIPAnswer.getResult();
  
              TungstenCommand deleteTungstenNetworkPolicyCommand = new DeleteTungstenNetworkPolicyCommand(
                  TungstenUtils.getPublicNetworkPolicyName(ipAddressVO.getId()), null, network.getUuid());
              TungstenAnswer tungstenDeleteNPAnswer = tungstenFabricUtils.sendTungstenCommand(
                  deleteTungstenNetworkPolicyCommand, network.getDataCenterId());
              result = result && tungstenDeleteNPAnswer.getResult();
          }
  
          return result;
      }
  
      @Override
      public boolean isReady(PhysicalNetworkServiceProvider provider) {
          PhysicalNetworkTrafficTypeVO managementNetwork = physicalNetworkTrafficTypeDao.findBy(
              provider.getPhysicalNetworkId(), Networks.TrafficType.Management);
          return managementNetwork != null;
      }
  
      @Override
      public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context)
          throws ConcurrentOperationException {
          long zoneId = physicalNetworkDao.findById(provider.getPhysicalNetworkId()).getDataCenterId();
  
          TungstenProvider tungstenProvider = tungstenProviderDao.findByZoneId(zoneId);
          if (tungstenProvider != null) {
              DataCenter dataCenter = dataCenterDao.findById(zoneId);
  
              if (!dataCenter.isSecurityGroupEnabled()) {
                  // delete network service map
                  Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(zoneId,
                      Networks.TrafficType.Public);
                  if (publicNetwork != null) {
                      networkServiceMapDao.deleteByNetworkId(publicNetwork.getId());
                      List<VlanVO> listVlanVO = vlanDao.listVlansByNetworkIdIncludingRemoved(publicNetwork.getId());
                      for (VlanVO vlanVO : listVlanVO) {
                          tungstenService.removePublicNetworkSubnet(vlanVO);
                      }
  
                      tungstenService.deletePublicNetwork(zoneId);
                  }
              }
  
              List<HostPodVO> listPod = podDao.listByDataCenterId(zoneId);
              for (HostPodVO pod : listPod) {
                  tungstenService.removeManagementNetworkSubnet(pod);
              }
  
              tungstenService.deleteManagementNetwork(zoneId);
              tungstenProviderDao.expunge(tungstenProvider.getId());
          }
          return true;
      }
  
      @Override
      public boolean canEnableIndividualServices() {
          return true;
      }
  
      @Override
      public boolean verifyServicesCombination(final Set<Network.Service> services) {
          // tf can use configdrive for userdata
          services.remove(Network.Service.UserData);
          final Set<Network.Service> sharedZoneServices = new HashSet<>(
              Arrays.asList(Network.Service.Connectivity,
                  Network.Service.Dhcp,
                  Network.Service.Dns, Network.Service.SecurityGroup));
          final Set<Network.Service> isolatedZoneServices = new HashSet<>(
              Arrays.asList(Network.Service.Connectivity,
                  Network.Service.Dhcp,
                  Network.Service.Dns, Network.Service.SourceNat, Network.Service.StaticNat,
                  Network.Service.Lb, Network.Service.PortForwarding, Network.Service.Firewall));
          return (services.containsAll(sharedZoneServices) && sharedZoneServices.containsAll(services)) || (
              services.containsAll(isolatedZoneServices) && isolatedZoneServices.containsAll(services));
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          super.configure(name, params);
          agentMgr.registerForHostEvents(this, true, true, true);
          resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
          VirtualMachine.State.getStateMachine().registerListener(this);
  
          return true;
      }
  
      @Override
      public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) {
          return null;
      }
  
      @Override
      public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource,
          Map<String, String> details, List<String> hostTags) {
          if (!(startup[0] instanceof StartupTungstenCommand)) {
              return null;
          }
          host.setType(Host.Type.L2Networking);
          return host;
      }
  
      @Override
      public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) {
          if (host.getType() != Host.Type.L2Networking) {
              return null;
          }
          return new DeleteHostAnswer(true);
      }
  
      @Override
      public boolean processAnswers(final long agentId, final long seq, final Answer[] answers) {
          return false;
      }
  
      @Override
      public boolean processCommands(final long agentId, final long seq, final Command[] commands) {
          return false;
      }
  
      @Override
      public AgentControlAnswer processControlCommand(final long agentId, final AgentControlCommand cmd) {
          return null;
      }
  
      @Override
      public void processHostAdded(final long hostId) {
          // Do nothing
      }
  
      @Override
      public void processConnect(final Host host, final StartupCommand cmd, final boolean forRebalance) {
          long zoneId = host.getDataCenterId();
          TungstenProviderVO tungstenProvider = tungstenProviderDao.findByZoneId(zoneId);
          if (host.getHypervisorType() == Hypervisor.HypervisorType.KVM && tungstenProvider != null
              && host.getPublicIpAddress().equals(tungstenProvider.getGateway())) {
              setupVrouter(host);
          }
  
          if ((host.getType() == Host.Type.ConsoleProxy || host.getType() == Host.Type.SecondaryStorageVM) && tungstenProvider != null) {
              VirtualMachine.Type type = host.getType() == Host.Type.SecondaryStorageVM ? VirtualMachine.Type.SecondaryStorageVm : VirtualMachine.Type.ConsoleProxy;
              List<VMInstanceVO> vmList = vmInstanceDao.listByZoneIdAndType(zoneId, type);
              if (vmList.size() == 1 && vmList.get(0).getState() == VirtualMachine.State.Running) {
                  NicVO nicVO = nicDao.getControlNicForVM(vmList.get(0).getId());
                  HostVO kvmHost = hostDao.findById(vmList.get(0).getHostId());
                  String srcNetwork = NetUtils.getCidrFromGatewayAndNetmask(kvmHost.getPrivateIpAddress(), kvmHost.getPrivateNetmask());
                  Command setupTfRoute = new SetupTfRouteCommand(nicVO.getIPv4Address(), host.getPublicIpAddress(), srcNetwork);
                  agentMgr.easySend(vmList.get(0).getHostId(), setupTfRoute);
              }
          }
      }
  
      private void setupVrouter(Host host) {
          long zoneId = host.getDataCenterId();
          DataCenterVO dataCenterVO = dataCenterDao.findById(zoneId);
          if (dataCenterVO.isSecurityGroupEnabled()) {
              List<NetworkVO> networks = networkDao.listByZoneSecurityGroup(zoneId);
              for (NetworkVO network : networks) {
                  NetworkDetailVO networkDetail = networkDetailsDao.findDetail(network.getId(), "vrf");
                  if (networkDetail != null) {
                      Command setupTungstenVRouterCommand = new SetupTungstenVRouterCommand(
                              "create", TungstenUtils.getSgVgwName(network.getId()), network.getCidr(),
                              NetUtils.ALL_IP4_CIDRS, networkDetail.getValue());
                      agentMgr.easySend(host.getId(), setupTungstenVRouterCommand);
                  }
              }
          } else {
              List<VlanVO> vlanList = vlanDao.listByZone(zoneId);
              List<String> publicSubnetList = new ArrayList<>();
  
              for (VlanVO vlanVO : vlanList) {
                  String subnet = NetUtils.getCidrFromGatewayAndNetmask(vlanVO.getVlanGateway(),
                          vlanVO.getVlanNetmask());
                  publicSubnetList.add(subnet);
              }
  
              String publicSubnet = StringUtils.join(publicSubnetList.toArray(), " ");
              Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(zoneId,
                      Networks.TrafficType.Public);
              NetworkDetailVO networkDetail = networkDetailsDao.findDetail(publicNetwork.getId(), "vrf");
              if (networkDetail != null) {
                  Command setupTungstenVRouterCommand = new SetupTungstenVRouterCommand("create",
                          TungstenUtils.getVgwName(zoneId), publicSubnet, NetUtils.ALL_IP4_CIDRS,
                          networkDetail.getValue());
                  agentMgr.easySend(host.getId(), setupTungstenVRouterCommand);
              }
          }
      }
  
      @Override
      public boolean processDisconnect(final long agentId, final Status state) {
          return true;
      }
  
      @Override
      public void processHostAboutToBeRemoved(final long hostId) {
          Host host = hostDao.findById(hostId);
          long zoneId = host.getDataCenterId();
  
          TungstenProviderVO tungstenProvider = tungstenProviderDao.findByZoneId(zoneId);
          if (host.getHypervisorType() == Hypervisor.HypervisorType.KVM && tungstenProvider != null
              && host.getPublicIpAddress().equals(tungstenProvider.getGateway())) {
              DataCenterVO dataCenterVO = dataCenterDao.findById(zoneId);
              if (dataCenterVO.isSecurityGroupEnabled()) {
                  List<NetworkVO> networks = networkDao.listByZoneSecurityGroup(zoneId);
                  for (NetworkVO network : networks) {
                      Command setupTungstenVRouterCommand = new SetupTungstenVRouterCommand("delete",
                          TungstenUtils.getSgVgwName(network.getId()), NetUtils.ALL_IP4_CIDRS, NetUtils.ALL_IP4_CIDRS,
                          NetUtils.ALL_IP4_CIDRS);
                      agentMgr.easySend(host.getId(), setupTungstenVRouterCommand);
                  }
              } else {
                  Command setupTungstenVRouterCommand = new SetupTungstenVRouterCommand("delete",
                      TungstenUtils.getVgwName(zoneId), NetUtils.ALL_IP4_CIDRS, NetUtils.ALL_IP4_CIDRS,
                      NetUtils.ALL_IP4_CIDRS);
                  agentMgr.easySend(host.getId(), setupTungstenVRouterCommand);
              }
          }
      }
  
      @Override
      public void processHostRemoved(final long hostId, final long clusterId) {
          // Do no thing
      }
  
      @Override
      public boolean isRecurring() {
          return false;
      }
  
      @Override
      public int getTimeout() {
          return 0;
      }
  
      @Override
      public boolean processTimeout(final long agentId, final long seq) {
          return false;
      }
  
      @Override
      public boolean applyFWRules(final Network network, final List<? extends FirewallRule> rules)
          throws ResourceUnavailableException {
          String tungstenProjectFqn;
          String networkUuid;
          String policyName;
          Network publicNetwork = networkModel.getSystemNetworkByZoneAndTrafficType(network.getDataCenterId(),
              Networks.TrafficType.Public);
          for (FirewallRule firewallRule : rules) {
              if (firewallRule.getPurpose() == FirewallRule.Purpose.Firewall) {
                  if (firewallRule.getDestinationCidrList() == null) {
                      return false;
                  }
  
                  if (firewallRule.getTrafficType() == FirewallRule.TrafficType.Egress) {
                      tungstenProjectFqn = tungstenService.getTungstenProjectFqn(network);
                      networkUuid = network.getUuid();
                      policyName = TungstenUtils.getRuleNetworkPolicyName(firewallRule.getId());
                  } else {
                      tungstenProjectFqn = null;
                      networkUuid = publicNetwork.getUuid();
                      policyName = TungstenUtils.getPublicNetworkPolicyName(firewallRule.getSourceIpAddressId());
                  }
  
                  applyAddRule(network, firewallRule, tungstenProjectFqn, networkUuid,policyName);
  
                  if (firewallRule.getState() == FirewallRule.State.Revoke) {
                      TungstenCommand deleteTungstenNetworkPolicyCommand = new DeleteTungstenNetworkPolicyCommand(
                          policyName, tungstenProjectFqn, networkUuid);
                      tungstenFabricUtils.sendTungstenCommand(
                          deleteTungstenNetworkPolicyCommand, network.getDataCenterId());
                  }
              }
          }
  
          return true;
      }
  
      private void applyAddRule(Network network, FirewallRule firewallRule, String tungstenProjectFqn, String networkUuid,String policyName) throws ResourceUnavailableException {
          if (firewallRule.getState() == FirewallRule.State.Add) {
              TungstenRule tungstenRule = convertFirewallRule(firewallRule);
              List<TungstenRule> tungstenRuleList = new ArrayList<>();
              tungstenRuleList.add(tungstenRule);
  
              TungstenCommand createTungstenNetworkPolicyCommand = new CreateTungstenNetworkPolicyCommand(
                      policyName, tungstenProjectFqn, tungstenRuleList);
              TungstenAnswer createNetworkPolicyAnswer = tungstenFabricUtils.sendTungstenCommand(
                      createTungstenNetworkPolicyCommand, network.getDataCenterId());
              if (!createNetworkPolicyAnswer.getResult()) {
                  throw new CloudRuntimeException("Can not create tungsten fabric network policy");
              }
  
              TungstenCommand applyTungstenNetworkPolicyCommand = new ApplyTungstenNetworkPolicyCommand(
                      tungstenProjectFqn, policyName, networkUuid, 0, 0);
              TungstenAnswer applyNetworkPolicyAnswer = tungstenFabricUtils.sendTungstenCommand(
                      applyTungstenNetworkPolicyCommand, network.getDataCenterId());
              if (!applyNetworkPolicyAnswer.getResult()) {
                  throw new CloudRuntimeException("Can not apply tungsten fabric network policy");
              }
          }
      }
  
      private TungstenRule convertFirewallRule(FirewallRule firewallRule) throws ResourceUnavailableException {
          List<String> srcCidrs = firewallRule.getSourceCidrList();
          List<String> dstCidrs = firewallRule.getDestinationCidrList();
  
          if (firewallRule.getTrafficType() == FirewallRule.TrafficType.Egress && dstCidrs == null) {
              throw new ResourceUnavailableException("invalid destination cidr", FirewallRule.class,
                      firewallRule.getId());
          }
  
          String dstPrefix = dstCidrs.size() == 1 ? NetUtils.getCidr(dstCidrs.get(0)).first() : "0.0.0.0";
          int dstPrefixLen = dstCidrs.size() == 1 ? NetUtils.getCidr(dstCidrs.get(0)).second() : 0;
          int dstPortStart = firewallRule.getSourcePortStart() != null ? firewallRule.getSourcePortStart() : -1;
          int dstPortEnd = firewallRule.getSourcePortEnd() != null ? firewallRule.getSourcePortEnd() : -1;
          String tungstenProtocol =
              firewallRule.getProtocol().equals(NetUtils.ALL_PROTO) ? TungstenUtils.ANY : firewallRule.getProtocol();
  
          if (srcCidrs == null || srcCidrs.size() != 1) {
              throw new ResourceUnavailableException("invalid source cidr", FirewallRule.class, firewallRule.getId());
          }
  
          Pair<String, Integer> srcSubnet = NetUtils.getCidr(srcCidrs.get(0));
  
          if (firewallRule.getTrafficType() == FirewallRule.TrafficType.Ingress) {
              long id = firewallRule.getSourceIpAddressId();
              IPAddressVO ipAddressVO = ipAddressDao.findById(id);
              dstPrefix = ipAddressVO.getAddress().addr();
              dstPrefixLen = 32;
          }
  
          return new TungstenRule(firewallRule.getUuid(), TungstenUtils.PASS_ACTION, TungstenUtils.ONE_WAY_DIRECTION,
              tungstenProtocol, TungstenUtils.ANY, srcSubnet.first(), srcSubnet.second(), -1, -1, TungstenUtils.ANY,
              dstPrefix, dstPrefixLen, dstPortStart, dstPortEnd);
      }
  
      private TungstenRule getDefaultIngressRule(String ip, String protocol, int startPort, int endPort) {
          return new TungstenRule(TungstenUtils.PASS_ACTION, TungstenUtils.TWO_WAY_DIRECTION, protocol, TungstenUtils.ANY,
              TungstenUtils.ALL_IP4_PREFIX, 0, -1, -1, TungstenUtils.ANY, ip, TungstenUtils.MAX_CIDR, startPort, endPort);
      }
  
      private TungstenRule getDefaultEgressRule(String ip, String protocol, int startPort, int endPort) {
          return new TungstenRule(TungstenUtils.PASS_ACTION, TungstenUtils.ONE_WAY_DIRECTION, protocol, TungstenUtils.ANY,
              TungstenUtils.ALL_IP4_PREFIX, 0, startPort, endPort, TungstenUtils.ANY, ip, TungstenUtils.MAX_CIDR, -1, -1);
      }
  
      private List<TungstenRule> createDefaultTungstenFirewallRuleList(VirtualMachine.Type vmType, String ip) {
          List<TungstenRule> tungstenRuleList = new ArrayList<>();
          tungstenRuleList.add(getDefaultIngressRule(ip, NetUtils.ICMP_PROTO, -1, -1));
          tungstenRuleList.add(getDefaultEgressRule(ip, NetUtils.UDP_PROTO, TungstenUtils.DNS_SERVICE_PORT,
              TungstenUtils.DNS_SERVICE_PORT));
          tungstenRuleList.add(getDefaultEgressRule(ip, NetUtils.UDP_PROTO, TungstenUtils.NTP_SERVICE_PORT,
              TungstenUtils.NTP_SERVICE_PORT));
          if (vmType == VirtualMachine.Type.ConsoleProxy) {
              tungstenRuleList.add(getDefaultIngressRule(ip, NetUtils.TCP_PROTO, NetUtils.HTTP_PORT, NetUtils.HTTP_PORT));
              tungstenRuleList.add(getDefaultIngressRule(ip, NetUtils.TCP_PROTO, TungstenUtils.WEB_SERVICE_PORT,
                  TungstenUtils.WEB_SERVICE_PORT));
              tungstenRuleList.add(
                  getDefaultIngressRule(ip, NetUtils.TCP_PROTO, NetUtils.HTTPS_PORT, NetUtils.HTTPS_PORT));
          } else {
              tungstenRuleList.add(getDefaultEgressRule(ip, NetUtils.TCP_PROTO, NetUtils.HTTP_PORT, NetUtils.HTTP_PORT));
          }
  
          return tungstenRuleList;
      }
  
      @Override
      public boolean addDnsEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest,
          ReservationContext context)
          throws ConcurrentOperationException {
          return networkModel.isProviderSupportServiceInNetwork(network.getId(), Network.Service.Dhcp, getProvider());
      }
  
      @Override
      public boolean configDnsSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm,
          DeployDestination dest, ReservationContext context)
          throws ConcurrentOperationException {
          return networkModel.isProviderSupportServiceInNetwork(network.getId(), Network.Service.Dhcp, getProvider());
      }
  
      @Override
      public boolean removeDnsSupportForSubnet(Network network) {
          return networkModel.isProviderSupportServiceInNetwork(network.getId(), Network.Service.Dhcp, getProvider());
      }
  
      @Override
      public boolean preStateTransitionEvent(final VirtualMachine.State oldState, final VirtualMachine.Event event,
          final VirtualMachine.State newState, final VirtualMachine vo, final boolean status, final Object opaque) {
          return true;
      }
  
      @Override
      public boolean postStateTransitionEvent(
          final StateMachine2.Transition<VirtualMachine.State, VirtualMachine.Event> transition, final VirtualMachine vo,
          final boolean status, final Object opaque) {
          if (!status) {
              return false;
          }
  
          VirtualMachine.State oldState = transition.getCurrentState();
          VirtualMachine.State newState = transition.getToState();
          VirtualMachine.Event event = transition.getEvent();
          if (VirtualMachine.State.isVmStarted(oldState, event, newState)) {
              return tungstenService.addTungstenVmSecurityGroup((VMInstanceVO) vo);
          } else if (VirtualMachine.State.isVmStopped(oldState, event, newState)) {
              return tungstenService.removeTungstenVmSecurityGroup((VMInstanceVO) vo);
          }
  
          return true;
      }
  
      @Override
      public boolean prepareMigration(final NicProfile nic, final Network network, final VirtualMachineProfile vm,
          final DeployDestination dest, final ReservationContext context) {
          if (vm.getType().isUsedBySystem()) {
              TungstenAnswer answer = createTungstenVM(network, nic, vm, dest);
              return answer.getResult();
          } else {
              return true;
          }
      }
  
      @Override
      public void rollbackMigration(final NicProfile nic, final Network network, final VirtualMachineProfile vm,
          final ReservationContext src, final ReservationContext dst) {
          if (vm.getType().isUsedBySystem()) {
              Long hostId = vm.getVirtualMachine().getHostId();
              HostVO host = hostDao.findById(hostId);
              TungstenCommand deleteTungstenVRouterPortCommand = new DeleteTungstenVRouterPortCommand(
                  host.getPublicIpAddress(), nic.getUuid());
              tungstenFabricUtils.sendTungstenCommand(deleteTungstenVRouterPortCommand, network.getDataCenterId());
          }
      }
  
      @Override
      public void commitMigration(final NicProfile nic, final Network network, final VirtualMachineProfile vm,
          final ReservationContext src, final ReservationContext dst) {
          if (vm.getType().isUsedBySystem()) {
              Long hostId = vm.getVirtualMachine().getLastHostId();
              HostVO host = hostDao.findById(hostId);
              TungstenCommand deleteTungstenVRouterPortCommand = new DeleteTungstenVRouterPortCommand(
                  host.getPublicIpAddress(), nic.getUuid());
              tungstenFabricUtils.sendTungstenCommand(deleteTungstenVRouterPortCommand, network.getDataCenterId());
          }
      }
  
      private TungstenAnswer createTungstenVM(Network network, NicProfile nic, VirtualMachineProfile vm,
          DeployDestination dest) {
          String tungstenProjectFqn = tungstenService.getTungstenProjectFqn(network);
          VMInstanceVO vmInstanceVO = vmInstanceDao.findById(vm.getId());
          Host host = dest.getHost();
          if (host == null) {
              host = hostDao.findById(vmInstanceVO.getHostId());
          }
  
          TungstenCommand createTungstenVirtualMachineCommand = new CreateTungstenVirtualMachineCommand(
              tungstenProjectFqn, network.getUuid(), vm.getUuid(), vm.getInstanceName(), nic.getUuid(), nic.getId(),
              nic.getIPv4Address(), null, nic.getMacAddress(), vm.getType().toString(),
              network.getTrafficType().toString(), host.getPublicIpAddress(), network.getGateway(), nic.isDefaultNic());
          return tungstenFabricUtils.sendTungstenCommand(createTungstenVirtualMachineCommand, network.getDataCenterId());
      }
  
      private void updateTungstenFabricService(NicProfile nic) {
          if (nic.getIPv4Address() != null) {
              nic.setReservationStrategy(Nic.ReservationStrategy.Create);
          }
  
          nic.setBroadcastType(Networks.BroadcastDomainType.TUNGSTEN);
          nic.setBroadcastUri(Networks.BroadcastDomainType.TUNGSTEN.toUri("tf"));
  
          if (nic.getName() == null) {
              nic.setName(TungstenUtils.DEFAULT_VHOST_INTERFACE);
          }
      }
  }
  