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 java.sql.Connection;
  import java.sql.PreparedStatement;
  import java.sql.SQLException;
  import java.util.List;
  
  import javax.inject.Inject;
  
  import org.springframework.stereotype.Component;
  
  import org.apache.cloudstack.api.command.admin.network.CreateStorageNetworkIpRangeCmd;
  import org.apache.cloudstack.api.command.admin.network.DeleteStorageNetworkIpRangeCmd;
  import org.apache.cloudstack.api.command.admin.network.ListStorageNetworkIpRangeCmd;
  import org.apache.cloudstack.api.command.admin.network.UpdateStorageNetworkIpRangeCmd;
  
  import com.cloud.dc.HostPodVO;
  import com.cloud.dc.StorageNetworkIpAddressVO;
  import com.cloud.dc.StorageNetworkIpRange;
  import com.cloud.dc.StorageNetworkIpRangeVO;
  import com.cloud.dc.dao.HostPodDao;
  import com.cloud.dc.dao.StorageNetworkIpAddressDao;
  import com.cloud.dc.dao.StorageNetworkIpRangeDao;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.QueryBuilder;
  import com.cloud.utils.db.SearchCriteria.Op;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallbackNoReturn;
  import com.cloud.utils.db.TransactionCallbackWithException;
  import com.cloud.utils.db.TransactionLegacy;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.net.NetUtils;
  import com.cloud.vm.SecondaryStorageVmVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.dao.SecondaryStorageVmDao;
  
  @Component
  public class StorageNetworkManagerImpl extends ManagerBase implements StorageNetworkManager, StorageNetworkService {
  
      @Inject
      StorageNetworkIpAddressDao _sNwIpDao;
      @Inject
      StorageNetworkIpRangeDao _sNwIpRangeDao;
      @Inject
      NetworkDao _networkDao;
      @Inject
      HostPodDao _podDao;
      @Inject
      SecondaryStorageVmDao _ssvmDao;
  
      private void checkOverlapPrivateIpRange(long podId, String startIp, String endIp) {
          HostPodVO pod = _podDao.findById(podId);
          if (pod == null) {
              throw new CloudRuntimeException("Cannot find pod " + podId);
          }
  
          final String[] existingPodIpRanges = pod.getDescription().split(",");
  
          for(String podIpRange: existingPodIpRanges) {
              final String[] existingPodIpRange = podIpRange.split("-");
  
              if (existingPodIpRange.length > 1) {
                  if (!NetUtils.isValidIp4(existingPodIpRange[0]) || !NetUtils.isValidIp4(existingPodIpRange[1])) {
                      continue;
                  }
  
                  if (NetUtils.ipRangesOverlap(startIp, endIp, existingPodIpRange[0], existingPodIpRange[1])) {
                      throw new InvalidParameterValueException("The Storage network Start IP and endIP address range overlap with private IP :" + existingPodIpRange[0] + ":" + existingPodIpRange[1]);
                  }
              }
          }
      }
  
      private void checkOverlapStorageIpRange(long podId, String startIp, String endIp) {
          List<StorageNetworkIpRangeVO> curRanges = _sNwIpRangeDao.listByPodId(podId);
          for (StorageNetworkIpRangeVO range : curRanges) {
              if (NetUtils.ipRangesOverlap(startIp, endIp, range.getStartIp(), range.getEndIp())) {
                  throw new InvalidParameterValueException("The Storage network Start IP and endIP address range overlap with private IP :" + range.getStartIp() + " - " +
                      range.getEndIp());
              }
          }
      }
  
      private void createStorageIpEntires(TransactionLegacy txn, long rangeId, String startIp, String endIp, long zoneId) throws SQLException {
          long startIPLong = NetUtils.ip2Long(startIp);
          long endIPLong = NetUtils.ip2Long(endIp);
          String insertSql =
              "INSERT INTO `cloud`.`op_dc_storage_network_ip_address` (range_id, ip_address, mac_address, taken) VALUES (?, ?, (select mac_address from `cloud`.`data_center` where id=?), ?)";
          String updateSql = "UPDATE `cloud`.`data_center` set mac_address = mac_address+1 where id=?";
          Connection conn = txn.getConnection();
          while (startIPLong <= endIPLong) {
              try (PreparedStatement stmt_insert = conn.prepareStatement(insertSql);) {
                  stmt_insert.setLong(1, rangeId);
                  stmt_insert.setString(2, NetUtils.long2Ip(startIPLong++));
                  stmt_insert.setLong(3, zoneId);
                  stmt_insert.setNull(4, java.sql.Types.DATE);
                  stmt_insert.executeUpdate();
              }
  
              try (PreparedStatement stmt_update = txn.prepareStatement(updateSql);) {
                  stmt_update.setLong(1, zoneId);
                  stmt_update.executeUpdate();
              }
          }
      }
  
      @Override
      @DB
      public StorageNetworkIpRange updateIpRange(UpdateStorageNetworkIpRangeCmd cmd) {
          final Integer vlan = cmd.getVlan();
          final Long rangeId = cmd.getId();
          String startIp = cmd.getStartIp();
          String endIp = cmd.getEndIp();
          final String netmask = cmd.getNetmask();
  
          if (netmask != null && !NetUtils.isValidIp4Netmask(netmask)) {
              throw new CloudRuntimeException("Invalid netmask:" + netmask);
          }
  
          if (_sNwIpDao.countInUseIpByRangeId(rangeId) > 0) {
              throw new CloudRuntimeException("Cannot update the range," + getInUseIpAddress(rangeId));
          }
  
          StorageNetworkIpRangeVO range = _sNwIpRangeDao.findById(rangeId);
          if (range == null) {
              throw new CloudRuntimeException("Cannot find storage ip range " + rangeId);
          }
  
          if (startIp != null || endIp != null) {
              long podId = range.getPodId();
              startIp = startIp == null ? range.getStartIp() : startIp;
              endIp = endIp == null ? range.getEndIp() : endIp;
              checkOverlapPrivateIpRange(podId, startIp, endIp);
              checkOverlapStorageIpRange(podId, startIp, endIp);
          }
  
          final String startIpFinal = startIp;
          final String endIpFinal = endIp;
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                  StorageNetworkIpRangeVO range = null;
                  try {
                      range = _sNwIpRangeDao.acquireInLockTable(rangeId);
                      if (range == null) {
                          throw new CloudRuntimeException("Cannot acquire lock on storage ip range " + rangeId);
                      }
                      StorageNetworkIpRangeVO vo = _sNwIpRangeDao.createForUpdate();
                      if (vlan != null) {
                          vo.setVlan(vlan);
                      }
                      if (startIpFinal != null) {
                          vo.setStartIp(startIpFinal);
                      }
                      if (endIpFinal != null) {
                          vo.setEndIp(endIpFinal);
                      }
                      if (netmask != null) {
                          vo.setNetmask(netmask);
                      }
                      _sNwIpRangeDao.update(rangeId, vo);
                  } finally {
                      if (range != null) {
                          _sNwIpRangeDao.releaseFromLockTable(range.getId());
                      }
                  }
              }
          });
  
          return _sNwIpRangeDao.findById(rangeId);
      }
  
      @Override
      @DB
      public StorageNetworkIpRange createIpRange(final CreateStorageNetworkIpRangeCmd cmd) throws SQLException {
          final Long podId = cmd.getPodId();
          final String startIp = cmd.getStartIp();
          String endIp = cmd.getEndIp();
          final Integer vlan = cmd.getVlan();
          final String netmask = cmd.getNetmask();
  
          if (endIp == null) {
              endIp = startIp;
          }
  
          if (!NetUtils.isValidIp4Netmask(netmask)) {
              throw new CloudRuntimeException("Invalid netmask:" + netmask);
          }
  
          HostPodVO pod = _podDao.findById(podId);
          if (pod == null) {
              throw new CloudRuntimeException("Cannot find pod " + podId);
          }
          final Long zoneId = pod.getDataCenterId();
  
          List<NetworkVO> nws = _networkDao.listByZoneAndTrafficType(zoneId, TrafficType.Storage);
          if (nws.size() == 0) {
              throw new CloudRuntimeException("Cannot find storage network in zone " + zoneId);
          }
          if (nws.size() > 1) {
              throw new CloudRuntimeException("Find more than one storage network in zone " + zoneId + "," + nws.size() + " found");
          }
          final NetworkVO nw = nws.get(0);
  
          checkOverlapPrivateIpRange(podId, startIp, endIp);
          checkOverlapStorageIpRange(podId, startIp, endIp);
  
          StorageNetworkIpRangeVO range = null;
  
          final String endIpFinal = endIp;
          return Transaction.execute(new TransactionCallbackWithException<StorageNetworkIpRangeVO, SQLException>() {
              @Override
              public StorageNetworkIpRangeVO doInTransaction(TransactionStatus status) throws SQLException {
                  StorageNetworkIpRangeVO range = new StorageNetworkIpRangeVO(zoneId, podId, nw.getId(), startIp, endIpFinal, vlan, netmask, cmd.getGateWay());
                  _sNwIpRangeDao.persist(range);
                  try {
                      createStorageIpEntires(TransactionLegacy.currentTxn(), range.getId(), startIp, endIpFinal, zoneId);
                  } catch (SQLException e) {
                      StringBuilder err = new StringBuilder();
                      err.append("Create storage network range failed.");
                      err.append("startIp=" + startIp);
                      err.append("endIp=" + endIpFinal);
                      err.append("netmask=" + netmask);
                      err.append("zoneId=" + zoneId);
                      logger.debug(err.toString(), e);
                      throw e;
                  }
  
                  return range;
              }
          });
      }
  
      private String getInUseIpAddress(long rangeId) {
          List<String> ips = _sNwIpDao.listInUseIpByRangeId(rangeId);
          StringBuilder res = new StringBuilder();
          res.append("Below IP of range " + rangeId + " is still in use:");
          for (String ip : ips) {
              res.append(ip).append(",");
          }
          return res.toString();
      }
  
      @Override
      @DB
      public void deleteIpRange(DeleteStorageNetworkIpRangeCmd cmd) {
          final long rangeId = cmd.getId();
          StorageNetworkIpRangeVO range = _sNwIpRangeDao.findById(rangeId);
          if (range == null) {
              throw new CloudRuntimeException("Can not find storage network ip range " + rangeId);
          }
  
          if (_sNwIpDao.countInUseIpByRangeId(rangeId) > 0) {
              throw new CloudRuntimeException(getInUseIpAddress(rangeId));
          }
  
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                  StorageNetworkIpRangeVO range = null;
                  try {
                      range = _sNwIpRangeDao.acquireInLockTable(rangeId);
                      if (range == null) {
                          String msg = "Unable to acquire lock on storage network ip range id=" + rangeId + ", delete failed";
                          logger.warn(msg);
                          throw new CloudRuntimeException(msg);
                      }
                      /*
                       * entries in op_dc_storage_network_ip_address will be deleted automatically due to
                       * fk_storage_ip_address__range_id constraint key
                       */
                      _sNwIpRangeDao.remove(rangeId);
                  } finally {
                      if (range != null) {
                          _sNwIpRangeDao.releaseFromLockTable(rangeId);
                      }
                  }
              }
          });
  
      }
  
      @Override
      public List<StorageNetworkIpRange> listIpRange(ListStorageNetworkIpRangeCmd cmd) {
          Long rangeId = cmd.getRangeId();
          Long podId = cmd.getPodId();
          Long zoneId = cmd.getZoneId();
  
          List result = null;
          if (rangeId != null) {
              result = _sNwIpRangeDao.listByRangeId(rangeId);
          } else if (podId != null) {
              result = _sNwIpRangeDao.listByPodId(podId);
          } else if (zoneId != null) {
              result = _sNwIpRangeDao.listByDataCenterId(zoneId);
          } else {
              result = _sNwIpRangeDao.listAll();
          }
  
          return result;
      }
  
      @Override
      public void releaseIpAddress(String ip) {
          _sNwIpDao.releaseIpAddress(ip);
      }
  
      @Override
      public StorageNetworkIpAddressVO acquireIpAddress(long podId) {
          List<StorageNetworkIpRangeVO> ranges = _sNwIpRangeDao.listByPodId(podId);
          for (StorageNetworkIpRangeVO r : ranges) {
              try {
                  Long rangeId = r.getId();
                  r = _sNwIpRangeDao.acquireInLockTable(rangeId);
                  if (r == null) {
                      String msg = "Unable to acquire lock on storage network ip range id=" + rangeId + ", delete failed";
                      logger.warn(msg);
                      throw new CloudRuntimeException(msg);
                  }
  
                  StorageNetworkIpAddressVO ip = _sNwIpDao.takeIpAddress(r.getId());
                  if (ip != null) {
                      return ip;
                  }
              } finally {
                  if (r != null) {
                      _sNwIpRangeDao.releaseFromLockTable(r.getId());
                  }
              }
          }
  
          return null;
      }
  
      @Override
      public boolean isStorageIpRangeAvailable(long zoneId) {
          QueryBuilder<StorageNetworkIpRangeVO> sc = QueryBuilder.create(StorageNetworkIpRangeVO.class);
          sc.and(sc.entity().getDataCenterId(), Op.EQ, zoneId);
          List<StorageNetworkIpRangeVO> entries = sc.list();
          return entries.size() > 0;
      }
  
      @Override
      public List<SecondaryStorageVmVO> getSSVMWithNoStorageNetwork(long zoneId) {
          List<SecondaryStorageVmVO> ssvms =
              _ssvmDao.getSecStorageVmListInStates(null, zoneId, VirtualMachine.State.Starting, VirtualMachine.State.Running, VirtualMachine.State.Stopping);
          return ssvms;
      }
  
      @Override
      public boolean isAnyStorageIpInUseInZone(long zoneId) {
          List<StorageNetworkIpRangeVO> ranges = _sNwIpRangeDao.listByDataCenterId(zoneId);
          for (StorageNetworkIpRangeVO r : ranges) {
              if (_sNwIpDao.countInUseIpByRangeId(r.getId()) > 0) {
                  return true;
              }
          }
          return false;
      }
  }
  