diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java index 6e7b37d9378f..051e4f7ee519 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java @@ -43,17 +43,17 @@ public interface NASFeignClient { //File Operations @RequestMapping(method = RequestMethod.GET, value="/{volume.uuid}/files/{path}") - OntapResponse getFileResponse(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath); + OntapResponse getFileResponse(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid") String volumeUUID, + @PathVariable(name = "path") String filePath); @RequestMapping(method = RequestMethod.DELETE, value="/{volume.uuid}/files/{path}") - void deleteFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath); + void deleteFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid") String volumeUUID, + @PathVariable(name = "path") String filePath); @RequestMapping(method = RequestMethod.PATCH, value="/{volume.uuid}/files/{path}") - void updateFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo fileInfo); + void updateFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid") String volumeUUID, + @PathVariable(name = "path") String filePath, @RequestBody FileInfo fileInfo); @RequestMapping(method = RequestMethod.POST, value="/{volume.uuid}/files/{path}") - void createFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo file); + void createFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid") String volumeUUID, + @PathVariable(name = "path") String filePath, @RequestBody FileInfo file); @@ -68,12 +68,12 @@ ExportPolicy createExportPolicy(URI uri, @RequestHeader("Authorization") String OntapResponse getExportPolicyResponse(URI baseURL, @RequestHeader("Authorization") String header); @RequestMapping(method = RequestMethod.GET, value="/{id}") - OntapResponse getExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id); + OntapResponse getExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id") String id); @RequestMapping(method = RequestMethod.DELETE, value="/{id}") - void deleteExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id); + void deleteExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id") String id); @RequestMapping(method = RequestMethod.PATCH, value="/{id}") - OntapResponse updateExportPolicy(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id, + OntapResponse updateExportPolicy(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id") String id, @RequestBody ExportPolicy request); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java new file mode 100644 index 000000000000..325823f8515c --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java @@ -0,0 +1,91 @@ +/* + * 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.feign.client; + +import org.apache.cloudstack.storage.feign.model.Igroup; +import org.apache.cloudstack.storage.feign.model.Lun; +import org.apache.cloudstack.storage.feign.FeignConfiguration; +import org.apache.cloudstack.storage.feign.model.LunMap; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.net.URI; + +@Lazy +@FeignClient(name = "SANClient", url = "", configuration = FeignConfiguration.class ) +public interface SANFeignClient { + + //Lun Operation APIs + @RequestMapping(method = RequestMethod.POST) + OntapResponse createLun(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestHeader("return_records") boolean value, + @RequestBody Lun lun); + + //this method to get all luns and also filtered luns based on query params as a part of URL + @RequestMapping(method = RequestMethod.GET) + OntapResponse getLunResponse(URI baseURL, @RequestHeader("Authorization") String authHeader); + + @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") + Lun getLunByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid); + + @RequestMapping(method = RequestMethod.PATCH, value = "/{uuid}") + void updateLun(URI uri, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid, + @RequestBody Lun lun); + + @RequestMapping(method = RequestMethod.DELETE, value = "/{uuid}") + void deleteLun(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid); + + + //iGroup Operation APIs + + @RequestMapping(method = RequestMethod.POST) + OntapResponse createIgroup(URI uri, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value, + @RequestBody Igroup igroupRequest); + + //this method to get all igroups and also filtered igroups based on query params as a part of URL + @RequestMapping(method = RequestMethod.GET) + OntapResponse getIgroupResponse(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); + @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") + Igroup getIgroupByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); + @RequestMapping(method = RequestMethod.DELETE, value = "/{uuid}") + void deleteIgroup(URI baseUri, @RequestHeader("Authorization") String authHeader, @PathVariable(name = "uuid", required = true) String uuid); + + @RequestMapping(method = RequestMethod.POST, value = "/{uuid}/igroups") + OntapResponse addNestedIgroups(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid, + @RequestBody Igroup igroupNestedRequest, @RequestHeader(value="return_records", defaultValue = "true") boolean value); + + + //Lun Maps Operation APIs + + @RequestMapping(method = RequestMethod.POST) + OntapResponse createLunMap(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody LunMap lunMap); + + @RequestMapping(method = RequestMethod.GET) + OntapResponse getLunMapResponse(URI baseURL, @RequestHeader("Authorization") String authHeader); + + @RequestMapping(method = RequestMethod.GET, value = "/{lun.uuid}/{igroup.uuid}") + void deleteLunMap(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="lun.uuid", required=true) String uuid, + @PathVariable(name="igroup.uuid", required=true) String igroupUUID); + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java index 52ee30d71c8a..4370f48ff308 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java @@ -23,9 +23,7 @@ import org.apache.cloudstack.storage.feign.model.Svm; import org.apache.cloudstack.storage.feign.model.response.OntapResponse; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.*; import java.net.URI; @@ -34,9 +32,12 @@ public interface SvmFeignClient { //this method to get all svms and also filtered svms based on query params as a part of URL @RequestMapping(method = RequestMethod.GET) - OntapResponse getSvmResponse(URI baseURL, @RequestHeader("Authorization") String header); + OntapResponse getSvms(URI baseURL, @RequestHeader("Authorization") String header); @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") - Svm getSvmByUUID(URI baseURL, @RequestHeader("Authorization") String header); + Svm getSvmByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable("uuid") String uuid); + + @RequestMapping(method = RequestMethod.PATCH, value = "/{uuid}") + Svm updateSVM(URI baseURL, @RequestHeader("Authorization") String header, @RequestBody Svm svm); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java index fb3c9712d750..af92754da42e 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java @@ -30,21 +30,23 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMethod; +import java.net.URI; + @Lazy @FeignClient(name = "VolumeClient", url = "https://{clusterIP}/api/storage/volumes", configuration = FeignConfiguration.class) public interface VolumeFeignClient { @RequestMapping(method = RequestMethod.DELETE, value="/{uuid}") - void deleteVolume(@RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); + void deleteVolume(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); @RequestMapping(method = RequestMethod.POST) - JobResponse createVolumeWithJob(@RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest); + JobResponse createVolumeWithJob(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest); @RequestMapping(method = RequestMethod.GET, value="/{uuid}") - Volume getVolumeByUUID(@RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); + Volume getVolumeByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); @RequestMapping(method = RequestMethod.PATCH) - JobResponse updateVolumeRebalancing(@RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest); + JobResponse updateVolumeRebalancing(URI baseURL, @RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java index be78639844b3..ab0ed0b9e356 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java @@ -96,7 +96,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash( id, name, rules, svm); + return Objects.hash( id, name); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java index b67704435a9e..973e957ada63 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java @@ -255,7 +255,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(bytesUsed, creationTime, fillEnabled, isEmpty, isSnapshot, isVmAligned, modifiedTime, name, overwriteEnabled, path, size, target, type, uniqueBytes, unixPermissions); + return Objects.hash(name, path); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java new file mode 100644 index 000000000000..3ddde89f9184 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java @@ -0,0 +1,255 @@ +/* + * 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.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Igroup { + @JsonProperty("delete_on_unmap") + private Boolean deleteOnUnmap = null; + @JsonProperty("initiators") + private List initiators = null; + @JsonProperty("lun_maps") + private List lunMaps = null; + @JsonProperty("os_type") + private OsTypeEnum osType = null; + + @JsonProperty("parent_igroups") + private List parentIgroups = null; + + @JsonProperty("igroups") + private List igroups = null; + + @JsonProperty("name") + private String name = null; + + @JsonProperty("protocol") + private ProtocolEnum protocol = ProtocolEnum.mixed; + @JsonProperty("svm") + private Svm svm = null; + @JsonProperty("uuid") + private String uuid = null; + + public enum OsTypeEnum { + hyper_v("hyper_v"), + + linux("linux"), + + vmware("vmware"), + + windows("windows"), + + xen("xen"); + + private String value; + + OsTypeEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static OsTypeEnum fromValue(String text) { + for (OsTypeEnum b : OsTypeEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + } + + public List getParentIgroups() { + return parentIgroups; + } + + public void setParentIgroups(List parentIgroups) { + this.parentIgroups = parentIgroups; + } + + public Igroup igroups(List igroups) { + this.igroups = igroups; + return this; + } + + public List getIgroups() { + return igroups; + } + + public void setIgroups(List igroups) { + this.igroups = igroups; + } + + public enum ProtocolEnum { + iscsi("iscsi"), + + mixed("mixed"); + + private String value; + + ProtocolEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static ProtocolEnum fromValue(String text) { + for (ProtocolEnum b : ProtocolEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + } + public Igroup deleteOnUnmap(Boolean deleteOnUnmap) { + this.deleteOnUnmap = deleteOnUnmap; + return this; + } + + public Boolean isDeleteOnUnmap() { + return deleteOnUnmap; + } + + public void setDeleteOnUnmap(Boolean deleteOnUnmap) { + this.deleteOnUnmap = deleteOnUnmap; + } + + public Igroup initiators(List initiators) { + this.initiators = initiators; + return this; + } + public List getInitiators() { + return initiators; + } + + public void setInitiators(List initiators) { + this.initiators = initiators; + } + + public Igroup lunMaps(List lunMaps) { + this.lunMaps = lunMaps; + return this; + } + public List getLunMaps() { + return lunMaps; + } + + public void setLunMaps(List lunMaps) { + this.lunMaps = lunMaps; + } + + public Igroup name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public Igroup osType(OsTypeEnum osType) { + this.osType = osType; + return this; + } + public OsTypeEnum getOsType() { + return osType; + } + + public void setOsType(OsTypeEnum osType) { + this.osType = osType; + } + + public Igroup protocol(ProtocolEnum protocol) { + this.protocol = protocol; + return this; + } + + public ProtocolEnum getProtocol() { + return protocol; + } + + public void setProtocol(ProtocolEnum protocol) { + this.protocol = protocol; + } + + public Igroup svm(Svm svm) { + this.svm = svm; + return this; + } + public Svm getSvm() { + return svm; + } + + public void setSvm(Svm svm) { + this.svm = svm; + } + + public String getUuid() { + return uuid; + } + + @Override + public int hashCode() { + return Objects.hash(name, uuid); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Igroup other = (Igroup) obj; + return Objects.equals(name, other.name) && Objects.equals(uuid, other.uuid); + } + + @Override + public String toString() { + return "Igroup [deleteOnUnmap=" + deleteOnUnmap + ", initiators=" + initiators + ", lunMaps=" + lunMaps + + ", name=" + name + ", replication=" + ", osType=" + osType + ", parentIgroups=" + + parentIgroups + ", igroups=" + igroups + ", protocol=" + protocol + ", svm=" + svm + ", uuid=" + uuid + + ", portset=" + "]"; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java new file mode 100644 index 000000000000..dc290bdc2fc9 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java @@ -0,0 +1,36 @@ +/* + * 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.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Initiator { + @JsonProperty("name") + private String name = null; + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java new file mode 100644 index 000000000000..d8f66106dd5c --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java @@ -0,0 +1,296 @@ +/* + * 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.feign.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.List; +import java.util.Objects; + +/** + * A LUN is the logical representation of storage in a storage area network (SAN).<br/> In ONTAP, a LUN is located within a volume. Optionally, it can be located within a qtree in a volume.<br/> A LUN can be created to a specified size using thin or thick provisioning. A LUN can then be renamed, resized, cloned, and moved to a different volume. LUNs support the assignment of a quality of service (QoS) policy for performance management or a QoS policy can be assigned to the volume containing the LUN. See the LUN object model to learn more about each of the properties supported by the LUN REST API.<br/> A LUN must be mapped to an initiator group to grant access to the initiator group's initiators (client hosts). Initiators can then access the LUN and perform I/O over a Fibre Channel (FC) fabric using the Fibre Channel Protocol or a TCP/IP network using iSCSI. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Lun { + + @JsonProperty("auto_delete") + private Boolean autoDelete = null; + + /** + * The class of LUN.<br/> Optional in POST. + */ + public enum PropertyClassEnum { + REGULAR("regular"); + + private String value; + + PropertyClassEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static PropertyClassEnum fromValue(String value) { + for (PropertyClassEnum b : PropertyClassEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + return null; + } + } + + @JsonProperty("class") + private PropertyClassEnum propertyClass = null; + + @JsonProperty("enabled") + private Boolean enabled = null; + + @JsonProperty("lun_maps") + private List lunMaps = null; + + @JsonProperty("name") + private String name = null; + + /** + * The operating system type of the LUN.<br/> Required in POST when creating a LUN that is not a clone of another. Disallowed in POST when creating a LUN clone. + */ + public enum OsTypeEnum { + HYPER_V("hyper_v"), + + LINUX("linux"), + + VMWARE("vmware"), + + WINDOWS("windows"), + + XEN("xen"); + + private String value; + + OsTypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static OsTypeEnum fromValue(String value) { + for (OsTypeEnum b : OsTypeEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + return null; + } + } + + @JsonProperty("os_type") + private OsTypeEnum osType = null; + + @JsonProperty("serial_number") + private String serialNumber = null; + + @JsonProperty("space") + private LunSpace space = null; + + @JsonProperty("svm") + private Svm svm = null; + + @JsonProperty("uuid") + private String uuid = null; + + public Lun autoDelete(Boolean autoDelete) { + this.autoDelete = autoDelete; + return this; + } + + public Boolean isAutoDelete() { + return autoDelete; + } + + public void setAutoDelete(Boolean autoDelete) { + this.autoDelete = autoDelete; + } + + public Lun propertyClass(PropertyClassEnum propertyClass) { + this.propertyClass = propertyClass; + return this; + } + + public PropertyClassEnum getPropertyClass() { + return propertyClass; + } + + public void setPropertyClass(PropertyClassEnum propertyClass) { + this.propertyClass = propertyClass; + } + + public Lun enabled(Boolean enabled) { + this.enabled = enabled; + return this; + } + + public Boolean isEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public List getLunMaps() { + return lunMaps; + } + + public void setLunMaps(List lunMaps) { + this.lunMaps = lunMaps; + } + + public Lun name(String name) { + this.name = name; + return this; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Lun osType(OsTypeEnum osType) { + this.osType = osType; + return this; + } + + public OsTypeEnum getOsType() { + return osType; + } + + public void setOsType(OsTypeEnum osType) { + this.osType = osType; + } + + public String getSerialNumber() { + return serialNumber; + } + + public Lun space(LunSpace space) { + this.space = space; + return this; + } + + public LunSpace getSpace() { + return space; + } + + public void setSpace(LunSpace space) { + this.space = space; + } + + public Lun svm(Svm svm) { + this.svm = svm; + return this; + } + + public Svm getSvm() { + return svm; + } + + public void setSvm(Svm svm) { + this.svm = svm; + } + + public String getUuid() { + return uuid; + } + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Lun lun = (Lun) o; + return Objects.equals(this.name, lun.name) && Objects.equals(this.uuid, lun.uuid); + } + + @Override + public int hashCode() { + return Objects.hash(name, uuid); + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class Lun {\n"); + sb.append(" autoDelete: ").append(toIndentedString(autoDelete)).append("\n"); + sb.append(" propertyClass: ").append(toIndentedString(propertyClass)).append("\n"); + sb.append(" enabled: ").append(toIndentedString(enabled)).append("\n"); + sb.append(" lunMaps: ").append(toIndentedString(lunMaps)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" osType: ").append(toIndentedString(osType)).append("\n"); + sb.append(" serialNumber: ").append(toIndentedString(serialNumber)).append("\n"); + sb.append(" space: ").append(toIndentedString(space)).append("\n"); + sb.append(" svm: ").append(toIndentedString(svm)).append("\n"); + sb.append(" uuid: ").append(toIndentedString(uuid)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java new file mode 100644 index 000000000000..4804a71e406d --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java @@ -0,0 +1,109 @@ +/* + * 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.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LunMap { + @JsonProperty("igroup") + private Igroup igroup = null; + @JsonProperty("logical_unit_number") + private Integer logicalUnitNumber = null; + @JsonProperty("lun") + private Lun lun = null; + @JsonProperty("svm") + @SerializedName("svm") + private Svm svm = null; + + public LunMap igroup (Igroup igroup) { + this.igroup = igroup; + return this; + } + + public Igroup getIgroup () { + return igroup; + } + + public void setIgroup (Igroup igroup) { + this.igroup = igroup; + } + + public LunMap logicalUnitNumber (Integer logicalUnitNumber) { + this.logicalUnitNumber = logicalUnitNumber; + return this; + } + + public Integer getLogicalUnitNumber () { + return logicalUnitNumber; + } + + public void setLogicalUnitNumber (Integer logicalUnitNumber) { + this.logicalUnitNumber = logicalUnitNumber; + } + + public LunMap lun (Lun lun) { + this.lun = lun; + return this; + } + + public Lun getLun () { + return lun; + } + + public void setLun (Lun lun) { + this.lun = lun; + } + + public LunMap svm (Svm svm) { + this.svm = svm; + return this; + } + + public Svm getSvm () { + return svm; + } + + public void setSvm (Svm svm) { + this.svm = svm; + } + + @Override + public String toString () { + StringBuilder sb = new StringBuilder(); + sb.append("class LunMap {\n"); + sb.append(" igroup: ").append(toIndentedString(igroup)).append("\n"); + sb.append(" logicalUnitNumber: ").append(toIndentedString(logicalUnitNumber)).append("\n"); + sb.append(" lun: ").append(toIndentedString(lun)).append("\n"); + sb.append(" svm: ").append(toIndentedString(svm)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + private String toIndentedString (Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java new file mode 100644 index 000000000000..d0956cd5366f --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java @@ -0,0 +1,95 @@ +/* + * 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.feign.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The storage space related properties of the LUN. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LunSpace { + + @JsonProperty("scsi_thin_provisioning_support_enabled") + private Boolean scsiThinProvisioningSupportEnabled = null; + + @JsonProperty("size") + private Long size = null; + + @JsonProperty("used") + private Long used = null; + @JsonProperty("physical_used") + private Long physicalUsed = null; + + public LunSpace scsiThinProvisioningSupportEnabled(Boolean scsiThinProvisioningSupportEnabled) { + this.scsiThinProvisioningSupportEnabled = scsiThinProvisioningSupportEnabled; + return this; + } + + public Boolean isScsiThinProvisioningSupportEnabled() { + return scsiThinProvisioningSupportEnabled; + } + + public void setScsiThinProvisioningSupportEnabled(Boolean scsiThinProvisioningSupportEnabled) { + this.scsiThinProvisioningSupportEnabled = scsiThinProvisioningSupportEnabled; + } + + public LunSpace size(Long size) { + this.size = size; + return this; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public Long getUsed() { + return used; + } + + public Long getPhysicalUsed() { + return physicalUsed; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class LunSpace {\n"); + sb.append(" scsiThinProvisioningSupportEnabled: ").append(toIndentedString(scsiThinProvisioningSupportEnabled)).append("\n"); + sb.append(" size: ").append(toIndentedString(size)).append("\n"); + sb.append(" used: ").append(toIndentedString(used)).append("\n"); + sb.append(" physicalUsed: ").append(toIndentedString(physicalUsed)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java new file mode 100644 index 000000000000..d02112422f60 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java @@ -0,0 +1,86 @@ +/* + * 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.feign.model; + +public class OntapStorage { + public static String Username; + public static String Password; + public static String ManagementLIF; + public static String Svm; + public static String Protocol; + public static Boolean IsDisaggregated; + + public OntapStorage(String username, String password, String managementLIF, String svm, String protocol, Boolean isDisaggregated) { + Username = username; + Password = password; + ManagementLIF = managementLIF; + Svm = svm; + Protocol = protocol; + IsDisaggregated = isDisaggregated; + } + + public String getUsername() { + return Username; + } + + public void setUsername(String username) { + Username = username; + } + + public String getPassword() { + return Password; + } + + public void setPassword(String password) { + Password = password; + } + + public String getManagementLIF() { + return ManagementLIF; + } + + public void setManagementLIF(String managementLIF) { + ManagementLIF = managementLIF; + } + + public String getSVM() { + return Svm; + } + + public void setSVM(String svm) { + Svm = svm; + } + + public String getProtocol() { + return Protocol; + } + + public void setProtocol(String protocol) { + Protocol = protocol; + } + + public Boolean getIsDisaggregated() { + return IsDisaggregated; + } + + public void setIsDisaggregated(Boolean isDisaggregated) { + IsDisaggregated = isDisaggregated; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java index 933c1ec0bdf4..b78f41e7df3b 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java @@ -24,7 +24,7 @@ import java.util.List; /** - * OnTapResponse + * OntapResponse */ @JsonInclude(JsonInclude.Include.NON_NULL) public class OntapResponse { @@ -34,11 +34,11 @@ public class OntapResponse { @JsonProperty("records") private List records; - public OntapResponse () { + public OntapResponse() { // Default constructor } - public OntapResponse (List records) { + public OntapResponse(List records) { this.records = records; this.numRecords = (records != null) ? records.size() : 0; } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 2aad3a3e8029..ce057f6cee4c 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -21,19 +21,42 @@ import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.host.HostVO; import com.cloud.hypervisor.Hypervisor; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.common.base.Preconditions; import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters; import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.lifecycle.BasePrimaryDataStoreLifeCycleImpl; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.apache.cloudstack.storage.provider.StorageProviderFactory; +import org.apache.cloudstack.storage.service.StorageStrategy; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Map; -public class OntapPrimaryDatastoreLifecycle implements PrimaryDataStoreLifeCycle { +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.UUID; +public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { + @Inject private ClusterDao _clusterDao; + @Inject private StorageManager _storageMgr; + @Inject private ResourceManager _resourceMgr; + @Inject private PrimaryDataStoreHelper _dataStoreHelper; private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class); /** @@ -43,14 +66,127 @@ public class OntapPrimaryDatastoreLifecycle implements PrimaryDataStoreLifeCycle */ @Override public DataStore initialize(Map dsInfos) { + s_logger.info("initialize {}", dsInfos); + if (dsInfos == null) { + throw new CloudRuntimeException("Datastore info map is null, cannot create primary storage"); + } + String url = dsInfos.get("url").toString(); // TODO: Decide on whether should the customer enter just the Management LIF IP or https://ManagementLIF + Long zoneId = (Long) dsInfos.get("zoneId"); + Long podId = (Long)dsInfos.get("podId"); + Long clusterId = (Long)dsInfos.get("clusterId"); + String storagePoolName = dsInfos.get("name").toString(); + String providerName = dsInfos.get("providerName").toString(); + String tags = dsInfos.get("tags").toString(); + Boolean isTagARule = (Boolean) dsInfos.get("isTagARule"); + String scheme = dsInfos.get("scheme").toString(); + + s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + + ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme); + + // Additional details requested for ONTAP primary storage pool creation + @SuppressWarnings("unchecked") + Map details = (Map)dsInfos.get("details"); + // Validations + if (podId != null && clusterId == null) { + s_logger.error("Cluster Id is null, cannot create primary storage"); + return null; + } else if (podId == null && clusterId != null) { + s_logger.error("Pod Id is null, cannot create primary storage"); + return null; + } + + if (podId == null) { + if (zoneId != null) { + s_logger.info("Both Pod Id and Cluster Id are null, Primary storage pool will be associated with a Zone"); + } else { + throw new CloudRuntimeException("Pod Id, Cluster Id and Zone Id are all null, cannot create primary storage"); + } + } + + if (storagePoolName == null || storagePoolName.isEmpty()) { + throw new CloudRuntimeException("Storage pool name is null or empty, cannot create primary storage"); + } + + if (providerName == null || providerName.isEmpty()) { + throw new CloudRuntimeException("Provider name is null or empty, cannot create primary storage"); + } + + PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters(); + if (clusterId != null) { + ClusterVO clusterVO = _clusterDao.findById(clusterId); + Preconditions.checkNotNull(clusterVO, "Unable to locate the specified cluster"); + if (clusterVO.getHypervisorType() != Hypervisor.HypervisorType.KVM) { + throw new CloudRuntimeException("ONTAP primary storage does not support" + clusterVO.getHypervisorType() + "hypervisor currently"); + } + parameters.setHypervisorType(clusterVO.getHypervisorType()); + } + + // TODO: While testing need to check what does this actually do and if the fields corresponding to each protocol should also be set + // TODO: scheme could be 'custom' in our case and we might have to ask 'protocol' separately to the user + String protocol = details.get(Constants.PROTOCOL); + switch (protocol.toLowerCase()) { + case Constants.NFS: + parameters.setType(Storage.StoragePoolType.NetworkFilesystem); + break; + case Constants.ISCSI: + parameters.setType(Storage.StoragePoolType.Iscsi); + break; + default: + throw new CloudRuntimeException("Unsupported protocol: " + scheme + ", cannot create primary storage"); + } - return null; + details.put(Constants.MANAGEMENTLIF, url); + // Validate the ONTAP details + if(details.get(Constants.ISDISAGGREGATED) == null || details.get(Constants.ISDISAGGREGATED).isEmpty()) { + details.put(Constants.ISDISAGGREGATED, "false"); + } + + OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD), + details.get(Constants.MANAGEMENTLIF), details.get(Constants.SVMNAME), details.get(Constants.PROTOCOL), + Boolean.parseBoolean(details.get(Constants.ISDISAGGREGATED))); + StorageStrategy storageStrategy = StorageProviderFactory.createStrategy(ontapStorage); + boolean isValid = storageStrategy.connect(); + if (isValid) { +// String volumeName = storagePoolName + "_vol"; //TODO: Figure out a better naming convention + storageStrategy.createVolume(storagePoolName, Long.parseLong((details.get("size")))); // TODO: size should be in bytes, so see if conversion is needed + } else { + throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage"); + } + + String storagePath = url + ":/" + storagePoolName; + parameters.setTags(tags); + parameters.setIsTagARule(isTagARule); + parameters.setDetails(details); + parameters.setUuid(UUID.randomUUID().toString()); + parameters.setZoneId(zoneId); + parameters.setPodId(podId); + parameters.setClusterId(clusterId); + parameters.setName(storagePoolName); + parameters.setProviderName(providerName); + parameters.setManaged(true); + parameters.setPath(storagePath); + + return _dataStoreHelper.createPrimaryDataStore(parameters); } @Override - public boolean attachCluster(DataStore store, ClusterScope scope) { - return false; + public boolean attachCluster(DataStore dataStore, ClusterScope scope) { + logger.debug("In attachCluster for ONTAP primary storage"); + PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo)dataStore; + List hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInClusterForStorageConnection(primarystore); + + logger.debug(String.format("Attaching the pool to each of the hosts %s in the cluster: %s", hostsToConnect, primarystore.getClusterId())); + for (HostVO host : hostsToConnect) { + // TODO: Fetch the host IQN and add to the initiator group on ONTAP cluster + try { + _storageMgr.connectHostToSharedPool(host, dataStore.getId()); + } catch (Exception e) { + logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e); + } + } + _dataStoreHelper.attachCluster(dataStore); + return true; } @Override @@ -60,7 +196,20 @@ public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo exis @Override public boolean attachZone(DataStore dataStore, ZoneScope scope, Hypervisor.HypervisorType hypervisorType) { - return false; + logger.debug("In attachZone for ONTAP primary storage"); + List hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInZoneForStorageConnection(dataStore, scope.getScopeId(), Hypervisor.HypervisorType.KVM); + + logger.debug(String.format("In createPool. Attaching the pool to each of the hosts in %s.", hostsToConnect)); + for (HostVO host : hostsToConnect) { + // TODO: Fetch the host IQN and add to the initiator group on ONTAP cluster + try { + _storageMgr.connectHostToSharedPool(host, dataStore.getId()); + } catch (Exception e) { + logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e); + } + } + _dataStoreHelper.attachZone(dataStore); + return true; } @Override diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java new file mode 100644 index 000000000000..db598a0dc265 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java @@ -0,0 +1,57 @@ +/* + * 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.provider; + +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.apache.cloudstack.storage.service.StorageStrategy; +import org.apache.cloudstack.storage.service.UnifiedNASStrategy; +import org.apache.cloudstack.storage.service.UnifiedSANStrategy; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +public class StorageProviderFactory { + private static final Logger s_logger = (Logger) LogManager.getLogger(StorageProviderFactory.class); + + public static StorageStrategy createStrategy(OntapStorage ontapStorage) { + String protocol = ontapStorage.getProtocol(); + s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol); + switch (protocol.toLowerCase()) { + case Constants.NFS: + if(!ontapStorage.getIsDisaggregated()) { + return new UnifiedNASStrategy(ontapStorage); + } else { + throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + } + case Constants.ISCSI: + if (!ontapStorage.getIsDisaggregated()) { + return new UnifiedSANStrategy(ontapStorage); + } else { + throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + } + default: + + throw new CloudRuntimeException("Unsupported protocol: " + protocol); + } + } + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java new file mode 100644 index 000000000000..e4da5404eae4 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java @@ -0,0 +1,37 @@ +/* + * 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.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public abstract class NASStrategy extends StorageStrategy { + public NASStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + public abstract String createExportPolicy(String svmName, String policyName); + public abstract boolean deleteExportPolicy(String svmName, String policyName); + public abstract boolean exportPolicyExists(String svmName, String policyName); + + public abstract String addExportRule(String policyName, String clientMatch, String[] protocols, String[] roRule, String[] rwRule); + public abstract String assignExportPolicyToVolume(String volumeUuid, String policyName); + public abstract String enableNFS(String svmUuid); +} + diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java new file mode 100644 index 000000000000..4e6846ef7610 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java @@ -0,0 +1,33 @@ +/* + * 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.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public abstract class SANStrategy extends StorageStrategy { + public SANStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + public abstract String createLUN(String svmName, String volumeName, String lunName, long sizeBytes, String osType); + public abstract String createIgroup(String svmName, String igroupName, String[] initiators); + public abstract String mapLUNToIgroup(String lunName, String igroupName); + public abstract String enableISCSI(String svmUuid); +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java new file mode 100644 index 000000000000..8799a25d1569 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -0,0 +1,173 @@ +/* + * 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.service; + +import com.cloud.utils.exception.CloudRuntimeException; +import feign.FeignException; +import org.apache.cloudstack.storage.feign.client.JobFeignClient; +import org.apache.cloudstack.storage.feign.client.SvmFeignClient; +import org.apache.cloudstack.storage.feign.client.VolumeFeignClient; +import org.apache.cloudstack.storage.feign.model.Aggregate; +import org.apache.cloudstack.storage.feign.model.Job; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.apache.cloudstack.storage.feign.model.Svm; +import org.apache.cloudstack.storage.feign.model.Volume; +import org.apache.cloudstack.storage.feign.model.response.JobResponse; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.cloudstack.storage.utils.Utility; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.net.URI; +import java.util.List; +import java.util.Objects; + +public abstract class StorageStrategy { + @Inject + private Utility utils; + + @Inject + private VolumeFeignClient volumeFeignClient; + + @Inject + private SvmFeignClient svmFeignClient; + + @Inject + private JobFeignClient jobFeignClient; + + private final OntapStorage storage; + + private List aggregates; + + private static final Logger s_logger = (Logger) LogManager.getLogger(StorageStrategy.class); + + public StorageStrategy(OntapStorage ontapStorage) { + storage = ontapStorage; + } + + // Connect method to validate ONTAP cluster, credentials, protocol, and SVM + public boolean connect() { + s_logger.info("Attempting to connect to ONTAP cluster at " + storage.getManagementLIF()); + //Get AuthHeader + String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + try { + // Call the SVM API to check if the SVM exists + Svm svm = null; + URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GETSVMs); + OntapResponse svms = svmFeignClient.getSvms(url, authHeader); + for (Svm storageVM : svms.getRecords()) { + if (storageVM.getName().equals(storage.getSVM())) { + svm = storageVM; + s_logger.info("Found SVM: " + storage.getSVM()); + break; + } + } + + // Validations + if (svm == null) { + s_logger.error("SVM with name " + storage.getSVM() + " not found."); + throw new CloudRuntimeException("SVM with name " + storage.getSVM() + " not found."); + } else { + if (svm.getState() != Constants.RUNNING) { + s_logger.error("SVM " + storage.getSVM() + " is not in running state."); + throw new CloudRuntimeException("SVM " + storage.getSVM() + " is not in running state."); + } + if (Objects.equals(storage.getProtocol(), Constants.NFS) && !svm.getNfsEnabled()) { + s_logger.error("NFS protocol is not enabled on SVM " + storage.getSVM()); + throw new CloudRuntimeException("NFS protocol is not enabled on SVM " + storage.getSVM()); + } else if (Objects.equals(storage.getProtocol(), Constants.ISCSI) && !svm.getIscsiEnabled()) { + s_logger.error("iSCSI protocol is not enabled on SVM " + storage.getSVM()); + throw new CloudRuntimeException("iSCSI protocol is not enabled on SVM " + storage.getSVM()); + } + List aggrs = svm.getAggregates(); + if (aggrs == null || aggrs.isEmpty()) { + s_logger.error("No aggregates are assigned to SVM " + storage.getSVM()); + throw new CloudRuntimeException("No aggregates are assigned to SVM " + storage.getSVM()); + } + this.aggregates = aggrs; + } + s_logger.info("Successfully connected to ONTAP cluster and validated ONTAP details provided"); + } catch (Exception e) { + throw new CloudRuntimeException("Failed to connect to ONTAP cluster: " + e.getMessage()); + } + return true; + } + + // Common methods like create/delete etc., should be here + public void createVolume(String volumeName, Long size) { + s_logger.info("Creating volume: " + volumeName + " of size: " + size + " bytes"); + + if (aggregates == null || aggregates.isEmpty()) { + s_logger.error("No aggregates available to create volume on SVM " + storage.getSVM()); + throw new CloudRuntimeException("No aggregates available to create volume on SVM " + storage.getSVM()); + } + // Get the AuthHeader + String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + + // Generate the Create Volume Request + Volume volumeRequest = new Volume(); + Svm svm = new Svm(); + svm.setName(storage.getSVM()); + + volumeRequest.setName(volumeName); + volumeRequest.setSvm(svm); + volumeRequest.setAggregates(aggregates); + volumeRequest.setSize(size); + // Make the POST API call to create the volume + try { + // Create URI for POST CreateVolume API + URI url = utils.generateURI(Constants.CREATEVOLUME); + // Call the VolumeFeignClient to create the volume + JobResponse jobResponse = volumeFeignClient.createVolumeWithJob(url, authHeader, volumeRequest); + String jobUUID = jobResponse.getJob().getUuid(); + + //Create URI for GET Job API + url = utils.generateURI(Constants.GETJOBBYUUID); + int jobRetryCount = 0, maxJobRetries = Constants.JOBMAXRETRIES; + Job createVolumeJob = null; + while(createVolumeJob == null || createVolumeJob.getState().equals(Constants.JOBRUNNING) || createVolumeJob.getState().equals(Constants.JOBQUEUE) || createVolumeJob.getState().equals(Constants.JOBPAUSED)) { + if(jobRetryCount >= maxJobRetries) { + s_logger.error("Job to create volume " + volumeName + " did not complete within expected time."); + throw new CloudRuntimeException("Job to create volume " + volumeName + " did not complete within expected time."); + } + + try { + createVolumeJob = jobFeignClient.getJobByUUID(url, authHeader, jobUUID); + if (createVolumeJob == null) { + s_logger.warn("Job with UUID " + jobUUID + " not found. Retrying..."); + } else if (createVolumeJob.getState().equals(Constants.JOBFAILURE)) { + throw new CloudRuntimeException("Job to create volume " + volumeName + " failed with error: " + createVolumeJob.getMessage()); + } + } catch (FeignException.FeignClientException e) { + throw new CloudRuntimeException("Failed to fetch job status: " + e.getMessage()); + } + + jobRetryCount++; + Thread.sleep(Constants.CREATEVOLUMECHECKSLEEPTIME); // Sleep for 2 seconds before polling again + } + } catch (Exception e) { + s_logger.error("Exception while creating volume: ", e); + throw new CloudRuntimeException("Failed to create volume: " + e.getMessage()); + } + s_logger.info("Volume created successfully: " + volumeName); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java new file mode 100755 index 000000000000..249f5cac1872 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -0,0 +1,363 @@ +/* + * 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.service; + +import com.cloud.utils.exception.CloudRuntimeException; +import feign.FeignException; +import org.apache.cloudstack.storage.feign.client.NASFeignClient; +import org.apache.cloudstack.storage.feign.client.SvmFeignClient; +import org.apache.cloudstack.storage.feign.client.VolumeFeignClient; +import org.apache.cloudstack.storage.feign.model.*; +import org.apache.cloudstack.storage.feign.model.response.OntapResponse; +import org.apache.cloudstack.storage.utils.Constants; +import org.apache.cloudstack.storage.utils.Utility; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.net.URI; + +public class UnifiedNASStrategy extends NASStrategy{ + + @Inject + private Utility utils; + + @Inject + private NASFeignClient nasFeignClient; + + @Inject + private SvmFeignClient svmFeignClient; + + @Inject + private VolumeFeignClient volumeFeignClient; + + private static final Logger s_logger = LogManager.getLogger(NASStrategy.class); + public UnifiedNASStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + @Override + public String createExportPolicy(String svmName, String policyName) { + s_logger.info("Creating export policy: {} for SVM: {}", policyName, svmName); + + try { + // Get AuthHeader + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); // TODO change these once ontapStorage is made singleton + + // Create ExportPolicy object + ExportPolicy exportPolicy = new ExportPolicy(); + exportPolicy.setName(policyName); + + // Set SVM + Svm svm = new Svm(); + svm.setName(svmName); + exportPolicy.setSvm(svm); + + // Create URI for export policy creation + URI url = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + "/api/protocols/nfs/export-policies"); // TODO move this to constants ? + + // Create export policy + ExportPolicy createdPolicy = nasFeignClient.createExportPolicy(url, authHeader, true, exportPolicy); + + if (createdPolicy != null && createdPolicy.getId() != null) { + s_logger.info("Export policy created successfully with ID: {}", createdPolicy.getId()); + return createdPolicy.getId().toString(); + } else { + throw new CloudRuntimeException("Failed to create export policy: " + policyName); + } + + } catch (FeignException e) { + s_logger.error("Failed to create export policy: {}", policyName, e); + throw new CloudRuntimeException("Failed to create export policy: " + e.getMessage()); + } catch (Exception e) { + s_logger.error("Exception while creating export policy: {}", policyName, e); + throw new CloudRuntimeException("Failed to create export policy: " + e.getMessage()); + } + } + + @Override + public boolean deleteExportPolicy(String svmName, String policyName) { + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + // Get policy ID first + URI getUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/protocols/nfs/export-policies?name=" + policyName + "&svm.name=" + svmName); // TODO move this to constants and how to dynamic pass params later ? + + OntapResponse policiesResponse = nasFeignClient.getExportPolicyResponse(getUrl, authHeader); + + if (policiesResponse.getRecords() == null || policiesResponse.getRecords().isEmpty()) { + s_logger.warn("Export policy not found for deletion: {}", policyName); + return false; + } + + String policyId = policiesResponse.getRecords().get(0).getId().toString(); + + // Delete the policy + URI deleteUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/protocols/nfs/export-policies/" + policyId); + + nasFeignClient.deleteExportPolicyById(deleteUrl, authHeader, policyId); + + s_logger.info("Export policy deleted successfully: {}", policyName); + return true; + + } catch (Exception e) { + s_logger.error("Failed to delete export policy: {}", policyName, e); + return false; + } + } + + @Override + public boolean exportPolicyExists(String svmName, String policyName) { + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + URI url = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/protocols/nfs/export-policies?name=" + policyName + "&svm.name=" + svmName); + + OntapResponse response = nasFeignClient.getExportPolicyResponse(url, authHeader); + return response.getRecords() != null && !response.getRecords().isEmpty(); + + } catch (Exception e) { + s_logger.warn("Error checking export policy existence: {}", e.getMessage()); + return false; + } + } + + @Override + public String addExportRule(String policyName, String clientMatch, String[] protocols, String[] roRule, String[] rwRule) { + return ""; + } + + @Override + public String assignExportPolicyToVolume(String volumeUuid, String policyName) { + s_logger.info("Assigning export policy: {} to volume: {}", policyName, volumeUuid); + + try { + // Get AuthHeader + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + // First, get the export policy by name + URI getPolicyUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/protocols/nfs/export-policies?name=" + policyName + "&svm.name=" + OntapStorage.Svm); + + OntapResponse policiesResponse = nasFeignClient.getExportPolicyResponse(getPolicyUrl, authHeader); + + if (policiesResponse.getRecords() == null || policiesResponse.getRecords().isEmpty()) { + throw new CloudRuntimeException("Export policy not found: " + policyName); + } + + ExportPolicy exportPolicy = policiesResponse.getRecords().get(0); + + // Create Volume update object with NAS configuration + Volume volumeUpdate = new Volume(); + Nas nas = new Nas(); + nas.setExportPolicy(exportPolicy); + volumeUpdate.setNas(nas); + + // Update the volume + URI updateVolumeUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/storage/volumes/" + volumeUuid); + + volumeFeignClient.updateVolumeRebalancing(updateVolumeUrl, authHeader, volumeUuid, volumeUpdate); + + s_logger.info("Export policy successfully assigned to volume: {}", volumeUuid); + return "Export policy " + policyName + " assigned to volume " + volumeUuid; + + } catch (FeignException e) { + s_logger.error("Failed to assign export policy to volume: {}", volumeUuid, e); + throw new CloudRuntimeException("Failed to assign export policy: " + e.getMessage()); + } catch (Exception e) { + s_logger.error("Exception while assigning export policy to volume: {}", volumeUuid, e); + throw new CloudRuntimeException("Failed to assign export policy: " + e.getMessage()); + } + } + + @Override + public String enableNFS(String svmUuid) { + s_logger.info("Enabling NFS on SVM: {}", svmUuid); + + try { + // Get AuthHeader + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + // Create SVM update object to enable NFS + Svm svmUpdate = new Svm(); + svmUpdate.setNfsEnabled(true); + + // Update the SVM to enable NFS + URI updateSvmUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/svm/svms/" + svmUuid); + + svmFeignClient.updateSVM(updateSvmUrl, authHeader, svmUpdate); + + s_logger.info("NFS successfully enabled on SVM: {}", svmUuid); + return "NFS enabled on SVM: " + svmUuid; + + } catch (FeignException e) { + s_logger.error("Failed to enable NFS on SVM: {}", svmUuid, e); + throw new CloudRuntimeException("Failed to enable NFS: " + e.getMessage()); + } catch (Exception e) { + s_logger.error("Exception while enabling NFS on SVM: {}", svmUuid, e); + throw new CloudRuntimeException("Failed to enable NFS: " + e.getMessage()); + } + } + + // TODO should we return boolean or string ? + private boolean createFile(String volumeUuid, String filePath, Long fileSize) { + s_logger.info("Creating file: {} in volume: {}", filePath, volumeUuid); + + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + FileInfo fileInfo = new FileInfo(); + fileInfo.setPath(filePath); + fileInfo.setType(FileInfo.TypeEnum.FILE); + + if (fileSize != null && fileSize > 0) { + fileInfo.setSize(fileSize); + } + + URI createFileUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/storage/volumes/" + volumeUuid + "/files" + filePath); + + nasFeignClient.createFile(createFileUrl, authHeader, volumeUuid, filePath, fileInfo); + + s_logger.info("File created successfully: {} in volume: {}", filePath, volumeUuid); + return true; + + } catch (FeignException e) { + s_logger.error("Failed to create file: {} in volume: {}", filePath, volumeUuid, e); + return false; + } catch (Exception e) { + s_logger.error("Exception while creating file: {} in volume: {}", filePath, volumeUuid, e); + return false; + } + } + + private boolean deleteFile(String volumeUuid, String filePath) { + s_logger.info("Deleting file: {} from volume: {}", filePath, volumeUuid); + + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + // Check if file exists first + if (!fileExists(volumeUuid, filePath)) { + s_logger.warn("File does not exist: {} in volume: {}", filePath, volumeUuid); + return false; + } + + URI deleteFileUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/storage/volumes/" + volumeUuid + "/files" + filePath); + + nasFeignClient.deleteFile(deleteFileUrl, authHeader, volumeUuid, filePath); + + s_logger.info("File deleted successfully: {} from volume: {}", filePath, volumeUuid); + return true; + + } catch (FeignException e) { + s_logger.error("Failed to delete file: {} from volume: {}", filePath, volumeUuid, e); + return false; + } catch (Exception e) { + s_logger.error("Exception while deleting file: {} from volume: {}", filePath, volumeUuid, e); + return false; + } + } + + private boolean fileExists(String volumeUuid, String filePath) { + s_logger.debug("Checking if file exists: {} in volume: {}", filePath, volumeUuid); + + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + // Build URI for file info retrieval - volume-specific endpoint + URI getFileUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/storage/volumes/" + volumeUuid + "/files" + filePath); + + nasFeignClient.getFileResponse(getFileUrl, authHeader, volumeUuid, filePath); + + s_logger.debug("File exists: {} in volume: {}", filePath, volumeUuid); + return true; + + } catch (FeignException e) { + // TODO check the status code while testing for file not found error + if (e.status() == 404) { + s_logger.debug("File does not exist: {} in volume: {}", filePath, volumeUuid); + return false; + } + s_logger.error("Error checking file existence: {} in volume: {}", filePath, volumeUuid, e); + return false; + } catch (Exception e) { + s_logger.error("Exception while checking file existence: {} in volume: {}", filePath, volumeUuid, e); + return false; + } + } + + private OntapResponse getFileInfo(String volumeUuid, String filePath) { + s_logger.debug("Getting file info for: {} in volume: {}", filePath, volumeUuid); + + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + URI getFileUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/storage/volumes/" + volumeUuid + "/files" + filePath); + + OntapResponse response = nasFeignClient.getFileResponse(getFileUrl, authHeader, volumeUuid, filePath); + + s_logger.debug("Retrieved file info for: {} in volume: {}", filePath, volumeUuid); + return response; + + } catch (FeignException e) { + if (e.status() == 404) { + s_logger.debug("File not found: {} in volume: {}", filePath, volumeUuid); + return null; + } + s_logger.error("Failed to get file info: {} in volume: {}", filePath, volumeUuid, e); + throw new CloudRuntimeException("Failed to get file info: " + e.getMessage()); + } catch (Exception e) { + s_logger.error("Exception while getting file info: {} in volume: {}", filePath, volumeUuid, e); + throw new CloudRuntimeException("Failed to get file info: " + e.getMessage()); + } + } + + private boolean updateFile(String volumeUuid, String filePath, FileInfo fileInfo) { + s_logger.info("Updating file: {} in volume: {}", filePath, volumeUuid); + + try { + String authHeader = utils.generateAuthHeader(OntapStorage.Username, OntapStorage.Password); + + URI updateFileUrl = URI.create(Constants.HTTPS + OntapStorage.ManagementLIF + + "/api/storage/volumes/" + volumeUuid + "/files" + filePath); + + nasFeignClient.updateFile(updateFileUrl, authHeader, volumeUuid, filePath, fileInfo); + + s_logger.info("File updated successfully: {} in volume: {}", filePath, volumeUuid); + return true; + + } catch (FeignException e) { + s_logger.error("Failed to update file: {} in volume: {}", filePath, volumeUuid, e); + return false; + } catch (Exception e) { + s_logger.error("Exception while updating file: {} in volume: {}", filePath, volumeUuid, e); + return false; + } + } + +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java new file mode 100644 index 000000000000..e954ec312006 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java @@ -0,0 +1,48 @@ +/* + * 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.service; + +import org.apache.cloudstack.storage.feign.model.OntapStorage; + +public class UnifiedSANStrategy extends SANStrategy{ + public UnifiedSANStrategy(OntapStorage ontapStorage) { + super(ontapStorage); + } + + @Override + public String createLUN(String svmName, String volumeName, String lunName, long sizeBytes, String osType) { + return ""; + } + + @Override + public String createIgroup(String svmName, String igroupName, String[] initiators) { + return ""; + } + + @Override + public String mapLUNToIgroup(String lunName, String igroupName) { + return ""; + } + + @Override + public String enableISCSI(String svmUuid) { + return ""; + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java new file mode 100644 index 000000000000..0dff67941e5e --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java @@ -0,0 +1,46 @@ +/* + * 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.utils; + +public class Constants { + public static final String NFS = "nfs"; + public static final String ISCSI = "iscsi"; + public static final String PROTOCOL = "protocol"; + public static final String SVMNAME = "svmName"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; + public static final String MANAGEMENTLIF = "managementLIF"; + public static final String ISDISAGGREGATED = "isDisaggregated"; + public static final String RUNNING = "running"; + + public static final String JOBRUNNING = "running"; + public static final String JOBQUEUE = "queued"; + public static final String JOBPAUSED = "paused"; + public static final String JOBFAILURE = "failure"; + public static final String JOBSUCCESS = "success"; + + public static final int JOBMAXRETRIES = 100; + public static final int CREATEVOLUMECHECKSLEEPTIME = 2000; + + public static final String HTTPS = "https://"; + public static final String GETSVMs = "/api/svm/svms"; + public static final String CREATEVOLUME = "/api/storage/volumes"; + public static final String GETJOBBYUUID = "/api/cluster/jobs"; +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java new file mode 100644 index 000000000000..6fcf155e27b5 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java @@ -0,0 +1,52 @@ +/* + * 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.utils; + +import com.cloud.utils.StringUtils; +import org.apache.cloudstack.storage.feign.model.OntapStorage; +import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; + +import javax.inject.Inject; +import java.net.URI; + +@Component +public class Utility { + @Inject + OntapStorage ontapStorage; + + private static final String BASIC = "Basic"; + private static final String AUTH_HEADER_COLON = ":"; + /** + * Method generates authentication headers using storage backend credentials passed as normal string + * @param username -->> username of the storage backend + * @param password -->> normal decoded password of the storage backend + * @return + */ + public String generateAuthHeader(String username, String password) { + byte[] encodedBytes = Base64Utils.encode((username + AUTH_HEADER_COLON + password).getBytes()); + return BASIC + StringUtils.SPACE + new String(encodedBytes); + } + + public URI generateURI(String path) { + String uriString = Constants.HTTPS + ontapStorage.getManagementLIF() + path; + return URI.create(uriString); + } +}