/*
* Copyright 2016 Dell Inc.
*
* Licensed 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.emc.storageos.driver.dellsc.scapi;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.driver.dellsc.scapi.objects.ApiConnection;
import com.emc.storageos.driver.dellsc.scapi.objects.EmDataCollector;
import com.emc.storageos.driver.dellsc.scapi.objects.ScConfiguration;
import com.emc.storageos.driver.dellsc.scapi.objects.ScControllerPort;
import com.emc.storageos.driver.dellsc.scapi.objects.ScControllerPortFibreChannelConfiguration;
import com.emc.storageos.driver.dellsc.scapi.objects.ScControllerPortIscsiConfiguration;
import com.emc.storageos.driver.dellsc.scapi.objects.ScCopyMirrorMigrate;
import com.emc.storageos.driver.dellsc.scapi.objects.ScFaultDomain;
import com.emc.storageos.driver.dellsc.scapi.objects.ScMapping;
import com.emc.storageos.driver.dellsc.scapi.objects.ScMappingProfile;
import com.emc.storageos.driver.dellsc.scapi.objects.ScObject;
import com.emc.storageos.driver.dellsc.scapi.objects.ScPhysicalServer;
import com.emc.storageos.driver.dellsc.scapi.objects.ScReplay;
import com.emc.storageos.driver.dellsc.scapi.objects.ScReplayConsistencyGroup;
import com.emc.storageos.driver.dellsc.scapi.objects.ScReplayProfile;
import com.emc.storageos.driver.dellsc.scapi.objects.ScServer;
import com.emc.storageos.driver.dellsc.scapi.objects.ScServerHba;
import com.emc.storageos.driver.dellsc.scapi.objects.ScServerOperatingSystem;
import com.emc.storageos.driver.dellsc.scapi.objects.ScStorageType;
import com.emc.storageos.driver.dellsc.scapi.objects.ScStorageTypeStorageUsage;
import com.emc.storageos.driver.dellsc.scapi.objects.ScVolume;
import com.emc.storageos.driver.dellsc.scapi.objects.ScVolumeConfiguration;
import com.emc.storageos.driver.dellsc.scapi.objects.ScVolumeStorageUsage;
import com.emc.storageos.driver.dellsc.scapi.objects.StorageCenter;
import com.emc.storageos.driver.dellsc.scapi.objects.StorageCenterStorageUsage;
import com.emc.storageos.driver.dellsc.scapi.rest.Parameters;
import com.emc.storageos.driver.dellsc.scapi.rest.PayloadFilter;
import com.emc.storageos.driver.dellsc.scapi.rest.PayloadFilter.ValueFilterType;
import com.emc.storageos.driver.dellsc.scapi.rest.RestClient;
import com.emc.storageos.driver.dellsc.scapi.rest.RestResult;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* API client for managing Storage Center via DSM.
*/
public class StorageCenterAPI implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(StorageCenterAPI.class);
private static final String NOTES_STRING = "Created by CoprHD driver";
private RestClient restClient;
private Gson gson;
private String host;
private StorageCenterAPI(String host, int port, String user, String password)
throws StorageCenterAPIException {
LOG.debug("{} {} {}", host, port, user);
restClient = new RestClient(host, port, user, password);
gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssX").create();
Parameters params = new Parameters();
params.add("Application", "CoprHD Driver");
params.add("ApplicationVersion", "1.0");
RestResult result = restClient.post("/ApiConnection/Login", params.toJson());
if (result.getResponseCode() != 200) {
throw new StorageCenterAPIException(result.getErrorMsg(), result.getResponseCode());
}
ApiConnection connection = gson.fromJson(result.getResult(), ApiConnection.class);
LOG.debug("Connected to {} {} {}", connection.hostName, connection.provider, connection.providerVersion);
this.host = host;
}
/**
* Gets the connection host name.
*
* @return The host name.
*/
public String getHost() {
return host;
}
/**
* Get a Storage Center API connection.
*
* @param host The DSM host name or IP address.
* @param port The DSM port.
* @param user The user to connect as.
* @param password The password.
* @return The Storage Center API connection.
* @throws StorageCenterAPIException the storage center API exception
*/
public static StorageCenterAPI openConnection(String host, int port, String user, String password)
throws StorageCenterAPIException {
return new StorageCenterAPI(host, port, user, password);
}
/**
* Close API connection.
*/
public void closeConnection() {
// Log out of the API
restClient.post("/ApiConnection/Logout", "{}");
restClient = null;
}
@Override
public void close() {
this.closeConnection();
}
@SuppressWarnings("unchecked")
private boolean checkResults(RestResult result) {
if (result.getResponseCode() >= 200 &&
result.getResponseCode() < 300) {
return true;
}
// Some versions return the reason text, some return a JSON structure
String text = result.getResult();
if (text.startsWith("{\"")) {
Map<String, String> resultJson = new HashMap<>(0);
resultJson = gson.fromJson(text, resultJson.getClass());
text = resultJson.get("result");
}
LOG.warn("REST call result:\n\tURL: {}\n\tStatus Code: {}\n\tReason: {}\n\tText: {}",
result.getUrl(), result.getResponseCode(), result.getErrorMsg(), text);
result.setErrorMsg(text);
return false;
}
/**
* Get all Storage Centers managed by this DSM instance.
*
* @return Collection of Storage Centers.
*/
public StorageCenter[] getStorageCenterInfo() {
RestResult result = restClient.get("/StorageCenter/StorageCenter");
try {
return gson.fromJson(result.getResult(), StorageCenter[].class);
} catch (Exception e) {
LOG.warn("Error getting Storage Center info results: {}", e);
}
return new StorageCenter[0];
}
/**
* Find a specific Storage Center.
*
* @param ssn The Storage Center serial number.
* @return The Storage Center.
* @throws StorageCenterAPIException if the Storage Center is not found.
*/
public StorageCenter findStorageCenter(String ssn) throws StorageCenterAPIException {
StorageCenter[] scs = getStorageCenterInfo();
for (StorageCenter sc : scs) {
if (ssn.equals(sc.scSerialNumber)) {
return sc;
}
}
throw new StorageCenterAPIException(String.format("Unable to locate Storage Center %s", ssn));
}
/**
* Creates a volume on the Storage Center.
*
* @param ssn The Storage Center SN on which to create the volume.
* @param name The volume name.
* @param storageType The storage type to use.
* @param sizeInGB The size in GB
* @param cgID The consistency group ID to add volume to or null.
* @return The Storage Center volume.
* @throws StorageCenterAPIException
*/
public ScVolume createVolume(String ssn, String name, String storageType, int sizeInGB, String cgID) throws StorageCenterAPIException {
LOG.debug("Creating {}GB volume: '{}'", sizeInGB, name);
String errorMessage = "";
Parameters params = new Parameters();
params.add("Name", name);
params.add("Notes", NOTES_STRING);
params.add("Size", String.format("%d MB", sizeInGB));
params.add("StorageCenter", ssn);
if (cgID != null && !cgID.isEmpty()) {
String[] ids = { cgID };
params.add("ReplayProfileList", ids);
}
ScStorageType[] storageTypes = getStorageTypes(ssn);
for (ScStorageType storType : storageTypes) {
if (storType.name.equals(storageType)) {
params.add("StorageType", storType.instanceId);
break;
}
}
try {
RestResult result = restClient.post("StorageCenter/ScVolume", params.toJson());
if (checkResults(result)) {
return gson.fromJson(result.getResult(), ScVolume.class);
}
} catch (Exception e) {
errorMessage = String.format("Error creating volume: %s", e);
LOG.warn(errorMessage);
}
if (errorMessage.length() == 0) {
errorMessage = String.format("Unable to create volume %s on SC %s", name, ssn);
}
throw new StorageCenterAPIException(errorMessage);
}
/**
* Gets a volume.
*
* @param instanceId the instance id of the volume.
* @return the volume or null if not found
*/
public ScVolume getVolume(String instanceId) {
ScVolume result = null;
if (instanceId != null) {
RestResult rr = restClient.get(String.format("StorageCenter/ScVolume/%s", instanceId));
if (checkResults(rr)) {
result = gson.fromJson(rr.getResult(), ScVolume.class);
}
}
return result;
}
/**
* Delete a volume.
*
* If the volume can't be found we return success assuming the volume has been deleted
* by some other means.
*
* @param instanceId the instance id of the volume.
* @throws StorageCenterAPIException if error encountered deleting the volume.
*/
public void deleteVolume(String instanceId) throws StorageCenterAPIException {
ScVolume vol = getVolume(instanceId);
if (vol == null) {
LOG.warn("Volume delete request for {}, volume not found. Assuming deleted.", instanceId);
return;
}
RestResult rr = restClient.delete(String.format("StorageCenter/ScVolume/%s", instanceId));
if (!checkResults(rr)) {
String msg = String.format("Error deleting volume %s", instanceId);
LOG.error(msg);
throw new StorageCenterAPIException(msg);
}
}
/**
* Find an initiator WWN or iSCSI IQN.
*
* @param ssn The Storage Center SN on which to check.
* @param iqnOrWwn The FC WWN or iSCSI IQN.
* @return The HBA object.
*/
private ScServerHba findServerHba(String ssn, String iqnOrWwn) {
ScServerHba result = null;
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
filter.append("instanceName", iqnOrWwn);
RestResult rr = restClient.post("StorageCenter/ScServerHba/GetList", filter.toJson());
if (checkResults(rr)) {
ScServerHba[] hbas = gson.fromJson(rr.getResult(), ScServerHba[].class);
for (ScServerHba hba : hbas) {
// Should only return one if found, but just grab first from list
result = hba;
break;
}
}
return result;
}
/**
* Find a server definition.
*
* @param ssn The Storage Center SN on which to check.
* @param iqnOrWwn The WWN or IQN.
* @return The server definition.
*/
public ScServer findServer(String ssn, String iqnOrWwn) {
ScServer result = null;
ScServerHba hba = findServerHba(ssn, iqnOrWwn);
// If the initiator has been seen but never used to define a server it will have a null server
if (hba != null && hba.server != null) {
RestResult rr = restClient.get(String.format("StorageCenter/ScServer/%s", hba.server.instanceId));
if (checkResults(rr)) {
result = gson.fromJson(rr.getResult(), ScServer.class);
}
}
return result;
}
/**
* Gets all server definitions on the array.
*
* @param ssn The Storage Center system serial number.
* @return The server definitions.
*/
public ScServer[] getServerDefinitions(String ssn) {
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
RestResult rr = restClient.post("StorageCenter/ScServer/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScServer[].class);
}
return new ScServer[0];
}
/**
* Gets a specific server definition.
*
* @param instanceId The server instance ID.
* @return The server or null if not found.
*/
public ScServer getServerDefinition(String instanceId) {
RestResult rr = restClient.get(String.format("StorageCenter/ScServer/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScServer.class);
}
return null;
}
/**
* Gets a specific physical server definition.
*
* @param instanceId The server instance ID.
* @return The server or null if not found.
*/
public ScPhysicalServer getPhysicalServerDefinition(String instanceId) {
RestResult rr = restClient.get(String.format("StorageCenter/ScPhysicalServer/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScPhysicalServer.class);
}
return null;
}
/**
* Get all storage types from the system.
*
* @param ssn The Storage Center system serial number.
* @return The storage types.
*/
public ScStorageType[] getStorageTypes(String ssn) {
PayloadFilter filter = new PayloadFilter();
if (ssn != null) {
filter.append("scSerialNumber", ssn);
}
RestResult rr = restClient.post("StorageCenter/ScStorageType/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScStorageType[].class);
}
return new ScStorageType[0];
}
/**
* Gets a specific storage type.
*
* @param instanceId The storage type instance ID.
* @return The storage type.
*/
public ScStorageType getStorageType(String instanceId) {
RestResult rr = restClient.get(
String.format("StorageCenter/ScStorageType/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScStorageType.class);
}
return null;
}
/**
* Gets the storage usage information for a storage type.
*
* @param instanceId The storage type to get.
* @return The storage usage.
*/
public ScStorageTypeStorageUsage getStorageTypeStorageUsage(String instanceId) {
RestResult rr = restClient.get(String.format("StorageCenter/ScStorageType/%s/StorageUsage", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScStorageTypeStorageUsage.class);
}
return new ScStorageTypeStorageUsage();
}
/**
* Gets the Storage Center usage data.
*
* @param ssn The Storage Center system serial number.
* @return The storage usage.
*/
public StorageCenterStorageUsage getStorageUsage(String ssn) {
RestResult rr = restClient.get(String.format("StorageCenter/StorageCenter/%s/StorageUsage", ssn));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), StorageCenterStorageUsage.class);
}
return new StorageCenterStorageUsage();
}
/**
* Gets all controller target ports.
*
* @param ssn The Storage Center serial number.
* @param type The type of port to get or Null for all types.
* @return The controller ports.
*/
public ScControllerPort[] getTargetPorts(String ssn, String type) {
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
if (type != null && type.length() > 0) {
filter.append("transportType", type);
}
RestResult rr = restClient.post("StorageCenter/ScControllerPort/GetList", filter.toJson());
if (checkResults(rr)) {
List<ScControllerPort> ports = new ArrayList<>();
ScControllerPort[] scPorts = gson.fromJson(rr.getResult(), ScControllerPort[].class);
for (ScControllerPort port : scPorts) {
if (port.purpose.startsWith("FrontEnd") && !"FrontEndReserved".equals(port.purpose)) {
ports.add(port);
}
}
return ports.toArray(new ScControllerPort[0]);
}
return new ScControllerPort[0];
}
/**
* Gets all iSCSI target ports.
*
* @param ssn The Storage Center serial number.
* @return The iSCSI controller ports.
*/
public ScControllerPort[] getIscsiTargetPorts(String ssn) {
return getTargetPorts(ssn, ScControllerPort.ISCSI_TRANSPORT_TYPE);
}
/**
* Gets all fibre channel target ports.
*
* @param ssn The Storage Center serial number.
* @return The FC controller ports.
*/
public ScControllerPort[] getFcTargetPorts(String ssn) {
return getTargetPorts(ssn, ScControllerPort.FC_TRANSPORT_TYPE);
}
/**
* Gets all defined HBAs for a server definition.
*
* @param ssn The Storage Center serial number.
* @param serverInstanceId The server definition.
* @return The HBAs.
*/
public ScServerHba[] getServerHbas(String ssn, String serverInstanceId) {
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
filter.append("Server", serverInstanceId);
RestResult rr = restClient.post("StorageCenter/ScServerHba/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScServerHba[].class);
}
return new ScServerHba[0];
}
/**
* Gets a server HBA.
*
* @param instanceId The server HBA instance ID.
* @return The server HBA.
*/
public ScServerHba getServerHba(String instanceId) {
RestResult rr = restClient.get(
String.format("StorageCenter/ScServerHba/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScServerHba.class);
}
return null;
}
/**
* Get the fault domains configured for a port.
*
* @param instanceId The port instance ID.
* @return The fault domains.
*/
public ScFaultDomain[] getControllerPortFaultDomains(String instanceId) {
RestResult rr = restClient.get(
String.format("StorageCenter/ScControllerPort/%s/FaultDomainList", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScFaultDomain[].class);
}
return new ScFaultDomain[0];
}
/**
* Gets configuration about an iSCSI controller port.
*
* @param instanceId The port instance ID.
* @return The port configuration.
*/
public ScControllerPortIscsiConfiguration getControllerPortIscsiConfig(String instanceId) {
RestResult rr = restClient.get(
String.format("/StorageCenter/ScControllerPort/%s/ControllerPortConfiguration", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScControllerPortIscsiConfiguration.class);
}
return new ScControllerPortIscsiConfiguration();
}
/**
* Gets configuration about a fibre channel controller port.
*
* @param instanceId The port instance ID.
* @return The port configuration.
*/
public ScControllerPortFibreChannelConfiguration getControllerPortFCConfig(String instanceId) {
RestResult rr = restClient.get(
String.format("/StorageCenter/ScControllerPort/%s/ControllerPortConfiguration", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScControllerPortFibreChannelConfiguration.class);
}
return new ScControllerPortFibreChannelConfiguration();
}
/**
* Expand a volume to a larger size.
*
* @param instanceId The volume instance ID.
* @param newSize The new size.
* @return The ScVolume object.
* @throws StorageCenterAPIException
*/
public ScVolume expandVolume(String instanceId, int newSize) throws StorageCenterAPIException {
LOG.debug("Expanding volume '{}' to {}GB", instanceId, newSize);
Parameters params = new Parameters();
params.add("NewSize", String.format("%d MB", newSize));
try {
RestResult result = restClient.post(
String.format("StorageCenter/ScVolume/%s/ExpandToSize", instanceId),
params.toJson());
if (checkResults(result)) {
return gson.fromJson(result.getResult(), ScVolume.class);
}
throw new StorageCenterAPIException(
String.format("Failed to expande volume: %s", result.getErrorMsg()));
} catch (Exception e) {
LOG.warn(String.format("Error expanding volume: %s", e));
throw new StorageCenterAPIException("Error expanding volume", e);
}
}
/**
* Gets all volumes.
*
* @param ssn The Storage Center to query from.
* @return The volumes on the requested SC.
*/
public ScVolume[] getAllVolumes(String ssn) {
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
RestResult rr = restClient.post("StorageCenter/ScVolume/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScVolume[].class);
}
return new ScVolume[0];
}
/**
* Gets all replays for a given volume.
*
* @param instanceId The volume instance ID.
* @return The volume's replays.
*/
public ScReplay[] getVolumeSnapshots(String instanceId) {
PayloadFilter filter = new PayloadFilter();
filter.append("createVolume", instanceId);
filter.append("active", false);
filter.append("markedForExpiration", false);
RestResult rr = restClient.post("StorageCenter/ScReplay/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScReplay[].class);
}
return new ScReplay[0];
}
/**
* Gets the storage consumption information for a volume.
*
* @param instanceId The volume instance ID.
* @return The storage usage information.
* @throws StorageCenterAPIException
*/
public ScVolumeStorageUsage getVolumeStorageUsage(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScVolume/%s/StorageUsage", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScVolumeStorageUsage.class);
}
String message = "Unknown";
if (rr.getErrorMsg().length() > 0) {
message = rr.getErrorMsg();
}
message = String.format("Error getting storage usage for volume %s: %s", instanceId, message);
throw new StorageCenterAPIException(message);
}
/**
* Creates a new consistency group on the Storage Center.
*
* @param ssn The Storage Center on which to create the CG.
* @param name The name of the CG.
* @return The created consistency group.
* @throws StorageCenterAPIException
*/
public ScReplayProfile createConsistencyGroup(String ssn, String name) throws StorageCenterAPIException {
LOG.debug("Creating consistency group '{}'", name);
String errorMessage = "";
Parameters params = new Parameters();
params.add("Name", name);
params.add("Notes", NOTES_STRING);
params.add("Type", "Consistent");
params.add("StorageCenter", ssn);
try {
RestResult result = restClient.post("StorageCenter/ScReplayProfile", params.toJson());
if (checkResults(result)) {
return gson.fromJson(result.getResult(), ScReplayProfile.class);
}
errorMessage = String.format(
"Unable to create CG %s on SC %s: %s", name, ssn, result.getErrorMsg());
} catch (Exception e) {
errorMessage = String.format("Error creating consistency group: %s", e);
LOG.warn(errorMessage);
}
if (errorMessage.length() == 0) {
errorMessage = String.format("Unable to create CG %s on SC %s", name, ssn);
}
throw new StorageCenterAPIException(errorMessage);
}
/**
* Deletes a consistency group.
*
* @param instanceId The replay profile instance ID.
* @throws StorageCenterAPIException
*/
public void deleteConsistencyGroup(String instanceId) throws StorageCenterAPIException {
LOG.debug("Deleting consistency group '{}'", instanceId);
RestResult rr = restClient.delete(String.format("StorageCenter/ScReplayProfile/%s", instanceId));
if (!checkResults(rr)) {
String msg = String.format("Error deleting CG %s: %s", instanceId, rr.getErrorMsg());
LOG.error(msg);
throw new StorageCenterAPIException(msg);
}
}
/**
* Gets the volumes that are part of a consistency group.
*
* @param instanceId The CG ID.
* @return The volumes.
* @throws StorageCenterAPIException
*/
public ScVolume[] getConsistencyGroupVolumes(String instanceId) throws StorageCenterAPIException {
LOG.debug("Getting volume for consistency group {}", instanceId);
RestResult rr = restClient.get(String.format("StorageCenter/ScReplayProfile/%s/VolumeList", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScVolume[].class);
}
throw new StorageCenterAPIException(rr.getErrorMsg());
}
/**
* Create snapshots for the consistency group.
*
* @param instanceId The replay profile instance ID.
* @return The replays created.
* @throws StorageCenterAPIException
*/
public ScReplay[] createConsistencyGroupSnapshots(String instanceId) throws StorageCenterAPIException {
LOG.debug("Creating consistency group snapshots for '{}'", instanceId);
// Get a random identifier that will fit in our description field
String id = UUID.randomUUID().toString().substring(0, 31);
Parameters params = new Parameters();
params.add("description", id);
params.add("expireTime", 0);
RestResult rr = restClient.post(
String.format("StorageCenter/ScReplayProfile/%s/CreateReplay", instanceId),
params.toJson());
if (!checkResults(rr)) {
String msg = String.format("Error creating snapshots from CG %s: %s", instanceId, rr.getErrorMsg());
LOG.error(msg);
throw new StorageCenterAPIException(msg);
}
rr = restClient.get(
String.format("StorageCenter/ScReplayProfile/%s/ConsistencyGroupList", instanceId));
if (!checkResults(rr)) {
String msg = String.format("Error getting consistent groups: %s", rr.getErrorMsg());
LOG.warn(msg);
throw new StorageCenterAPIException(msg);
}
ScReplayConsistencyGroup consistentGroup = null;
ScReplayConsistencyGroup[] cgs = gson.fromJson(rr.getResult(), ScReplayConsistencyGroup[].class);
for (ScReplayConsistencyGroup cg : cgs) {
if (id.equals(cg.description)) {
consistentGroup = cg;
}
}
if (consistentGroup != null) {
rr = restClient.get(
String.format(
"StorageCenter/ScReplayConsistencyGroup/%s/ReplayList",
consistentGroup.instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScReplay[].class);
}
}
throw new StorageCenterAPIException("Unable to get replays created for consistency group.");
}
/**
* Gets all replay profiles that are consistent type.
*
* @param ssn The Storage Center serial number.
* @return The consistent replay profiles.
*/
public ScReplayProfile[] getConsistencyGroups(String ssn) {
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
filter.append("type", "Consistent");
RestResult rr = restClient.post("StorageCenter/ScReplayProfile/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScReplayProfile[].class);
}
return new ScReplayProfile[0];
}
/**
* Gets all volumes that are part of a replay profile.
*
* @param instanceId The replay profile instance ID.
* @return The member volumes.
* @throws StorageCenterAPIException
*/
public ScVolume[] getReplayProfileVolumes(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScReplayProfile/%s/VolumeList", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScVolume[].class);
}
String message = "Unknown";
if (rr.getErrorMsg().length() > 0) {
message = rr.getErrorMsg();
}
message = String.format("Error getting replay profile member volumes: %s", message);
throw new StorageCenterAPIException(message);
}
/**
* Gets a replay.
*
* @param instanceId The replay's instance ID.
* @return The replay.
*/
public ScReplay getReplay(String instanceId) {
RestResult rr = restClient.get(String.format("StorageCenter/ScReplay/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScReplay.class);
}
return null;
}
/**
* Creates a replay on a volume.
*
* @param instanceId The volume instance ID.
* @return The created replay.
* @throws StorageCenterAPIException
*/
public ScReplay createReplay(String instanceId) throws StorageCenterAPIException {
return createReplay(instanceId, 0);
}
/**
* Creates a replay on a volume.
*
* @param instanceId The volume instance ID.
* @param expireTime The number of minutes before the snapshot expires.
* @return The created replay.
* @throws StorageCenterAPIException
*/
public ScReplay createReplay(String instanceId, int expireTime) throws StorageCenterAPIException {
Parameters params = new Parameters();
params.add("description", NOTES_STRING);
params.add("expireTime", expireTime);
RestResult rr = restClient.post(
String.format("StorageCenter/ScVolume/%s/CreateReplay", instanceId), params.toJson());
if (!checkResults(rr)) {
String msg = String.format("Error creating replay %s: %s", instanceId, rr.getErrorMsg());
LOG.warn(msg);
throw new StorageCenterAPIException(msg);
}
return gson.fromJson(rr.getResult(), ScReplay.class);
}
/**
* Create a view volume of a replay.
*
* @param instanceId The replay instance ID.
* @param name The name to give the volume.
* @return The created volume.
* @throws StorageCenterAPIException
*/
public ScVolume createReplayView(String instanceId, String name) throws StorageCenterAPIException {
Parameters params = new Parameters();
params.add("Name", name);
params.add("Notes", instanceId);
RestResult rr = restClient.post(
String.format("StorageCenter/ScReplay/%s/CreateView", instanceId), params.toJson());
if (!checkResults(rr)) {
LOG.warn("Error creating view volume of replay {}: {}", instanceId, rr.getErrorMsg());
throw new StorageCenterAPIException(rr.getErrorMsg());
}
return gson.fromJson(rr.getResult(), ScVolume.class);
}
/**
* Finds a view volume for a given replay.
*
* @param instanceId The replay instance ID.
* @return The volume or null if not found.
*/
public ScVolume findReplayView(String instanceId) {
PayloadFilter filter = new PayloadFilter();
filter.append("notes", instanceId);
RestResult rr = restClient.post("StorageCenter/ScVolumeConfiguration/GetList", filter.toJson());
if (checkResults(rr)) {
ScVolumeConfiguration[] config = gson.fromJson(rr.getResult(), ScVolumeConfiguration[].class);
rr = restClient.get(
String.format("StorageCenter/ScVolume/%s", config[0].volume.instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScVolume.class);
} else {
LOG.warn(rr.getErrorMsg());
}
} else {
LOG.warn(rr.getErrorMsg());
}
return null;
}
/**
* Expire a replay.
*
* @param instanceId The replay instance ID.
* @throws StorageCenterAPIException
*/
public void expireReplay(String instanceId) throws StorageCenterAPIException {
Parameters params = new Parameters();
RestResult rr = restClient.post(
String.format("StorageCenter/ScReplay/%s/Expire", instanceId), params.toJson());
if (!checkResults(rr)) {
String msg = String.format("Error expiring replay %s: %s", instanceId, rr.getErrorMsg());
LOG.warn(msg);
throw new StorageCenterAPIException(msg);
}
}
/**
* Gets all copy, mirror, and migrate operations for a volume.
*
* @param instanceId The volume instance ID.
* @return The CMM operations.
*/
public ScCopyMirrorMigrate[] getVolumeCopyMirrorMigrate(String instanceId) {
RestResult rr = restClient.get(String.format("StorageCenter/ScVolume/%s/CopyMirrorMigrateList", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScCopyMirrorMigrate[].class);
}
return new ScCopyMirrorMigrate[0];
}
/**
* Adds a volume to a consistency group.
*
* @param instanceId The volume instance ID.
* @param cgID The consistency group ID.
* @throws StorageCenterAPIException
*/
public void addVolumeToConsistencyGroup(String instanceId, String cgID) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScVolumeConfiguration/%s", instanceId));
if (!checkResults(rr)) {
throw new StorageCenterAPIException(String.format("Error getting volume configuration: %s", rr.getErrorMsg()));
}
ScVolumeConfiguration volConfig = gson.fromJson(rr.getResult(), ScVolumeConfiguration.class);
List<String> profiles = new ArrayList<>();
for (ScObject profile : volConfig.replayProfileList) {
if (!cgID.equals(profile.instanceId)) {
profiles.add(profile.instanceId);
}
}
profiles.add(cgID);
Parameters params = new Parameters();
params.add("ReplayProfileList", profiles.toArray(new String[0]));
rr = restClient.put(String.format("StorageCenter/ScVolumeConfiguration/%s", instanceId), params.toJson());
if (!checkResults(rr)) {
throw new StorageCenterAPIException(String.format("Error updating volume replay profile membership: %s", rr.getErrorMsg()));
}
}
/**
* Removes a volume from a consistency group.
*
* @param instanceId The volume instance ID.
* @param cgID The consistency group ID.
* @throws StorageCenterAPIException
*/
public void removeVolumeFromConsistencyGroup(String instanceId, String cgID) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScVolumeConfiguration/%s", instanceId));
if (!checkResults(rr)) {
throw new StorageCenterAPIException(String.format("Error getting volume configuration: %s", rr.getErrorMsg()));
}
ScVolumeConfiguration volConfig = gson.fromJson(rr.getResult(), ScVolumeConfiguration.class);
List<String> profiles = new ArrayList<>();
for (ScObject profile : volConfig.replayProfileList) {
if (!cgID.equals(profile.instanceId)) {
profiles.add(profile.instanceId);
}
}
Parameters params = new Parameters();
params.add("ReplayProfileList", profiles.toArray(new String[0]));
rr = restClient.put(String.format("StorageCenter/ScVolumeConfiguration/%s", instanceId), params.toJson());
if (!checkResults(rr)) {
throw new StorageCenterAPIException(String.format("Error updating volume replay profile membership: %s", rr.getErrorMsg()));
}
}
/**
* Gets a replay profile with the given ID.
*
* @param instanceId The replay profile instance ID.
* @return The replay profile.
* @throws StorageCenterAPIException
*/
public ScReplayProfile getConsistencyGroup(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScReplayProfile/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScReplayProfile.class);
}
String message = String.format("Error getting replay profile : %s", rr.getErrorMsg());
throw new StorageCenterAPIException(message);
}
/**
* Gets existing mapping profiles between a server and volume.
*
* @param volInstanceId The volume instance ID.
* @param serverInstanceId The server instance ID.
* @return The mapping profiles between the two.
*/
public ScMappingProfile[] getServerVolumeMapping(String volInstanceId, String serverInstanceId) {
PayloadFilter filter = new PayloadFilter();
filter.append("volume", volInstanceId);
filter.append("server", serverInstanceId);
RestResult rr = restClient.post("StorageCenter/ScMappingProfile/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScMappingProfile[].class);
}
return new ScMappingProfile[0];
}
/**
* Maps a volume to a server.
*
* @param volInstanceId The volume instance ID.
* @param serverInstanceId The server instance ID.
* @param preferredLun The preferred LUN to use or -1 to let the system decided.
* @param preferredPorts The preferred server HBA ports to use.
* @param maxPathCount The maximum paths to map or -1 for no restriction.
* @param preferredController The preferred controller to map through or null.
* @return The created mapping profile.
* @throws StorageCenterAPIException
*/
public ScMappingProfile createVolumeMappingProfile(
String volInstanceId, String serverInstanceId, int preferredLun,
String[] preferredPorts, int maxPathCount, String preferredController)
throws StorageCenterAPIException {
Parameters advancedParams = new Parameters();
advancedParams.add("MapToDownServerHbas", true);
if (preferredLun != -1) {
advancedParams.add("PreferredLun", preferredLun);
}
if (preferredPorts != null && preferredPorts.length > 0) {
advancedParams.add("PreferredServerHbaList", preferredPorts);
}
if (maxPathCount > 0) {
advancedParams.add("MaximumPathCount", maxPathCount);
}
if (preferredController != null) {
advancedParams.add("PreferredController", preferredController);
}
Parameters params = new Parameters();
params.add("server", serverInstanceId);
params.add("Advanced", advancedParams.getRawPayload());
RestResult rr = restClient.post(
String.format("StorageCenter/ScVolume/%s/MapToServer", volInstanceId), params.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScMappingProfile.class);
}
throw new StorageCenterAPIException(
String.format("Error creating volume mapping: %s", rr.getErrorMsg()));
}
/**
* Gets the individual maps created for a mapping profile.
*
* @param instanceId The mapping profile instance ID.
* @return The mappings.
* @throws StorageCenterAPIException
*/
public ScMapping[] getMappingProfileMaps(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(
String.format("StorageCenter/ScMappingProfile/%s/MappingList", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScMapping[].class);
}
String message = String.format("Error getting volume maps: %s", rr.getErrorMsg());
throw new StorageCenterAPIException(message);
}
/**
* Gets the individual maps for a volume.
*
* @param instanceId The volume instance ID.
* @return The mappings.
* @throws StorageCenterAPIException
*/
public ScMapping[] getVolumeMaps(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(
String.format("StorageCenter/ScVolume/%s/MappingList", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScMapping[].class);
}
String message = String.format("Error getting volume maps: %s", rr.getErrorMsg());
throw new StorageCenterAPIException(message);
}
/**
* Gets a controller port.
*
* @param instanceId The controller port ID.
* @return The controller port.
* @throws StorageCenterAPIException
*/
public ScControllerPort getControllerPort(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(
String.format("/StorageCenter/ScControllerPort/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScControllerPort.class);
}
String error = String.format("Error getting controller port %s: %s", instanceId, rr.getErrorMsg());
throw new StorageCenterAPIException(error);
}
/**
* Add an HBA to a server definition.
*
* @param instanceId The server instance ID.
* @param iqnOrWwn The IQN or WWN to add.
* @param isIscsi Whether it is iSCSI or FC.
* @return The added ScServerHba.
*/
public ScServerHba addHbaToServer(String instanceId, String iqnOrWwn, boolean isIscsi) {
Parameters params = new Parameters();
params.add("HbaPortType", isIscsi ? "Iscsi" : "FibreChannel");
params.add("WwnOrIscsiName", iqnOrWwn);
params.add("AllowManual", true);
RestResult rr = restClient.post(
String.format("StorageCenter/ScPhysicalServer/%s/AddHba", instanceId), params.toJson());
if (!checkResults(rr)) {
LOG.warn("Error adding HBA to server {}", rr.getErrorMsg());
return null;
}
return gson.fromJson(rr.getResult(), ScServerHba.class);
}
/**
* Create a new server definition.
*
* @param ssn The Storage Center system serial number.
* @param hostName The host name.
* @param isIscsi Whether it is iSCSI or FC.
* @param osId The OS instance ID.
* @return The created server.
* @throws StorageCenterAPIException
*/
public ScPhysicalServer createServer(String ssn, String hostName, boolean isIscsi, String osId)
throws StorageCenterAPIException {
Parameters params = new Parameters();
params.add("Name", hostName);
params.add("StorageCenter", ssn);
params.add("Notes", NOTES_STRING);
params.add("OperatingSystem", osId);
RestResult rr = restClient.post("StorageCenter/ScPhysicalServer", params.toJson());
if (!checkResults(rr)) {
String error = String.format("Error creating server '%s': %s", hostName, rr.getErrorMsg());
throw new StorageCenterAPIException(error);
}
return gson.fromJson(rr.getResult(), ScPhysicalServer.class);
}
/**
* Creates a new cluster server definition.
*
* @param ssn The Storage Center serial number.
* @param clusterName The cluster name.
* @param osId The OS instance ID.
* @return The created server.
* @throws StorageCenterAPIException
*/
public ScServer createClusterServer(String ssn, String clusterName, String osId) throws StorageCenterAPIException {
Parameters params = new Parameters();
params.add("Name", clusterName);
params.add("StorageCenter", ssn);
params.add("Notes", NOTES_STRING);
params.add("OperatingSystem", osId);
RestResult rr = restClient.post("StorageCenter/ScServerCluster", params.toJson());
if (!checkResults(rr)) {
String error = String.format("Error creating cluster server '%s': %s", clusterName, rr.getErrorMsg());
throw new StorageCenterAPIException(error);
}
return gson.fromJson(rr.getResult(), ScServer.class);
}
/**
* Move a server under a cluster.
*
* @param serverId The server instance ID.
* @param clusterId The cluster instance ID.
* @return True if successful, false otherwise.
*/
public boolean setAddServerToCluster(String serverId, String clusterId) {
Parameters params = new Parameters();
params.add("Parent", clusterId);
RestResult rr = restClient.post(
String.format("StorageCenter/ScPhysicalServer/%s/AddToCluster", serverId), params.toJson());
return checkResults(rr);
}
/**
* Gets the OS types.
*
* @param ssn The Storage Center system serial number.
* @param product The product to filter on or null.
* @return The OS types.
*/
public ScServerOperatingSystem[] getServerOperatingSystems(String ssn, String product) {
PayloadFilter filter = new PayloadFilter();
filter.append("scSerialNumber", ssn);
if (product != null && !product.isEmpty()) {
filter.append("product", product, ValueFilterType.INCLUDESSTRING);
}
RestResult rr = restClient.post("StorageCenter/ScServerOperatingSystem/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScServerOperatingSystem[].class);
}
return new ScServerOperatingSystem[0];
}
/**
* Finds mapping between a server and volume.
*
* @param serverId The server instance ID.
* @param volumeId The volume instance ID.
* @return The mapping profiles.
* @throws StorageCenterAPIException
*/
public ScMappingProfile[] findMappingProfiles(String serverId, String volumeId) throws StorageCenterAPIException {
PayloadFilter filter = new PayloadFilter();
filter.append("server", serverId);
filter.append("volume", volumeId);
RestResult rr = restClient.post("StorageCenter/ScMappingProfile/GetList", filter.toJson());
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScMappingProfile[].class);
}
String message = String.format("Error getting mapping profiles from server %s and volume %s: %s",
serverId, volumeId, rr.getErrorMsg());
throw new StorageCenterAPIException(message);
}
/**
* Deletes a mapping profile.
*
* @param instanceId The mapping profile instance ID.
* @throws StorageCenterAPIException
*/
public void deleteMappingProfile(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.delete(String.format("StorageCenter/ScMappingProfile/%s", instanceId));
if (!checkResults(rr)) {
throw new StorageCenterAPIException(rr.getErrorMsg());
}
}
/**
* Creates a volume from a snapshot.
*
* @param name The name for the new volume.
* @param instanceId The replay instance ID.
* @return The created volume.
* @throws StorageCenterAPIException
*/
public ScVolume createViewVolume(String name, String instanceId) throws StorageCenterAPIException {
LOG.debug("Creating view volume of replay {}", instanceId);
String errorMessage = "";
Parameters params = new Parameters();
params.add("Name", name);
params.add("Notes", NOTES_STRING);
try {
RestResult result = restClient.post(
String.format("StorageCenter/ScReplay/%s/CreateView", instanceId),
params.toJson());
if (checkResults(result)) {
return gson.fromJson(result.getResult(), ScVolume.class);
}
} catch (Exception e) {
errorMessage = String.format("Error creating view volume: %s", e);
LOG.warn(errorMessage);
}
if (errorMessage.length() == 0) {
errorMessage = String.format("Unable to create view volume %s from replay %s", name, instanceId);
}
throw new StorageCenterAPIException(errorMessage);
}
/**
* Create a mirror from one volume to another.
*
* @param ssn The Storage Center to create the mirror.
* @param srcId The source volume ID.
* @param dstId The destination volume ID.
* @return The CMM operation.
* @throws StorageCenterAPIException
*/
public ScCopyMirrorMigrate createMirror(String ssn, String srcId, String dstId) throws StorageCenterAPIException {
Parameters params = new Parameters();
params.add("StorageCenter", ssn);
params.add("SourceVolume", srcId);
params.add("DestinationVolume", dstId);
params.add("CopyReplays", false);
RestResult rr = restClient.post("StorageCenter/ScCopyMirrorMigrate/Mirror", params.toJson());
if (!checkResults(rr)) {
String msg = String.format("Error creating mirror from %s to %s: %s", srcId, dstId, rr.getErrorMsg());
LOG.warn(msg);
throw new StorageCenterAPIException(msg);
}
return gson.fromJson(rr.getResult(), ScCopyMirrorMigrate.class);
}
/**
* Gets a mirror operation.
*
* @param instanceId The CMM instance ID.
* @return The CMM operation.
* @throws StorageCenterAPIException
*/
public ScCopyMirrorMigrate getMirror(String instanceId) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScCopyMirrorMigrate/%s", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScCopyMirrorMigrate.class);
}
String message = String.format("Error getting mirror operation: %s", rr.getErrorMsg());
throw new StorageCenterAPIException(message);
}
/**
* Delete a mirror operation.
*
* @param instanceId The CMM instance ID.
* @throws StorageCenterAPIException
*/
public void deleteMirror(String instanceId) throws StorageCenterAPIException {
LOG.debug("Deleting mirror '{}'", instanceId);
RestResult rr = restClient.delete(String.format("StorageCenter/ScCopyMirrorMigrate/%s", instanceId));
if (!checkResults(rr)) {
String msg = String.format("Error deleting mirror %s: %s", instanceId, rr.getErrorMsg());
LOG.error(msg);
throw new StorageCenterAPIException(msg);
}
}
/**
* Gets some basic DSM (EM) information.
*
* @return The EmDataCollector information.
*/
public EmDataCollector getDSMInfo() {
RestResult rr = restClient.get("EnterpriseManager/EmDataCollector");
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), EmDataCollector.class);
}
return null;
}
/**
* Gets API connection information.
*
* @return The API connection info.
* @throws StorageCenterAPIException
*/
public ApiConnection getApiConnection() throws StorageCenterAPIException {
RestResult rr = restClient.get("ApiConnection/ApiConnection");
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ApiConnection.class);
}
throw new StorageCenterAPIException("Failed to get API connection");
}
/**
* Workaround for tests. Will make sure a volume is active somewhere so
* snapshots and other operations can be performed on it.
*
* @param volumeId The instance ID of the volume.
*/
public void checkAndInitVolume(String volumeId) {
ScVolume volume = getVolume(volumeId);
if (volume == null) {
// Just let the calling method handle the subsequent failure
return;
}
if (volume.active && volume.replayAllowed) {
// It's all good
return;
}
ScServer[] servers = getServerDefinitions(volume.scSerialNumber);
for (ScServer server : servers) {
if (!"down".equalsIgnoreCase(server.status)) {
try {
ScMappingProfile mapping = createVolumeMappingProfile(
volume.instanceId, server.instanceId, -1, new String[0], -1, null);
deleteMappingProfile(mapping.instanceId);
break;
} catch (StorageCenterAPIException e) {
LOG.debug("Failed to activate volume to server {}.", server.instanceId);
}
}
}
}
/**
* Gets a volume's configuration.
*
* @param instanceId The volume instance ID.
* @return The volume config.
*/
public ScVolumeConfiguration getVolumeConfig(String instanceId) {
RestResult rr = restClient.get(
String.format("StorageCenter/ScVolume/%s/VolumeConfiguration", instanceId));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScVolumeConfiguration.class);
}
LOG.warn(String.format("Error getting volume configuration: %s", rr.getErrorMsg()));
return null;
}
/**
* Gets SC configuration settings.
*
* @param ssn The system serial number.
* @return The configuration settings.
* @throws StorageCenterAPIException
*/
public ScConfiguration getScConfig(String ssn) throws StorageCenterAPIException {
RestResult rr = restClient.get(String.format("StorageCenter/ScConfiguration/%s", ssn));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScConfiguration.class);
}
String message = String.format("Error getting Storage Center configuration: %s", rr.getErrorMsg());
throw new StorageCenterAPIException(message);
}
/**
* Gets all fault domains for a system.
*
* @param ssn The system serial number.
* @return The fault domains.
*/
public ScFaultDomain[] getFaultDomains(String ssn) {
RestResult rr = restClient.get(String.format("StorageCenter/StorageCenter/%s/FaultDomainList", ssn));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScFaultDomain[].class);
}
LOG.warn(String.format("Error getting Storage Center configuration: %s", rr.getErrorMsg()));
return new ScFaultDomain[0];
}
/**
* Gets the virtual ports in a fault domain.
*
* @param instanceId The fault domain instance ID.
* @param True to get virtual ports, false to get physical.
* @return The controller ports.
*/
public ScControllerPort[] getFaultDomainPorts(String instanceId, boolean virtualPorts) {
String portType = virtualPorts ? "Virtual" : "Physical";
RestResult rr = restClient.get(
String.format("StorageCenter/ScFaultDomain/%s/%sPortList", instanceId, portType));
if (checkResults(rr)) {
return gson.fromJson(rr.getResult(), ScControllerPort[].class);
}
LOG.warn(String.format("Error getting controller ports for fault domain %s: %s", instanceId, rr.getErrorMsg()));
return new ScControllerPort[0];
}
}