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;
  
  import static com.cloud.network.Network.Service.SecurityGroup;
  
  import java.math.BigInteger;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeSet;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.acl.ControlledEntity.ACLType;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
  import org.apache.cloudstack.framework.config.ConfigKey;
  import org.apache.cloudstack.framework.config.Configurable;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
  import org.apache.cloudstack.network.NetworkPermissionVO;
  import org.apache.cloudstack.network.dao.NetworkPermissionDao;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang3.StringUtils;
  
  import com.cloud.api.ApiDBUtils;
  import com.cloud.configuration.Config;
  import com.cloud.configuration.ConfigurationManager;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.PodVlanMapVO;
  import com.cloud.dc.Vlan;
  import com.cloud.dc.Vlan.VlanType;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.PodVlanMapDao;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.domain.Domain;
  import com.cloud.domain.DomainVO;
  import com.cloud.domain.dao.DomainDao;
  import com.cloud.exception.InsufficientAddressCapacityException;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.PermissionDeniedException;
  import com.cloud.exception.UnsupportedServiceException;
  import com.cloud.hypervisor.Hypervisor.HypervisorType;
  import com.cloud.network.IpAddress.State;
  import com.cloud.network.Network.Capability;
  import com.cloud.network.Network.GuestType;
  import com.cloud.network.Network.IpAddresses;
  import com.cloud.network.Network.Provider;
  import com.cloud.network.Network.Service;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.network.addr.PublicIp;
  import com.cloud.network.dao.FirewallRulesDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.dao.NetworkAccountDao;
  import com.cloud.network.dao.NetworkAccountVO;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkDomainDao;
  import com.cloud.network.dao.NetworkDomainVO;
  import com.cloud.network.dao.NetworkServiceMapDao;
  import com.cloud.network.dao.NetworkServiceMapVO;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
  import com.cloud.network.dao.PhysicalNetworkServiceProviderVO;
  import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao;
  import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO;
  import com.cloud.network.dao.PhysicalNetworkVO;
  import com.cloud.network.dao.TungstenGuestNetworkIpAddressDao;
  import com.cloud.network.dao.UserIpv6AddressDao;
  import com.cloud.network.element.IpDeployer;
  import com.cloud.network.element.IpDeployingRequester;
  import com.cloud.network.element.NetworkElement;
  import com.cloud.network.element.UserDataServiceProvider;
  import com.cloud.network.router.VirtualRouter;
  import com.cloud.network.rules.FirewallRule.Purpose;
  import com.cloud.network.rules.FirewallRuleVO;
  import com.cloud.network.rules.dao.PortForwardingRulesDao;
  import com.cloud.network.vpc.Vpc;
  import com.cloud.network.vpc.VpcGatewayVO;
  import com.cloud.network.vpc.dao.PrivateIpDao;
  import com.cloud.network.vpc.dao.VpcDao;
  import com.cloud.network.vpc.dao.VpcGatewayDao;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.offering.NetworkOffering.Detail;
  import com.cloud.offerings.NetworkOfferingServiceMapVO;
  import com.cloud.offerings.NetworkOfferingVO;
  import com.cloud.offerings.dao.NetworkOfferingDao;
  import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
  import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
  import com.cloud.projects.Project;
  import com.cloud.projects.ProjectAccount;
  import com.cloud.projects.dao.ProjectAccountDao;
  import com.cloud.projects.dao.ProjectDao;
  import com.cloud.user.Account;
  import com.cloud.user.AccountManager;
  import com.cloud.user.AccountVO;
  import com.cloud.user.DomainManager;
  import com.cloud.user.User;
  import com.cloud.user.dao.AccountDao;
  import com.cloud.utils.Pair;
  import com.cloud.utils.component.AdapterBase;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.EntityManager;
  import com.cloud.utils.db.JoinBuilder;
  import com.cloud.utils.db.JoinBuilder.JoinType;
  import com.cloud.utils.db.SearchBuilder;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.db.SearchCriteria.Op;
  import com.cloud.utils.exception.CloudRuntimeException;
  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.VMInstanceVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.VirtualMachine.Type;
  import com.cloud.vm.VirtualMachineManager;
  import com.cloud.vm.dao.NicDao;
  import com.cloud.vm.dao.NicSecondaryIpDao;
  import com.cloud.vm.dao.VMInstanceDao;
  
  public class NetworkModelImpl extends ManagerBase implements NetworkModel, Configurable {
      public static final String UNABLE_TO_USE_NETWORK = "Unable to use network with id= %s, permission denied";
      @Inject
      EntityManager _entityMgr;
      @Inject
      DataCenterDao _dcDao = null;
      @Inject
      VlanDao _vlanDao = null;
      @Inject
      IPAddressDao _ipAddressDao = null;
      @Inject
      AccountDao _accountDao = null;
      @Inject
      DomainDao _domainDao = null;
      @Inject
      AccountManager _accountMgr;
      @Inject
      ConfigurationDao _configDao;
      @Inject
      ConfigurationManager _configMgr;
      @Inject
      NetworkOfferingDao _networkOfferingDao = null;
      @Inject
      NetworkDao _networksDao = null;
      @Inject
      NicDao _nicDao = null;
      @Inject
      PodVlanMapDao _podVlanMapDao;
      @Inject
      VpcGatewayDao _vpcGatewayDao;
      @Inject
      ProjectDao projectDao;
      @Inject
      NetworkPermissionDao _networkPermissionDao;
      @Inject
      VpcDao vpcDao;
  
      private List<NetworkElement> networkElements;
  
      public List<NetworkElement> getNetworkElements() {
          return networkElements;
      }
  
      public void setNetworkElements(List<NetworkElement> networkElements) {
          this.networkElements = networkElements;
      }
  
      @Inject
      NetworkAccountDao _networkAccountDao;
      @Inject
      NetworkDomainDao _networkDomainDao;
      @Inject
      VMInstanceDao _vmDao;
  
      @Inject
      FirewallRulesDao _firewallDao;
      @Inject
      DomainManager _domainMgr;
  
      @Inject
      protected NetworkOfferingServiceMapDao _ntwkOfferingSrvcDao;
      @Inject
      PhysicalNetworkDao _physicalNetworkDao;
      @Inject
      PhysicalNetworkServiceProviderDao _pNSPDao;
      @Inject
      PortForwardingRulesDao _portForwardingRulesDao;
      @Inject
      PhysicalNetworkTrafficTypeDao _pNTrafficTypeDao;
      @Inject
      NetworkServiceMapDao _ntwkSrvcDao;
      @Inject
      PrivateIpDao _privateIpDao;
      @Inject
      UserIpv6AddressDao _ipv6Dao;
      @Inject
      NicSecondaryIpDao _nicSecondaryIpDao;
      @Inject
      ApplicationLoadBalancerRuleDao _appLbRuleDao;
      @Inject
      private ProjectAccountDao _projectAccountDao;
      @Inject
      NetworkOfferingDetailsDao _ntwkOffDetailsDao;
      @Inject
      private NetworkService _networkService;
      @Inject
      TungstenGuestNetworkIpAddressDao tungstenGuestNetworkIpAddressDao;
  
      private final HashMap<String, NetworkOfferingVO> _systemNetworks = new HashMap<String, NetworkOfferingVO>(5);
  
      SearchBuilder<IPAddressVO> IpAddressSearch;
      SearchBuilder<NicVO> NicForTrafficTypeSearch;
  
      private boolean _allowSubdomainNetworkAccess;
  
      private Map<String, String> _configs;
  
      protected boolean _executeInSequenceNtwkElmtCmd;
  
      HashMap<Long, Long> _lastNetworkIdsToFree = new HashMap<Long, Long>();
  
      static HashMap<Service, List<Provider>> s_serviceToImplementedProvidersMap = new HashMap<Service, List<Provider>>();
      static HashMap<String, String> s_providerToNetworkElementMap = new HashMap<String, String>();
  
      /**
       *
       */
      public NetworkModelImpl() {
          super();
      }
  
      @Override
      public NetworkElement getElementImplementingProvider(String providerName) {
          String elementName = s_providerToNetworkElementMap.get(providerName);
          NetworkElement element = AdapterBase.getAdapterByName(networkElements, elementName);
          return element;
      }
  
      @Override
      public List<Service> getElementServices(Provider provider) {
          NetworkElement element = getElementImplementingProvider(provider.getName());
          if (element == null) {
              throw new InvalidParameterValueException("Unable to find the Network Element implementing the Service Provider '" + provider.getName() + "'");
          }
          return new ArrayList<Service>(element.getCapabilities().keySet());
      }
  
      @Override
      public boolean canElementEnableIndividualServices(Provider provider) {
          NetworkElement element = getElementImplementingProvider(provider.getName());
          if (element == null) {
              throw new InvalidParameterValueException("Unable to find the Network Element implementing the Service Provider '" + provider.getName() + "'");
          }
          return element.canEnableIndividualServices();
      }
  
      Set<Purpose> getPublicIpPurposeInRules(PublicIpAddress ip, boolean includeRevoked, boolean includingFirewall) {
          Set<Purpose> result = new HashSet<Purpose>();
          List<FirewallRuleVO> rules = null;
          if (includeRevoked) {
              rules = _firewallDao.listByIp(ip.getId());
          } else {
              rules = _firewallDao.listByIpAndNotRevoked(ip.getId());
          }
  
          if (rules == null || rules.isEmpty()) {
              return null;
          }
  
          for (FirewallRuleVO rule : rules) {
              if (rule.getPurpose() != Purpose.Firewall || includingFirewall) {
                  result.add(rule.getPurpose());
              }
          }
  
          return result;
      }
  
      @Override
      public Map<PublicIpAddress, Set<Service>> getIpToServices(List<? extends PublicIpAddress> publicIps, boolean postApplyRules, boolean includingFirewall) {
          Map<PublicIpAddress, Set<Service>> ipToServices = new HashMap<PublicIpAddress, Set<Service>>();
  
          if (publicIps != null && !publicIps.isEmpty()) {
              Set<Long> networkSNAT = new HashSet<Long>();
              for (PublicIpAddress ip : publicIps) {
                  Set<Service> services = ipToServices.get(ip);
                  if (services == null) {
                      services = new HashSet<Service>();
                  }
                  if (ip.isSourceNat()) {
                      if (!networkSNAT.contains(ip.getAssociatedWithNetworkId())) {
                          services.add(Service.SourceNat);
                          networkSNAT.add(ip.getAssociatedWithNetworkId());
                      } else {
                          CloudRuntimeException ex = new CloudRuntimeException("Multiple generic source NAT IPs provided for network");
                          // see the IPAddressVO.java class.
                          IPAddressVO ipAddr = ApiDBUtils.findIpAddressById(ip.getAssociatedWithNetworkId());
                          String ipAddrUuid = ip.getAssociatedWithNetworkId().toString();
                          if (ipAddr != null) {
                              ipAddrUuid = ipAddr.getUuid();
                          }
                          ex.addProxyObject(ipAddrUuid, "networkId");
                          throw ex;
                      }
                  }
                  ipToServices.put(ip, services);
  
                  // if IP in allocating state then it will not have any rules attached so skip IPAssoc to network service
                  // provider
                  if (ip.getState() == State.Allocating) {
                      continue;
                  }
  
                  // check if any active rules are applied on the public IP
                  Set<Purpose> purposes = getPublicIpPurposeInRules(ip, false, includingFirewall);
                  // Firewall rules didn't cover static NAT
                  if (ip.isOneToOneNat() && ip.getAssociatedWithVmId() != null) {
                      if (purposes == null) {
                          purposes = new HashSet<Purpose>();
                      }
                      purposes.add(Purpose.StaticNat);
                  }
                  if (purposes == null || purposes.isEmpty()) {
                      // since no active rules are there check if any rules are applied on the public IP but are in
                      // revoking state
  
                      purposes = getPublicIpPurposeInRules(ip, true, includingFirewall);
                      if (ip.isOneToOneNat()) {
                          if (purposes == null) {
                              purposes = new HashSet<Purpose>();
                          }
                          purposes.add(Purpose.StaticNat);
                      }
                      if (purposes == null || purposes.isEmpty()) {
                          // IP is not being used for any purpose so skip IPAssoc to network service provider
                          continue;
                      } else {
                          if (postApplyRules) {
                              // no active rules/revoked rules are associated with this public IP, so remove the
                              // association with the provider
                              if (ip.isSourceNat()) {
                                  logger.debug("Not releasing ip " + ip.getAddress().addr() + " as it is in use for SourceNat");
                              } else {
                                  ip.setState(State.Releasing);
                              }
                          } else {
                              if (ip.getState() == State.Releasing) {
                                  // rules are not revoked yet, so don't let the network service provider revoke the IP
                                  // association
                                  // mark IP is allocated so that IP association will not be removed from the provider
                                  ip.setState(State.Allocated);
                              }
                          }
                      }
                  }
                  if (purposes.contains(Purpose.StaticNat)) {
                      services.add(Service.StaticNat);
                  }
                  if (purposes.contains(Purpose.LoadBalancing)) {
                      services.add(Service.Lb);
                  }
                  if (purposes.contains(Purpose.PortForwarding)) {
                      services.add(Service.PortForwarding);
                  }
                  if (purposes.contains(Purpose.Vpn)) {
                      services.add(Service.Vpn);
                  }
                  if (purposes.contains(Purpose.Firewall)) {
                      services.add(Service.Firewall);
                  }
                  if (services.isEmpty()) {
                      continue;
                  }
                  ipToServices.put(ip, services);
              }
          }
          return ipToServices;
      }
  
      public boolean canIpUsedForNonConserveService(PublicIp ip, Service service) {
          // If it's non-conserve mode, then the new ip should not be used by any other services
          List<PublicIpAddress> ipList = new ArrayList<PublicIpAddress>();
          ipList.add(ip);
          Map<PublicIpAddress, Set<Service>> ipToServices = getIpToServices(ipList, false, false);
          Set<Service> services = ipToServices.get(ip);
          // Not used currently, safe
          if (services == null || services.isEmpty()) {
              return true;
          }
          // Since it's non-conserve mode, only one service should used for IP
          if (services.size() != 1) {
              throw new InvalidParameterValueException("There are multiple services used ip " + ip.getAddress() + ".");
          }
          if (service != null && !((Service)services.toArray()[0] == service || service.equals(Service.Firewall))) {
              throw new InvalidParameterValueException("The IP " + ip.getAddress() + " is already used as " + ((Service)services.toArray()[0]).getName() + " rather than " +
                  service.getName());
          }
          return true;
      }
  
      Map<Service, Set<Provider>> getServiceProvidersMap(long networkId) {
          Map<Service, Set<Provider>> map = new HashMap<Service, Set<Provider>>();
          List<NetworkServiceMapVO> nsms = _ntwkSrvcDao.getServicesInNetwork(networkId);
          for (NetworkServiceMapVO nsm : nsms) {
              Set<Provider> providers = map.get(Service.getService(nsm.getService()));
              if (providers == null) {
                  providers = new HashSet<Provider>();
              }
              providers.add(Provider.getProvider(nsm.getProvider()));
              map.put(Service.getService(nsm.getService()), providers);
          }
          return map;
      }
  
      public boolean canIpUsedForService(PublicIp publicIp, Service service, Long networkId) {
          List<PublicIpAddress> ipList = new ArrayList<PublicIpAddress>();
          ipList.add(publicIp);
          Map<PublicIpAddress, Set<Service>> ipToServices = getIpToServices(ipList, false, true);
          Set<Service> services = ipToServices.get(publicIp);
          if (services == null || services.isEmpty()) {
              return true;
          }
  
          if (networkId == null) {
              networkId = publicIp.getAssociatedWithNetworkId();
          }
  
          // We only support one provider for one service now
          Map<Service, Set<Provider>> serviceToProviders = getServiceProvidersMap(networkId);
          // Since IP already has service to bind with, the oldProvider can't be null
          Set<Provider> newProviders = serviceToProviders.get(service);
          if (newProviders == null || newProviders.isEmpty()) {
              throw new InvalidParameterValueException("There is no new provider for IP " + publicIp.getAddress() + " of service " + service.getName() + "!");
          }
          Provider newProvider = (Provider)newProviders.toArray()[0];
          Set<Provider> oldProviders = serviceToProviders.get(services.toArray()[0]);
          Provider oldProvider = (Provider)oldProviders.toArray()[0];
          Network network = _networksDao.findById(networkId);
          NetworkElement oldElement = getElementImplementingProvider(oldProvider.getName());
          NetworkElement newElement = getElementImplementingProvider(newProvider.getName());
          if (oldElement instanceof IpDeployingRequester && newElement instanceof IpDeployingRequester) {
              IpDeployer oldIpDeployer = ((IpDeployingRequester)oldElement).getIpDeployer(network);
              IpDeployer newIpDeployer = ((IpDeployingRequester)newElement).getIpDeployer(network);
              // FIXME: I ignored this check
          } else {
              throw new InvalidParameterValueException("Ip cannot be applied for new provider!");
          }
          return true;
      }
  
      Map<Provider, Set<Service>> getProviderServicesMap(long networkId) {
          Map<Provider, Set<Service>> map = new HashMap<Provider, Set<Service>>();
          List<NetworkServiceMapVO> nsms = _ntwkSrvcDao.getServicesInNetwork(networkId);
          for (NetworkServiceMapVO nsm : nsms) {
              Set<Service> services = map.get(Provider.getProvider(nsm.getProvider()));
              if (services == null) {
                  services = new HashSet<Service>();
              }
              services.add(Service.getService(nsm.getService()));
              map.put(Provider.getProvider(nsm.getProvider()), services);
          }
          return map;
      }
  
      @Override
      public Map<Provider, ArrayList<PublicIpAddress>> getProviderToIpList(Network network, Map<PublicIpAddress, Set<Service>> ipToServices) {
          NetworkOffering offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
          boolean isForNsx = isProviderForNetworkOffering(Provider.Nsx, offering.getId());
          if (!offering.isConserveMode() && !isForNsx) {
              for (PublicIpAddress ip : ipToServices.keySet()) {
                  Set<Service> services = new HashSet<Service>();
                  services.addAll(ipToServices.get(ip));
                  if (services != null && services.contains(Service.Firewall)) {
                      services.remove(Service.Firewall);
                  }
                  if (services != null && services.size() > 1) {
                      throw new CloudRuntimeException("Ip " + ip.getAddress() + " is used by multiple services!");
                  }
              }
          }
          Map<Service, Set<PublicIpAddress>> serviceToIps = new HashMap<Service, Set<PublicIpAddress>>();
          for (PublicIpAddress ip : ipToServices.keySet()) {
              for (Service service : ipToServices.get(ip)) {
                  Set<PublicIpAddress> ips = serviceToIps.get(service);
                  if (ips == null) {
                      ips = new HashSet<PublicIpAddress>();
                  }
                  ips.add(ip);
                  serviceToIps.put(service, ips);
              }
          }
          // TODO Check different provider for same IP
          Map<Provider, Set<Service>> providerToServices = getProviderServicesMap(network.getId());
          Map<Provider, ArrayList<PublicIpAddress>> providerToIpList = new HashMap<Provider, ArrayList<PublicIpAddress>>();
          for (Provider provider : providerToServices.keySet()) {
              if (!(getElementImplementingProvider(provider.getName()) instanceof IpDeployingRequester)) {
                  continue;
              }
              Set<Service> services = providerToServices.get(provider);
              ArrayList<PublicIpAddress> ipList = new ArrayList<PublicIpAddress>();
              Set<PublicIpAddress> ipSet = new HashSet<PublicIpAddress>();
              for (Service service : services) {
                  Set<PublicIpAddress> serviceIps = serviceToIps.get(service);
                  if (serviceIps == null || serviceIps.isEmpty()) {
                      continue;
                  }
                  ipSet.addAll(serviceIps);
              }
              Set<PublicIpAddress> sourceNatIps = serviceToIps.get(Service.SourceNat);
              if (sourceNatIps != null && !sourceNatIps.isEmpty()) {
                  ipList.addAll(0, sourceNatIps);
                  ipSet.removeAll(sourceNatIps);
              }
              ipList.addAll(ipSet);
              providerToIpList.put(provider, ipList);
          }
          return providerToIpList;
      }
  
      @Override
      public List<IPAddressVO> listPublicIpsAssignedToGuestNtwk(long accountId, long associatedNetworkId, Boolean sourceNat) {
          SearchCriteria<IPAddressVO> sc = IpAddressSearch.create();
          sc.setParameters("accountId", accountId);
          sc.setParameters("associatedWithNetworkId", associatedNetworkId);
          if (sourceNat != null) {
              sc.addAnd("sourceNat", SearchCriteria.Op.EQ, sourceNat);
          }
          sc.setJoinParameters("virtualNetworkVlanSB", "vlanType", VlanType.VirtualNetwork);
  
          return _ipAddressDao.search(sc, null);
      }
  
      @Override
      public List<IPAddressVO> listPublicIpsAssignedToGuestNtwk(long associatedNetworkId, Boolean sourceNat) {
          SearchCriteria<IPAddressVO> sc = IpAddressSearch.create();
          sc.setParameters("associatedWithNetworkId", associatedNetworkId);
  
          if (sourceNat != null) {
              sc.addAnd("sourceNat", SearchCriteria.Op.EQ, sourceNat);
          }
          sc.setJoinParameters("virtualNetworkVlanSB", "vlanType", VlanType.VirtualNetwork);
  
          return _ipAddressDao.search(sc, null);
      }
  
      @Override
      public List<IPAddressVO> listPublicIpsAssignedToAccount(long accountId, long dcId, Boolean sourceNat) {
          SearchCriteria<IPAddressVO> sc = IpAddressSearch.create();
          sc.setParameters("accountId", accountId);
          sc.setParameters("dataCenterId", dcId);
  
          if (sourceNat != null) {
              sc.addAnd("sourceNat", SearchCriteria.Op.EQ, sourceNat);
          }
          sc.setJoinParameters("virtualNetworkVlanSB", "vlanType", VlanType.VirtualNetwork);
  
          return _ipAddressDao.search(sc, null);
      }
  
      @Override
      public List<? extends Nic> getNics(long vmId) {
          return _nicDao.listByVmId(vmId);
      }
  
      @Override
      public String getNextAvailableMacAddressInNetwork(long networkId) throws InsufficientAddressCapacityException {
          NetworkVO network = _networksDao.findById(networkId);
          Integer zoneIdentifier = MACIdentifier.value();
          if (zoneIdentifier.intValue() == 0) {
              zoneIdentifier = Long.valueOf(network.getDataCenterId()).intValue();
          }
          String mac;
          do {
              mac = _networksDao.getNextAvailableMacAddress(networkId, zoneIdentifier);
              if (mac == null) {
                  throw new InsufficientAddressCapacityException("Unable to create another mac address", Network.class, networkId);
              }
          } while(! isMACUnique(mac));
          return mac;
      }
  
      private boolean isMACUnique(String mac) {
          return (_nicDao.findByMacAddress(mac) == null);
      }
  
      @Override
      @DB
      public Network getNetwork(long id) {
          return _networksDao.findById(id);
      }
  
      @Override
      public boolean canUseForDeploy(Network network) {
          if (network.getTrafficType() != TrafficType.Guest) {
              return false;
          }
          if (network.getGuestType() == GuestType.L2 || listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) {
              return true; // do not check free IPs if there is no service in the network
          }
          boolean hasFreeIps = true;
          if (network.getGuestType() == GuestType.Shared) {
              if (network.getGateway() != null) {
                  hasFreeIps = _ipAddressDao.countFreeIPsInNetwork(network.getId()) > 0;
              }
              if (!hasFreeIps) {
                  return false;
              }
          } else {
              if (network.getCidr() == null) {
                  logger.debug("Network - {} has NULL CIDR.", network);
                  return false;
              }
              hasFreeIps = (getAvailableIps(network, null)).size() > 0;
          }
  
          return hasFreeIps;
      }
  
      @Override
      public boolean areThereIPv6AddressAvailableInNetwork(long networkId) {
          Network network = _networksDao.findById(networkId);
          if (network == null) {
              return false;
          }
          if (network.getIp6Gateway() == null) {
              return false;
          }
          List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(networkId);
          for (Vlan vlan : vlans) {
              if (isIP6AddressAvailableInVlan(vlan.getId())) {
                  return true;
              }
          }
          return false;
      }
  
      @Override
      public boolean isIP6AddressAvailableInVlan(long vlanId) {
          VlanVO vlan = _vlanDao.findById(vlanId);
          if (vlan.getIp6Range() == null) {
              return false;
          }
          long existedCount = _ipv6Dao.countExistedIpsInVlan(vlanId);
          BigInteger existedInt = BigInteger.valueOf(existedCount);
          BigInteger rangeInt = NetUtils.countIp6InRange(vlan.getIp6Range());
          return (existedInt.compareTo(rangeInt) < 0);
      }
  
      @Override
      public Map<Service, Map<Capability, String>> getNetworkCapabilities(long networkId) {
  
          Map<Service, Map<Capability, String>> networkCapabilities = new HashMap<Service, Map<Capability, String>>();
  
          // list all services of this networkOffering
          List<NetworkServiceMapVO> servicesMap = _ntwkSrvcDao.getServicesInNetwork(networkId);
          for (NetworkServiceMapVO instance : servicesMap) {
              Service service = Service.getService(instance.getService());
              NetworkElement element = getElementImplementingProvider(instance.getProvider());
              if (element != null) {
                  Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
                  if (elementCapabilities != null) {
                      networkCapabilities.put(service, elementCapabilities.get(service));
                  }
              }
          }
  
          return networkCapabilities;
      }
  
      @Override
      public Map<Capability, String> getNetworkServiceCapabilities(long networkId, Service service) {
  
          if (!areServicesSupportedInNetwork(networkId, service)) {
              // TBD: networkId to uuid. No VO object being passed. So we will need to call
              // addProxyObject with hardcoded tablename. Or we should probably look up the correct dao proxy object.
              throw new UnsupportedServiceException("Service " + service.getName() + " is not supported in the network id=" + networkId);
          }
  
          Map<Capability, String> serviceCapabilities = new HashMap<Capability, String>();
  
          // get the Provider for this Service for this offering
          String provider = _ntwkSrvcDao.getProviderForServiceInNetwork(networkId, service);
  
          NetworkElement element = getElementImplementingProvider(provider);
          if (element != null) {
              Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
              ;
  
              if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
                  throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
                      " implementing Provider=" + provider);
              }
              serviceCapabilities = elementCapabilities.get(service);
          }
  
          return serviceCapabilities;
      }
  
      @Override
      public Map<Capability, String> getNetworkOfferingServiceCapabilities(NetworkOffering offering, Service service) {
  
          if (!areServicesSupportedByNetworkOffering(offering.getId(), service)) {
              // TBD: We should be sending networkOfferingId and not the offering object itself.
              throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the network offering " + offering);
          }
  
          Map<Capability, String> serviceCapabilities = new HashMap<Capability, String>();
  
          // get the Provider for this Service for this offering
          List<String> providers = _ntwkOfferingSrvcDao.listProvidersForServiceForNetworkOffering(offering.getId(), service);
          if (providers.isEmpty()) {
              // TBD: We should be sending networkOfferingId and not the offering object itself.
              throw new InvalidParameterValueException("Service " + service.getName() + " is not supported by the network offering " + offering);
          }
  
          // FIXME - in post 3.0 we are going to support multiple providers for the same service per network offering, so
          // we have to calculate capabilities for all of them
          String provider = providers.get(0);
  
          // FIXME we return the capabilities of the first provider of the service - what if we have multiple providers
          // for same Service?
          NetworkElement element = getElementImplementingProvider(provider);
          if (element != null) {
              Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
              ;
  
              if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
                  // TBD: We should be sending providerId and not the offering object itself.
                  throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
                      " implementing Provider=" + provider);
              }
              serviceCapabilities = elementCapabilities.get(service);
          }
  
          return serviceCapabilities;
      }
  
      @Override
      public NetworkVO getSystemNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) {
          // find system public network offering
          Long networkOfferingId = null;
          List<NetworkOfferingVO> offerings = _networkOfferingDao.listSystemNetworkOfferings();
          for (NetworkOfferingVO offering : offerings) {
              if (offering.getTrafficType() == trafficType) {
                  networkOfferingId = offering.getId();
                  break;
              }
          }
  
          if (networkOfferingId == null) {
              throw new InvalidParameterValueException("Unable to find system network offering with traffic type " + trafficType);
          }
  
          List<NetworkVO> networks = _networksDao.listBy(Account.ACCOUNT_ID_SYSTEM, networkOfferingId, zoneId);
          if (networks == null || networks.isEmpty()) {
              // TBD: send uuid instead of zoneId. Hardcode tablename in call to addProxyObject().
              throw new InvalidParameterValueException("Unable to find network with traffic type " + trafficType + " in zone " + zoneId);
          }
          return networks.get(0);
      }
  
      @Override
      public NetworkVO getNetworkWithSGWithFreeIPs(Account account, Long zoneId) {
          List<NetworkVO> networks = _networksDao.listByZoneSecurityGroup(zoneId);
          if (networks == null || networks.isEmpty()) {
              return null;
          }
          NetworkVO ret_network = null;
          for (NetworkVO nw : networks) {
              try {
                  checkNetworkPermissions(account, nw);
              } catch (PermissionDeniedException e) {
                  continue;
              }
  
              List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(nw.getId());
              for (VlanVO vlan : vlans) {
                  if (_ipAddressDao.countFreeIpsInVlan(vlan.getId()) > 0) {
                      ret_network = nw;
                      break;
                  }
              }
              if (ret_network != null) {
                  break;
              }
          }
          if (ret_network == null) {
              logger.debug("Can not find network with security group enabled with free IPs");
          }
          return ret_network;
      }
  
      @Override
      public NetworkVO getNetworkWithSecurityGroupEnabled(Long zoneId) {
          List<NetworkVO> networks = _networksDao.listByZoneSecurityGroup(zoneId);
          if (networks == null || networks.isEmpty()) {
              return null;
          }
  
          if (networks.size() > 1) {
              logger.debug("There are multiple network with security group enabled? select one of them...");
          }
          return networks.get(0);
      }
  
      @Override
      public PublicIpAddress getPublicIpAddress(long ipAddressId) {
          IPAddressVO addr = _ipAddressDao.findById(ipAddressId);
          if (addr == null) {
              return null;
          }
  
          return PublicIp.createFromAddrAndVlan(addr, _vlanDao.findById(addr.getVlanId()));
      }
  
      @Override
      public List<VlanVO> listPodVlans(long podId) {
          List<VlanVO> vlans = _vlanDao.listVlansForPodByType(podId, VlanType.DirectAttached);
          return vlans;
      }
  
      @Override
      public List<NetworkVO> listNetworksUsedByVm(long vmId, boolean isSystem) {
          List<NetworkVO> networks = new ArrayList<NetworkVO>();
  
          List<NicVO> nics = _nicDao.listByVmId(vmId);
          if (nics != null) {
              for (Nic nic : nics) {
                  NetworkVO network = _networksDao.findByIdIncludingRemoved(nic.getNetworkId());
  
                  if (isNetworkSystem(network) == isSystem) {
                      networks.add(network);
                  }
              }
          }
  
          return networks;
      }
  
      @Override
      public Nic getNicInNetwork(long vmId, long networkId) {
          return _nicDao.findByNtwkIdAndInstanceId(networkId, vmId);
      }
  
      @Override
      public String getIpInNetwork(long vmId, long networkId) {
          Nic guestNic = getNicInNetwork(vmId, networkId);
          assert (guestNic != null && guestNic.getIPv4Address() != null) : "Vm doesn't belong to network associated with " + "ipAddress or ip4 address is null";
          return guestNic.getIPv4Address();
      }
  
      @Override
      public String getIpInNetworkIncludingRemoved(long vmId, long networkId) {
          Nic guestNic = getNicInNetworkIncludingRemoved(vmId, networkId);
          assert (guestNic != null && guestNic.getIPv4Address() != null) : "Vm doesn't belong to network associated with " + "ipAddress or ip4 address is null";
          return guestNic.getIPv4Address();
      }
  
      @Override
      public List<NicVO> getNicsForTraffic(long vmId, TrafficType type) {
          SearchCriteria<NicVO> sc = NicForTrafficTypeSearch.create();
          sc.setParameters("instance", vmId);
          sc.setJoinParameters("network", "traffictype", type);
  
          return _nicDao.search(sc, null);
      }
  
      @Override
      public IpAddress getIp(long ipAddressId) {
          return _ipAddressDao.findById(ipAddressId);
      }
  
      @Override
      public Network getDefaultNetworkForVm(long vmId) {
          Nic defaultNic = getDefaultNic(vmId);
          if (defaultNic == null) {
              return null;
          } else {
              return _networksDao.findById(defaultNic.getNetworkId());
          }
      }
  
      @Override
      public Nic getDefaultNic(long vmId) {
          List<NicVO> nics = _nicDao.listByVmId(vmId);
          Nic defaultNic = null;
          if (nics != null) {
              for (Nic nic : nics) {
                  if (nic.isDefaultNic()) {
                      defaultNic = nic;
                      break;
                  }
              }
          } else {
              logger.debug("Unable to find default network for the vm; vm doesn't have any nics");
              return null;
          }
  
          if (defaultNic == null) {
              logger.debug("Unable to find default network for the vm; vm doesn't have default nic");
          }
  
          return defaultNic;
  
      }
  
      @Override
      public UserDataServiceProvider getUserDataUpdateProvider(Network network) {
          if (network == null) {
              logger.warn("No network details, can't fetch user data provider");
              return null;
          }
  
          String userDataProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.UserData);
          if (userDataProvider == null) {
              logger.debug("Network " + network + " doesn't support service " + Service.UserData.getName());
              return null;
          }
  
          return (UserDataServiceProvider)getElementImplementingProvider(userDataProvider);
      }
  
      @Override
      public  boolean isSharedNetworkWithoutServices (long networkId) {
  
          Network network = _networksDao.findById(networkId);
  
          if (network != null && network.getGuestType() != GuestType.Shared) {
              return false;
          }
  
          List<Service> services = listNetworkOfferingServices(network.getNetworkOfferingId());
  
          if (services == null || services.isEmpty()) {
              return true;
          }
  
          return false;
      }
  
  
      @Override
      public boolean areServicesSupportedByNetworkOffering(long networkOfferingId, Service... services) {
          return (_ntwkOfferingSrvcDao.areServicesSupportedByNetworkOffering(networkOfferingId, services));
      }
  
      @Override
      public boolean areServicesSupportedInNetwork(long networkId, Service... services) {
          return (_ntwkSrvcDao.areServicesSupportedInNetwork(networkId, services));
      }
  
      @Override
      public String getIpOfNetworkElementInVirtualNetwork(long accountId, long dataCenterId) {
  
          List<NetworkVO> virtualNetworks = _networksDao.listByZoneAndGuestType(accountId, dataCenterId, GuestType.Isolated, false);
  
          if (virtualNetworks.isEmpty()) {
              logger.trace("Unable to find default Virtual network for account: {}", () -> _accountDao.findById(accountId));
              return null;
          }
  
          NetworkVO virtualNetwork = virtualNetworks.get(0);
  
          NicVO networkElementNic = _nicDao.findByNetworkIdAndType(virtualNetwork.getId(), Type.DomainRouter);
  
          if (networkElementNic != null) {
              return networkElementNic.getIPv4Address();
          } else {
              logger.warn("Unable to set find network element for the network {}", virtualNetwork);
              return null;
          }
      }
  
      @Override
      public List<NetworkVO> listNetworksForAccount(long accountId, long zoneId, GuestType type) {
          List<NetworkVO> accountNetworks = new ArrayList<NetworkVO>();
          List<NetworkVO> zoneNetworks = _networksDao.listByZone(zoneId);
  
          for (NetworkVO network : zoneNetworks) {
              if (!isNetworkSystem(network)) {
                  if (network.getGuestType() == GuestType.Shared || !_networksDao.listBy(accountId, network.getId()).isEmpty()) {
                      if (type == null || type == network.getGuestType()) {
                          accountNetworks.add(network);
                      }
                  }
              }
          }
          return accountNetworks;
      }
  
      @Override
      public List<NetworkVO> listAllNetworksInAllZonesByType(GuestType type) {
          List<NetworkVO> networks = new ArrayList<NetworkVO>();
          for (NetworkVO network : _networksDao.listAll()) {
              if (!isNetworkSystem(network)) {
                  networks.add(network);
              }
          }
          return networks;
      }
  
      @Override
      public Long getDedicatedNetworkDomain(long networkId) {
          NetworkDomainVO networkMaps = _networkDomainDao.getDomainNetworkMapByNetworkId(networkId);
          if (networkMaps != null) {
              return networkMaps.getDomainId();
          } else {
              return null;
          }
      }
  
      @Override
      public Integer getNetworkRate(long networkId, Long vmId) {
          VMInstanceVO vm = null;
          if (vmId != null) {
              vm = _vmDao.findById(vmId);
          }
          final Network network = getNetwork(networkId);
          final NetworkOffering ntwkOff = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId());
  
          // For user VM: For default nic use network rate from the service/compute offering,
          //              or on NULL from vm.network.throttling.rate global setting
          // For router: Get network rate for guest and public networks from the guest network offering
          //              or on NULL from network.throttling.rate
          // For others: Use network rate from their network offering,
          //              or on NULL from network.throttling.rate setting at zone > global level
          // http://docs.cloudstack.apache.org/en/latest/adminguide/service_offerings.html#network-throttling
          if (vm != null) {
              switch (vm.getType()) {
                  case User:
                      final Nic nic = _nicDao.findByNtwkIdAndInstanceId(networkId, vmId);
                      if (nic != null && nic.isDefaultNic()) {
                          return _configMgr.getServiceOfferingNetworkRate(vm.getServiceOfferingId(), network.getDataCenterId());
                      }
                      break;
                  case DomainRouter:
                      if (TrafficType.Guest.equals(network.getTrafficType())) {
                          final Nic routerNic = _nicDao.findByNtwkIdAndInstanceId(networkId, vmId);
                          if (routerNic != null) {
                              return _configMgr.getNetworkOfferingNetworkRate(network.getNetworkOfferingId(), network.getDataCenterId());
                          }
                      } else if (TrafficType.Public.equals(network.getTrafficType())) {
                          List<NicVO> routerNics = _nicDao.listByVmId(vmId);
                          for (final Nic routerNic : routerNics) {
                              final NetworkVO nw = _networksDao.findById(routerNic.getNetworkId());
                              if (TrafficType.Guest.equals(nw.getTrafficType())) {
                                  return _configMgr.getNetworkOfferingNetworkRate(nw.getNetworkOfferingId(), network.getDataCenterId());
                              }
                          }
                      }
                      break;
                  case ConsoleProxy:
                  case SecondaryStorageVm:
                      return -1;
              }
          }
          if (ntwkOff != null) {
              return _configMgr.getNetworkOfferingNetworkRate(ntwkOff.getId(), network.getDataCenterId());
          }
          final Integer networkRate = NetworkOrchestrationService.NetworkThrottlingRate.valueIn(network.getDataCenterId());
          if (networkRate != null && networkRate > 0) {
              return networkRate;
          }
          return -1;
      }
  
      @Override
      public String getAccountNetworkDomain(long accountId, long zoneId) {
          String networkDomain = _accountDao.findById(accountId).getNetworkDomain();
  
          if (networkDomain == null) {
              // get domain level network domain
              return getDomainNetworkDomain(_accountDao.findById(accountId).getDomainId(), zoneId);
          }
  
          return networkDomain;
      }
  
      @Override
      public String getStartIpAddress(long networkId) {
          List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(networkId);
          if (vlans.isEmpty()) {
              return null;
          }
  
          String startIP = vlans.get(0).getIpRange().split("-")[0];
  
          for (VlanVO vlan : vlans) {
              String startIP1 = vlan.getIpRange().split("-")[0];
              long startIPLong = NetUtils.ip2Long(startIP);
              long startIPLong1 = NetUtils.ip2Long(startIP1);
  
              if (startIPLong1 < startIPLong) {
                  startIP = startIP1;
              }
          }
  
          return startIP;
      }
  
      @Override
      public Long getPodIdForVlan(long vlanDbId) {
          PodVlanMapVO podVlanMaps = _podVlanMapDao.listPodVlanMapsByVlan(vlanDbId);
          if (podVlanMaps == null) {
              return null;
          } else {
              return podVlanMaps.getPodId();
          }
      }
  
      @Override
      public Map<Service, Set<Provider>> getNetworkOfferingServiceProvidersMap(long networkOfferingId) {
          Map<Service, Set<Provider>> serviceProviderMap = new HashMap<Service, Set<Provider>>();
          List<NetworkOfferingServiceMapVO> map = _ntwkOfferingSrvcDao.listByNetworkOfferingId(networkOfferingId);
  
          for (NetworkOfferingServiceMapVO instance : map) {
              String service = instance.getService();
              Set<Provider> providers;
              providers = serviceProviderMap.get(Service.getService(service));
              if (providers == null) {
                  providers = new HashSet<Provider>();
              }
              providers.add(Provider.getProvider(instance.getProvider()));
              serviceProviderMap.put(Service.getService(service), providers);
          }
  
          return serviceProviderMap;
      }
  
      @Override
      public boolean isProviderSupportServiceInNetwork(long networkId, Service service, Provider provider) {
          return _ntwkSrvcDao.canProviderSupportServiceInNetwork(networkId, service, provider);
      }
  
      @Override
      public boolean isAnyServiceSupportedInNetwork(long networkId, Provider provider, Service... services) {
          return _ntwkSrvcDao.isAnyServiceSupportedInNetwork(networkId, provider, services);
      }
  
      @Override
      public List<? extends Provider> listSupportedNetworkServiceProviders(String serviceName) {
          Network.Service service = null;
          if (serviceName != null) {
              service = Network.Service.getService(serviceName);
              if (service == null) {
                  throw new InvalidParameterValueException("Invalid Network Service=" + serviceName);
              }
          }
  
          Set<Provider> supportedProviders = new HashSet<Provider>();
  
          if (service != null) {
              List<Provider> providers = s_serviceToImplementedProvidersMap.get(service);
              if (providers != null && !providers.isEmpty()) {
                  supportedProviders.addAll(providers);
              }
          } else {
              for (List<Provider> pList : s_serviceToImplementedProvidersMap.values()) {
                  supportedProviders.addAll(pList);
              }
          }
  
          return new ArrayList<Provider>(supportedProviders);
      }
  
      @Override
      public Provider getDefaultUniqueProviderForService(String serviceName) {
          List<? extends Provider> providers = listSupportedNetworkServiceProviders(serviceName);
          if (providers.isEmpty()) {
              throw new CloudRuntimeException("No providers supporting service " + serviceName + " found in cloudStack");
          }
          if (providers.size() > 1) {
              throw new CloudRuntimeException("More than 1 provider supporting service " + serviceName + " found in cloudStack");
          }
  
          return providers.get(0);
      }
  
      @Override
      public long findPhysicalNetworkId(long zoneId, String tag, TrafficType trafficType) {
          List<PhysicalNetworkVO> pNtwks = new ArrayList<PhysicalNetworkVO>();
          if (trafficType != null) {
              pNtwks = _physicalNetworkDao.listByZoneAndTrafficType(zoneId, trafficType);
          } else {
              pNtwks = _physicalNetworkDao.listByZone(zoneId);
          }
  
          if (pNtwks.isEmpty()) {
              throw new InvalidParameterValueException("Unable to find physical network in zone id=" + zoneId);
          }
  
          if (pNtwks.size() > 1) {
              return getPhysicalNetworkId(zoneId, pNtwks, tag);
          } else {
              return pNtwks.get(0).getId();
          }
      }
  
      private Long getPhysicalNetworkId(long zoneId, List<PhysicalNetworkVO> pNtwks, String tag) {
          Long pNtwkId = null;
          for (PhysicalNetwork pNtwk : pNtwks) {
              if (tag == null && pNtwk.getTags().isEmpty()) {
                  logger.debug("Found physical network {} with null tag", pNtwk);
                  if (pNtwkId != null) {
                      throw new CloudRuntimeException(String.format("There is more than 1 physical" +
                              " network with empty tag in the zone %s", _dcDao.findById(zoneId)));
                  }
                  pNtwkId = pNtwk.getId();
              } else if (tag != null && pNtwk.getTags().contains(tag)) {
                  logger.debug("Found physical network {} based on requested tags {}", pNtwk, tag);
                  pNtwkId = pNtwk.getId();
                  break;
              }
          }
          if (pNtwkId == null) {
              throw new InvalidParameterValueException("Unable to find physical network which match the tags " + tag);
          }
          return pNtwkId;
      }
  
      @Override
      public List<Long> listNetworkOfferingsForUpgrade(long networkId) {
          List<Long> offeringsToReturn = new ArrayList<Long>();
          NetworkOffering originalOffering = _entityMgr.findById(NetworkOffering.class, getNetwork(networkId).getNetworkOfferingId());
  
          boolean securityGroupSupportedByOriginalOff = areServicesSupportedByNetworkOffering(originalOffering.getId(), Service.SecurityGroup);
  
          // security group supported property should be the same
  
          List<Long> offerings = _networkOfferingDao.getOfferingIdsToUpgradeFrom(originalOffering);
  
          for (Long offeringId : offerings) {
              if (areServicesSupportedByNetworkOffering(offeringId, Service.SecurityGroup) == securityGroupSupportedByOriginalOff) {
                  offeringsToReturn.add(offeringId);
              }
          }
  
          return offeringsToReturn;
      }
  
      @Override
      public boolean isSecurityGroupSupportedInNetwork(Network network) {
          if (network.getTrafficType() != TrafficType.Guest) {
              logger.trace("Security group can be enabled for Guest networks only; and network " + network + " has a diff traffic type");
              return false;
          }
  
          Long physicalNetworkId = network.getPhysicalNetworkId();
  
          // physical network id can be null in Guest Network in Basic zone, so locate the physical network
          if (physicalNetworkId == null) {
              physicalNetworkId = findPhysicalNetworkId(network.getDataCenterId(), null, null);
          }
  
          return isServiceEnabledInNetwork(physicalNetworkId, network, SecurityGroup);
      }
  
      @Override
      public PhysicalNetwork getDefaultPhysicalNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) {
  
          List<PhysicalNetworkVO> networkList = _physicalNetworkDao.listByZoneAndTrafficType(zoneId, trafficType);
          DataCenter dc = ApiDBUtils.findZoneById(zoneId);
          String dcUuid = String.valueOf(zoneId);
          if (dc != null) {
              dcUuid = dc.getUuid();
          }
  
          if (networkList.isEmpty()) {
              InvalidParameterValueException ex =
                  new InvalidParameterValueException("Unable to find the default physical network with traffic=" + trafficType + " in the specified zone id");
              ex.addProxyObject(dcUuid, "zoneId");
              throw ex;
          }
  
          if (networkList.size() > 1) {
              InvalidParameterValueException ex =
                  new InvalidParameterValueException("More than one physical networks exist in zone id=" + zoneId + " with traffic type=" + trafficType);
              ex.addProxyObject(dcUuid, "zoneId");
              throw ex;
          }
  
          return networkList.get(0);
      }
  
      @Override
      public String getDefaultManagementTrafficLabel(long zoneId, HypervisorType hypervisorType) {
          try {
              PhysicalNetwork mgmtPhyNetwork = getDefaultPhysicalNetworkByZoneAndTrafficType(zoneId, TrafficType.Management);
              PhysicalNetworkTrafficTypeVO mgmtTraffic = _pNTrafficTypeDao.findBy(mgmtPhyNetwork.getId(), TrafficType.Management);
              if (mgmtTraffic != null) {
                  String label = null;
                  if (hypervisorType.equals(HypervisorType.XenServer)) {
                      label = mgmtTraffic.getXenNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.KVM)) {
                      label = mgmtTraffic.getKvmNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.VMware)) {
                      label = mgmtTraffic.getVmwareNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Hyperv)) {
                      label = mgmtTraffic.getHypervNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Ovm3)) {
                      label = mgmtTraffic.getOvm3NetworkLabel();
                  }
                  return label;
              }
          } catch (Exception ex) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Failed to retrieve the default label for management " +
                          "traffic: zone: {} hypervisor: {} due to: {}",
                          () -> _dcDao.findById(zoneId), hypervisorType::toString, ex::getMessage);
              }
          }
          return null;
      }
  
      @Override
      public String getDefaultStorageTrafficLabel(long zoneId, HypervisorType hypervisorType) {
          try {
              PhysicalNetwork storagePhyNetwork = getDefaultPhysicalNetworkByZoneAndTrafficType(zoneId, TrafficType.Storage);
              PhysicalNetworkTrafficTypeVO storageTraffic = _pNTrafficTypeDao.findBy(storagePhyNetwork.getId(), TrafficType.Storage);
              if (storageTraffic != null) {
                  String label = null;
                  if (hypervisorType.equals(HypervisorType.XenServer)) {
                      label = storageTraffic.getXenNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.KVM)) {
                      label = storageTraffic.getKvmNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.VMware)) {
                      label = storageTraffic.getVmwareNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Hyperv)) {
                      label = storageTraffic.getHypervNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Ovm3)) {
                      label = storageTraffic.getOvm3NetworkLabel();
                  }
                  return label;
              }
          } catch (Exception ex) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Failed to retrieve the default label for storage " +
                          "traffic: zone: {} hypervisor: {} due to: {}",
                          () -> _dcDao.findById(zoneId), hypervisorType::toString, ex::getMessage);
              }
          }
          return null;
      }
  
      @Override
      public List<PhysicalNetworkSetupInfo> getPhysicalNetworkInfo(long dcId, HypervisorType hypervisorType) {
          List<PhysicalNetworkSetupInfo> networkInfoList = new ArrayList<PhysicalNetworkSetupInfo>();
          List<PhysicalNetworkVO> physicalNtwkList = _physicalNetworkDao.listByZone(dcId);
          for (PhysicalNetworkVO pNtwk : physicalNtwkList) {
              String publicName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Public, hypervisorType);
              String privateName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Management, hypervisorType);
              String guestName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Guest, hypervisorType);
              String storageName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Storage, hypervisorType);
              // String controlName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Control, hypervisorType);
              PhysicalNetworkSetupInfo info = new PhysicalNetworkSetupInfo();
              info.setPhysicalNetworkId(pNtwk.getId());
              info.setGuestNetworkName(guestName);
              info.setPrivateNetworkName(privateName);
              info.setPublicNetworkName(publicName);
              info.setStorageNetworkName(storageName);
              PhysicalNetworkTrafficTypeVO mgmtTraffic = _pNTrafficTypeDao.findBy(pNtwk.getId(), TrafficType.Management);
              if (mgmtTraffic != null) {
                  String vlan = mgmtTraffic.getVlan();
                  info.setMgmtVlan(vlan);
              }
              networkInfoList.add(info);
          }
          return networkInfoList;
      }
  
      @Override
      public boolean isProviderEnabledInPhysicalNetwork(long physicalNetowrkId, String providerName) {
          PhysicalNetworkServiceProviderVO ntwkSvcProvider = _pNSPDao.findByServiceProvider(physicalNetowrkId, providerName);
          if (ntwkSvcProvider == null) {
              logger.warn("Unable to find provider {} in physical network {}",
                      providerName::toString, () -> _physicalNetworkDao.findById(physicalNetowrkId));
              return false;
          }
          return isProviderEnabled(ntwkSvcProvider);
      }
  
      @Override
      public boolean isProviderEnabledInZone(long zoneId, String provider) {
          //the provider has to be enabled at least in one network in the zone
          for (PhysicalNetwork pNtwk : _physicalNetworkDao.listByZone(zoneId)) {
              if (isProviderEnabledInPhysicalNetwork(pNtwk.getId(), provider)) {
                  return true;
              }
          }
  
          return false;
      }
  
      @Override
      public String getNetworkTag(HypervisorType hType, Network network) {
          // no network tag for control traffic type
          TrafficType effectiveTrafficType = network.getTrafficType();
          if (hType == HypervisorType.VMware && effectiveTrafficType == TrafficType.Control)
              effectiveTrafficType = TrafficType.Management;
  
          if (effectiveTrafficType == TrafficType.Control) {
              return null;
          }
  
          Long physicalNetworkId = null;
          if (effectiveTrafficType != TrafficType.Guest) {
              physicalNetworkId = getNonGuestNetworkPhysicalNetworkId(network, effectiveTrafficType);
          } else {
              NetworkOffering offering = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId());
              physicalNetworkId = network.getPhysicalNetworkId();
              if (physicalNetworkId == null) {
                  physicalNetworkId = findPhysicalNetworkId(network.getDataCenterId(), offering.getTags(), offering.getTrafficType());
              }
          }
  
          if (physicalNetworkId == null) {
              assert (false) : "Can't get the physical network";
              logger.warn("Can't get the physical network");
              return null;
          }
  
          return _pNTrafficTypeDao.getNetworkTag(physicalNetworkId, effectiveTrafficType, hType);
      }
  
      @Override
      public NetworkVO getExclusiveGuestNetwork(long zoneId) {
          List<NetworkVO> networks = _networksDao.listBy(Account.ACCOUNT_ID_SYSTEM, zoneId, GuestType.Shared, TrafficType.Guest);
          if (networks == null || networks.isEmpty()) {
              throw new InvalidParameterValueException("Unable to find network with trafficType " + TrafficType.Guest + " and guestType " + GuestType.Shared + " in zone " +
                  zoneId);
          }
  
          if (networks.size() > 1) {
              throw new InvalidParameterValueException("Found more than 1 network with trafficType " + TrafficType.Guest + " and guestType " + GuestType.Shared +
                  " in zone " + zoneId);
  
          }
  
          return networks.get(0);
      }
  
      @Override
      public boolean isNetworkSystem(Network network) {
          NetworkOffering no = _networkOfferingDao.findByIdIncludingRemoved(network.getNetworkOfferingId());
          if (no.isSystemOnly()) {
              return true;
          } else {
              return false;
          }
      }
  
      @Override
      public Long getPhysicalNetworkId(Network network) {
          if (network.getTrafficType() != TrafficType.Guest) {
              return getNonGuestNetworkPhysicalNetworkId(network);
          }
  
          Long physicalNetworkId = network.getPhysicalNetworkId();
          NetworkOffering offering = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId());
          if (physicalNetworkId == null) {
              physicalNetworkId = findPhysicalNetworkId(network.getDataCenterId(), offering.getTags(), offering.getTrafficType());
          }
          return physicalNetworkId;
      }
  
      @Override
      public boolean getAllowSubdomainAccessGlobal() {
          return _allowSubdomainNetworkAccess;
      }
  
      @Override
      public boolean isProviderForNetwork(Provider provider, long networkId) {
          if (_ntwkSrvcDao.isProviderForNetwork(networkId, provider) != null) {
              return true;
          } else {
              return false;
          }
      }
  
      @Override
      public boolean isProviderForNetworkOffering(Provider provider, long networkOfferingId) {
          if (_ntwkOfferingSrvcDao.isProviderForNetworkOffering(networkOfferingId, provider)) {
              return true;
          } else {
              return false;
          }
      }
  
      @Override
      public void canProviderSupportServices(Map<Provider, Set<Service>> providersMap) {
          for (Provider provider : providersMap.keySet()) {
              // check if services can be turned off
              NetworkElement element = getElementImplementingProvider(provider.getName());
              if (element == null) {
                  throw new InvalidParameterValueException("Unable to find the Network Element implementing the Service Provider '" + provider.getName() + "'");
              }
  
              Set<Service> enabledServices = new HashSet<Service>();
              enabledServices.addAll(providersMap.get(provider));
  
              if (enabledServices != null && !enabledServices.isEmpty()) {
                  if (!element.canEnableIndividualServices()) {
                      Set<Service> requiredServices = new HashSet<Service>();
                      requiredServices.addAll(element.getCapabilities().keySet());
  
                      if (requiredServices.contains(Network.Service.Gateway)) {
                          requiredServices.remove(Network.Service.Gateway);
                      }
  
                      if (requiredServices.contains(Network.Service.Firewall)) {
                          requiredServices.remove(Network.Service.Firewall);
                      }
  
                      if (enabledServices.contains(Network.Service.Firewall)) {
                          enabledServices.remove(Network.Service.Firewall);
                      }
  
                      // exclude gateway service
                      if (enabledServices.size() != requiredServices.size()) {
                          StringBuilder servicesSet = new StringBuilder();
  
                          for (Service requiredService : requiredServices) {
                              // skip gateway service as we don't allow setting it via API
                              if (requiredService == Service.Gateway) {
                                  continue;
                              }
                              servicesSet.append(requiredService.getName() + ", ");
                          }
                          servicesSet.delete(servicesSet.toString().length() - 2, servicesSet.toString().length());
  
                          throw new InvalidParameterValueException("Cannot enable subset of Services, Please specify the complete list of Services: " +
                              servicesSet.toString() + "  for Service Provider " + provider.getName());
                      }
                  }
                  List<String> serviceList = new ArrayList<String>();
                  for (Service service : enabledServices) {
                      // check if the service is provided by this Provider
                      if (!element.getCapabilities().containsKey(service)) {
                          throw new UnsupportedServiceException(provider.getName() + " Provider cannot provide service " + service.getName());
                      }
                      serviceList.add(service.getName());
                  }
                  if (!element.verifyServicesCombination(enabledServices)) {
                      throw new UnsupportedServiceException("Provider " + provider.getName() + " doesn't support services combination: " + serviceList);
                  }
              }
          }
      }
  
      @Override
      public boolean canAddDefaultSecurityGroup() {
          String defaultAdding = _configDao.getValue(Config.SecurityGroupDefaultAdding.key());
          return (defaultAdding != null && defaultAdding.equalsIgnoreCase("true"));
      }
  
      @Override
      public List<Service> listNetworkOfferingServices(long networkOfferingId) {
          List<Service> services = new ArrayList<Service>();
          List<String> servicesStr = _ntwkOfferingSrvcDao.listServicesForNetworkOffering(networkOfferingId);
          for (String serviceStr : servicesStr) {
              services.add(Service.getService(serviceStr));
          }
  
          return services;
      }
  
      @Override
      public boolean areServicesEnabledInZone(long zoneId, NetworkOffering offering, List<Service> services) {
          long physicalNtwkId = findPhysicalNetworkId(zoneId, offering.getTags(), offering.getTrafficType());
          boolean result = true;
          List<String> checkedProvider = new ArrayList<String>();
          for (Service service : services) {
              // get all the providers, and check if each provider is enabled
              List<String> providerNames = _ntwkOfferingSrvcDao.listProvidersForServiceForNetworkOffering(offering.getId(), service);
              for (String providerName : providerNames) {
                  if (!checkedProvider.contains(providerName)) {
                      result = result && isProviderEnabledInPhysicalNetwork(physicalNtwkId, providerName);
                  }
                  checkedProvider.add(providerName);
              }
          }
  
          return result;
      }
  
      @Override
      public boolean checkIpForService(IpAddress userIp, Service service, Long networkId) {
          if (networkId == null) {
              networkId = userIp.getAssociatedWithNetworkId();
          }
  
          NetworkVO network = _networksDao.findById(networkId);
          if (network == null) {
              throw new CloudRuntimeException("Could not find network associated with public IP.");
          }
  
          NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
          if (offering.getGuestType() != GuestType.Isolated) {
              return true;
          }
          IPAddressVO ipVO = _ipAddressDao.findById(userIp.getId());
          PublicIp publicIp = PublicIp.createFromAddrAndVlan(ipVO, _vlanDao.findById(userIp.getVlanId()));
          if (!canIpUsedForService(publicIp, service, networkId)) {
              return false;
          }
          boolean isForNsx = isProviderForNetworkOffering(Provider.Nsx, offering.getId());
          if (!offering.isConserveMode() && !isForNsx) {
              return canIpUsedForNonConserveService(publicIp, service);
          }
          return true;
      }
  
      @Override
      public boolean providerSupportsCapability(Set<Provider> providers, Service service, Capability cap) {
          for (Provider provider : providers) {
              NetworkElement element = getElementImplementingProvider(provider.getName());
              if (element != null) {
                  Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
                  if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
                      throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
                              " implementing Provider=" + provider.getName());
                  }
                  Map<Capability, String> serviceCapabilities = elementCapabilities.get(service);
                  if (serviceCapabilities == null || serviceCapabilities.isEmpty()) {
                      throw new UnsupportedServiceException("Service " + service.getName() + " doesn't have capabilites for element=" + element.getName() +
                              " implementing Provider=" + provider.getName());
                  }
  
                  if (serviceCapabilities.containsKey(cap)) {
                      return true;
                  }
              } else {
                  throw new UnsupportedServiceException("Unable to find network element for provider " + provider.getName());
              }
          }
          return false;
      }
  
      @Override
      public void checkCapabilityForProvider(Set<Provider> providers, Service service, Capability cap, String capValue) {
          for (Provider provider : providers) {
              NetworkElement element = getElementImplementingProvider(provider.getName());
              if (element != null) {
                  Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
                  if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
                      throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
                          " implementing Provider=" + provider.getName());
                  }
                  Map<Capability, String> serviceCapabilities = elementCapabilities.get(service);
                  if (serviceCapabilities == null || serviceCapabilities.isEmpty()) {
                      throw new UnsupportedServiceException("Service " + service.getName() + " doesn't have capabilities for element=" + element.getName() +
                          " implementing Provider=" + provider.getName());
                  }
  
                  String value = serviceCapabilities.get(cap);
                  if (value == null || value.isEmpty()) {
                      throw new UnsupportedServiceException("Service " + service.getName() + " doesn't have capability " + cap.getName() + " for element=" +
                          element.getName() + " implementing Provider=" + provider.getName());
                  }
  
                  if (!value.toLowerCase().contains(capValue.toLowerCase())) {
                      throw new UnsupportedServiceException("Service " + service.getName() + " doesn't support value " + capValue + " for capability " + cap.getName() +
                          " for element=" + element.getName() + " implementing Provider=" + provider.getName());
                  }
              } else {
                  throw new UnsupportedServiceException("Unable to find network element for provider " + provider.getName());
              }
          }
      }
  
      @Override
      public final void checkNetworkPermissions(Account caller, Network network) {
          if (_accountMgr.isRootAdmin(caller.getAccountId()) && Boolean.TRUE.equals(AdminIsAllowedToDeployAnywhere.value())) {
              if (logger.isDebugEnabled()) {
                  logger.debug("root admin is permitted to do stuff on every network");
              }
          } else {
              if (network == null) {
                  throw new CloudRuntimeException("cannot check permissions on (Network) <null>");
              }
              logger.info("Checking permission for account {} on network {}", caller, network);
              if (network.getGuestType() != GuestType.Shared || network.getAclType() == ACLType.Account) {
                  checkAccountNetworkPermissions(caller, network);
  
              } else {
                  checkDomainNetworkPermissions(caller, network);
              }
          }
      }
  
      private void checkAccountNetworkPermissions(Account caller, Network network) {
          AccountVO networkOwner = _accountDao.findById(network.getAccountId());
          if (networkOwner == null)
              throw new PermissionDeniedException("Unable to use network with id= " + ((NetworkVO) network).getUuid() +
                  ", network does not have an owner");
          if (!Account.Type.PROJECT.equals(caller.getType()) && Account.Type.PROJECT.equals(networkOwner.getType())) {
              checkProjectNetworkPermissions(caller, networkOwner, network);
          } else {
              List<NetworkVO> networkMap = _networksDao.listBy(caller.getId(), network.getId());
              NetworkPermissionVO networkPermission = _networkPermissionDao.findByNetworkAndAccount(network.getId(), caller.getId());
              if (CollectionUtils.isEmpty(networkMap) && networkPermission == null) {
                  throw new PermissionDeniedException(String.format(UNABLE_TO_USE_NETWORK, ((NetworkVO) network).getUuid()));
              }
          }
      }
  
      private void checkDomainNetworkPermissions(Account caller, Network network) {
          if (!isNetworkAvailableInDomain(network.getId(), caller.getDomainId())) {
              DomainVO callerDomain = _domainDao.findById(caller.getDomainId());
              if (callerDomain == null) {
                  throw new CloudRuntimeException("cannot check permission on account " + caller.getAccountName() + " whose domain does not exist");
              }
              throw new PermissionDeniedException("Shared network id=" + ((NetworkVO) network).getUuid() + " is not available in domain id=" +
                      callerDomain.getUuid());
          }
      }
  
      private void checkProjectNetworkPermissions(Account owner, Account networkOwner, Network network){
          User user = CallContext.current().getCallingUser();
          Project project = projectDao.findByProjectAccountId(networkOwner.getId());
          if (project == null) {
              throw new CloudRuntimeException("Unable to find project to which the network belongs to");
          }
          ProjectAccount projectAccountUser = _projectAccountDao.findByProjectIdUserId(project.getId(), user.getAccountId(), user.getId());
          if (projectAccountUser != null) {
              if (!_projectAccountDao.canUserAccessProjectAccount(user.getAccountId(), user.getId(), networkOwner.getId())) {
                  throw new PermissionDeniedException(String.format(UNABLE_TO_USE_NETWORK, ((NetworkVO)network).getUuid()));
              }
          } else {
              if (!_projectAccountDao.canAccessProjectAccount(owner.getAccountId(), networkOwner.getId())) {
                  throw new PermissionDeniedException(String.format(UNABLE_TO_USE_NETWORK, ((NetworkVO) network).getUuid()));
              }
          }
      }
  
      @Override
      public void checkNetworkOperatePermissions(Account owner, Network network) {
          if (network == null) {
              throw new CloudRuntimeException("cannot check permissions on (Network) <null>");
          }
          if (owner.getType() == Account.Type.ADMIN) {
              return;
          }
          if (network.getGuestType() == GuestType.Shared) {
              checkSharedNetworkOperatePermissions(owner, network);
          } else {
              checkNonSharedNetworkOperatePermissions(owner, network);
          }
      }
  
      @Override
      public void checkRouterPermissions(Account owner, VirtualRouter router) {
          Account account = _accountMgr.getAccount(router.getAccountId());
          try {
              _accountMgr.checkAccess(owner, null, true, account);
              return;
          } catch (PermissionDeniedException ex) {
              logger.info("Account " + owner + " do not have permission on router owner " + account);
          }
          List<NicVO> routerNics = _nicDao.listByVmId(router.getId());
          for (final Nic routerNic : routerNics) {
              final NetworkVO network = _networksDao.findById(routerNic.getNetworkId());
              if (TrafficType.Guest.equals(network.getTrafficType())) {
                  checkNetworkOperatePermissions(owner, network);
              }
          }
      }
  
      private void checkNonSharedNetworkOperatePermissions(Account owner, Network network) {
          // check on isolated/L2 networks
          Account networkOwner = _accountDao.findByIdIncludingRemoved(network.getAccountId());
          if (owner.getType() == Account.Type.DOMAIN_ADMIN) {
              if (!_domainDao.isChildDomain(owner.getDomainId(), networkOwner.getDomainId())) {
                  throw new PermissionDeniedException(String.format("network %s cannot be operated by domain admin %s", network, owner));
              }
          } else if (owner.getType() == Account.Type.NORMAL) {
              if (owner.getType() != Account.Type.PROJECT && networkOwner.getType() == Account.Type.PROJECT) {
                  checkProjectNetworkPermissions(owner, networkOwner, network);
              } else if (networkOwner.getAccountId() != owner.getAccountId()) {
                  throw new PermissionDeniedException(String.format("network %s cannot be operated by normal user %s", network, owner));
              }
          } else {
              throw new PermissionDeniedException(String.format("network %s cannot be operated by this account %s", network, owner));
          }
      }
  
      private void checkSharedNetworkOperatePermissions(Account owner, Network network) {
          NetworkOffering networkOffering = _networkOfferingDao.findById(network.getNetworkOfferingId());
          if (networkOffering.isSpecifyVlan() && owner.getType() != Account.Type.ADMIN) {
              throw new PermissionDeniedException(String.format("Shared network %s with specifyvlan=true can only be operated by root admin", network));
          }
          if (owner.getType() == Account.Type.DOMAIN_ADMIN) {
              if (network.getAclType() == ACLType.Domain) {
                  // Allow domain admins to operate shared network for their domain.
                  Long networkDomainId = getDomainIdForSharedNetwork(network);
                  if (!_domainDao.isChildDomain(owner.getDomainId(), networkDomainId)) {
                      throw new PermissionDeniedException(String.format("Shared network %s belongs to another domain cannot be operated by domain admin %s", network, owner));
                  }
              } else if (network.getAclType() == ACLType.Account) {
                  // Allow domain admins to operate shared network for an account in their domain.
                  Long networkAccountId = getAccountIdForSharedNetwork(network);
                  if (!_domainDao.isChildDomain(owner.getDomainId(), _accountDao.findByIdIncludingRemoved(networkAccountId).getDomainId())) {
                      throw new PermissionDeniedException(String.format("Shared network %s belongs to an account in another domain cannot be operated by domain admin %s", network, owner));
                  }
              }
          } else if (owner.getType() == Account.Type.NORMAL) {
              // Allow normal users to operate shared network for themselves.
              if (network.getAclType() == ACLType.Account) {
                  // Allow domain admin to operate shared network for an account in its domain.
                  Long networkAccountId = getAccountIdForSharedNetwork(network);
                  Account networkOwner = _accountDao.findByIdIncludingRemoved(networkAccountId);
                  if (owner.getType() != Account.Type.PROJECT && networkOwner.getType() == Account.Type.PROJECT) {
                      checkProjectNetworkPermissions(owner, networkOwner, network);
                  } else if (networkOwner.getAccountId() != owner.getAccountId()) {
                      throw new PermissionDeniedException(String.format("Shared network %s belongs to another account cannot be operated by normal user %s", network, owner));
                  }
              } else {
                  throw new PermissionDeniedException(String.format("Shared network %s belongs to domain cannot be operated by normal user %s", network, owner));
              }
          } else if (owner.getType() != Account.Type.ADMIN) {
              throw new PermissionDeniedException(String.format("Shared network %s cannot be operated by account %s with type = %s", network, owner, owner.getType()));
          }
      }
  
      private Long getAccountIdForSharedNetwork(Network network) {
          NetworkAccountVO networkAccountMap = _networkAccountDao.getAccountNetworkMapByNetworkId(network.getId());
          if (networkAccountMap == null) {
              throw new CloudRuntimeException(String.format("Cannot find account info for Shared network %s with aclType=Account", network));
          }
          return networkAccountMap.getAccountId();
      }
  
      private Long getDomainIdForSharedNetwork(Network network) {
          NetworkDomainVO networkDomainMap = _networkDomainDao.getDomainNetworkMapByNetworkId(network.getId());
          if (networkDomainMap == null) {
              throw new CloudRuntimeException(String.format("Cannot find domain info for Shared network %s with aclType=Domain", network));
          }
          return networkDomainMap.getDomainId();
      }
  
      @Override
      public String getDefaultPublicTrafficLabel(long dcId, HypervisorType hypervisorType) {
          try {
              PhysicalNetwork publicPhyNetwork = getOnePhysicalNetworkByZoneAndTrafficType(dcId, TrafficType.Public);
              PhysicalNetworkTrafficTypeVO publicTraffic = _pNTrafficTypeDao.findBy(publicPhyNetwork.getId(), TrafficType.Public);
              if (publicTraffic != null) {
                  String label = null;
                  if (hypervisorType.equals(HypervisorType.XenServer)) {
                      label = publicTraffic.getXenNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.KVM)) {
                      label = publicTraffic.getKvmNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.VMware)) {
                      label = publicTraffic.getVmwareNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Hyperv)) {
                      label = publicTraffic.getHypervNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Ovm3)) {
                      label = publicTraffic.getOvm3NetworkLabel();
                  }
                  return label;
              }
          } catch (Exception ex) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Failed to retrieve the default label for public " +
                          "traffic. zone: {} hypervisor: {} due to: {}",
                          () -> _dcDao.findById(dcId), hypervisorType::toString, ex::getMessage);
              }
          }
          return null;
      }
  
      @Override
      public String getDefaultGuestTrafficLabel(long dcId, HypervisorType hypervisorType) {
          try {
              PhysicalNetwork guestPhyNetwork = getOnePhysicalNetworkByZoneAndTrafficType(dcId, TrafficType.Guest);
              PhysicalNetworkTrafficTypeVO guestTraffic = _pNTrafficTypeDao.findBy(guestPhyNetwork.getId(), TrafficType.Guest);
              if (guestTraffic != null) {
                  String label = null;
                  if (hypervisorType.equals(HypervisorType.XenServer)) {
                      label = guestTraffic.getXenNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.KVM)) {
                      label = guestTraffic.getKvmNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.VMware)) {
                      label = guestTraffic.getVmwareNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Hyperv)) {
                      label = guestTraffic.getHypervNetworkLabel();
                  } else if (hypervisorType.equals(HypervisorType.Ovm3)) {
                      label = guestTraffic.getOvm3NetworkLabel();
                  }
                  return label;
              }
          } catch (Exception ex) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Failed to retrieve the default label for guest " +
                          "traffic: zone: {} hypervisor: {} due to:{}",
                          () -> _dcDao.findById(dcId), hypervisorType::toString, ex::getMessage);
              }
          }
          return null;
      }
  
      @Override
      public List<? extends Network> listNetworksByVpc(long vpcId) {
          return _networksDao.listByVpc(vpcId);
      }
  
      @Override
      public List<Provider> getNtwkOffDistinctProviders(long ntkwOffId) {
          List<String> providerNames = _ntwkOfferingSrvcDao.getDistinctProviders(ntkwOffId);
          List<Provider> providers = new ArrayList<Provider>();
          for (String providerName : providerNames) {
              providers.add(Network.Provider.getProvider(providerName));
          }
  
          return providers;
      }
  
      @Override
      public boolean isVmPartOfNetwork(long vmId, long ntwkId) {
          if (_nicDao.findNonReleasedByInstanceIdAndNetworkId(ntwkId, vmId) != null) {
              return true;
          }
          return false;
      }
  
      @Override
      public List<? extends PhysicalNetwork> getPhysicalNtwksSupportingTrafficType(long zoneId, TrafficType trafficType) {
  
          List<? extends PhysicalNetwork> pNtwks = _physicalNetworkDao.listByZone(zoneId);
  
          Iterator<? extends PhysicalNetwork> it = pNtwks.iterator();
          while (it.hasNext()) {
              PhysicalNetwork pNtwk = it.next();
              if (!_pNTrafficTypeDao.isTrafficTypeSupported(pNtwk.getId(), trafficType)) {
                  it.remove();
              }
          }
          return pNtwks;
      }
  
      @Override
      public boolean isPrivateGateway(long ntwkId) {
          final VpcGatewayVO gateway = _vpcGatewayDao.getVpcGatewayByNetworkId(ntwkId);
          if (gateway == null) {
              return false;
          }
          return true;
      }
  
      @Override
      public List<NetworkOfferingVO> getSystemAccountNetworkOfferings(String... offeringNames) {
          List<NetworkOfferingVO> offerings = new ArrayList<NetworkOfferingVO>(offeringNames.length);
          for (String offeringName : offeringNames) {
              NetworkOfferingVO network = _systemNetworks.get(offeringName);
              if (network == null) {
                  throw new CloudRuntimeException("Unable to find system network profile for " + offeringName);
              }
              offerings.add(network);
          }
          return offerings;
      }
  
      @Override
      public boolean isNetworkAvailableInDomain(long networkId, long domainId) {
          Long networkDomainId = null;
          Network network = getNetwork(networkId);
          if (network.getGuestType() != GuestType.Shared) {
              logger.trace("Network {} is not shared", network);
              return false;
          }
  
          NetworkDomainVO networkDomainMap = _networkDomainDao.getDomainNetworkMapByNetworkId(networkId);
          if (networkDomainMap == null) {
              logger.trace("Network {} is shared, but not domain specific", network);
              return true;
          } else {
              networkDomainId = networkDomainMap.getDomainId();
          }
  
          if (domainId == networkDomainId.longValue()) {
              return true;
          }
  
          if (networkDomainMap.subdomainAccess) {
              Set<Long> parentDomains = _domainMgr.getDomainParentIds(domainId);
  
              if (parentDomains.contains(networkDomainId)) {
                  return true;
              }
          }
  
          return false;
      }
  
      @Override
      public Set<Long> getAvailableIps(Network network, String requestedIp) {
          if (network.getCidr() == null) {
              return Collections.emptySet();
          }
          String[] cidr = network.getCidr().split("/");
          List<String> ips = getUsedIpsInNetwork(network);
          Set<Long> usedIps = new TreeSet<Long>();
  
          for (String ip : ips) {
              if (requestedIp != null && requestedIp.equals(ip)) {
                  logger.warn("Requested ip address " + requestedIp + " is already in use in network" + network);
                  return null;
              }
  
              usedIps.add(NetUtils.ip2Long(ip));
          }
  
          Set<Long> allPossibleIps = NetUtils.getAllIpsFromCidr(cidr[0], Integer.parseInt(cidr[1]), usedIps, -1);
  
          String gateway = network.getGateway();
          if ((gateway != null) && (allPossibleIps.contains(NetUtils.ip2Long(gateway))))
              allPossibleIps.remove(NetUtils.ip2Long(gateway));
  
          return allPossibleIps;
      }
  
      @Override
      public List<String> getUsedIpsInNetwork(Network network) {
          //Get all ips used by vms nics
          List<String> ips = _nicDao.listIpAddressInNetwork(network.getId());
          //Get all secondary ips for nics
          List<String> secondaryIps = _nicSecondaryIpDao.listSecondaryIpAddressInNetwork(network.getId());
          ips.addAll(secondaryIps);
          //Get ips used by load balancers
          List<String> lbIps = _appLbRuleDao.listLbIpsBySourceIpNetworkId(network.getId());
          ips.addAll(lbIps);
          //Get ips used by tungsten
          List<String> tfIps = tungstenGuestNetworkIpAddressDao.listGuestIpAddressByNetworkId(network.getId());
          ips.addAll(tfIps);
          return ips;
      }
  
      @Override
      public String getDomainNetworkDomain(long domainId, long zoneId) {
          String networkDomain = null;
          Long searchDomainId = domainId;
          while (searchDomainId != null) {
              DomainVO domain = _domainDao.findById(searchDomainId);
              if (domain.getNetworkDomain() != null) {
                  networkDomain = domain.getNetworkDomain();
                  break;
              }
              searchDomainId = domain.getParent();
          }
          if (networkDomain == null) {
              return getZoneNetworkDomain(zoneId);
          }
          return networkDomain;
      }
  
      boolean isProviderEnabled(PhysicalNetworkServiceProvider provider) {
          if (provider == null || provider.getState() != PhysicalNetworkServiceProvider.State.Enabled) { // TODO: check
              // for other states: Shutdown?
              return false;
          }
          return true;
      }
  
      boolean isServiceEnabledInNetwork(long physicalNetworkId, Network network, Service service) {
          // check if the service is supported in the network
          if (!areServicesSupportedInNetwork(network.getId(), service)) {
              logger.debug("Service {} is not supported in the network {}", service.getName(), network);
              return false;
          }
  
          // get provider for the service and check if all of them are supported
          String provider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), service);
          if (!isProviderEnabledInPhysicalNetwork(physicalNetworkId, provider)) {
              logger.debug("Provider {} is not enabled in physical network {}",
                      provider::toString, () -> _physicalNetworkDao.findById(physicalNetworkId));
              return false;
          }
  
          return true;
      }
  
      @Override
      public Nic getNicInNetworkIncludingRemoved(long vmId, long networkId) {
          return _nicDao.findByInstanceIdAndNetworkIdIncludingRemoved(networkId, vmId);
      }
  
      String getZoneNetworkDomain(long zoneId) {
          return _dcDao.findById(zoneId).getDomain();
      }
  
      PhysicalNetwork getOnePhysicalNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) {
          List<PhysicalNetworkVO> networkList = _physicalNetworkDao.listByZoneAndTrafficType(zoneId, trafficType);
  
          if (networkList.isEmpty()) {
              throw new InvalidParameterValueException("Unable to find the default physical network with traffic=" + trafficType + " in zone id=" + zoneId + ". ");
          }
  
          if (networkList.size() > 1) {
              logger.info("More than one physical networks exist in zone {} with traffic type {}",
                      () -> _dcDao.findById(zoneId), trafficType::toString);
          }
  
          return networkList.get(0);
      }
  
      protected Long getNonGuestNetworkPhysicalNetworkId(Network network, TrafficType trafficType) {
          // VMware control network is management network
          // we need to retrieve traffic label information through physical network
          Long physicalNetworkId = network.getPhysicalNetworkId();
  
          if (physicalNetworkId == null) {
              List<PhysicalNetworkVO> pNtwks = _physicalNetworkDao.listByZone(network.getDataCenterId());
              if (pNtwks.size() == 1) {
                  physicalNetworkId = pNtwks.get(0).getId();
              } else {
                  // locate physicalNetwork with supported traffic type
                  // We can make this assumptions based on the fact that Public/Management/Control traffic types are
                  // supported only in one physical network in the zone in 3.0
                  for (PhysicalNetworkVO pNtwk : pNtwks) {
                      if (_pNTrafficTypeDao.isTrafficTypeSupported(pNtwk.getId(), trafficType)) {
                          physicalNetworkId = pNtwk.getId();
                          break;
                      }
                  }
              }
          }
          return physicalNetworkId;
      }
  
      protected Long getNonGuestNetworkPhysicalNetworkId(Network network) {
          // no physical network for control traffic type
  
          // have to remove this sanity check as VMware control network is management network
          // we need to retrieve traffic label information through physical network
          /*
                  if (network.getTrafficType() == TrafficType.Control) {
                      return null;
                  }
          */
          Long physicalNetworkId = network.getPhysicalNetworkId();
  
          if (physicalNetworkId == null) {
              List<PhysicalNetworkVO> pNtwks = _physicalNetworkDao.listByZone(network.getDataCenterId());
              if (pNtwks.size() == 1) {
                  physicalNetworkId = pNtwks.get(0).getId();
              } else {
                  // locate physicalNetwork with supported traffic type
                  // We can make this assumptions based on the fact that Public/Management/Control traffic types are
                  // supported only in one physical network in the zone in 3.0
                  for (PhysicalNetworkVO pNtwk : pNtwks) {
                      if (_pNTrafficTypeDao.isTrafficTypeSupported(pNtwk.getId(), network.getTrafficType())) {
                          physicalNetworkId = pNtwk.getId();
                          break;
                      }
                  }
              }
          }
          return physicalNetworkId;
      }
  
      @Override
      public NicProfile getNicProfile(VirtualMachine vm, long networkId, String broadcastUri) {
          NicVO nic = null;
          if (broadcastUri != null) {
              nic = _nicDao.findByNetworkIdInstanceIdAndBroadcastUri(networkId, vm.getId(), broadcastUri);
          } else {
              nic = _nicDao.findByNtwkIdAndInstanceId(networkId, vm.getId());
          }
          if (nic == null) {
              return null;
          }
          DataCenter dc = _dcDao.findById(vm.getDataCenterId());
          return getNicProfile(vm, nic, dc);
      }
  
      @Override
      public NicProfile getNicProfile(VirtualMachine vm, Nic nic, DataCenter dataCenter) {
          NetworkVO network = _networksDao.findById(nic.getNetworkId());
          Integer networkRate = getNetworkRate(network.getId(), vm.getId());
          NicProfile profile =
                  new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate,
                          isSecurityGroupSupportedInNetwork(network), getNetworkTag(vm.getHypervisorType(), network));
          if (network.getTrafficType() == TrafficType.Public && network.getPublicMtu() != null) {
              profile.setMtu(network.getPublicMtu());
          }
          if (network.getTrafficType() == TrafficType.Guest && network.getPrivateMtu() != null) {
              profile.setMtu(network.getPrivateMtu());
          }
          Pair<String, String> ip4Dns = getNetworkIp4Dns(network, dataCenter);
          profile.setIPv4Dns1(ip4Dns.first());
          profile.setIPv4Dns2(ip4Dns.second());
          Pair<String, String> ip6Dns = getNetworkIp6Dns(network, dataCenter);
          profile.setIPv6Dns1(ip6Dns.first());
          profile.setIPv6Dns2(ip6Dns.second());
          return profile;
      }
  
      @Override
      public boolean networkIsConfiguredForExternalNetworking(long zoneId, long networkId) {
          List<Provider> networkProviders = getNetworkProviders(networkId);
          for (Provider provider : networkProviders) {
              if (provider.isExternal()) {
                  return true;
              }
          }
          return false;
      }
  
      private List<Provider> getNetworkProviders(long networkId) {
          List<String> providerNames = _ntwkSrvcDao.getDistinctProviders(networkId);
          Map<String, Provider> providers = new HashMap<String, Provider>();
          for (String providerName : providerNames) {
              if (!providers.containsKey(providerName)) {
                  providers.put(providerName, Network.Provider.getProvider(providerName));
              }
          }
  
          return new ArrayList<Provider>(providers.values());
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          _configs = _configDao.getConfiguration("Network", params);
          _allowSubdomainNetworkAccess = Boolean.valueOf(_configs.get(Config.SubDomainNetworkAccess.key()));
          _executeInSequenceNtwkElmtCmd = Boolean.valueOf(_configs.get(Config.ExecuteInSequenceNetworkElementCommands.key()));
  
          NetworkOfferingVO publicNetworkOffering = new NetworkOfferingVO(NetworkOffering.SystemPublicNetwork, TrafficType.Public, true);
          publicNetworkOffering = _networkOfferingDao.persistDefaultNetworkOffering(publicNetworkOffering);
          _systemNetworks.put(NetworkOffering.SystemPublicNetwork, publicNetworkOffering);
          NetworkOfferingVO managementNetworkOffering = new NetworkOfferingVO(NetworkOffering.SystemManagementNetwork, TrafficType.Management, false);
          managementNetworkOffering = _networkOfferingDao.persistDefaultNetworkOffering(managementNetworkOffering);
          _systemNetworks.put(NetworkOffering.SystemManagementNetwork, managementNetworkOffering);
          NetworkOfferingVO controlNetworkOffering = new NetworkOfferingVO(NetworkOffering.SystemControlNetwork, TrafficType.Control, false);
          controlNetworkOffering = _networkOfferingDao.persistDefaultNetworkOffering(controlNetworkOffering);
          _systemNetworks.put(NetworkOffering.SystemControlNetwork, controlNetworkOffering);
          NetworkOfferingVO storageNetworkOffering = new NetworkOfferingVO(NetworkOffering.SystemStorageNetwork, TrafficType.Storage, true);
          storageNetworkOffering = _networkOfferingDao.persistDefaultNetworkOffering(storageNetworkOffering);
          _systemNetworks.put(NetworkOffering.SystemStorageNetwork, storageNetworkOffering);
          NetworkOfferingVO privateGatewayNetworkOffering = new NetworkOfferingVO(NetworkOffering.SystemPrivateGatewayNetworkOffering, GuestType.Isolated, true);
          privateGatewayNetworkOffering = _networkOfferingDao.persistDefaultNetworkOffering(privateGatewayNetworkOffering);
          _systemNetworks.put(NetworkOffering.SystemPrivateGatewayNetworkOffering, privateGatewayNetworkOffering);
          NetworkOfferingVO privateGatewayNetworkOfferingWithoutVlan = new NetworkOfferingVO(NetworkOffering.SystemPrivateGatewayNetworkOfferingWithoutVlan, GuestType.Isolated, false);
          privateGatewayNetworkOfferingWithoutVlan = _networkOfferingDao.persistDefaultNetworkOffering(privateGatewayNetworkOfferingWithoutVlan);
          _systemNetworks.put(NetworkOffering.SystemPrivateGatewayNetworkOfferingWithoutVlan, privateGatewayNetworkOfferingWithoutVlan);
  
          IpAddressSearch = _ipAddressDao.createSearchBuilder();
          IpAddressSearch.and("accountId", IpAddressSearch.entity().getAllocatedToAccountId(), Op.EQ);
          IpAddressSearch.and("dataCenterId", IpAddressSearch.entity().getDataCenterId(), Op.EQ);
          IpAddressSearch.and("vpcId", IpAddressSearch.entity().getVpcId(), Op.EQ);
          IpAddressSearch.and("associatedWithNetworkId", IpAddressSearch.entity().getAssociatedWithNetworkId(), Op.EQ);
          SearchBuilder<VlanVO> virtualNetworkVlanSB = _vlanDao.createSearchBuilder();
          virtualNetworkVlanSB.and("vlanType", virtualNetworkVlanSB.entity().getVlanType(), Op.EQ);
          IpAddressSearch.join("virtualNetworkVlanSB", virtualNetworkVlanSB, IpAddressSearch.entity().getVlanId(), virtualNetworkVlanSB.entity().getId(),
              JoinBuilder.JoinType.INNER);
          IpAddressSearch.done();
  
          NicForTrafficTypeSearch = _nicDao.createSearchBuilder();
          SearchBuilder<NetworkVO> networkSearch = _networksDao.createSearchBuilder();
          NicForTrafficTypeSearch.join("network", networkSearch, networkSearch.entity().getId(), NicForTrafficTypeSearch.entity().getNetworkId(), JoinType.INNER);
          NicForTrafficTypeSearch.and("instance", NicForTrafficTypeSearch.entity().getInstanceId(), Op.EQ);
          networkSearch.and("traffictype", networkSearch.entity().getTrafficType(), Op.EQ);
          NicForTrafficTypeSearch.done();
  
          logger.info("Network Model is configured.");
  
          return true;
      }
  
      @Override
      public boolean start() {
          // populate s_serviceToImplementedProvidersMap & s_providerToNetworkElementMap with current _networkElements
          // Need to do this in start() since _networkElements are not completely configured until then.
          for (NetworkElement element : networkElements) {
              Map<Service, Map<Capability, String>> capabilities = element.getCapabilities();
              Provider implementedProvider = element.getProvider();
              if (implementedProvider != null) {
                  if (s_providerToNetworkElementMap.containsKey(implementedProvider.getName())) {
                      logger.error("Cannot start NetworkModel: Provider <-> NetworkElement must be a one-to-one map, " + "multiple NetworkElements found for Provider: " +
                          implementedProvider.getName());
                      continue;
                  }
                  logger.info("Add provider <-> element map entry. " + implementedProvider.getName() + "-" + element.getName() + "-" + element.getClass().getSimpleName());
                  s_providerToNetworkElementMap.put(implementedProvider.getName(), element.getName());
              }
              if (capabilities != null && implementedProvider != null) {
                  for (Service service : capabilities.keySet()) {
                      logger.info("Add provider {} and service {}", implementedProvider.getName(), service.getName());
                      if (s_serviceToImplementedProvidersMap.containsKey(service)) {
                          List<Provider> providers = s_serviceToImplementedProvidersMap.get(service);
                          providers.add(implementedProvider);
                      } else {
                          List<Provider> providers = new ArrayList<Provider>();
                          providers.add(implementedProvider);
                          s_serviceToImplementedProvidersMap.put(service, providers);
                      }
                  }
              }
          }
  
          //After network elements are configured correctly, verify ConfigDrive entries on enabled zones
          verifyDisabledConfigDriveEntriesOnEnabledZones();
  
          logger.info("Started Network Model");
          return true;
      }
  
      /**
       * Verifies ConfigDrive entries on a zone and adds disabled ConfigDrive provider if missing.
       */
      protected void addDisabledConfigDriveEntriesOnZone(DataCenterVO zone) {
          if (zone.getNetworkType() == DataCenter.NetworkType.Advanced) {
              List<PhysicalNetworkVO> physicalNetworks = _physicalNetworkDao.listByZoneAndTrafficType(
                      zone.getId(), TrafficType.Guest);
              for (PhysicalNetworkVO physicalNetworkVO : physicalNetworks) {
                  PhysicalNetworkServiceProviderVO provider = _pNSPDao.findByServiceProvider(
                          physicalNetworkVO.getId(), Provider.ConfigDrive.getName());
                  if (provider == null) {
                      _networkService.addProviderToPhysicalNetwork(
                              physicalNetworkVO.getId(), Provider.ConfigDrive.getName(), null, null);
                  }
              }
          }
      }
  
      /**
       * Verifies ConfigDrive entries on enabled zones, adds disabled ConfigDrive provider if missing.
       */
      protected void verifyDisabledConfigDriveEntriesOnEnabledZones() {
          List<DataCenterVO> zones = _dcDao.listEnabledZones();
          if (CollectionUtils.isNotEmpty(zones)) {
              for (DataCenterVO zone : zones) {
                  addDisabledConfigDriveEntriesOnZone(zone);
              }
          }
      }
  
      @Override
      public boolean stop() {
          return true;
      }
  
      @Override
      public PublicIpAddress getSourceNatIpAddressForGuestNetwork(Account owner, Network guestNetwork) {
          List<? extends IpAddress> addrs = listPublicIpsAssignedToGuestNtwk(owner.getId(), guestNetwork.getId(), true);
  
          IPAddressVO sourceNatIp = null;
          if (addrs.isEmpty()) {
              return null;
          } else {
              for (IpAddress addr : addrs) {
                  if (addr.isSourceNat()) {
                      sourceNatIp = _ipAddressDao.findById(addr.getId());
                      return PublicIp.createFromAddrAndVlan(sourceNatIp, _vlanDao.findById(sourceNatIp.getVlanId()));
                  }
              }
          }
  
          return null;
      }
  
      @Override
      public boolean isNetworkInlineMode(Network network) {
          NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
          return offering.isInline();
      }
  
      @Override
      public void checkIp6Parameters(String startIPv6, String endIPv6, String ip6Gateway, String ip6Cidr) throws InvalidParameterValueException {
  
          if (StringUtils.isAnyBlank(ip6Gateway, ip6Cidr)) {
              throw new InvalidParameterValueException("ip6Gateway and ip6Cidr should be defined for an IPv6 network work properly");
          }
  
          if (!NetUtils.isValidIp6(ip6Gateway)) {
              throw new InvalidParameterValueException("Invalid ip6Gateway");
          }
          if (!NetUtils.isValidIp6Cidr(ip6Cidr)) {
              throw new InvalidParameterValueException("Invalid ip6cidr");
          }
  
          if (!NetUtils.isIp6InNetwork(ip6Gateway, ip6Cidr)) {
              throw new InvalidParameterValueException("ip6Gateway is not in ip6cidr indicated network!");
          }
  
          if (StringUtils.isNotBlank(startIPv6)) {
              if (!NetUtils.isValidIp6(startIPv6)) {
                  throw new InvalidParameterValueException("Invalid format for the startIPv6 parameter");
              }
              if (!NetUtils.isIp6InNetwork(startIPv6, ip6Cidr)) {
                  throw new InvalidParameterValueException("startIPv6 is not in ip6cidr indicated network!");
              }
          }
  
          if (StringUtils.isNotBlank(endIPv6)) {
              if (!NetUtils.isValidIp6(endIPv6)) {
                  throw new InvalidParameterValueException("Invalid format for the endIPv6 parameter");
              }
              if (!NetUtils.isIp6InNetwork(endIPv6, ip6Cidr)) {
                  throw new InvalidParameterValueException("endIPv6 is not in ip6cidr indicated network!");
              }
          }
      }
  
      public void checkIp6CidrSizeEqualTo64(String ip6Cidr) {
          int cidrSize = NetUtils.getIp6CidrSize(ip6Cidr);
          // we only support cidr == 64
          if (cidrSize != 64) {
              throw new InvalidParameterValueException("The cidr size of IPv6 network must be 64 bits!");
          }
      }
  
      @Override
      public void checkRequestedIpAddresses(long networkId, IpAddresses ips) throws InvalidParameterValueException {
          String ip4 = ips.getIp4Address();
          String ip6 = ips.getIp6Address();
          String mac = ips.getMacAddress();
          if (ip4 != null) {
              if (!NetUtils.isValidIp4(ip4)) {
                  throw new InvalidParameterValueException("Invalid specified IPv4 address " + ip4);
              }
              //Other checks for ipv4 are done in assignPublicIpAddress()
          }
          if (ip6 != null) {
              if (!NetUtils.isValidIp6(ip6)) {
                  throw new InvalidParameterValueException("Invalid specified IPv6 address " + ip6);
              }
              if (_ipv6Dao.findByNetworkIdAndIp(networkId, ip6) != null) {
                  throw new InvalidParameterValueException("The requested IP is already taken!");
              }
              List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(networkId);
              if (vlans == null) {
                  throw new CloudRuntimeException("Cannot find related vlan attached to network " + networkId);
              }
              Vlan ipVlan = null;
              for (Vlan vlan : vlans) {
                  if (NetUtils.isIp6InRange(ip6, vlan.getIp6Range())) {
                      ipVlan = vlan;
                      break;
                  }
              }
              if (ipVlan == null) {
                  throw new InvalidParameterValueException("Requested IPv6 is not in the predefined range!");
              }
          }
          if (mac != null) {
              if(!NetUtils.isValidMac(mac)) {
                  throw new InvalidParameterValueException("Invalid specified MAC address " + mac);
              }
              if (_nicDao.findByNetworkIdAndMacAddress(networkId, mac) != null) {
                  throw new InvalidParameterValueException("The requested Mac address is already taken! " + mac);
              }
  
          }
      }
  
      @Override
      public String getStartIpv6Address(long networkId) {
          List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(networkId);
          if (vlans == null) {
              return null;
          }
          String startIpv6 = null;
          // Get the start ip of first create vlan(not the lowest, because if you add a lower vlan, lowest vlan would change)
          for (Vlan vlan : vlans) {
              if (vlan.getIp6Range() != null) {
                  startIpv6 = vlan.getIp6Range().split("-")[0];
                  break;
              }
          }
          return startIpv6;
      }
  
      @Override
      public NicVO getPlaceholderNicForRouter(Network network, Long podId) {
          List<NicVO> nics = _nicDao.listPlaceholderNicsByNetworkIdAndVmType(network.getId(), VirtualMachine.Type.DomainRouter);
          List<? extends Vlan> vlans = new ArrayList<VlanVO>();
          if (podId != null) {
              vlans = _vlanDao.listVlansForPod(podId);
          }
          for (NicVO nic : nics) {
              if (nic.getReserver() == null && (nic.getIPv4Address() != null || nic.getIPv6Address() != null)) {
                  if (podId == null) {
                      return nic;
                  } else {
                      IpAddress ip = null;
                      UserIpv6AddressVO ipv6 = null;
  
                      if (nic.getIPv4Address() != null) {
                          ip = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), nic.getIPv4Address());
                      } else {
                          ipv6 = _ipv6Dao.findByNetworkIdAndIp(network.getId(), nic.getIPv6Address());
                      }
  
                      if (vlans.isEmpty()) {
                          return nic;
                      }
                      //return nic only when its ip address belong to the pod range (for the Basic zone case)
                      for (Vlan vlan : vlans) {
                          if (ip != null && ip.getVlanId() == vlan.getId()) {
                              return nic;
                          } else if (ipv6 != null && ipv6.getVlanId() == vlan.getId()) {
                              return nic;
                          }
                      }
                  }
              }
          }
          return null;
      }
  
      @Override
      public IpAddress getPublicIpAddress(String ipAddress, long zoneId) {
          List<? extends Network> networks = _networksDao.listByZoneAndTrafficType(zoneId, TrafficType.Public);
          if (networks.isEmpty() || networks.size() > 1) {
              throw new CloudRuntimeException("Can't find public network in the zone specified");
          }
  
          return _ipAddressDao.findByIpAndSourceNetworkId(networks.get(0).getId(), ipAddress);
      }
  
      @Override
      public Map<Detail, String> getNtwkOffDetails(long offId) {
          return _ntwkOffDetailsDao.getNtwkOffDetails(offId);
      }
  
      @Override
      public Networks.IsolationType[] listNetworkIsolationMethods() {
          return Networks.IsolationType.values();
      }
  
      @Override
      public boolean getExecuteInSeqNtwkElmtCmd() {
          return _executeInSequenceNtwkElmtCmd;
      }
  
      @Override
      public boolean isNetworkReadyForGc(long networkId) {
          Network network = getNetwork(networkId);
          List<Long> networkIds = _networksDao.findNetworksToGarbageCollect();
          List<String> secondaryIps = _nicSecondaryIpDao.listSecondaryIpAddressInNetwork(networkId);
          if (!networkIds.contains(networkId)) {
              return false;
          }
  
          // add an exception for networks that use external networking devices and has secondary guest IP's allocated.
          // On network GC, when network goes through implement phase a new vlan is allocated, based on the acquired VLAN
          // id cidr of the network is decided in case of external networking case. While NIC uses reservation strategy 'Start'
          // which ensures that new primary ip is allocated for the NiC from the new CIDR. Secondary IP's have hardcoded IP's in
          // network rules. So prevent network GC.
          if (secondaryIps != null && !secondaryIps.isEmpty() && networkIsConfiguredForExternalNetworking(network.getDataCenterId(), networkId)) {
              return false;
          }
  
          // if the network has user vms in Starting/Stopping/Migrating/Running state, or VRs in Starting/Stopping/Migrating state, don't GC
          // The active nics count (nics_count in op_networks table) might be wrong due to some reasons, should check the state of vms as well.
          // (nics for Starting VMs might not be allocated yet as Starting state also used when vm is being Created)
          if (_nicDao.countNicsForNonStoppedVms(networkId) > 0 || _nicDao.countNicsForNonStoppedRunningVrs(networkId) > 0) {
              logger.debug("Network {} is not ready for GC as it has vms that are not Stopped at the moment", network);
              return false;
          }
  
          return true;
      }
  
      @Override
      public boolean getNetworkEgressDefaultPolicy(Long networkId) {
          NetworkVO network = _networksDao.findById(networkId);
  
          if (network != null) {
              NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
              return offering.isEgressDefaultPolicy();
          } else {
              InvalidParameterValueException ex = new InvalidParameterValueException("network with network id does not exist");
              throw ex;
          }
      }
  
      @Override
      public List<String[]> generateVmData(String userData, String userDataDetails, String serviceOffering, long datacenterId,
                                           String vmName, String vmHostName, long vmId, String vmUuid,
                                           String guestIpAddress, String publicKey, String password, Boolean isWindows, String hostname) {
  
          DataCenterVO dcVo = _dcDao.findById(datacenterId);
          final String zoneName = dcVo.getName();
  
          IPAddressVO publicIp = _ipAddressDao.findByAssociatedVmId(vmId);
          VirtualMachine vm = _vmDao.findById(vmId);
          if (vm == null) {
              throw new CloudRuntimeException(String.format("Cannot generate VM instance data, no VM exists by ID: %d", vmId));
          }
  
          final List<String[]> vmData = new ArrayList<String[]>();
  
          if (userData != null) {
              vmData.add(new String[]{USERDATA_DIR, USERDATA_FILE, userData});
          }
          vmData.add(new String[]{METATDATA_DIR, SERVICE_OFFERING_FILE, com.cloud.utils.StringUtils.unicodeEscape(serviceOffering)});
          vmData.add(new String[]{METATDATA_DIR, AVAILABILITY_ZONE_FILE, com.cloud.utils.StringUtils.unicodeEscape(zoneName)});
          vmData.add(new String[]{METATDATA_DIR, LOCAL_HOSTNAME_FILE, com.cloud.utils.StringUtils.unicodeEscape(vmHostName)});
          vmData.add(new String[]{METATDATA_DIR, LOCAL_IPV4_FILE, guestIpAddress});
  
          addUserDataDetailsToCommand(vmData, userDataDetails);
  
          String publicIpAddress = guestIpAddress;
          String publicHostName = com.cloud.utils.StringUtils.unicodeEscape(vmHostName);
  
          if (dcVo.getNetworkType() != DataCenter.NetworkType.Basic) {
              if (publicIp != null) {
                  publicIpAddress = publicIp.getAddress().addr();
                  publicHostName = publicIp.getAddress().addr();
              } else {
                  publicHostName = null;
              }
          }
          vmData.add(new String[]{METATDATA_DIR, PUBLIC_IPV4_FILE, publicIpAddress});
          vmData.add(new String[]{METATDATA_DIR, PUBLIC_HOSTNAME_FILE, publicHostName});
  
          if (vmUuid == null) {
              vmData.add(new String[]{METATDATA_DIR, INSTANCE_ID_FILE, vmName});
              vmData.add(new String[]{METATDATA_DIR, VM_ID_FILE, String.valueOf(vmId)});
          } else {
              vmData.add(new String[]{METATDATA_DIR, INSTANCE_ID_FILE, vmUuid});
              vmData.add(new String[]{METATDATA_DIR, VM_ID_FILE, vmUuid});
          }
  
          vmData.add(new String[]{METATDATA_DIR, PUBLIC_KEYS_FILE, publicKey});
  
          String cloudIdentifier = _configDao.getValue("cloud.identifier");
          if (cloudIdentifier == null) {
              cloudIdentifier = "";
          } else {
              cloudIdentifier = "CloudStack-{" + cloudIdentifier + "}";
          }
          vmData.add(new String[]{METATDATA_DIR, CLOUD_IDENTIFIER_FILE, cloudIdentifier});
  
          if (password != null && !password.isEmpty() && !password.equals("saved_password")) {
  
              // Here we are calculating MD5 checksum to reduce the over head of calculating MD5 checksum
              // in windows VM in password reset script.
  
              if (isWindows) {
                  MessageDigest md5 = null;
                  try {
                      md5 = MessageDigest.getInstance("MD5");
                  } catch (NoSuchAlgorithmException e) {
                      logger.error("Unexpected exception " + e.getMessage(), e);
                      throw new CloudRuntimeException("Unable to get MD5 MessageDigest", e);
                  }
                  md5.reset();
                  md5.update(password.getBytes(com.cloud.utils.StringUtils.getPreferredCharset()));
                  byte[] digest = md5.digest();
                  BigInteger bigInt = new BigInteger(1, digest);
                  String hashtext = bigInt.toString(16);
  
                  vmData.add(new String[]{PASSWORD_DIR, PASSWORD_CHECKSUM_FILE, hashtext});
              }
  
              vmData.add(new String[]{PASSWORD_DIR, PASSWORD_FILE, password});
          }
          vmData.add(new String[]{METATDATA_DIR, HYPERVISOR_HOST_NAME_FILE, hostname});
  
          Domain domain = _domainDao.findById(vm.getDomainId());
          if (domain != null && VirtualMachineManager.AllowExposeDomainInMetadata.valueIn(domain.getId())) {
              logger.debug("Adding domain info to cloud metadata");
              vmData.add(new String[]{METATDATA_DIR, CLOUD_DOMAIN_FILE, domain.getName()});
              vmData.add(new String[]{METATDATA_DIR, CLOUD_DOMAIN_ID_FILE, domain.getUuid()});
          }
  
          String customCloudName = VirtualMachineManager.MetadataCustomCloudName.valueIn(datacenterId);
          if (org.apache.commons.lang3.StringUtils.isNotBlank(customCloudName)) {
              vmData.add(new String[]{METATDATA_DIR, CLOUD_NAME_FILE, customCloudName});
          }
  
          return vmData;
      }
  
      protected void addUserDataDetailsToCommand(List<String[]> vmData, String userDataDetails) {
          if(userDataDetails != null && !userDataDetails.isEmpty()) {
              userDataDetails = userDataDetails.substring(1, userDataDetails.length()-1);
              String[] keyValuePairs = userDataDetails.split(",");
              for(String pair : keyValuePairs)
              {
                  final Pair<String, String> keyValue = com.cloud.utils.StringUtils.getKeyValuePairWithSeparator(pair, "=");
                  vmData.add(new String[]{METATDATA_DIR, keyValue.first(), com.cloud.utils.StringUtils.unicodeEscape(keyValue.second())});
              }
          }
      }
  
      @Override
      public String getConfigComponentName() {
          return NetworkModel.class.getSimpleName();
      }
  
      @Override
      public ConfigKey<?>[] getConfigKeys() {
          return new ConfigKey<?>[] {MACIdentifier, AdminIsAllowedToDeployAnywhere};
      }
  
      @Override
      public String getValidNetworkCidr(Network guestNetwork) {
          String networkCidr = guestNetwork.getNetworkCidr();
          return networkCidr == null ? guestNetwork.getCidr() : networkCidr;
      }
  
      @Override
      public Pair<String, String> getNetworkIp4Dns(final Network network, final DataCenter zone) {
          if (StringUtils.isNotBlank(network.getDns1())) {
              return new Pair<>(network.getDns1(), network.getDns2());
          }
          if (network.getVpcId() != null) {
              Vpc vpc = vpcDao.findById(network.getVpcId());
              if (vpc != null && StringUtils.isNotBlank(vpc.getIp4Dns1())) {
                  return new Pair<>(vpc.getIp4Dns1(), vpc.getIp4Dns2());
              }
          }
          return new Pair<>(zone.getDns1(), zone.getDns2());
      }
  
      @Override
      public Pair<String, String> getNetworkIp6Dns(final Network network, final DataCenter zone) {
          if (StringUtils.isNotBlank(network.getIp6Dns1())) {
              return new Pair<>(network.getIp6Dns1(), network.getIp6Dns2());
          }
          if (network.getVpcId() != null) {
              Vpc vpc = vpcDao.findById(network.getVpcId());
              if (vpc != null && StringUtils.isNotBlank(vpc.getIp6Dns1())) {
                  return new Pair<>(vpc.getIp6Dns1(), vpc.getIp6Dns2());
              }
          }
          return new Pair<>(zone.getIp6Dns1(), zone.getIp6Dns2());
      }
  
      @Override
      public void verifyIp4DnsPair(String ip4Dns1, String ip4Dns2) {
          if (StringUtils.isEmpty(ip4Dns1) && StringUtils.isNotEmpty(ip4Dns2)) {
              throw new InvalidParameterValueException("Second IPv4 DNS can be specified only with the first IPv4 DNS");
          }
          if (StringUtils.isNotEmpty(ip4Dns1) && !NetUtils.isValidIp4(ip4Dns1)) {
              throw new InvalidParameterValueException("Invalid IPv4 for DNS1");
          }
          if (StringUtils.isNotEmpty(ip4Dns2) && !NetUtils.isValidIp4(ip4Dns2)) {
              throw new InvalidParameterValueException("Invalid IPv4 for DNS2");
          }
      }
  
      @Override
      public void verifyIp6DnsPair(String ip6Dns1, String ip6Dns2) {
          if (StringUtils.isEmpty(ip6Dns1) && StringUtils.isNotEmpty(ip6Dns2)) {
              throw new InvalidParameterValueException("Second IPv6 DNS can be specified only with the first IPv6 DNS");
          }
          if (StringUtils.isNotEmpty(ip6Dns1) && !NetUtils.isValidIp6(ip6Dns1)) {
              throw new InvalidParameterValueException("Invalid IPv6 for IPv6 DNS1");
          }
          if (StringUtils.isNotEmpty(ip6Dns2) && !NetUtils.isValidIp6(ip6Dns2)) {
              throw new InvalidParameterValueException("Invalid IPv6 for IPv6 DNS2");
          }
      }
  
      @Override
      public boolean isSecurityGroupSupportedForZone(Long zoneId) {
          List<? extends PhysicalNetwork> networks = getPhysicalNtwksSupportingTrafficType(zoneId, TrafficType.Guest);
          for (PhysicalNetwork network : networks ) {
              if (_pNSPDao.isServiceProviderEnabled(network.getId(), Provider.SecurityGroupProvider.getName(), Service.SecurityGroup.getName())) {
                  return true;
              }
          }
          return false;
      }
  
      @Override
      public boolean checkSecurityGroupSupportForNetwork(Account account, DataCenter zone,
                                                         List<Long> networkIds,
                                                         List<Long> securityGroupsIds) {
          if (zone.isSecurityGroupEnabled()) {
              return true;
          }
          if (CollectionUtils.isNotEmpty(networkIds)) {
              for (Long networkId : networkIds) {
                  Network network = _networksDao.findById(networkId);
                  if (network == null) {
                      throw new InvalidParameterValueException("Unable to find network by id " + networkId);
                  }
                  if (network.getGuestType() == Network.GuestType.Shared && isSecurityGroupSupportedInNetwork(network)) {
                      return true;
                  }
              }
          } else if (CollectionUtils.isNotEmpty(securityGroupsIds)) {
              Network networkWithSecurityGroup = getNetworkWithSGWithFreeIPs(account, zone.getId());
              return networkWithSecurityGroup != null;
          }
          return false;
      }
  }
  