| // 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.apache.log4j.Logger; |
| 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 { |
| private static final Logger s_logger = Logger.getLogger(StorageNetworkManagerImpl.class); |
| |
| @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); |
| s_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"; |
| s_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"; |
| s_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; |
| } |
| } |