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 com.cloud.network.element;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.UUID;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  import javax.persistence.EntityExistsException;
  
  import com.cloud.user.User;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
  import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.AssociateAsaWithLogicalEdgeFirewallCommand;
  import com.cloud.agent.api.CleanupLogicalEdgeFirewallCommand;
  import com.cloud.agent.api.ConfigureNexusVsmForAsaCommand;
  import com.cloud.agent.api.CreateLogicalEdgeFirewallCommand;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.StartupExternalFirewallCommand;
  import com.cloud.agent.api.routing.NetworkElementCommand;
  import com.cloud.agent.api.routing.SetFirewallRulesCommand;
  import com.cloud.agent.api.routing.SetPortForwardingRulesCommand;
  import com.cloud.agent.api.routing.SetSourceNatCommand;
  import com.cloud.agent.api.routing.SetStaticNatRulesCommand;
  import com.cloud.agent.api.to.FirewallRuleTO;
  import com.cloud.agent.api.to.IpAddressTO;
  import com.cloud.agent.api.to.PortForwardingRuleTO;
  import com.cloud.agent.api.to.StaticNatRuleTO;
  import com.cloud.api.commands.AddCiscoAsa1000vResourceCmd;
  import com.cloud.api.commands.AddCiscoVnmcResourceCmd;
  import com.cloud.api.commands.DeleteCiscoAsa1000vResourceCmd;
  import com.cloud.api.commands.DeleteCiscoVnmcResourceCmd;
  import com.cloud.api.commands.ListCiscoAsa1000vResourcesCmd;
  import com.cloud.api.commands.ListCiscoVnmcResourcesCmd;
  import com.cloud.api.response.CiscoAsa1000vResourceResponse;
  import com.cloud.api.response.CiscoVnmcResourceResponse;
  import com.cloud.configuration.ConfigurationManager;
  import com.cloud.dc.ClusterVO;
  import com.cloud.dc.ClusterVSMMapVO;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenter.NetworkType;
  import com.cloud.dc.Vlan;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.dc.dao.ClusterVSMMapDao;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.deploy.DeployDestination;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InsufficientAddressCapacityException;
  import com.cloud.exception.InsufficientCapacityException;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.ResourceAllocationException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.host.DetailVO;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.host.dao.HostDetailsDao;
  import com.cloud.network.CiscoNexusVSMDeviceVO;
  import com.cloud.network.IpAddress;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.Network;
  import com.cloud.network.Network.Capability;
  import com.cloud.network.Network.Provider;
  import com.cloud.network.Network.Service;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.PhysicalNetworkServiceProvider;
  import com.cloud.network.PublicIpAddress;
  import com.cloud.network.addr.PublicIp;
  import com.cloud.network.cisco.CiscoAsa1000vDevice;
  import com.cloud.network.cisco.CiscoAsa1000vDeviceVO;
  import com.cloud.network.cisco.CiscoVnmcController;
  import com.cloud.network.cisco.CiscoVnmcControllerVO;
  import com.cloud.network.cisco.NetworkAsa1000vMapVO;
  import com.cloud.network.dao.CiscoAsa1000vDao;
  import com.cloud.network.dao.CiscoNexusVSMDeviceDao;
  import com.cloud.network.dao.CiscoVnmcDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.dao.NetworkAsa1000vMapDao;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
  import com.cloud.network.dao.PhysicalNetworkServiceProviderVO;
  import com.cloud.network.dao.PhysicalNetworkVO;
  import com.cloud.network.resource.CiscoVnmcResource;
  import com.cloud.network.rules.FirewallRule;
  import com.cloud.network.rules.FirewallRule.TrafficType;
  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.ResourceState;
  import com.cloud.resource.ResourceStateAdapter;
  import com.cloud.resource.ServerResource;
  import com.cloud.resource.UnableDeleteHostException;
  import com.cloud.user.Account;
  import com.cloud.utils.component.AdapterBase;
  import com.cloud.utils.db.EntityManager;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallback;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.exception.ExceptionUtil;
  import com.cloud.utils.net.NetUtils;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.ReservationContext;
  import com.cloud.vm.VirtualMachine.Type;
  import com.cloud.vm.VirtualMachineProfile;
  
  public class CiscoVnmcElement extends AdapterBase implements SourceNatServiceProvider, FirewallServiceProvider, PortForwardingServiceProvider, IpDeployer,
          StaticNatServiceProvider, ResourceStateAdapter, NetworkElement, CiscoVnmcElementService, CiscoAsa1000vService {
      private static final Map<Service, Map<Capability, String>> capabilities = setCapabilities();
  
      @Inject
      EntityManager _entityMgr;
      @Inject
      AgentManager _agentMgr;
      @Inject
      ResourceManager _resourceMgr;
      @Inject
      ConfigurationManager _configMgr;
      @Inject
      NetworkOrchestrationService _networkMgr;
      @Inject
      NetworkModel _networkModel;
      @Inject
      IpAddressManager _ipAddrMgr;
  
      @Inject
      PhysicalNetworkDao _physicalNetworkDao;
      @Inject
      PhysicalNetworkServiceProviderDao _physicalNetworkServiceProviderDao;
      @Inject
      IPAddressDao _ipAddressDao;
      @Inject
      HostDetailsDao _hostDetailsDao;
      @Inject
      HostDao _hostDao;
      @Inject
      NetworkDao _networkDao;
      @Inject
      ClusterDao _clusterDao;
      @Inject
      VlanDao _vlanDao;
      @Inject
      ClusterVSMMapDao _clusterVsmMapDao;
      @Inject
      CiscoNexusVSMDeviceDao _vsmDeviceDao;
      @Inject
      CiscoVnmcDao _ciscoVnmcDao;
      @Inject
      CiscoAsa1000vDao _ciscoAsa1000vDao;
      @Inject
      NetworkAsa1000vMapDao _networkAsa1000vMapDao;
  
      protected boolean canHandle(Network network) {
          if (network.getBroadcastDomainType() != BroadcastDomainType.Vlan) {
              return false; //TODO: should handle VxLAN as well
          }
  
          return true;
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          super.configure(name, params);
          _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
          return true;
      }
  
      private static Map<Service, Map<Capability, String>> setCapabilities() {
          Map<Service, Map<Capability, String>> capabilities = new HashMap<Service, Map<Capability, String>>();
          capabilities.put(Service.Gateway, null);
  
          Map<Capability, String> firewallCapabilities = new HashMap<Capability, String>();
          firewallCapabilities.put(Capability.TrafficStatistics, "per public ip");
          firewallCapabilities.put(Capability.SupportedTrafficDirection, "ingress,egress");
          firewallCapabilities.put(Capability.SupportedProtocols, "tcp,udp,icmp");
          firewallCapabilities.put(Capability.SupportedEgressProtocols, "tcp,udp,icmp");
          firewallCapabilities.put(Capability.MultipleIps, "true");
          capabilities.put(Service.Firewall, firewallCapabilities);
  
          capabilities.put(Service.StaticNat, null);
          capabilities.put(Service.PortForwarding, null);
  
          Map<Capability, String> sourceNatCapabilities = new HashMap<Capability, String>();
          sourceNatCapabilities.put(Capability.SupportedSourceNatTypes, "peraccount");
          sourceNatCapabilities.put(Capability.RedundantRouter, "false"); //TODO:
          capabilities.put(Service.SourceNat, sourceNatCapabilities);
          return capabilities;
      }
  
      @Override
      public Map<Service, Map<Capability, String>> getCapabilities() {
          return capabilities;
      }
  
      @Override
      public Provider getProvider() {
          return Provider.CiscoVnmc;
      }
  
      private boolean createLogicalEdgeFirewall(long vlanId, String gateway, String gatewayNetmask, String publicIp, String publicNetmask, List<String> publicGateways,
          long hostId) {
          CreateLogicalEdgeFirewallCommand cmd = new CreateLogicalEdgeFirewallCommand(vlanId, publicIp, gateway, publicNetmask, gatewayNetmask);
          for (String publicGateway : publicGateways) {
              cmd.getPublicGateways().add(publicGateway);
          }
          Answer answer = _agentMgr.easySend(hostId, cmd);
          return answer.getResult();
      }
  
      private boolean configureNexusVsmForAsa(long vlanId, String gateway, String vsmUsername, String vsmPassword, String vsmIp, String asaInPortProfile, long hostId) {
          ConfigureNexusVsmForAsaCommand cmd = new ConfigureNexusVsmForAsaCommand(vlanId, gateway, vsmUsername, vsmPassword, vsmIp, asaInPortProfile);
          Answer answer = _agentMgr.easySend(hostId, cmd);
          return answer.getResult();
      }
  
      private boolean configureSourceNat(long vlanId, String guestCidr, PublicIp sourceNatIp, long hostId) {
          boolean add = (sourceNatIp.getState() == IpAddress.State.Releasing ? false : true);
          IpAddressTO ip =
              new IpAddressTO(sourceNatIp.getAccountId(), sourceNatIp.getAddress().addr(), add, false, sourceNatIp.isSourceNat(), sourceNatIp.getVlanTag(),
                  sourceNatIp.getGateway(), sourceNatIp.getNetmask(), sourceNatIp.getMacAddress(), null, sourceNatIp.isOneToOneNat());
          boolean addSourceNat = false;
          if (sourceNatIp.isSourceNat()) {
              addSourceNat = add;
          }
  
          SetSourceNatCommand cmd = new SetSourceNatCommand(ip, addSourceNat);
          cmd.setContextParam(NetworkElementCommand.GUEST_VLAN_TAG, Long.toString(vlanId));
          cmd.setContextParam(NetworkElementCommand.GUEST_NETWORK_CIDR, guestCidr);
          Answer answer = _agentMgr.easySend(hostId, cmd);
          return answer.getResult();
      }
  
      private boolean associateAsaWithLogicalEdgeFirewall(long vlanId, String asaMgmtIp, long hostId) {
          AssociateAsaWithLogicalEdgeFirewallCommand cmd = new AssociateAsaWithLogicalEdgeFirewallCommand(vlanId, asaMgmtIp);
          Answer answer = _agentMgr.easySend(hostId, cmd);
          return answer.getResult();
      }
  
      @Override
      public boolean implement(final Network network, final NetworkOffering offering, final DeployDestination dest, final ReservationContext context)
          throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
          final DataCenter zone = _entityMgr.findById(DataCenter.class, network.getDataCenterId());
  
          if (zone.getNetworkType() == NetworkType.Basic) {
              logger.debug("Not handling network implement in zone of type " + NetworkType.Basic);
              return false;
          }
  
          if (!canHandle(network)) {
              return false;
          }
  
          final List<CiscoVnmcControllerVO> devices = _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No Cisco Vnmc device on network {}", network);
              return false;
          }
  
          List<CiscoAsa1000vDeviceVO> asaList = _ciscoAsa1000vDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (asaList.isEmpty()) {
              logger.debug("No Cisco ASA 1000v device on network {}", network);
              return false;
          }
  
          NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
          if (asaForNetwork != null) {
              logger.debug("Cisco ASA 1000v device already associated with network {}", network);
              return true;
          }
  
          if (!_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.CiscoVnmc)) {
              logger.error("SourceNat service is not provided by Cisco Vnmc device on network {}", network);
              return false;
          }
  
          try {
              // ensure that there is an ASA 1000v assigned to this network
              CiscoAsa1000vDevice assignedAsa = assignAsa1000vToNetwork(network);
              if (assignedAsa == null) {
                  logger.error("Unable to assign ASA 1000v device to network {}", network);
                  throw new CloudRuntimeException(String.format("Unable to assign ASA 1000v device to network %s", network));
              }
  
              ClusterVO asaCluster = _clusterDao.findById(assignedAsa.getClusterId());
              ClusterVSMMapVO clusterVsmMap = _clusterVsmMapDao.findByClusterId(assignedAsa.getClusterId());
              if (clusterVsmMap == null) {
                  logger.error("Vmware cluster {} has no Cisco Nexus VSM device associated with it", asaCluster);
                  throw new CloudRuntimeException(String.format("Vmware cluster %s has no Cisco Nexus VSM device associated with it", asaCluster));
              }
  
              CiscoNexusVSMDeviceVO vsmDevice = _vsmDeviceDao.findById(clusterVsmMap.getVsmId());
              if (vsmDevice == null) {
                  logger.error("Unable to load details of Cisco Nexus VSM device associated with cluster {}", asaCluster);
                  throw new CloudRuntimeException(String.format("Unable to load details of Cisco Nexus VSM device associated with cluster %s", asaCluster));
              }
  
              CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
              HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());
              _hostDao.loadDetails(ciscoVnmcHost);
              Account owner = context.getAccount();
              PublicIp sourceNatIp = _ipAddrMgr.assignSourceNatIpAddressToGuestNetwork(owner, network);
              long vlanId = Long.parseLong(BroadcastDomainType.getValue(network.getBroadcastUri()));
  
              List<VlanVO> vlanVOList = _vlanDao.listVlansByPhysicalNetworkId(network.getPhysicalNetworkId());
              List<String> publicGateways = new ArrayList<String>();
              for (VlanVO vlanVO : vlanVOList) {
                  publicGateways.add(vlanVO.getVlanGateway());
              }
  
              // due to VNMC limitation of not allowing source NAT ip as the outside ip of firewall,
              // an additional public ip needs to acquired for assigning as firewall outside ip.
              // In case there are already additional ip addresses available (network restart) use one
              // of them such that it is not the source NAT ip
              IpAddress outsideIp = null;
              List<IPAddressVO> publicIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), null);
              for (IPAddressVO ip : publicIps) {
                  if (!ip.isSourceNat()) {
                      outsideIp = ip;
                      break;
                  }
              }
              if (outsideIp == null) { // none available, acquire one
                  try {
                      Account caller = CallContext.current().getCallingAccount();
                      User callerUser = CallContext.current().getCallingUser();
                      outsideIp = _ipAddrMgr.allocateIp(owner, false, caller, callerUser, zone, true, null);
                  } catch (ResourceAllocationException e) {
                      logger.error("Unable to allocate additional public Ip address. Exception details " + e);
                      throw new CloudRuntimeException("Unable to allocate additional public Ip address. Exception details " + e);
                  }
  
                  try {
                      outsideIp = _ipAddrMgr.associateIPToGuestNetwork(outsideIp.getId(), network.getId(), true);
                  } catch (ResourceAllocationException e) {
                      logger.error("Unable to assign allocated additional public Ip " + outsideIp.getAddress().addr() + " to network with vlan " + vlanId +
                          ". Exception details " + e);
                      throw new CloudRuntimeException("Unable to assign allocated additional public Ip " + outsideIp.getAddress().addr() + " to network with vlan " +
                          vlanId + ". Exception details " + e);
                  }
              }
  
              // create logical edge firewall in VNMC
              String gatewayNetmask = NetUtils.getCidrNetmask(network.getCidr());
              // due to ASA limitation of allowing single subnet to be assigned to firewall interfaces,
              // all public ip addresses must be from same subnet, this essentially means single public subnet in zone
              if (!createLogicalEdgeFirewall(vlanId, network.getGateway(), gatewayNetmask, outsideIp.getAddress().addr(), sourceNatIp.getNetmask(), publicGateways,
                  ciscoVnmcHost.getId())) {
                  logger.error("Failed to create logical edge firewall in Cisco VNMC device for network {}", network);
                  throw new CloudRuntimeException(String.format("Failed to create logical edge firewall in Cisco VNMC device for network %s", network));
              }
  
              // create stuff in VSM for ASA device
              if (!configureNexusVsmForAsa(vlanId, network.getGateway(), vsmDevice.getUserName(), vsmDevice.getPassword(), vsmDevice.getipaddr(),
                  assignedAsa.getInPortProfile(), ciscoVnmcHost.getId())) {
                  logger.error("Failed to configure Cisco Nexus VSM {} for ASA device for network {}", vsmDevice.getipaddr(), network);
                  throw new CloudRuntimeException(String.format("Failed to configure Cisco Nexus VSM %s for ASA device for network %s", vsmDevice.getipaddr(), network));
              }
  
              // configure source NAT
              if (!configureSourceNat(vlanId, network.getCidr(), sourceNatIp, ciscoVnmcHost.getId())) {
                  logger.error("Failed to configure source NAT in Cisco VNMC device for network {}", network);
                  throw new CloudRuntimeException(String.format("Failed to configure source NAT in Cisco VNMC device for network %s", network));
              }
  
              // associate Asa 1000v instance with logical edge firewall
              if (!associateAsaWithLogicalEdgeFirewall(vlanId, assignedAsa.getManagementIp(), ciscoVnmcHost.getId())) {
                  logger.error("Failed to associate Cisco ASA 1000v ({}) with logical edge firewall in VNMC for network {}", assignedAsa.getManagementIp(), network);
                  throw new CloudRuntimeException(String.format("Failed to associate Cisco ASA 1000v (%s) with logical edge firewall in VNMC for network %s", assignedAsa.getManagementIp(), network));
              }
          } catch (CloudRuntimeException e) {
              unassignAsa1000vFromNetwork(network);
              logger.error("CiscoVnmcElement failed", e);
              return false;
          } catch (Exception e) {
              unassignAsa1000vFromNetwork(network);
              ExceptionUtil.rethrowRuntime(e);
              ExceptionUtil.rethrow(e, InsufficientAddressCapacityException.class);
              ExceptionUtil.rethrow(e, ResourceUnavailableException.class);
              throw new IllegalStateException(e);
          }
  
          return true;
      }
  
      @Override
      public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context)
          throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
          if (vm.getType() != Type.User) {
              return false;
          }
  
          // ensure that there is an ASA 1000v assigned to this network
          NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
          if (asaForNetwork == null) {
              return false;
          }
  
          return true;
      }
  
      @Override
      public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException,
          ResourceUnavailableException {
          return true;
      }
  
      private boolean cleanupLogicalEdgeFirewall(long vlanId, long hostId) {
          CleanupLogicalEdgeFirewallCommand cmd = new CleanupLogicalEdgeFirewallCommand(vlanId);
          Answer answer = _agentMgr.easySend(hostId, cmd);
          return answer.getResult();
      }
  
      @Override
      public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
  
          unassignAsa1000vFromNetwork(network);
  
          long vlanId = Long.parseLong(BroadcastDomainType.getValue(network.getBroadcastUri()));
          List<CiscoVnmcControllerVO> devices = _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (!devices.isEmpty()) {
              CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
              HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());
              cleanupLogicalEdgeFirewall(vlanId, ciscoVnmcHost.getId());
          }
  
          return true;
      }
  
      @Override
      public boolean isReady(PhysicalNetworkServiceProvider provider) {
          return true;
      }
  
      @Override
      public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException,
          ResourceUnavailableException {
          return true;
      }
  
      @Override
      public boolean canEnableIndividualServices() {
          return true;
      }
  
      @Override
      public boolean verifyServicesCombination(Set<Service> services) {
          if (!services.contains(Service.Firewall)) {
              logger.warn("CiscoVnmc must be used as Firewall Service Provider in the network");
              return false;
          }
          return true;
      }
  
      @Override
      public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
          return true;
      }
  
      @Override
      public List<Class<?>> getCommands() {
          List<Class<?>> cmdList = new ArrayList<Class<?>>();
          cmdList.add(AddCiscoVnmcResourceCmd.class);
          cmdList.add(DeleteCiscoVnmcResourceCmd.class);
          cmdList.add(ListCiscoVnmcResourcesCmd.class);
          cmdList.add(AddCiscoAsa1000vResourceCmd.class);
          cmdList.add(DeleteCiscoAsa1000vResourceCmd.class);
          cmdList.add(ListCiscoAsa1000vResourcesCmd.class);
          return cmdList;
      }
  
      @Override
      public CiscoVnmcController addCiscoVnmcResource(AddCiscoVnmcResourceCmd cmd) {
          final String deviceName = Provider.CiscoVnmc.getName();
          NetworkDevice networkDevice = NetworkDevice.getNetworkDevice(deviceName);
          final Long physicalNetworkId = cmd.getPhysicalNetworkId();
          CiscoVnmcController ciscoVnmcResource = null;
  
          PhysicalNetworkVO physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId);
          if (physicalNetwork == null) {
              throw new InvalidParameterValueException("Could not find phyical network with ID: " + physicalNetworkId);
          }
          long zoneId = physicalNetwork.getDataCenterId();
  
          final PhysicalNetworkServiceProviderVO ntwkSvcProvider =
              _physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetwork.getId(), networkDevice.getNetworkServiceProvder());
          if (ntwkSvcProvider == null) {
              throw new CloudRuntimeException(String.format("Network Service Provider: %s is not enabled in the physical network: %s to add this device",
                      networkDevice.getNetworkServiceProvder(), physicalNetwork));
          } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) {
              throw new CloudRuntimeException(String.format("Network Service Provider: %s is in shutdown state in the physical network: %s to add this device",
                      ntwkSvcProvider.getProviderName(), physicalNetwork));
          }
  
          if (_ciscoVnmcDao.listByPhysicalNetwork(physicalNetworkId).size() != 0) {
              throw new CloudRuntimeException("A Cisco Vnmc device is already configured on this physical network");
          }
  
          Map<String, String> params = new HashMap<String, String>();
          params.put("guid", UUID.randomUUID().toString());
          params.put("zoneId", String.valueOf(physicalNetwork.getDataCenterId()));
          params.put("physicalNetworkId", String.valueOf(physicalNetwork.getId()));
          params.put("name", "Cisco VNMC Controller - " + cmd.getHost());
          params.put("ip", cmd.getHost());
          params.put("username", cmd.getUsername());
          params.put("password", cmd.getPassword());
  
          Map<String, Object> hostdetails = new HashMap<String, Object>();
          hostdetails.putAll(params);
  
          ServerResource resource = new CiscoVnmcResource();
          try {
              resource.configure(cmd.getHost(), hostdetails);
  
              final Host host = _resourceMgr.addHost(zoneId, resource, Host.Type.ExternalFirewall, params);
              if (host != null) {
                  return Transaction.execute(new TransactionCallback<CiscoVnmcController>() {
                      @Override
                      public CiscoVnmcController doInTransaction(TransactionStatus status) {
                          CiscoVnmcController ciscoVnmcResource = new CiscoVnmcControllerVO(host.getId(), physicalNetworkId, ntwkSvcProvider.getProviderName(), deviceName);
                          _ciscoVnmcDao.persist((CiscoVnmcControllerVO)ciscoVnmcResource);
  
                          DetailVO detail = new DetailVO(host.getId(), "deviceid", String.valueOf(ciscoVnmcResource.getId()));
                          _hostDetailsDao.persist(detail);
  
                          return ciscoVnmcResource;
                      }
                  });
              } else {
                  throw new CloudRuntimeException("Failed to add Cisco Vnmc device due to internal error.");
              }
          } catch (ConfigurationException e) {
              throw new CloudRuntimeException(e.getMessage());
          }
      }
  
      @Override
      public CiscoVnmcResourceResponse createCiscoVnmcResourceResponse(CiscoVnmcController ciscoVnmcResourceVO) {
          HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcResourceVO.getHostId());
  
          CiscoVnmcResourceResponse response = new CiscoVnmcResourceResponse();
          response.setId(ciscoVnmcResourceVO.getUuid());
          response.setPhysicalNetworkId(ciscoVnmcResourceVO.getPhysicalNetworkId());
          response.setProviderName(ciscoVnmcResourceVO.getProviderName());
          response.setResourceName(ciscoVnmcHost.getName());
  
          return response;
      }
  
      @Override
      public boolean deleteCiscoVnmcResource(DeleteCiscoVnmcResourceCmd cmd) {
          Long vnmcResourceId = cmd.getCiscoVnmcResourceId();
          CiscoVnmcControllerVO vnmcResource = _ciscoVnmcDao.findById(vnmcResourceId);
          if (vnmcResource == null) {
              throw new InvalidParameterValueException("Could not find a Cisco VNMC appliance with id " + vnmcResourceId);
          }
  
          // Check if there any ASA 1000v appliances
          Long physicalNetworkId = vnmcResource.getPhysicalNetworkId();
          PhysicalNetworkVO physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId);
          if (physicalNetwork != null) {
              List<CiscoAsa1000vDeviceVO> responseList = _ciscoAsa1000vDao.listByPhysicalNetwork(physicalNetworkId);
              if (responseList.size() > 0) {
                  throw new CloudRuntimeException(String.format("Cisco VNMC appliance %s cannot be deleted as there Cisco ASA 1000v appliances using it", vnmcResource));
              }
          }
  
          HostVO vnmcHost = _hostDao.findById(vnmcResource.getHostId());
          Long hostId = vnmcHost.getId();
          vnmcHost.setResourceState(ResourceState.Maintenance);
          _hostDao.update(hostId, vnmcHost);
          _resourceMgr.deleteHost(hostId, false, false);
          _ciscoVnmcDao.remove(vnmcResourceId);
  
          return true;
      }
  
      @Override
      public List<CiscoVnmcControllerVO> listCiscoVnmcResources(ListCiscoVnmcResourcesCmd cmd) {
          Long physicalNetworkId = cmd.getPhysicalNetworkId();
          Long ciscoVnmcResourceId = cmd.getCiscoVnmcResourceId();
          List<CiscoVnmcControllerVO> responseList = new ArrayList<CiscoVnmcControllerVO>();
  
          if (physicalNetworkId == null && ciscoVnmcResourceId == null) {
              throw new InvalidParameterValueException("Either physical network Id or vnmc device Id must be specified");
          }
  
          if (ciscoVnmcResourceId != null) {
              CiscoVnmcControllerVO ciscoVnmcResource = _ciscoVnmcDao.findById(ciscoVnmcResourceId);
              if (ciscoVnmcResource == null) {
                  throw new InvalidParameterValueException(String.format("Could not find Cisco Vnmc device with id: %d", ciscoVnmcResourceId));
              }
              responseList.add(ciscoVnmcResource);
          } else {
              PhysicalNetworkVO physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId);
              if (physicalNetwork == null) {
                  throw new InvalidParameterValueException("Could not find a physical network with id: " + physicalNetworkId);
              }
              responseList = _ciscoVnmcDao.listByPhysicalNetwork(physicalNetworkId);
          }
  
          return responseList;
      }
  
      @Override
      public IpDeployer getIpDeployer(Network network) {
          return this;
      }
  
      @Override
      public boolean applyFWRules(Network network, List<? extends FirewallRule> rules) throws ResourceUnavailableException {
  
          if (!_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Firewall, Provider.CiscoVnmc)) {
              logger.error("Firewall service is not provided by Cisco Vnmc device on network {}", network);
              return false;
          }
  
          // Find VNMC host for physical network
          List<CiscoVnmcControllerVO> devices = _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No Cisco Vnmc device on network {}", network);
              return true;
          }
  
          // Find if ASA 1000v is associated with network
          NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
          if (asaForNetwork == null) {
              logger.debug("Cisco ASA 1000v device is not associated with network {}", network);
              return true;
          }
  
          if (network.getState() == Network.State.Allocated) {
              logger.debug("External firewall was asked to apply firewall rules for network {}; this network is not implemented. Skipping backend commands.", network);
              return true;
          }
  
          CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
          HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());
  
          List<FirewallRuleTO> rulesTO = new ArrayList<FirewallRuleTO>();
          for (FirewallRule rule : rules) {
              String address = "0.0.0.0";
              if (rule.getTrafficType() == TrafficType.Ingress) {
                  IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
                  address = sourceIp.getAddress().addr();
              }
              FirewallRuleTO ruleTO = new FirewallRuleTO(rule, null, address, rule.getPurpose(), rule.getTrafficType());
              rulesTO.add(ruleTO);
          }
  
          if (!rulesTO.isEmpty()) {
              SetFirewallRulesCommand cmd = new SetFirewallRulesCommand(rulesTO);
              cmd.setContextParam(NetworkElementCommand.GUEST_VLAN_TAG, BroadcastDomainType.getValue(network.getBroadcastUri()));
              cmd.setContextParam(NetworkElementCommand.GUEST_NETWORK_CIDR, network.getCidr());
              Answer answer = _agentMgr.easySend(ciscoVnmcHost.getId(), cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "Unable to apply firewall rules to Cisco ASA 1000v appliance due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, network.getDataCenterId());
              }
          }
  
          return true;
      }
  
      @Override
      public boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException {
  
          if (!_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.PortForwarding, Provider.CiscoVnmc)) {
              logger.error("Port forwarding service is not provided by Cisco Vnmc device on network {}", network);
              return false;
          }
  
          // Find VNMC host for physical network
          List<CiscoVnmcControllerVO> devices = _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No Cisco Vnmc device on network {}", network);
              return true;
          }
  
          // Find if ASA 1000v is associated with network
          NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
          if (asaForNetwork == null) {
              logger.debug("Cisco ASA 1000v device is not associated with network {}", network);
              return true;
          }
  
          if (network.getState() == Network.State.Allocated) {
              logger.debug("External firewall was asked to apply port forwarding rules for network with ID {}; this network is not implemented. Skipping backend commands.", network);
              return true;
          }
  
          CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
          HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());
  
          List<PortForwardingRuleTO> rulesTO = new ArrayList<PortForwardingRuleTO>();
          for (PortForwardingRule rule : rules) {
              IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
              Vlan vlan = _vlanDao.findById(sourceIp.getVlanId());
              PortForwardingRuleTO ruleTO = new PortForwardingRuleTO(rule, vlan.getVlanTag(), sourceIp.getAddress().addr());
              rulesTO.add(ruleTO);
          }
  
          if (!rulesTO.isEmpty()) {
              SetPortForwardingRulesCommand cmd = new SetPortForwardingRulesCommand(rulesTO);
              cmd.setContextParam(NetworkElementCommand.GUEST_VLAN_TAG, BroadcastDomainType.getValue(network.getBroadcastUri()));
              cmd.setContextParam(NetworkElementCommand.GUEST_NETWORK_CIDR, network.getCidr());
              Answer answer = _agentMgr.easySend(ciscoVnmcHost.getId(), cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "Unable to apply port forwarding rules to Cisco ASA 1000v appliance due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, network.getDataCenterId());
              }
          }
  
          return true;
      }
  
      @Override
      public boolean applyStaticNats(Network network, List<? extends StaticNat> rules) throws ResourceUnavailableException {
          if (!_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.StaticNat, Provider.CiscoVnmc)) {
              logger.error("Static NAT service is not provided by Cisco Vnmc device on network {}", network);
              return false;
          }
  
          // Find VNMC host for physical network
          List<CiscoVnmcControllerVO> devices = _ciscoVnmcDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No Cisco Vnmc device on network {}", network);
              return true;
          }
  
          // Find if ASA 1000v is associated with network
          NetworkAsa1000vMapVO asaForNetwork = _networkAsa1000vMapDao.findByNetworkId(network.getId());
          if (asaForNetwork == null) {
              logger.debug("Cisco ASA 1000v device is not associated with network {}", network);
              return true;
          }
  
          if (network.getState() == Network.State.Allocated) {
              logger.debug("External firewall was asked to apply static NAT rules for network with ID {}; this network is not implemented. Skipping backend commands.", network);
              return true;
          }
  
          CiscoVnmcControllerVO ciscoVnmcDevice = devices.get(0);
          HostVO ciscoVnmcHost = _hostDao.findById(ciscoVnmcDevice.getHostId());
  
          List<StaticNatRuleTO> rulesTO = new ArrayList<StaticNatRuleTO>();
          for (StaticNat rule : rules) {
              IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
              StaticNatRuleTO ruleTO =
                  new StaticNatRuleTO(rule.getSourceIpAddressId(), sourceIp.getAddress().addr(), null, null, rule.getDestIpAddress(), null, null, null, rule.isForRevoke(),
                      false);
              rulesTO.add(ruleTO);
          }
  
          if (!rulesTO.isEmpty()) {
              SetStaticNatRulesCommand cmd = new SetStaticNatRulesCommand(rulesTO, null);
              cmd.setContextParam(NetworkElementCommand.GUEST_VLAN_TAG, BroadcastDomainType.getValue(network.getBroadcastUri()));
              cmd.setContextParam(NetworkElementCommand.GUEST_NETWORK_CIDR, network.getCidr());
              Answer answer = _agentMgr.easySend(ciscoVnmcHost.getId(), cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "Unable to apply static NAT rules to Cisco ASA 1000v appliance due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, network.getDataCenterId());
              }
          }
  
          return true;
      }
  
      @Override
      public boolean applyIps(Network network, List<? extends PublicIpAddress> ipAddress, Set<Service> services) throws ResourceUnavailableException {
          // TODO Auto-generated method stub
          return false;
      }
  
      @Override
      public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) {
          // TODO Auto-generated method stub
          return null;
      }
  
      @Override
      public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map<String, String> details, List<String> hostTags) {
          if (!(startup[0] instanceof StartupExternalFirewallCommand)) {
              return null;
          }
          host.setType(Host.Type.ExternalFirewall);
          return host;
      }
  
      @Override
      public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException {
          if (host.getType() != com.cloud.host.Host.Type.ExternalFirewall) {
              return null;
          }
          return new DeleteHostAnswer(true);
      }
  
      @Override
      public CiscoAsa1000vDevice addCiscoAsa1000vResource(AddCiscoAsa1000vResourceCmd cmd) {
          Long physicalNetworkId = cmd.getPhysicalNetworkId();
          CiscoAsa1000vDevice ciscoAsa1000vResource = null;
  
          PhysicalNetworkVO physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId);
          if (physicalNetwork == null) {
              throw new InvalidParameterValueException("Could not find phyical network with ID: " + physicalNetworkId);
          }
  
          ciscoAsa1000vResource = new CiscoAsa1000vDeviceVO(physicalNetworkId, cmd.getManagementIp().trim(), cmd.getInPortProfile(), cmd.getClusterId());
          try {
              _ciscoAsa1000vDao.persist((CiscoAsa1000vDeviceVO)ciscoAsa1000vResource);
          } catch (EntityExistsException e) {
              throw new InvalidParameterValueException("An ASA 1000v appliance already exists with same configuration");
          }
  
          return ciscoAsa1000vResource;
      }
  
      @Override
      public CiscoAsa1000vResourceResponse createCiscoAsa1000vResourceResponse(CiscoAsa1000vDevice ciscoAsa1000vDeviceVO) {
          CiscoAsa1000vResourceResponse response = new CiscoAsa1000vResourceResponse();
          response.setId(ciscoAsa1000vDeviceVO.getUuid());
          response.setManagementIp(ciscoAsa1000vDeviceVO.getManagementIp());
          response.setInPortProfile(ciscoAsa1000vDeviceVO.getInPortProfile());
  
          NetworkAsa1000vMapVO networkAsaMap = _networkAsa1000vMapDao.findByAsa1000vId(ciscoAsa1000vDeviceVO.getId());
          if (networkAsaMap != null) {
              response.setGuestNetworkId(networkAsaMap.getNetworkId());
          }
  
          return response;
      }
  
      @Override
      public boolean deleteCiscoAsa1000vResource(DeleteCiscoAsa1000vResourceCmd cmd) {
          Long asaResourceId = cmd.getCiscoAsa1000vResourceId();
          CiscoAsa1000vDeviceVO asaResource = _ciscoAsa1000vDao.findById(asaResourceId);
          if (asaResource == null) {
              throw new InvalidParameterValueException("Could not find a Cisco ASA 1000v appliance with id " + asaResourceId);
          }
  
          NetworkAsa1000vMapVO networkAsaMap = _networkAsa1000vMapDao.findByAsa1000vId(asaResource.getId());
          if (networkAsaMap != null) {
              throw new CloudRuntimeException(String.format("Cisco ASA 1000v appliance %s cannot be deleted as it is associated with guest network", asaResource));
          }
  
          _ciscoAsa1000vDao.remove(asaResourceId);
  
          return true;
      }
  
      @Override
      public List<CiscoAsa1000vDeviceVO> listCiscoAsa1000vResources(ListCiscoAsa1000vResourcesCmd cmd) {
          Long physicalNetworkId = cmd.getPhysicalNetworkId();
          Long ciscoAsa1000vResourceId = cmd.getCiscoAsa1000vResourceId();
          List<CiscoAsa1000vDeviceVO> responseList = new ArrayList<CiscoAsa1000vDeviceVO>();
  
          if (physicalNetworkId == null && ciscoAsa1000vResourceId == null) {
              throw new InvalidParameterValueException("Either physical network Id or Asa 1000v device Id must be specified");
          }
  
          if (ciscoAsa1000vResourceId != null) {
              CiscoAsa1000vDeviceVO ciscoAsa1000vResource = _ciscoAsa1000vDao.findById(ciscoAsa1000vResourceId);
              if (ciscoAsa1000vResource == null) {
                  throw new InvalidParameterValueException("Could not find Cisco Asa 1000v device with id: " + ciscoAsa1000vResourceId);
              }
              responseList.add(ciscoAsa1000vResource);
          } else {
              PhysicalNetworkVO physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId);
              if (physicalNetwork == null) {
                  throw new InvalidParameterValueException("Could not find a physical network with id: " + physicalNetworkId);
              }
              responseList = _ciscoAsa1000vDao.listByPhysicalNetwork(physicalNetworkId);
          }
  
          return responseList;
      }
  
      @Override
      public CiscoAsa1000vDevice assignAsa1000vToNetwork(Network network) {
          List<CiscoAsa1000vDeviceVO> asaList = _ciscoAsa1000vDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          for (CiscoAsa1000vDeviceVO asa : asaList) {
              NetworkAsa1000vMapVO assignedToNetwork = _networkAsa1000vMapDao.findByAsa1000vId(asa.getId());
              if (assignedToNetwork == null) {
                  NetworkAsa1000vMapVO networkAsaMap = new NetworkAsa1000vMapVO(network.getId(), asa.getId());
                  _networkAsa1000vMapDao.persist(networkAsaMap);
                  return asa;
              }
          }
          return null;
      }
  
      private void unassignAsa1000vFromNetwork(Network network) {
          NetworkAsa1000vMapVO networkAsaMap = _networkAsa1000vMapDao.findByNetworkId(network.getId());
          if (networkAsaMap != null) {
              _networkAsa1000vMapDao.remove(networkAsaMap.getId());
          }
      }
  }
  