| /* |
| * 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 org.apache.cloudstack.storage.datastore.util; |
| |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| |
| import org.apache.cloudstack.storage.datastore.util.NexentaNmsClient.NmsResponse; |
| import org.apache.log4j.LogManager; |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.google.gson.annotations.SerializedName; |
| |
| public class NexentaStorAppliance { |
| private static final Logger logger = LogManager.getLogger(NexentaStorAppliance.class); |
| |
| protected NexentaNmsClient client; |
| protected NexentaUtil.NexentaPluginParameters parameters; |
| |
| public NexentaStorAppliance(NexentaUtil.NexentaPluginParameters parameters) { |
| client = new NexentaNmsClient(parameters.getNmsUrl()); |
| this.parameters = parameters; |
| } |
| |
| NexentaStorAppliance(NexentaNmsClient client, NexentaUtil.NexentaPluginParameters parameters) { |
| this.client = client; |
| this.parameters = parameters; |
| } |
| |
| String getVolumeName(String volumeName) { |
| if (volumeName.startsWith("/")) { |
| return String.format("%s%s", parameters.getVolume(), volumeName); |
| } |
| return String.format("%s/%s", parameters.getVolume(), volumeName); |
| } |
| |
| static String getTargetName(String volumeName) { |
| return NexentaUtil.ISCSI_TARGET_NAME_PREFIX + volumeName; |
| } |
| |
| static String getTargetGroupName(String volumeName) { |
| return NexentaUtil.ISCSI_TARGET_GROUP_PREFIX + volumeName; |
| } |
| |
| @SuppressWarnings("unused") |
| static final class IntegerNmsResponse extends NmsResponse { |
| Integer result; |
| |
| IntegerNmsResponse(int result) { |
| this.result = Integer.valueOf(result); |
| } |
| |
| public Integer getResult() { |
| return result; |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| static final class IscsiTarget { |
| protected String status; |
| protected String protocol; |
| protected String name; |
| protected String sessions; |
| protected String alias; |
| protected String provider; |
| |
| IscsiTarget(String status, String protocol, String name, String sessions, String alias, String provider) { |
| this.status = status; |
| this.protocol = protocol; |
| this.name = name; |
| this.sessions = sessions; |
| this.alias = alias; |
| this.provider = provider; |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| static final class ListOfIscsiTargetsNmsResponse extends NmsResponse { |
| protected HashMap<String, IscsiTarget> result; |
| |
| ListOfIscsiTargetsNmsResponse() {} |
| |
| ListOfIscsiTargetsNmsResponse(HashMap<String, IscsiTarget> result) { |
| this.result = result; |
| } |
| |
| public HashMap<String, IscsiTarget> getResult() { |
| return result; |
| } |
| } |
| |
| /** |
| * Checks if iSCSI target exists. |
| * @param targetName iSCSI target name |
| * @return true if iSCSI target exists, else false |
| */ |
| boolean isIscsiTargetExists(String targetName) { |
| ListOfIscsiTargetsNmsResponse response = (ListOfIscsiTargetsNmsResponse) client.execute(ListOfIscsiTargetsNmsResponse.class, "stmf", "list_targets"); |
| if (response == null) { |
| return false; |
| } |
| HashMap<String, IscsiTarget> result = response.getResult(); |
| return result != null && result.keySet().contains(targetName); |
| } |
| |
| @SuppressWarnings("unused") |
| static final class CreateIscsiTargetRequestParams { |
| @SerializedName("target_name") String targetName; |
| |
| CreateIscsiTargetRequestParams(String targetName) { |
| this.targetName = targetName; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| return other instanceof CreateIscsiTargetRequestParams && targetName.equals(((CreateIscsiTargetRequestParams) other).targetName); |
| } |
| } |
| |
| /** |
| * Creates iSCSI target on NexentaStor Appliance. |
| * @param targetName iSCSI target name |
| */ |
| void createIscsiTarget(String targetName) { |
| try { |
| client.execute(NmsResponse.class, "iscsitarget", "create_target", new CreateIscsiTargetRequestParams(targetName)); |
| } catch (CloudRuntimeException ex) { |
| if (!ex.getMessage().contains("already configured")) { |
| throw ex; |
| } |
| logger.debug("Ignored target creation error: " + ex); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| static final class ListOfStringsNmsResponse extends NmsResponse { |
| LinkedList<String> result; |
| |
| ListOfStringsNmsResponse() {} |
| |
| ListOfStringsNmsResponse(LinkedList<String> result) { |
| this.result = result; |
| } |
| |
| public LinkedList<String> getResult() { |
| return result; |
| } |
| } |
| |
| /** |
| * Checks if iSCSI target group already exists on NexentaStor Appliance. |
| * @param targetGroupName iSCSI target group name |
| * @return true if iSCSI target group already exists, else false |
| */ |
| boolean isIscsiTargetGroupExists(String targetGroupName) { |
| ListOfStringsNmsResponse response = (ListOfStringsNmsResponse) client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroups"); |
| if (response == null) { |
| return false; |
| } |
| LinkedList<String> result = response.getResult(); |
| return result != null && result.contains(targetGroupName); |
| } |
| |
| /** |
| * Creates iSCSI target group on NexentaStor Appliance. |
| * @param targetGroupName iSCSI target group name |
| */ |
| void createIscsiTargetGroup(String targetGroupName) { |
| try { |
| client.execute(NmsResponse.class, "stmf", "create_targetgroup", targetGroupName); |
| } catch (CloudRuntimeException ex) { |
| if (!ex.getMessage().contains("already exists") && !ex.getMessage().contains("target must be offline")) { |
| throw ex; |
| } |
| logger.info("Ignored target group creation error: " + ex); |
| } |
| } |
| |
| /** |
| * Checks if iSCSI target is member of target group. |
| * @param targetGroupName iSCSI target group name |
| * @param targetName iSCSI target name |
| * @return true if target is member of iSCSI target group, else false |
| */ |
| boolean isTargetMemberOfTargetGroup(String targetGroupName, String targetName) { |
| ListOfStringsNmsResponse response = (ListOfStringsNmsResponse) client.execute(ListOfStringsNmsResponse.class, "stmf", "list_targetgroup_members", targetGroupName); |
| if (response == null) { |
| return false; |
| } |
| LinkedList<String> result = response.getResult(); |
| return result != null && result.contains(targetName); |
| } |
| |
| /** |
| * Adds iSCSI target to target group. |
| * @param targetGroupName iSCSI target group name |
| * @param targetName iSCSI target name |
| */ |
| void addTargetGroupMember(String targetGroupName, String targetName) { |
| try { |
| client.execute(NmsResponse.class, "stmf", "add_targetgroup_member", targetGroupName, targetName); |
| } catch (CloudRuntimeException ex) { |
| if (!ex.getMessage().contains("already exists") && !ex.getMessage().contains("target must be offline")) { |
| throw ex; |
| } |
| logger.debug("Ignored target group member addition error: " + ex); |
| } |
| } |
| |
| /** |
| * Checks if LU already exists on NexentaStor appliance. |
| * @param luName LU name |
| * @return true if LU already exists, else false |
| */ |
| boolean isLuExists(String luName) { |
| IntegerNmsResponse response; |
| try { |
| response = (IntegerNmsResponse) client.execute(IntegerNmsResponse.class, "scsidisk", "lu_exists", luName); |
| } catch (CloudRuntimeException ex) { |
| if (ex.getMessage().contains("does not exist")) { |
| return false; |
| } |
| throw ex; |
| } |
| return response!= null && response.getResult() > 0; |
| } |
| |
| @SuppressWarnings("unused") |
| static final class LuParams { |
| @Override |
| public boolean equals(Object other) { |
| return other instanceof LuParams; |
| } |
| } |
| |
| /** |
| * Creates LU for volume. |
| * @param volumeName volume name |
| */ |
| void createLu(String volumeName) { |
| try { |
| client.execute(NmsResponse.class, "scsidisk", "create_lu", volumeName, new LuParams()); |
| } catch (CloudRuntimeException ex) { |
| if (!ex.getMessage().contains("in use")) { |
| throw ex; |
| } |
| logger.info("Ignored LU creation error: " + ex); |
| } |
| } |
| |
| /** |
| * Checks if LU shared on NexentaStor appliance. |
| * @param luName LU name |
| * @return true if LU was already shared, else false |
| */ |
| boolean isLuShared(String luName) { |
| IntegerNmsResponse response; |
| try { |
| response = (IntegerNmsResponse) client.execute(IntegerNmsResponse.class, "scsidisk", "lu_shared", luName); |
| } catch (CloudRuntimeException ex) { |
| if (ex.getMessage().contains("does not exist")) { |
| return false; |
| } |
| throw ex; |
| } |
| return response != null && response.getResult() > 0; |
| } |
| |
| @SuppressWarnings("unused") |
| static final class MappingEntry { |
| @SerializedName("target_group") String targetGroup; |
| String lun; |
| String zvol; |
| @SerializedName("host_group") String hostGroup; |
| @SerializedName("entry_number") String entryNumber; |
| |
| MappingEntry(String targetGroup, String lun) { |
| this.targetGroup = targetGroup; |
| this.lun = lun; |
| } |
| |
| static boolean isEquals(Object a, Object b) { |
| return (a == null && b == null) || (a != null && a.equals(b)); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other instanceof MappingEntry) { |
| MappingEntry o = (MappingEntry) other; |
| return isEquals(targetGroup, o.targetGroup) && isEquals(lun, o.lun) && isEquals(zvol, o.zvol) && |
| isEquals(hostGroup, o.hostGroup) && isEquals(entryNumber, o.entryNumber); |
| } |
| return false; |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| static final class AddMappingEntryNmsResponse extends NmsResponse { |
| MappingEntry result; |
| } |
| |
| /** |
| * Adds LU mapping entry to iSCSI target group. |
| * @param luName LU name |
| * @param targetGroupName iSCSI target group name |
| */ |
| void addLuMappingEntry(String luName, String targetGroupName) { |
| MappingEntry mappingEntry = new MappingEntry(targetGroupName, "0"); |
| try { |
| client.execute(AddMappingEntryNmsResponse.class, "scsidisk", "add_lun_mapping_entry", luName, mappingEntry); |
| } catch (CloudRuntimeException ex) { |
| if (!ex.getMessage().contains("view already exists")) { |
| throw ex; |
| } |
| logger.debug("Ignored LU mapping entry addition error " + ex); |
| } |
| } |
| |
| NexentaStorZvol createIscsiVolume(String volumeName, Long volumeSize) { |
| final String zvolName = getVolumeName(volumeName); |
| String volumeSizeString = String.format("%dB", volumeSize); |
| |
| client.execute(NmsResponse.class, "zvol", "create", zvolName, volumeSizeString, parameters.getVolumeBlockSize(), parameters.getSparseVolumes()); |
| |
| final String targetName = getTargetName(volumeName); |
| final String targetGroupName = getTargetGroupName(volumeName); |
| |
| if (!isIscsiTargetExists(targetName)) { |
| createIscsiTarget(targetName); |
| } |
| |
| if (!isIscsiTargetGroupExists(targetGroupName)) { |
| createIscsiTargetGroup(targetGroupName); |
| } |
| |
| if (!isTargetMemberOfTargetGroup(targetGroupName, targetName)) { |
| addTargetGroupMember(targetGroupName, targetName); |
| } |
| |
| if (!isLuExists(zvolName)) { |
| createLu(zvolName); |
| } |
| |
| if (!isLuShared(zvolName)) { |
| addLuMappingEntry(zvolName, targetGroupName); |
| } |
| |
| return new NexentaStorZvol(zvolName, targetName); |
| } |
| |
| static abstract class NexentaStorVolume { |
| protected String name; |
| |
| NexentaStorVolume(String name) { |
| this.name = name; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| } |
| |
| public static final class NexentaStorZvol extends NexentaStorVolume { |
| protected String iqn; |
| |
| public NexentaStorZvol(String name, String iqn) { |
| super(name); |
| this.iqn = iqn; |
| } |
| |
| public String getIqn() { |
| return iqn; |
| } |
| } |
| |
| public void deleteIscsiVolume(String volumeName) { |
| try { |
| NmsResponse response = client.execute(NmsResponse.class, "zvol", "destroy", volumeName, ""); |
| } catch (CloudRuntimeException ex) { |
| if (!ex.getMessage().contains("does not exist")) { |
| throw ex; |
| } |
| logger.debug(String.format( |
| "Volume %s does not exist, it seems it was already " + |
| "deleted.", volumeName)); |
| } |
| } |
| |
| public NexentaStorVolume createVolume(String volumeName, Long volumeSize) { |
| return createIscsiVolume(volumeName, volumeSize); |
| } |
| |
| public void deleteVolume(String volumeName) { |
| deleteIscsiVolume(volumeName); |
| } |
| } |