| // 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.netapp; |
| |
| import java.io.IOException; |
| import java.net.UnknownHostException; |
| import java.rmi.ServerException; |
| import java.util.ArrayList; |
| import java.util.ConcurrentModificationException; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import javax.inject.Inject; |
| import javax.naming.ConfigurationException; |
| |
| import netapp.manage.NaAPIFailedException; |
| import netapp.manage.NaElement; |
| import netapp.manage.NaException; |
| import netapp.manage.NaServer; |
| |
| import org.apache.log4j.Logger; |
| import org.springframework.stereotype.Component; |
| |
| import com.cloud.api.commands.netapp.AssociateLunCmd; |
| import com.cloud.api.commands.netapp.CreateLunCmd; |
| import com.cloud.api.commands.netapp.CreateVolumeOnFilerCmd; |
| import com.cloud.api.commands.netapp.CreateVolumePoolCmd; |
| import com.cloud.api.commands.netapp.DeleteVolumePoolCmd; |
| import com.cloud.api.commands.netapp.DestroyLunCmd; |
| import com.cloud.api.commands.netapp.DestroyVolumeOnFilerCmd; |
| import com.cloud.api.commands.netapp.DissociateLunCmd; |
| import com.cloud.api.commands.netapp.ListLunsCmd; |
| import com.cloud.api.commands.netapp.ListVolumePoolsCmd; |
| import com.cloud.api.commands.netapp.ListVolumesOnFilerCmd; |
| import com.cloud.api.commands.netapp.ModifyVolumePoolCmd; |
| import com.cloud.exception.InvalidParameterValueException; |
| import com.cloud.exception.ResourceAllocationException; |
| import com.cloud.exception.ResourceInUseException; |
| import com.cloud.netapp.dao.LunDao; |
| import com.cloud.netapp.dao.PoolDao; |
| import com.cloud.netapp.dao.VolumeDao; |
| import com.cloud.utils.component.ManagerBase; |
| import com.cloud.utils.db.DB; |
| import com.cloud.utils.db.TransactionLegacy; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| |
| @Component |
| public class NetappManagerImpl extends ManagerBase implements NetappManager { |
| public enum Algorithm { |
| roundrobin, leastfull |
| } |
| |
| public static final Logger s_logger = Logger.getLogger(NetappManagerImpl.class.getName()); |
| @Inject |
| public VolumeDao _volumeDao; |
| @Inject |
| public PoolDao _poolDao; |
| @Inject |
| public LunDao _lunDao; |
| private NetappAllocator _netappAllocator = null; |
| |
| /** |
| * Default constructor |
| */ |
| public NetappManagerImpl() { |
| } |
| |
| @Override |
| public void createPool(String poolName, String algorithm) throws InvalidParameterValueException { |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> createPool "); |
| |
| PoolVO pool = null; |
| validAlgorithm(algorithm); |
| try { |
| pool = new PoolVO(poolName, algorithm); |
| _poolDao.persist(pool); |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Response --> createPool:success"); |
| |
| } catch (CloudRuntimeException cre) { |
| pool = _poolDao.findPool(poolName); |
| if (pool != null) { |
| throw new InvalidParameterValueException("Duplicate Pool Name"); |
| } else { |
| throw cre; |
| } |
| } |
| } |
| |
| /** |
| * This method validates the algorithm used for allocation of the volume |
| * @param algorithm -- algorithm type |
| * @throws InvalidParameterValueException |
| */ |
| private void validAlgorithm(String algorithm) throws InvalidParameterValueException { |
| //TODO: use enum |
| if (!algorithm.equalsIgnoreCase("roundrobin") && !algorithm.equalsIgnoreCase("leastfull")) { |
| throw new InvalidParameterValueException("Unknown algorithm " + algorithm); |
| } |
| } |
| |
| /** |
| * Utility method to get the netapp server object |
| * @param serverIp -- ip address of netapp box |
| * @param userName -- username |
| * @param password -- password |
| * @return |
| * @throws UnknownHostException |
| */ |
| private NaServer getServer(String serverIp, String userName, String password) throws UnknownHostException { |
| //Initialize connection to server, and |
| //request version 1.3 of the API set |
| NaServer s = new NaServer(serverIp, 1, 3); |
| s.setStyle(NaServer.STYLE_LOGIN_PASSWORD); |
| s.setAdminUser(userName, password); |
| |
| return s; |
| } |
| |
| @Override |
| public List<Class<?>> getCommands() { |
| List<Class<?>> cmdList = new ArrayList<Class<?>>(); |
| cmdList.add(CreateLunCmd.class); |
| cmdList.add(ListLunsCmd.class); |
| cmdList.add(DissociateLunCmd.class); |
| cmdList.add(CreateVolumeOnFilerCmd.class); |
| cmdList.add(ModifyVolumePoolCmd.class); |
| cmdList.add(ListVolumesOnFilerCmd.class); |
| cmdList.add(ListVolumePoolsCmd.class); |
| cmdList.add(DestroyLunCmd.class); |
| cmdList.add(CreateVolumePoolCmd.class); |
| cmdList.add(DeleteVolumePoolCmd.class); |
| cmdList.add(AssociateLunCmd.class); |
| cmdList.add(DestroyVolumeOnFilerCmd.class); |
| return cmdList; |
| } |
| |
| @Override |
| public void modifyPool(String poolName, String algorithm) throws InvalidParameterValueException { |
| validAlgorithm(algorithm); |
| PoolVO pool = _poolDao.findPool(poolName); |
| |
| if (pool == null) { |
| throw new InvalidParameterValueException("Cannot find pool " + poolName); |
| } |
| |
| validAlgorithm(algorithm); |
| |
| pool.setAlgorithm(algorithm); |
| pool.setName(poolName); |
| |
| _poolDao.update(pool.getId(), pool); |
| } |
| |
| @Override |
| public void deletePool(String poolName) throws InvalidParameterValueException, ResourceInUseException { |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> deletePool "); |
| |
| PoolVO pool = _poolDao.findPool(poolName); |
| if (pool == null) { |
| throw new InvalidParameterValueException("Cannot find pool " + poolName); |
| } |
| //check if pool is empty |
| int volCount = _volumeDao.listVolumes(poolName).size(); |
| |
| if (volCount == 0) { |
| _poolDao.remove(pool.getId()); |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> deletePool: Success "); |
| |
| } else { |
| throw new ResourceInUseException("Cannot delete non-empty pool"); |
| } |
| } |
| |
| @Override |
| public List<PoolVO> listPools() { |
| return _poolDao.listAll(); |
| } |
| |
| /** |
| * This method destroys the volume on netapp filer |
| * @param ipAddress -- ip address of filer |
| * @param aggrName -- name of containing aggregate |
| * @param volName -- name of volume to destroy |
| * @throws ResourceInUseException |
| * @throws NaException |
| * @throws NaAPIFailedException |
| */ |
| @Override |
| @DB |
| public void destroyVolumeOnFiler(String ipAddress, String aggrName, String volName) throws ServerException, InvalidParameterValueException, ResourceInUseException { |
| NaElement xi0; |
| NaElement xi1; |
| NetappVolumeVO volume = null; |
| |
| volume = _volumeDao.findVolume(ipAddress, aggrName, volName); |
| |
| if (volume == null) { |
| s_logger.warn("The volume does not exist in our system"); |
| throw new InvalidParameterValueException("The given tuple:" + ipAddress + "," + aggrName + "," + volName + " doesn't exist in our system"); |
| } |
| |
| List<LunVO> lunsOnVol = _lunDao.listLunsByVolId(volume.getId()); |
| |
| if (lunsOnVol != null && lunsOnVol.size() > 0) { |
| s_logger.warn("There are luns on the volume"); |
| throw new ResourceInUseException("There are luns on the volume"); |
| } |
| |
| final TransactionLegacy txn = TransactionLegacy.currentTxn(); |
| txn.start(); |
| PoolVO pool = _poolDao.findById(volume.getPoolId()); |
| if (pool == null) { |
| throw new InvalidParameterValueException("Failed to find pool for given volume"); |
| //FIXME: choose a better exception. this is a db integrity exception |
| } |
| pool = _poolDao.acquireInLockTable(pool.getId()); |
| if (pool == null) { |
| throw new ConcurrentModificationException("Failed to acquire lock on pool " + volume.getPoolId()); |
| } |
| NaServer s = null; |
| try { |
| s = getServer(volume.getIpAddress(), volume.getUsername(), volume.getPassword()); |
| //bring the volume down |
| xi0 = new NaElement("volume-offline"); |
| xi0.addNewChild("name", volName); |
| s.invokeElem(xi0); |
| |
| //now destroy it |
| xi1 = new NaElement("volume-destroy"); |
| xi1.addNewChild("name", volName); |
| s.invokeElem(xi1); |
| |
| //now delete from our records |
| _volumeDao.remove(volume.getId()); |
| txn.commit(); |
| |
| } catch (UnknownHostException uhe) { |
| s_logger.warn("Unable to delete volume on filer ", uhe); |
| throw new ServerException("Unable to delete volume on filer", uhe); |
| } catch (NaAPIFailedException naf) { |
| s_logger.warn("Unable to delete volume on filer ", naf); |
| if (naf.getErrno() == 13040) { |
| s_logger.info("Deleting the volume: " + volName); |
| _volumeDao.remove(volume.getId()); |
| txn.commit(); |
| } |
| |
| throw new ServerException("Unable to delete volume on filer", naf); |
| } catch (NaException nae) { |
| txn.rollback(); |
| s_logger.warn("Unable to delete volume on filer ", nae); |
| throw new ServerException("Unable to delete volume on filer", nae); |
| } catch (IOException ioe) { |
| txn.rollback(); |
| s_logger.warn("Unable to delete volume on filer ", ioe); |
| throw new ServerException("Unable to delete volume on filer", ioe); |
| } finally { |
| if (pool != null) { |
| _poolDao.releaseFromLockTable(pool.getId()); |
| } |
| if (s != null) |
| s.close(); |
| } |
| |
| } |
| |
| /** |
| * This method creates a volume on netapp filer |
| * @param ipAddress -- ip address of the filer |
| * @param aggName -- name of aggregate |
| * @param poolName -- name of pool |
| * @param volName -- name of volume |
| * @param volSize -- size of volume to be created |
| * @param snapshotPolicy -- associated snapshot policy for volume |
| * @param snapshotReservation -- associated reservation for snapshots |
| * @param username -- username |
| * @param password -- password |
| * @throws UnknownHostException |
| * @throws InvalidParameterValueException |
| */ |
| @Override |
| @DB |
| public void createVolumeOnFiler(String ipAddress, String aggName, String poolName, String volName, String volSize, String snapshotPolicy, |
| Integer snapshotReservation, String username, String password) throws UnknownHostException, ServerException, InvalidParameterValueException { |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> createVolume " + "serverIp:" + ipAddress); |
| |
| boolean snapPolicy = false; |
| boolean snapshotRes = false; |
| boolean volumeCreated = false; |
| |
| NaServer s = getServer(ipAddress, username, password); |
| |
| NaElement xi = new NaElement("volume-create"); |
| xi.addNewChild("volume", volName); |
| xi.addNewChild("containing-aggr-name", aggName); |
| xi.addNewChild("size", volSize); |
| |
| NaElement xi1 = new NaElement("snapshot-set-reserve"); |
| if (snapshotReservation != null) { |
| snapshotRes = true; |
| xi1.addNewChild("percentage", snapshotReservation.toString()); |
| xi1.addNewChild("volume", volName); |
| } |
| |
| NaElement xi2 = new NaElement("snapshot-set-schedule"); |
| |
| if (snapshotPolicy != null) { |
| snapPolicy = true; |
| |
| String weeks = null; |
| String days = null; |
| String hours = null; |
| String whichHours = null; |
| String minutes = null; |
| String whichMinutes = null; |
| |
| StringTokenizer s1 = new StringTokenizer(snapshotPolicy, " "); |
| |
| //count=4: weeks days hours@csi mins@csi |
| //count=3: weeks days hours@csi |
| //count=2: weeks days |
| //count=1: weeks |
| |
| if (s1.hasMoreTokens()) { |
| weeks = s1.nextToken(); |
| } |
| if (weeks != null && s1.hasMoreTokens()) { |
| days = s1.nextToken(); |
| } |
| if (days != null && s1.hasMoreTokens()) { |
| String[] hoursArr = s1.nextToken().split("@"); |
| hours = hoursArr[0]; |
| whichHours = hoursArr[1]; |
| } |
| if (hours != null && s1.hasMoreTokens()) { |
| String[] minsArr = s1.nextToken().split("@"); |
| minutes = minsArr[0]; |
| whichMinutes = minsArr[1]; |
| } |
| |
| if (weeks != null) |
| xi2.addNewChild("weeks", weeks); |
| if (days != null) |
| xi2.addNewChild("days", days); |
| if (hours != null) |
| xi2.addNewChild("hours", hours); |
| if (minutes != null) |
| xi2.addNewChild("minutes", minutes); |
| xi2.addNewChild("volume", volName); |
| |
| if (whichHours != null) |
| xi2.addNewChild("which-hours", whichHours); |
| if (whichMinutes != null) |
| xi2.addNewChild("which-minutes", whichMinutes); |
| } |
| Long volumeId = null; |
| |
| final TransactionLegacy txn = TransactionLegacy.currentTxn(); |
| txn.start(); |
| NetappVolumeVO volume = null; |
| volume = _volumeDao.findVolume(ipAddress, aggName, volName); |
| |
| if (volume != null) { |
| throw new InvalidParameterValueException("The volume for the given ipAddress/aggregateName/volumeName tuple already exists"); |
| } |
| PoolVO pool = _poolDao.findPool(poolName); |
| if (pool == null) { |
| throw new InvalidParameterValueException("Cannot find pool " + poolName); |
| } |
| pool = _poolDao.acquireInLockTable(pool.getId()); |
| if (pool == null) { |
| s_logger.warn("Failed to acquire lock on pool " + poolName); |
| throw new ConcurrentModificationException("Failed to acquire lock on pool " + poolName); |
| } |
| volume = new NetappVolumeVO(ipAddress, aggName, pool.getId(), volName, volSize, "", 0, username, password, 0, pool.getName()); |
| volume = _volumeDao.persist(volume); |
| |
| volumeId = volume.getId(); |
| try { |
| s.invokeElem(xi); |
| volumeCreated = true; |
| |
| if (snapshotRes) { |
| s.invokeElem(xi1); |
| volume.setSnapshotReservation(snapshotReservation); |
| _volumeDao.update(volumeId, volume); |
| } |
| |
| if (snapPolicy) { |
| s.invokeElem(xi2); |
| volume.setSnapshotPolicy(snapshotPolicy); |
| _volumeDao.update(volumeId, volume); |
| } |
| txn.commit(); |
| } catch (NaException nae) { |
| //zapi call failed, log and throw e |
| s_logger.warn("Failed to create volume on the netapp filer:", nae); |
| txn.rollback(); |
| if (volumeCreated) { |
| try { |
| deleteRogueVolume(volName, s);//deletes created volume on filer |
| } catch (NaException e) { |
| s_logger.warn("Failed to cleanup created volume whilst rolling back on the netapp filer:", e); |
| throw new ServerException("Unable to create volume via cloudtools." |
| + "Failed to cleanup created volume on netapp filer whilst rolling back on the cloud db:", e); |
| } catch (IOException e) { |
| s_logger.warn("Failed to cleanup created volume whilst rolling back on the netapp filer:", e); |
| throw new ServerException("Unable to create volume via cloudtools." |
| + "Failed to cleanup created volume on netapp filer whilst rolling back on the cloud db:", e); |
| } |
| } |
| throw new ServerException("Unable to create volume", nae); |
| } catch (IOException ioe) { |
| s_logger.warn("Failed to create volume on the netapp filer:", ioe); |
| txn.rollback(); |
| if (volumeCreated) { |
| try { |
| deleteRogueVolume(volName, s);//deletes created volume on filer |
| } catch (NaException e) { |
| s_logger.warn("Failed to cleanup created volume whilst rolling back on the netapp filer:", e); |
| throw new ServerException("Unable to create volume via cloudtools." |
| + "Failed to cleanup created volume on netapp filer whilst rolling back on the cloud db:", e); |
| } catch (IOException e) { |
| s_logger.warn("Failed to cleanup created volume whilst rolling back on the netapp filer:", e); |
| throw new ServerException("Unable to create volume via cloudtools." |
| + "Failed to cleanup created volume on netapp filer whilst rolling back on the cloud db:", e); |
| } |
| } |
| throw new ServerException("Unable to create volume", ioe); |
| } finally { |
| if (s != null) |
| s.close(); |
| if (pool != null) |
| _poolDao.releaseFromLockTable(pool.getId()); |
| |
| } |
| } |
| |
| /** |
| * This method is primarily used to cleanup volume created on the netapp filer, when createVol api command fails at snapshot reservation. |
| * We roll back the db record, but the record on the netapp box still exists. We clean up that record using this helper method. |
| * @param volName |
| * @param s -- server reference |
| * @throws NaException |
| * @throws IOException |
| */ |
| private void deleteRogueVolume(String volName, NaServer s) throws NaException, IOException { |
| //bring the volume down |
| NaElement xi0 = new NaElement("volume-offline"); |
| xi0.addNewChild("name", volName); |
| s.invokeElem(xi0); |
| |
| //now destroy it |
| NaElement xi1 = new NaElement("volume-destroy"); |
| xi1.addNewChild("name", volName); |
| s.invokeElem(xi1); |
| } |
| |
| /** |
| * This method lists all the volumes by pool name |
| * @param poolName |
| * @return -- volumes in that pool |
| */ |
| @Override |
| public List<NetappVolumeVO> listVolumesOnFiler(String poolName) { |
| |
| List<NetappVolumeVO> vols = _volumeDao.listVolumesAscending(poolName); |
| |
| for (NetappVolumeVO vol : vols) { |
| try { |
| String snapScheduleOnFiler = returnSnapshotSchedule(vol); |
| vol.setSnapshotPolicy(snapScheduleOnFiler); |
| |
| } catch (ServerException e) { |
| s_logger.warn("Error trying to get snapshot schedule for volume" + vol.getVolumeName()); |
| } |
| } |
| return vols; |
| } |
| |
| /** |
| * Utility method to return snapshot schedule for a volume |
| * @param vol -- volume for the snapshot schedule creation |
| * @return -- the snapshot schedule |
| * @throws ServerException |
| */ |
| private String returnSnapshotSchedule(NetappVolumeVO vol) throws ServerException { |
| |
| NaElement xi = new NaElement("snapshot-get-schedule"); |
| xi.addNewChild("volume", vol.getVolumeName()); |
| NaServer s = null; |
| try { |
| s = getServer(vol.getIpAddress(), vol.getUsername(), vol.getPassword()); |
| NaElement xo = s.invokeElem(xi); |
| String weeks = xo.getChildContent("weeks"); |
| String days = xo.getChildContent("days"); |
| String hours = xo.getChildContent("hours"); |
| String minutes = xo.getChildContent("minutes"); |
| String whichHours = xo.getChildContent("which-hours"); |
| String whichMinutes = xo.getChildContent("which-minutes"); |
| |
| StringBuilder sB = new StringBuilder(); |
| sB.append(weeks) |
| .append(" ") |
| .append(days) |
| .append(" ") |
| .append(hours) |
| .append("@") |
| .append(whichHours) |
| .append(" ") |
| .append(minutes) |
| .append("@") |
| .append(whichMinutes); |
| return sB.toString(); |
| } catch (NaException nae) { |
| s_logger.warn("Failed to get volume size ", nae); |
| throw new ServerException("Failed to get volume size", nae); |
| } catch (IOException ioe) { |
| s_logger.warn("Failed to get volume size ", ioe); |
| throw new ServerException("Failed to get volume size", ioe); |
| } finally { |
| if (s != null) |
| s.close(); |
| } |
| } |
| |
| /** |
| * This method returns the ascending order list of volumes based on their ids |
| * @param poolName -- name of pool |
| * @return -- ascending ordered list of volumes based on ids |
| */ |
| @Override |
| public List<NetappVolumeVO> listVolumesAscending(String poolName) { |
| return _volumeDao.listVolumesAscending(poolName); |
| } |
| |
| /** |
| * This method returns the available size on the volume in terms of bytes |
| * @param volName -- name of volume |
| * @param userName -- username |
| * @param password -- password |
| * @param serverIp -- ip address of filer |
| * @throws UnknownHostException |
| * @return-- available size on the volume in terms of bytes; return -1 if volume is offline |
| * @throws ServerException |
| */ |
| @Override |
| public long returnAvailableVolumeSize(String volName, String userName, String password, String serverIp) throws ServerException { |
| long availableSize = 0; |
| |
| NaElement xi = new NaElement("volume-list-info"); |
| xi.addNewChild("volume", volName); |
| NaServer s = null; |
| String volumeState = null; |
| try { |
| s = getServer(serverIp, userName, password); |
| NaElement xo = s.invokeElem(xi); |
| List volList = xo.getChildByName("volumes").getChildren(); |
| Iterator volIter = volList.iterator(); |
| while (volIter.hasNext()) { |
| NaElement volInfo = (NaElement)volIter.next(); |
| availableSize = volInfo.getChildLongValue("size-available", -1); |
| volumeState = volInfo.getChildContent("state"); |
| } |
| |
| if (volumeState != null) { |
| return volumeState.equalsIgnoreCase("online") ? availableSize : -1; //return -1 if volume is offline |
| } else { |
| //catch all |
| //volume state unreported |
| return -1; // as good as volume offline |
| } |
| |
| } catch (NaException nae) { |
| s_logger.warn("Failed to get volume size ", nae); |
| throw new ServerException("Failed to get volume size", nae); |
| } catch (IOException ioe) { |
| s_logger.warn("Failed to get volume size ", ioe); |
| throw new ServerException("Failed to get volume size", ioe); |
| } finally { |
| if (s != null) |
| s.close(); |
| } |
| } |
| |
| /** |
| * This method creates a lun on the netapp filer |
| * @param poolName -- name of the pool |
| * @param lunSize -- size of the lun to be created |
| * @return -- lun path |
| * @throws IOException |
| * @throws ResourceAllocationException |
| * @throws NaException |
| */ |
| @Override |
| @DB |
| public String[] createLunOnFiler(String poolName, Long lunSize) throws ServerException, InvalidParameterValueException, ResourceAllocationException { |
| String[] result = new String[3]; |
| StringBuilder lunName = new StringBuilder("lun-"); |
| LunVO lun = null; |
| final TransactionLegacy txn = TransactionLegacy.currentTxn(); |
| txn.start(); |
| PoolVO pool = _poolDao.findPool(poolName); |
| |
| if (pool == null) { |
| throw new InvalidParameterValueException("Cannot find pool " + poolName); |
| } |
| |
| if (lunSize <= 0) { |
| throw new InvalidParameterValueException("Please specify a valid lun size in Gb"); |
| } |
| |
| String algorithm = pool.getAlgorithm(); |
| NetappVolumeVO selectedVol = null; |
| |
| //sanity check |
| int numVolsInPool = _volumeDao.listVolumes(poolName).size(); |
| |
| if (numVolsInPool == 0) { |
| throw new InvalidParameterValueException("No volumes exist in the given pool"); |
| } |
| pool = _poolDao.acquireInLockTable(pool.getId()); |
| if (pool == null) { |
| s_logger.warn("Failed to acquire lock on the pool " + poolName); |
| return result; |
| } |
| NaServer s = null; |
| |
| try { |
| if (algorithm == null || algorithm.equals(Algorithm.roundrobin.toString())) { |
| selectedVol = _netappAllocator.chooseVolumeFromPool(poolName, lunSize); |
| } else if (algorithm.equals(Algorithm.leastfull.toString())) { |
| |
| selectedVol = _netappAllocator.chooseLeastFullVolumeFromPool(poolName, lunSize); |
| } |
| |
| if (selectedVol == null) { |
| throw new ServerException("Could not find a suitable volume to create lun on"); |
| } |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> createLun " + "serverIp:" + selectedVol.getIpAddress()); |
| |
| StringBuilder exportPath = new StringBuilder("/vol/"); |
| exportPath.append(selectedVol.getVolumeName()); |
| exportPath.append("/"); |
| |
| lun = new LunVO(exportPath.toString(), selectedVol.getId(), lunSize, "", ""); |
| lun = _lunDao.persist(lun); |
| |
| //Lun id created: 6 digits right justified eg. 000045 |
| String lunIdStr = String.valueOf(lun.getId()); |
| String zeroStr = "000000"; |
| int length = lunIdStr.length(); |
| int offset = 6 - length; |
| StringBuilder lunIdOnPath = new StringBuilder(); |
| lunIdOnPath.append(zeroStr.substring(0, offset)); |
| lunIdOnPath.append(lunIdStr); |
| exportPath.append("lun-").append(lunIdOnPath.toString()); |
| |
| lunName.append(lunIdOnPath.toString()); |
| |
| //update lun name |
| lun.setLunName(lunName.toString()); |
| _lunDao.update(lun.getId(), lun); |
| |
| NaElement xi; |
| NaElement xi1; |
| |
| long lSizeBytes = 1L * lunSize * 1024 * 1024 * 1024; //This prevents integer overflow |
| Long lunSizeBytes = new Long(lSizeBytes); |
| |
| s = getServer(selectedVol.getIpAddress(), selectedVol.getUsername(), selectedVol.getPassword()); |
| |
| //create lun |
| xi = new NaElement("lun-create-by-size"); |
| xi.addNewChild("ostype", "linux"); |
| xi.addNewChild("path", exportPath.toString()); |
| xi.addNewChild("size", (lunSizeBytes.toString())); |
| |
| s.invokeElem(xi); |
| |
| try { |
| //now create an igroup |
| xi1 = new NaElement("igroup-create"); |
| xi1.addNewChild("initiator-group-name", lunName.toString()); |
| xi1.addNewChild("initiator-group-type", "iscsi"); |
| xi1.addNewChild("os-type", "linux"); |
| s.invokeElem(xi1); |
| } catch (NaAPIFailedException e) { |
| if (e.getErrno() == 9004) { |
| //igroup already exists hence no error |
| s_logger.warn("Igroup already exists"); |
| } |
| } |
| |
| //get target iqn |
| NaElement xi4 = new NaElement("iscsi-node-get-name"); |
| NaElement xo = s.invokeElem(xi4); |
| String iqn = xo.getChildContent("node-name"); |
| |
| lun.setTargetIqn(iqn); |
| _lunDao.update(lun.getId(), lun); |
| |
| //create lun mapping |
| //now map the lun to the igroup |
| NaElement xi3 = new NaElement("lun-map"); |
| xi3.addNewChild("force", "true"); |
| xi3.addNewChild("initiator-group", lunName.toString()); |
| xi3.addNewChild("path", lun.getPath() + lun.getLunName()); |
| |
| xi3.addNewChild("lun-id", lunIdStr); |
| s.invokeElem(xi3); |
| |
| txn.commit(); |
| //set the result |
| result[0] = lunName.toString();//lunname |
| result[1] = iqn;//iqn |
| result[2] = selectedVol.getIpAddress(); |
| |
| return result; |
| |
| } catch (NaAPIFailedException naf) { |
| if (naf.getErrno() == 9023) { //lun is already mapped to this group |
| result[0] = lunName.toString();//lunname; |
| result[1] = lun.getTargetIqn();//iqn |
| result[2] = selectedVol.getIpAddress(); |
| return result; |
| } |
| if (naf.getErrno() == 9024) { //another lun mapped at this group |
| result[0] = lunName.toString();//lunname; |
| result[1] = lun.getTargetIqn();//iqn |
| result[2] = selectedVol.getIpAddress(); |
| return result; |
| } |
| } catch (NaException nae) { |
| txn.rollback(); |
| throw new ServerException("Unable to create LUN", nae); |
| } catch (IOException ioe) { |
| txn.rollback(); |
| throw new ServerException("Unable to create LUN", ioe); |
| } finally { |
| if (pool != null) { |
| _poolDao.releaseFromLockTable(pool.getId()); |
| } |
| if (s != null) { |
| s.close(); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * This method destroys a lun on the netapp filer |
| * @param lunName -- name of the lun to be destroyed |
| */ |
| @Override |
| @DB |
| public void destroyLunOnFiler(String lunName) throws InvalidParameterValueException, ServerException { |
| |
| final TransactionLegacy txn = TransactionLegacy.currentTxn(); |
| txn.start(); |
| |
| LunVO lun = _lunDao.findByName(lunName); |
| |
| if (lun == null) |
| throw new InvalidParameterValueException("Cannot find lun"); |
| |
| NetappVolumeVO vol = _volumeDao.acquireInLockTable(lun.getVolumeId()); |
| if (vol == null) { |
| s_logger.warn("Failed to lock volume id= " + lun.getVolumeId()); |
| return; |
| } |
| NaServer s = null; |
| try { |
| s = getServer(vol.getIpAddress(), vol.getUsername(), vol.getPassword()); |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> destroyLun " + ":serverIp:" + vol.getIpAddress()); |
| |
| try { |
| //Unmap lun |
| NaElement xi2 = new NaElement("lun-unmap"); |
| xi2.addNewChild("initiator-group", lunName); |
| xi2.addNewChild("path", lun.getPath() + lun.getLunName()); |
| s.invokeElem(xi2); |
| } catch (NaAPIFailedException naf) { |
| if (naf.getErrno() == 9016) |
| s_logger.warn("no map exists excpn 9016 caught in deletelun, continuing with delete"); |
| } |
| |
| //destroy lun |
| NaElement xi = new NaElement("lun-destroy"); |
| xi.addNewChild("force", "true"); |
| xi.addNewChild("path", lun.getPath() + lun.getLunName()); |
| s.invokeElem(xi); |
| |
| //destroy igroup |
| NaElement xi1 = new NaElement("igroup-destroy"); |
| //xi1.addNewChild("force","true"); |
| xi1.addNewChild("initiator-group-name", lunName); |
| s.invokeElem(xi1); |
| |
| _lunDao.remove(lun.getId()); |
| txn.commit(); |
| } catch (UnknownHostException uhe) { |
| txn.rollback(); |
| s_logger.warn("Failed to delete lun", uhe); |
| throw new ServerException("Failed to delete lun", uhe); |
| } catch (IOException ioe) { |
| txn.rollback(); |
| s_logger.warn("Failed to delete lun", ioe); |
| throw new ServerException("Failed to delete lun", ioe); |
| } catch (NaAPIFailedException naf) { |
| if (naf.getErrno() == 9017) {//no such group exists excpn |
| s_logger.warn("no such group exists excpn 9017 caught in deletelun, continuing with delete"); |
| _lunDao.remove(lun.getId()); |
| txn.commit(); |
| } else if (naf.getErrno() == 9029) {//LUN maps for this initiator group exist |
| s_logger.warn("LUN maps for this initiator group exist errno 9029 caught in deletelun, continuing with delete"); |
| _lunDao.remove(lun.getId()); |
| txn.commit(); |
| } else { |
| txn.rollback(); |
| s_logger.warn("Failed to delete lun", naf); |
| throw new ServerException("Failed to delete lun", naf); |
| } |
| |
| } catch (NaException nae) { |
| txn.rollback(); |
| s_logger.warn("Failed to delete lun", nae); |
| throw new ServerException("Failed to delete lun", nae); |
| } finally { |
| if (vol != null) { |
| _volumeDao.releaseFromLockTable(vol.getId()); |
| } |
| if (s != null) |
| s.close(); |
| } |
| |
| } |
| |
| /** |
| * This method lists the luns on the netapp filer |
| * @param volId -- id of the containing volume |
| * @return -- list of netapp luns |
| * @throws NaException |
| * @throws IOException |
| */ |
| @Override |
| public List<LunVO> listLunsOnFiler(String poolName) { |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> listLunsOnFiler "); |
| |
| List<LunVO> luns = new ArrayList<LunVO>(); |
| |
| List<NetappVolumeVO> vols = _volumeDao.listVolumes(poolName); |
| |
| for (NetappVolumeVO vol : vols) { |
| luns.addAll(_lunDao.listLunsByVolId(vol.getId())); |
| } |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Response --> listLunsOnFiler:success"); |
| |
| return luns; |
| } |
| |
| /** |
| * This method disassociates a lun from the igroup on the filer |
| * @param iGroup -- igroup name |
| * @param lunName -- lun name |
| */ |
| @Override |
| public void disassociateLun(String iGroup, String lunName) throws ServerException, InvalidParameterValueException { |
| NaElement xi; |
| LunVO lun = _lunDao.findByName(lunName); |
| |
| if (lun == null) |
| throw new InvalidParameterValueException("Cannot find LUN " + lunName); |
| |
| NetappVolumeVO vol = _volumeDao.findById(lun.getVolumeId()); |
| NaServer s = null; |
| try { |
| s = getServer(vol.getIpAddress(), vol.getUsername(), vol.getPassword()); |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> disassociateLun " + ":serverIp:" + vol.getIpAddress()); |
| |
| xi = new NaElement("igroup-remove"); |
| xi.addNewChild("force", "true"); |
| xi.addNewChild("initiator", iGroup); |
| xi.addNewChild("initiator-group-name", lunName); |
| s.invokeElem(xi); |
| |
| } catch (UnknownHostException uhe) { |
| throw new ServerException("Failed to disassociate lun", uhe); |
| } catch (IOException ioe) { |
| throw new ServerException("Failed to disassociate lun", ioe); |
| } catch (NaException nae) { |
| throw new ServerException("Failed to disassociate lun", nae); |
| } finally { |
| if (s != null) |
| s.close(); |
| } |
| |
| } |
| |
| /** |
| * This method associates a lun to a particular igroup |
| * @param iqn |
| * @param iGroup |
| * @param lunName |
| */ |
| @Override |
| public String[] associateLun(String guestIqn, String lunName) throws ServerException, InvalidParameterValueException |
| |
| { |
| NaElement xi2; |
| |
| //get lun id from path |
| String[] splitLunName = lunName.split("-"); |
| String[] returnVal = new String[3]; |
| if (splitLunName.length != 2) |
| throw new InvalidParameterValueException("The lun id is malformed"); |
| |
| String lunIdStr = splitLunName[1]; |
| |
| Long lId = new Long(lunIdStr); |
| |
| LunVO lun = _lunDao.findById(lId); |
| |
| if (lun == null) |
| throw new InvalidParameterValueException("Cannot find LUN " + lunName); |
| |
| NetappVolumeVO vol = _volumeDao.findById(lun.getVolumeId()); |
| |
| //assert(vol != null); |
| |
| returnVal[0] = lunIdStr; |
| returnVal[1] = lun.getTargetIqn(); |
| returnVal[2] = vol.getIpAddress(); |
| |
| NaServer s = null; |
| |
| try { |
| s = getServer(vol.getIpAddress(), vol.getUsername(), vol.getPassword()); |
| |
| if (s_logger.isDebugEnabled()) |
| s_logger.debug("Request --> associateLun " + ":serverIp:" + vol.getIpAddress()); |
| |
| //add iqn to the group |
| xi2 = new NaElement("igroup-add"); |
| xi2.addNewChild("force", "true"); |
| xi2.addNewChild("initiator", guestIqn); |
| xi2.addNewChild("initiator-group-name", lunName); |
| s.invokeElem(xi2); |
| |
| return returnVal; |
| } catch (UnknownHostException uhe) { |
| s_logger.warn("Unable to associate LUN ", uhe); |
| throw new ServerException("Unable to associate LUN", uhe); |
| } catch (NaAPIFailedException naf) { |
| if (naf.getErrno() == 9008) { //initiator group already contains node |
| return returnVal; |
| } |
| s_logger.warn("Unable to associate LUN ", naf); |
| throw new ServerException("Unable to associate LUN", naf); |
| } catch (NaException nae) { |
| s_logger.warn("Unable to associate LUN ", nae); |
| throw new ServerException("Unable to associate LUN", nae); |
| } catch (IOException ioe) { |
| s_logger.warn("Unable to associate LUN ", ioe); |
| throw new ServerException("Unable to associate LUN", ioe); |
| } finally { |
| if (s != null) |
| s.close(); |
| } |
| |
| } |
| |
| @Override |
| public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { |
| |
| _netappAllocator = new NetappDefaultAllocatorImpl(this); |
| |
| return true; |
| } |
| } |