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} + ${project.java.version} + true + + + + org.codehaus.mojo + aspectj-maven-plugin + ${aspectj.plugin.version} + + + + compile + test-compile + + + + + ${project.java.version} + ${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(msg) { + @Override + public void success(String returnValue) { + reply.setInstallPath(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 DeleteBitsOnPrimaryStorageMsg msg) { + DeleteCmd cmd = new DeleteCmd(); + cmd.installPath = msg.getInstallPath(); + + final DeleteBitsOnPrimaryStorageReply reply = new DeleteBitsOnPrimaryStorageReply(); + + 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 DownloadIsoToPrimaryStorageMsg msg) { + final DownloadIsoToPrimaryStorageReply reply = new DownloadIsoToPrimaryStorageReply(); + DownloadToCache downloadToCache = new DownloadToCache(); + downloadToCache.image = msg.getIsoSpec(); + downloadToCache.download(new ReturnValueCompletion(msg) { + @Override + public void success(ImageCacheVO returnValue) { + reply.setInstallPath(returnValue.getInstallUrl()); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(DeleteIsoFromPrimaryStorageMsg msg) { + DeleteIsoFromPrimaryStorageReply reply = new DeleteIsoFromPrimaryStorageReply(); + bus.reply(msg, reply); + } + + @Override + protected void handle(AskVolumeSnapshotCapabilityMsg msg) { + AskVolumeSnapshotCapabilityReply reply = new AskVolumeSnapshotCapabilityReply(); + VolumeSnapshotCapability cap = new VolumeSnapshotCapability(); + cap.setSupport(true); + cap.setArrangementType(VolumeSnapshotArrangementType.INDIVIDUAL); + reply.setCapability(cap); + bus.reply(msg, reply); + } + + @Override + protected void handle(final SyncVolumeSizeOnPrimaryStorageMsg msg) { + final SyncVolumeSizeOnPrimaryStorageReply reply = new SyncVolumeSizeOnPrimaryStorageReply(); + final VolumeVO vol = dbf.findByUuid(msg.getVolumeUuid(), VolumeVO.class); + + String installPath = vol.getInstallPath(); + GetVolumeSizeCmd cmd = new GetVolumeSizeCmd(); + cmd.fsId = getSelf().getFsid(); + cmd.uuid = self.getUuid(); + cmd.volumeUuid = msg.getVolumeUuid(); + cmd.installPath = installPath; + + httpCall(GET_VOLUME_SIZE_PATH, cmd, GetVolumeSizeRsp.class, new ReturnValueCompletion(msg) { + @Override + public void success(GetVolumeSizeRsp rsp) { + // current surfs has no way to get actual size + long asize = rsp.actualSize == null ? vol.getActualSize() : rsp.actualSize; + reply.setActualSize(asize); + reply.setSize(rsp.size); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + protected void httpCall(final String path, final AgentCommand cmd, final Class retClass, final ReturnValueCompletion callback) { + cmd.setUuid(self.getUuid()); + cmd.setFsId(getSelf().getFsid()); + + final List mons = new ArrayList(); + for (SurfsPrimaryStorageNodeVO monvo : getSelf().getNodes()) { + if (monvo.getStatus() == NodeStatus.Connected) { + mons.add(new SurfsPrimaryStorageNodeBase(monvo)); + } + } + + if (mons.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + String.format("all surfs mons of primary storage[uuid:%s] are not in Connected state", self.getUuid()) + )); + } + + Collections.shuffle(mons); + + class HttpCaller { + Iterator it = mons.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; + } + + SurfsPrimaryStorageNodeBase base = it.next(); + + base.httpCall(path, cmd, retClass, new ReturnValueCompletion(callback) { + @Override + public void success(T ret) { + if (!ret.success) { + callback.fail(errf.stringToOperationError(ret.error)); + return; + } + + if (!(cmd instanceof InitCmd)) { + updateCapacityIfNeeded(ret); + } + callback.success(ret); + } + + @Override + public void fail(ErrorCode errorCode) { + errorCodes.add(errorCode); + call(); + } + }); + } + } + + new HttpCaller().call(); + } + + private void updateCapacityIfNeeded(AgentResponse rsp) { + if (rsp.totalCapacity != null && rsp.availableCapacity != null) { + new SurfsCapacityUpdater().update(getSelf().getFsid(), rsp.totalCapacity, rsp.availableCapacity); + } + } + + private void connect(final boolean newAdded, final Completion completion) { + final List mons = CollectionUtils.transformToList(getSelf().getNodes(), new Function() { + @Override + public SurfsPrimaryStorageNodeBase call(SurfsPrimaryStorageNodeVO arg) { + return new SurfsPrimaryStorageNodeBase(arg); + } + }); + + class Connector { + List errorCodes = new ArrayList(); + Iterator it = mons.iterator(); + + void connect(final FlowTrigger trigger) { + if (!it.hasNext()) { + if (errorCodes.size() == mons.size()) { + trigger.fail(errf.stringToOperationError( + String.format("unable to connect to the surfs primary storage[uuid:%s]. Failed to connect all surfs mons. Errors are %s", + self.getUuid(), JSONObjectUtil.toJsonString(errorCodes)) + )); + } else { + // reload because mon status changed + PrimaryStorageVO vo = dbf.reload(self); + if (vo == null) { + if (newAdded){ + if (!getSelf().getNodes().isEmpty()) { + dbf.removeCollection(getSelf().getNodes(), SurfsPrimaryStorageNodeVO.class); + } + } + trigger.fail(operr("surfs primary storage[uuid:%s] may have been deleted.", self.getUuid())); + } else { + self = vo; + trigger.next(); + } + } + + return; + } + + final SurfsPrimaryStorageNodeBase base = it.next(); + base.connect(new Completion(trigger) { + @Override + public void success() { + connect(trigger); + } + + @Override + public void fail(ErrorCode errorCode) { + errorCodes.add(errorCode); + + if (newAdded) { + // the mon fails to connect, remove it + dbf.remove(base.getSelf()); + } + + connect(trigger); + } + }); + } + } + + FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("connect-surfs-primary-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 mons = CollectionUtils.transformToList(getSelf().getNodes(), new Function() { + @Override + public SurfsPrimaryStorageNodeBase call(SurfsPrimaryStorageNodeVO arg) { + return arg.getStatus() == NodeStatus.Connected ? new SurfsPrimaryStorageNodeBase(arg) : null; + } + }); + + DebugUtils.Assert(!mons.isEmpty(), "how can be no connected MON !!!???"); + + final AsyncLatch latch = new AsyncLatch(mons.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 mons are mismatching, it seems the mons belong to different surfs clusters:\n"); + for (SurfsPrimaryStorageNodeBase mon : mons) { + String fsid = fsids.get(mon.getSelf().getUuid()); + sb.append(String.format("%s (mon ip) --> %s (fsid)\n", mon.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(SurfsPrimaryStorageVO.class); + q.add(SurfsPrimaryStorageVO_.fsid, Op.EQ, fsId); + q.add(SurfsPrimaryStorageVO_.uuid, Op.NOT_EQ, self.getUuid()); + SurfsPrimaryStorageVO otherFusion = q.find(); + if (otherFusion != null) { + throw new OperationFailureException(errf.stringToOperationError( + String.format("there is another Surfs primary storage[name:%s, uuid:%s] with the same" + + " FSID[%s], you cannot add the same Surfs setup as two different primary storage", + otherFusion.getName(), otherFusion.getUuid(), fsId) + )); + } + + trigger.next(); + } + }); + + for (final SurfsPrimaryStorageNodeBase mon : mons) { + GetFactsCmd cmd = new GetFactsCmd(); + cmd.uuid = self.getUuid(); + cmd.monUuid = mon.getSelf().getUuid(); + mon.httpCall(GET_FACTS, cmd, GetFactsRsp.class, new ReturnValueCompletion(latch) { + @Override + public void success(GetFactsRsp rsp) { + if (!rsp.success) { + // one mon cannot get the facts, directly error out + trigger.fail(errf.stringToOperationError(rsp.error)); + return; + } + + fsids.put(mon.getSelf().getUuid(), rsp.fsid); + latch.ack(); + } + + @Override + public void fail(ErrorCode errorCode) { + // one mon 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(); + List pools = new ArrayList(); + List monHostnames = new ArrayList(); + List sshUsernames = new ArrayList(); + List sshPasswords = new ArrayList(); + + final List mons = CollectionUtils.transformToList(getSelf().getNodes(), new Function() { + @Override + public SurfsPrimaryStorageNodeBase call(SurfsPrimaryStorageNodeVO arg) { + return new SurfsPrimaryStorageNodeBase(arg); + } + }); + + for (SurfsPrimaryStorageNodeBase mon : mons) { + monHostnames.add(mon.getSelf().getHostname()); + sshUsernames.add(mon.getSelf().getSshUsername()); + sshPasswords.add(mon.getSelf().getSshPassword()); + } + + Pool p = new Pool(); + p.name = getSelf().getImageCachePoolName(); + p.predefined = SurfsSystemTags.PREDEFINED_PRIMARY_STORAGE_IMAGE_CACHE_POOL.hasTag(self.getUuid()); + pools.add(p); + + p = new Pool(); + p.name = getSelf().getRootVolumePoolName(); + p.predefined = SurfsSystemTags.PREDEFINED_PRIMARY_STORAGE_ROOT_VOLUME_POOL.hasTag(self.getUuid()); + pools.add(p); + + p = new Pool(); + p.name = getSelf().getDataVolumePoolName(); + p.predefined = SurfsSystemTags.PREDEFINED_PRIMARY_STORAGE_DATA_VOLUME_POOL.hasTag(self.getUuid()); + pools.add(p); + + cmd.pools = pools; + cmd.monHostnames = monHostnames; + cmd.sshUsernames = sshUsernames; + cmd.sshPasswords = sshPasswords; + cmd.surfsType = SurfsGlobalProperty.SURFS_PRIMARY_STORAGE_TYPE; + + 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); + } + + getSelf().setUserKey(ret.userKey); + 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) { + PrimaryStorageVO vo = dbf.reload(self); + if (vo != null) { + self = vo; + } + if (!getSelf().getNodes().isEmpty()) { + dbf.removeCollection(getSelf().getNodes(), SurfsPrimaryStorageNodeVO.class); + } + } + + completion.fail(errCode); + } + }); + } + }).start(); + } + + @Override + protected void connectHook(ConnectParam param, final Completion completion) { + connect(param.isNewAdded(), completion); + } + + @Override + protected void pingHook(final Completion completion) { + final List mons = CollectionUtils.transformToList(getSelf().getNodes(), new Function() { + @Override + public SurfsPrimaryStorageNodeBase call(SurfsPrimaryStorageNodeVO arg) { + return new SurfsPrimaryStorageNodeBase(arg); + } + }); + + final List errors = new ArrayList(); + + class Ping { + private AtomicBoolean replied = new AtomicBoolean(false); + + @AsyncThread + private void reconnectNode(final SurfsPrimaryStorageNodeBase mon, boolean delay) { + if (!SurfsGlobalConfig.PRIMARY_STORAGE_MON_AUTO_RECONNECT.value(Boolean.class)) { + logger.debug(String.format("do not reconnect the surfs primary storage node[uuid:%s] as the global config[%s] is set to false", + self.getUuid(), SurfsGlobalConfig.PRIMARY_STORAGE_MON_AUTO_RECONNECT.getCanonicalName())); + return; + } + + // there has been a reconnect in process + if (!reconnectNodeLock.lock()) { + logger.debug(String.format("ignore this call, reconnect surfs primary storage node[uuid:%s] is in process", self.getUuid())); + return; + } + + final NoErrorCompletion releaseLock = new NoErrorCompletion() { + @Override + public void done() { + reconnectNodeLock.unlock(); + } + }; + + try { + if (delay) { + try { + TimeUnit.SECONDS.sleep(SurfsGlobalConfig.PRIMARY_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 primary" + + " 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 primary" + + " 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 nodes 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 primary storage[uuid:%s, name:%s]", + self.getUuid(), self.getName()), errors); + completion.fail(err); + } + }); + + for (final SurfsPrimaryStorageNodeBase mon : mons) { + mon.ping(new ReturnValueCompletion(latch) { + private void thisNodeIsDown(ErrorCode err) { + //TODO + logger.warn(String.format("cannot ping node[uuid:%s] of the surfs primary storage[uuid:%s, name:%s], %s", + mon.getSelf().getUuid(), self.getUuid(), self.getName(), err)); + errors.add(err); + mon.changeStatus(NodeStatus.Disconnected); + reconnectNode(mon, true); + latch.ack(); + } + + @Override + public void success(PingResult res) { + if (res.success) { + // as long as there is one mon working, the primary storage works + pingSuccess(); + + if (mon.getSelf().getStatus() == NodeStatus.Disconnected) { + reconnectNode(mon, false); + } + + } else if (res.operationFailure) { + // as long as there is one mon saying the surfs not working, the primary storage goes down + logger.warn(String.format("the surfs primary storage[uuid:%s, name:%s] is down, as one mon[uuid:%s] reports" + + " an operation failure[%s]", self.getUuid(), self.getName(), mon.getSelf().getUuid(), res.error)); + ErrorCode errorCode = errf.stringToOperationError(res.error); + errors.add(errorCode); + primaryStorageDown(); + } else { + // this mon is down(success == false, operationFailure == false), but the primary storage may still work as other mons may work + ErrorCode errorCode = errf.stringToOperationError(res.error); + thisNodeIsDown(errorCode); + } + } + + @Override + public void fail(ErrorCode errorCode) { + thisNodeIsDown(errorCode); + } + }); + } + } + + // this is called once a mon return an operation failure + private void primaryStorageDown() { + if (!replied.compareAndSet(false, true)) { + return; + } + + // set all nodes to be disconnected + for (SurfsPrimaryStorageNodeBase base : mons) { + base.changeStatus(NodeStatus.Disconnected); + } + + ErrorCode err = errf.stringToOperationError(String.format("failed to ping the surfs 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 + protected void syncPhysicalCapacity(ReturnValueCompletion completion) { + PrimaryStorageCapacityVO cap = dbf.findByUuid(self.getUuid(), PrimaryStorageCapacityVO.class); + PhysicalCapacityUsage usage = new PhysicalCapacityUsage(); + usage.availablePhysicalSize = cap.getAvailablePhysicalCapacity(); + usage.totalPhysicalSize = cap.getTotalPhysicalCapacity(); + completion.success(usage); + } + + @Override + protected void handle(APIReconnectPrimaryStorageMsg msg) { + final APIReconnectPrimaryStorageEvent evt = new APIReconnectPrimaryStorageEvent(msg.getId()); + self.setStatus(PrimaryStorageStatus.Connecting); + dbf.update(self); + connect(false, new Completion(msg) { + @Override + public void success() { + self = dbf.reload(self); + self.setStatus(PrimaryStorageStatus.Connected); + self = dbf.updateAndRefresh(self); + evt.setInventory(getSelfInventory()); + bus.publish(evt); + } + + @Override + public void fail(ErrorCode errorCode) { + self = dbf.reload(self); + self.setStatus(PrimaryStorageStatus.Disconnected); + self = dbf.updateAndRefresh(self); + evt.setError(errorCode); + bus.publish(evt); + } + }); + } + + @Override + protected void handleApiMessage(APIMessage msg) { + if (msg instanceof APIAddNodeToSurfsPrimaryStorageMsg) { + handle((APIAddNodeToSurfsPrimaryStorageMsg) msg); + } else if (msg instanceof APIRemoveNodeFromSurfsPrimaryStorageMsg) { + handle((APIRemoveNodeFromSurfsPrimaryStorageMsg) msg); + } else if (msg instanceof APIUpdateSurfsPrimaryStorageNodeMsg) { + handle((APIUpdateSurfsPrimaryStorageNodeMsg)msg); + } else { + super.handleApiMessage(msg); + } + } + + private void handle(APIUpdateSurfsPrimaryStorageNodeMsg msg) { + final APIUpdateNodeToSurfsPrimaryStorageEvent evt = new APIUpdateNodeToSurfsPrimaryStorageEvent(msg.getId()); + SurfsPrimaryStorageNodeVO monvo = dbf.findByUuid(msg.getNodeUuid(), SurfsPrimaryStorageNodeVO.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(SurfsPrimaryStorageInventory.valueOf((dbf.reload(getSelf())))); + bus.publish(evt); + } + + private void handle(APIRemoveNodeFromSurfsPrimaryStorageMsg msg) { + APIRemoveNodeFromSurfsPrimaryStorageEvent evt = new APIRemoveNodeFromSurfsPrimaryStorageEvent(msg.getId()); + + SimpleQuery q = dbf.createQuery(SurfsPrimaryStorageNodeVO.class); + q.add(SurfsPrimaryStorageNodeVO_.hostname, Op.IN, msg.getNodeHostnames()); + List vos = q.list(); + + dbf.removeCollection(vos, SurfsPrimaryStorageNodeVO.class); + evt.setInventory(SurfsPrimaryStorageInventory.valueOf(dbf.reload(getSelf()))); + bus.publish(evt); + } + + private void handle(final APIAddNodeToSurfsPrimaryStorageMsg msg) { + final APIAddNodeToSurfsPrimaryStorageEvent evt = new APIAddNodeToSurfsPrimaryStorageEvent(msg.getId()); + + FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("add-node-surfs-primary-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 (SurfsPrimaryStorageNodeVO nov :getSelf().getNodes()){ + if (nov.getHostname().equals(uri.getHostname())){ + trigger.fail(errf.stringToInternalError( + String.format("the node[%s] is exists in surfsprimarystorage[%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()) { + SurfsPrimaryStorageNodeVO monvo = new SurfsPrimaryStorageNodeVO(); + 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.setPrimaryStorageUuid(self.getUuid()); + monVOs.add(monvo); + } + + dbf.persistCollection(monVOs); + trigger.next(); + } + + @Override + public void rollback(FlowRollback trigger, Map data) { + dbf.removeCollection(monVOs, SurfsPrimaryStorageNodeVO.class); + trigger.rollback(); + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "connect-nodes"; + + @Override + public void run(final FlowTrigger trigger, Map data) { + List bases = CollectionUtils.transformToList(monVOs, new Function() { + @Override + public SurfsPrimaryStorageNodeBase call(SurfsPrimaryStorageNodeVO arg) { + return new SurfsPrimaryStorageNodeBase(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 (SurfsPrimaryStorageNodeBase base : bases) { + base.connect(new Completion(trigger) { + @Override + public void success() { + latch.ack(); + } + + @Override + public void fail(ErrorCode errorCode) { + // one fails, all fail + errorCodes.add(errorCode); + latch.ack(); + } + }); + } + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "check-node-integrity"; + + @Override + public void run(final FlowTrigger trigger, Map data) { + List bases = CollectionUtils.transformToList(monVOs, new Function() { + @Override + public SurfsPrimaryStorageNodeBase call(SurfsPrimaryStorageNodeVO arg) { + return new SurfsPrimaryStorageNodeBase(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 node to surfs primary storage", errors)); + } else { + trigger.next(); + } + } + }); + + for (final SurfsPrimaryStorageNodeBase base : bases) { + GetFactsCmd cmd = new GetFactsCmd(); + cmd.uuid = self.getUuid(); + cmd.monUuid = 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 node[ip:%s] returns a fsid[%s] different from the current fsid[%s] of the surfs cluster," + + "are you adding a node 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(SurfsPrimaryStorageInventory.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(); + } + + @Override + protected void handleLocalMessage(Message msg) { + if (msg instanceof TakeSnapshotMsg) { + handle((TakeSnapshotMsg) msg); + } else if (msg instanceof CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg) { + handle((CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg) msg); + } else if (msg instanceof BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg) { + handle((BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg) msg); + } else if (msg instanceof CreateKvmSecretMsg) { + handle((CreateKvmSecretMsg) msg); + } else if (msg instanceof UploadBitsToBackupStorageMsg) { + handle((UploadBitsToBackupStorageMsg) msg); + } else if (msg instanceof SetupSelfFencerOnKvmHostMsg) { + handle((SetupSelfFencerOnKvmHostMsg) msg); + } else { + super.handleLocalMessage(msg); + } + } + + private void handle(final SetupSelfFencerOnKvmHostMsg msg) { + KvmSetupSelfFencerParam param = msg.getParam(); + KvmSetupSelfFencerCmd cmd = new KvmSetupSelfFencerCmd(); + cmd.uuid = self.getUuid(); + cmd.fsId = getSelf().getFsid(); + cmd.hostUuid = param.getHostUuid(); + cmd.interval = param.getInterval(); + cmd.maxAttempts = param.getMaxAttempts(); + cmd.storageCheckerTimeout = param.getStorageCheckerTimeout(); + cmd.userKey = getSelf().getUserKey(); + cmd.heartbeatImagePath = String.format("%s/surfs-primary-storage-%s-heartbeat-file", getSelf().getRootVolumePoolName(), self.getUuid()); + cmd.monUrls = CollectionUtils.transformToList(getSelf().getNodes(), new Function() { + @Override + public String call(SurfsPrimaryStorageNodeVO arg) { + return String.format("%s:%s", arg.getHostname(), arg.getNodePort()); + } + }); + + final SetupSelfFencerOnKvmHostReply reply = new SetupSelfFencerOnKvmHostReply(); + new KvmCommandSender(param.getHostUuid()).send(cmd, KVM_HA_SETUP_SELF_FENCER, new KvmCommandFailureChecker() { + @Override + public ErrorCode getError(KvmResponseWrapper wrapper) { + AgentResponse rsp = wrapper.getResponse(AgentResponse.class); + return rsp.isSuccess() ? null : errf.stringToOperationError(rsp.getError()); + } + }, new ReturnValueCompletion(msg) { + @Override + public void success(KvmResponseWrapper wrapper) { + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + + private void handle(final UploadBitsToBackupStorageMsg msg) { + SimpleQuery q = dbf.createQuery(BackupStorageVO.class); + q.select(BackupStorageVO_.type); + q.add(BackupStorageVO_.uuid, Op.EQ, msg.getBackupStorageUuid()); + String bsType = q.findValue(); + + if (!SurfsConstants.SURFS_BACKUP_STORAGE_TYPE.equals(bsType)) { + throw new OperationFailureException(errf.stringToOperationError( + String.format("unable to upload bits to the backup storage[type:%s], we only support FUSIONSTOR", bsType) + )); + } + + final UploadBitsToBackupStorageReply reply = new UploadBitsToBackupStorageReply(); + + CpCmd cmd = new CpCmd(); + cmd.fsId = getSelf().getFsid(); + cmd.srcPath = msg.getPrimaryStorageInstallPath(); + cmd.dstPath = msg.getBackupStorageInstallPath(); + httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion(msg) { + @Override + public void success(CpRsp rsp) { + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(GetInstallPathForDataVolumeDownloadMsg msg) { + GetInstallPathForDataVolumeDownloadReply reply = new GetInstallPathForDataVolumeDownloadReply(); + reply.setInstallPath(makeDataVolumeInstallPath(msg.getVolumeUuid())); + bus.reply(msg, reply); + } + + private void handle(final CreateKvmSecretMsg msg) { + final CreateKvmSecretReply reply = new CreateKvmSecretReply(); + createSecretOnKvmHosts(msg.getHostUuids(), new Completion(msg) { + @Override + public void success() { + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + private void handle(BackupVolumeSnapshotFromPrimaryStorageToBackupStorageMsg msg) { + BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply reply = new BackupVolumeSnapshotFromPrimaryStorageToBackupStorageReply(); + reply.setError(errf.stringToOperationError("backing up snapshots to backup storage is a depreciated feature, which will be removed in future version")); + bus.reply(msg, reply); + } + + private void handle(final CreateVolumeFromVolumeSnapshotOnPrimaryStorageMsg msg) { + final CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply reply = new CreateVolumeFromVolumeSnapshotOnPrimaryStorageReply(); + + final String volPath = makeDataVolumeInstallPath(msg.getVolumeUuid()); + VolumeSnapshotInventory sp = msg.getSnapshot(); + CpCmd cmd = new CpCmd(); + cmd.resourceUuid = msg.getSnapshot().getVolumeUuid(); + cmd.srcPath = sp.getPrimaryStorageInstallPath(); + cmd.dstPath = volPath; + httpCall(CP_PATH, cmd, CpRsp.class, new ReturnValueCompletion(msg) { + @Override + public void success(CpRsp rsp) { + reply.setInstallPath(volPath); + reply.setSize(rsp.size); + + // current surfs has no way to get the actual size + long asize = rsp.actualSize == null ? 1 : rsp.actualSize; + reply.setActualSize(asize); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + protected void handle(final RevertVolumeFromSnapshotOnPrimaryStorageMsg msg) { + final RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); + + final FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("revert-volume-[uuid:%s]-from-snapshot-[uuid:%s]-on-surfs-primary-storage", + msg.getVolume().getUuid(), msg.getSnapshot().getUuid())); + chain.then(new ShareFlow() { + @Override + public void setup() { + String originalVolumePath = msg.getVolume().getInstallPath(); + // get volume path from snapshot path, just split @ + String volumePath = msg.getSnapshot().getPrimaryStorageInstallPath().split("@")[0]; + + flow(new NoRollbackFlow() { + @Override + public void run(FlowTrigger trigger, Map data) { + RollbackSnapshotCmd cmd = new RollbackSnapshotCmd(); + cmd.snapshotPath = msg.getSnapshot().getPrimaryStorageInstallPath(); + httpCall(ROLLBACK_SNAPSHOT_PATH, cmd, RollbackSnapshotRsp.class, new ReturnValueCompletion(msg) { + @Override + public void success(RollbackSnapshotRsp returnValue) { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "delete-origin-root-volume-which-has-no-snapshot"; + + @Override + public void run(FlowTrigger trigger, Map data) { + SimpleQuery sq = dbf.createQuery(VolumeSnapshotVO.class); + sq.add(VolumeSnapshotVO_.primaryStorageInstallPath, Op.LIKE, + String.format("%s%%", originalVolumePath)); + sq.count(); + if (sq.count() == 0) { + DeleteCmd cmd = new DeleteCmd(); + cmd.installPath = originalVolumePath; + httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(null) { + @Override + public void success(DeleteRsp returnValue) { + logger.debug(String.format("successfully deleted %s", originalVolumePath)); + } + + @Override + public void fail(ErrorCode errorCode) { + //TODO GC + logger.warn(String.format("unable to delete %s, %s. Need a cleanup", + originalVolumePath, errorCode)); + } + }); + } + trigger.next(); + } + }); + + done(new FlowDoneHandler(msg) { + @Override + public void handle(Map data) { + reply.setNewVolumeInstallPath(volumePath); + bus.reply(msg, reply); + } + }); + + error(new FlowErrorHandler(msg) { + @Override + public void handle(ErrorCode errCode, Map data) { + reply.setError(errCode); + bus.reply(msg, reply); + } + }); + } + }).start(); + + + } + + protected void handle(final ReInitRootVolumeFromTemplateOnPrimaryStorageMsg msg) { + final ReInitRootVolumeFromTemplateOnPrimaryStorageReply reply = new ReInitRootVolumeFromTemplateOnPrimaryStorageReply(); + + final FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("reimage-vm-root-volume-%s", msg.getVolume().getUuid())); + chain.then(new ShareFlow() { + String cloneInstallPath; + String originalVolumePath = msg.getVolume().getInstallPath(); + String volumePath = makeResetImageRootVolumeInstallPath(msg.getVolume().getUuid()); + ImageCacheVO cache; + + @Override + public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "clone-image"; + + @Override + public void run(final FlowTrigger trigger, Map data) { + SimpleQuery sq = dbf.createQuery(ImageCacheVO.class); + sq.add(ImageCacheVO_.imageUuid, Op.EQ, msg.getVolume().getRootImageUuid()); + sq.add(ImageCacheVO_.primaryStorageUuid, Op.EQ, msg.getPrimaryStorageUuid()); + ImageCacheVO ivo = sq.find(); + + CloneCmd cmd = new CloneCmd(); + cmd.srcPath = ivo.getInstallUrl(); + 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(); + } + }); + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "delete-origin-root-volume-which-has-no-snapshot"; + + @Override + public void run(FlowTrigger trigger, Map data) { + SimpleQuery sq = dbf.createQuery(VolumeSnapshotVO.class); + sq.add(VolumeSnapshotVO_.primaryStorageInstallPath, Op.LIKE, + String.format("%s%%", originalVolumePath)); + sq.count(); + if (sq.count() == 0) { + DeleteCmd cmd = new DeleteCmd(); + cmd.installPath = originalVolumePath; + httpCall(DELETE_PATH, cmd, DeleteRsp.class, new ReturnValueCompletion(null) { + @Override + public void success(DeleteRsp returnValue) { + logger.debug(String.format("successfully deleted %s", originalVolumePath)); + } + + @Override + public void fail(ErrorCode errorCode) { + //TODO GC + logger.warn(String.format("unable to delete %s, %s. Need a cleanup", + originalVolumePath, errorCode)); + } + }); + } + trigger.next(); + } + }); + + done(new FlowDoneHandler(msg) { + @Override + public void handle(Map data) { + reply.setNewVolumeInstallPath(volumePath); + 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 DeleteSnapshotOnPrimaryStorageMsg msg) { + final DeleteSnapshotOnPrimaryStorageReply reply = new DeleteSnapshotOnPrimaryStorageReply(); + DeleteSnapshotCmd cmd = new DeleteSnapshotCmd(); + cmd.snapshotPath = msg.getSnapshot().getPrimaryStorageInstallPath(); + httpCall(DELETE_SNAPSHOT_PATH, cmd, DeleteSnapshotRsp.class, new ReturnValueCompletion(msg) { + @Override + public void success(DeleteSnapshotRsp returnValue) { + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + protected void handle(MergeVolumeSnapshotOnPrimaryStorageMsg msg) { + MergeVolumeSnapshotOnPrimaryStorageReply reply = new MergeVolumeSnapshotOnPrimaryStorageReply(); + bus.reply(msg, reply); + } + + private void handle(final TakeSnapshotMsg msg) { + final TakeSnapshotReply reply = new TakeSnapshotReply(); + + final VolumeSnapshotInventory sp = msg.getStruct().getCurrent(); + SimpleQuery q = dbf.createQuery(VolumeVO.class); + q.select(VolumeVO_.installPath); + q.add(VolumeVO_.uuid, Op.EQ, sp.getVolumeUuid()); + String volumePath = q.findValue(); + + final String spPath = String.format("%s@%s@volume", volumePath, sp.getUuid()); + CreateSnapshotCmd cmd = new CreateSnapshotCmd(); + cmd.volumeUuid = sp.getVolumeUuid(); + cmd.snapshotPath = spPath; + cmd.setVolsize(sp.getSize()); + httpCall(CREATE_SNAPSHOT_PATH, cmd, CreateSnapshotRsp.class, new ReturnValueCompletion(msg) { + @Override + public void success(CreateSnapshotRsp rsp) { + // current surfs has no way to get actual size + long asize = rsp.getActualSize() == null ? 1 : rsp.getActualSize(); + sp.setSize(asize); + sp.setPrimaryStorageUuid(self.getUuid()); + sp.setPrimaryStorageInstallPath(spPath); + sp.setType(VolumeSnapshotConstant.STORAGE_SNAPSHOT_TYPE.toString()); + sp.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); + reply.setInventory(sp); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + public void attachHook(String clusterUuid, Completion completion) { + SimpleQuery q = dbf.createQuery(ClusterVO.class); + q.select(ClusterVO_.hypervisorType); + q.add(ClusterVO_.uuid, Op.EQ, clusterUuid); + String hvType = q.findValue(); + if (KVMConstant.KVM_HYPERVISOR_TYPE.equals(hvType)) { + attachToKvmCluster(clusterUuid, completion); + } else { + completion.success(); + } + } + + private void createSecretOnKvmHosts(List hostUuids, final Completion completion) { + completion.success(); + } + + private void attachToKvmCluster(String clusterUuid, Completion completion) { + SimpleQuery q = dbf.createQuery(HostVO.class); + q.select(HostVO_.uuid); + q.add(HostVO_.clusterUuid, Op.EQ, clusterUuid); + q.add(HostVO_.status, Op.EQ, HostStatus.Connected); + List hostUuids = q.listValue(); + if (hostUuids.isEmpty()) { + completion.success(); + } else { + createSecretOnKvmHosts(hostUuids, completion); + } + } + + public String attachVolumeToVmPrepare(String hostname,String volumeinstallpath,String vol_id,long vol_size,String vol_type,String device_tp){ + final List nodes = new ArrayList(); + for (SurfsPrimaryStorageNodeVO nodevo : getSelf().getNodes()) { + if (nodevo.getStatus() == NodeStatus.Connected && nodevo.getHostname().equals(hostname)) { + nodes.add(new SurfsPrimaryStorageNodeBase(nodevo)); + } + } + + if (nodes.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + + String.format("node[uuid:%s] of primary storage[uuid:%s] are not in Connected state", hostname,self.getUuid()) + )); + } + Iterator it = nodes.iterator(); + List errorCodes = new ArrayList(); + SurfsPrimaryStorageNodeBase base = it.next(); + class NoMsg extends Message{ + String smsg; + String device_type; + public String getSmsg(){ + return smsg; + } + public void setSmsg(String msg){ + this.smsg=msg; + } + public String getDeviceType(){ + return this.device_type; + } + public void setDeviceType(String d_type){ + this.device_type=d_type; + } + } + AttachDataVolToVmRsp advtv= new AttachDataVolToVmRsp(); + NoMsg nomsg=new NoMsg(); + AttachDataVolToVm cmd =new AttachDataVolToVm(); + final FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("before-attach-volume[%s]-to-vm[%s]", vol_id,hostname)); + chain.then(new ShareFlow() { + @Override + public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "set-something"; + + @Override + public void run(FlowTrigger trigger, Map data) { + if (nomsg.getSmsg() == null){ + nomsg.setSmsg("start"); + nomsg.setDeviceType(device_tp); + } + cmd.setInstallPath(volumeinstallpath); + cmd.setUuid(self.getUuid()); + cmd.setFsId(getSelf().getFsid()); + cmd.setVoltype(vol_type); + cmd.setVolsize(vol_size); + cmd.setMgip(hostname); + trigger.next(); + } + + }); + flow(new NoRollbackFlow() { + String __name__ = "call-surfs-primary-agent-prepare"; + + @Override + public void run(FlowTrigger trigger, Map data) { + base.httpCall(ATTACH_VOLUME_PREPARE,cmd,AttachDataVolToVmRsp.class,new ReturnValueCompletion(null){ + @Override + public void success(AttachDataVolToVmRsp ret) { + nomsg.setDeviceType(ret.getDeviceType()); + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + errorCodes.add(errorCode); + trigger.next(); + } + + }); + + } + }); + flow(new NoRollbackFlow() { + String __name__ = "attach-before-end"; + @Override + public void run(FlowTrigger trigger, Map data) { + + nomsg.setSmsg("end"); + trigger.next(); + } + + }); + + done(new FlowDoneHandler(nomsg) { + @Override + public void handle(Map data) { + return; + } + }); + + error(new FlowErrorHandler(nomsg) { + @Override + public void handle(ErrorCode errCode, Map data) { + return; + } + }); + + } + + }).start(); + try{ + while (true){ + if (nomsg.getSmsg().equals("end")){ + break; + } + + Thread.sleep(500); + } + }catch(Exception ex){ + logger.debug(String.format("error to sleep for attach[%s]",cmd.installPath)); + } + return nomsg.getDeviceType(); + } + + public void detachVolumeFromVmafter(String hostname,String volumeinstallpath,String vol_id){ + final List nodes = new ArrayList(); + for (SurfsPrimaryStorageNodeVO nodevo : getSelf().getNodes()) { + if (nodevo.getStatus() == NodeStatus.Connected && nodevo.getHostname().equals(hostname)) { + nodes.add(new SurfsPrimaryStorageNodeBase(nodevo)); + } + } + + if (nodes.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + + String.format("node[uuid:%s] of primary storage[uuid:%s] are not in Connected state", hostname,self.getUuid()) + )); + } + Iterator it = nodes.iterator(); + List errorCodes = new ArrayList(); + SurfsPrimaryStorageNodeBase base = it.next(); + class NoMsg extends Message{ + String smsg; + public String getSmsg(){ + return smsg; + } + public void setSmsg(String msg){ + this.smsg=msg; + } + } + NoMsg nomsg=new NoMsg(); + AttachDataVolToVm cmd =new AttachDataVolToVm(); + + final FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("after-detach-volume[%s]-from-vm[%s]", vol_id,hostname)); + chain.then(new ShareFlow() { + @Override + public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "set-something"; + + @Override + public void run(FlowTrigger trigger, Map data) { + if (nomsg.getSmsg() == null){ + nomsg.setSmsg("start"); + } + cmd.installPath=volumeinstallpath; + cmd.setUuid(self.getUuid()); + cmd.setFsId(getSelf().getFsid()); + trigger.next(); + } + + }); + flow(new NoRollbackFlow() { + String __name__ = "call-surfs-primary-agent-detach"; + + @Override + public void run(FlowTrigger trigger, Map data) { + base.httpCall(DETACH_VOLUME_AFTER,cmd,AttachDataVolToVmRsp.class,new ReturnValueCompletion(null){ + @Override + public void success(AttachDataVolToVmRsp ret) { + trigger.next(); + return; + } + + @Override + public void fail(ErrorCode errorCode) { + errorCodes.add(errorCode); + trigger.next(); + } + + }); + + } + }); + flow(new NoRollbackFlow() { + String __name__ = "attach-before-end"; + @Override + public void run(FlowTrigger trigger, Map data) { + nomsg.setSmsg("end"); + trigger.next(); + } + + }); + + done(new FlowDoneHandler(nomsg) { + @Override + public void handle(Map data) { + return; + } + }); + + error(new FlowErrorHandler(nomsg) { + @Override + public void handle(ErrorCode errCode, Map data) { + return; + } + }); + + } + + }).start(); + + } + + public void startVmBefore(String hostip,String installpath,String root_vol_id,String vm_id,String datavols,int intevel){ + final List nodes = new ArrayList(); + for (SurfsPrimaryStorageNodeVO nodevo : getSelf().getNodes()) { + if (nodevo.getStatus() == NodeStatus.Connected && nodevo.getHostname().equals(hostip)) { + nodes.add(new SurfsPrimaryStorageNodeBase(nodevo)); + } + } + + if (nodes.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + + String.format("node[uuid:%s] of primary storage[uuid:%s] are not in Connected state", hostip,self.getUuid()) + )); + } + Iterator it = nodes.iterator(); + List errorCodes = new ArrayList(); + SurfsPrimaryStorageNodeBase base = it.next(); + class NoMsg extends Message{ + String smsg; + public String getSmsg(){ + return smsg; + } + public void setSmsg(String msg){ + this.smsg=msg; + } + } + StartVmBeforeRsp svbr=new StartVmBeforeRsp(); + NoMsg nomsg=new NoMsg(); + StartVmBefore cmd =new StartVmBefore(); + final FlowChain chain = FlowChainBuilder.newShareFlowChain(); + chain.setName(String.format("start-vm[%s]-before-on-host[%s]",vm_id,hostip)); + chain.then(new ShareFlow() { + @Override + public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "set-something"; + + @Override + public void run(FlowTrigger trigger, Map data) { + if (nomsg.getSmsg() == null){ + nomsg.setSmsg("start"); + } + cmd.installPath=installpath; + cmd.volinstallPath=datavols; + cmd.setUuid(self.getUuid()); + cmd.setFsId(getSelf().getFsid()); + cmd.setNodeIp(hostip); + trigger.next(); + } + + }); + flow(new NoRollbackFlow() { + String __name__ = "call-surfs-primary-agent-prepare"; + + @Override + public void run(FlowTrigger trigger, Map data) { + base.httpCall(START_VM_BEFORE,cmd,StartVmBeforeRsp.class,new ReturnValueCompletion(null){ + @Override + public void success(StartVmBeforeRsp ret) { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + errorCodes.add(errorCode); + trigger.next(); + } + + }); + + } + }); + flow(new NoRollbackFlow() { + String __name__ = "start-vm-before-end"; + @Override + public void run(FlowTrigger trigger, Map data) { + + nomsg.setSmsg("end"); + trigger.next(); + } + + }); + + done(new FlowDoneHandler(nomsg) { + @Override + public void handle(Map data) { + return; + } + }); + + error(new FlowErrorHandler(nomsg) { + @Override + public void handle(ErrorCode errCode, Map data) { + return; + } + }); + + } + + }).start(); + try{ + int sk=0; + while (true){ + if (nomsg.getSmsg().equals("end")){ + break; + } + if (sk > intevel){ + break; + } + Thread.sleep(500); + sk =sk + 1; + } + }catch(Exception ex){ + logger.debug(String.format("error to sleep for start vm[%s] befroe",vm_id)); + } + + } + + @Override + public void deleteHook() { + if (SurfsGlobalConfig.PRIMARY_STORAGE_DELETE_POOL.value(Boolean.class)) { + DeletePoolCmd cmd = new DeletePoolCmd(); + cmd.poolNames = list(getSelf().getImageCachePoolName(), getSelf().getDataVolumePoolName(), getSelf().getRootVolumePoolName()); + FutureReturnValueCompletion completion = new FutureReturnValueCompletion(null); + httpCall(DELETE_POOL_PATH, cmd, DeletePoolRsp.class, completion); + completion.await(TimeUnit.MINUTES.toMillis(30)); + if (!completion.isSuccess()) { + throw new OperationFailureException(completion.getErrorCode()); + } + } + dbf.removeCollection(getSelf().getNodes(), SurfsPrimaryStorageNodeVO.class); + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageFactory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageFactory.java new file mode 100644 index 00000000000..41dd74354bc --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageFactory.java @@ -0,0 +1,624 @@ +package org.zstack.storage.surfs.primary; + +import org.springframework.beans.factory.annotation.Autowired; +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.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.config.GlobalConfig; +import org.zstack.core.config.GlobalConfigUpdateExtensionPoint; +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.core.thread.PeriodicTask; +import org.zstack.core.thread.ThreadFacade; +import org.zstack.header.Component; +import org.zstack.header.core.Completion; +import org.zstack.header.core.workflow.*; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.OperationFailureException; +import org.zstack.header.message.MessageReply; +import org.zstack.header.storage.backup.BackupStorageAskInstallPathMsg; +import org.zstack.header.storage.backup.BackupStorageAskInstallPathReply; +import org.zstack.header.storage.backup.BackupStorageConstant; +import org.zstack.header.storage.backup.DeleteBitsOnBackupStorageMsg; +import org.zstack.header.storage.primary.*; +import org.zstack.storage.surfs.primary.KVMSurfsVolumeTO.NodeInfo; +import org.zstack.header.storage.snapshot.CreateTemplateFromVolumeSnapshotExtensionPoint; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.volume.SyncVolumeSizeMsg; +import org.zstack.header.volume.SyncVolumeSizeReply; +import org.zstack.header.volume.VolumeConstant; +import org.zstack.header.volume.VolumeInventory; +import org.zstack.kvm.KVMAgentCommands.*; +import org.zstack.kvm.*; +import org.zstack.storage.surfs.*; +import org.zstack.storage.primary.PrimaryStorageCapacityUpdater; +import org.zstack.tag.SystemTagCreator; +import org.zstack.utils.CollectionUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.function.Function; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.zstack.utils.CollectionDSL.e; +import static org.zstack.utils.CollectionDSL.map; + +/** + * Created by zhouhaiping 2017-09-14 + */ +public class SurfsPrimaryStorageFactory implements PrimaryStorageFactory, SurfsCapacityUpdateExtensionPoint, KVMStartVmExtensionPoint, + KVMAttachVolumeExtensionPoint, KVMDetachVolumeExtensionPoint, CreateTemplateFromVolumeSnapshotExtensionPoint, KvmSetupSelfFencerExtensionPoint, Component { + private static final CLogger logger = Utils.getLogger(SurfsPrimaryStorageFactory.class); + + public static final PrimaryStorageType type = new PrimaryStorageType(SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE); + + public static final String QEMUPATH = "/opt/surfstack/qemu/bin/qemu-system-x86_64"; + + private SurfsPrimaryStorageBase SurfsPbase; + + @Autowired + private DatabaseFacade dbf; + @Autowired + private ErrorFacade errf; + @Autowired + private AnsibleFacade asf; + @Autowired + private ThreadFacade thdf; + @Autowired + private CloudBus bus; + + private Future imageCacheCleanupThread; + + static { + type.setSupportHeartbeatFile(true); + } + + void init() { + type.setPrimaryStorageFindBackupStorage(new PrimaryStorageFindBackupStorage() { + @Override + @Transactional(readOnly = true) + public List findBackupStorage(String primaryStorageUuid) { + String sql = "select b.uuid from SurfsPrimaryStorageVO p, SurfsBackupStorageVO b where b.fsid = p.fsid" + + " and p.uuid = :puuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, String.class); + q.setParameter("puuid", primaryStorageUuid); + return q.getResultList(); + } + }); + + } + + @Override + public PrimaryStorageType getPrimaryStorageType() { + return type; + } + + @Override + @Transactional + public PrimaryStorageInventory createPrimaryStorage(PrimaryStorageVO vo, APIAddPrimaryStorageMsg msg) { + APIAddSurfsPrimaryStorageMsg cmsg = (APIAddSurfsPrimaryStorageMsg) msg; + + SurfsPrimaryStorageVO cvo = new SurfsPrimaryStorageVO(vo); + cvo.setType(SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE); + cvo.setMountPath(SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE); + cvo.setRootVolumePoolName(cmsg.getRootVolumePoolName() == null ? String.format("pri-v-r-%s", vo.getUuid()) : cmsg.getRootVolumePoolName()); + cvo.setDataVolumePoolName(cmsg.getDataVolumePoolName() == null ? String.format("pri-v-d-%s", vo.getUuid()) : cmsg.getDataVolumePoolName()); + cvo.setImageCachePoolName(cmsg.getImageCachePoolName() == null ? String.format("pri-c-%s", vo.getUuid()) : cmsg.getImageCachePoolName()); + + dbf.getEntityManager().persist(cvo); + + if (cmsg.getImageCachePoolName() != null) { + SystemTagCreator creator = SurfsSystemTags.PREDEFINED_PRIMARY_STORAGE_IMAGE_CACHE_POOL.newSystemTagCreator(cvo.getUuid()); + creator.ignoreIfExisting = true; + creator.create(); + } + if (cmsg.getRootVolumePoolName() != null) { + SystemTagCreator creator = SurfsSystemTags.PREDEFINED_PRIMARY_STORAGE_ROOT_VOLUME_POOL.newSystemTagCreator(cvo.getUuid()); + creator.ignoreIfExisting = true; + creator.create(); + } + if (cmsg.getDataVolumePoolName() != null) { + SystemTagCreator creator = SurfsSystemTags.PREDEFINED_PRIMARY_STORAGE_DATA_VOLUME_POOL.newSystemTagCreator(cvo.getUuid()); + creator.ignoreIfExisting = true; + creator.create(); + } + + for (String url : cmsg.getNodeUrls()) { + SurfsPrimaryStorageNodeVO mvo = new SurfsPrimaryStorageNodeVO(); + NodeUri uri = new NodeUri(url); + mvo.setUuid(Platform.getUuid()); + mvo.setStatus(NodeStatus.Connecting); + mvo.setHostname(uri.getHostname()); + mvo.setNodeAddr(mvo.getHostname()); + mvo.setNodePort(uri.getNodePort()); + mvo.setSshPort(uri.getSshPort()); + mvo.setSshUsername(uri.getSshUsername()); + mvo.setSshPassword(uri.getSshPassword()); + mvo.setPrimaryStorageUuid(cvo.getUuid()); + dbf.getEntityManager().persist(mvo); + } + + SystemTagCreator creator = SurfsSystemTags.KVM_SECRET_UUID.newSystemTagCreator(vo.getUuid()); + creator.inherent = true; + creator.recreate = true; + creator.setTagByTokens(map(e(SurfsSystemTags.KVM_SECRET_UUID_TOKEN, UUID.randomUUID().toString()))); + creator.create(); + + return PrimaryStorageInventory.valueOf(cvo); + } + + @Override + public PrimaryStorage getPrimaryStorage(PrimaryStorageVO vo) { + SurfsPrimaryStorageVO cvo = dbf.findByUuid(vo.getUuid(), SurfsPrimaryStorageVO.class); + SurfsPbase = new SurfsPrimaryStorageBase(cvo); + return SurfsPbase; + } + + @Override + public PrimaryStorageInventory getInventory(String uuid) { + return SurfsPrimaryStorageInventory.valueOf(dbf.findByUuid(uuid, SurfsPrimaryStorageVO.class)); + } + + @Override + public void update(String fsid, final long total, final long avail) { + String sql = "select cap from PrimaryStorageCapacityVO cap, SurfsPrimaryStorageVO pri where pri.uuid = cap.uuid and pri.fsid = :fsid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, PrimaryStorageCapacityVO.class); + q.setParameter("fsid", fsid); + PrimaryStorageCapacityUpdater updater = new PrimaryStorageCapacityUpdater(q); + updater.run(new PrimaryStorageCapacityUpdaterRunnable() { + @Override + public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) { + /* + if (cap.getTotalCapacity() == 0 && cap.getAvailableCapacity() == 0) { + // init + cap.setTotalCapacity(total); + cap.setAvailableCapacity(avail); + } + */ + cap.setTotalCapacity(total); + cap.setAvailableCapacity(avail); + cap.setTotalPhysicalCapacity(total); + cap.setAvailablePhysicalCapacity(avail); + + return cap; + } + }); + } + + + private IsoTO convertIsoToSurfsIfNeeded(final IsoTO to) { + if (to == null || !to.getPath().startsWith("iscsi")) { + return to; + } + + SurfsPrimaryStorageVO pri = new Callable() { + @Override + @Transactional(readOnly = true) + public SurfsPrimaryStorageVO call() { + String sql = "select pri from SurfsPrimaryStorageVO pri, ImageCacheVO c where pri.uuid = c.primaryStorageUuid" + + " and c.imageUuid = :imgUuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, SurfsPrimaryStorageVO.class); + q.setParameter("imgUuid", to.getImageUuid()); + return q.getSingleResult(); + } + }.call(); + + KvmSurfsIsoTO cto = new KvmSurfsIsoTO(to); + cto.setNodeInfo(CollectionUtils.transformToList(pri.getNodes(), new Function() { + @Override + public KvmSurfsIsoTO.NodeInfo call(SurfsPrimaryStorageNodeVO arg) { + if (NodeStatus.Connected != arg.getStatus()) { + return null; + } + + KvmSurfsIsoTO.NodeInfo info = new KvmSurfsIsoTO.NodeInfo(); + info.setHostname(arg.getHostname()); + info.setPort(arg.getNodePort()); + return info; + } + })); + + if (cto.getNodeInfo().isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + String.format("cannot find any Connected surfs mon for the primary storage[uuid:%s]", pri.getUuid()) + )); + } + + return cto; + } + + private VolumeTO convertVolumeToSurfsIfNeeded(VolumeInventory vol, VolumeTO to) { + + if (!vol.getInstallPath().startsWith("iscsi")) { + return to; + } + + SimpleQuery q = dbf.createQuery(SurfsPrimaryStorageNodeVO.class); + q.select(SurfsPrimaryStorageNodeVO_.hostname, SurfsPrimaryStorageNodeVO_.nodePort); + q.add(SurfsPrimaryStorageNodeVO_.primaryStorageUuid, Op.EQ, vol.getPrimaryStorageUuid()); + q.add(SurfsPrimaryStorageNodeVO_.status, Op.EQ, NodeStatus.Connected); + List ts = q.listTuple(); + + if (ts.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + String.format("cannot find any Connected surfs node for the primary storage[uuid:%s]", vol.getPrimaryStorageUuid()) + )); + } + + List monInfos = CollectionUtils.transformToList(ts, new Function() { + @Override + public NodeInfo call(Tuple t) { + String hostname = t.get(0, String.class); + int port = t.get(1, Integer.class); + + NodeInfo info = new NodeInfo(); + info.hostname = hostname; + info.port = port; + return info; + } + }); + + KVMSurfsVolumeTO cto = new KVMSurfsVolumeTO(to); + cto.setNodeInfo(monInfos); + cto.setDeviceType("surfs"); + return cto; + } + + @Override + public void beforeAttachVolume(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, AttachDataVolumeCmd cmd) { + if (SurfsPbase == null){ + return; + } + cmd.setVolume(convertVolumeToSurfsIfNeeded(volume, cmd.getVolume())); + class VolType{ + String voltype; + public String getVoltype(){ + return this.voltype; + } + public void setVoltype(String vltype){ + this.voltype=vltype; + } + } + VolType voltp=new VolType(); + try{ + Field fds=volume.getClass().getDeclaredField("poolcls"); + fds.setAccessible(true); + voltp.setVoltype(fds.get(volume).toString()); + }catch(Exception ex){ + voltp.setVoltype("hdd"); + logger.warn("Can not get volume pool class,now set common type to hdd "); + } + String dtype=SurfsPbase.attachVolumeToVmPrepare(host.getManagementIp(), cmd.getVolume().getInstallPath(), cmd.getVolume().getVolumeUuid(),volume.getSize(),voltp.getVoltype(),cmd.getVolume().getDeviceType()); + cmd.getVolume().setDeviceType(dtype); + if (dtype.equals("file")){ + cmd.getVolume().setUseVirtioSCSI(false); + cmd.getVolume().setUseVirtio(true); + } + + } + + @Override + public void afterAttachVolume(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, AttachDataVolumeCmd cmd) { + + } + + @Override + public void attachVolumeFailed(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, AttachDataVolumeCmd cmd, ErrorCode err) { + + } + + @Override + public void beforeDetachVolume(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, DetachDataVolumeCmd cmd) { + cmd.setVolume(convertVolumeToSurfsIfNeeded(volume, cmd.getVolume())); + + } + + @Override + public void afterDetachVolume(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, DetachDataVolumeCmd cmd) { + if (SurfsPbase == null){ + return; + } + SurfsPbase.detachVolumeFromVmafter(host.getManagementIp(), cmd.getVolume().getInstallPath(), cmd.getVolume().getVolumeUuid()); + } + + @Override + public void detachVolumeFailed(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, DetachDataVolumeCmd cmd, ErrorCode err) { + + } + + @Override + public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, StartVmCmd cmd) throws KVMException { + if (SurfsPbase == null){ + return; + } + VolumeInventory root = spec.getDestRootVolume(); + String datavols=""; + + for (VolumeTO dvl : cmd.getDataVolumes()) { + if (datavols.equals("")){ + datavols= dvl.getInstallPath().split("/")[2] + ":" + dvl.getVolumeUuid(); + }else{ + datavols= datavols + "," + dvl.getInstallPath().split("/")[2] + ":" + dvl.getVolumeUuid(); + } + } + + SurfsPbase.startVmBefore(host.getManagementIp(),root.getInstallPath(),root.getUuid(), cmd.getVmInstanceUuid(),datavols,25); + + if (!root.getInstallPath().startsWith("iscsi")) { + return; + } + + cmd.getAddons().put("qemuPath", QEMUPATH); + cmd.setRootVolume(convertVolumeToSurfsIfNeeded(root, cmd.getRootVolume())); + + List dtos = new ArrayList(); + for (VolumeTO to : cmd.getDataVolumes()) { + VolumeInventory dvol = null; + for (VolumeInventory vol : spec.getDestDataVolumes()) { + if (vol.getUuid().equals(to.getVolumeUuid())) { + dvol = vol; + break; + } + } + + dtos.add(convertVolumeToSurfsIfNeeded(dvol, to)); + } + + cmd.setDataVolumes(dtos); +/* cmd.setBootIso(convertIsoToSurfsIfNeeded(cmd.getBootIso())); */ + List isoTOList = CollectionUtils.transformToList(cmd.getBootIso(), new Function() { + @Override + public IsoTO call(IsoTO arg) { + return convertIsoToSurfsIfNeeded(arg); + } + }); + cmd.setBootIso(isoTOList); + } + + @Override + public void startVmOnKvmSuccess(KVMHostInventory host, VmInstanceSpec spec) { + + } + + @Override + public void startVmOnKvmFailed(KVMHostInventory host, VmInstanceSpec spec, ErrorCode err) { + + } + + @Override + public boolean start() { + if (!CoreGlobalProperty.UNIT_TEST_ON) { + asf.deployModule(SurfsGlobalProperty.PRIMARY_STORAGE_MODULE_PATH, SurfsGlobalProperty.PRIMARY_STORAGE_PLAYBOOK_NAME); + } + + startCleanupThread(); + + SurfsGlobalConfig.IMAGE_CACHE_CLEANUP_INTERVAL.installUpdateExtension(new GlobalConfigUpdateExtensionPoint() { + @Override + public void updateGlobalConfig(GlobalConfig oldConfig, GlobalConfig newConfig) { + if (imageCacheCleanupThread != null) { + imageCacheCleanupThread.cancel(true); + } + startCleanupThread(); + } + }); + + return true; + } + + private void startCleanupThread() { + imageCacheCleanupThread = thdf.submitPeriodicTask(new PeriodicTask() { + @Override + public TimeUnit getTimeUnit() { + return TimeUnit.SECONDS; + } + + @Override + public long getInterval() { + return SurfsGlobalConfig.IMAGE_CACHE_CLEANUP_INTERVAL.value(Long.class); + } + + @Override + public String getName() { + return "surfs-primary-storage-image-cleanup-thread"; + } + + @Override + public void run() { + List staleCache = getStaleCache(); + if (staleCache == null || staleCache.isEmpty()) { + return; + } + + for (final ImageCacheVO c : staleCache) { + DeleteBitsOnPrimaryStorageMsg msg = new DeleteBitsOnPrimaryStorageMsg(); + msg.setInstallPath(c.getInstallUrl().split("@")[0]); + msg.setPrimaryStorageUuid(c.getPrimaryStorageUuid()); + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, c.getPrimaryStorageUuid()); + bus.send(msg, new CloudBusCallBack(null) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.debug(String.format("successfully cleanup a stale image cache[path:%s, primary storage uuid:%s]", c.getInstallUrl(), c.getPrimaryStorageUuid())); + dbf.remove(c); + } else { + logger.warn(String.format("failed to cleanup a stale image cache[path:%s, primary storage uuid:%s], %s", c.getInstallUrl(), c.getPrimaryStorageUuid(), reply.getError())); + } + } + }); + } + } + + @Transactional(readOnly = true) + private List getStaleCache() { + String sql = "select c.id from ImageCacheVO c, PrimaryStorageVO pri, ImageEO i where ((c.imageUuid is null) or (i.uuid = c.imageUuid and i.deleted is not null)) and " + + "pri.type = :ptype and pri.uuid = c.primaryStorageUuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, Long.class); + q.setParameter("ptype", SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE); + List ids = q.getResultList(); + if (ids.isEmpty()) { + return null; + } + + sql = "select ref.imageCacheId from ImageCacheVolumeRefVO ref where ref.imageCacheId in (:ids)"; + TypedQuery refq = dbf.getEntityManager().createQuery(sql, Long.class); + refq.setParameter("ids", ids); + List existing = refq.getResultList(); + + ids.removeAll(existing); + + if (ids.isEmpty()) { + return null; + } + + sql = "select c from ImageCacheVO c where c.id in (:ids)"; + TypedQuery fq = dbf.getEntityManager().createQuery(sql, ImageCacheVO.class); + fq.setParameter("ids", ids); + return fq.getResultList(); + } + }); + } + + @Override + public boolean stop() { + if (imageCacheCleanupThread != null) { + imageCacheCleanupThread.cancel(true); + } + return true; + } + + @Override + public WorkflowTemplate createTemplateFromVolumeSnapshot(final ParamIn paramIn) { + WorkflowTemplate template = new WorkflowTemplate(); + template.setCreateTemporaryTemplate(new NoRollbackFlow() { + @Override + public void run(final FlowTrigger trigger, final Map data) { + SyncVolumeSizeMsg msg = new SyncVolumeSizeMsg(); + msg.setVolumeUuid(paramIn.getSnapshot().getVolumeUuid()); + bus.makeTargetServiceIdByResourceUuid(msg, VolumeConstant.SERVICE_ID, paramIn.getSnapshot().getVolumeUuid()); + bus.send(msg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + ParamOut paramOut = (ParamOut) data.get(ParamOut.class); + SyncVolumeSizeReply gr = reply.castReply(); + paramOut.setActualSize(gr.getActualSize()); + paramOut.setSize(gr.getSize()); + trigger.next(); + } else { + trigger.fail(reply.getError()); + } + } + }); + } + }); + + template.setUploadToBackupStorage(new Flow() { + String __name__ = "upload-to-backup-storage"; + + @Override + public void run(final FlowTrigger trigger, Map data) { + final ParamOut out = (ParamOut) data.get(ParamOut.class); + + BackupStorageAskInstallPathMsg ask = new BackupStorageAskInstallPathMsg(); + ask.setImageUuid(paramIn.getImage().getUuid()); + ask.setBackupStorageUuid(paramIn.getBackupStorageUuid()); + ask.setImageMediaType(paramIn.getImage().getMediaType()); + bus.makeTargetServiceIdByResourceUuid(ask, BackupStorageConstant.SERVICE_ID, paramIn.getBackupStorageUuid()); + MessageReply ar = bus.call(ask); + if (!ar.isSuccess()) { + trigger.fail(ar.getError()); + return; + } + + String bsInstallPath = ((BackupStorageAskInstallPathReply)ar).getInstallPath(); + + UploadBitsToBackupStorageMsg msg = new UploadBitsToBackupStorageMsg(); + msg.setPrimaryStorageUuid(paramIn.getPrimaryStorageUuid()); + msg.setPrimaryStorageInstallPath(paramIn.getSnapshot().getPrimaryStorageInstallPath()); + msg.setBackupStorageUuid(paramIn.getBackupStorageUuid()); + msg.setBackupStorageInstallPath(bsInstallPath); + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, paramIn.getPrimaryStorageUuid()); + + bus.send(msg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + trigger.fail(reply.getError()); + } else { + out.setBackupStorageInstallPath(bsInstallPath); + trigger.next(); + } + } + }); + } + + @Override + public void rollback(FlowRollback trigger, Map data) { + final ParamOut out = (ParamOut) data.get(ParamOut.class); + if (out.getBackupStorageInstallPath() != null) { + DeleteBitsOnBackupStorageMsg msg = new DeleteBitsOnBackupStorageMsg(); + msg.setInstallPath(out.getBackupStorageInstallPath()); + msg.setBackupStorageUuid(paramIn.getBackupStorageUuid()); + bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, paramIn.getBackupStorageUuid()); + bus.send(msg); + } + + trigger.rollback(); + } + }); + + template.setDeleteTemporaryTemplate(new NopeFlow()); + + return template; + } + + @Override + public String createTemplateFromVolumeSnapshotPrimaryStorageType() { + return SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE; + } + + @Override + public String kvmSetupSelfFencerStorageType() { + return SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE; + } + + @Override + public void kvmSetupSelfFencer(KvmSetupSelfFencerParam param, final Completion completion) { + SetupSelfFencerOnKvmHostMsg msg = new SetupSelfFencerOnKvmHostMsg(); + msg.setParam(param); + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, param.getPrimaryStorage().getUuid()); + bus.send(msg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + completion.success(); + } else { + completion.fail(reply.getError()); + } + } + }); + } + + @Override + public void kvmCancelSelfFencer(KvmCancelSelfFencerParam param, Completion completion) { + completion.fail(errf.stringToOperationError("this has not been supported by surfs")); + } + +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageInventory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageInventory.java new file mode 100644 index 00000000000..e3898440868 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageInventory.java @@ -0,0 +1,95 @@ +package org.zstack.storage.surfs.primary; + +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.primary.PrimaryStorageInventory; +import org.zstack.storage.surfs.SurfsConstants; +import org.zstack.storage.surfs.backup.SurfsBackupStorageNodeInventory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Created by frank on 7/28/2015. + */ +@Inventory(mappingVOClass = SurfsPrimaryStorageVO.class, collectionValueOfMethod = "valueOf1", + parent = {@Parent(inventoryClass = PrimaryStorageInventory.class, type = SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE)}) +@ExpandedQueries({ + @ExpandedQuery(expandedField = "nodes", inventoryClass = SurfsPrimaryStorageNodeInventory.class, + foreignKey = "uuid", expandedInventoryKey = "primaryStorageUuid") +}) +public class SurfsPrimaryStorageInventory extends PrimaryStorageInventory { + private List nodes; + private String fsid; + private String rootVolumePoolName; + private String dataVolumePoolName; + private String imageCachePoolName; + + public List getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + 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; + } + + public SurfsPrimaryStorageInventory() { + } + + public SurfsPrimaryStorageInventory(SurfsPrimaryStorageVO vo) { + super(vo); + setNodes(SurfsPrimaryStorageNodeInventory.valueOf(vo.getNodes())); + setFsid(vo.getFsid()); + rootVolumePoolName = vo.getRootVolumePoolName(); + dataVolumePoolName = vo.getDataVolumePoolName(); + imageCachePoolName = vo.getImageCachePoolName(); + } + + public static SurfsPrimaryStorageInventory valueOf(SurfsPrimaryStorageVO vo) { + return new SurfsPrimaryStorageInventory(vo); + } + + public static List valueOf1(Collection vos) { + List invs = new ArrayList(); + for (SurfsPrimaryStorageVO vo : vos) { + invs.add(valueOf(vo)); + } + + return invs; + } + + public String getFsid() { + return fsid; + } + + public void setFsid(String fsid) { + this.fsid = fsid; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageInventoryDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..274557fca10 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageInventoryDoc_zh_cn.groovy @@ -0,0 +1,150 @@ +package org.zstack.storage.surfs.primary + +import org.zstack.storage.surfs.primary.SurfsPrimaryStorageNodeInventory +import java.lang.Long +import java.lang.Long +import java.lang.Long +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.primary.SurfsPrimaryStorageInventory.nodes" + desc "null" + type "List" + since "0.6" + clz SurfsPrimaryStorageNodeInventory.class + } + field { + name "fsid" + desc "" + type "String" + since "0.6" + } + field { + name "rootVolumePoolName" + desc "" + type "String" + since "0.6" + } + field { + name "dataVolumePoolName" + desc "" + type "String" + since "0.6" + } + field { + name "imageCachePoolName" + desc "" + type "String" + since "0.6" + } + field { + name "uuid" + desc "资源的UUID,唯一标示该资源" + type "String" + since "0.6" + } + field { + name "zoneUuid" + 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 "totalPhysicalCapacity" + desc "" + type "Long" + since "0.6" + } + field { + name "availablePhysicalCapacity" + desc "" + type "Long" + since "0.6" + } + field { + name "systemUsedCapacity" + 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 "mountPath" + 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 "attachedClusterUuids" + desc "" + type "List" + since "0.6" + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeBase.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeBase.java new file mode 100644 index 00000000000..c888c33f5ee --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeBase.java @@ -0,0 +1,321 @@ +package org.zstack.storage.surfs.primary; + +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.SurfsPoolClassUpdater; +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 frank on 7/28/2015. + */ +public class SurfsPrimaryStorageNodeBase extends SurfsNodeBase { + private static final CLogger logger = Utils.getLogger(SurfsPrimaryStorageNodeBase.class); + + @Autowired + private DatabaseFacade dbf; + @Autowired + private ThreadFacade thdf; + + private String syncId; + + public static final String ECHO_PATH = "/surfs/primarystorage/echo"; + public static final String PING_PATH = "/surfs/primarystorage/ping"; + + public static class AgentCmd { + public String monUuid; + public String primaryStorageUuid; + } + + 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 String fsid; + public String poolclsmsg; + } + + + public SurfsPrimaryStorageNodeVO getSelf() { + return (SurfsPrimaryStorageNodeVO) 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 primary 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-primary-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-primary-storage-%s", self.getHostname(), getSelf().getPrimaryStorageUuid())); + 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/surfsp/package/%s", AnsibleGlobalProperty.ZSTACKLIB_PACKAGE_NAME)); + checker.addSrcDestPair(PathUtil.findFileOnClassPath(String.format("ansible/surfsp/%s", SurfsGlobalProperty.PRIMARY_STORAGE_PACKAGE_NAME), true).getAbsolutePath(), + String.format("/var/lib/zstack/surfsp/package/%s", SurfsGlobalProperty.PRIMARY_STORAGE_PACKAGE_NAME)); + AnsibleRunner runner = new AnsibleRunner(); + runner.installChecker(checker); + runner.setPassword(getSelf().getSshPassword()); + runner.setUsername(getSelf().getSshUsername()); + runner.setSshPort(getSelf().getSshPort()); + runner.setTargetIp(getSelf().getHostname()); + runner.setAgentPort(SurfsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT); + runner.setPlayBookName(SurfsGlobalProperty.PRIMARY_STORAGE_PLAYBOOK_NAME); + runner.putArgument("pkg_surfspagent", SurfsGlobalProperty.PRIMARY_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.PRIMARY_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.PRIMARY_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) { + @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-primary-storage-%s", self.getUuid()); + } + }); + } + + @Override + protected int getAgentPort() { + return SurfsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT; + } + + private void doPing(final ReturnValueCompletion completion) { + SimpleQuery q = dbf.createQuery(SurfsPrimaryStorageVO.class); + q.select(SurfsPrimaryStorageVO_.rootVolumePoolName); + q.add(SurfsPrimaryStorageVO_.uuid, Op.EQ, getSelf().getPrimaryStorageUuid()); + 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.monUuid = getSelf().getUuid(); + cmd.primaryStorageUuid = getSelf().getPrimaryStorageUuid(); + + 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; + + SurfsPoolClassUpdater spcu=new SurfsPoolClassUpdater(); + try{ + spcu.update(rsp.fsid, rsp.poolclsmsg); + }catch(Exception ex){ + logger.warn("Failed to update poolmsg"); + } + } else { + res.success = false; + res.error = rsp.error; + res.operationFailure = rsp.operationFailure; + } + + completion.success(res); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + public SurfsPrimaryStorageNodeBase(SurfsNodeAO self) { + super(self); + syncId = String.format("surfs-primary-storage-mon-%s", self.getUuid()); + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeInventory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeInventory.java new file mode 100644 index 00000000000..7c5e1a5d31f --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeInventory.java @@ -0,0 +1,138 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.search.Inventory; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Created by frank on 7/28/2015. + */ +@Inventory(mappingVOClass = SurfsPrimaryStorageNodeVO.class) +public class SurfsPrimaryStorageNodeInventory { + private String hostname; + private Integer nodePort; + private Timestamp createDate; + private Timestamp lastOpDate; + private String primaryStorageUuid; + private String nodeAddr; + private String sshUsername; + private String sshPassword; + private Integer sshPort; + private String status; + private String nodeUuid; + + public static SurfsPrimaryStorageNodeInventory valueOf(SurfsPrimaryStorageNodeVO vo) { + SurfsPrimaryStorageNodeInventory inv = new SurfsPrimaryStorageNodeInventory(); + inv.setHostname(vo.getHostname()); + inv.setNodePort(vo.getNodePort()); + inv.setCreateDate(vo.getCreateDate()); + inv.setLastOpDate(vo.getLastOpDate()); + inv.setPrimaryStorageUuid(vo.getPrimaryStorageUuid()); + inv.setSshPort(vo.getSshPort()); + inv.setSshUsername(vo.getSshUsername()); + inv.setSshPassword(vo.getSshPassword()); + inv.setStatus(vo.getStatus().toString()); + inv.setNodeAddr(vo.getHostname()); + inv.setNodeUuid(vo.getUuid()); + return inv; + } + + public static List valueOf(Collection vos) { + List invs = new ArrayList(); + for (SurfsPrimaryStorageNodeVO vo : vos) { + invs.add(valueOf(vo)); + } + + return invs; + } + + public Integer getSshPort() { + return sshPort; + } + + public void setSshPort(Integer sshPort) { + this.sshPort = sshPort; + } + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public Integer getNodePort() { + return nodePort; + } + + public void setNodePort(Integer monPort) { + this.nodePort = monPort; + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + 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 getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + 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 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/primary/SurfsPrimaryStorageNodeInventoryDoc_zh_cn.groovy b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..1eeded03821 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeInventoryDoc_zh_cn.groovy @@ -0,0 +1,78 @@ +package org.zstack.storage.surfs.primary + +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 "primaryStorageUuid" + desc "主存储UUID" + type "String" + since "0.6" + } + field { + name "nodeAddr" + desc "" + 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 "nodeUuid" + desc "" + type "String" + since "0.6" + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeVO.java new file mode 100644 index 00000000000..c301a5ccd4d --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeVO.java @@ -0,0 +1,34 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.storage.primary.PrimaryStorageEO; +import org.zstack.header.storage.primary.PrimaryStorageVO; +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 frank on 7/28/2015. + */ +@Entity +@Table +@Inheritance(strategy= InheritanceType.JOINED) +@SoftDeletionCascades({ + @SoftDeletionCascade(parent = PrimaryStorageVO.class, joinColumn = "primaryStorageUuid") +}) +public class SurfsPrimaryStorageNodeVO extends SurfsNodeAO { + @Column + @ForeignKey(parentEntityClass = PrimaryStorageEO.class, parentKey = "uuid", onDeleteAction = ReferenceOption.CASCADE) + private String primaryStorageUuid; + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeVO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeVO_.java new file mode 100644 index 00000000000..31647618795 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageNodeVO_.java @@ -0,0 +1,15 @@ +package org.zstack.storage.surfs.primary; + +/** + * Created by zhouhaipng + */ + +import org.zstack.storage.surfs.SurfsNodeAO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@StaticMetamodel(SurfsPrimaryStorageNodeVO.class) +public class SurfsPrimaryStorageNodeVO_ extends SurfsNodeAO_ { + public static volatile SingularAttribute primaryStorageUuid; +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolInventory.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolInventory.java new file mode 100644 index 00000000000..8c9e2c43a97 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolInventory.java @@ -0,0 +1,108 @@ +package org.zstack.storage.surfs.primary; + +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-11-24 + */ +@Inventory(mappingVOClass = SurfsPrimaryStoragePoolVO.class) +public class SurfsPrimaryStoragePoolInventory { + private String uuid; + private String primaryStorageUuid; + private String poolName; + private String aliasName; + private String description; + private Timestamp createDate; + private Timestamp lastOpDate; + private String type; + + public static SurfsPrimaryStoragePoolInventory valueOf(SurfsPrimaryStoragePoolVO vo) { + SurfsPrimaryStoragePoolInventory inv = new SurfsPrimaryStoragePoolInventory(); + inv.uuid = vo.getUuid(); + inv.primaryStorageUuid = vo.getPrimaryStorageUuid(); + inv.poolName = vo.getPoolName(); + inv.description = vo.getDescription(); + inv.createDate = vo.getCreateDate(); + inv.lastOpDate = vo.getLastOpDate(); + inv.aliasName = vo.getAliasName(); + inv.type = vo.getType(); + return inv; + } + + public static List valueOf(Collection vos) { + List invs = new ArrayList<>(); + for (SurfsPrimaryStoragePoolVO vo : vos) { + invs.add(valueOf(vo)); + } + return invs; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getPoolName() { + return poolName; + } + + public void setPoolName(String poolName) { + this.poolName = poolName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + 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 getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAliasName() { + return aliasName; + } + + public void setAliasName(String aliasName) { + this.aliasName = aliasName; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolType.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolType.java new file mode 100644 index 00000000000..77e13349851 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolType.java @@ -0,0 +1,10 @@ +package org.zstack.storage.surfs.primary; + +/** + * Created by zhouhaiping 2017-11-24 + */ +public enum SurfsPrimaryStoragePoolType { + hdd, + ssd, + nvme +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolVO.java new file mode 100644 index 00000000000..379e4c7219f --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolVO.java @@ -0,0 +1,90 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.storage.primary.PrimaryStorageEO; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ResourceVO; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Created by xing5 on 2017/2/28. + */ +@Entity +@Table +public class SurfsPrimaryStoragePoolVO extends ResourceVO { + @Column + @ForeignKey(parentEntityClass = PrimaryStorageEO.class, parentKey = "uuid", onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String primaryStorageUuid; + @Column + private String poolName; + @Column + private String aliasName; + @Column + private String description; + @Column + private Timestamp createDate; + @Column + private Timestamp lastOpDate; + @Column + private String type; + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getPoolName() { + return poolName; + } + + public void setPoolName(String poolName) { + this.poolName = poolName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + 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 getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + public String getAliasName() { + return aliasName; + } + + public void setAliasName(String aliasName) { + this.aliasName = aliasName; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolVO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolVO_.java new file mode 100644 index 00000000000..dbc24b66e38 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStoragePoolVO_.java @@ -0,0 +1,21 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +/** + * Created by zhouhaiping 2017-11-24 + */ +@StaticMetamodel(SurfsPrimaryStoragePoolVO.class) +public class SurfsPrimaryStoragePoolVO_ extends ResourceVO_ { + public static volatile SingularAttribute primaryStorageUuid; + public static volatile SingularAttribute poolName; + public static volatile SingularAttribute aliasName; + public static volatile SingularAttribute description; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; + public static volatile SingularAttribute type; +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageSimulator.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageSimulator.java new file mode 100644 index 00000000000..b6643075f20 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageSimulator.java @@ -0,0 +1,301 @@ +package org.zstack.storage.surfs.primary; + +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.Platform; +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.primary.PrimaryStorageVO; +import org.zstack.kvm.KVMAgentCommands; +import org.zstack.storage.surfs.primary.SurfsPrimaryStorageBase.*; +import org.zstack.storage.surfs.primary.SurfsPrimaryStorageNodeBase.PingCmd; +import org.zstack.storage.surfs.primary.SurfsPrimaryStorageNodeBase.PingRsp; +import org.zstack.storage.surfs.primary.SurfsPrimaryStorageSimulatorConfig.SurfsPrimaryStorageConfig; +import org.zstack.utils.gson.JSONObjectUtil; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by frank on 7/28/2015. + */ +@Controller +public class SurfsPrimaryStorageSimulator { + @Autowired + private DatabaseFacade dbf; + @Autowired + private RESTFacade restf; + @Autowired + private SurfsPrimaryStorageSimulatorConfig config; + + private Map bitSizeMap = new HashMap(); + + 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 SurfsPrimaryStorageConfig getConfig(AgentCommand cmd) { + SimpleQuery q = dbf.createQuery(PrimaryStorageVO.class); + q.select(BackupStorageVO_.name); + q.add(BackupStorageVO_.uuid, Op.EQ, cmd.getUuid()); + String name = q.findValue(); + + SurfsPrimaryStorageConfig c = config.config.get(name); + if (c == null) { + throw new CloudRuntimeException(String.format("cannot find SurfsPrimaryStorageConfig by name[%s], uuid[%s]", name, cmd.getUuid())); + } + + return c; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.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.monUuid); + if (fsid == null) { + SurfsPrimaryStorageConfig c = getConfig(cmd); + fsid = c.fsid; + } + + rsp.fsid = fsid; + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.DELETE_POOL_PATH, method= RequestMethod.POST) + public @ResponseBody + String deletePool(HttpEntity entity) { + DeletePoolCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeletePoolCmd.class); + config.deletePoolCmds.add(cmd); + reply(entity, new DeletePoolRsp()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.INIT_PATH, method= RequestMethod.POST) + public @ResponseBody + String initialize(HttpEntity entity) { + InitCmd cmd = JSONObjectUtil.toObject(entity.getBody(), InitCmd.class); + SurfsPrimaryStorageConfig cpc = getConfig(cmd); + + InitRsp rsp = new InitRsp(); + if (!config.monInitSuccess) { + rsp.error = "on purpose"; + rsp.success = false; + } else { + rsp.fsid = cpc.fsid; + rsp.userKey = Platform.getUuid(); + rsp.totalCapacity = cpc.totalCapacity; + rsp.availableCapacity = cpc.availCapacity; + } + + reply(entity, rsp); + return null; + } + + private void setCapacity(AgentCommand cmd, AgentResponse rsp, long size) { + SurfsPrimaryStorageConfig cpc = getConfig(cmd); + rsp.totalCapacity = cpc.totalCapacity; + rsp.availableCapacity = cpc.availCapacity + size; + } + + @RequestMapping(value= SurfsPrimaryStorageNodeBase.PING_PATH, method= RequestMethod.POST) + public @ResponseBody + String pingNode(HttpEntity entity) { + PingCmd cmd = JSONObjectUtil.toObject(entity.getBody(), PingCmd.class); + Boolean success = config.pingCmdSuccess.get(cmd.monUuid); + PingRsp rsp = new PingRsp(); + rsp.success = success == null ? true : success; + if (!rsp.success) { + rsp.error = "on purpose"; + } + Boolean operationFailure = config.pingCmdOperationFailure.get(cmd.monUuid); + rsp.operationFailure = operationFailure == null ? false : operationFailure; + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.CREATE_VOLUME_PATH, method= RequestMethod.POST) + public @ResponseBody + String createEmptyVolume(HttpEntity entity) { + CreateEmptyVolumeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateEmptyVolumeCmd.class); + config.createEmptyVolumeCmds.add(cmd); + + CreateEmptyVolumeRsp rsp = new CreateEmptyVolumeRsp(); + //setCapacity(cmd, rsp, -cmd.getSize()); + bitSizeMap.put(cmd.getInstallPath(), cmd.getSize()); + reply(entity, rsp); + return null; + } + + public @ResponseBody + String createKvmSecret(HttpEntity entity) { + CreateKvmSecretCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateKvmSecretCmd.class); + config.createKvmSecretCmds.add(cmd); + reply(entity, new KVMAgentCommands.AgentResponse()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.DELETE_PATH, method= RequestMethod.POST) + public @ResponseBody + String doDelete(HttpEntity entity) { + DeleteCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeleteCmd.class); + config.deleteCmds.add(cmd); + Long size = bitSizeMap.get(cmd.getInstallPath()); + size = size == null ? 0 : size; + + DeleteRsp rsp = new DeleteRsp(); + setCapacity(cmd, rsp, size); + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.CREATE_SNAPSHOT_PATH, method= RequestMethod.POST) + public @ResponseBody + String createSnapshot(HttpEntity entity) { + CreateSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateSnapshotCmd.class); + config.createSnapshotCmds.add(cmd); + + CreateSnapshotRsp rsp = new CreateSnapshotRsp(); + Long size = config.createSnapshotCmdSize.get(cmd.getVolumeUuid()); + rsp.setActualSize(size == null ? 0 : size); + + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.DELETE_SNAPSHOT_PATH, method= RequestMethod.POST) + public @ResponseBody + String deleteSnapshot(HttpEntity entity) { + DeleteSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeleteSnapshotCmd.class); + config.deleteSnapshotCmds.add(cmd); + + reply(entity, new DeleteSnapshotRsp()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.PROTECT_SNAPSHOT_PATH, method= RequestMethod.POST) + public @ResponseBody + String protectSnapshot(HttpEntity entity) { + ProtectSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), ProtectSnapshotCmd.class); + config.protectSnapshotCmds.add(cmd); + + reply(entity, new ProtectSnapshotRsp()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.UNPROTECT_SNAPSHOT_PATH, method= RequestMethod.POST) + public @ResponseBody + String unprotectSnapshot(HttpEntity entity) { + UnprotectedSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), UnprotectedSnapshotCmd.class); + config.unprotectedSnapshotCmds.add(cmd); + + reply(entity, new UnprotectedSnapshotRsp()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.CLONE_PATH, method= RequestMethod.POST) + public @ResponseBody + String clone(HttpEntity entity) { + CloneCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CloneCmd.class); + config.cloneCmds.add(cmd); + + CloneRsp rsp = new CloneRsp(); + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.FLATTEN_PATH, method= RequestMethod.POST) + public @ResponseBody + String flatten(HttpEntity entity) { + FlattenCmd cmd = JSONObjectUtil.toObject(entity.getBody(), FlattenCmd.class); + config.flattenCmds.add(cmd); + + reply(entity, new FlattenRsp()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.CP_PATH, method= RequestMethod.POST) + public @ResponseBody + String cp(HttpEntity entity) { + CpRsp rsp = new CpRsp(); + CpCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CpCmd.class); + config.cpCmds.add(cmd); + + Long size = config.cpCmdSize.get(cmd.resourceUuid); + rsp.size = size == null ? 0 : size; + Long asize = config.cpCmdActualSize.get(cmd.resourceUuid); + rsp.actualSize = asize == null ? 0 : asize; + + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.GET_VOLUME_SIZE_PATH, method= RequestMethod.POST) + public @ResponseBody + String getVolumeSize(HttpEntity entity) { + GetVolumeSizeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVolumeSizeCmd.class); + config.getVolumeSizeCmds.add(cmd); + + Long asize = config.getVolumeSizeCmdActualSize.get(cmd.volumeUuid); + GetVolumeSizeRsp rsp = new GetVolumeSizeRsp(); + rsp.actualSize = asize == null ? 0 : asize; + Long size = config.getVolumeSizeCmdSize.get(cmd.volumeUuid); + rsp.size = size == null ? 0 : size; + reply(entity, rsp); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.SFTP_UPLOAD_PATH, method= RequestMethod.POST) + public @ResponseBody + String sftpUpload(HttpEntity entity) { + SftpUpLoadCmd cmd = JSONObjectUtil.toObject(entity.getBody(), SftpUpLoadCmd.class); + config.sftpUpLoadCmds.add(cmd); + + reply(entity, new SftpUpLoadCmd()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.SFTP_DOWNLOAD_PATH, method= RequestMethod.POST) + public @ResponseBody + String sftpDownload(HttpEntity entity) { + SftpDownloadCmd cmd = JSONObjectUtil.toObject(entity.getBody(), SftpDownloadCmd.class); + config.sftpDownloadCmds.add(cmd); + + reply(entity, new SftpDownloadRsp()); + return null; + } + + @RequestMapping(value= SurfsPrimaryStorageBase.ROLLBACK_SNAPSHOT_PATH, method= RequestMethod.POST) + public @ResponseBody + String rollback(HttpEntity entity) { + RollbackSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RollbackSnapshotCmd.class); + config.rollbackSnapshotCmds.add(cmd); + + reply(entity, new RollbackSnapshotRsp()); + return null; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageSimulatorConfig.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageSimulatorConfig.java new file mode 100644 index 00000000000..656927cadad --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageSimulatorConfig.java @@ -0,0 +1,49 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.storage.surfs.primary.SurfsPrimaryStorageBase.*; + +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 SurfsPrimaryStorageSimulatorConfig { + public static class SurfsPrimaryStorageConfig { + public long totalCapacity; + public long availCapacity; + public String fsid; + } + + public volatile boolean monInitSuccess = true; + public Map config = new HashMap(); + public List createEmptyVolumeCmds = new ArrayList(); + public List deleteCmds = new ArrayList(); + public List createSnapshotCmds = new ArrayList(); + public Map createSnapshotCmdSize = new HashMap(); + public List deleteSnapshotCmds = new ArrayList(); + public List protectSnapshotCmds = new ArrayList(); + public List unprotectedSnapshotCmds = new ArrayList(); + public List cloneCmds = new ArrayList(); + public List flattenCmds = new ArrayList(); + public List cpCmds = new ArrayList(); + public List sftpDownloadCmds = new ArrayList(); + public List sftpUpLoadCmds = new ArrayList(); + public List rollbackSnapshotCmds = new ArrayList(); + public List createKvmSecretCmds = new ArrayList(); + public List deletePoolCmds = new ArrayList(); + public Map cpCmdSize = new HashMap(); + public Map cpCmdActualSize = new HashMap(); + public Map getVolumeActualSizeCmdSize = new HashMap(); + + public List getVolumeSizeCmds = new ArrayList(); + public Map getVolumeSizeCmdSize = new HashMap(); + public Map getVolumeSizeCmdActualSize = 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/primary/SurfsPrimaryStorageVO.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVO.java new file mode 100644 index 00000000000..3d0e652227a --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVO.java @@ -0,0 +1,99 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.storage.primary.PrimaryStorageEO; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.tag.AutoDeleteTag; +import org.zstack.header.vo.EO; +import org.zstack.header.vo.NoView; + +import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by zhouhaiping 2017-09-13 + */ +@Entity +@Table +@PrimaryKeyJoinColumn(name="uuid", referencedColumnName="uuid") +@EO(EOClazz = PrimaryStorageEO.class, needView = false) +@AutoDeleteTag +public class SurfsPrimaryStorageVO extends PrimaryStorageVO { + @OneToMany(fetch= FetchType.EAGER) + @JoinColumn(name="primaryStorageUuid", insertable=false, updatable=false) + @NoView + private Set nodes = new HashSet(); + + @Column + private String fsid; + + @Column + private String rootVolumePoolName; + @Column + private String dataVolumePoolName; + @Column + private String imageCachePoolName; + @Column + private String userKey; + + public String getUserKey() { + return userKey; + } + + public void setUserKey(String userKey) { + this.userKey = userKey; + } + + 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; + } + + public SurfsPrimaryStorageVO() { + } + + public SurfsPrimaryStorageVO(PrimaryStorageVO other) { + super(other); + } + + public SurfsPrimaryStorageVO(SurfsPrimaryStorageVO other) { + super(other); + this.nodes = other.nodes; + this.fsid = other.fsid; + } + + public Set getNodes() { + return nodes; + } + + public void setNodes(Set nodes) { + this.nodes = nodes; + } + + public String getFsid() { + return fsid; + } + + public void setFsid(String fsid) { + this.fsid = fsid; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVO_.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVO_.java new file mode 100644 index 00000000000..100fc5a233a --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVO_.java @@ -0,0 +1,18 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.storage.primary.PrimaryStorageVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +/** + * Created by zhouhaiping 2017-09-13 + */ +@StaticMetamodel(SurfsPrimaryStorageVO.class) +public class SurfsPrimaryStorageVO_ extends PrimaryStorageVO_ { + public static volatile SingularAttribute fsid; + public static volatile SingularAttribute rootVolumePoolName; + public static volatile SingularAttribute dataVolumePoolName; + public static volatile SingularAttribute imageCachePoolName; + public static volatile SingularAttribute userKey; +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVmMigrationExtension.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVmMigrationExtension.java new file mode 100644 index 00000000000..1fa2e85275d --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/SurfsPrimaryStorageVmMigrationExtension.java @@ -0,0 +1,316 @@ +package org.zstack.storage.surfs.primary; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.errorcode.ErrorFacade; +import org.zstack.core.timeout.ApiTimeoutManager; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.OperationFailureException; +import org.zstack.header.message.Message; +import org.zstack.header.rest.JsonAsyncRESTCallback; +import org.zstack.header.rest.RESTFacade; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceMigrateExtensionPoint; +import org.zstack.header.volume.VolumeInventory; +import org.zstack.storage.surfs.SurfsConstants; +import org.zstack.storage.surfs.SurfsGlobalProperty; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.TypedQuery; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by zhouhaiping 2017-09-14 + */ + + +public class SurfsPrimaryStorageVmMigrationExtension implements VmInstanceMigrateExtensionPoint { + public static final String KVM_SURFS_QUERY_PATH = "/surfs/query"; + public static final String SURFS_MIGRATE_PREPARE = "/surfs/primarystorage/migrateprepare"; + public static final String SURFS_MIGRATE_AFTER = "/surfs/primarystorage/migrateafter"; + + public static class SurfsQueryRsp extends AgentResponse { + public String rsp; + } + + public static class SurfsQueryCmd extends AgentCommand { + public String query; + } + + private CLogger logger = Utils.getLogger(SurfsPrimaryStorageVmMigrationExtension.class); + private Map> vmVolumes = new ConcurrentHashMap>(); + + @Autowired + private CloudBus bus; + @Autowired + private DatabaseFacade dbf; + @Autowired + private ErrorFacade errf; + @Autowired + private ApiTimeoutManager timeoutMgr; + @Autowired + protected RESTFacade restf; + private String dsthostname; + + 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 MigrateVmBeforeCmd extends AgentCommand{ + String rootinstallPath; + String datainstallPath; + } + public static class MigrateVmBeforeRsp extends AgentResponse{ + + } + + @Transactional(readOnly = true) + private boolean needLink(VmInstanceInventory inv) { + String sql = "select ps.type from PrimaryStorageVO ps, VolumeVO vol where ps.uuid = vol.primaryStorageUuid" + + " and vol.uuid = :uuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, String.class); + q.setParameter("uuid", inv.getRootVolumeUuid()); + List res = q.getResultList(); + if (res.isEmpty()) { + return false; + } + + String type = res.get(0); + return SurfsConstants.SURFS_PRIMARY_STORAGE_TYPE.equals(type); + } + + @Override + public void preMigrateVm(VmInstanceInventory inv, String destHostUuid) { + + /* + SurfsQueryCmd cmd = new SurfsQueryCmd(); + cmd.query = "query"; + + KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); + msg.setCommand(cmd); + msg.setCommandTimeout(timeoutMgr.getTimeout(cmd.getClass(), "5m")); + msg.setPath(KVM_SURFS_QUERY_PATH); + msg.setHostUuid(destHostUuid); + bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, destHostUuid); + MessageReply reply = bus.call(msg); + if (!reply.isSuccess()) { + throw new OperationFailureException(reply.getError()); + } + + KVMHostAsyncHttpCallReply r = reply.castReply(); + SurfsQueryRsp rsp = r.toResponse(SurfsQueryRsp.class); + if (!rsp.isSuccess()) { + throw new OperationFailureException(errf.stringToOperationError(rsp.getError())); + } + */ + + } + + @Override + public void beforeMigrateVm(VmInstanceInventory inv, String destHostUuid) { + if (!needLink(inv)) { + return; + } + this.dsthostname=""; + String sql = "select ssv.hostname from SurfsPrimaryStorageNodeVO ssv , HostVO hv " + + "where ssv.hostname=hv.managementIp and hv.uuid= :uuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, String.class); + q.setParameter("uuid", destHostUuid); + List res = q.getResultList(); + if (res.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError(String.format("node[uuid:%s] is not in surfs primary nodes", destHostUuid))); + } + this.dsthostname=res.get(0); + MigrateVmBeforeCmd cmd=new MigrateVmBeforeCmd(); + cmd.rootinstallPath=inv.getRootVolume().getInstallPath(); + String datavols=""; + for(VolumeInventory dvl :inv.getAllVolumes()){ + if (cmd.rootinstallPath.equals(dvl.getInstallPath())){ + continue; + } + if (datavols.equals("")){ + datavols= dvl.getInstallPath().split("/")[2] + ":" + dvl.getUuid(); + }else{ + datavols= datavols + "," + dvl.getInstallPath().split("/")[2] + ":" + dvl.getUuid(); + } + }; + class NoMsg extends Message{ + String smsg; + public String getSmsg(){ + return smsg; + } + public void setSmsg(String msg){ + this.smsg=msg; + } + } + NoMsg nomsg=new NoMsg(); + nomsg.setSmsg("start"); + cmd.datainstallPath=datavols; + httpCall(SURFS_MIGRATE_PREPARE, cmd,MigrateVmBeforeRsp.class,new ReturnValueCompletion(null){ + @Override + public void success(MigrateVmBeforeRsp ret) { + nomsg.setSmsg("end"); + logger.debug(String.format("Success to prepare for vm[%s] before migrate",inv.getUuid())); + } + + @Override + public void fail(ErrorCode errorCode) { + nomsg.setSmsg("end"); + logger.warn(String.format("Error to prepare for vm[%s] before migrate",inv.getUuid())); + } + + }); + int intevel=30; + try{ + int sk=0; + while (true){ + if (nomsg.getSmsg().equals("end")){ + break; + } + if (sk > intevel){ + break; + } + Thread.sleep(500); + sk =sk + 1; + } + }catch(Exception ex){ + logger.debug(String.format("Error to sleep for migrate[%s] befroe",inv.getUuid())); + } + + } + + @Override + public void afterMigrateVm(VmInstanceInventory inv, String srcHostUuid) { + if (!needLink(inv)) { + return; + } + this.dsthostname=""; + String sql = "select ssv.hostname from SurfsPrimaryStorageNodeVO ssv , HostVO hv " + + "where ssv.hostname=hv.managementIp and hv.uuid= :uuid"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, String.class); + q.setParameter("uuid", srcHostUuid); + List res = q.getResultList(); + if (res.isEmpty()) { + throw new OperationFailureException(errf.stringToOperationError( + + String.format("node[uuid:%s] is not in surfs primary nodes", res.get(0)) + )); + } + this.dsthostname=res.get(0); + MigrateVmBeforeCmd cmd=new MigrateVmBeforeCmd(); + cmd.rootinstallPath=inv.getRootVolume().getInstallPath(); + String datavols=""; + for(VolumeInventory dvl :inv.getAllVolumes()){ + if (cmd.rootinstallPath.equals(dvl.getInstallPath())){ + continue; + } + if (datavols.equals("")){ + datavols= dvl.getInstallPath().split("/")[2] + ":" + dvl.getUuid(); + }else{ + datavols= datavols + "," + dvl.getInstallPath().split("/")[2] + ":" + dvl.getUuid(); + } + }; + + cmd.datainstallPath=datavols; + httpCall(SURFS_MIGRATE_AFTER, cmd,MigrateVmBeforeRsp.class,new ReturnValueCompletion(null){ + @Override + public void success(MigrateVmBeforeRsp ret) { + logger.debug(String.format("Success to clean for vm[%s] after migrate",inv.getUuid())); + } + + @Override + public void fail(ErrorCode errorCode) { + + logger.warn(String.format("Error to clean for vm[%s] after migrate",inv.getUuid())); + } + + }); + + } + + @Override + public void failedToMigrateVm(VmInstanceInventory inv, String destHostUuid, ErrorCode reason) { + } + + private void httpCall(String path, MigrateVmBeforeCmd cmd, final Class rspClass, final ReturnValueCompletion completion) { + restf.asyncJsonPost(String.format("http://%s:%s%s", this.dsthostname, SurfsGlobalProperty.PRIMARY_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/primary/UploadBitsToBackupStorageMsg.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/UploadBitsToBackupStorageMsg.java new file mode 100644 index 00000000000..5481d849083 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/UploadBitsToBackupStorageMsg.java @@ -0,0 +1,47 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.storage.primary.PrimaryStorageMessage; + +/** + * Created by xing5 on 2016/4/29. + */ +public class UploadBitsToBackupStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + private String primaryStorageInstallPath; + private String backupStorageUuid; + private String backupStorageInstallPath; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getPrimaryStorageInstallPath() { + return primaryStorageInstallPath; + } + + public void setPrimaryStorageInstallPath(String primaryStorageInstallPath) { + this.primaryStorageInstallPath = primaryStorageInstallPath; + } + + public String getBackupStorageUuid() { + return backupStorageUuid; + } + + public void setBackupStorageUuid(String backupStorageUuid) { + this.backupStorageUuid = backupStorageUuid; + } + + public String getBackupStorageInstallPath() { + return backupStorageInstallPath; + } + + public void setBackupStorageInstallPath(String backupStorageInstallPath) { + this.backupStorageInstallPath = backupStorageInstallPath; + } +} diff --git a/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/UploadBitsToBackupStorageReply.java b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/UploadBitsToBackupStorageReply.java new file mode 100644 index 00000000000..b18f3ac23c9 --- /dev/null +++ b/plugin/surfs/src/main/java/org/zstack/storage/surfs/primary/UploadBitsToBackupStorageReply.java @@ -0,0 +1,9 @@ +package org.zstack.storage.surfs.primary; + +import org.zstack.header.message.MessageReply; + +/** + * Created by xing5 on 2016/4/29. + */ +public class UploadBitsToBackupStorageReply extends MessageReply { +}