diff --git a/conf/db/upgrade/V2.3.1.1__schema.sql b/conf/db/upgrade/V2.3.1.1__schema.sql
new file mode 100755
index 00000000000..8a520219724
--- /dev/null
+++ b/conf/db/upgrade/V2.3.1.1__schema.sql
@@ -0,0 +1,92 @@
+CREATE TABLE if not exists `zstack`.`SurfsBackupStorageVO` (
+ `uuid` varchar(32) NOT NULL UNIQUE,
+ `fsid` varchar(64) DEFAULT NULL,
+ `poolName` varchar(255) NOT NULL,
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE if not exists `zstack`.`SurfsBackupStorageNodeVO` (
+ `uuid` varchar(32) NOT NULL UNIQUE,
+ `sshUsername` varchar(64) NOT NULL,
+ `sshPassword` varchar(255) NOT NULL,
+ `hostname` varchar(255) NOT NULL,
+ `status` varchar(255) NOT NULL,
+ `nodeAddr` varchar(255) NOT NULL,
+ `sshPort` int unsigned NOT NULL,
+ `nodePort` int unsigned NOT NULL,
+ `backupStorageUuid` varchar(32) NOT NULL,
+ `lastOpDate` timestamp ON UPDATE CURRENT_TIMESTAMP,
+ `createDate` timestamp,
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE if not exists `zstack`.`SurfsPrimaryStorageVO` (
+ `uuid` varchar(32) NOT NULL UNIQUE,
+ `fsid` varchar(64) DEFAULT NULL,
+ `rootVolumePoolName` varchar(255) NOT NULL,
+ `dataVolumePoolName` varchar(255) NOT NULL,
+ `imageCachePoolName` varchar(255) NOT NULL,
+ `userKey` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE if not exists `zstack`.`SurfsPrimaryStorageNodeVO` (
+ `uuid` varchar(32) NOT NULL UNIQUE,
+ `sshUsername` varchar(64) NOT NULL,
+ `sshPassword` varchar(255) NOT NULL,
+ `hostname` varchar(255) NOT NULL,
+ `status` varchar(255) NOT NULL,
+ `nodeAddr` varchar(255) NOT NULL,
+ `sshPort` int unsigned NOT NULL,
+ `nodePort` int unsigned NOT NULL,
+ `primaryStorageUuid` varchar(32) NOT NULL,
+ `lastOpDate` timestamp ON UPDATE CURRENT_TIMESTAMP,
+ `createDate` timestamp,
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE if not exists `zstack`.`SurfsCapacityVO` (
+ `fsid` varchar(64) NOT NULL UNIQUE,
+ `totalCapacity` bigint unsigned DEFAULT 0,
+ `availableCapacity` bigint unsigned DEFAULT 0,
+ `lastOpDate` timestamp ON UPDATE CURRENT_TIMESTAMP,
+ `createDate` timestamp,
+ PRIMARY KEY (`fsid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE if not exists `zstack`.`SurfsPoolClassVO` (
+ `uuid` varchar(32) NOT NULL UNIQUE,
+ `fsid` varchar(64) NOT NULL,
+ `clsname` varchar(32) NOT NULL,
+ `clsdisplayname` varchar(64) NOT NULL,
+ `isrootcls` tinyint(1) NOT NULL DEFAULT 0,
+ `isactive` tinyint(1) NOT NULL DEFAULT 0,
+ `totalCapacity` bigint unsigned DEFAULT 0,
+ `availableCapacity` bigint unsigned DEFAULT 0,
+ `lastOpDate` timestamp ON UPDATE CURRENT_TIMESTAMP,
+ `createDate` timestamp,
+ PRIMARY KEY (`uuid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+delimiter //
+drop procedure if exists do_surfs_alter //
+create procedure do_surfs_alter()
+begin
+ IF not exists(select CONSTRAINT_NAME from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where CONSTRAINT_NAME='fkSurfsBackupStorageNodeVOBackupStorageEO') then
+ ALTER TABLE SurfsBackupStorageNodeVO ADD CONSTRAINT fkSurfsBackupStorageNodeVOBackupStorageEO FOREIGN KEY (backupStorageUuid) REFERENCES BackupStorageEO (uuid) ON DELETE CASCADE;
+ END IF;
+ IF not exists(select CONSTRAINT_NAME from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where CONSTRAINT_NAME='fkSurfsBackupStorageVOBackupStorageEO') then
+ ALTER TABLE SurfsBackupStorageVO ADD CONSTRAINT fkSurfsBackupStorageVOBackupStorageEO FOREIGN KEY (uuid) REFERENCES BackupStorageEO (uuid) ON UPDATE RESTRICT ON DELETE CASCADE;
+ END IF;
+ IF not exists(select CONSTRAINT_NAME from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where CONSTRAINT_NAME='fkSurfsPrimaryStorageNodeVOPrimaryStorageEO') then
+ ALTER TABLE SurfsPrimaryStorageNodeVO ADD CONSTRAINT fkSurfsPrimaryStorageNodeVOPrimaryStorageEO FOREIGN KEY (primaryStorageUuid) REFERENCES PrimaryStorageEO (uuid) ON DELETE CASCADE;
+ END IF;
+ IF not exists(select CONSTRAINT_NAME from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where CONSTRAINT_NAME='fkSurfsPrimaryStorageVOPrimaryStorageEO') then
+ ALTER TABLE SurfsPrimaryStorageVO ADD CONSTRAINT fkSurfsPrimaryStorageVOPrimaryStorageEO FOREIGN KEY (uuid) REFERENCES PrimaryStorageEO (uuid) ON UPDATE RESTRICT ON DELETE CASCADE;
+ END IF;
+end;//
+call do_surfs_alter() //
+drop procedure if exists do_surfs_alter //
+delimiter ;
+
+
diff --git a/conf/globalConfig/surfs.xml b/conf/globalConfig/surfs.xml
new file mode 100755
index 00000000000..12d03ef2825
--- /dev/null
+++ b/conf/globalConfig/surfs.xml
@@ -0,0 +1,53 @@
+
+
+
+ backupStorage.image.download.timeout
+ timeout of command of downloading images to surfs backup storage, in seconds.
+ surfs
+ 3600
+ java.lang.Long
+
+
+ imageCache.cleanup.interval
+ interval to cleanup stale image cache on primary storage, in seconds.
+ surfs
+ 43200
+ java.lang.Long
+
+
+ primaryStorage.deletePool
+ delete all primary storage related pools when deleting the surfs primary storage
+ surfs
+ false
+ java.lang.Boolean
+
+
+ primaryStorage.node.reconnectDelay
+ the delay to reconnect a primary storage surfs node after a ping failure, in seconds
+ surfs
+ 30
+ java.lang.Integer
+
+
+ backupStorage.node.reconnectDelay
+ the delay to reconnect a backup storage surfs node after a ping failure, in seconds
+ surfs
+ 30
+ java.lang.Integer
+
+
+ primaryStorage.node.autoReconnect
+ whether to automatically reconnect a surfs primary storage node when it's unable to be pinged
+ surfs
+ true
+ java.lang.Boolean
+
+
+ backupStorage.node.autoReconnect
+ whether to automatically reconnect a surfs backup storage node when it's unable to be pinged
+ surfs
+ true
+ java.lang.Boolean
+
+
+
diff --git a/conf/persistence.xml b/conf/persistence.xml
index a9afe7a58df..62b81bdfeb8 100755
--- a/conf/persistence.xml
+++ b/conf/persistence.xml
@@ -122,6 +122,11 @@
org.zstack.storage.fusionstor.primary.FusionstorPrimaryStorageMonVO
org.zstack.storage.fusionstor.primary.FusionstorPrimaryStorageVO
org.zstack.storage.fusionstor.FusionstorCapacityVO
+ org.zstack.storage.surfs.backup.SurfsBackupStorageVO
+ org.zstack.storage.surfs.backup.SurfsBackupStorageNodeVO
+ org.zstack.storage.surfs.backup.SurfsPrimaryStorageVO
+ org.zstack.storage.surfs.backup.SurfsPrimaryStorageNodeVO
+ org.zstack.storage.surfs.SurfsCapacityVO
org.zstack.core.gc.GarbageCollectorVO
org.zstack.header.storage.primary.ImageCacheVolumeRefVO
org.zstack.network.service.virtualrouter.lb.VirtualRouterLoadBalancerRefVO
diff --git a/conf/serviceConfig/surfs.xml b/conf/serviceConfig/surfs.xml
new file mode 100755
index 00000000000..b0fa239bf92
--- /dev/null
+++ b/conf/serviceConfig/surfs.xml
@@ -0,0 +1,60 @@
+
+
+ SurfsApiInterceptor
+
+
+ org.zstack.storage.surfs.backup.APIAddSurfsBackupStorageMsg
+ storage.backup
+
+
+
+ org.zstack.storage.surfs.backup.APIAddNodeToSurfsBackupStorageMsg
+ storage.backup
+
+
+
+ org.zstack.storage.surfs.backup.APIUpdateSurfsBackupStorageNodeMsg
+ storage.backup
+
+
+
+ org.zstack.storage.surfs.backup.APIRemoveNodeFromSurfsBackupStorageMsg
+ storage.backup
+
+
+
+ org.zstack.storage.surfs.backup.APIQuerySurfsBackupStorageMsg
+ query
+
+
+
+ org.zstack.storage.surfs.primary.APIAddSurfsPrimaryStorageMsg
+ storage.primary
+
+
+
+ org.zstack.storage.surfs.primary.APIAddNodeToSurfsPrimaryStorageMsg
+ storage.primary
+
+
+
+ org.zstack.storage.surfs.primary.APIUpdateSurfsPrimaryStorageNodeMsg
+ storage.primary
+
+
+
+ org.zstack.storage.surfs.primary.APIRemoveNodeFromSurfsPrimaryStorageMsg
+ storage.primary
+
+
+
+ org.zstack.storage.surfs.primary.APIQuerySurfsPrimaryStorageMsg
+ query
+
+
+
+ org.zstack.storage.surfs.primary.APIQuerySurfsPoolClassMsg
+ query
+
+
+
diff --git a/conf/springConfigXml/HostAllocatorManager.xml b/conf/springConfigXml/HostAllocatorManager.xml
index 6f2b0f119c0..0f64610c289 100755
--- a/conf/springConfigXml/HostAllocatorManager.xml
+++ b/conf/springConfigXml/HostAllocatorManager.xml
@@ -42,6 +42,10 @@
Ceph
XSky
+
+
+ Surfs
+
Fusionstor
@@ -55,6 +59,7 @@
+
diff --git a/conf/springConfigXml/surfs.xml b/conf/springConfigXml/surfs.xml
new file mode 100755
index 00000000000..97613af7ca1
--- /dev/null
+++ b/conf/springConfigXml/surfs.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/zstack.xml b/conf/zstack.xml
index 33d13cf3809..f610a67d179 100755
--- a/conf/zstack.xml
+++ b/conf/zstack.xml
@@ -42,6 +42,7 @@
+
diff --git a/plugin/pom.xml b/plugin/pom.xml
index 20c40526dca..61143cc736a 100755
--- a/plugin/pom.xml
+++ b/plugin/pom.xml
@@ -21,7 +21,8 @@
eip
mediator
localstorage
- ceph
+ surfs
+ ceph
fusionstor
loadBalancer
flatNetworkProvider
diff --git a/plugin/surfs/pom.xml b/plugin/surfs/pom.xml
new file mode 100644
index 00000000000..3f140c328fe
--- /dev/null
+++ b/plugin/surfs/pom.xml
@@ -0,0 +1,80 @@
+
+
+ 4.0.0
+
+ org.zstack
+ plugin
+ 2.3.0
+
+ surfs
+ surfs
+ http://maven.apache.org
+
+
+ org.zstack
+ kvm
+ ${project.version}
+
+
+ org.zstack
+ sftpBackupStorage
+ ${project.version}
+
+
+ org.zstack
+ storage
+ ${project.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${project.compiler.version}
+
+
+ ${project.java.version}
+ true
+
+
+
+ org.codehaus.mojo
+ aspectj-maven-plugin
+ ${aspectj.plugin.version}
+
+
+
+ compile
+ test-compile
+
+
+
+
+
+ ${project.java.version}
+ ${project.java.version}
+ true
+
+
+ org.springframework
+ spring-aspects
+
+
+ org.zstack
+ core
+
+
+ org.zstack
+ header
+
+
+
+
+
+
+
+
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/NodeStatus.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/NodeStatus.java
new file mode 100644
index 00000000000..f870b479cc0
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/NodeStatus.java
@@ -0,0 +1,10 @@
+package org.zstack.storage.surfs;
+
+/**
+ * Created by zhouhaiping 2017-09-04
+ */
+public enum NodeStatus {
+ Connecting,
+ Connected,
+ Disconnected
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/NodeUri.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/NodeUri.java
new file mode 100644
index 00000000000..0c29a17d274
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/NodeUri.java
@@ -0,0 +1,149 @@
+package org.zstack.storage.surfs;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.zstack.core.Platform;
+import org.zstack.core.errorcode.ErrorFacade;
+import org.zstack.core.keyvalue.Op;
+import org.zstack.header.apimediator.ApiMessageInterceptionException;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.errorcode.OperationFailureException;
+import org.zstack.header.errorcode.SysErrors;
+import org.zstack.header.exception.CloudRuntimeException;
+
+import static org.zstack.core.Platform.argerr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.zstack.utils.CollectionDSL.list;
+
+/**
+ * Created by zhouhaiping 2017-09-05
+ */
+public class NodeUri {
+ private String hostname;
+ private int nodePort = 6543;
+ private String sshUsername;
+ private String sshPassword;
+ private int sshPort = 22;
+
+ private static List allowedQueryParameter;
+ static {
+ allowedQueryParameter = list("nodePort");
+ }
+
+ public static void checkQuery(URI uri) {
+ List params = URLEncodedUtils.parse(uri, "UTF-8");
+ for (NameValuePair p : params) {
+ if (!allowedQueryParameter.contains(p.getName())) {
+ throw new CloudRuntimeException(String.format("unknown parameter[%s]", p.getName()));
+ }
+ }
+ }
+
+ public static String getQueryValue(URI uri, String name) {
+ List params = URLEncodedUtils.parse(uri, "UTF-8");
+ for (NameValuePair p : params) {
+ if (!allowedQueryParameter.contains(p.getName())) {
+ throw new CloudRuntimeException(String.format("unknown parameter[%s]", p.getName()));
+ }
+
+ if (p.getName().equals(name)) {
+ return p.getValue();
+ }
+ }
+
+ return null;
+ }
+
+ private static final String NODE_URL_FORMAT = "sshUsername:sshPassword@hostname:[sshPort]/?[monPort=]";
+
+ private ErrorCode errorCode(String err) {
+ return argerr(err);
+ }
+
+ public NodeUri(String url) {
+ try {
+ int at = url.lastIndexOf("@");
+ if (at == -1) {
+ throw new OperationFailureException(errorCode(String.format("invalid nodeUrl[%s], the sshUsername:sshPassword part is invalid. A valid nodeUrl is" +
+ " in format of %s", url, NODE_URL_FORMAT)));
+ }
+
+ String userInfo = url.substring(0, at);
+ if (!userInfo.contains(":")) {
+ throw new OperationFailureException(errorCode(String.format("invalid nodeUrl[%s], the sshUsername:sshPassword part is invalid. A valid nodeUrl is" +
+ " in format of %s", url, NODE_URL_FORMAT)));
+ }
+
+ String rest = url.substring(at+1, url.length());
+ String[] ssh = userInfo.split(":");
+ sshUsername = ssh[0];
+ sshPassword = ssh[1];
+
+ URI uri = new URI(String.format("ssh://%s", rest));
+ hostname = uri.getHost();
+ if (hostname == null) {
+ throw new OperationFailureException(errorCode(
+ String.format("invalid nodeUrl[%s], hostname cannot be null. A valid nodeUrl is" +
+ " in format of %s", url, NODE_URL_FORMAT)
+ ));
+ }
+
+ sshPort = uri.getPort() == -1 ? sshPort : uri.getPort();
+ if (sshPort < 1 || sshPort > 65535) {
+ throw new OperationFailureException(errorCode(
+ String.format("invalid nodeUrl[%s], the ssh port is greater than 65535 or smaller than 1. A valid nodeUrl is" +
+ " in format of %s", url, NODE_URL_FORMAT)
+ ));
+ }
+ String v = getQueryValue(uri, SurfsConstants.NODE_PARAM_NODE_PORT);
+ nodePort = v == null ? nodePort : Integer.valueOf(v);
+ } catch (URISyntaxException e) {
+ throw new CloudRuntimeException(e);
+ }
+ }
+
+ public int getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(int sshPort) {
+ this.sshPort = sshPort;
+ }
+
+ public int getNodePort() {
+ return nodePort;
+ }
+
+ public void setNodePort(int nodePort) {
+ this.nodePort = nodePort;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public String getSshUsername() {
+ return sshUsername;
+ }
+
+ public void setSshUsername(String sshUsername) {
+ this.sshUsername = sshUsername;
+ }
+
+ public String getSshPassword() {
+ return sshPassword;
+ }
+
+ public void setSshPassword(String sshPassword) {
+ this.sshPassword = sshPassword;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsApiInterceptor.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsApiInterceptor.java
new file mode 100644
index 00000000000..222f6b147c7
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsApiInterceptor.java
@@ -0,0 +1,189 @@
+package org.zstack.storage.surfs;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.SimpleQuery;
+import org.zstack.core.db.SimpleQuery.Op;
+import org.zstack.core.errorcode.ErrorFacade;
+import org.zstack.header.apimediator.ApiMessageInterceptionException;
+import org.zstack.header.apimediator.ApiMessageInterceptor;
+import org.zstack.header.errorcode.OperationFailureException;
+import org.zstack.header.message.APIMessage;
+import org.zstack.utils.CollectionUtils;
+import org.zstack.utils.Utils;
+
+import org.zstack.storage.surfs.backup.APIAddSurfsBackupStorageMsg;
+import org.zstack.storage.surfs.backup.APIAddNodeToSurfsBackupStorageMsg;
+import org.zstack.storage.surfs.backup.APIUpdateSurfsBackupStorageNodeMsg;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageNodeVO;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageNodeVO_;
+
+import org.zstack.storage.surfs.primary.APIAddSurfsPrimaryStorageMsg;
+import org.zstack.storage.surfs.primary.APIAddNodeToSurfsPrimaryStorageMsg;
+import org.zstack.storage.surfs.primary.APIUpdateSurfsPrimaryStorageNodeMsg;
+import org.zstack.storage.surfs.primary.SurfsPrimaryStorageNodeVO;
+import org.zstack.storage.surfs.primary.SurfsPrimaryStorageNodeVO_;
+
+import org.zstack.utils.function.Function;
+import org.zstack.utils.logging.CLogger;
+import org.zstack.utils.network.NetworkUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by frank on 7/29/2015.
+ */
+public class SurfsApiInterceptor implements ApiMessageInterceptor {
+ private static final CLogger logger = Utils.getLogger(SurfsApiInterceptor.class);
+
+ @Autowired
+ private ErrorFacade errf;
+ @Autowired
+ private DatabaseFacade dbf;
+
+ private static final String MON_URL_FORMAT = "sshUsername:sshPassword@hostname:[sshPort]/?[monPort=]";
+
+ @Override
+ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
+ if (msg instanceof APIAddSurfsBackupStorageMsg) {
+ validate((APIAddSurfsBackupStorageMsg) msg);
+ } else if (msg instanceof APIAddSurfsPrimaryStorageMsg) {
+ validate((APIAddSurfsPrimaryStorageMsg) msg);
+ } else if (msg instanceof APIAddNodeToSurfsBackupStorageMsg) {
+ validate((APIAddNodeToSurfsBackupStorageMsg) msg);
+ } else if (msg instanceof APIAddNodeToSurfsPrimaryStorageMsg) {
+ validate((APIAddNodeToSurfsPrimaryStorageMsg) msg);
+ } else if (msg instanceof APIUpdateSurfsPrimaryStorageNodeMsg) {
+ validate((APIUpdateSurfsPrimaryStorageNodeMsg) msg);
+ } else if (msg instanceof APIUpdateSurfsBackupStorageNodeMsg) {
+ validate((APIUpdateSurfsBackupStorageNodeMsg) msg);
+ }
+
+ return msg;
+ }
+
+ private void checkExistingPrimaryStorage(List monUrls) {
+ List hostnames = CollectionUtils.transformToList(monUrls, new Function() {
+ @Override
+ public String call(String url) {
+ NodeUri uri = new NodeUri(url);
+ return uri.getHostname();
+ }
+ });
+
+ SimpleQuery q = dbf.createQuery(SurfsPrimaryStorageNodeVO.class);
+ q.select(SurfsPrimaryStorageNodeVO_.hostname);
+ q.add(SurfsPrimaryStorageNodeVO_.hostname, Op.IN, hostnames);
+ List existing = q.listValue();
+ if (!existing.isEmpty()) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ String.format("cannot add surfs primary storage, there has been some surfs primary storage using mon[hostnames:%s]", existing)
+ ));
+ }
+ }
+
+ private void validate(APIAddNodeToSurfsPrimaryStorageMsg msg) {
+ checkNodeUrls(msg.getNodeUrls());
+ }
+
+ private void validate(APIAddNodeToSurfsBackupStorageMsg msg) {
+ checkNodeUrls(msg.getNodeUrls());
+ }
+
+ private void checkNodeUrls(List monUrls) {
+ List urls = new ArrayList();
+ for (String monUrl : monUrls) {
+ String url = String.format("ssh://%s", monUrl);
+ try {
+ new NodeUri(url);
+ } catch (OperationFailureException ae) {
+ throw new ApiMessageInterceptionException(ae.getErrorCode());
+ } catch (Exception e) {
+ logger.warn(e.getMessage(), e);
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ String.format("invalid monUrl[%s]. A valid url is in format of %s", monUrl, MON_URL_FORMAT)
+ ));
+ }
+ }
+ }
+
+ private void validate(APIAddSurfsPrimaryStorageMsg msg) {
+ if (msg.getDataVolumePoolName() != null && msg.getDataVolumePoolName().isEmpty()) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ "dataVolumePoolName can be null but cannot be an empty string"
+ ));
+ }
+ if (msg.getRootVolumePoolName() != null && msg.getRootVolumePoolName().isEmpty()) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ "rootVolumePoolName can be null but cannot be an empty string"
+ ));
+ }
+ if (msg.getImageCachePoolName() != null && msg.getImageCachePoolName().isEmpty()) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ "imageCachePoolName can be null but cannot be an empty string"
+ ));
+ }
+
+ checkNodeUrls(msg.getNodeUrls());
+ checkExistingPrimaryStorage(msg.getNodeUrls());
+ }
+
+ private void checkExistingBackupStorage(List monUrls) {
+ List hostnames = CollectionUtils.transformToList(monUrls, new Function() {
+ @Override
+ public String call(String url) {
+ NodeUri uri = new NodeUri(url);
+ return uri.getHostname();
+ }
+ });
+
+ SimpleQuery q = dbf.createQuery(SurfsBackupStorageNodeVO.class);
+ q.select(SurfsBackupStorageNodeVO_.hostname);
+ q.add(SurfsBackupStorageNodeVO_.hostname, Op.IN, hostnames);
+ List existing = q.listValue();
+ if (!existing.isEmpty()) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ String.format("cannot add surfs backup storage, there has been some surfs backup storage using mon[hostnames:%s]", existing)
+ ));
+ }
+ }
+
+ private void validate(APIAddSurfsBackupStorageMsg msg) {
+ if (msg.getPoolName() != null && msg.getPoolName().isEmpty()) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ "poolName can be null but cannot be an empty string"
+ ));
+ }
+
+ checkNodeUrls(msg.getNodeUrls());
+ checkExistingBackupStorage(msg.getNodeUrls());
+ }
+
+ private void validate(APIUpdateSurfsBackupStorageNodeMsg msg) {
+ if (msg.getHostname() != null && !NetworkUtils.isIpv4Address(msg.getHostname()) && !NetworkUtils.isHostname(msg.getHostname())) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ String.format("hostname[%s] is neither an IPv4 address nor a valid hostname", msg.getHostname())
+ ));
+ }
+ SimpleQuery q = dbf.createQuery(SurfsBackupStorageNodeVO.class);
+ q.select(SurfsBackupStorageNodeVO_.backupStorageUuid);
+ q.add(SurfsBackupStorageNodeVO_.uuid, Op.EQ, msg.getNodeUuid());
+ String bsUuid = q.findValue();
+ msg.setBackupStorageUuid(bsUuid);
+ }
+
+ private void validate(APIUpdateSurfsPrimaryStorageNodeMsg msg) {
+ if (msg.getHostname() != null && !NetworkUtils.isIpv4Address(msg.getHostname()) && !NetworkUtils.isHostname(msg.getHostname())) {
+ throw new ApiMessageInterceptionException(errf.stringToInvalidArgumentError(
+ String.format("hostname[%s] is neither an IPv4 address nor a valid hostname", msg.getHostname())
+ ));
+ }
+
+ SimpleQuery q = dbf.createQuery(SurfsPrimaryStorageNodeVO.class);
+ q.select(SurfsPrimaryStorageNodeVO_.primaryStorageUuid);
+ q.add(SurfsPrimaryStorageNodeVO_.uuid, Op.EQ, msg.getNodeUuid());
+ String psUuid = q.findValue();
+ msg.setPrimaryStorageUuid(psUuid);
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityUpdateExtensionPoint.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityUpdateExtensionPoint.java
new file mode 100644
index 00000000000..ed0fc82741a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityUpdateExtensionPoint.java
@@ -0,0 +1,8 @@
+package org.zstack.storage.surfs;
+
+/**
+ * Created by zhouhaiping 2017-09-01
+ */
+public interface SurfsCapacityUpdateExtensionPoint {
+ void update(String fsid,long total, long avail);
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityUpdater.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityUpdater.java
new file mode 100644
index 00000000000..521cea39df2
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityUpdater.java
@@ -0,0 +1,70 @@
+package org.zstack.storage.surfs;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.transaction.annotation.Transactional;
+import org.zstack.core.componentloader.PluginRegistry;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.GLock;
+
+import javax.persistence.LockModeType;
+
+/**
+ * Created by zhouhaiping 2017-08-26
+ */
+@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
+public class SurfsCapacityUpdater {
+ @Autowired
+ private DatabaseFacade dbf;
+ @Autowired
+ private PluginRegistry pluginRgty;
+
+ public void update(String fsid, long total, long avail) {
+ update(fsid, total, avail, true);
+ }
+
+ @Transactional
+ public void update(String fsid, long total, long avail, boolean updatedAnyway) {
+ SurfsCapacityVO vo = dbf.getEntityManager().find(SurfsCapacityVO.class, fsid, LockModeType.PESSIMISTIC_WRITE);
+ boolean updated = false;
+
+ if (vo == null) {
+ GLock lock = new GLock(String.format("surfs-%s", fsid), 120);
+ lock.lock();
+ try {
+ vo = dbf.getEntityManager().find(SurfsCapacityVO.class, fsid, LockModeType.PESSIMISTIC_WRITE);
+ if (vo == null) {
+ vo = new SurfsCapacityVO();
+ vo.setFsid(fsid);
+ vo.setTotalCapacity(total);
+ vo.setAvailableCapacity(avail);
+ dbf.getEntityManager().persist(vo);
+ updated = true;
+ } else {
+ if (vo.getAvailableCapacity() != avail || vo.getTotalCapacity() != total) {
+ vo.setTotalCapacity(total);
+ vo.setAvailableCapacity(avail);
+ dbf.getEntityManager().merge(vo);
+ updated = true;
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ } else {
+ if (vo.getAvailableCapacity() != avail || vo.getTotalCapacity() != total) {
+ vo.setTotalCapacity(total);
+ vo.setAvailableCapacity(avail);
+ dbf.getEntityManager().merge(vo);
+ updated = true;
+ }
+ }
+
+ if (updatedAnyway || updated) {
+ for (SurfsCapacityUpdateExtensionPoint ext : pluginRgty.getExtensionList(SurfsCapacityUpdateExtensionPoint.class)) {
+ ext.update(fsid, total, avail);
+ }
+ }
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityVO.java
new file mode 100644
index 00000000000..1639e83bd21
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsCapacityVO.java
@@ -0,0 +1,72 @@
+package org.zstack.storage.surfs;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+
+/**
+ * Created by frank on 7/28/2015.
+ */
+@Entity
+@Table
+public class SurfsCapacityVO {
+ @Id
+ @Column
+ private String fsid;
+
+ @Column
+ private long totalCapacity;
+
+ @Column
+ private long availableCapacity;
+
+ @Column
+ private Timestamp createDate;
+
+ @Column
+ private Timestamp lastOpDate;
+
+ @PreUpdate
+ private void preUpdate() {
+ lastOpDate = null;
+ }
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+
+ public long getTotalCapacity() {
+ return totalCapacity;
+ }
+
+ public void setTotalCapacity(long totalCapacity) {
+ this.totalCapacity = totalCapacity;
+ }
+
+ public long getAvailableCapacity() {
+ return availableCapacity;
+ }
+
+ public void setAvailableCapacity(long availableCapacity) {
+ this.availableCapacity = availableCapacity;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsConstants.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsConstants.java
new file mode 100644
index 00000000000..af295afb8df
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsConstants.java
@@ -0,0 +1,16 @@
+package org.zstack.storage.surfs;
+/**
+ * Created by zhouhaiping 2017-07-11
+ */
+
+import org.zstack.header.configuration.PythonClass;
+
+@PythonClass
+public interface SurfsConstants {
+ @PythonClass
+ String SURFS_BACKUP_STORAGE_TYPE = "Surfs";
+ @PythonClass
+ String SURFS_PRIMARY_STORAGE_TYPE = "Surfs";
+ String NODE_PARAM_NODE_PORT = "nodePort";
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsGlobalConfig.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsGlobalConfig.java
new file mode 100644
index 00000000000..734b85b4287
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsGlobalConfig.java
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs;
+
+import org.zstack.core.config.GlobalConfig;
+import org.zstack.core.config.GlobalConfigDefinition;
+import org.zstack.core.config.GlobalConfigValidation;
+
+/**
+ * Created by zhouhaiping 2017-09-11
+ */
+@GlobalConfigDefinition
+public class SurfsGlobalConfig {
+ public static final String CATEGORY = "surfs";
+
+ @GlobalConfigValidation(numberGreaterThan = 0)
+ public static GlobalConfig IMAGE_CACHE_CLEANUP_INTERVAL = new GlobalConfig(CATEGORY, "imageCache.cleanup.interval");
+ @GlobalConfigValidation
+ public static GlobalConfig PRIMARY_STORAGE_DELETE_POOL = new GlobalConfig(CATEGORY, "primaryStorage.deletePool");
+ @GlobalConfigValidation(numberGreaterThan = 0)
+ public static GlobalConfig PRIMARY_STORAGE_MON_RECONNECT_DELAY = new GlobalConfig(CATEGORY, "primaryStorage.node.reconnectDelay");
+ @GlobalConfigValidation
+ public static GlobalConfig PRIMARY_STORAGE_MON_AUTO_RECONNECT = new GlobalConfig(CATEGORY, "primaryStorage.node.autoReconnect");
+ @GlobalConfigValidation(numberGreaterThan = 0)
+ public static GlobalConfig BACKUP_STORAGE_MON_RECONNECT_DELAY = new GlobalConfig(CATEGORY, "backupStorage.node.reconnectDelay");
+ @GlobalConfigValidation
+ public static GlobalConfig BACKUP_STORAGE_MON_AUTO_RECONNECT = new GlobalConfig(CATEGORY, "backupStorage.node.autoReconnect");
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsGlobalProperty.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsGlobalProperty.java
new file mode 100644
index 00000000000..aa6007b58ac
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsGlobalProperty.java
@@ -0,0 +1,29 @@
+package org.zstack.storage.surfs;
+
+import org.zstack.core.GlobalProperty;
+import org.zstack.core.GlobalPropertyDefinition;
+
+/**
+ * Created by zhouhaiping 2017-09-01
+ */
+@GlobalPropertyDefinition
+public class SurfsGlobalProperty {
+ @GlobalProperty(name="Surfs.backupStorageAgent.port", defaultValue = "6732")
+ public static int BACKUP_STORAGE_AGENT_PORT;
+ @GlobalProperty(name="Surfs.primaryStorageAgent.port", defaultValue = "6731")
+ public static int PRIMARY_STORAGE_AGENT_PORT;
+ @GlobalProperty(name="Surfs.backupStorage.agentPackageName", defaultValue = "surfsbackupstorage-2.2.0.tar.gz")
+ public static String BACKUP_STORAGE_PACKAGE_NAME;
+ @GlobalProperty(name="Surfs.backupStorage.ansiblePlaybook", defaultValue = "surfsb.py")
+ public static String BACKUP_STORAGE_PLAYBOOK_NAME;
+ @GlobalProperty(name="Surfs.backupStorage.ansibleModulePath", defaultValue = "ansible/surfsb")
+ public static String BACKUP_STORAGE_MODULE_PATH;
+ @GlobalProperty(name="Surfs.primaryStorage.agentPackageName", defaultValue = "surfsprimarystorage-2.2.0.tar.gz")
+ public static String PRIMARY_STORAGE_PACKAGE_NAME;
+ @GlobalProperty(name="Surfs.primaryStorage.ansiblePlaybook", defaultValue = "surfsp.py")
+ public static String PRIMARY_STORAGE_PLAYBOOK_NAME;
+ @GlobalProperty(name="Surfs.primaryStorage.ansibleModulePath", defaultValue = "ansible/surfsp")
+ public static String PRIMARY_STORAGE_MODULE_PATH;
+ @GlobalProperty(name="Surfs.type", defaultValue = "Surfs")
+ public static String SURFS_PRIMARY_STORAGE_TYPE;
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeAO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeAO.java
new file mode 100644
index 00000000000..e3211511464
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeAO.java
@@ -0,0 +1,129 @@
+package org.zstack.storage.surfs;
+
+import javax.persistence.*;
+
+import java.sql.Timestamp;
+
+/**
+ * Created by zhouhaiping 2017-08-25
+ */
+@MappedSuperclass
+public class SurfsNodeAO {
+ @Id
+ @Column
+ private String uuid;
+
+ @Column
+ private String sshUsername;
+
+ @Column
+ private String sshPassword;
+
+ @Column
+ private String hostname;
+
+ @Column
+ private String nodeAddr;
+
+ @Column
+ private int nodePort = 6543;
+
+ @Column
+ private int sshPort = 22;
+
+ @Column
+ @Enumerated(EnumType.STRING)
+ private NodeStatus status;
+
+ @Column
+ private Timestamp createDate;
+
+ @Column
+ private Timestamp lastOpDate;
+
+ @PreUpdate
+ private void preUpdate() {
+ lastOpDate = null;
+ }
+
+ public String getNodeAddr() {
+ return nodeAddr;
+ }
+
+ public void setNodeAddr(String monAddr) {
+ this.nodeAddr = monAddr;
+ }
+
+ public int getNodePort() {
+ return nodePort;
+ }
+
+ public void setNodePort(int nodePort) {
+ this.nodePort = nodePort;
+ }
+
+ public int getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(int sshPort) {
+ this.sshPort = sshPort;
+ }
+
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public String getSshUsername() {
+ return sshUsername;
+ }
+
+ public void setSshUsername(String sshUsername) {
+ this.sshUsername = sshUsername;
+ }
+
+ public String getSshPassword() {
+ return sshPassword;
+ }
+
+ public void setSshPassword(String sshPassword) {
+ this.sshPassword = sshPassword;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public NodeStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(NodeStatus status) {
+ this.status = status;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeAO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeAO_.java
new file mode 100644
index 00000000000..81ef323792c
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeAO_.java
@@ -0,0 +1,20 @@
+package org.zstack.storage.surfs;
+
+import javax.persistence.metamodel.SingularAttribute;
+import javax.persistence.metamodel.StaticMetamodel;
+import java.sql.Timestamp;
+
+/**
+ * Created by zhouhaiping 2017-08-25
+ */
+@StaticMetamodel(SurfsNodeAO.class)
+public class SurfsNodeAO_ {
+ public static volatile SingularAttribute uuid;
+ public static volatile SingularAttribute sshUsername;
+ public static volatile SingularAttribute sshPassword;
+ public static volatile SingularAttribute hostname;
+ public static volatile SingularAttribute nodePort;
+ public static volatile SingularAttribute status;
+ public static volatile SingularAttribute createDate;
+ public static volatile SingularAttribute lastOpDate;
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeBase.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeBase.java
new file mode 100644
index 00000000000..f07ec8b8638
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsNodeBase.java
@@ -0,0 +1,70 @@
+package org.zstack.storage.surfs;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.zstack.header.core.Completion;
+import org.zstack.header.core.ReturnValueCompletion;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.rest.JsonAsyncRESTCallback;
+import org.zstack.header.rest.RESTFacade;
+import org.zstack.utils.ssh.Ssh;
+
+/**
+ * Created by zhouhaiping 2017-09-01
+ */
+@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE, dependencyCheck = true)
+public abstract class SurfsNodeBase {
+ protected SurfsNodeAO self;
+
+ @Autowired
+ protected RESTFacade restf;
+
+ public static class PingResult {
+ public boolean operationFailure;
+ public boolean success;
+ public String error;
+ }
+
+ public abstract void connect(Completion completion);
+ public abstract void ping(ReturnValueCompletion completion);
+
+ protected abstract int getAgentPort();
+
+ public SurfsNodeBase(SurfsNodeAO self) {
+ this.self = self;
+ }
+
+ protected void checkTools() {
+ Ssh ssh = new Ssh();
+ ssh.setHostname(self.getHostname()).setUsername(self.getSshUsername()).setPassword(self.getSshPassword()).setPort(self.getSshPort())
+ .checkTool("surfs").runErrorByExceptionAndClose();
+ }
+
+ protected String makeHttpPath(String ip, String path) {
+ return String.format("http://%s:%s%s", ip, getAgentPort(), path);
+ }
+
+ public void httpCall(final String path, final Object cmd, final Class retClass, final ReturnValueCompletion completion) {
+ restf.asyncJsonPost(makeHttpPath(self.getHostname(), path), cmd, new JsonAsyncRESTCallback(completion) {
+ @Override
+ public void fail(ErrorCode err) {
+ completion.fail(err);
+ }
+
+ @Override
+ public void success(T ret) {
+ completion.success(ret);
+ }
+
+ @Override
+ public Class getReturnClass() {
+ return retClass;
+ }
+ });
+ }
+
+ public SurfsNodeAO getSelf() {
+ return self;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassUpdater.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassUpdater.java
new file mode 100644
index 00000000000..d105f1bd6b8
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassUpdater.java
@@ -0,0 +1,159 @@
+package org.zstack.storage.surfs;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.transaction.annotation.Transactional;
+import org.zstack.core.componentloader.PluginRegistry;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.Platform;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+import org.zstack.core.db.GLock;
+import org.zstack.header.storage.primary.ImageCacheVO;
+
+import java.util.*;
+
+import javax.persistence.TypedQuery;
+
+@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
+public class SurfsPoolClassUpdater {
+ @Autowired
+ private DatabaseFacade dbf;
+ @Autowired
+ private PluginRegistry pluginRgty;
+ public static List poolclss =new ArrayList();
+ private static final CLogger logger = Utils.getLogger(SurfsPoolClassUpdater.class);
+ @Transactional
+ private void addpoolcls(String fsid,String[] plmsg){
+ SurfsPoolClassVO spcvo=new SurfsPoolClassVO();
+ spcvo.setUuid(Platform.getUuid());
+ spcvo.setFsid(fsid);
+ spcvo.setClsname(plmsg[0]);
+ spcvo.setDisplayName(plmsg[0]);
+ if (plmsg[3].equals("true")){
+ spcvo.setIsActive(true);
+ }else{
+ spcvo.setIsActive(false);
+ }
+ try{
+ spcvo.setTotalCapacity(Long.parseLong(plmsg[1]));
+ spcvo.setAvailableCapacity(Long.parseLong(plmsg[2]));
+ }catch(Exception ex){
+ spcvo.setTotalCapacity(0);
+ spcvo.setAvailableCapacity(0);
+ logger.warn(String.format(
+ "PoolClass[%s] msg error:TotalCapacity[%s] and AvailableCapacity[%s]",plmsg[0],plmsg[1],plmsg[2]
+ ));
+ }
+ try{
+ poolclss.add(spcvo);
+ dbf.getEntityManager().persist(spcvo);
+ }catch(Exception ex){
+ logger.debug(ex.getMessage());
+ }
+
+ }
+ @Transactional
+ private void updatepoolcls(SurfsPoolClassVO spcvo,String[] plmsg){
+ if (plmsg[3].equals("true")){
+ spcvo.setIsActive(true);
+ }else{
+ spcvo.setIsActive(false);
+ }
+ try{
+ spcvo.setTotalCapacity(Long.parseLong(plmsg[1]));
+ spcvo.setAvailableCapacity(Long.parseLong(plmsg[2]));
+ }catch(Exception ex){
+ spcvo.setTotalCapacity(0);
+ spcvo.setAvailableCapacity(0);
+ logger.warn(String.format(
+ "PoolClass[%s] msg error:TotalCapacity[%s] and AvailableCapacity[%s]",plmsg[0],plmsg[1],plmsg[2]
+ ));
+ }
+ try{
+ dbf.getEntityManager().merge(spcvo);
+ }catch(Exception ex){
+ logger.debug(ex.getMessage());
+ }
+
+
+ }
+
+ @Transactional
+ private void refreshspc(){
+ String sql = "select c from SurfsPoolClassVO c";
+ TypedQuery fq = dbf.getEntityManager().createQuery(sql, SurfsPoolClassVO.class);
+ List spclist =fq.getResultList();
+ if (spclist == null || spclist.isEmpty()){
+ return;
+ }
+ for(final SurfsPoolClassVO spc : spclist ){
+ poolclss.add(spc);
+ }
+ }
+ public void update(String fsid,String clsmsg){
+ String[] pools=clsmsg.split(",");
+ if (pools.length == 0){
+ return;
+ }
+
+ GLock lock = new GLock(String.format("surfs-p-%s", fsid), 240);
+
+ lock.lock();
+ try{
+ String poolist="";
+ for (int i=0;i < pools.length;i++){
+ String[] plmsg=pools[i].split(":");
+ poolist=poolist + "," + plmsg[0];
+ if (plmsg.length !=4){
+
+ continue;
+ }
+
+ if (poolclss.isEmpty()){
+ refreshspc();
+ if (poolclss.isEmpty()) {
+ addpoolcls(fsid,plmsg);
+ }
+ }else{
+ Iterator it = poolclss.iterator();
+ int dosign=0;
+ while (it.hasNext()){
+ SurfsPoolClassVO svo=it.next();
+ if (svo.getClsname().equals(plmsg[0])){
+ if (Long.parseLong(plmsg[1]) !=svo.getTotalCapacity() || Long.parseLong(plmsg[2]) !=svo.getAvailableCapacity()){
+ updatepoolcls(svo,plmsg);
+ }
+ dosign=1;
+ }
+ }
+ if (dosign==0){
+ addpoolcls(fsid,plmsg);
+ }
+ }
+ }
+ for (int j=0;j< poolclss.size();j++){
+ if (poolist.indexOf(poolclss.get(j).getClsname()) !=-1){
+ continue;
+ }
+ try{
+ dbf.getEntityManager().remove(poolclss.get(j));
+ poolclss.remove(j);
+
+ }catch(Exception ex){
+ logger.warn(String.format("Failed to remove the pool type [%s] for out of date",poolclss.get(j).getClsname()));
+ }
+ break;
+ }
+ }finally {
+ lock.unlock();
+ }
+
+
+ }
+
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassVO.java
new file mode 100644
index 00000000000..1eb788791ab
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassVO.java
@@ -0,0 +1,133 @@
+package org.zstack.storage.surfs;
+
+import java.sql.Timestamp;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.PreUpdate;
+import javax.persistence.Table;
+
+/**
+ * Created by zhouhaiping 2017-11-14
+ */
+@Entity
+@Table
+public class SurfsPoolClassVO{
+ @Id
+ @Column
+ private String uuid;
+
+ @Column
+ private String fsid;
+
+ @Column
+ private String clsname;
+
+ @Column
+ private String clsdisplayname;
+
+ @Column
+ private boolean isrootcls=false;
+
+ @Column
+ private boolean isactive=false;
+
+ @Column
+ private long totalCapacity;
+
+ @Column
+ private long availableCapacity;
+
+ @Column
+ private Timestamp createDate;
+
+ @Column
+ private Timestamp lastOpDate;
+
+ @PreUpdate
+ private void preUpdate() {
+ lastOpDate = null;
+ }
+
+ public void setUuid(String cuuid){
+ this.uuid=cuuid;
+ }
+
+ public String getUuid(){
+ return this.uuid;
+ }
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+
+ public void setClsname(String cname){
+ this.clsname=cname;
+ }
+
+ public String getClsname(){
+ return this.clsname;
+ }
+
+ public void setDisplayName(String dpname){
+ this.clsdisplayname=dpname;
+ }
+
+ public String getDisplayName(){
+ return this.clsdisplayname;
+ }
+
+ public void setIsRootCls(boolean isrc){
+ this.isrootcls=isrc;
+ }
+
+ public boolean getIsRootCls(){
+ return this.isrootcls;
+ }
+
+ public void setIsActive(boolean isac){
+ this.isactive=isac;
+ }
+
+ public boolean getIsActive(){
+ return this.isactive;
+ }
+
+ public long getTotalCapacity() {
+ return totalCapacity;
+ }
+
+ public void setTotalCapacity(long totalCapacity) {
+ this.totalCapacity = totalCapacity;
+ }
+
+ public long getAvailableCapacity() {
+ return availableCapacity;
+ }
+
+ public void setAvailableCapacity(long availableCapacity) {
+ this.availableCapacity = availableCapacity;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassVO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassVO_.java
new file mode 100644
index 00000000000..673f1fd8e77
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsPoolClassVO_.java
@@ -0,0 +1,23 @@
+package org.zstack.storage.surfs;
+
+import java.sql.Timestamp;
+
+import javax.persistence.metamodel.SingularAttribute;
+import javax.persistence.metamodel.StaticMetamodel;
+
+import org.zstack.header.vo.ResourceVO_;
+import org.zstack.storage.surfs.SurfsPoolClassVO;
+
+@StaticMetamodel(SurfsPoolClassVO.class)
+public class SurfsPoolClassVO_ extends ResourceVO_ {
+ public static volatile SingularAttribute Uuid;
+ public static volatile SingularAttribute fsid;
+ public static volatile SingularAttribute clsname;
+ public static volatile SingularAttribute clsdisplayname;
+ public static volatile SingularAttribute createDate;
+ public static volatile SingularAttribute lastOpDate;
+ public static volatile SingularAttribute totalCapacity;
+ public static volatile SingularAttribute availableCapacity;
+ public static volatile SingularAttribute isrootcls;
+ public static volatile SingularAttribute isactive;
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsSystemTags.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsSystemTags.java
new file mode 100644
index 00000000000..edbc9c97dcc
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/SurfsSystemTags.java
@@ -0,0 +1,20 @@
+package org.zstack.storage.surfs;
+
+import org.zstack.header.storage.backup.BackupStorageVO;
+import org.zstack.header.storage.primary.PrimaryStorageVO;
+import org.zstack.header.tag.TagDefinition;
+import org.zstack.tag.PatternedSystemTag;
+
+/**
+ * Created by zhouhaiping on 8/21/2017.
+ */
+@TagDefinition
+public class SurfsSystemTags {
+ public static PatternedSystemTag PREDEFINED_BACKUP_STORAGE_POOL = new PatternedSystemTag("surfs::predefinedPool", BackupStorageVO.class);
+ public static PatternedSystemTag PREDEFINED_PRIMARY_STORAGE_IMAGE_CACHE_POOL = new PatternedSystemTag("surfs::predefinedImageCachePool", PrimaryStorageVO.class);
+ public static PatternedSystemTag PREDEFINED_PRIMARY_STORAGE_ROOT_VOLUME_POOL = new PatternedSystemTag("surfs::predefinedRootVolumePool", PrimaryStorageVO.class);
+ public static PatternedSystemTag PREDEFINED_PRIMARY_STORAGE_DATA_VOLUME_POOL = new PatternedSystemTag("surfs::predefinedDataVolumePool", PrimaryStorageVO.class);
+
+ public static final String KVM_SECRET_UUID_TOKEN = "uuid";
+ public static PatternedSystemTag KVM_SECRET_UUID = new PatternedSystemTag(String.format("surfs::kvm::secret::{%s}", KVM_SECRET_UUID_TOKEN), PrimaryStorageVO.class);
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageEvent.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageEvent.java
new file mode 100644
index 00000000000..9844798646d
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageEvent.java
@@ -0,0 +1,35 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+/**
+ * Created by zhouhaiping 2017-09-12
+ */
+@RestResponse(allTo = "inventory")
+public class APIAddNodeToSurfsBackupStorageEvent extends APIEvent {
+ private SurfsBackupStorageInventory inventory;
+
+ public APIAddNodeToSurfsBackupStorageEvent() {
+ }
+
+ public APIAddNodeToSurfsBackupStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public SurfsBackupStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(SurfsBackupStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public static APIAddNodeToSurfsBackupStorageEvent __example__() {
+ APIAddNodeToSurfsBackupStorageEvent event = new APIAddNodeToSurfsBackupStorageEvent();
+
+
+ return event;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageEventDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..8791df0ffe5
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.backup.SurfsBackupStorageInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.backup.APIAddNodeToSurfsBackupStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventory"
+ path "org.zstack.storage.surfs.backup.APIAddNodeToSurfsBackupStorageEvent.inventory"
+ desc "null"
+ type "SurfsBackupStorageInventory"
+ since "0.6"
+ clz SurfsBackupStorageInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageMsg.java
new file mode 100644
index 00000000000..4a4cd896992
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageMsg.java
@@ -0,0 +1,56 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.backup.BackupStorageMessage;
+
+import java.util.List;
+
+import static org.codehaus.groovy.runtime.InvokerHelper.asList;
+
+/**
+ * Created by zhouhaiping 2017-08-23
+ */
+@RestRequest(
+ path = "/backup-storage/surfs/{uuid}/nodes",
+ method = HttpMethod.POST,
+ parameterName = "params",
+ responseClass = APIAddNodeToSurfsBackupStorageEvent.class
+)
+public class APIAddNodeToSurfsBackupStorageMsg extends APIMessage implements BackupStorageMessage {
+ @APIParam(resourceType = SurfsBackupStorageVO.class)
+ private String uuid;
+ @APIParam(nonempty = true)
+ private List nodeUrls;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public List getNodeUrls() {
+ return nodeUrls;
+ }
+
+ public void setNodeUrls(List nodeUrls) {
+ this.nodeUrls = nodeUrls;
+ }
+
+ @Override
+ public String getBackupStorageUuid() {
+ return uuid;
+ }
+
+ public static APIAddNodeToSurfsBackupStorageMsg __example__() {
+ APIAddNodeToSurfsBackupStorageMsg msg = new APIAddNodeToSurfsBackupStorageMsg();
+ msg.setUuid(uuid());
+ msg.setNodeUrls(asList("root:password@localhost:23"));
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..eded9c9774b
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddNodeToSurfsBackupStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,72 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.storage.surfs.backup.APIAddNodeToSurfsBackupStorageEvent
+
+doc {
+ title "AddNodeToSurfsBackupStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "POST /v1/backup-storage/surfs/{uuid}/nodes"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIAddNodeToSurfsBackupStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn "params"
+ desc "资源的UUID,唯一标示该资源"
+ location "url"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "nodeUrls"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "List"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIAddNodeToSurfsBackupStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddSurfsBackupStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddSurfsBackupStorageMsg.java
new file mode 100644
index 00000000000..f50958a9e8d
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddSurfsBackupStorageMsg.java
@@ -0,0 +1,66 @@
+package org.zstack.storage.surfs.backup;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.message.OverriddenApiParam;
+import org.zstack.header.message.OverriddenApiParams;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.backup.APIAddBackupStorageEvent;
+import org.zstack.header.storage.backup.APIAddBackupStorageMsg;
+import org.zstack.storage.surfs.SurfsConstants;
+
+/**
+ * Created by zhouhaiping 2017-09-01
+ */
+@OverriddenApiParams({
+ @OverriddenApiParam(field = "url", param = @APIParam(maxLength = 2048, required = false))
+})
+@RestRequest(
+ path = "/backup-storage/surfs",
+ method = HttpMethod.POST,
+ parameterName = "params",
+ responseClass = APIAddBackupStorageEvent.class
+)
+public class APIAddSurfsBackupStorageMsg extends APIAddBackupStorageMsg {
+ @APIParam(nonempty = false, emptyString = false)
+ private List nodeUrls;
+
+ @APIParam(required = false, maxLength = 255)
+ private String poolName;
+
+ public String getUrl() {
+ return "not used";
+ }
+
+ public String getPoolName() {
+ return poolName;
+ }
+
+ public void setPoolName(String poolName) {
+ this.poolName = poolName;
+ }
+
+ public List getNodeUrls() {
+ return nodeUrls;
+ }
+
+ public void setNodeUrls(List nodeUrls) {
+ this.nodeUrls = nodeUrls;
+ }
+
+ @Override
+ public String getType() {
+ return SurfsConstants.SURFS_BACKUP_STORAGE_TYPE;
+ }
+
+ public static APIAddSurfsBackupStorageMsg __example__() {
+ APIAddSurfsBackupStorageMsg msg = new APIAddSurfsBackupStorageMsg();
+ msg.setNodeUrls(Collections.singletonList("root:password@localhost/?nodePort=7777"));
+ msg.setName("surfs");
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddSurfsBackupStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddSurfsBackupStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..0c3bbe13694
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIAddSurfsBackupStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,132 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.header.storage.backup.APIAddBackupStorageEvent
+
+doc {
+ title "AddSurfsBackupStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "POST /v1/backup-storage/surfs"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIAddSurfsBackupStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "nodeUrls"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "List"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "poolName"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "url"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "name"
+ enclosedIn "params"
+ desc "资源名称"
+ location "body"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "description"
+ enclosedIn "params"
+ desc "资源的详细描述"
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "type"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "importImages"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "boolean"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "resourceUuid"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIAddBackupStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIQuerySurfsBackupStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIQuerySurfsBackupStorageMsg.java
new file mode 100644
index 00000000000..fa647c1911a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIQuerySurfsBackupStorageMsg.java
@@ -0,0 +1,32 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.identity.Action;
+import org.zstack.header.query.APIQueryMessage;
+import org.zstack.header.query.AutoQuery;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.backup.APIQueryBackupStorageReply;
+import org.zstack.header.storage.backup.BackupStorageConstant;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+@Action(category = BackupStorageConstant.ACTION_CATEGORY, names = {"read"})
+@AutoQuery(replyClass = APIQueryBackupStorageReply.class, inventoryClass = SurfsBackupStorageInventory.class)
+@RestRequest(
+ path = "/backup-storage/surfs",
+ optionalPaths = {"/backup-storage/surfs/{uuid}"},
+ method = HttpMethod.GET,
+ responseClass = APIQueryBackupStorageReply.class
+)
+public class APIQuerySurfsBackupStorageMsg extends APIQueryMessage {
+
+ public static List __example__() {
+ return asList();
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIQuerySurfsBackupStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIQuerySurfsBackupStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..b4f0321bd9f
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIQuerySurfsBackupStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,33 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.header.storage.backup.APIQueryBackupStorageReply
+import org.zstack.header.query.APIQueryMessage
+
+doc {
+ title "QuerySurfsBackupStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "GET /v1/backup-storage/surfs"
+
+ url "GET /v1/backup-storage/surfs/{uuid}"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIQuerySurfsBackupStorageMsg.class
+
+ desc """"""
+
+ params APIQueryMessage.class
+ }
+
+ response {
+ clz APIQueryBackupStorageReply.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageEvent.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageEvent.java
new file mode 100644
index 00000000000..80780ee1c47
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageEvent.java
@@ -0,0 +1,35 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+/**
+ * Created by frank on 8/1/2015.
+ */
+@RestResponse(allTo = "inventory")
+public class APIRemoveNodeFromSurfsBackupStorageEvent extends APIEvent {
+ private SurfsBackupStorageInventory inventory;
+
+ public APIRemoveNodeFromSurfsBackupStorageEvent() {
+ }
+
+ public APIRemoveNodeFromSurfsBackupStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public SurfsBackupStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(SurfsBackupStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public static APIRemoveNodeFromSurfsBackupStorageEvent __example__() {
+ APIRemoveNodeFromSurfsBackupStorageEvent event = new APIRemoveNodeFromSurfsBackupStorageEvent();
+
+
+ return event;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageEventDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..4ef5e652204
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.backup.SurfsBackupStorageInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.backup.APIRemoveNodeFromSurfsBackupStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventory"
+ path "org.zstack.storage.surfs.backup.APIRemoveNodeFromSurfsBackupStorageEvent.inventory"
+ desc "null"
+ type "SurfsBackupStorageInventory"
+ since "0.6"
+ clz SurfsBackupStorageInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageMsg.java
new file mode 100644
index 00000000000..3614b399060
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageMsg.java
@@ -0,0 +1,55 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.backup.BackupStorageMessage;
+
+import java.util.List;
+
+import static org.codehaus.groovy.runtime.InvokerHelper.asList;
+
+/**
+ * Created by zhouhaiping 2017-09-12
+ */
+@RestRequest(
+ path = "/backup-storage/surfs/{uuid}/nodes",
+ method = HttpMethod.DELETE,
+ responseClass = APIRemoveNodeFromSurfsBackupStorageEvent.class
+)
+public class APIRemoveNodeFromSurfsBackupStorageMsg extends APIMessage implements BackupStorageMessage {
+ @APIParam(resourceType = SurfsBackupStorageVO.class)
+ private String uuid;
+ @APIParam(nonempty = true)
+ private List nodeHostnames;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public List getNodeHostnames() {
+ return nodeHostnames;
+ }
+
+ public void setNodeHostnames(List nodeHostnames) {
+ this.nodeHostnames = nodeHostnames;
+ }
+
+ @Override
+ public String getBackupStorageUuid() {
+ return uuid;
+ }
+
+ public static APIRemoveNodeFromSurfsBackupStorageMsg __example__() {
+ APIRemoveNodeFromSurfsBackupStorageMsg msg = new APIRemoveNodeFromSurfsBackupStorageMsg();
+ msg.setUuid(uuid());
+ msg.setNodeHostnames(asList("192.20.12.12"));
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..e2f549aeabc
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIRemoveNodeFromSurfsBackupStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,72 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.storage.surfs.backup.APIRemoveNodeFromSurfsBackupStorageEvent
+
+doc {
+ title "RemoveNodeFromSurfsBackupStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "DELETE /v1/backup-storage/surfs/{uuid}/nodes"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIRemoveNodeFromSurfsBackupStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn ""
+ desc "资源的UUID,唯一标示该资源"
+ location "url"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "nodeHostnames"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIRemoveNodeFromSurfsBackupStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateNodeToSurfsBackupStorageEvent.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateNodeToSurfsBackupStorageEvent.java
new file mode 100644
index 00000000000..01c5c56a5e0
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateNodeToSurfsBackupStorageEvent.java
@@ -0,0 +1,35 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+/**
+ * Created by zhouhaiping 2017-09-13
+ */
+@RestResponse(allTo = "inventory")
+public class APIUpdateNodeToSurfsBackupStorageEvent extends APIEvent {
+ private SurfsBackupStorageInventory inventory;
+
+ public APIUpdateNodeToSurfsBackupStorageEvent() {
+ }
+
+ public APIUpdateNodeToSurfsBackupStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public SurfsBackupStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(SurfsBackupStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public static APIUpdateNodeToSurfsBackupStorageEvent __example__() {
+ APIUpdateNodeToSurfsBackupStorageEvent event = new APIUpdateNodeToSurfsBackupStorageEvent();
+
+
+ return event;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateNodeToSurfsBackupStorageEventDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateNodeToSurfsBackupStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..45d92f4c73c
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateNodeToSurfsBackupStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.backup.SurfsBackupStorageInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.backup.APIUpdateNodeToSurfsBackupStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventory"
+ path "org.zstack.storage.surfs.backup.APIUpdateNodeToSurfsBackupStorageEvent.inventory"
+ desc "null"
+ type "SurfsBackupStorageInventory"
+ since "0.6"
+ clz SurfsBackupStorageInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateSurfsBackupStorageNodeMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateSurfsBackupStorageNodeMsg.java
new file mode 100644
index 00000000000..2cb600a2b9c
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateSurfsBackupStorageNodeMsg.java
@@ -0,0 +1,109 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.APINoSee;
+import org.zstack.header.rest.RestRequest;
+
+import org.zstack.header.storage.backup.BackupStorageMessage;
+
+
+/**
+ * Created by zhouhaiping 2017-09-11
+ */
+@RestRequest(
+ path = "/backup-storage/surfs/nodes/{nodeUuid}/actions",
+ method = HttpMethod.PUT,
+ isAction = true,
+ responseClass = APIUpdateNodeToSurfsBackupStorageEvent.class
+)
+public class APIUpdateSurfsBackupStorageNodeMsg extends APIMessage implements BackupStorageMessage {
+ @APINoSee
+ private String backupStorageUuid;
+
+ @APIParam(resourceType = SurfsBackupStorageNodeVO.class, emptyString = false)
+ private String nodeUuid;
+
+ @APIParam(maxLength = 255, required = false)
+ private String hostname;
+
+ @APIParam(maxLength = 255, required = false)
+ private String sshUsername;
+
+ @APIParam(maxLength = 255, required = false)
+ private String sshPassword;
+
+ @APIParam(numberRange = {1, 65535}, required = false)
+ private Integer sshPort;
+
+ @APIParam(numberRange = {1, 65535}, required = false)
+ private Integer nodePort;
+
+ public String getNodeUuid() {
+ return nodeUuid;
+ }
+
+ public void setNodeUuid(String nodeUuid) {
+ this.nodeUuid = nodeUuid;
+ }
+
+ public String getSshUsername() {
+ return sshUsername;
+ }
+
+ public void setSshUsername(String sshUsername) {
+ this.sshUsername = sshUsername;
+ }
+
+ public String getSshPassword() {
+ return sshPassword;
+ }
+
+ public void setSshPassword(String sshPassword) {
+ this.sshPassword = sshPassword;
+ }
+
+ public Integer getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(Integer sshPort) {
+ this.sshPort = sshPort;
+ }
+
+ public Integer getNodePort() {
+ return nodePort;
+ }
+
+ public void setNodePort(Integer nodePort) {
+ this.nodePort = nodePort;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ @Override
+ public String getBackupStorageUuid() {
+ return backupStorageUuid;
+ }
+
+ public void setBackupStorageUuid(String backupStorageUuid) {
+ this.backupStorageUuid = backupStorageUuid;
+ }
+
+ public static APIUpdateSurfsBackupStorageNodeMsg __example__() {
+ APIUpdateSurfsBackupStorageNodeMsg msg = new APIUpdateSurfsBackupStorageNodeMsg();
+ msg.setNodeUuid(uuid());
+ msg.setNodePort(7798);
+
+ return msg;
+ }
+}
+
+
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateSurfsBackupStorageNodeMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateSurfsBackupStorageNodeMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..79797190df5
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/APIUpdateSurfsBackupStorageNodeMsgDoc_zh_cn.groovy
@@ -0,0 +1,112 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.storage.surfs.backup.APIUpdateNodeToSurfsBackupStorageEvent
+
+doc {
+ title "UpdateSurfsBackupStorageNode"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "PUT /v1/backup-storage/surfs/nodes/{nodeUuid}/actions"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIUpdateSurfsBackupStorageNodeMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "nodeUuid"
+ enclosedIn "updateSurfsBackupStorageNode"
+ desc ""
+ location "url"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "hostname"
+ enclosedIn "updateSurfsBackupStorageNode"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "sshUsername"
+ enclosedIn "updateSurfsBackupStorageNode"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "sshPassword"
+ enclosedIn "updateSurfsBackupStorageNode"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "sshPort"
+ enclosedIn "updateSurfsBackupStorageNode"
+ desc ""
+ location "body"
+ type "Integer"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "nodePort"
+ enclosedIn "updateSurfsBackupStorageNode"
+ desc ""
+ location "body"
+ type "Integer"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIUpdateNodeToSurfsBackupStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageBase.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageBase.java
new file mode 100644
index 00000000000..50bfc37a7a6
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageBase.java
@@ -0,0 +1,1296 @@
+package org.zstack.storage.surfs.backup;
+
+import static org.zstack.utils.CollectionDSL.list;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.transaction.annotation.Transactional;
+import org.zstack.core.Platform;
+import org.zstack.core.db.SimpleQuery;
+import org.zstack.core.db.SimpleQuery.Op;
+import org.zstack.core.thread.AsyncThread;
+import org.zstack.core.workflow.FlowChainBuilder;
+import org.zstack.core.workflow.ShareFlow;
+import org.zstack.header.core.*;
+import org.zstack.header.core.workflow.*;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.errorcode.OperationFailureException;
+import org.zstack.header.errorcode.SysErrors;
+import org.zstack.header.exception.CloudRuntimeException;
+import org.zstack.header.image.APIAddImageMsg;
+import org.zstack.header.image.ImageBackupStorageRefInventory;
+import org.zstack.header.image.ImageInventory;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.rest.JsonAsyncRESTCallback;
+import org.zstack.header.rest.RESTFacade;
+import org.zstack.header.storage.backup.*;
+import org.zstack.storage.backup.BackupStorageBase;
+import org.zstack.storage.surfs.SurfsNodeBase.PingResult;
+import org.zstack.storage.surfs.*;
+import org.zstack.utils.CollectionUtils;
+import org.zstack.utils.DebugUtils;
+import org.zstack.utils.Utils;
+import org.zstack.utils.function.Function;
+import org.zstack.utils.gson.JSONObjectUtil;
+import org.zstack.utils.logging.CLogger;
+
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+/**
+ * Created by zhouhaiping 2017-08-23
+ */
+@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
+public class SurfsBackupStorageBase extends BackupStorageBase {
+ private static final CLogger logger = Utils.getLogger(SurfsBackupStorageBase.class);
+
+ class ReconnectNodeLock {
+ AtomicBoolean hold = new AtomicBoolean(false);
+
+ boolean lock() {
+ return hold.compareAndSet(false, true);
+ }
+
+ void unlock() {
+ hold.set(false);
+ }
+ }
+
+ ReconnectNodeLock reconnectNodeLock = new ReconnectNodeLock();
+
+ @Autowired
+ protected RESTFacade restf;
+
+ public static class AgentCommand {
+ String fsid;
+ String uuid;
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+ }
+
+ public static class AgentResponse {
+ String error;
+ boolean success = true;
+ Long totalCapacity;
+ Long availableCapacity;
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public void setSuccess(boolean success) {
+ this.success = success;
+ }
+
+ public Long getTotalCapacity() {
+ return totalCapacity;
+ }
+
+ public void setTotalCapacity(Long totalCapacity) {
+ this.totalCapacity = totalCapacity;
+ }
+
+ public Long getAvailableCapacity() {
+ return availableCapacity;
+ }
+
+ public void setAvailableCapacity(Long availableCapacity) {
+ this.availableCapacity = availableCapacity;
+ }
+ }
+
+ public static class Pool {
+ String name;
+ boolean predefined;
+ }
+
+ public static class InitCmd extends AgentCommand {
+ List pools;
+ }
+
+ public static class InitRsp extends AgentResponse {
+ String fsid;
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+ }
+
+ @ApiTimeout(apiClasses = {APIAddImageMsg.class})
+ public static class DownloadCmd extends AgentCommand {
+ String url;
+ String installPath;
+ String imageUuid;
+ String imageFormat;
+
+ public String getImageFormat() {
+ return imageFormat;
+ }
+
+ public void setImageFormat(String imageFormat) {
+ this.imageFormat = imageFormat;
+ }
+
+ public String getImageUuid() {
+ return imageUuid;
+ }
+
+ public void setImageUuid(String imageUuid) {
+ this.imageUuid = imageUuid;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getInstallPath() {
+ return installPath;
+ }
+
+ public void setInstallPath(String installPath) {
+ this.installPath = installPath;
+ }
+ }
+
+ public static class DownloadRsp extends AgentResponse {
+ long size;
+ Long actualSize;
+
+ public Long getActualSize() {
+ return actualSize;
+ }
+
+ public void setActualSize(Long actualSize) {
+ this.actualSize = actualSize;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+ }
+
+ public static class DeleteCmd extends AgentCommand {
+ String installPath;
+
+ public String getInstallPath() {
+ return installPath;
+ }
+
+ public void setInstallPath(String installPath) {
+ this.installPath = installPath;
+ }
+ }
+
+ public static class DeleteRsp extends AgentResponse {
+ }
+
+ public static class PingCmd extends AgentCommand {
+ }
+
+ public static class PingRsp extends AgentResponse {
+
+ }
+
+ public static class GetImageSizeCmd extends AgentCommand {
+ public String imageUuid;
+ public String installPath;
+ }
+
+ public static class GetImageSizeRsp extends AgentResponse {
+ public Long size;
+ public Long actualSize;
+ }
+
+ public static class GetFactsCmd extends AgentCommand {
+ public String nodeUuid;
+ }
+
+ public static class GetFactsRsp extends AgentResponse {
+ public String fsid;
+ }
+
+ public static class GetLocalFileSizeCmd extends AgentCommand {
+ public String path ;
+ }
+
+ public static class GetLocalFileSizeRsp extends AgentResponse {
+ public long size;
+ }
+
+
+ public static final String INIT_PATH = "/surfs/backupstorage/init";
+ public static final String DOWNLOAD_IMAGE_PATH = "/surfs/backupstorage/image/download";
+ public static final String DELETE_IMAGE_PATH = "/surfs/backupstorage/image/delete";
+ public static final String GET_IMAGE_SIZE_PATH = "/surfs/backupstorage/image/getsize";
+ public static final String PING_PATH = "/surfs/backupstorage/ping";
+ public static final String GET_FACTS = "/surfs/backupstorage/facts";
+ public static final String GET_LOCAL_FILE_SIZE = "/surfs/backupstorage/getlocalfilesize";
+
+ protected String makeImageInstallPath(String imageUuid) {
+ return String.format("surfs://%s/%s", getSelf().getPoolName(), imageUuid);
+ }
+
+ private void httpCall(final String path, final AgentCommand cmd, final Class retClass, final ReturnValueCompletion callback) {
+ cmd.setUuid(self.getUuid());
+
+ final List nodes = new ArrayList();
+ for (SurfsBackupStorageNodeVO nodevo : getSelf().getNodes()) {
+ if (nodevo.getStatus() == NodeStatus.Connected) {
+ nodes.add(new SurfsBackupStorageNodeBase(nodevo));
+ }
+ }
+
+ if (nodes.isEmpty()) {
+ throw new OperationFailureException(errf.stringToOperationError(
+ String.format("all surfs mons are Disconnected in surfs backup storage[uuid:%s]", self.getUuid())
+ ));
+ }
+
+ Collections.shuffle(nodes);
+
+ class HttpCaller {
+ Iterator it = nodes.iterator();
+ List errorCodes = new ArrayList();
+
+ void call() {
+ if (!it.hasNext()) {
+ callback.fail(errf.stringToOperationError(
+ String.format("all nodes failed to execute http call[%s], errors are %s", path, JSONObjectUtil.toJsonString(errorCodes))
+ ));
+
+ return;
+ }
+
+ SurfsBackupStorageNodeBase base = it.next();
+ base.httpCall(path, cmd, retClass, new ReturnValueCompletion(callback) {
+ @Override
+ public void success(T ret) {
+ if (!ret.success) {
+ // not an IO error but an operation error, return it
+ String details = String.format("[node:%s], %s", base.getSelf().getHostname(), ret.error);
+ callback.fail(errf.stringToOperationError(details));
+ } else {
+ if (!(cmd instanceof InitCmd)) {
+ updateCapacityIfNeeded(ret);
+ }
+
+ callback.success(ret);
+ }
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ String details = String.format("[node:%s], %s", base.getSelf().getHostname(), errorCode.getDetails());
+ errorCode.setDetails(details);
+ errorCodes.add(errorCode);
+ call();
+ }
+ });
+ }
+ }
+
+ new HttpCaller().call();
+
+ }
+
+ public SurfsBackupStorageBase(BackupStorageVO self) {
+ super(self);
+ }
+
+ protected SurfsBackupStorageVO getSelf() {
+ return (SurfsBackupStorageVO) self;
+ }
+
+ protected SurfsBackupStorageInventory getInventory() {
+ return SurfsBackupStorageInventory.valueOf(getSelf());
+ }
+
+ private void updateCapacityIfNeeded(AgentResponse rsp) {
+ if (rsp.getTotalCapacity() != null && rsp.getAvailableCapacity() != null) {
+ new SurfsCapacityUpdater().update(getSelf().getFsid(), rsp.totalCapacity, rsp.availableCapacity);
+ }
+ }
+
+ @Override
+ @Transactional
+ protected void handle(final DownloadImageMsg msg) {
+ final DownloadCmd cmd = new DownloadCmd();
+ cmd.url = msg.getImageInventory().getUrl();
+ cmd.installPath = makeImageInstallPath(msg.getImageInventory().getUuid());
+ cmd.imageUuid = msg.getImageInventory().getUuid();
+ cmd.imageFormat = msg.getImageInventory().getFormat();
+
+ String sql = "update ImageBackupStorageRefVO set installPath = :installPath " +
+ "where backupStorageUuid = :bsUuid and imageUuid = :imageUuid";
+ Query q = dbf.getEntityManager().createQuery(sql);
+ q.setParameter("installPath", cmd.installPath);
+ q.setParameter("bsUuid", msg.getBackupStorageUuid());
+ q.setParameter("imageUuid", msg.getImageInventory().getUuid());
+ q.executeUpdate();
+
+ final DownloadImageReply reply = new DownloadImageReply();
+ logger.debug("---------localfile----------100--------");
+ if (cmd.url.startsWith("file://")){
+ logger.debug("---------localfile----------101--------");
+ checklocalfilenode(cmd.url);
+ }
+ if (snd.getIsLocalFile()){
+ logger.debug("---------localfile----------003--------");
+ logger.debug(snd.getNodeIp());
+ snd.setIsLocalFile(false);
+ snd.setSign(0);
+ singleCall(DOWNLOAD_IMAGE_PATH, cmd, DownloadRsp.class,snd.getNodeIp(),new ReturnValueCompletion(msg){
+ @Override
+ public void success(DownloadRsp ret) {
+ snd.setSign(1);
+ reply.setInstallPath(cmd.installPath);
+ reply.setSize(ret.size);
+ long asize = ret.actualSize == null ? ret.size : ret.actualSize;
+ reply.setActualSize(asize);
+ reply.setMd5sum("not calculated");
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ snd.setSign(1);
+ bus.reply(msg, reply);
+ }
+
+ });
+ return;
+ }
+ httpCall(DOWNLOAD_IMAGE_PATH, cmd, DownloadRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(DownloadRsp ret) {
+ reply.setInstallPath(cmd.installPath);
+ reply.setSize(ret.size);
+
+ // current surfs has no way to get the actual size
+ // if we cannot get the actual size from HTTP, use the virtual size
+ long asize = ret.actualSize == null ? ret.size : ret.actualSize;
+ reply.setActualSize(asize);
+ reply.setMd5sum("not calculated");
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final DownloadVolumeMsg msg) {
+ final DownloadCmd cmd = new DownloadCmd();
+ cmd.url = msg.getUrl();
+ cmd.installPath = makeImageInstallPath(msg.getVolume().getUuid());
+ cmd.imageFormat = msg.getVolume().getFormat();
+
+ final DownloadVolumeReply reply = new DownloadVolumeReply();
+ httpCall(DOWNLOAD_IMAGE_PATH, cmd, DownloadRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(DownloadRsp ret) {
+ reply.setInstallPath(cmd.installPath);
+ reply.setSize(ret.size);
+ reply.setMd5sum("not calculated");
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Transactional(readOnly = true)
+ private boolean canDelete(String installPath) {
+ String sql = "select count(c)" +
+ " from ImageBackupStorageRefVO img, ImageCacheVO c" +
+ " where img.imageUuid = c.imageUuid" +
+ " and img.backupStorageUuid = :bsUuid" +
+ " and img.installPath = :installPath";
+ TypedQuery q = dbf.getEntityManager().createQuery(sql, Long.class);
+ q.setParameter("bsUuid", self.getUuid());
+ q.setParameter("installPath", installPath);
+ return q.getSingleResult() == 0;
+ }
+
+ @Override
+ protected void handle(final GetImageSizeOnBackupStorageMsg msg){
+ //TODO
+ throw new CloudRuntimeException(String.format("not implemented"));
+ }
+
+ @Override
+ protected void handle(final DeleteBitsOnBackupStorageMsg msg) {
+ final DeleteBitsOnBackupStorageReply reply = new DeleteBitsOnBackupStorageReply();
+ if (!canDelete(msg.getInstallPath())) {
+ //TODO: GC, the image is still referred, need to cleanup
+ bus.reply(msg, reply);
+ return;
+ }
+ DeleteCmd cmd = new DeleteCmd();
+ cmd.installPath = msg.getInstallPath();
+
+ httpCall(DELETE_IMAGE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ //TODO GC, instead of error
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(DeleteRsp ret) {
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(BackupStorageAskInstallPathMsg msg) {
+ BackupStorageAskInstallPathReply reply = new BackupStorageAskInstallPathReply();
+ reply.setInstallPath(makeImageInstallPath(msg.getImageUuid()));
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ protected void handle(final SyncImageSizeOnBackupStorageMsg msg) {
+ GetImageSizeCmd cmd = new GetImageSizeCmd();
+ cmd.imageUuid = msg.getImage().getUuid();
+
+ ImageBackupStorageRefInventory ref = CollectionUtils.find(msg.getImage().getBackupStorageRefs(), new Function() {
+ @Override
+ public ImageBackupStorageRefInventory call(ImageBackupStorageRefInventory arg) {
+ return self.getUuid().equals(arg.getBackupStorageUuid()) ? arg : null;
+ }
+ });
+
+ if (ref == null) {
+ throw new CloudRuntimeException(String.format("cannot find ImageBackupStorageRefInventory of image[uuid:%s] for" +
+ " the backup storage[uuid:%s]", msg.getImage().getUuid(), self.getUuid()));
+ }
+
+ final SyncImageSizeOnBackupStorageReply reply = new SyncImageSizeOnBackupStorageReply();
+ cmd.installPath = ref.getInstallPath();
+ httpCall(GET_IMAGE_SIZE_PATH, cmd, GetImageSizeRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(GetImageSizeRsp rsp) {
+ reply.setSize(rsp.size);
+
+ // current surfs cannot get actual size
+ long asize = rsp.actualSize == null ? msg.getImage().getActualSize() : rsp.actualSize;
+ reply.setActualSize(asize);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+ private static class SelectedNode{
+ private String nodeip;
+ private long size;
+ private int sign;
+ private boolean isLocalFile=false;
+ public boolean getIsLocalFile(){
+ return this.isLocalFile;
+ }
+ public void setIsLocalFile(boolean islfile){
+ this.isLocalFile=islfile;
+ }
+ public int getSign()
+ {
+ return this.sign;
+ }
+ public void setSign(int csign){
+ this.sign=csign;
+ }
+ public String getNodeIp(){
+ return this.nodeip;
+ }
+ public void setNodeIp(String hostip){
+ this.nodeip=hostip;
+ }
+ public long getSize(){
+ return this.size;
+ }
+ public void setSize(long fsize){
+ this.size=fsize;
+ }
+ }
+ private static SelectedNode snd=new SelectedNode();
+ private void checklocalfilenode(String fileurl){
+ GetLocalFileSizeCmd cmd = new GetLocalFileSizeCmd();
+ cmd.path = fileurl;
+ snd.setSize(0);
+ for (SurfsBackupStorageNodeVO node :getSelf().getNodes()){
+ snd.setSign(0);
+ singleCall(GET_LOCAL_FILE_SIZE, cmd,GetLocalFileSizeRsp.class,node.getHostname(),new ReturnValueCompletion(null){
+ @Override
+ public void success(GetLocalFileSizeRsp ret) {
+ logger.debug(String.valueOf(ret.size));
+ snd.setSize(ret.size);
+ snd.setSign(1);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ snd.setSign(1);
+ }
+
+ });
+ int sk=0;
+ while (true) {
+ if (snd.getSign() >0){
+ break;
+ }else{
+ try{
+ Thread.sleep(500);
+ }catch(Exception ex){
+ logger.debug(String.format("Error to sleep for getlocalfilesize from[%s]",node.getHostname()));
+ }
+ }
+ if (sk > 20){
+ break;
+ }
+ sk = sk + 1;
+ }
+ if (snd.getSize() >0){
+ logger.debug(node.getHostname());
+ snd.setNodeIp(node.getHostname());
+ break;
+ }
+ }
+ if (snd.getSize() >0){
+ logger.debug(snd.getNodeIp());
+ snd.setIsLocalFile(true);
+ }
+ }
+ @Override
+ protected void handle(GetLocalFileSizeOnBackupStorageMsg msg) {
+ GetLocalFileSizeOnBackupStorageReply reply = new GetLocalFileSizeOnBackupStorageReply();
+ GetLocalFileSizeCmd cmd = new GetLocalFileSizeCmd();
+ cmd.path = msg.getUrl();
+ class EndSign{
+ int sign;
+ long size;
+ public void setSign(int ss){
+ this.sign=ss;
+ }
+ public int getSign(){
+ return this.sign;
+ }
+ public void setSize(long sz){
+ this.size=sz;
+ }
+ public long getSize(){
+ return this.size;
+ }
+ }
+ EndSign mysign=new EndSign();
+ for (SurfsBackupStorageNodeVO node :getSelf().getNodes()){
+ mysign.setSign(0);
+ singleCall(GET_LOCAL_FILE_SIZE, cmd,GetLocalFileSizeRsp.class,node.getHostname(),new ReturnValueCompletion(null){
+ @Override
+ public void success(GetLocalFileSizeRsp ret) {
+ mysign.setSize(ret.size);
+ reply.setSuccess(true);
+ mysign.setSign(1);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ mysign.setSize(0);
+ mysign.setSign(1);
+ }
+
+ });
+ int sk=0;
+ while (true) {
+ if (mysign.getSign() >0){
+ break;
+ }else{
+ try{
+ Thread.sleep(500);
+ }catch(Exception ex){
+ logger.debug(String.format("Error to sleep for getlocalfilesize from[%s]",node.getHostname()));
+ }
+ }
+ if (sk > 20){
+ break;
+ }
+ sk = sk + 1;
+ }
+ if (mysign.getSize() >0){
+ break;
+ }
+ }
+ if (mysign.getSize() >0){
+ reply.setSize(mysign.getSize());
+ bus.reply(msg,reply);
+ }else{
+ bus.reply(msg, reply);
+ }
+
+ }
+
+ @Override
+ protected void connectHook(final boolean newAdded, final Completion completion) {
+ final List nodes = CollectionUtils.transformToList(getSelf().getNodes(), new Function() {
+ @Override
+ public SurfsBackupStorageNodeBase call(SurfsBackupStorageNodeVO arg) {
+ return new SurfsBackupStorageNodeBase(arg);
+ }
+ });
+
+ class Connector {
+ List errorCodes = new ArrayList();
+ Iterator it = nodes.iterator();
+
+ void connect(final FlowTrigger trigger) {
+ if (!it.hasNext()) {
+ if (errorCodes.size() == nodes.size()) {
+ trigger.fail(errf.stringToOperationError(
+ String.format("unable to connect to the surfs backup storage[uuid:%s]. Failed to connect all surfs nodes. Errors are %s",
+ self.getUuid(), JSONObjectUtil.toJsonString(errorCodes))
+ ));
+ } else {
+ // reload because node status changed
+ self = dbf.reload(self);
+ trigger.next();
+ }
+ return;
+ }
+
+ final SurfsBackupStorageNodeBase base = it.next();
+ base.connect(new Completion(completion) {
+ @Override
+ public void success() {
+ connect(trigger);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ errorCodes.add(errorCode);
+
+ if (newAdded) {
+ // the node fails to connect, remove it
+ dbf.remove(base.getSelf());
+ }
+
+ connect(trigger);
+ }
+ });
+ }
+ }
+
+ FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("connect-surfs-backup-storage-%s", self.getUuid()));
+ chain.then(new ShareFlow() {
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "connect-monitor";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ new Connector().connect(trigger);
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "check-mon-integrity";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ final Map fsids = new HashMap();
+
+ final List nodes = CollectionUtils.transformToList(getSelf().getNodes(), new Function() {
+ @Override
+ public SurfsBackupStorageNodeBase call(SurfsBackupStorageNodeVO arg) {
+ return arg.getStatus() == NodeStatus.Connected ? new SurfsBackupStorageNodeBase(arg) : null;
+ }
+ });
+
+ DebugUtils.Assert(!nodes.isEmpty(), "how can be no connected node!!! ???");
+
+ final AsyncLatch latch = new AsyncLatch(nodes.size(), new NoErrorCompletion(trigger) {
+ @Override
+ public void done() {
+ Set set = new HashSet();
+ set.addAll(fsids.values());
+
+ if (set.size() != 1) {
+ StringBuilder sb = new StringBuilder("the fsid returned by nodes are mismatching, it seems the nodes belong to different surfs clusters:\n");
+ for (SurfsBackupStorageNodeBase node : nodes) {
+ String fsid = fsids.get(node.getSelf().getUuid());
+ sb.append(String.format("%s (node ip) --> %s (fsid)\n", node.getSelf().getHostname(), fsid));
+ }
+
+ throw new OperationFailureException(errf.stringToOperationError(sb.toString()));
+ }
+
+ // check if there is another surfs setup having the same fsid
+ String fsId = set.iterator().next();
+
+ SimpleQuery q = dbf.createQuery(SurfsBackupStorageVO.class);
+ q.add(SurfsBackupStorageVO_.fsid, Op.EQ, fsId);
+ q.add(SurfsBackupStorageVO_.uuid, Op.NOT_EQ, self.getUuid());
+ SurfsBackupStorageVO othersurfs = q.find();
+ if (othersurfs != null) {
+ throw new OperationFailureException(errf.stringToOperationError(
+ String.format("there is another surfs backup storage[name:%s, uuid:%s] with the same" +
+ " FSID[%s], you cannot add the same surfs setup as two different backup storage",
+ othersurfs.getName(), othersurfs.getUuid(), fsId)
+ ));
+ }
+
+ trigger.next();
+ }
+ });
+
+ for (final SurfsBackupStorageNodeBase node : nodes) {
+ GetFactsCmd cmd = new GetFactsCmd();
+ cmd.uuid = self.getUuid();
+ cmd.nodeUuid = node.getSelf().getUuid();
+ node.httpCall(GET_FACTS, cmd, GetFactsRsp.class, new ReturnValueCompletion(latch) {
+ @Override
+ public void success(GetFactsRsp rsp) {
+ if (!rsp.success) {
+ // one node cannot get the facts, directly error out
+ trigger.fail(errf.stringToOperationError(rsp.error));
+ return;
+ }
+
+ fsids.put(node.getSelf().getUuid(), rsp.fsid);
+ latch.ack();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ // one node cannot get the facts, directly error out
+ trigger.fail(errorCode);
+ }
+ });
+ }
+
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "init";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ InitCmd cmd = new InitCmd();
+ Pool p = new Pool();
+ p.name = getSelf().getPoolName();
+ p.predefined = SurfsSystemTags.PREDEFINED_BACKUP_STORAGE_POOL.hasTag(self.getUuid());
+ cmd.pools = list(p);
+
+ httpCall(INIT_PATH, cmd, InitRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void fail(ErrorCode err) {
+ trigger.fail(err);
+ }
+
+ @Override
+ public void success(InitRsp ret) {
+ if (getSelf().getFsid() == null) {
+ getSelf().setFsid(ret.fsid);
+ self = dbf.updateAndRefresh(self);
+ }
+
+ SurfsCapacityUpdater updater = new SurfsCapacityUpdater();
+ updater.update(ret.fsid, ret.totalCapacity, ret.availableCapacity, true);
+ trigger.next();
+ }
+ });
+ }
+ });
+
+ done(new FlowDoneHandler(completion) {
+ @Override
+ public void handle(Map data) {
+ completion.success();
+ }
+ });
+
+ error(new FlowErrorHandler(completion) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ if (newAdded) {
+ self = dbf.reload(self);
+ if (!getSelf().getNodes().isEmpty()) {
+ dbf.removeCollection(getSelf().getNodes(), SurfsBackupStorageNodeVO.class);
+ }
+ }
+
+ completion.fail(errCode);
+ }
+ });
+ }
+ }).start();
+ }
+
+ @Override
+ protected void pingHook(final Completion completion) {
+ final List mons = CollectionUtils.transformToList(getSelf().getNodes(), new Function() {
+ @Override
+ public SurfsBackupStorageNodeBase call(SurfsBackupStorageNodeVO arg) {
+ return new SurfsBackupStorageNodeBase(arg);
+ }
+ });
+
+ final List errors = new ArrayList();
+
+ class Ping {
+ private AtomicBoolean replied = new AtomicBoolean(false);
+
+ @AsyncThread
+ private void reconnectMon(final SurfsBackupStorageNodeBase mon, boolean delay) {
+ if (!SurfsGlobalConfig.BACKUP_STORAGE_MON_AUTO_RECONNECT.value(Boolean.class)) {
+ logger.debug(String.format("do not reconnect the surfs backup storage node[uuid:%s] as the global config[%s] is set to false",
+ self.getUuid(), SurfsGlobalConfig.BACKUP_STORAGE_MON_AUTO_RECONNECT.getCanonicalName()));
+ return;
+ }
+
+ // there has been a reconnect in process
+ if (!reconnectNodeLock.lock()) {
+ return;
+ }
+
+ final NoErrorCompletion releaseLock = new NoErrorCompletion() {
+ @Override
+ public void done() {
+ reconnectNodeLock.unlock();
+ }
+ };
+
+ try {
+ if (delay) {
+ try {
+ TimeUnit.SECONDS.sleep(SurfsGlobalConfig.BACKUP_STORAGE_MON_RECONNECT_DELAY.value(Long.class));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ mon.connect(new Completion(releaseLock) {
+ @Override
+ public void success() {
+ logger.debug(String.format("successfully reconnected the node[uuid:%s] of the surfs backup" +
+ " storage[uuid:%s, name:%s]", mon.getSelf().getUuid(), self.getUuid(), self.getName()));
+ releaseLock.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ //TODO
+ logger.warn(String.format("failed to reconnect the node[uuid:%s] of the surfs backup" +
+ " storage[uuid:%s, name:%s], %s", mon.getSelf().getUuid(), self.getUuid(), self.getName(), errorCode));
+ releaseLock.done();
+ }
+ });
+ } catch (Throwable t) {
+ releaseLock.done();
+ logger.warn(t.getMessage(), t);
+ }
+ }
+
+ void ping() {
+ // this is only called when all mons are disconnected
+ final AsyncLatch latch = new AsyncLatch(mons.size(), new NoErrorCompletion() {
+ @Override
+ public void done() {
+ if (!replied.compareAndSet(false, true)) {
+ return;
+ }
+
+ ErrorCode err = errf.stringToOperationError(String.format("failed to ping the surfs backup storage[uuid:%s, name:%s]",
+ self.getUuid(), self.getName()), errors);
+ completion.fail(err);
+ }
+ });
+
+ for (final SurfsBackupStorageNodeBase mon : mons) {
+ mon.ping(new ReturnValueCompletion(latch) {
+ private void thisMonIsDown(ErrorCode err) {
+ //TODO
+ logger.warn(String.format("cannot ping node[uuid:%s] of the surfs backup storage[uuid:%s, name:%s], %s",
+ mon.getSelf().getUuid(), self.getUuid(), self.getName(), err));
+ errors.add(err);
+ mon.changeStatus(NodeStatus.Disconnected);
+ reconnectMon(mon, true);
+ latch.ack();
+ }
+
+ @Override
+ public void success(PingResult res) {
+ if (res.success) {
+ // as long as there is one mon working, the backup storage works
+ pingSuccess();
+
+ if (mon.getSelf().getStatus() == NodeStatus.Disconnected) {
+ reconnectMon(mon, false);
+ }
+
+ } else if (res.operationFailure) {
+ // as long as there is one mon saying the surfs not working, the backup storage goes down
+ logger.warn(String.format("the surfs backup storage[uuid:%s, name:%s] is down, as one node[uuid:%s] reports" +
+ " an operation failure[%s]", self.getUuid(), self.getName(), mon.getSelf().getUuid(), res.error));
+ backupStorageDown();
+ } else {
+ // this mon is down(success == false, operationFailure == false), but the backup storage may still work as other mons may work
+ ErrorCode errorCode = errf.stringToOperationError(res.error);
+ thisMonIsDown(errorCode);
+ }
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ thisMonIsDown(errorCode);
+ }
+ });
+ }
+ }
+
+ // this is called once a mon return an operation failure
+ private void backupStorageDown() {
+ if (!replied.compareAndSet(false, true)) {
+ return;
+ }
+
+ // set all mons to be disconnected
+ for (SurfsBackupStorageNodeBase base : mons) {
+ base.changeStatus(NodeStatus.Disconnected);
+ }
+
+ ErrorCode err = errf.stringToOperationError(String.format("failed to ping the backup primary storage[uuid:%s, name:%s]",
+ self.getUuid(), self.getName()), errors);
+ completion.fail(err);
+ }
+
+ private void pingSuccess() {
+ if (!replied.compareAndSet(false, true)) {
+ return;
+ }
+
+ completion.success();
+ }
+ }
+
+ new Ping().ping();
+ }
+
+ @Override
+ public List scanImages() {
+ return null;
+ }
+
+ @Override
+ protected void handleApiMessage(APIMessage msg) {
+ if (msg instanceof APIAddNodeToSurfsBackupStorageMsg) {
+ handle((APIAddNodeToSurfsBackupStorageMsg) msg);
+ } else if (msg instanceof APIUpdateSurfsBackupStorageNodeMsg){
+ handle((APIUpdateSurfsBackupStorageNodeMsg) msg);
+ } else if (msg instanceof APIRemoveNodeFromSurfsBackupStorageMsg) {
+ handle((APIRemoveNodeFromSurfsBackupStorageMsg) msg);
+ } else {
+ super.handleApiMessage(msg);
+ }
+ }
+
+ @Override
+ public void deleteHook() {
+ dbf.removeCollection(getSelf().getNodes(), SurfsBackupStorageNodeVO.class);
+ }
+
+
+ private void handle(final APIUpdateSurfsBackupStorageNodeMsg msg) {
+ final APIUpdateNodeToSurfsBackupStorageEvent evt = new APIUpdateNodeToSurfsBackupStorageEvent(msg.getId());
+ SurfsBackupStorageNodeVO monvo = dbf.findByUuid(msg.getNodeUuid(), SurfsBackupStorageNodeVO.class);
+ if (msg.getHostname() != null) {
+ monvo.setHostname(msg.getHostname());
+ }
+ if (msg.getNodePort() != null && msg.getNodePort() > 0 && msg.getNodePort() <= 65535) {
+ monvo.setNodePort(msg.getNodePort());
+ }
+ if (msg.getSshPort() != null && msg.getSshPort() > 0 && msg.getSshPort() <= 65535) {
+ monvo.setSshPort(msg.getSshPort());
+ }
+ if (msg.getSshUsername() != null) {
+ monvo.setSshUsername(msg.getSshUsername());
+ }
+ if (msg.getSshPassword() != null) {
+ monvo.setSshPassword(msg.getSshPassword());
+ }
+ dbf.update(monvo);
+ evt.setInventory(SurfsBackupStorageInventory.valueOf(dbf.reload(getSelf())));
+ bus.publish(evt);
+ }
+
+ private void handle(APIRemoveNodeFromSurfsBackupStorageMsg msg) {
+ SimpleQuery q = dbf.createQuery(SurfsBackupStorageNodeVO.class);
+ q.add(SurfsBackupStorageNodeVO_.hostname, Op.IN, msg.getNodeHostnames());
+ q.add(SurfsBackupStorageNodeVO_.backupStorageUuid, Op.EQ, self.getUuid());
+ List vos = q.list();
+
+ if (!vos.isEmpty()) {
+ dbf.removeCollection(vos, SurfsBackupStorageNodeVO.class);
+ }
+
+ APIRemoveNodeFromSurfsBackupStorageEvent evt = new APIRemoveNodeFromSurfsBackupStorageEvent(msg.getId());
+ evt.setInventory(SurfsBackupStorageInventory.valueOf(dbf.reload(getSelf())));
+ bus.publish(evt);
+ }
+
+
+ private void handle(final APIAddNodeToSurfsBackupStorageMsg msg) {
+ final APIAddNodeToSurfsBackupStorageEvent evt = new APIAddNodeToSurfsBackupStorageEvent(msg.getId());
+
+ FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("add-node-Surfs-backup-storage-%s", self.getUuid()));
+ chain.then(new ShareFlow() {
+ List monVOs = new ArrayList();
+
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "node-if-exist";
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ for (String url : msg.getNodeUrls()){
+ NodeUri uri = new NodeUri(url);
+ for (SurfsBackupStorageNodeVO nov :getSelf().getNodes()){
+ if (nov.getHostname().equals(uri.getHostname())){
+ trigger.fail(errf.stringToInternalError(
+ String.format("the node[%s] is exists in surfsbackupstorage[%s]",nov.getHostname(),getSelf().getUuid())
+ ));
+ }
+ }
+ }
+ trigger.next();
+ }
+ });
+ flow(new Flow() {
+ String __name__ = "create-node-in-db";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ for (String url : msg.getNodeUrls()) {
+ SurfsBackupStorageNodeVO monvo = new SurfsBackupStorageNodeVO();
+ NodeUri uri = new NodeUri(url);
+ monvo.setUuid(Platform.getUuid());
+ monvo.setStatus(NodeStatus.Connecting);
+ monvo.setHostname(uri.getHostname());
+ monvo.setNodeAddr(uri.getHostname());
+ monvo.setNodePort(uri.getNodePort());
+ monvo.setSshPort(uri.getSshPort());
+ monvo.setSshUsername(uri.getSshUsername());
+ monvo.setSshPassword(uri.getSshPassword());
+ monvo.setBackupStorageUuid(self.getUuid());
+ monVOs.add(monvo);
+ }
+
+ dbf.persistCollection(monVOs);
+ trigger.next();
+ }
+
+ @Override
+ public void rollback(FlowRollback trigger, Map data) {
+ dbf.removeCollection(monVOs, SurfsBackupStorageNodeVO.class);
+ trigger.rollback();
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "connect-mons";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ List bases = CollectionUtils.transformToList(monVOs, new Function() {
+ @Override
+ public SurfsBackupStorageNodeBase call(SurfsBackupStorageNodeVO arg) {
+ return new SurfsBackupStorageNodeBase(arg);
+ }
+ });
+
+ final List errorCodes = new ArrayList();
+ final AsyncLatch latch = new AsyncLatch(bases.size(), new NoErrorCompletion(trigger) {
+ @Override
+ public void done() {
+ if (!errorCodes.isEmpty()) {
+ trigger.fail(errf.instantiateErrorCode(SysErrors.OPERATION_ERROR, "unable to connect mons", errorCodes));
+ } else {
+ trigger.next();
+ }
+ }
+ });
+
+ for (SurfsBackupStorageNodeBase base : bases) {
+ base.connect(new Completion(trigger) {
+ @Override
+ public void success() {
+ latch.ack();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ errorCodes.add(errorCode);
+ latch.ack();
+ }
+ });
+ }
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "check-mon-integrity";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ List bases = CollectionUtils.transformToList(monVOs, new Function() {
+ @Override
+ public SurfsBackupStorageNodeBase call(SurfsBackupStorageNodeVO arg) {
+ return new SurfsBackupStorageNodeBase(arg);
+ }
+ });
+
+ final List errors = new ArrayList();
+
+ final AsyncLatch latch = new AsyncLatch(bases.size(), new NoErrorCompletion(trigger) {
+ @Override
+ public void done() {
+ // one fail, all fail
+ if (!errors.isEmpty()) {
+ trigger.fail(errf.instantiateErrorCode(SysErrors.OPERATION_ERROR, "unable to add mon to Surfs backup storage", errors));
+ } else {
+ trigger.next();
+ }
+ }
+ });
+
+ for (final SurfsBackupStorageNodeBase base : bases) {
+ GetFactsCmd cmd = new GetFactsCmd();
+ cmd.uuid = self.getUuid();
+ cmd.nodeUuid = base.getSelf().getUuid();
+ base.httpCall(GET_FACTS, cmd, GetFactsRsp.class, new ReturnValueCompletion(latch) {
+ @Override
+ public void success(GetFactsRsp rsp) {
+ if (!rsp.isSuccess()) {
+ errors.add(errf.stringToOperationError(rsp.getError()));
+ } else {
+ String fsid = rsp.fsid;
+ if (!getSelf().getFsid().equals(fsid)) {
+ errors.add(errf.stringToOperationError(
+ String.format("the mon[ip:%s] returns a fsid[%s] different from the current fsid[%s] of the cep cluster," +
+ "are you adding a mon not belonging to current cluster mistakenly?", base.getSelf().getHostname(), fsid, getSelf().getFsid())
+ ));
+ }
+ }
+
+ latch.ack();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ errors.add(errorCode);
+ latch.ack();
+ }
+ });
+ }
+ }
+ });
+
+ done(new FlowDoneHandler(msg) {
+ @Override
+ public void handle(Map data) {
+ evt.setInventory(SurfsBackupStorageInventory.valueOf(dbf.reload(getSelf())));
+ bus.publish(evt);
+ }
+ });
+
+ error(new FlowErrorHandler(msg) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ evt.setError(errCode);
+ bus.publish(evt);
+ }
+ });
+ }
+ }).start();
+ }
+
+ private void singleCall(String path, final AgentCommand cmd, final Class rspClass,String dsthost, final ReturnValueCompletion completion) {
+ restf.asyncJsonPost(String.format("http://%s:%s%s", dsthost, SurfsGlobalProperty.BACKUP_STORAGE_AGENT_PORT, path),
+ cmd, new JsonAsyncRESTCallback(completion) {
+ @Override
+ public void fail(ErrorCode err) {
+ completion.fail(err);
+ }
+
+ @Override
+ public void success(T ret) {
+ completion.success(ret);
+ }
+
+ @Override
+ public Class getReturnClass() {
+ return rspClass;
+ }
+ });
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageFactory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageFactory.java
new file mode 100644
index 00000000000..d3874346ce3
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageFactory.java
@@ -0,0 +1,133 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.transaction.annotation.Transactional;
+import org.zstack.core.CoreGlobalProperty;
+import org.zstack.core.Platform;
+import org.zstack.core.ansible.AnsibleFacade;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.header.Component;
+import org.zstack.header.storage.backup.*;
+import org.zstack.storage.surfs.*;
+import org.zstack.tag.SystemTagCreator;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+import javax.persistence.LockModeType;
+import javax.persistence.TypedQuery;
+
+import java.util.List;
+
+/**
+ * Created by zhouhaiping 2017-09-01
+ */
+public class SurfsBackupStorageFactory implements BackupStorageFactory, SurfsCapacityUpdateExtensionPoint, Component {
+ @Autowired
+ private DatabaseFacade dbf;
+ @Autowired
+ private AnsibleFacade asf;
+
+ public static final BackupStorageType type = new BackupStorageType(SurfsConstants.SURFS_BACKUP_STORAGE_TYPE);
+ private static final CLogger logger = Utils.getLogger(SurfsBackupStorageFactory.class);
+
+ static {
+ type.setOrder(431);
+ }
+
+ void init() {
+ type.setPrimaryStorageFinder(new BackupStorageFindRelatedPrimaryStorage() {
+ @Override
+ @Transactional(readOnly = true)
+ public List findRelatedPrimaryStorage(String backupStorageUuid) {
+ String sql = "select p.uuid from SurfsPrimaryStorageVO p, SurfsBackupStorageVO b where b.fsid = p.fsid" +
+ " and b.uuid = :buuid";
+ TypedQuery q = dbf.getEntityManager().createQuery(sql, String.class);
+ q.setParameter("buuid", backupStorageUuid);
+ return q.getResultList();
+ }
+ });
+ }
+
+ @Override
+ public BackupStorageType getBackupStorageType() {
+ return type;
+ }
+
+ @Override
+ @Transactional
+ public BackupStorageInventory createBackupStorage(final BackupStorageVO vo, APIAddBackupStorageMsg msg) {
+ APIAddSurfsBackupStorageMsg cmsg = (APIAddSurfsBackupStorageMsg)msg;
+ SurfsBackupStorageVO cvo = new SurfsBackupStorageVO(vo);
+ cvo.setType(SurfsConstants.SURFS_BACKUP_STORAGE_TYPE);
+ String poolName = cmsg.getPoolName() == null ? String.format("bak-t-%s", vo.getUuid()) : cmsg.getPoolName();
+ cvo.setPoolName(poolName);
+
+ dbf.getEntityManager().persist(cvo);
+
+ if (cmsg.getPoolName() != null) {
+ SystemTagCreator creator = SurfsSystemTags.PREDEFINED_BACKUP_STORAGE_POOL.newSystemTagCreator(cvo.getUuid());
+ creator.ignoreIfExisting = true;
+ creator.create();
+ }
+ for (String url : cmsg.getNodeUrls()) {
+ SurfsBackupStorageNodeVO monvo = new SurfsBackupStorageNodeVO();
+ NodeUri uri = new NodeUri(url);
+ monvo.setUuid(Platform.getUuid());
+ monvo.setStatus(NodeStatus.Connecting);
+ monvo.setHostname(uri.getHostname());
+ monvo.setNodeAddr(monvo.getHostname());
+ monvo.setNodePort(uri.getNodePort());
+ monvo.setSshPort(uri.getSshPort());
+ monvo.setSshUsername(uri.getSshUsername());
+ monvo.setSshPassword(uri.getSshPassword());
+ monvo.setBackupStorageUuid(cvo.getUuid());
+ dbf.getEntityManager().persist(monvo);
+ }
+
+ return BackupStorageInventory.valueOf(cvo);
+ }
+
+ @Override
+ public BackupStorage getBackupStorage(BackupStorageVO vo) {
+ SurfsBackupStorageVO cvo = dbf.findByUuid(vo.getUuid(), SurfsBackupStorageVO.class);
+ return new SurfsBackupStorageBase(cvo);
+ }
+
+ @Override
+ public BackupStorageInventory reload(String uuid) {
+ return SurfsBackupStorageInventory.valueOf(dbf.findByUuid(uuid, SurfsBackupStorageVO.class));
+ }
+
+ @Override
+ @Transactional
+ public void update(String fsid,long total, long avail) {
+ String sql = "select c from SurfsBackupStorageVO c where c.fsid = :fsid";
+ TypedQuery q = dbf.getEntityManager().createQuery(sql, SurfsBackupStorageVO.class);
+ q.setParameter("fsid", fsid);
+ q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
+ try {
+ SurfsBackupStorageVO vo = q.getSingleResult();
+ vo.setTotalCapacity(total);
+ vo.setAvailableCapacity(avail);
+ dbf.getEntityManager().merge(vo);
+
+ } catch (EmptyResultDataAccessException e) {
+ return;
+ }
+ }
+
+ @Override
+ public boolean start() {
+ if (!CoreGlobalProperty.UNIT_TEST_ON) {
+ asf.deployModule(SurfsGlobalProperty.BACKUP_STORAGE_MODULE_PATH, SurfsGlobalProperty.BACKUP_STORAGE_PLAYBOOK_NAME);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ return true;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageInventory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageInventory.java
new file mode 100644
index 00000000000..62c89a59597
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageInventory.java
@@ -0,0 +1,83 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.query.ExpandedQueries;
+import org.zstack.header.query.ExpandedQuery;
+import org.zstack.header.search.Inventory;
+import org.zstack.header.search.Parent;
+import org.zstack.header.storage.backup.BackupStorageInventory;
+import org.zstack.storage.surfs.SurfsConstants;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Created by zhouhaiping 2017-08-23
+ */
+@Inventory(mappingVOClass = SurfsBackupStorageVO.class, collectionValueOfMethod = "valueOf1",
+ parent = {@Parent(inventoryClass = BackupStorageInventory.class, type = SurfsConstants.SURFS_BACKUP_STORAGE_TYPE)}
+)
+@ExpandedQueries({
+ @ExpandedQuery(expandedField = "nodes", inventoryClass = SurfsBackupStorageNodeInventory.class,
+ foreignKey = "uuid", expandedInventoryKey = "backupStorageUuid")
+})
+public class SurfsBackupStorageInventory extends BackupStorageInventory {
+ private List nodes = new ArrayList();
+ private String fsid;
+ private String poolName;
+ private Integer sshPort;
+
+ public SurfsBackupStorageInventory(SurfsBackupStorageVO vo) {
+ super(vo);
+ nodes = SurfsBackupStorageNodeInventory.valueOf(vo.getNodes());
+ fsid = vo.getFsid();
+ poolName = vo.getPoolName();
+ }
+
+ public SurfsBackupStorageInventory() {
+ }
+ public Integer getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(Integer sshPort) {
+ this.sshPort = sshPort;
+ }
+
+ public String getPoolName() {
+ return poolName;
+ }
+
+ public void setPoolName(String poolName) {
+ this.poolName = poolName;
+ }
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+
+ public static SurfsBackupStorageInventory valueOf(SurfsBackupStorageVO vo) {
+ return new SurfsBackupStorageInventory(vo);
+ }
+
+ public static List valueOf1(Collection vos) {
+ List invs = new ArrayList();
+ for (SurfsBackupStorageVO vo : vos) {
+ invs.add(new SurfsBackupStorageInventory(vo));
+ }
+
+ return invs;
+ }
+
+ public List getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(List nodes) {
+ this.nodes = nodes;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageInventoryDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageInventoryDoc_zh_cn.groovy
new file mode 100644
index 00000000000..2df1d7d3ae3
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageInventoryDoc_zh_cn.groovy
@@ -0,0 +1,112 @@
+package org.zstack.storage.surfs.backup
+
+import org.zstack.storage.surfs.backup.SurfsBackupStorageNodeInventory
+import java.lang.Integer
+import java.lang.Long
+import java.lang.Long
+import java.sql.Timestamp
+import java.sql.Timestamp
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "nodes"
+ path "org.zstack.storage.surfs.backup.SurfsBackupStorageInventory.nodes"
+ desc "null"
+ type "List"
+ since "0.6"
+ clz SurfsBackupStorageNodeInventory.class
+ }
+ field {
+ name "fsid"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "poolName"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "sshPort"
+ desc ""
+ type "Integer"
+ since "0.6"
+ }
+ field {
+ name "uuid"
+ desc "资源的UUID,唯一标示该资源"
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "name"
+ desc "资源名称"
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "url"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "description"
+ desc "资源的详细描述"
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "totalCapacity"
+ desc ""
+ type "Long"
+ since "0.6"
+ }
+ field {
+ name "availableCapacity"
+ desc ""
+ type "Long"
+ since "0.6"
+ }
+ field {
+ name "type"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "state"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "status"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "createDate"
+ desc "创建时间"
+ type "Timestamp"
+ since "0.6"
+ }
+ field {
+ name "lastOpDate"
+ desc "最后一次修改时间"
+ type "Timestamp"
+ since "0.6"
+ }
+ field {
+ name "attachedZoneUuids"
+ desc ""
+ type "List"
+ since "0.6"
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeBase.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeBase.java
new file mode 100644
index 00000000000..d5ef6c425a3
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeBase.java
@@ -0,0 +1,310 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.CoreGlobalProperty;
+import org.zstack.core.Platform;
+import org.zstack.core.ansible.AnsibleGlobalProperty;
+import org.zstack.core.ansible.AnsibleRunner;
+import org.zstack.core.ansible.SshFileMd5Checker;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.SimpleQuery;
+import org.zstack.core.db.SimpleQuery.Op;
+import org.zstack.core.thread.ChainTask;
+import org.zstack.core.thread.SyncTaskChain;
+import org.zstack.core.thread.ThreadFacade;
+import org.zstack.core.workflow.FlowChainBuilder;
+import org.zstack.core.workflow.ShareFlow;
+import org.zstack.header.core.Completion;
+import org.zstack.header.core.ReturnValueCompletion;
+import org.zstack.header.core.workflow.*;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.rest.JsonAsyncRESTCallback;
+import org.zstack.storage.surfs.SurfsGlobalProperty;
+import org.zstack.storage.surfs.SurfsNodeAO;
+import org.zstack.storage.surfs.SurfsNodeBase;
+import org.zstack.storage.surfs.NodeStatus;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+import org.zstack.utils.path.PathUtil;
+
+import java.util.Map;
+
+/**
+ * Created by zhouhaiping 2017-09-07
+ */
+
+public class SurfsBackupStorageNodeBase extends SurfsNodeBase {
+ private static final CLogger logger = Utils.getLogger(SurfsBackupStorageNodeBase.class);
+
+ @Autowired
+ private DatabaseFacade dbf;
+ @Autowired
+ private ThreadFacade thdf;
+
+ private String syncId;
+
+ public static final String ECHO_PATH = "/surfs/backupstorage/echo";
+ public static final String PING_PATH = "/surfs/backupstorage/ping";
+
+ public static class AgentCmd {
+ public String NodeUuid;
+ public String backupStorageUuid;
+ }
+
+ public static class AgentRsp {
+ public boolean success;
+ public String error;
+ }
+
+ public static class PingCmd extends AgentCmd {
+ public String testImagePath;
+ }
+
+ public static class PingRsp extends AgentRsp {
+ public boolean operationFailure;
+ }
+
+ public SurfsBackupStorageNodeBase(SurfsNodeAO self) {
+ super(self);
+ syncId = String.format("surfs-backup-storage-node-%s", getSelf().getUuid());
+ }
+
+ public SurfsBackupStorageNodeVO getSelf() {
+ return (SurfsBackupStorageNodeVO) self;
+ }
+
+ public void changeStatus(NodeStatus status) {
+ if (self.getStatus() == status) {
+ return;
+ }
+
+ NodeStatus oldStatus = self.getStatus();
+ self.setStatus(status);
+ self = dbf.updateAndRefresh(self);
+ logger.debug(String.format("surfs backup storage node[uuid:%s] changed status from %s to %s",
+ self.getUuid(), oldStatus, status));
+ }
+
+ @Override
+ public void connect(final Completion completion) {
+ thdf.chainSubmit(new ChainTask(completion) {
+ @Override
+ public String getSyncSignature() {
+ return syncId;
+ }
+
+ @Override
+ public void run(final SyncTaskChain chain) {
+ doConnect(new Completion(completion, chain) {
+ @Override
+ public void success() {
+ completion.success();
+ chain.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ chain.next();
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return String.format("connect-surfs-backup-storage-node-%s", self.getUuid());
+ }
+ });
+ }
+
+ private void doConnect(final Completion completion) {
+ changeStatus(NodeStatus.Connecting);
+
+ final FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("connect-node-%s-surfs-backup-storage-%s", self.getHostname(), getSelf().getBackupStorageUuid()));
+ chain.allowEmptyFlow();
+ chain.then(new ShareFlow() {
+ @Override
+ public void setup() {
+ if (!CoreGlobalProperty.UNIT_TEST_ON) {
+ flow(new NoRollbackFlow() {
+ String __name__ = "check-tools";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ checkTools();
+ trigger.next();
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "deploy-agent";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+
+ SshFileMd5Checker checker = new SshFileMd5Checker();
+ checker.setTargetIp(getSelf().getHostname());
+ checker.setUsername(getSelf().getSshUsername());
+ checker.setPassword(getSelf().getSshPassword());
+ checker.addSrcDestPair(SshFileMd5Checker.ZSTACKLIB_SRC_PATH, String.format("/var/lib/zstack/surfsb/package/%s", AnsibleGlobalProperty.ZSTACKLIB_PACKAGE_NAME));
+ checker.addSrcDestPair(PathUtil.findFileOnClassPath(String.format("ansible/surfsb/%s", SurfsGlobalProperty.BACKUP_STORAGE_PACKAGE_NAME), true).getAbsolutePath(),
+ String.format("/var/lib/zstack/surfsb/package/%s", SurfsGlobalProperty.BACKUP_STORAGE_PACKAGE_NAME));
+ AnsibleRunner runner = new AnsibleRunner();
+ runner.installChecker(checker);
+ runner.setPassword(getSelf().getSshPassword());
+ runner.setUsername(getSelf().getSshUsername());
+ runner.setTargetIp(getSelf().getHostname());
+ runner.setSshPort(getSelf().getSshPort());
+ runner.setAgentPort(SurfsGlobalProperty.BACKUP_STORAGE_AGENT_PORT);
+ runner.setPlayBookName(SurfsGlobalProperty.BACKUP_STORAGE_PLAYBOOK_NAME);
+ runner.putArgument("pkg_surfsbagent", SurfsGlobalProperty.BACKUP_STORAGE_PACKAGE_NAME);
+ runner.run(new Completion(trigger) {
+ @Override
+ public void success() {
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "echo-agent";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ restf.echo(String.format("http://%s:%s%s", getSelf().getHostname(),
+ SurfsGlobalProperty.BACKUP_STORAGE_AGENT_PORT, ECHO_PATH), new Completion(trigger) {
+ @Override
+ public void success() {
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+ }
+
+ done(new FlowDoneHandler(completion) {
+ @Override
+ public void handle(Map data) {
+ changeStatus(NodeStatus.Connected);
+ completion.success();
+ }
+ });
+
+ error(new FlowErrorHandler(completion) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ changeStatus(NodeStatus.Disconnected);
+ completion.fail(errCode);
+ }
+ });
+ }
+ }).start();
+ }
+
+ private void httpCall(String path, AgentCmd cmd, final ReturnValueCompletion completion) {
+ httpCall(path, cmd, AgentRsp.class, completion);
+ }
+
+ private void httpCall(String path, AgentCmd cmd, final Class rspClass, final ReturnValueCompletion completion) {
+ restf.asyncJsonPost(String.format("http://%s:%s%s", self.getHostname(), SurfsGlobalProperty.BACKUP_STORAGE_AGENT_PORT, path),
+ cmd, new JsonAsyncRESTCallback(completion) {
+ @Override
+ public void fail(ErrorCode err) {
+ completion.fail(err);
+ }
+
+ @Override
+ public void success(T ret) {
+ completion.success(ret);
+ }
+
+ @Override
+ public Class getReturnClass() {
+ return rspClass;
+ }
+ });
+ }
+
+ @Override
+ public void ping(final ReturnValueCompletion completion) {
+ thdf.chainSubmit(new ChainTask(completion) {
+ @Override
+ public String getSyncSignature() {
+ return syncId;
+ }
+
+ @Override
+ public void run(final SyncTaskChain chain) {
+ doPing(new ReturnValueCompletion(completion, chain) {
+ @Override
+ public void success(PingResult ret) {
+ completion.success(ret);
+ chain.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ chain.next();
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return String.format("ping-surfs-backup-storage-node-%s", self.getUuid());
+ }
+ });
+ }
+
+ @Override
+ protected int getAgentPort() {
+ return SurfsGlobalProperty.BACKUP_STORAGE_AGENT_PORT;
+ }
+
+ public void doPing(final ReturnValueCompletion completion) {
+ SimpleQuery q = dbf.createQuery(SurfsBackupStorageVO.class);
+ q.select(SurfsBackupStorageVO_.poolName);
+ q.add(SurfsBackupStorageVO_.uuid, Op.EQ, getSelf().getBackupStorageUuid());
+ String poolName = q.findValue();
+
+ PingCmd cmd = new PingCmd();
+ cmd.testImagePath = String.format("%s/%s-this-is-a-test-image-with-long-name", poolName, Platform.getUuid());
+ cmd.NodeUuid = getSelf().getUuid();
+ cmd.backupStorageUuid = getSelf().getBackupStorageUuid();
+
+ httpCall(PING_PATH, cmd, PingRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(PingRsp rsp) {
+ PingResult res = new PingResult();
+ if (rsp.success) {
+ res.success = true;
+ } else {
+ res.success = false;
+ res.error = rsp.error;
+ res.operationFailure = rsp.operationFailure;
+ }
+
+ completion.success(res);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeInventory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeInventory.java
new file mode 100644
index 00000000000..2214e86079a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeInventory.java
@@ -0,0 +1,137 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.search.Inventory;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Created by zhouhaiping 2017-09-08.
+ */
+@Inventory(mappingVOClass = SurfsBackupStorageNodeVO.class)
+public class SurfsBackupStorageNodeInventory {
+ private String hostname;
+ private Integer nodePort;
+ private Timestamp createDate;
+ private Timestamp lastOpDate;
+ private String backupStorageUuid;
+ private String sshUsername;
+ private String sshPassword;
+ private Integer sshPort;
+ private String status;
+ private String nodeAddr;
+ private String nodeUuid;
+
+ public static SurfsBackupStorageNodeInventory valueOf(SurfsBackupStorageNodeVO vo) {
+ SurfsBackupStorageNodeInventory inv = new SurfsBackupStorageNodeInventory();
+ inv.setHostname(vo.getHostname());
+ inv.setNodePort(vo.getNodePort());
+ inv.setCreateDate(vo.getCreateDate());
+ inv.setLastOpDate(vo.getLastOpDate());
+ inv.setBackupStorageUuid(vo.getBackupStorageUuid());
+ inv.setSshPort(vo.getSshPort());
+ inv.setSshPassword(vo.getSshPassword());
+ inv.setSshUsername(vo.getSshUsername());
+ inv.setStatus(vo.getStatus().toString());
+ inv.setNodeUuid(vo.getUuid());
+ inv.setNodeAddr(vo.getHostname());
+ return inv;
+ }
+
+ public static List valueOf(Collection vos) {
+ List invs = new ArrayList();
+ for (SurfsBackupStorageNodeVO vo : vos) {
+ invs.add(valueOf(vo));
+ }
+
+ return invs;
+ }
+
+ public String getSshPassword() {
+
+ return sshPassword;
+ }
+
+ public void setSshPassword(String sshPassword) {
+ this.sshPassword = sshPassword;
+ }
+ public String getSshUsername() {
+ return sshUsername;
+ }
+
+ public void setSshUsername(String sshUsername) {
+ this.sshUsername = sshUsername;
+ }
+ public Integer getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(Integer sshPort) {
+ this.sshPort = sshPort;
+ }
+ public String getBackupStorageUuid() {
+ return backupStorageUuid;
+ }
+
+ public void setBackupStorageUuid(String backupStorageUuid) {
+ this.backupStorageUuid = backupStorageUuid;
+ }
+
+ public Integer getNodePort() {
+ return nodePort;
+ }
+
+ public void setNodePort(Integer nodePort) {
+ this.nodePort = nodePort;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getNodeAddr() {
+ return nodeAddr;
+ }
+
+ public void setNodeAddr(String nodeAddr) {
+ this.nodeAddr = nodeAddr;
+ }
+
+ public String getNodeUuid() {
+ return nodeUuid;
+ }
+
+ public void setNodeUuid(String nodeUuid) {
+ this.nodeUuid = nodeUuid;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeInventoryDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeInventoryDoc_zh_cn.groovy
new file mode 100644
index 00000000000..c8022d7e23a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeInventoryDoc_zh_cn.groovy
@@ -0,0 +1,78 @@
+package org.zstack.storage.surfs.backup
+
+import java.lang.Integer
+import java.sql.Timestamp
+import java.sql.Timestamp
+import java.lang.Integer
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ field {
+ name "hostname"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "nodePort"
+ desc ""
+ type "Integer"
+ since "0.6"
+ }
+ field {
+ name "createDate"
+ desc "创建时间"
+ type "Timestamp"
+ since "0.6"
+ }
+ field {
+ name "lastOpDate"
+ desc "最后一次修改时间"
+ type "Timestamp"
+ since "0.6"
+ }
+ field {
+ name "backupStorageUuid"
+ desc "镜像存储UUID"
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "sshUsername"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "sshPassword"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "sshPort"
+ desc ""
+ type "Integer"
+ since "0.6"
+ }
+ field {
+ name "status"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "nodeAddr"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "nodeUuid"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeVO.java
new file mode 100644
index 00000000000..adbad648c1e
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeVO.java
@@ -0,0 +1,34 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.storage.backup.BackupStorageEO;
+import org.zstack.header.storage.backup.BackupStorageVO;
+import org.zstack.header.vo.ForeignKey;
+import org.zstack.header.vo.ForeignKey.ReferenceOption;
+import org.zstack.header.vo.SoftDeletionCascade;
+import org.zstack.header.vo.SoftDeletionCascades;
+import org.zstack.storage.surfs.SurfsNodeAO;
+
+import javax.persistence.*;
+
+/**
+ * Created by zhouhaiping 2017-08-31
+ */
+@Entity
+@Table
+@Inheritance(strategy= InheritanceType.JOINED)
+@SoftDeletionCascades({
+ @SoftDeletionCascade(parent = BackupStorageVO.class, joinColumn = "backupStorageUuid")
+})
+public class SurfsBackupStorageNodeVO extends SurfsNodeAO {
+ @Column
+ @ForeignKey(parentEntityClass = BackupStorageEO.class, parentKey = "uuid", onDeleteAction = ReferenceOption.CASCADE)
+ private String backupStorageUuid;
+
+ public String getBackupStorageUuid() {
+ return backupStorageUuid;
+ }
+
+ public void setBackupStorageUuid(String backupStorageUuid) {
+ this.backupStorageUuid = backupStorageUuid;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeVO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeVO_.java
new file mode 100644
index 00000000000..75cddc093b4
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageNodeVO_.java
@@ -0,0 +1,12 @@
+package org.zstack.storage.surfs.backup;
+import org.zstack.storage.surfs.SurfsNodeAO_;
+import javax.persistence.metamodel.SingularAttribute;
+import javax.persistence.metamodel.StaticMetamodel;
+
+/**
+ * Created by frank on 7/29/2015.
+ */
+@StaticMetamodel(SurfsBackupStorageNodeVO.class)
+public class SurfsBackupStorageNodeVO_ extends SurfsNodeAO_ {
+ public static volatile SingularAttribute backupStorageUuid;
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageSimulator.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageSimulator.java
new file mode 100644
index 00000000000..5ae26469145
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageSimulator.java
@@ -0,0 +1,167 @@
+package org.zstack.storage.surfs.backup;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.SimpleQuery;
+import org.zstack.core.db.SimpleQuery.Op;
+import org.zstack.header.exception.CloudRuntimeException;
+import org.zstack.header.rest.RESTConstant;
+import org.zstack.header.rest.RESTFacade;
+import org.zstack.header.storage.backup.BackupStorageVO;
+import org.zstack.header.storage.backup.BackupStorageVO_;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageBase.*;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageSimulatorConfig.SurfsBackupStorageConfig;
+import org.zstack.utils.DebugUtils;
+import org.zstack.utils.Utils;
+import org.zstack.utils.gson.JSONObjectUtil;
+import org.zstack.utils.logging.CLogger;
+
+/**
+ * Created by frank on 7/28/2015.
+ */
+@Controller
+public class SurfsBackupStorageSimulator {
+ CLogger logger = Utils.getLogger(SurfsBackupStorageSimulator.class);
+
+ @Autowired
+ private SurfsBackupStorageSimulatorConfig config;
+ @Autowired
+ private DatabaseFacade dbf;
+ @Autowired
+ private RESTFacade restf;
+
+ public void reply(HttpEntity entity, Object rsp) {
+ String taskUuid = entity.getHeaders().getFirst(RESTConstant.TASK_UUID);
+ String callbackUrl = entity.getHeaders().getFirst(RESTConstant.CALLBACK_URL);
+ String rspBody = JSONObjectUtil.toJsonString(rsp);
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.setContentLength(rspBody.length());
+ headers.set(RESTConstant.TASK_UUID, taskUuid);
+ HttpEntity rreq = new HttpEntity(rspBody, headers);
+ restf.getRESTTemplate().exchange(callbackUrl, HttpMethod.POST, rreq, String.class);
+ }
+
+ private SurfsBackupStorageConfig getConfig(AgentCommand cmd) {
+ SimpleQuery q = dbf.createQuery(BackupStorageVO.class);
+ q.select(BackupStorageVO_.name);
+ q.add(BackupStorageVO_.uuid, Op.EQ, cmd.getUuid());
+ String name = q.findValue();
+
+ SurfsBackupStorageConfig c = config.config.get(name);
+ if (c == null) {
+ throw new CloudRuntimeException(String.format("cannot find SurfsBackupStorageConfig by name[%s], uuid[%s]", name, cmd.getUuid()));
+ }
+
+ c.name = name;
+
+ return c;
+ }
+
+ @RequestMapping(value= SurfsBackupStorageBase.GET_FACTS, method= RequestMethod.POST)
+ public @ResponseBody
+ String getFacts(HttpEntity entity) {
+ GetFactsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetFactsCmd.class);
+ GetFactsRsp rsp = new GetFactsRsp();
+
+ config.getFactsCmds.add(cmd);
+ String fsid = config.getFactsCmdFsid.get(cmd.nodeUuid);
+ if (fsid == null) {
+ SurfsBackupStorageConfig c = getConfig(cmd);
+ fsid = c.fsid;
+ }
+
+ rsp.fsid = fsid;
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value= SurfsBackupStorageNodeBase.PING_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String pingNode(HttpEntity entity) {
+ SurfsBackupStorageNodeBase.PingCmd cmd = JSONObjectUtil.toObject(entity.getBody(), SurfsBackupStorageNodeBase.PingCmd.class);
+ Boolean success = config.pingCmdSuccess.get(cmd.NodeUuid);
+ SurfsBackupStorageNodeBase.PingRsp rsp = new SurfsBackupStorageNodeBase.PingRsp();
+ rsp.success = success == null ? true : success;
+ if (!rsp.success) {
+ rsp.error = "on purpose";
+ }
+ Boolean operationFailure = config.pingCmdOperationFailure.get(cmd.NodeUuid);
+ rsp.operationFailure = operationFailure == null ? false : operationFailure;
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value=SurfsBackupStorageBase.GET_IMAGE_SIZE_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String getImageSize(HttpEntity entity) {
+ GetImageSizeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetImageSizeCmd.class);
+ config.getImageSizeCmds.add(cmd);
+
+ GetImageSizeRsp rsp = new GetImageSizeRsp();
+ Long size = config.getImageSizeCmdSize.get(cmd.imageUuid);
+ rsp.size = size == null ? 0 : size;
+ Long asize = config.getImageSizeCmdActualSize.get(cmd.imageUuid);
+ rsp.actualSize = asize == null ? 0 : asize;
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value=SurfsBackupStorageBase.INIT_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String initialize(HttpEntity entity) {
+ InitCmd cmd = JSONObjectUtil.toObject(entity.getBody(), InitCmd.class);
+ SurfsBackupStorageConfig cbc = getConfig(cmd);
+ config.initCmds.add(cmd);
+
+ DebugUtils.Assert(cbc.fsid != null, String.format("fsid for fusionstor backup storage[%s] is null", cbc.name));
+
+ InitRsp rsp = new InitRsp();
+
+ if (!config.monInitSuccess) {
+ rsp.error = "on purpose";
+ rsp.success = false;
+ } else {
+ rsp.fsid = cbc.fsid;
+ rsp.totalCapacity = cbc.totalCapacity;
+ rsp.availableCapacity = cbc.availCapacity;
+ }
+
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value=SurfsBackupStorageBase.DOWNLOAD_IMAGE_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String download(HttpEntity entity) {
+ DownloadRsp rsp = new DownloadRsp();
+ DownloadCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DownloadCmd.class);
+ config.downloadCmds.add(cmd);
+
+ Long size = config.imageSize.get(cmd.imageUuid);
+ rsp.setSize(size == null ? 0 : size);
+ Long asize = config.imageActualSize.get(cmd.imageUuid);
+ rsp.setActualSize(asize == null ? 0 : asize);
+
+ reply(entity, rsp);
+ return null;
+ }
+
+ @RequestMapping(value=SurfsBackupStorageBase.DELETE_IMAGE_PATH, method= RequestMethod.POST)
+ public @ResponseBody
+ String doDelete(HttpEntity entity) {
+ DeleteCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeleteCmd.class);
+ config.deleteCmds.add(cmd);
+ DeleteRsp rsp = new DeleteRsp();
+ reply(entity, rsp);
+ return null;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageSimulatorConfig.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageSimulatorConfig.java
new file mode 100644
index 00000000000..89911494778
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageSimulatorConfig.java
@@ -0,0 +1,38 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.storage.surfs.backup.SurfsBackupStorageBase.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by frank on 7/28/2015.
+ */
+public class SurfsBackupStorageSimulatorConfig {
+ public static class SurfsBackupStorageConfig {
+ public long totalCapacity;
+ public long availCapacity;
+ public String fsid;
+ public String name;
+ }
+
+ public volatile boolean monInitSuccess = true;
+ public List initCmds = new ArrayList();
+ public Map config = new HashMap();
+ public List downloadCmds = new ArrayList();
+ public List deleteCmds = new ArrayList();
+ public List pingCmds = new ArrayList();
+ public Map imageSize = new HashMap();
+ public Map imageActualSize = new HashMap();
+
+ public List getImageSizeCmds = new ArrayList();
+ public Map getImageSizeCmdSize = new HashMap();
+ public Map getImageSizeCmdActualSize = new HashMap();
+
+ public Map pingCmdSuccess = new HashMap();
+ public Map pingCmdOperationFailure = new HashMap();
+ public List getFactsCmds = new ArrayList();
+ public Map getFactsCmdFsid = new HashMap();
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageVO.java
new file mode 100644
index 00000000000..fb52b6feb43
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageVO.java
@@ -0,0 +1,73 @@
+package org.zstack.storage.surfs.backup;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.zstack.header.storage.backup.BackupStorageEO;
+import org.zstack.header.storage.backup.BackupStorageVO;
+import org.zstack.header.tag.AutoDeleteTag;
+import org.zstack.header.vo.EO;
+import org.zstack.header.vo.NoView;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageNodeVO;
+
+import javax.persistence.*;
+
+
+/**
+ * Created by zhouhaiping on 2017-07-18
+ */
+@Entity
+@Table
+@PrimaryKeyJoinColumn(name="uuid", referencedColumnName="uuid")
+@EO(EOClazz = BackupStorageEO.class, needView = false)
+@AutoDeleteTag
+public class SurfsBackupStorageVO extends BackupStorageVO {
+ @OneToMany(fetch= FetchType.EAGER)
+ @JoinColumn(name="backupStorageUuid", insertable=false, updatable=false)
+ @NoView
+ private Set nodes = new HashSet();
+
+ @Column
+ private String poolName;
+
+ public String getPoolName() {
+ return poolName;
+ }
+
+ public void setPoolName(String poolName) {
+ this.poolName = poolName;
+ }
+
+ @Column
+ private String fsid;
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+
+ this.fsid = fsid;
+ }
+
+ public SurfsBackupStorageVO() {
+ }
+
+ public SurfsBackupStorageVO(BackupStorageVO vo) {
+ super(vo);
+ }
+
+ public SurfsBackupStorageVO(SurfsBackupStorageVO other) {
+ super(other);
+ this.nodes=other.nodes;
+ }
+
+ public Set getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageVO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageVO_.java
new file mode 100644
index 00000000000..9d3034b8249
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/backup/SurfsBackupStorageVO_.java
@@ -0,0 +1,15 @@
+package org.zstack.storage.surfs.backup;
+
+import org.zstack.header.storage.backup.BackupStorageVO_;
+
+import javax.persistence.metamodel.SingularAttribute;
+import javax.persistence.metamodel.StaticMetamodel;
+
+/**
+ * Created by frank on 7/29/2015.
+ */
+@StaticMetamodel(SurfsBackupStorageVO.class)
+public class SurfsBackupStorageVO_ extends BackupStorageVO_ {
+ public static volatile SingularAttribute fsid;
+ public static volatile SingularAttribute poolName;
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageEvent.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageEvent.java
new file mode 100644
index 00000000000..10b3068e2f2
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageEvent.java
@@ -0,0 +1,35 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+/**
+ * Created by frank on 8/6/2015.
+ */
+@RestResponse(allTo = "inventory")
+public class APIAddNodeToSurfsPrimaryStorageEvent extends APIEvent {
+ private SurfsPrimaryStorageInventory inventory;
+
+ public APIAddNodeToSurfsPrimaryStorageEvent() {
+ }
+
+ public APIAddNodeToSurfsPrimaryStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public SurfsPrimaryStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(SurfsPrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public static APIAddNodeToSurfsPrimaryStorageEvent __example__() {
+ APIAddNodeToSurfsPrimaryStorageEvent event = new APIAddNodeToSurfsPrimaryStorageEvent();
+
+
+ return event;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageEventDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..00803022d0c
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.primary.SurfsPrimaryStorageInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.primary.APIAddNodeToSurfsPrimaryStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventory"
+ path "org.zstack.storage.surfs.primary.APIAddNodeToSurfsPrimaryStorageEvent.inventory"
+ desc "null"
+ type "SurfsPrimaryStorageInventory"
+ since "0.6"
+ clz SurfsPrimaryStorageInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageMsg.java
new file mode 100644
index 00000000000..6d24a4f3d1a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageMsg.java
@@ -0,0 +1,56 @@
+package org.zstack.storage.surfs.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.primary.PrimaryStorageMessage;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Created by frank on 8/6/2015.
+ */
+@RestRequest(
+ path = "/primary-storage/surfs/{uuid}/nodes",
+ method = HttpMethod.POST,
+ responseClass = APIAddNodeToSurfsPrimaryStorageEvent.class,
+ parameterName = "params"
+)
+public class APIAddNodeToSurfsPrimaryStorageMsg extends APIMessage implements PrimaryStorageMessage {
+ @APIParam(resourceType = SurfsPrimaryStorageVO.class)
+ private String uuid;
+ @APIParam(nonempty = true)
+ private List nodeUrls;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public List getNodeUrls() {
+ return nodeUrls;
+ }
+
+ public void setNodeUrls(List nodeUrls) {
+ this.nodeUrls = nodeUrls;
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return uuid;
+ }
+
+ public static APIAddNodeToSurfsPrimaryStorageMsg __example__() {
+ APIAddNodeToSurfsPrimaryStorageMsg msg = new APIAddNodeToSurfsPrimaryStorageMsg();
+ msg.setUuid(uuid());
+ msg.setNodeUrls(asList("root:password@localhost/?monPort=7777"));
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..40c336656c4
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddNodeToSurfsPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,72 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.storage.surfs.primary.APIAddNodeToSurfsPrimaryStorageEvent
+
+doc {
+ title "AddNodeToSurfsPrimaryStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "POST /v1/primary-storage/surfs/{uuid}/nodes"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIAddNodeToSurfsPrimaryStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn "params"
+ desc "资源的UUID,唯一标示该资源"
+ location "url"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "nodeUrls"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "List"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIAddNodeToSurfsPrimaryStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddSurfsPrimaryStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddSurfsPrimaryStorageMsg.java
new file mode 100644
index 00000000000..a827ecd453d
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddSurfsPrimaryStorageMsg.java
@@ -0,0 +1,87 @@
+package org.zstack.storage.surfs.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.message.OverriddenApiParam;
+import org.zstack.header.message.OverriddenApiParams;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.primary.APIAddPrimaryStorageEvent;
+import org.zstack.header.storage.primary.APIAddPrimaryStorageMsg;
+import org.zstack.storage.surfs.SurfsConstants;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Created by zhouhaiping 2017-09-11
+ */
+@OverriddenApiParams({
+ @OverriddenApiParam(field = "url", param = @APIParam(maxLength = 2048, required = false))
+})
+@RestRequest(
+ path = "/primary-storage/surfs",
+ method = HttpMethod.POST,
+ responseClass = APIAddPrimaryStorageEvent.class,
+ parameterName = "params"
+)
+public class APIAddSurfsPrimaryStorageMsg extends APIAddPrimaryStorageMsg {
+ @APIParam(nonempty = false, emptyString = false)
+ private List nodeUrls;
+ @APIParam(required = false, maxLength = 255)
+ private String rootVolumePoolName;
+ @APIParam(required = false, maxLength = 255)
+ private String dataVolumePoolName;
+ @APIParam(required = false, maxLength = 255)
+ private String imageCachePoolName;
+
+ public String getUrl() {
+ return "not used";
+ }
+
+ public String getRootVolumePoolName() {
+ return rootVolumePoolName;
+ }
+
+ public void setRootVolumePoolName(String rootVolumePoolName) {
+ this.rootVolumePoolName = rootVolumePoolName;
+ }
+
+ public String getDataVolumePoolName() {
+ return dataVolumePoolName;
+ }
+
+ public void setDataVolumePoolName(String dataVolumePoolName) {
+ this.dataVolumePoolName = dataVolumePoolName;
+ }
+
+ public String getImageCachePoolName() {
+ return imageCachePoolName;
+ }
+
+ public void setImageCachePoolName(String imageCachePoolName) {
+ this.imageCachePoolName = imageCachePoolName;
+ }
+
+ @Override
+ public String getType() {
+ return SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE;
+ }
+
+ public List getNodeUrls() {
+ return nodeUrls;
+ }
+
+ public void setNodeUrls(List nodeUrls) {
+ this.nodeUrls = nodeUrls;
+ }
+
+ public static APIAddSurfsPrimaryStorageMsg __example__() {
+ APIAddSurfsPrimaryStorageMsg msg = new APIAddSurfsPrimaryStorageMsg();
+ msg.setNodeUrls(asList("root:password@localhost/?monPort=7777"));
+ msg.setName("surfs");
+ msg.setZoneUuid(uuid());
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddSurfsPrimaryStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddSurfsPrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..0f751a92522
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIAddSurfsPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,152 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.header.storage.primary.APIAddPrimaryStorageEvent
+
+doc {
+ title "AddSurfsPrimaryStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "POST /v1/primary-storage/surfs"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIAddSurfsPrimaryStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "nodeUrls"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "List"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "rootVolumePoolName"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "dataVolumePoolName"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "imageCachePoolName"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "url"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "name"
+ enclosedIn "params"
+ desc "资源名称"
+ location "body"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "description"
+ enclosedIn "params"
+ desc "资源的详细描述"
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "type"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "zoneUuid"
+ enclosedIn "params"
+ desc "区域UUID"
+ location "body"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "resourceUuid"
+ enclosedIn "params"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIAddPrimaryStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassMsg.java
new file mode 100644
index 00000000000..fb92c18da28
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassMsg.java
@@ -0,0 +1,23 @@
+package org.zstack.storage.surfs.primary;
+import static java.util.Arrays.asList;
+import java.util.List;
+import org.springframework.http.HttpMethod;
+import org.zstack.header.query.APIQueryMessage;
+import org.zstack.header.query.AutoQuery;
+import org.zstack.header.rest.RestRequest;
+
+
+@AutoQuery(replyClass = APIQuerySurfsPoolClassReplay.class, inventoryClass = SurfsPoolClassInventory.class)
+@RestRequest(
+ path = "/primary-storage/surfs/poolclss",
+ optionalPaths = {"/primary-storage/surfs/poolclss/{uuid}"},
+ method = HttpMethod.GET,
+ responseClass = APIQuerySurfsPoolClassReplay.class
+)
+public class APIQuerySurfsPoolClassMsg extends APIQueryMessage {
+
+ public static List __example__() {
+ return asList();
+ }
+
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..bbe3d040a17
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassMsgDoc_zh_cn.groovy
@@ -0,0 +1,33 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.storage.surfs.primary.APIQuerySurfsPoolClassReplay
+import org.zstack.header.query.APIQueryMessage
+
+doc {
+ title "QuerySurfsPoolClass"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "GET /v1/primary-storage/surfs/poolclss"
+
+ url "GET /v1/primary-storage/surfs/poolclss/{uuid}"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIQuerySurfsPoolClassMsg.class
+
+ desc """"""
+
+ params APIQueryMessage.class
+ }
+
+ response {
+ clz APIQuerySurfsPoolClassReplay.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassReplay.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassReplay.java
new file mode 100644
index 00000000000..7429393c475
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassReplay.java
@@ -0,0 +1,29 @@
+package org.zstack.storage.surfs.primary;
+import static java.util.Arrays.asList;
+
+import java.util.List;
+
+import org.zstack.header.query.APIQueryReply;
+import org.zstack.header.rest.RestResponse;
+
+@RestResponse(allTo = "inventories")
+public class APIQuerySurfsPoolClassReplay extends APIQueryReply {
+ private List inventories;
+
+ public List getInventories() {
+ return inventories;
+ }
+
+ public void setInventories(List inventories) {
+ this.inventories = inventories;
+ }
+ public static APIQuerySurfsPoolClassReplay __example__() {
+ APIQuerySurfsPoolClassReplay reply=new APIQuerySurfsPoolClassReplay();
+ SurfsPoolClassInventory surfspool=new SurfsPoolClassInventory();
+ surfspool.setUuid("aaabbb");
+ surfspool.setClsname("hdd");
+ reply.setInventories(asList(surfspool));
+ reply.setSuccess(true);
+ return reply;
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassReplayDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassReplayDoc_zh_cn.groovy
new file mode 100644
index 00000000000..8a2fb7cf962
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPoolClassReplayDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.primary.SurfsPoolClassInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.primary.APIQuerySurfsPoolClassReplay.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventories"
+ path "org.zstack.storage.surfs.primary.APIQuerySurfsPoolClassReplay.inventories"
+ desc "null"
+ type "List"
+ since "0.6"
+ clz SurfsPoolClassInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPrimaryStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPrimaryStorageMsg.java
new file mode 100644
index 00000000000..60638279569
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPrimaryStorageMsg.java
@@ -0,0 +1,29 @@
+package org.zstack.storage.surfs.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.query.APIQueryMessage;
+import org.zstack.header.query.AutoQuery;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.primary.APIQueryPrimaryStorageReply;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+@AutoQuery(replyClass = APIQueryPrimaryStorageReply.class, inventoryClass = SurfsPrimaryStorageInventory.class)
+@RestRequest(
+ path = "/primary-storage/surfs",
+ optionalPaths = {"/primary-storage/surfs/{uuid}"},
+ method = HttpMethod.GET,
+ responseClass = APIQueryPrimaryStorageReply.class
+)
+public class APIQuerySurfsPrimaryStorageMsg extends APIQueryMessage {
+
+ public static List __example__() {
+ return asList();
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPrimaryStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..b32f7ccf246
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIQuerySurfsPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,33 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.header.storage.primary.APIQueryPrimaryStorageReply
+import org.zstack.header.query.APIQueryMessage
+
+doc {
+ title "QuerySurfsPrimaryStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "GET /v1/primary-storage/surfs"
+
+ url "GET /v1/primary-storage/surfs/{uuid}"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIQuerySurfsPrimaryStorageMsg.class
+
+ desc """"""
+
+ params APIQueryMessage.class
+ }
+
+ response {
+ clz APIQueryPrimaryStorageReply.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageEvent.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageEvent.java
new file mode 100644
index 00000000000..3207903344a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageEvent.java
@@ -0,0 +1,35 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+@RestResponse(allTo = "inventory")
+public class APIRemoveNodeFromSurfsPrimaryStorageEvent extends APIEvent {
+ private SurfsPrimaryStorageInventory inventory;
+
+ public APIRemoveNodeFromSurfsPrimaryStorageEvent() {
+ }
+
+ public APIRemoveNodeFromSurfsPrimaryStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public SurfsPrimaryStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(SurfsPrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public static APIRemoveNodeFromSurfsPrimaryStorageEvent __example__() {
+ APIRemoveNodeFromSurfsPrimaryStorageEvent event = new APIRemoveNodeFromSurfsPrimaryStorageEvent();
+
+
+ return event;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageEventDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..24e2796194f
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.primary.SurfsPrimaryStorageInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.primary.APIRemoveNodeFromSurfsPrimaryStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventory"
+ path "org.zstack.storage.surfs.primary.APIRemoveNodeFromSurfsPrimaryStorageEvent.inventory"
+ desc "null"
+ type "SurfsPrimaryStorageInventory"
+ since "0.6"
+ clz SurfsPrimaryStorageInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageMsg.java
new file mode 100644
index 00000000000..d4af65df44e
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageMsg.java
@@ -0,0 +1,57 @@
+package org.zstack.storage.surfs.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.primary.PrimaryStorageMessage;
+import org.zstack.header.storage.primary.PrimaryStorageVO;
+
+import java.util.List;
+
+import static org.codehaus.groovy.runtime.InvokerHelper.asList;
+
+/**
+ * Created by zhouhaiping 2016-09-12
+ */
+@RestRequest(
+ path = "/primary-storage/surfs/{uuid}/nodes",
+ method = HttpMethod.DELETE,
+ responseClass = APIRemoveNodeFromSurfsPrimaryStorageEvent.class
+)
+public class APIRemoveNodeFromSurfsPrimaryStorageMsg extends APIMessage implements PrimaryStorageMessage {
+ @APIParam(resourceType = PrimaryStorageVO.class)
+ private String uuid;
+ @APIParam(nonempty = true)
+ private List nodeHostnames;
+
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public List getNodeHostnames() {
+ return nodeHostnames;
+ }
+
+ public void setNodeHostnames(List nodeHostnames) {
+ this.nodeHostnames = nodeHostnames;
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return uuid;
+ }
+
+ public static APIRemoveNodeFromSurfsPrimaryStorageMsg __example__() {
+ APIRemoveNodeFromSurfsPrimaryStorageMsg msg = new APIRemoveNodeFromSurfsPrimaryStorageMsg();
+ msg.setUuid(uuid());
+ msg.setNodeHostnames(asList("192.168.0.100"));
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..621082e1206
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIRemoveNodeFromSurfsPrimaryStorageMsgDoc_zh_cn.groovy
@@ -0,0 +1,72 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.storage.surfs.primary.APIRemoveNodeFromSurfsPrimaryStorageEvent
+
+doc {
+ title "RemoveNodeFromSurfsPrimaryStorage"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "DELETE /v1/primary-storage/surfs/{uuid}/nodes"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIRemoveNodeFromSurfsPrimaryStorageMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "uuid"
+ enclosedIn ""
+ desc "资源的UUID,唯一标示该资源"
+ location "url"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "nodeHostnames"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIRemoveNodeFromSurfsPrimaryStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateNodeToSurfsPrimaryStorageEvent.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateNodeToSurfsPrimaryStorageEvent.java
new file mode 100644
index 00000000000..49f639d6273
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateNodeToSurfsPrimaryStorageEvent.java
@@ -0,0 +1,35 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+@RestResponse(allTo = "inventory")
+public class APIUpdateNodeToSurfsPrimaryStorageEvent extends APIEvent {
+ private SurfsPrimaryStorageInventory inventory;
+
+ public APIUpdateNodeToSurfsPrimaryStorageEvent() {
+ }
+
+ public APIUpdateNodeToSurfsPrimaryStorageEvent(String apiId) {
+ super(apiId);
+ }
+
+ public SurfsPrimaryStorageInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(SurfsPrimaryStorageInventory inventory) {
+ this.inventory = inventory;
+ }
+
+ public static APIUpdateNodeToSurfsPrimaryStorageEvent __example__() {
+ APIUpdateNodeToSurfsPrimaryStorageEvent event = new APIUpdateNodeToSurfsPrimaryStorageEvent();
+
+
+ return event;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateNodeToSurfsPrimaryStorageEventDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateNodeToSurfsPrimaryStorageEventDoc_zh_cn.groovy
new file mode 100644
index 00000000000..fe4d2489aab
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateNodeToSurfsPrimaryStorageEventDoc_zh_cn.groovy
@@ -0,0 +1,26 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.header.errorcode.ErrorCode
+import org.zstack.storage.surfs.primary.SurfsPrimaryStorageInventory
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ ref {
+ name "error"
+ path "org.zstack.storage.surfs.primary.APIUpdateNodeToSurfsPrimaryStorageEvent.error"
+ desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false
+ type "ErrorCode"
+ since "0.6"
+ clz ErrorCode.class
+ }
+ ref {
+ name "inventory"
+ path "org.zstack.storage.surfs.primary.APIUpdateNodeToSurfsPrimaryStorageEvent.inventory"
+ desc "null"
+ type "SurfsPrimaryStorageInventory"
+ since "0.6"
+ clz SurfsPrimaryStorageInventory.class
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateSurfsPrimaryStorageNodeMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateSurfsPrimaryStorageNodeMsg.java
new file mode 100644
index 00000000000..b04e92a33ef
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateSurfsPrimaryStorageNodeMsg.java
@@ -0,0 +1,107 @@
+package org.zstack.storage.surfs.primary;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.APINoSee;
+import org.zstack.header.rest.RestRequest;
+import org.zstack.header.storage.primary.PrimaryStorageMessage;
+
+/**
+ * Created by Mei Lei on 6/6/2016.
+ */
+@RestRequest(
+ path = "/primary-storage/surfs/nodes/{nodeUuid}/actions",
+ isAction = true,
+ method = HttpMethod.PUT,
+ responseClass = APIUpdateNodeToSurfsPrimaryStorageEvent.class
+)
+public class APIUpdateSurfsPrimaryStorageNodeMsg extends APIMessage implements PrimaryStorageMessage {
+ @APINoSee
+ private String primaryStorageUuid;
+
+ @APIParam(resourceType = SurfsPrimaryStorageNodeVO.class, emptyString = false)
+ private String nodeUuid;
+
+ @APIParam(maxLength = 255, required = false)
+ private String hostname;
+
+ @APIParam(maxLength = 255, required = false)
+ private String sshUsername;
+
+ @APIParam(maxLength = 255, required = false)
+ private String sshPassword;
+
+ @APIParam(numberRange = {1, 65535}, required = false)
+ private Integer sshPort;
+
+ @APIParam(numberRange = {1, 65535}, required = false)
+ private Integer nodePort;
+
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public String getSshUsername() {
+ return sshUsername;
+ }
+
+ public void setSshUsername(String sshUsername) {
+ this.sshUsername = sshUsername;
+ }
+
+ public String getSshPassword() {
+ return sshPassword;
+ }
+
+ public void setSshPassword(String sshPassword) {
+ this.sshPassword = sshPassword;
+ }
+
+ public Integer getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(Integer sshPort) {
+ this.sshPort = sshPort;
+ }
+
+ public Integer getNodePort() {
+ return nodePort;
+ }
+
+ public void setNodePort(Integer nodePort) {
+ this.nodePort = nodePort;
+ }
+
+ public String getNodeUuid() {
+ return nodeUuid;
+ }
+
+ public void setNodeUuid(String nodeUuid) {
+ this.nodeUuid = nodeUuid;
+ }
+
+ public static APIUpdateSurfsPrimaryStorageNodeMsg __example__() {
+ APIUpdateSurfsPrimaryStorageNodeMsg msg = new APIUpdateSurfsPrimaryStorageNodeMsg();
+ msg.setNodeUuid(uuid());
+ msg.setNodePort(1234);
+ return msg;
+ }
+
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateSurfsPrimaryStorageNodeMsgDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateSurfsPrimaryStorageNodeMsgDoc_zh_cn.groovy
new file mode 100644
index 00000000000..c40fa74cb84
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/APIUpdateSurfsPrimaryStorageNodeMsgDoc_zh_cn.groovy
@@ -0,0 +1,112 @@
+package org.zstack.storage.surfs.primary
+
+import org.zstack.storage.surfs.primary.APIUpdateNodeToSurfsPrimaryStorageEvent
+
+doc {
+ title "UpdateSurfsPrimaryStorageNode"
+
+ category "未知类别"
+
+ desc """在这里填写API描述"""
+
+ rest {
+ request {
+ url "PUT /v1/primary-storage/surfs/nodes/{nodeUuid}/actions"
+
+
+ header(Authorization: 'OAuth the-session-uuid')
+
+ clz APIUpdateSurfsPrimaryStorageNodeMsg.class
+
+ desc """"""
+
+ params {
+
+ column {
+ name "nodeUuid"
+ enclosedIn "updateSurfsPrimaryStorageNode"
+ desc ""
+ location "url"
+ type "String"
+ optional false
+ since "0.6"
+
+ }
+ column {
+ name "hostname"
+ enclosedIn "updateSurfsPrimaryStorageNode"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "sshUsername"
+ enclosedIn "updateSurfsPrimaryStorageNode"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "sshPassword"
+ enclosedIn "updateSurfsPrimaryStorageNode"
+ desc ""
+ location "body"
+ type "String"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "sshPort"
+ enclosedIn "updateSurfsPrimaryStorageNode"
+ desc ""
+ location "body"
+ type "Integer"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "nodePort"
+ enclosedIn "updateSurfsPrimaryStorageNode"
+ desc ""
+ location "body"
+ type "Integer"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "systemTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ column {
+ name "userTags"
+ enclosedIn ""
+ desc ""
+ location "body"
+ type "List"
+ optional true
+ since "0.6"
+
+ }
+ }
+ }
+
+ response {
+ clz APIUpdateNodeToSurfsPrimaryStorageEvent.class
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/CreateKvmSecretMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/CreateKvmSecretMsg.java
new file mode 100644
index 00000000000..fca15aca6c6
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/CreateKvmSecretMsg.java
@@ -0,0 +1,31 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+import org.zstack.header.storage.primary.PrimaryStorageMessage;
+
+import java.util.List;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+public class CreateKvmSecretMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String primaryStorageUuid;
+ private List hostUuids;
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return primaryStorageUuid;
+ }
+
+ public void setPrimaryStorageUuid(String primaryStorageUuid) {
+ this.primaryStorageUuid = primaryStorageUuid;
+ }
+
+ public List getHostUuids() {
+ return hostUuids;
+ }
+
+ public void setHostUuids(List hostUuids) {
+ this.hostUuids = hostUuids;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/CreateKvmSecretReply.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/CreateKvmSecretReply.java
new file mode 100644
index 00000000000..58f7d155a56
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/CreateKvmSecretReply.java
@@ -0,0 +1,9 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.MessageReply;
+
+/**
+ * Created by frank on 8/17/2015.
+ */
+public class CreateKvmSecretReply extends MessageReply {
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/KVMSurfsVolumeTO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/KVMSurfsVolumeTO.java
new file mode 100644
index 00000000000..80e3afe2b6e
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/KVMSurfsVolumeTO.java
@@ -0,0 +1,57 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.kvm.KVMAgentCommands.VolumeTO;
+
+import java.util.List;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+public class KVMSurfsVolumeTO extends VolumeTO {
+ public static class NodeInfo {
+ String hostname;
+ int port;
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+ }
+
+ public KVMSurfsVolumeTO() {
+ }
+
+ public KVMSurfsVolumeTO(VolumeTO other) {
+ super(other);
+ }
+
+ private List nodeInfo;
+ private String secretUuid;
+
+ public String getSecretUuid() {
+ return secretUuid;
+ }
+
+ public void setSecretUuid(String secretUuid) {
+ this.secretUuid = secretUuid;
+ }
+
+ public List getNodeInfo() {
+ return nodeInfo;
+ }
+
+ public void setNodeInfo(List nodeInfo) {
+ this.nodeInfo = nodeInfo;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/KvmSurfsIsoTO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/KvmSurfsIsoTO.java
new file mode 100644
index 00000000000..373d6163cdb
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/KvmSurfsIsoTO.java
@@ -0,0 +1,57 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.kvm.KVMAgentCommands.IsoTO;
+
+import java.util.List;
+
+/**
+ * Created by zhouhaiping 2017-09-14
+ */
+public class KvmSurfsIsoTO extends IsoTO {
+ public static class NodeInfo {
+ String hostname;
+ int port;
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+ }
+
+ public KvmSurfsIsoTO() {
+ }
+
+ public KvmSurfsIsoTO(IsoTO other) {
+ super(other);
+ }
+
+ private List nodeInfo;
+ private String secretUuid;
+
+ public List getNodeInfo() {
+ return nodeInfo;
+ }
+
+ public void setNodeInfo(List nodeInfo) {
+ this.nodeInfo = nodeInfo;
+ }
+
+ public String getSecretUuid() {
+ return secretUuid;
+ }
+
+ public void setSecretUuid(String secretUuid) {
+ this.secretUuid = secretUuid;
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SetupSelfFencerOnKvmHostMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SetupSelfFencerOnKvmHostMsg.java
new file mode 100644
index 00000000000..1067c20a66a
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SetupSelfFencerOnKvmHostMsg.java
@@ -0,0 +1,25 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+import org.zstack.header.storage.primary.PrimaryStorageMessage;
+import org.zstack.kvm.KvmSetupSelfFencerExtensionPoint.KvmSetupSelfFencerParam;
+
+/**
+ * Created by xing5 on 2016/5/10.
+ */
+public class SetupSelfFencerOnKvmHostMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private KvmSetupSelfFencerParam param;
+
+ public KvmSetupSelfFencerParam getParam() {
+ return param;
+ }
+
+ public void setParam(KvmSetupSelfFencerParam param) {
+ this.param = param;
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return param.getPrimaryStorage().getUuid();
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SetupSelfFencerOnKvmHostReply.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SetupSelfFencerOnKvmHostReply.java
new file mode 100644
index 00000000000..718ba6f24cb
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SetupSelfFencerOnKvmHostReply.java
@@ -0,0 +1,9 @@
+package org.zstack.storage.surfs.primary;
+
+import org.zstack.header.message.MessageReply;
+
+/**
+ * Created by xing5 on 2016/5/10.
+ */
+public class SetupSelfFencerOnKvmHostReply extends MessageReply {
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPoolClassInventory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPoolClassInventory.java
new file mode 100644
index 00000000000..68a157b1adf
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPoolClassInventory.java
@@ -0,0 +1,124 @@
+package org.zstack.storage.surfs.primary;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.zstack.header.search.Inventory;
+import org.zstack.storage.surfs.*;
+
+@Inventory(mappingVOClass =SurfsPoolClassVO.class)
+public class SurfsPoolClassInventory{
+ private String uuid;
+ private String fsid;
+ private String clsname;
+ private String clsdisplayname;
+ private boolean isrootcls;
+ private boolean isactive;
+ private long totalCapacity;
+ private long availableCapacity;
+ private Timestamp createDate;
+ private Timestamp lastOpDate;
+
+ public static SurfsPoolClassInventory valueOf(SurfsPoolClassVO vo){
+ SurfsPoolClassInventory inv= new SurfsPoolClassInventory();
+ inv.setUuid(vo.getUuid());
+ inv.setFsid(vo.getFsid());
+ inv.setClsname(vo.getClsname());
+ inv.setDisplayName(vo.getDisplayName());
+ inv.setIsRootCls(vo.getIsRootCls());
+ inv.setIsActive(vo.getIsActive());
+ inv.setTotalCapacity(vo.getTotalCapacity());
+ inv.setAvailableCapacity(vo.getAvailableCapacity());
+ inv.setCreateDate(vo.getCreateDate());
+ inv.setLastOpDate(vo.getLastOpDate());
+ return inv;
+ }
+ public static List valueOf(Collection vos){
+ List invs= new ArrayList();
+ for (SurfsPoolClassVO vo : vos){
+ invs.add(valueOf(vo));
+ }
+ return invs;
+ }
+ public void setUuid(String cuuid){
+ this.uuid=cuuid;
+ }
+
+ public String getUuid(){
+ return this.uuid;
+ }
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+
+ public void setClsname(String cname){
+ this.clsname=cname;
+ }
+
+ public String getClsname(){
+ return this.clsname;
+ }
+
+ public void setDisplayName(String dpname){
+ this.clsdisplayname=dpname;
+ }
+
+ public String getDisplayName(){
+ return this.clsdisplayname;
+ }
+
+ public void setIsRootCls(boolean isrc){
+ this.isrootcls=isrc;
+ }
+
+ public boolean getIsRootCls(){
+ return this.isrootcls;
+ }
+
+ public void setIsActive(boolean isac){
+ this.isactive=isac;
+ }
+
+ public boolean getIsActive(){
+ return this.isactive;
+ }
+
+ public long getTotalCapacity() {
+ return totalCapacity;
+ }
+
+ public void setTotalCapacity(long totalCapacity) {
+ this.totalCapacity = totalCapacity;
+ }
+
+ public long getAvailableCapacity() {
+ return availableCapacity;
+ }
+
+ public void setAvailableCapacity(long availableCapacity) {
+ this.availableCapacity = availableCapacity;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+}
\ No newline at end of file
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPoolClassInventoryDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPoolClassInventoryDoc_zh_cn.groovy
new file mode 100644
index 00000000000..caf6507dd8e
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPoolClassInventoryDoc_zh_cn.groovy
@@ -0,0 +1,70 @@
+package org.zstack.storage.surfs.primary
+
+import java.sql.Timestamp
+import java.sql.Timestamp
+
+doc {
+
+ title "在这里输入结构的名称"
+
+ field {
+ name "uuid"
+ desc "资源的UUID,唯一标示该资源"
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "fsid"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "clsname"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "clsdisplayname"
+ desc ""
+ type "String"
+ since "0.6"
+ }
+ field {
+ name "isrootcls"
+ desc ""
+ type "boolean"
+ since "0.6"
+ }
+ field {
+ name "isactive"
+ desc ""
+ type "boolean"
+ since "0.6"
+ }
+ field {
+ name "totalCapacity"
+ desc ""
+ type "long"
+ since "0.6"
+ }
+ field {
+ name "availableCapacity"
+ desc ""
+ type "long"
+ since "0.6"
+ }
+ field {
+ name "createDate"
+ desc "创建时间"
+ type "Timestamp"
+ since "0.6"
+ }
+ field {
+ name "lastOpDate"
+ desc "最后一次修改时间"
+ type "Timestamp"
+ since "0.6"
+ }
+}
diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageBase.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageBase.java
new file mode 100644
index 00000000000..8e434e6c11e
--- /dev/null
+++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageBase.java
@@ -0,0 +1,3297 @@
+package org.zstack.storage.surfs.primary;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.Platform;
+import org.zstack.core.cloudbus.CloudBusCallBack;
+import org.zstack.core.db.SimpleQuery;
+import org.zstack.core.db.SimpleQuery.Op;
+import org.zstack.core.thread.AsyncThread;
+import org.zstack.core.thread.ChainTask;
+import org.zstack.core.thread.SyncTaskChain;
+import org.zstack.core.thread.ThreadFacade;
+import org.zstack.core.timeout.ApiTimeoutManager;
+import org.zstack.core.workflow.FlowChainBuilder;
+import org.zstack.core.workflow.ShareFlow;
+import org.zstack.header.cluster.ClusterVO;
+import org.zstack.header.cluster.ClusterVO_;
+import org.zstack.header.core.*;
+import org.zstack.header.core.workflow.*;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.errorcode.OperationFailureException;
+import org.zstack.header.errorcode.SysErrors;
+import org.zstack.header.exception.CloudRuntimeException;
+import org.zstack.header.host.HostStatus;
+import org.zstack.header.host.HostVO;
+import org.zstack.header.host.HostVO_;
+import org.zstack.header.image.APICreateDataVolumeTemplateFromVolumeMsg;
+import org.zstack.header.image.APICreateRootVolumeTemplateFromRootVolumeMsg;
+import org.zstack.header.image.APICreateRootVolumeTemplateFromVolumeSnapshotMsg;
+import org.zstack.header.image.ImageConstant.ImageMediaType;
+import org.zstack.header.image.ImageInventory;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.Message;
+import org.zstack.header.message.MessageReply;
+import org.zstack.header.rest.RESTFacade;
+import org.zstack.header.storage.backup.*;
+import org.zstack.header.storage.primary.*;
+import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType;
+import org.zstack.header.storage.snapshot.VolumeSnapshotConstant;
+import org.zstack.header.storage.snapshot.VolumeSnapshotInventory;
+import org.zstack.header.storage.snapshot.VolumeSnapshotVO;
+import org.zstack.header.storage.snapshot.VolumeSnapshotVO_;
+import org.zstack.header.vm.VmInstanceSpec.ImageSpec;
+import org.zstack.header.volume.*;
+import org.zstack.kvm.*;
+import org.zstack.kvm.KvmSetupSelfFencerExtensionPoint.KvmSetupSelfFencerParam;
+import org.zstack.storage.backup.sftp.GetSftpBackupStorageDownloadCredentialMsg;
+import org.zstack.storage.backup.sftp.GetSftpBackupStorageDownloadCredentialReply;
+import org.zstack.storage.backup.sftp.SftpBackupStorageConstant;
+import org.zstack.storage.surfs.*;
+import org.zstack.storage.surfs.SurfsNodeBase.PingResult;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageVO;
+import org.zstack.storage.surfs.backup.SurfsBackupStorageVO_;
+import org.zstack.storage.primary.PrimaryStorageBase;
+import org.zstack.utils.CollectionUtils;
+import org.zstack.utils.DebugUtils;
+import org.zstack.utils.Utils;
+import org.zstack.utils.function.Function;
+import org.zstack.utils.gson.JSONObjectUtil;
+import org.zstack.utils.logging.CLogger;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.zstack.core.Platform.operr;
+import static org.zstack.utils.CollectionDSL.list;
+
+/**
+ * Created by zhouhaiping 2016-09-18
+ */
+public class SurfsPrimaryStorageBase extends PrimaryStorageBase {
+ private static final CLogger logger = Utils.getLogger(SurfsPrimaryStorageBase.class);
+
+ @Autowired
+ private RESTFacade restf;
+ @Autowired
+ private ThreadFacade thdf;
+ @Autowired
+ private ApiTimeoutManager timeoutMgr;
+
+ class ReconnectNodeLock {
+ AtomicBoolean hold = new AtomicBoolean(false);
+
+ boolean lock() {
+ return hold.compareAndSet(false, true);
+ }
+
+ void unlock() {
+ hold.set(false);
+ }
+ }
+
+ ReconnectNodeLock reconnectNodeLock = new ReconnectNodeLock();
+
+ public static class AgentCommand {
+ String fsId;
+ String uuid;
+
+ public String getFsId() {
+ return fsId;
+ }
+
+ public void setFsId(String fsId) {
+ this.fsId = fsId;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+ }
+
+ public static class AgentResponse {
+ String error;
+ boolean success = true;
+ Long totalCapacity;
+ Long availableCapacity;
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public void setSuccess(boolean success) {
+ this.success = success;
+ }
+
+ public Long getTotalCapacity() {
+ return totalCapacity;
+ }
+
+ public void setTotalCapacity(Long totalCapacity) {
+ this.totalCapacity = totalCapacity;
+ }
+
+ public Long getAvailableCapacity() {
+ return availableCapacity;
+ }
+
+ public void setAvailableCapacity(Long availableCapacity) {
+ this.availableCapacity = availableCapacity;
+ }
+ }
+
+ public static class GetVolumeSizeCmd extends AgentCommand {
+ public String volumeUuid;
+ public String installPath;
+ }
+
+ public static class GetVolumeSizeRsp extends AgentResponse {
+ public Long size;
+ public Long actualSize;
+ }
+
+ public static class Pool {
+ String name;
+ boolean predefined;
+ }
+
+ public static class InitCmd extends AgentCommand {
+ List pools;
+ List monHostnames;
+ List sshUsernames;
+ List sshPasswords;
+ String surfsType;
+
+ public List getPools() {
+ return pools;
+ }
+
+ public void setPools(List pools) {
+ this.pools = pools;
+ }
+ }
+
+ public static class InitRsp extends AgentResponse {
+ String fsid;
+ String userKey;
+
+ public String getUserKey() {
+ return userKey;
+ }
+
+ public void setUserKey(String userKey) {
+ this.userKey = userKey;
+ }
+
+ public String getFsid() {
+ return fsid;
+ }
+
+ public void setFsid(String fsid) {
+ this.fsid = fsid;
+ }
+ }
+
+ public static class StartVmBefore extends AgentCommand{
+ String installPath;
+ String volinstallPath;
+ String nodeIp;
+
+ public String getInstallPath(){
+ return this.installPath;
+ }
+
+ public void setInstallPath(String ispath){
+ this.installPath=ispath;
+ }
+
+ public String getVolInstallPath(){
+ return this.volinstallPath;
+ }
+
+ public void setVolInstallPath(String volpath){
+ this.volinstallPath=volpath;
+ }
+
+ public String getNodeIp(){
+ return this.nodeIp;
+ }
+
+ public void setNodeIp(String ndip){
+ this.nodeIp=ndip;
+ }
+ }
+ public static class StartVmBeforeRsp extends AgentResponse{
+
+ }
+
+ public static class GetVolumeInfo extends AgentCommand{
+ String volumeid;
+ public String getVolumeId(){
+ return this.volumeid;
+ }
+
+ public void setVolumeId(String volid){
+ this.volumeid=volid;
+ }
+ }
+
+ public static class GetVolumeInofRsp extends AgentResponse{
+ String volumeid;
+ String surfsserviceip;
+ String Hostip;
+ String vmuuid;
+ public String getVolumeId(){
+ return this.volumeid;
+ }
+
+ public void setVolumeId(String volid){
+ this.volumeid=volid;
+ }
+
+ public String getSurfsServiceIp(){
+ return this.surfsserviceip;
+ }
+
+ public void setSurfsServiceIp(String ssip){
+ this.surfsserviceip=ssip;
+ }
+
+ public String getHostIp(){
+ return this.Hostip;
+ }
+
+ public void setHostIp(String hip){
+ this.Hostip=hip;
+ }
+
+ public String getVmUuid(){
+ return this.vmuuid;
+ }
+
+ public void setVmUuid(String vmid){
+ this.vmuuid=vmid;
+ }
+ }
+
+ public static class AttachDataVolToVm extends AgentCommand{
+ String installPath;
+ String voltype;
+ long volsize;
+ String mgip;
+ public String getInstallPath(){
+ return this.installPath;
+ }
+
+ public void setInstallPath(String ispath){
+ this.installPath=ispath;
+ }
+
+ public String getVoltype(){
+ return this.voltype;
+ }
+
+ public void setVoltype(String vltype){
+ this.voltype=vltype;
+ }
+
+ public long getVolsize(){
+ return this.volsize;
+ }
+
+ public void setVolsize(long vlsize){
+ this.volsize=vlsize;
+ }
+
+ public String getMgip(){
+ return this.mgip;
+ }
+
+ public void setMgip(String mg_ip){
+ this.mgip =mg_ip;
+ }
+ }
+
+ public static class AttachDataVolToVmRsp extends AgentResponse{
+ String poolip;
+ String iscsiport;
+ String target;
+ String lun;
+ String devicetype;
+
+ public String getpoolip(){
+ return this.poolip;
+ }
+
+ public void setpoolip(String poolip){
+ this.poolip=poolip;
+ }
+
+ public String getiscsiport(){
+ return this.iscsiport;
+ }
+
+ public void setiscsiport(String port){
+ this.iscsiport=port;
+ }
+ public String gettarget(){
+ return this.target;
+ }
+
+ public void settarget(String target){
+ this.target=target;
+ }
+
+ public String getlun(){
+ return this.lun;
+ }
+
+ public void setlun(String lun){
+ this.lun=lun;
+ }
+
+ public String getDeviceType(){
+ return this.devicetype;
+ }
+
+ public void setDeviceType(String d_type){
+ this.devicetype=d_type;
+ }
+
+ public String getvolinstall(){
+ return String.format("iscsi://%s:%s/%s/%s",this.poolip,this.iscsiport,this.target,this.lun);
+ }
+ }
+
+ public static class CreateEmptyVolumeCmd extends AgentCommand {
+ String installPath;
+ String poolCls="None";
+ long size;
+
+ public String getInstallPath() {
+ return installPath;
+ }
+
+ public void setInstallPath(String installPath) {
+ this.installPath = installPath;
+ }
+
+ public String getPoolCls(){
+ return poolCls;
+ }
+
+ public void setPoolCls(String pcls){
+ this.poolCls=pcls;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+ }
+
+ public static class CreateEmptyVolumeRsp extends AgentResponse {
+ }
+
+ public static class DeleteCmd extends AgentCommand {
+ String installPath;
+
+ public String getInstallPath() {
+ return installPath;
+ }
+
+ public void setInstallPath(String installPath) {
+ this.installPath = installPath;
+ }
+ }
+
+ public static class DeleteRsp extends AgentResponse {
+
+ }
+
+ public static class CloneCmd extends AgentCommand {
+ String srcPath;
+ String dstPath;
+
+ public String getSrcPath() {
+ return srcPath;
+ }
+
+ public void setSrcPath(String srcPath) {
+ this.srcPath = srcPath;
+ }
+
+ public String getDstPath() {
+ return dstPath;
+ }
+
+ public void setDstPath(String dstPath) {
+ this.dstPath = dstPath;
+ }
+ }
+
+ public static class CloneRsp extends AgentResponse {
+ }
+
+ public static class FlattenCmd extends AgentCommand {
+ String path;
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+ }
+
+ public static class FlattenRsp extends AgentResponse {
+
+ }
+
+ public static class SftpDownloadCmd extends AgentCommand {
+ String sshKey;
+ String hostname;
+ String username;
+ int sshPort;
+ String backupStorageInstallPath;
+ String primaryStorageInstallPath;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public int getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(int sshPort) {
+ this.sshPort = sshPort;
+ }
+
+ public String getSshKey() {
+ return sshKey;
+ }
+
+ public void setSshKey(String sshKey) {
+ this.sshKey = sshKey;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public String getBackupStorageInstallPath() {
+ return backupStorageInstallPath;
+ }
+
+ public void setBackupStorageInstallPath(String backupStorageInstallPath) {
+ this.backupStorageInstallPath = backupStorageInstallPath;
+ }
+
+ public String getPrimaryStorageInstallPath() {
+ return primaryStorageInstallPath;
+ }
+
+ public void setPrimaryStorageInstallPath(String primaryStorageInstallPath) {
+ this.primaryStorageInstallPath = primaryStorageInstallPath;
+ }
+ }
+
+ public static class SftpDownloadRsp extends AgentResponse {
+ }
+
+ @ApiTimeout(apiClasses = {
+ APICreateRootVolumeTemplateFromRootVolumeMsg.class,
+ APICreateDataVolumeTemplateFromVolumeMsg.class
+ })
+ public static class SftpUpLoadCmd extends AgentCommand {
+ String primaryStorageInstallPath;
+ String backupStorageInstallPath;
+ String hostname;
+ String username;
+ String sshKey;
+ int sshPort;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public int getSshPort() {
+ return sshPort;
+ }
+
+ public void setSshPort(int sshPort) {
+ this.sshPort = sshPort;
+ }
+
+ public String getPrimaryStorageInstallPath() {
+ return primaryStorageInstallPath;
+ }
+
+ public void setPrimaryStorageInstallPath(String primaryStorageInstallPath) {
+ this.primaryStorageInstallPath = primaryStorageInstallPath;
+ }
+
+ public String getBackupStorageInstallPath() {
+ return backupStorageInstallPath;
+ }
+
+ public void setBackupStorageInstallPath(String backupStorageInstallPath) {
+ this.backupStorageInstallPath = backupStorageInstallPath;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ public String getSshKey() {
+ return sshKey;
+ }
+
+ public void setSshKey(String sshKey) {
+ this.sshKey = sshKey;
+ }
+ }
+
+ public static class SftpUploadRsp extends AgentResponse {
+ }
+
+ @ApiTimeout(apiClasses = {APICreateVolumeSnapshotMsg.class})
+ public static class CreateSnapshotCmd extends AgentCommand {
+ boolean skipOnExisting;
+ String snapshotPath;
+ String volumeUuid;
+ long volsize;
+
+ public String getVolumeUuid() {
+ return volumeUuid;
+ }
+
+ public void setVolumeUuid(String volumeUuid) {
+ this.volumeUuid = volumeUuid;
+ }
+
+ public boolean isSkipOnExisting() {
+ return skipOnExisting;
+ }
+
+ public void setSkipOnExisting(boolean skipOnExisting) {
+ this.skipOnExisting = skipOnExisting;
+ }
+
+ public String getSnapshotPath() {
+ return snapshotPath;
+ }
+
+ public void setSnapshotPath(String snapshotPath) {
+ this.snapshotPath = snapshotPath;
+ }
+
+ public long getVolsize(){
+ return this.volsize;
+ }
+
+ public void setVolsize(long v_size){
+ this.volsize=v_size;
+ }
+ }
+
+ public static class CreateSnapshotRsp extends AgentResponse {
+ Long size;
+ Long actualSize;
+
+ public Long getActualSize() {
+ return actualSize;
+ }
+
+ public void setActualSize(Long actualSize) {
+ this.actualSize = actualSize;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+ }
+
+ public static class DeleteSnapshotCmd extends AgentCommand {
+ String snapshotPath;
+
+ public String getSnapshotPath() {
+ return snapshotPath;
+ }
+
+ public void setSnapshotPath(String snapshotPath) {
+ this.snapshotPath = snapshotPath;
+ }
+ }
+
+ public static class DeleteSnapshotRsp extends AgentResponse {
+ }
+
+ public static class ProtectSnapshotCmd extends AgentCommand {
+ String snapshotPath;
+ boolean ignoreError;
+
+ public boolean isIgnoreError() {
+ return ignoreError;
+ }
+
+ public void setIgnoreError(boolean ignoreError) {
+ this.ignoreError = ignoreError;
+ }
+
+ public String getSnapshotPath() {
+ return snapshotPath;
+ }
+
+ public void setSnapshotPath(String snapshotPath) {
+ this.snapshotPath = snapshotPath;
+ }
+ }
+
+ public static class ProtectSnapshotRsp extends AgentResponse {
+ }
+
+ public static class UnprotectedSnapshotCmd extends AgentCommand {
+ String snapshotPath;
+
+ public String getSnapshotPath() {
+ return snapshotPath;
+ }
+
+ public void setSnapshotPath(String snapshotPath) {
+ this.snapshotPath = snapshotPath;
+ }
+ }
+
+ public static class UnprotectedSnapshotRsp extends AgentResponse {
+ }
+
+ @ApiTimeout(apiClasses = {
+ APICreateRootVolumeTemplateFromRootVolumeMsg.class,
+ APICreateDataVolumeTemplateFromVolumeMsg.class,
+ APICreateDataVolumeFromVolumeSnapshotMsg.class,
+ APICreateRootVolumeTemplateFromVolumeSnapshotMsg.class
+ })
+ public static class CpCmd extends AgentCommand {
+ String resourceUuid;
+ String srcPath;
+ String dstPath;
+ }
+
+ public static class CpRsp extends AgentResponse {
+ Long size;
+ Long actualSize;
+ }
+
+ public static class RollbackSnapshotCmd extends AgentCommand {
+ String snapshotPath;
+
+ public String getSnapshotPath() {
+ return snapshotPath;
+ }
+
+ public void setSnapshotPath(String snapshotPath) {
+ this.snapshotPath = snapshotPath;
+ }
+ }
+
+ public static class RollbackSnapshotRsp extends AgentResponse {
+ }
+
+ public static class CreateKvmSecretCmd extends KVMAgentCommands.AgentCommand {
+ String userKey;
+ String uuid;
+
+ public String getUserKey() {
+ return userKey;
+ }
+
+ public void setUserKey(String userKey) {
+ this.userKey = userKey;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+ }
+
+ public static class CreateKvmSecretRsp extends AgentResponse {
+
+ }
+
+ public static class DeletePoolCmd extends AgentCommand {
+ List poolNames;
+
+ public List getPoolNames() {
+ return poolNames;
+ }
+
+ public void setPoolNames(List poolNames) {
+ this.poolNames = poolNames;
+ }
+ }
+
+ public static class DeletePoolRsp extends AgentResponse {
+ }
+
+ public static class KvmSetupSelfFencerCmd extends AgentCommand {
+ public String heartbeatImagePath;
+ public String hostUuid;
+ public long interval;
+ public int maxAttempts;
+ public int storageCheckerTimeout;
+ public String userKey;
+ public List monUrls;
+ }
+
+ public static class GetFactsCmd extends AgentCommand {
+ public String monUuid;
+ }
+
+ public static class GetFactsRsp extends AgentResponse {
+ public String fsid;
+ }
+
+ public static final String INIT_PATH = "/surfs/primarystorage/init";
+ public static final String CREATE_VOLUME_PATH = "/surfs/primarystorage/volume/createempty";
+ public static final String DELETE_PATH = "/surfs/primarystorage/delete";
+ public static final String CLONE_PATH = "/surfs/primarystorage/volume/clone";
+ public static final String FLATTEN_PATH = "/surfs/primarystorage/volume/flatten";
+ public static final String SFTP_DOWNLOAD_PATH = "/surfs/primarystorage/sftpbackupstorage/download";
+ public static final String SFTP_UPLOAD_PATH = "/surfs/primarystorage/sftpbackupstorage/upload";
+ public static final String CREATE_SNAPSHOT_PATH = "/surfs/primarystorage/snapshot/create";
+ public static final String DELETE_SNAPSHOT_PATH = "/surfs/primarystorage/snapshot/delete";
+ public static final String PROTECT_SNAPSHOT_PATH = "/surfs/primarystorage/snapshot/protect";
+ public static final String ROLLBACK_SNAPSHOT_PATH = "/surfs/primarystorage/snapshot/rollback";
+ public static final String UNPROTECT_SNAPSHOT_PATH = "/surfs/primarystorage/snapshot/unprotect";
+ public static final String CP_PATH = "/surfs/primarystorage/volume/cp";
+ public static final String DELETE_POOL_PATH = "/surfs/primarystorage/deletepool";
+ public static final String GET_VOLUME_SIZE_PATH = "/surfs/primarystorage/getvolumesize";
+ public static final String KVM_HA_SETUP_SELF_FENCER = "/ha/surfs/setupselffencer";
+ public static final String GET_FACTS = "/surfs/primarystorage/facts";
+ public static final String ATTACH_VOLUME_PREPARE = "/surfs/primarystorage/attachprepare";
+ public static final String DETACH_VOLUME_AFTER = "/surfs/primarystorage/detachafter";
+ public static final String START_VM_BEFORE = "/surfs/primarystorage/startvmbefore";
+
+ private final Map backupStorageMediators = new HashMap();
+ {
+ backupStorageMediators.put(SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE, new SftpBackupStorageMediator());
+ backupStorageMediators.put(SurfsConstants.SURFS_BACKUP_STORAGE_TYPE, new SurfsBackupStorageMediator());
+ }
+
+ abstract class MediatorParam {
+ }
+
+ class DownloadParam extends MediatorParam {
+ ImageSpec image;
+ String installPath;
+ }
+
+ class UploadParam extends MediatorParam {
+ ImageInventory image;
+ String primaryStorageInstallPath;
+ String backupStorageInstallPath;
+ }
+
+ abstract class BackupStorageMediator {
+ BackupStorageInventory backupStorage;
+ MediatorParam param;
+
+ protected void checkParam() {
+ DebugUtils.Assert(backupStorage != null, "backupStorage cannot be null");
+ DebugUtils.Assert(param != null, "param cannot be null");
+ }
+
+ abstract void download(ReturnValueCompletion completion);
+
+ abstract void upload(ReturnValueCompletion completion);
+
+ abstract boolean deleteWhenRollabackDownload();
+ }
+
+ class SftpBackupStorageMediator extends BackupStorageMediator {
+ private void getSftpCredentials(final ReturnValueCompletion completion) {
+ GetSftpBackupStorageDownloadCredentialMsg gmsg = new GetSftpBackupStorageDownloadCredentialMsg();
+ gmsg.setBackupStorageUuid(backupStorage.getUuid());
+ bus.makeTargetServiceIdByResourceUuid(gmsg, BackupStorageConstant.SERVICE_ID, backupStorage.getUuid());
+ bus.send(gmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ } else {
+ completion.success((GetSftpBackupStorageDownloadCredentialReply) reply);
+ }
+ }
+ });
+ }
+
+ @Override
+ void download(final ReturnValueCompletion completion) {
+ checkParam();
+ final DownloadParam dparam = (DownloadParam) param;
+
+ FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("download-image-from-sftp-%s-to-surfs-%s", backupStorage.getUuid(), self.getUuid()));
+ chain.then(new ShareFlow() {
+ String sshkey;
+ int sshport;
+ String sftpHostname;
+ String username;
+
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "get-sftp-credentials";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ getSftpCredentials(new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(GetSftpBackupStorageDownloadCredentialReply greply) {
+ sshkey = greply.getSshKey();
+ sftpHostname = greply.getHostname();
+ username = greply.getUsername();
+ sshport = greply.getSshPort();
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "download-image";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ SftpDownloadCmd cmd = new SftpDownloadCmd();
+ cmd.backupStorageInstallPath = dparam.image.getSelectedBackupStorage().getInstallPath();
+ cmd.hostname = sftpHostname;
+ cmd.username = username;
+ cmd.sshKey = sshkey;
+ cmd.sshPort = sshport;
+ cmd.primaryStorageInstallPath = dparam.installPath;
+
+ httpCall(SFTP_DOWNLOAD_PATH, cmd, SftpDownloadRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(SftpDownloadRsp returnValue) {
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+ done(new FlowDoneHandler(completion) {
+ @Override
+ public void handle(Map data) {
+ completion.success(dparam.installPath);
+ }
+ });
+
+ error(new FlowErrorHandler(completion) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ completion.fail(errCode);
+ }
+ });
+ }
+ }).start();
+ }
+
+ @Override
+ void upload(final ReturnValueCompletion completion) {
+ checkParam();
+
+ final UploadParam uparam = (UploadParam) param;
+
+ FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("upload-image-surfs-%s-to-sftp-%s", self.getUuid(), backupStorage.getUuid()));
+ chain.then(new ShareFlow() {
+ String sshKey;
+ String hostname;
+ String username;
+ int sshPort;
+ String backupStorageInstallPath;
+
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "get-sftp-credentials";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ getSftpCredentials(new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(GetSftpBackupStorageDownloadCredentialReply returnValue) {
+ sshKey = returnValue.getSshKey();
+ hostname = returnValue.getHostname();
+ username = returnValue.getUsername();
+ sshPort = returnValue.getSshPort();
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "get-backup-storage-install-path";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ BackupStorageAskInstallPathMsg msg = new BackupStorageAskInstallPathMsg();
+ msg.setBackupStorageUuid(backupStorage.getUuid());
+ msg.setImageUuid(uparam.image.getUuid());
+ msg.setImageMediaType(uparam.image.getMediaType());
+ bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, backupStorage.getUuid());
+ bus.send(msg, new CloudBusCallBack(trigger) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ trigger.fail(reply.getError());
+ } else {
+ backupStorageInstallPath = ((BackupStorageAskInstallPathReply) reply).getInstallPath();
+ trigger.next();
+ }
+ }
+ });
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "upload-to-backup-storage";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ SftpUpLoadCmd cmd = new SftpUpLoadCmd();
+ cmd.setBackupStorageInstallPath(backupStorageInstallPath);
+ cmd.setHostname(hostname);
+ cmd.setUsername(username);
+ cmd.setSshKey(sshKey);
+ cmd.setSshPort(sshPort);
+ cmd.setPrimaryStorageInstallPath(uparam.primaryStorageInstallPath);
+
+ httpCall(SFTP_UPLOAD_PATH, cmd, SftpUploadRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(SftpUploadRsp returnValue) {
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+ done(new FlowDoneHandler(completion) {
+ @Override
+ public void handle(Map data) {
+ completion.success(backupStorageInstallPath);
+ }
+ });
+
+ error(new FlowErrorHandler(completion) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ completion.fail(errCode);
+ }
+ });
+ }
+ }).start();
+ }
+
+ @Override
+ boolean deleteWhenRollabackDownload() {
+ return true;
+ }
+ }
+
+ class SurfsBackupStorageMediator extends BackupStorageMediator {
+ protected void checkParam() {
+ super.checkParam();
+
+ SimpleQuery q = dbf.createQuery(SurfsBackupStorageVO.class);
+ q.select(SurfsBackupStorageVO_.fsid);
+ q.add(SurfsBackupStorageVO_.uuid, Op.EQ, backupStorage.getUuid());
+ String bsFsid = q.findValue();
+ if (!getSelf().getFsid().equals(bsFsid)) {
+ throw new OperationFailureException(errf.stringToOperationError(
+ String.format("the backup storage[uuid:%s, name:%s, fsid:%s] is not in the same surfs cluster" +
+ " with the primary storage[uuid:%s, name:%s, fsid:%s]", backupStorage.getUuid(),
+ backupStorage.getName(), bsFsid, self.getUuid(), self.getName(), getSelf().getFsid())
+ ));
+ }
+ }
+
+ @Override
+ void download(final ReturnValueCompletion completion) {
+ checkParam();
+
+ final DownloadParam dparam = (DownloadParam) param;
+ if (ImageMediaType.DataVolumeTemplate.toString().equals(dparam.image.getInventory().getMediaType())) {
+ CpCmd cmd = new CpCmd();
+ cmd.srcPath = dparam.image.getSelectedBackupStorage().getInstallPath();
+ cmd.dstPath = dparam.installPath;
+ httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion(completion) {
+ @Override
+ public void success(CpRsp returnValue) {
+ completion.success(dparam.installPath);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ }
+ });
+ } else {
+ completion.success(dparam.image.getSelectedBackupStorage().getInstallPath());
+ }
+ }
+
+ @Override
+ void upload(final ReturnValueCompletion completion) {
+ checkParam();
+
+ final UploadParam uparam = (UploadParam) param;
+
+ FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("upload-image-surfs-%s-to-surfs-%s", self.getUuid(), backupStorage.getUuid()));
+ chain.then(new ShareFlow() {
+ String backupStorageInstallPath;
+
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "get-backup-storage-install-path";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ BackupStorageAskInstallPathMsg msg = new BackupStorageAskInstallPathMsg();
+ msg.setBackupStorageUuid(backupStorage.getUuid());
+ msg.setImageUuid(uparam.image.getUuid());
+ msg.setImageMediaType(uparam.image.getMediaType());
+ bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, backupStorage.getUuid());
+ bus.send(msg, new CloudBusCallBack(trigger) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ trigger.fail(reply.getError());
+ } else {
+ backupStorageInstallPath = ((BackupStorageAskInstallPathReply) reply).getInstallPath();
+ trigger.next();
+ }
+ }
+ });
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "cp-to-the-image";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ CpCmd cmd = new CpCmd();
+ cmd.srcPath = uparam.primaryStorageInstallPath;
+ cmd.dstPath = backupStorageInstallPath;
+ httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(CpRsp returnValue) {
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+ done(new FlowDoneHandler(completion) {
+ @Override
+ public void handle(Map data) {
+ completion.success(backupStorageInstallPath);
+ }
+ });
+
+ error(new FlowErrorHandler(completion) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ completion.fail(errCode);
+ }
+ });
+ }
+ }).start();
+ }
+
+ @Override
+ boolean deleteWhenRollabackDownload() {
+ return false;
+ }
+ }
+
+ private BackupStorageMediator getBackupStorageMediator(String bsUuid) {
+ BackupStorageVO bsvo = dbf.findByUuid(bsUuid, BackupStorageVO.class);
+ BackupStorageMediator mediator = backupStorageMediators.get(bsvo.getType());
+ if (mediator == null) {
+ throw new CloudRuntimeException(String.format("cannot find BackupStorageMediator for type[%s]", bsvo.getType()));
+ }
+
+ mediator.backupStorage = BackupStorageInventory.valueOf(bsvo);
+ return mediator;
+ }
+
+ private String makeRootVolumeInstallPath(String volUuid) {
+ return String.format("/surfs_storage/%s/%s", getSelf().getRootVolumePoolName(), volUuid);
+ }
+
+ private String makeDataVolumeInstallPath(String volUuid) {
+ return String.format("/surfs_storage/%s/%s", getSelf().getDataVolumePoolName(), volUuid);
+ }
+
+ private String makeResetImageRootVolumeInstallPath(String volUuid) {
+ return String.format("surfs://%s/reset-image-%s-%s", getSelf().getRootVolumePoolName(), volUuid,
+ System.currentTimeMillis());
+ }
+
+ private String makeCacheInstallPath(String uuid) {
+ return String.format("/surfs_storage/%s/%s", getSelf().getImageCachePoolName(), uuid);
+// return String.format("surfs://%s/%s", getSelf().getImageCachePoolName(), uuid);
+ }
+
+ public SurfsPrimaryStorageBase(PrimaryStorageVO self) {
+ super(self);
+ }
+
+ protected SurfsPrimaryStorageVO getSelf() {
+ return (SurfsPrimaryStorageVO) self;
+ }
+
+ protected SurfsPrimaryStorageInventory getSelfInventory() {
+ return SurfsPrimaryStorageInventory.valueOf(getSelf());
+ }
+
+ private void createEmptyVolume(final InstantiateVolumeOnPrimaryStorageMsg msg) {
+ final CreateEmptyVolumeCmd cmd = new CreateEmptyVolumeCmd();
+ cmd.installPath = VolumeType.Root.toString().equals(msg.getVolume().getType()) ?
+ makeRootVolumeInstallPath(msg.getVolume().getUuid()) : makeDataVolumeInstallPath(msg.getVolume().getUuid());
+ cmd.size = msg.getVolume().getSize();
+ try{
+ Field fds=msg.getVolume().getClass().getDeclaredField("poolcls");
+ fds.setAccessible(true);
+ cmd.setPoolCls(fds.get(msg).toString());
+ }catch(Exception ex){
+ cmd.setPoolCls("None");
+ logger.warn("Can not get volume pool class ");
+ }
+
+ final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply();
+
+ httpCall(CREATE_VOLUME_PATH, cmd, CreateEmptyVolumeRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(CreateEmptyVolumeRsp ret) {
+ VolumeInventory vol = msg.getVolume();
+ vol.setInstallPath(cmd.getInstallPath());
+ vol.setFormat(VolumeConstant.VOLUME_FORMAT_RAW);
+ reply.setVolume(vol);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final InstantiateVolumeOnPrimaryStorageMsg msg) {
+ if (msg instanceof InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) {
+ createVolumeFromTemplate((InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg) msg);
+ } else {
+ createEmptyVolume(msg);
+ }
+ }
+
+ class DownloadToCache {
+ ImageSpec image;
+
+ private void doDownload(final ReturnValueCompletion completion) {
+ SimpleQuery q = dbf.createQuery(ImageCacheVO.class);
+ q.add(ImageCacheVO_.imageUuid, Op.EQ, image.getInventory().getUuid());
+ q.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, self.getUuid());
+ ImageCacheVO cache = q.find();
+ if (cache != null) {
+ completion.success(cache);
+ return;
+ }
+
+ final FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("prepare-image-cache-surfs-%s", self.getUuid()));
+ chain.then(new ShareFlow() {
+ String cachePath;
+ String snapshotPath;
+
+ @Override
+ public void setup() {
+ flow(new Flow() {
+ String __name__ = "allocate-primary-storage-capacity-for-image-cache";
+
+ boolean s = false;
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ AllocatePrimaryStorageMsg amsg = new AllocatePrimaryStorageMsg();
+ amsg.setRequiredPrimaryStorageUuid(self.getUuid());
+ amsg.setSize(image.getInventory().getActualSize());
+ amsg.setPurpose(PrimaryStorageAllocationPurpose.DownloadImage.toString());
+ amsg.setNoOverProvisioning(true);
+ bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID);
+ bus.send(amsg, new CloudBusCallBack(trigger) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ trigger.fail(reply.getError());
+ } else {
+ s = true;
+ trigger.next();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void rollback(FlowRollback trigger, Map data) {
+ if (s) {
+ IncreasePrimaryStorageCapacityMsg imsg = new IncreasePrimaryStorageCapacityMsg();
+ imsg.setNoOverProvisioning(true);
+ imsg.setPrimaryStorageUuid(self.getUuid());
+ imsg.setDiskSize(image.getInventory().getActualSize());
+ bus.makeLocalServiceId(imsg, PrimaryStorageConstant.SERVICE_ID);
+ bus.send(imsg);
+ }
+
+ trigger.rollback();
+ }
+ });
+
+ flow(new Flow() {
+ String __name__ = "download-from-backup-storage";
+
+ boolean deleteOnRollback;
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ DownloadParam param = new DownloadParam();
+ param.image = image;
+ param.installPath = makeCacheInstallPath(image.getInventory().getUuid());
+ BackupStorageMediator mediator = getBackupStorageMediator(image.getSelectedBackupStorage().getBackupStorageUuid());
+ mediator.param = param;
+
+ deleteOnRollback = mediator.deleteWhenRollabackDownload();
+ mediator.download(new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(String path) {
+ cachePath = path;
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ public void rollback(FlowRollback trigger, Map data) {
+ if (deleteOnRollback && cachePath != null) {
+ DeleteCmd cmd = new DeleteCmd();
+ cmd.installPath = cachePath;
+ httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(null) {
+ @Override
+ public void success(DeleteRsp returnValue) {
+ logger.debug(String.format("successfully deleted %s", cachePath));
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ //TODO
+ logger.warn(String.format("unable to delete %s, %s. Need a cleanup", cachePath, errorCode));
+ }
+ });
+ }
+
+ trigger.rollback();
+ }
+ });
+
+ flow(new Flow() {
+ String __name__ = "create-snapshot";
+
+ boolean needCleanup = false;
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ snapshotPath = String.format("%s@%s@image", cachePath, image.getInventory().getUuid());
+ CreateSnapshotCmd cmd = new CreateSnapshotCmd();
+ cmd.skipOnExisting = true;
+ cmd.snapshotPath = snapshotPath;
+ httpCall(CREATE_SNAPSHOT_PATH, cmd, CreateSnapshotRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(CreateSnapshotRsp returnValue) {
+ needCleanup = true;
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ public void rollback(FlowRollback trigger, Map data) {
+ if (needCleanup) {
+ DeleteSnapshotCmd cmd = new DeleteSnapshotCmd();
+ cmd.snapshotPath = snapshotPath;
+ httpCall(DELETE_SNAPSHOT_PATH, cmd, DeleteSnapshotRsp.class, new ReturnValueCompletion(null) {
+ @Override
+ public void success(DeleteSnapshotRsp returnValue) {
+ logger.debug(String.format("successfully deleted the snapshot %s", snapshotPath));
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ //TODO
+ logger.warn(String.format("unable to delete the snapshot %s, %s. Need a cleanup", snapshotPath, errorCode));
+ }
+ });
+ }
+
+ trigger.rollback();
+ }
+ });
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "protect-snapshot";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ ProtectSnapshotCmd cmd = new ProtectSnapshotCmd();
+ cmd.snapshotPath = snapshotPath;
+ cmd.ignoreError = true;
+ httpCall(PROTECT_SNAPSHOT_PATH, cmd, ProtectSnapshotRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(ProtectSnapshotRsp returnValue) {
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+ done(new FlowDoneHandler(completion) {
+ @Override
+ public void handle(Map data) {
+ ImageCacheVO cvo = new ImageCacheVO();
+ cvo.setMd5sum("not calculated");
+ cvo.setSize(image.getInventory().getActualSize());
+ cvo.setInstallUrl(snapshotPath);
+ cvo.setImageUuid(image.getInventory().getUuid());
+ cvo.setPrimaryStorageUuid(self.getUuid());
+ cvo.setMediaType(ImageMediaType.valueOf(image.getInventory().getMediaType()));
+ cvo.setState(ImageCacheState.ready);
+ cvo = dbf.persistAndRefresh(cvo);
+
+ completion.success(cvo);
+ }
+ });
+
+ error(new FlowErrorHandler(completion) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ completion.fail(errCode);
+ }
+ });
+ }
+ }).start();
+ }
+
+ void download(final ReturnValueCompletion completion) {
+ thdf.chainSubmit(new ChainTask(completion) {
+ @Override
+ public String getSyncSignature() {
+ return String.format("surfs-p-%s-download-image-%s", self.getUuid(), image.getInventory().getUuid());
+ }
+
+ @Override
+ public void run(final SyncTaskChain chain) {
+ doDownload(new ReturnValueCompletion(chain) {
+ @Override
+ public void success(ImageCacheVO returnValue) {
+ completion.success(returnValue);
+ chain.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(errorCode);
+ chain.next();
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return getSyncSignature();
+ }
+ });
+ }
+ }
+
+ private void createVolumeFromTemplate(final InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg msg) {
+ final ImageInventory img = msg.getTemplateSpec().getInventory();
+
+ final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply();
+ FlowChain chain = FlowChainBuilder.newShareFlowChain();
+ chain.setName(String.format("create-root-volume-%s", msg.getVolume().getUuid()));
+ chain.then(new ShareFlow() {
+ String cloneInstallPath;
+ String volumePath = makeRootVolumeInstallPath(msg.getVolume().getUuid());
+ ImageCacheVO cache;
+
+ @Override
+ public void setup() {
+ flow(new NoRollbackFlow() {
+ String __name__ = "download-image-to-cache";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ DownloadToCache downloadToCache = new DownloadToCache();
+ downloadToCache.image = msg.getTemplateSpec();
+ downloadToCache.download(new ReturnValueCompletion(trigger) {
+ @Override
+ public void success(ImageCacheVO returnValue) {
+ cloneInstallPath = returnValue.getInstallUrl();
+ cache = returnValue;
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+ });
+
+
+ flow(new NoRollbackFlow() {
+ String __name__ = "clone-image";
+
+ @Override
+ public void run(final FlowTrigger trigger, Map data) {
+ CloneCmd cmd = new CloneCmd();
+ cmd.srcPath = cloneInstallPath;
+ cmd.dstPath = volumePath;
+
+ httpCall(CLONE_PATH, cmd, CloneRsp.class, new ReturnValueCompletion(trigger) {
+ @Override
+ public void fail(ErrorCode err) {
+ trigger.fail(err);
+ }
+
+ @Override
+ public void success(CloneRsp ret) {
+ trigger.next();
+ }
+ });
+ }
+ });
+
+ done(new FlowDoneHandler(msg) {
+ @Override
+ public void handle(Map data) {
+ VolumeInventory vol = msg.getVolume();
+ vol.setInstallPath(volumePath);
+ vol.setFormat(VolumeConstant.VOLUME_FORMAT_RAW);
+ reply.setVolume(vol);
+
+ ImageCacheVolumeRefVO ref = new ImageCacheVolumeRefVO();
+ ref.setImageCacheId(cache.getId());
+ ref.setPrimaryStorageUuid(self.getUuid());
+ ref.setVolumeUuid(vol.getUuid());
+ dbf.persist(ref);
+
+ bus.reply(msg, reply);
+ }
+ });
+
+ error(new FlowErrorHandler(msg) {
+ @Override
+ public void handle(ErrorCode errCode, Map data) {
+ reply.setError(errCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+ }).start();
+ }
+
+ @Override
+ protected void handle(final GetPrimaryStorageFolderListMsg msg) {
+ GetPrimaryStorageFolderListReply reply = new GetPrimaryStorageFolderListReply();
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ protected void handle(final DeleteVolumeBitsOnPrimaryStorageMsg msg) {
+ DeleteCmd cmd = new DeleteCmd();
+ cmd.installPath = msg.getInstallPath();
+
+ final DeleteVolumeBitsOnPrimaryStorageReply reply = new DeleteVolumeBitsOnPrimaryStorageReply();
+
+ httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(DeleteRsp ret) {
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final DeleteVolumeOnPrimaryStorageMsg msg) {
+ DeleteCmd cmd = new DeleteCmd();
+ cmd.installPath = msg.getVolume().getInstallPath();
+
+ final DeleteVolumeOnPrimaryStorageReply reply = new DeleteVolumeOnPrimaryStorageReply();
+
+ httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(DeleteRsp ret) {
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final CreateTemplateFromVolumeOnPrimaryStorageMsg msg) {
+ final CreateTemplateFromVolumeOnPrimaryStorageReply reply = new CreateTemplateFromVolumeOnPrimaryStorageReply();
+ BackupStorageMediator mediator = getBackupStorageMediator(msg.getBackupStorageUuid());
+
+ UploadParam param = new UploadParam();
+ param.image = msg.getImageInventory();
+ param.primaryStorageInstallPath = msg.getVolumeInventory().getInstallPath();
+ mediator.param = param;
+ mediator.upload(new ReturnValueCompletion(msg) {
+ @Override
+ public void success(String returnValue) {
+ reply.setTemplateBackupStorageInstallPath(returnValue);
+ reply.setFormat(VolumeConstant.VOLUME_FORMAT_RAW);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
+ @Override
+ protected void handle(final DownloadDataVolumeToPrimaryStorageMsg msg) {
+ final DownloadDataVolumeToPrimaryStorageReply reply = new DownloadDataVolumeToPrimaryStorageReply();
+
+ BackupStorageMediator mediator = getBackupStorageMediator(msg.getBackupStorageRef().getBackupStorageUuid());
+ ImageSpec spec = new ImageSpec();
+ spec.setInventory(msg.getImage());
+ spec.setSelectedBackupStorage(msg.getBackupStorageRef());
+ DownloadParam param = new DownloadParam();
+ param.image = spec;
+ param.installPath = makeDataVolumeInstallPath(msg.getVolumeUuid());
+ mediator.param = param;
+ mediator.download(new ReturnValueCompletion