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.net.URISyntaxException;
  import java.util.ArrayList;
  import java.util.Collections;
  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 org.springframework.stereotype.Component;
  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.ConfigurePortForwardingRulesOnLogicalRouterAnswer;
  import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterCommand;
  import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterAnswer;
  import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterCommand;
  import com.cloud.agent.api.ConfigureSharedNetworkUuidAnswer;
  import com.cloud.agent.api.ConfigureSharedNetworkUuidCommand;
  import com.cloud.agent.api.ConfigureSharedNetworkVlanIdAnswer;
  import com.cloud.agent.api.ConfigureSharedNetworkVlanIdCommand;
  import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterAnswer;
  import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterCommand;
  import com.cloud.agent.api.CreateLogicalRouterAnswer;
  import com.cloud.agent.api.CreateLogicalRouterCommand;
  import com.cloud.agent.api.CreateLogicalSwitchPortAnswer;
  import com.cloud.agent.api.CreateLogicalSwitchPortCommand;
  import com.cloud.agent.api.DeleteLogicalRouterAnswer;
  import com.cloud.agent.api.DeleteLogicalRouterCommand;
  import com.cloud.agent.api.DeleteLogicalSwitchPortAnswer;
  import com.cloud.agent.api.DeleteLogicalSwitchPortCommand;
  import com.cloud.agent.api.FindLogicalSwitchPortAnswer;
  import com.cloud.agent.api.FindLogicalSwitchPortCommand;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.StartupNiciraNvpCommand;
  import com.cloud.agent.api.UpdateLogicalSwitchPortCommand;
  import com.cloud.agent.api.to.PortForwardingRuleTO;
  import com.cloud.agent.api.to.StaticNatRuleTO;
  import com.cloud.api.ApiDBUtils;
  import com.cloud.api.commands.AddNiciraNvpDeviceCmd;
  import com.cloud.api.commands.DeleteNiciraNvpDeviceCmd;
  import com.cloud.api.commands.ListNiciraNvpDeviceNetworksCmd;
  import com.cloud.api.commands.ListNiciraNvpDevicesCmd;
  import com.cloud.api.response.NiciraNvpDeviceResponse;
  import com.cloud.configuration.ConfigurationManager;
  import com.cloud.dc.Vlan;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.deploy.DeployDestination;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InsufficientCapacityException;
  import com.cloud.exception.InvalidParameterValueException;
  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.IpAddress;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.Network;
  import com.cloud.network.Network.Capability;
  import com.cloud.network.Network.GuestType;
  import com.cloud.network.Network.Provider;
  import com.cloud.network.Network.Service;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.NiciraNvpDeviceVO;
  import com.cloud.network.NiciraNvpNicMappingVO;
  import com.cloud.network.NiciraNvpRouterMappingVO;
  import com.cloud.network.PhysicalNetwork;
  import com.cloud.network.PhysicalNetworkServiceProvider;
  import com.cloud.network.PublicIpAddress;
  import com.cloud.network.addr.PublicIp;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkServiceMapDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.NiciraNvpDao;
  import com.cloud.network.dao.NiciraNvpNicMappingDao;
  import com.cloud.network.dao.NiciraNvpRouterMappingDao;
  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.NiciraNvpResource;
  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.DB;
  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.net.NetUtils;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.ReservationContext;
  import com.cloud.vm.VirtualMachineProfile;
  import com.cloud.vm.dao.NicDao;
  
  @Component
  public class NiciraNvpElement extends AdapterBase implements ConnectivityProvider, SourceNatServiceProvider, PortForwardingServiceProvider, StaticNatServiceProvider,
  NiciraNvpElementService, ResourceStateAdapter, IpDeployer {
  
      private static final int MAX_PORT = 65535;
      private static final int MIN_PORT = 0;
  
  
      private static final Map<Service, Map<Capability, String>> capabilities = setCapabilities();
  
      @Inject
      protected NicDao nicDao;
      @Inject
      protected ResourceManager resourceMgr;
      @Inject
      protected PhysicalNetworkDao physicalNetworkDao;
      @Inject
      protected PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao;
      @Inject
      protected NiciraNvpDao niciraNvpDao;
      @Inject
      protected HostDetailsDao hostDetailsDao;
      @Inject
      protected HostDao hostDao;
      @Inject
      protected AgentManager agentMgr;
      @Inject
      protected NiciraNvpNicMappingDao niciraNvpNicMappingDao;
      @Inject
      protected NiciraNvpRouterMappingDao niciraNvpRouterMappingDao;
      @Inject
      protected NetworkDao networkDao;
      @Inject
      protected NetworkOrchestrationService networkManager;
      @Inject
      protected NetworkModel networkModel;
      @Inject
      protected ConfigurationManager configMgr;
      @Inject
      protected NetworkServiceMapDao ntwkSrvcDao;
      @Inject
      protected VlanDao vlanDao;
      @Inject
      protected IpAddressManager ipAddrMgr;
  
      @Override
      public Map<Service, Map<Capability, String>> getCapabilities() {
          return capabilities;
      }
  
      @Override
      public Provider getProvider() {
          return Provider.NiciraNvp;
      }
  
      protected boolean canHandle(Network network, Service service) {
          logger.debug(String.format("Checking if NiciraNvpElement can handle service %s on network %s", service.getName(), network));
          if (network.getBroadcastDomainType() != BroadcastDomainType.Lswitch) {
              return false;
          }
  
          if (!networkModel.isProviderForNetwork(getProvider(), network.getId())) {
              logger.debug(String.format("NiciraNvpElement is not a provider for network %s", network));
              return false;
          }
  
          if (!ntwkSrvcDao.canProviderSupportServiceInNetwork(network.getId(), service, Network.Provider.NiciraNvp)) {
              logger.debug(String.format("NiciraNvpElement can't provide the %s service on network %s", service.getName(), network));
              return false;
          }
  
          return true;
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          super.configure(name, params);
          resourceMgr.registerResourceStateAdapter(name, this);
          return true;
      }
  
      @Override
      public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException,
      ResourceUnavailableException, InsufficientCapacityException {
          logger.debug(String.format("entering NiciraNvpElement implement function for network %s (state %s)", network, network.getState()));
  
          if (!canHandle(network, Service.Connectivity)) {
              return false;
          }
  
          if (network.getBroadcastUri() == null) {
              logger.error("Nic has no broadcast Uri with the LSwitch Uuid");
              return false;
          }
  
          List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
              return false;
          }
          NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
          hostDao.loadDetails(niciraNvpHost);
  
          Account owner = context.getAccount();
  
          /*
           * TODO Shouldn't we lock the network as we might need to do
           * multiple operations that should be done only once.
           */
  
          if (network.getGuestType().equals(GuestType.Shared)){
              //Support Shared Networks
              String lSwitchUuid = BroadcastDomainType.getValue(network.getBroadcastUri());
              String ownerName = context.getDomain().getName() + "-" + context.getAccount().getAccountName();
              return sharedNetworkSupport(network, lSwitchUuid, ownerName, niciraNvpHost);
          }
          else if (network.getGuestType().equals(GuestType.Isolated) && networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.NiciraNvp)) {
              // Implement SourceNat immediately as we have al the info already
              logger.debug("Apparently we are supposed to provide SourceNat on this network");
  
              PublicIp sourceNatIp = ipAddrMgr.assignSourceNatIpAddressToGuestNetwork(owner, network);
              String publicCidr = sourceNatIp.getAddress().addr() + "/" + NetUtils.getCidrSize(sourceNatIp.getVlanNetmask());
              String internalCidr = network.getGateway() + "/" + network.getCidr().split("/")[1];
              // assuming a vlan:
              String vtag = sourceNatIp.getVlanTag();
              BroadcastDomainType tiep = null;
              try {
                  tiep = BroadcastDomainType.getTypeOf(vtag);
              } catch (URISyntaxException use) {
                  throw new CloudRuntimeException("vlantag for sourceNatIp is not valid: " + vtag, use);
              }
              if (tiep == BroadcastDomainType.Vlan) {
                  vtag = BroadcastDomainType.Vlan.getValueFrom(BroadcastDomainType.fromString(vtag));
              } else if (!(tiep == BroadcastDomainType.UnDecided || tiep == BroadcastDomainType.Native)) {
                  throw new CloudRuntimeException("only vlans are supported for sourceNatIp, at this moment: " + vtag);
              }
              long vlanid = (Vlan.UNTAGGED.equals(vtag)) ? 0 : Long.parseLong(vtag);
  
              CreateLogicalRouterCommand cmd =
                      new CreateLogicalRouterCommand(niciraNvpHost.getDetail("l3gatewayserviceuuid"), vlanid, BroadcastDomainType.getValue(network.getBroadcastUri()),
                              "router-" + network.getDisplayText(), publicCidr, sourceNatIp.getGateway(), internalCidr, context.getDomain().getName() + "-" +
                                      context.getAccount().getAccountName());
              CreateLogicalRouterAnswer answer = (CreateLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
              if (answer.getResult() == false) {
                  logger.error(String.format("Failed to create Logical Router for network %s", network));
                  return false;
              }
  
              NiciraNvpRouterMappingVO routermapping = new NiciraNvpRouterMappingVO(answer.getLogicalRouterUuid(), network.getId());
              niciraNvpRouterMappingDao.persist(routermapping);
  
          }
  
          return true;
      }
  
      private boolean sharedNetworkSupport(Network network, String lSwitchUuid, String ownerName, HostVO niciraNvpHost) {
          //Support Shared Networks, we won’t be creating logical router, 2 cases:
          //Case 1) UUID Supplied for VLAN -> This is UUID of the logical router to attach lswitch
          //Case 2) Numerical ID supplied for VLAN -> lswitch is connected to L2 gateway service that we have as an attribute of the NVP device. If no L2 gateway attribute exists then exception.
  
          if (niciraNvpRouterMappingDao.existsMappingForNetworkId(network.getId())){
              //Case 1)
              return sharedNetworkSupportUUIDVlanId(network, lSwitchUuid, ownerName, niciraNvpHost);
          }
          else {
              //Case 2)
              return sharedNetworkSupportNumericalVlanId(network, lSwitchUuid, ownerName, niciraNvpHost);
          }
      }
  
      private boolean sharedNetworkSupportUUIDVlanId(Network network, String lSwitchUuid, String ownerName, HostVO niciraNvpHost) {
          String networkCidr = network.getCidr();
          String vlanGateway = network.getGateway();
          String portIpAddress = createLogicalRouterPortIpAddress(networkCidr, vlanGateway);
          NiciraNvpRouterMappingVO mapRouterNetwork = niciraNvpRouterMappingDao.findByNetworkId(network.getId());
          String lRouterUuid = mapRouterNetwork.getLogicalRouterUuid();
          ConfigureSharedNetworkUuidCommand cmd =
                  new ConfigureSharedNetworkUuidCommand(lRouterUuid, lSwitchUuid, portIpAddress, ownerName, network.getId());
          ConfigureSharedNetworkUuidAnswer answer = (ConfigureSharedNetworkUuidAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
          if (answer.getResult() == false) {
              logger.error(String.format("Failed to configure Logical Router for Shared network %s", network));
              return false;
          }
          return true;
      }
  
      private boolean sharedNetworkSupportNumericalVlanId(Network network, String lSwitchUuid, String ownerName, HostVO niciraNvpHost) {
          List<VlanVO> networkVlans = vlanDao.listVlansByNetworkId(network.getId());
          if (networkVlans.size() == 1){
              for (VlanVO vlanVO : networkVlans) {
                  long vlanId = Long.parseLong(vlanVO.getVlanTag());
                  String l2GatewayServiceUuid = niciraNvpHost.getDetail("l2gatewayserviceuuid");
                  if (l2GatewayServiceUuid == null){
                      throw new CloudRuntimeException("No L2 Gateway Service Uuid found on " + niciraNvpHost.getName());
                  }
                  ConfigureSharedNetworkVlanIdCommand cmd =
                          new ConfigureSharedNetworkVlanIdCommand(lSwitchUuid, l2GatewayServiceUuid , vlanId, ownerName, network.getId());
                  ConfigureSharedNetworkVlanIdAnswer answer = (ConfigureSharedNetworkVlanIdAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
                  if (answer.getResult() == false) {
                      logger.error(String.format("Failed to configure Shared network %s", network));
                      return false;
                  }
              }
          }
          return true;
      }
  
  
      private String createLogicalRouterPortIpAddress(String networkCidr, String vlanGateway) {
          if (networkCidr != null && vlanGateway != null){
              return networkCidr.replaceFirst("[\\d]{1,3}.[\\d]{1,3}.[\\d]{1,3}.[\\d]{1,3}", vlanGateway);
          }
          return null;
      }
  
      @Override
      public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context)
              throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
  
          if (!canHandle(network, Service.Connectivity)) {
              return false;
          }
  
          if (network.getBroadcastUri() == null) {
              logger.error("Nic has no broadcast Uri with the LSwitch Uuid");
              return false;
          }
  
          NicVO nicVO = nicDao.findById(nic.getId());
  
          List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
              return false;
          }
          NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
  
          NiciraNvpNicMappingVO existingNicMap = niciraNvpNicMappingDao.findByNicUuid(nicVO.getUuid());
          if (existingNicMap != null) {
              FindLogicalSwitchPortCommand findCmd = new FindLogicalSwitchPortCommand(existingNicMap.getLogicalSwitchUuid(), existingNicMap.getLogicalSwitchPortUuid());
              FindLogicalSwitchPortAnswer answer = (FindLogicalSwitchPortAnswer)agentMgr.easySend(niciraNvpHost.getId(), findCmd);
  
              if (answer.getResult()) {
                  logger.warn("Existing Logical Switchport found for nic " + nic.getName() + " with uuid " + existingNicMap.getLogicalSwitchPortUuid());
                  UpdateLogicalSwitchPortCommand cmd =
                          new UpdateLogicalSwitchPortCommand(existingNicMap.getLogicalSwitchPortUuid(), BroadcastDomainType.getValue(network.getBroadcastUri()),
                                  nicVO.getUuid(), context.getDomain().getName() + "-" + context.getAccount().getAccountName(), nic.getName());
                  agentMgr.easySend(niciraNvpHost.getId(), cmd);
                  return true;
              } else {
                  logger.error("Stale entry found for nic " + nic.getName() + " with logical switchport uuid " + existingNicMap.getLogicalSwitchPortUuid());
                  niciraNvpNicMappingDao.remove(existingNicMap.getId());
              }
          }
  
          CreateLogicalSwitchPortCommand cmd =
                  new CreateLogicalSwitchPortCommand(BroadcastDomainType.getValue(network.getBroadcastUri()), nicVO.getUuid(), context.getDomain().getName() + "-" +
                          context.getAccount().getAccountName(), nic.getName());
          CreateLogicalSwitchPortAnswer answer = (CreateLogicalSwitchPortAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
  
          if (answer == null || !answer.getResult()) {
              logger.error("CreateLogicalSwitchPortCommand failed");
              return false;
          }
  
          NiciraNvpNicMappingVO nicMap =
                  new NiciraNvpNicMappingVO(BroadcastDomainType.getValue(network.getBroadcastUri()), answer.getLogicalSwitchPortUuid(), nicVO.getUuid());
          niciraNvpNicMappingDao.persist(nicMap);
  
          return true;
      }
  
      @Override
      public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException,
      ResourceUnavailableException {
  
          if (!canHandle(network, Service.Connectivity)) {
              return false;
          }
  
          if (network.getBroadcastUri() == null) {
              logger.error("Nic has no broadcast Uri with the LSwitch Uuid");
              return false;
          }
  
          NicVO nicVO = nicDao.findById(nic.getId());
  
          List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
              return false;
          }
          NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
  
          NiciraNvpNicMappingVO nicMap = niciraNvpNicMappingDao.findByNicUuid(nicVO.getUuid());
          if (nicMap == null) {
              logger.error(String.format("No mapping for nic %s", nic));
              return false;
          }
  
          DeleteLogicalSwitchPortCommand cmd = new DeleteLogicalSwitchPortCommand(nicMap.getLogicalSwitchUuid(), nicMap.getLogicalSwitchPortUuid());
          DeleteLogicalSwitchPortAnswer answer = (DeleteLogicalSwitchPortAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
  
          if (answer == null || !answer.getResult()) {
              logger.error("DeleteLogicalSwitchPortCommand failed");
              return false;
          }
  
          niciraNvpNicMappingDao.remove(nicMap.getId());
  
          return true;
      }
  
      @Override
      public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
          if (!canHandle(network, Service.Connectivity)) {
              return false;
          }
  
          List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
              return false;
          }
          NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
  
          //Dont destroy logical router when removing Shared Networks
          if (! network.getGuestType().equals(GuestType.Shared) && networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.NiciraNvp)) {
              logger.debug("Apparently we were providing SourceNat on this network");
  
              // Deleting the LogicalRouter will also take care of all provisioned
              // nat rules.
              NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId());
              if (routermapping == null) {
                  logger.warn(String.format("No logical router uuid found for network %s", network));
                  // This might be cause by a failed deployment, so don't make shutdown fail as well.
                  return true;
              }
  
              DeleteLogicalRouterCommand cmd = new DeleteLogicalRouterCommand(routermapping.getLogicalRouterUuid());
              DeleteLogicalRouterAnswer answer = (DeleteLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
              if (answer.getResult() == false) {
                  logger.error(String.format("Failed to delete LogicalRouter for network %s", network));
                  return false;
              }
  
              niciraNvpRouterMappingDao.remove(routermapping.getId());
          }
  
          return true;
      }
  
      @Override
      public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
          if (!canHandle(network, Service.Connectivity)) {
              return false;
          }
  
          return true;
      }
  
      @Override
      public boolean isReady(PhysicalNetworkServiceProvider provider) {
          return true;
      }
  
      @Override
      public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException,
      ResourceUnavailableException {
          // Nothing to do here.
          return true;
      }
  
      @Override
      public boolean canEnableIndividualServices() {
          return true;
      }
  
      @Override
      public boolean verifyServicesCombination(Set<Service> services) {
          // This element can only function in a Nicra Nvp based
          // SDN network, so Connectivity needs to be present here
          if (!services.contains(Service.Connectivity)) {
              logger.warn("Unable to provide services without Connectivity service enabled for this element");
              return false;
          }
          if ((services.contains(Service.PortForwarding) || services.contains(Service.StaticNat)) && !services.contains(Service.SourceNat)) {
              logger.warn("Unable to provide StaticNat and/or PortForwarding without the SourceNat service");
              return false;
          }
          return true;
      }
  
      private static Map<Service, Map<Capability, String>> setCapabilities() {
          Map<Service, Map<Capability, String>> capabilities = new HashMap<Service, Map<Capability, String>>();
  
          // L2 Support : SDN provisioning
          capabilities.put(Service.Connectivity, null);
  
          // L3 Support : Generic?
          capabilities.put(Service.Gateway, null);
  
          // L3 Support : SourceNat
          Map<Capability, String> sourceNatCapabilities = new HashMap<Capability, String>();
          sourceNatCapabilities.put(Capability.SupportedSourceNatTypes, "peraccount");
          sourceNatCapabilities.put(Capability.RedundantRouter, "false");
          capabilities.put(Service.SourceNat, sourceNatCapabilities);
  
          // L3 Support : Port Forwarding
          capabilities.put(Service.PortForwarding, null);
  
          // L3 support : StaticNat
          capabilities.put(Service.StaticNat, null);
  
          return capabilities;
      }
  
      @Override
      public List<Class<?>> getCommands() {
          List<Class<?>> cmdList = new ArrayList<Class<?>>();
          cmdList.add(AddNiciraNvpDeviceCmd.class);
          cmdList.add(DeleteNiciraNvpDeviceCmd.class);
          cmdList.add(ListNiciraNvpDeviceNetworksCmd.class);
          cmdList.add(ListNiciraNvpDevicesCmd.class);
          return cmdList;
      }
  
      @Override
      @DB
      public NiciraNvpDeviceVO addNiciraNvpDevice(AddNiciraNvpDeviceCmd cmd) {
          ServerResource resource = new NiciraNvpResource();
          final String deviceName = Network.Provider.NiciraNvp.getName();
          NetworkDevice networkDevice = NetworkDevice.getNetworkDevice(deviceName);
          if (networkDevice == null) {
              throw new CloudRuntimeException("No network device found for " + deviceName);
          }
          final Long physicalNetworkId = cmd.getPhysicalNetworkId();
          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 (niciraNvpDao.listByPhysicalNetwork(physicalNetworkId).size() != 0) {
              throw new CloudRuntimeException("A NiciraNvp 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", "Nicira Controller - " + cmd.getHost());
          params.put("ip", cmd.getHost());
          params.put("adminuser", cmd.getUsername());
          params.put("adminpass", cmd.getPassword());
          params.put("transportzoneuuid", cmd.getTransportzoneUuid());
          // FIXME What to do with multiple isolation types
          params.put("transportzoneisotype", physicalNetwork.getIsolationMethods().get(0).toLowerCase());
          if (cmd.getL3GatewayServiceUuid() != null) {
              params.put("l3gatewayserviceuuid", cmd.getL3GatewayServiceUuid());
          }
          if (cmd.getL2GatewayServiceUuid() != null) {
              params.put("l2gatewayserviceuuid", cmd.getL2GatewayServiceUuid());
          }
  
          Map<String, Object> hostdetails = new HashMap<String, Object>();
          hostdetails.putAll(params);
  
          try {
              resource.configure(cmd.getHost(), hostdetails);
  
              final Host host = resourceMgr.addHost(zoneId, resource, Host.Type.L2Networking, params);
              if (host != null) {
                  return Transaction.execute(new TransactionCallback<NiciraNvpDeviceVO>() {
                      @Override
                      public NiciraNvpDeviceVO doInTransaction(TransactionStatus status) {
                          NiciraNvpDeviceVO niciraNvpDevice = new NiciraNvpDeviceVO(host.getId(), physicalNetworkId, ntwkSvcProvider.getProviderName(), deviceName);
                          niciraNvpDao.persist(niciraNvpDevice);
  
                          DetailVO detail = new DetailVO(host.getId(), "niciranvpdeviceid", String.valueOf(niciraNvpDevice.getId()));
                          hostDetailsDao.persist(detail);
  
                          return niciraNvpDevice;
                      }
                  });
              } else {
                  throw new CloudRuntimeException("Failed to add Nicira Nvp Device due to internal error.");
              }
          } catch (ConfigurationException e) {
              throw new CloudRuntimeException(e.getMessage());
          }
      }
  
      @Override
      public NiciraNvpDeviceResponse createNiciraNvpDeviceResponse(NiciraNvpDeviceVO niciraNvpDeviceVO) {
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDeviceVO.getHostId());
          hostDao.loadDetails(niciraNvpHost);
  
          NiciraNvpDeviceResponse response = new NiciraNvpDeviceResponse();
          response.setDeviceName(niciraNvpDeviceVO.getDeviceName());
          PhysicalNetwork pnw = ApiDBUtils.findPhysicalNetworkById(niciraNvpDeviceVO.getPhysicalNetworkId());
          if (pnw != null) {
              response.setPhysicalNetworkId(pnw.getUuid());
          }
          response.setId(niciraNvpDeviceVO.getUuid());
          response.setProviderName(niciraNvpDeviceVO.getProviderName());
          response.setHostName(niciraNvpHost.getDetail("ip"));
          response.setTransportZoneUuid(niciraNvpHost.getDetail("transportzoneuuid"));
          response.setL3GatewayServiceUuid(niciraNvpHost.getDetail("l3gatewayserviceuuid"));
          response.setL2GatewayServiceUuid(niciraNvpHost.getDetail("l2gatewayserviceuuid"));
          response.setObjectName("niciranvpdevice");
          return response;
      }
  
      @Override
      public boolean deleteNiciraNvpDevice(DeleteNiciraNvpDeviceCmd cmd) {
          Long niciraDeviceId = cmd.getNiciraNvpDeviceId();
          NiciraNvpDeviceVO niciraNvpDevice = niciraNvpDao.findById(niciraDeviceId);
          if (niciraNvpDevice == null) {
              throw new InvalidParameterValueException("Could not find a nicira device with id " + niciraDeviceId);
          }
  
          // Find the physical network we work for
          Long physicalNetworkId = niciraNvpDevice.getPhysicalNetworkId();
          PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId);
          if (physicalNetwork != null) {
              // Lets see if there are networks that use us
              // Find the nicira networks on this physical network
              List<NetworkVO> networkList = networkDao.listByPhysicalNetwork(physicalNetworkId);
              if (networkList != null) {
                  // Networks with broadcast type lswitch are ours
                  for (NetworkVO network : networkList) {
                      if (network.getBroadcastDomainType() == Networks.BroadcastDomainType.Lswitch) {
                          if ((network.getState() != Network.State.Shutdown) && (network.getState() != Network.State.Destroy)) {
                              throw new CloudRuntimeException("This Nicira Nvp device can not be deleted as there are one or more logical networks provisioned by cloudstack.");
                          }
                      }
                  }
              }
          }
  
          HostVO niciraHost = hostDao.findById(niciraNvpDevice.getHostId());
          Long hostId = niciraHost.getId();
  
          niciraHost.setResourceState(ResourceState.Maintenance);
          hostDao.update(hostId, niciraHost);
          resourceMgr.deleteHost(hostId, false, false);
  
          niciraNvpDao.remove(niciraDeviceId);
          return true;
      }
  
      @Override
      public List<NiciraNvpDeviceVO> listNiciraNvpDevices(ListNiciraNvpDevicesCmd cmd) {
          Long physicalNetworkId = cmd.getPhysicalNetworkId();
          Long niciraNvpDeviceId = cmd.getNiciraNvpDeviceId();
          List<NiciraNvpDeviceVO> responseList = new ArrayList<NiciraNvpDeviceVO>();
  
          if (physicalNetworkId == null && niciraNvpDeviceId == null) {
              throw new InvalidParameterValueException("Either physical network Id or nicira device Id must be specified");
          }
  
          if (niciraNvpDeviceId != null) {
              NiciraNvpDeviceVO niciraNvpDevice = niciraNvpDao.findById(niciraNvpDeviceId);
              if (niciraNvpDevice == null) {
                  throw new InvalidParameterValueException("Could not find Nicira Nvp device with id: " + niciraNvpDevice);
              }
              responseList.add(niciraNvpDevice);
          } else {
              PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId);
              if (physicalNetwork == null) {
                  throw new InvalidParameterValueException("Could not find a physical network with id: " + physicalNetworkId);
              }
              responseList = niciraNvpDao.listByPhysicalNetwork(physicalNetworkId);
          }
  
          return responseList;
      }
  
      @Override
      public List<? extends Network> listNiciraNvpDeviceNetworks(ListNiciraNvpDeviceNetworksCmd cmd) {
          Long niciraDeviceId = cmd.getNiciraNvpDeviceId();
          NiciraNvpDeviceVO niciraNvpDevice = niciraNvpDao.findById(niciraDeviceId);
          if (niciraNvpDevice == null) {
              throw new InvalidParameterValueException("Could not find a nicira device with id " + niciraDeviceId);
          }
  
          // Find the physical network we work for
          Long physicalNetworkId = niciraNvpDevice.getPhysicalNetworkId();
          PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId);
          if (physicalNetwork == null) {
              // No such physical network, so no provisioned networks
              return Collections.emptyList();
          }
  
          // Find the nicira networks on this physical network
          List<NetworkVO> networkList = networkDao.listByPhysicalNetwork(physicalNetworkId);
          if (networkList == null) {
              return Collections.emptyList();
          }
  
          // Networks with broadcast type lswitch are ours
          List<NetworkVO> responseList = new ArrayList<NetworkVO>();
          for (NetworkVO network : networkList) {
              if (network.getBroadcastDomainType() == Networks.BroadcastDomainType.Lswitch) {
                  responseList.add(network);
              }
          }
  
          return responseList;
      }
  
      @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 StartupNiciraNvpCommand)) {
              return null;
          }
          host.setType(Host.Type.L2Networking);
          return host;
      }
  
      @Override
      public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException {
          if (!(host.getType() == Host.Type.L2Networking)) {
              return null;
          }
          return new DeleteHostAnswer(true);
      }
  
      /**
       * From interface SourceNatServiceProvider
       */
      @Override
      public IpDeployer getIpDeployer(Network network) {
          return this;
      }
  
      /**
       * From interface IpDeployer
       *
       * @param network
       * @param ipAddress
       * @param services
       * @return
       * @throws ResourceUnavailableException
       */
      @Override
      public boolean applyIps(Network network, List<? extends PublicIpAddress> ipAddress, Set<Service> services) throws ResourceUnavailableException {
          if (services.contains(Service.SourceNat)) {
              // Only if we need to provide SourceNat we need to configure the logical router
              // SourceNat is required for StaticNat and PortForwarding
              List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
              if (devices.isEmpty()) {
                  logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
                  return false;
              }
              NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
              HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
              hostDao.loadDetails(niciraNvpHost);
  
              NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId());
              if (routermapping == null) {
                  logger.error(String.format("No logical router uuid found for network %s", network));
                  return false;
              }
  
              List<String> cidrs = new ArrayList<String>();
              for (PublicIpAddress ip : ipAddress) {
                  if (ip.getState() == IpAddress.State.Releasing) {
                      // If we are releasing we don't need to push this ip to
                      // the Logical Router
                      continue;
                  }
                  cidrs.add(ip.getAddress().addr() + "/" + NetUtils.getCidrSize(ip.getNetmask()));
              }
              ConfigurePublicIpsOnLogicalRouterCommand cmd =
                      new ConfigurePublicIpsOnLogicalRouterCommand(routermapping.getLogicalRouterUuid(), niciraNvpHost.getDetail("l3gatewayserviceuuid"), cidrs);
              ConfigurePublicIpsOnLogicalRouterAnswer answer = (ConfigurePublicIpsOnLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
              //FIXME answer can be null if the host is down
              return answer.getResult();
          } else {
              logger.debug("No need to provision ip addresses as we are not providing L3 services.");
          }
  
          return true;
      }
  
      /**
       * From interface StaticNatServiceProvider
       */
      @Override
      public boolean applyStaticNats(Network network, List<? extends StaticNat> rules) throws ResourceUnavailableException {
          if (!canHandle(network, Service.StaticNat)) {
              return false;
          }
  
          List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
              return false;
          }
          NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
  
          NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId());
          if (routermapping == null) {
              logger.error(String.format("No logical router uuid found for network %s", network));
              return false;
          }
  
          List<StaticNatRuleTO> staticNatRules = new ArrayList<StaticNatRuleTO>();
          for (StaticNat rule : rules) {
              IpAddress sourceIp = networkModel.getIp(rule.getSourceIpAddressId());
              // Force the nat rule into the StaticNatRuleTO, no use making a new TO object
              // we only need the source and destination ip. Unfortunately no mention if a rule
              // is new.
              StaticNatRuleTO ruleTO =
                      new StaticNatRuleTO(1, sourceIp.getAddress().addr(), MIN_PORT, MAX_PORT, rule.getDestIpAddress(), MIN_PORT, MAX_PORT, "any", rule.isForRevoke(), false);
              staticNatRules.add(ruleTO);
          }
  
          ConfigureStaticNatRulesOnLogicalRouterCommand cmd = new ConfigureStaticNatRulesOnLogicalRouterCommand(routermapping.getLogicalRouterUuid(), staticNatRules);
          ConfigureStaticNatRulesOnLogicalRouterAnswer answer = (ConfigureStaticNatRulesOnLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
  
          return answer.getResult();
      }
  
      /**
       * From interface PortForwardingServiceProvider
       */
      @Override
      public boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException {
          if (!canHandle(network, Service.PortForwarding)) {
              return false;
          }
  
          List<NiciraNvpDeviceVO> devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId());
          if (devices.isEmpty()) {
              logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId());
              return false;
          }
          NiciraNvpDeviceVO niciraNvpDevice = devices.get(0);
          HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId());
  
          NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId());
          if (routermapping == null) {
              logger.error(String.format("No logical router uuid found for network %s", network));
              return false;
          }
  
          List<PortForwardingRuleTO> portForwardingRules = 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());
              portForwardingRules.add(ruleTO);
          }
  
          ConfigurePortForwardingRulesOnLogicalRouterCommand cmd =
                  new ConfigurePortForwardingRulesOnLogicalRouterCommand(routermapping.getLogicalRouterUuid(), portForwardingRules);
          ConfigurePortForwardingRulesOnLogicalRouterAnswer answer = (ConfigurePortForwardingRulesOnLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd);
  
          return answer.getResult();
      }
  
  }
  