| @@ -0,0 +1,2 @@ | |||
| # storage-common | |||
| @@ -0,0 +1,39 @@ | |||
| { | |||
| "id": 1, | |||
| "local": { | |||
| "nodeID": 1, | |||
| "localIP": "127.0.0.1", | |||
| "externalIP": "127.0.0.1" | |||
| }, | |||
| "grpc": { | |||
| "ip": "127.0.0.1", | |||
| "port": 5010 | |||
| }, | |||
| "ecPacketSize": 10, | |||
| "storageBaseDir": ".", | |||
| "tempFileLifetime": 3600, | |||
| "logger": { | |||
| "output": "file", | |||
| "outputFileName": "agent", | |||
| "outputDirectory": "log", | |||
| "level": "debug" | |||
| }, | |||
| "rabbitMQ": { | |||
| "address": "127.0.0.1:5672", | |||
| "account": "cloudream", | |||
| "password": "123456", | |||
| "vhost": "/" | |||
| }, | |||
| "ipfs": { | |||
| "port": 5001 | |||
| }, | |||
| "distlock": { | |||
| "etcdAddress": "127.0.0.1:2379", | |||
| "etcdUsername": "", | |||
| "etcdPassword": "", | |||
| "etcdLockAcquireTimeoutMs": 5000, | |||
| "etcdLockLeaseTimeSec": 5, | |||
| "lockRequestLeaseTimeSec": 5, | |||
| "submitLockRequestWithoutLease": true | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| { | |||
| "local": { | |||
| "localIP": "127.0.0.1", | |||
| "externalIP": "127.0.0.1" | |||
| }, | |||
| "agentGRPC": { | |||
| "port": 5010 | |||
| }, | |||
| "ecPacketSize": 10, | |||
| "maxRepCount": 10, | |||
| "logger": { | |||
| "output": "stdout", | |||
| "level": "debug" | |||
| }, | |||
| "rabbitMQ": { | |||
| "address": "127.0.0.1:5672", | |||
| "account": "cloudream", | |||
| "password": "123456", | |||
| "vhost": "/" | |||
| }, | |||
| "ipfs": null, | |||
| "distlock": { | |||
| "etcdAddress": "127.0.0.1:2379", | |||
| "etcdUsername": "", | |||
| "etcdPassword": "", | |||
| "etcdLockAcquireTimeoutMs": 5000, | |||
| "etcdLockLeaseTimeSec": 5, | |||
| "lockRequestLeaseTimeSec": 5, | |||
| "submitLockRequestWithoutLease": true | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| { | |||
| "logger": { | |||
| "output": "file", | |||
| "outputFileName": "coordinator", | |||
| "outputDirectory": "log", | |||
| "level": "debug" | |||
| }, | |||
| "db": { | |||
| "address": "127.0.0.1:3306", | |||
| "account": "root", | |||
| "password": "123456", | |||
| "databaseName": "cloudream" | |||
| }, | |||
| "rabbitMQ": { | |||
| "address": "127.0.0.1:5672", | |||
| "account": "cloudream", | |||
| "password": "123456", | |||
| "vhost": "/" | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| { | |||
| "minAvailableRepProportion": 0.8, | |||
| "nodeUnavailableSeconds": 300, | |||
| "logger": { | |||
| "output": "file", | |||
| "outputFileName": "scanner", | |||
| "outputDirectory": "log", | |||
| "level": "debug" | |||
| }, | |||
| "db": { | |||
| "address": "127.0.0.1:3306", | |||
| "account": "root", | |||
| "password": "123456", | |||
| "databaseName": "cloudream" | |||
| }, | |||
| "rabbitMQ": { | |||
| "address": "127.0.0.1:5672", | |||
| "account": "cloudream", | |||
| "password": "123456", | |||
| "vhost": "/" | |||
| }, | |||
| "distlock": { | |||
| "etcdAddress": "127.0.0.1:2379", | |||
| "etcdUsername": "", | |||
| "etcdPassword": "", | |||
| "etcdLockAcquireTimeoutMs": 5000, | |||
| "etcdLockLeaseTimeSec": 5, | |||
| "lockRequestLeaseTimeSec": 5, | |||
| "submitLockRequestWithoutLease": true | |||
| } | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| <setting> | |||
| <attribute> | |||
| <name>local.addr</name> | |||
| <value>101.201.215.165</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>controller.addr</name> | |||
| <value>101.201.215.196</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>agents.addr</name> | |||
| <value>/hw-sh/123.60.146.162</value> | |||
| <value>/hw-bj/120.46.183.86</value> | |||
| <value>/ali/101.201.215.165</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>agents.location</name> | |||
| <value>ali</value> | |||
| <value>hw-sh</value> | |||
| <value>hw-bj</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>oec.controller.thread.num</name> | |||
| <value>4</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>oec.agent.thread.num</name> | |||
| <value>2</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>oec.cmddist.thread.num</name> | |||
| <value>2</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>packet.size</name> | |||
| <value>131072</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>ec.concurrent.num</name> | |||
| <value>2</value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>ec.policy</name> | |||
| <value><ecid>rs_9_6</ecid><class>RS96</class><n>9</n><k>6</k><w>1</w><opt>-1</opt></value> | |||
| <value><ecid>rs_3_2</ecid><class>RS96</class><n>3</n><k>2</k><w>1</w><opt>-1</opt></value> | |||
| <value><ecid>edu_9_6</ecid><class>EDU96</class><n>9</n><k>6</k><w>1</w><opt>-1</opt></value> | |||
| <value><ecid>edu_3_2</ecid><class>EDU32</class><n>3</n><k>2</k><w>1</w><opt>-1</opt></value> | |||
| <value><ecid>dfc_9_4</ecid><class>DFC</class><n>9</n><k>4</k><w>1</w><opt>-1</opt><param>3,2</param></value> | |||
| </attribute> | |||
| <attribute> | |||
| <name>inter.inner.addr</name> | |||
| <inner> | |||
| <dc><ip>172.23.85.69</ip><ip>172.23.85.71</ip><ip>172.23.85.70</ip></dc> | |||
| <dc><ip>192.168.0.69</ip></dc> | |||
| <dc><ip>192.168.0.76</ip></dc> | |||
| </inner> | |||
| <inter> | |||
| <dc><ip>101.201.215.196</ip><ip>101.201.215.165</ip><ip>101.201.214.111</ip></dc> | |||
| <dc><ip>123.60.146.162</ip></dc> | |||
| <dc><ip>120.46.183.86</ip></dc> | |||
| </inter> | |||
| </attribute> | |||
| </setting> | |||
| @@ -0,0 +1,168 @@ | |||
| drop database if exists cloudream; | |||
| create database cloudream; | |||
| use cloudream; | |||
| create table Node ( | |||
| NodeID int not null auto_increment primary key comment '节点ID', | |||
| Name varchar(128) not null comment '节点名称', | |||
| LocalIP varchar(128) not null comment '节点的内网IP', | |||
| ExternalIP varchar(128) not null comment '节点的外网IP', | |||
| LocationID int not null comment '节点的地域', | |||
| State varchar(128) comment '节点的状态', | |||
| LastReportTime timestamp comment '节点上次上报时间' | |||
| ) comment = '节点表'; | |||
| insert into | |||
| Node ( | |||
| NodeID, | |||
| Name, | |||
| LocalIP, | |||
| ExternalIP, | |||
| LocationID, | |||
| State | |||
| ) | |||
| values | |||
| (0, "LocalNode", "localhost", "localhost", 0, 1); | |||
| create table Storage ( | |||
| StorageID int not null auto_increment primary key comment '存储服务ID', | |||
| Name varchar(100) not null comment '存储服务名称', | |||
| NodeID int not null comment '存储服务所在节点的ID', | |||
| Directory varchar(4096) not null comment '存储服务所在节点的目录', | |||
| State varchar(100) comment '状态' | |||
| ) comment = "存储服务表"; | |||
| insert into | |||
| Storage (StorageID, Name, NodeID, Directory, State) | |||
| values | |||
| (1, "HuaWei-Cloud", 1, "/", "Online"); | |||
| create table NodeDelay ( | |||
| SourceNodeID int not null comment '发起检测的节点ID', | |||
| DestinationNodeID int not null comment '被检测节点的ID', | |||
| DelayInMs int not null comment '发起节点与被检测节点间延迟(毫秒)', | |||
| primary key(SourceNodeID, DestinationNodeID) | |||
| ) comment = '节点延迟表'; | |||
| create table User ( | |||
| UserID int not null primary key comment '用户ID', | |||
| Password varchar(100) not null comment '用户密码' | |||
| ) comment = '用户密码表'; | |||
| create table UserBucket ( | |||
| UserID int not null comment '用户ID', | |||
| BucketID int not null comment '用户可访问的桶ID', | |||
| primary key(UserID, BucketID) | |||
| ) comment = '用户桶权限表'; | |||
| insert into | |||
| UserBucket (UserID, BucketID) | |||
| values | |||
| (0, 1); | |||
| create table UserNode ( | |||
| UserID int not null comment '用户ID', | |||
| NodeID int not null comment '用户可使用的节点ID', | |||
| primary key(UserID, NodeID) | |||
| ) comment = '用户节点权限表'; | |||
| insert into | |||
| UserNode (UserID, NodeID) | |||
| values | |||
| (0, 1); | |||
| create table UserStorage ( | |||
| UserID int not null comment "用户ID", | |||
| StorageID int not null comment "存储服务ID", | |||
| primary key(UserID, StorageID) | |||
| ); | |||
| insert into | |||
| UserStorage (UserID, StorageID) | |||
| values | |||
| (0, 1); | |||
| create table Bucket ( | |||
| BucketID int not null auto_increment primary key comment '桶ID', | |||
| Name varchar(100) not null comment '桶名', | |||
| CreatorID int not null comment '创建者ID' | |||
| ) comment = '桶表'; | |||
| insert into | |||
| Bucket (BucketID, Name, CreatorID) | |||
| values | |||
| (0, "bucket01", 0); | |||
| create table Package ( | |||
| PackageID int not null auto_increment primary key comment '包ID', | |||
| Name varchar(100) not null comment '对象名', | |||
| BucketID int not null comment '桶ID', | |||
| State varchar(100) not null comment '状态', | |||
| Redundancy JSON not null comment '冗余策略' | |||
| ); | |||
| create table Object ( | |||
| ObjectID int not null auto_increment primary key comment '对象ID', | |||
| PackageID int not null comment '包ID', | |||
| Path varchar(500) not null comment '对象路径', | |||
| Size bigint not null comment '对象大小(Byte)', | |||
| UNIQUE KEY PackagePath (PackageID, Path) | |||
| ) comment = '对象表'; | |||
| create table ObjectRep ( | |||
| ObjectID int not null primary key comment '对象ID', | |||
| FileHash varchar(100) not null comment '副本哈希值' | |||
| ) comment = '对象副本表'; | |||
| create table ObjectBlock ( | |||
| ObjectID int not null comment '对象ID', | |||
| `Index` int not null comment '编码块在条带内的排序', | |||
| FileHash varchar(100) not null comment '编码块哈希值', | |||
| primary key(ObjectID, `Index`) | |||
| ) comment = '对象编码块表'; | |||
| create table Cache ( | |||
| FileHash varchar(100) not null comment '编码块块ID', | |||
| NodeID int not null comment '节点ID', | |||
| State varchar(100) not null comment '状态', | |||
| CacheTime timestamp not null comment '缓存时间', | |||
| Priority int not null comment '编码块优先级', | |||
| primary key(FileHash, NodeID) | |||
| ) comment = '缓存表'; | |||
| create table StoragePackage ( | |||
| PackageID int not null comment '包ID', | |||
| StorageID int not null comment '存储服务ID', | |||
| UserID int not null comment '调度了此文件的用户ID', | |||
| State varchar(100) not null comment '包状态', | |||
| primary key(PackageID, StorageID, UserID) | |||
| ); | |||
| create table Location ( | |||
| LocationID int not null auto_increment primary key comment 'ID', | |||
| Name varchar(128) not null comment '名称' | |||
| ) comment = '地域表'; | |||
| insert into | |||
| Location (LocationID, Name) | |||
| values | |||
| (1, "Local"); | |||
| create table Ec ( | |||
| EcID int not null comment '纠删码ID', | |||
| Name varchar(128) not null comment '纠删码名称', | |||
| EcK int not null comment 'ecK', | |||
| EcN int not null comment 'ecN' | |||
| ) comment = '纠删码表'; | |||
| insert into | |||
| Ec (EcID, Name, EcK, EcN) | |||
| values | |||
| (1, "rs_9_6", 6, 9); | |||
| insert into | |||
| Ec (EcID, Name, EcK, EcN) | |||
| values | |||
| (2, "rs_5_3", 3, 5); | |||
| @@ -0,0 +1,27 @@ | |||
| package consts | |||
| const ( | |||
| IPFSStateOK = "OK" | |||
| IPFSStateUnavailable = "Unavailable" | |||
| StorageDirectoryStateOK = "OK" | |||
| NodeStateNormal = "Normal" | |||
| NodeStateUnavailable = "Unavailable" | |||
| ) | |||
| const ( | |||
| PackageStateNormal = "Normal" | |||
| PackageStateDeleted = "Deleted" | |||
| ) | |||
| const ( | |||
| StoragePackageStateNormal = "Normal" | |||
| StoragePackageStateDeleted = "Deleted" | |||
| StoragePackageStateOutdated = "Outdated" | |||
| ) | |||
| const ( | |||
| CacheStatePinned = "Pinned" | |||
| CacheStateTemp = "Temp" | |||
| ) | |||
| @@ -0,0 +1,11 @@ | |||
| package globals | |||
| import ( | |||
| stgmodels "gitlink.org.cn/cloudream/storage-common/models" | |||
| ) | |||
| var Local *stgmodels.LocalMachineInfo | |||
| func InitLocal(info *stgmodels.LocalMachineInfo) { | |||
| Local = info | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package globals | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/ipfs" | |||
| agtrpc "gitlink.org.cn/cloudream/storage-common/pkgs/grpc/agent" | |||
| stgmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| agtmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/agent" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| scmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/scanner" | |||
| ) | |||
| var AgentMQPool *agtmq.Pool | |||
| var CoordinatorMQPool *coormq.Pool | |||
| var ScannerMQPool *scmq.Pool | |||
| func InitMQPool(cfg *stgmq.Config) { | |||
| AgentMQPool = agtmq.NewPool(cfg) | |||
| CoordinatorMQPool = coormq.NewPool(cfg) | |||
| ScannerMQPool = scmq.NewPool(cfg) | |||
| } | |||
| var AgentRPCPool *agtrpc.Pool | |||
| func InitAgentRPCPool(cfg *agtrpc.PoolConfig) { | |||
| AgentRPCPool = agtrpc.NewPool(cfg) | |||
| } | |||
| var IPFSPool *ipfs.Pool | |||
| func InitIPFSPool(cfg *ipfs.Config) { | |||
| IPFSPool = ipfs.NewPool(cfg) | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| module gitlink.org.cn/cloudream/storage-common | |||
| require ( | |||
| github.com/baohan10/reedsolomon v0.0.0-20230406042632-43574cac9fa7 | |||
| github.com/beevik/etree v1.2.0 | |||
| github.com/go-ping/ping v1.1.0 | |||
| github.com/go-sql-driver/mysql v1.7.1 | |||
| github.com/jmoiron/sqlx v1.3.5 | |||
| github.com/magefile/mage v1.15.0 | |||
| github.com/samber/lo v1.36.0 | |||
| github.com/smartystreets/goconvey v1.8.0 | |||
| gitlink.org.cn/cloudream/common v0.0.0 | |||
| google.golang.org/grpc v1.54.0 | |||
| google.golang.org/protobuf v1.30.0 | |||
| ) | |||
| require ( | |||
| github.com/antonfisher/nested-logrus-formatter v1.3.1 // indirect | |||
| github.com/benbjohnson/clock v1.3.0 // indirect | |||
| github.com/coreos/go-semver v0.3.0 // indirect | |||
| github.com/coreos/go-systemd/v22 v22.5.0 // indirect | |||
| github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect | |||
| github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect | |||
| github.com/gogo/protobuf v1.3.2 // indirect | |||
| github.com/golang/protobuf v1.5.3 // indirect | |||
| github.com/google/uuid v1.3.0 // indirect | |||
| github.com/gopherjs/gopherjs v1.17.2 // indirect | |||
| github.com/hashicorp/errwrap v1.1.0 // indirect | |||
| github.com/hashicorp/go-multierror v1.1.1 // indirect | |||
| github.com/ipfs/boxo v0.8.0 // indirect | |||
| github.com/ipfs/go-cid v0.4.0 // indirect | |||
| github.com/ipfs/go-ipfs-api v0.6.0 // indirect | |||
| github.com/json-iterator/go v1.1.12 // indirect | |||
| github.com/jtolds/gls v4.20.0+incompatible // indirect | |||
| github.com/klauspost/cpuid/v2 v2.2.3 // indirect | |||
| github.com/libp2p/go-buffer-pool v0.1.0 // indirect | |||
| github.com/libp2p/go-flow-metrics v0.1.0 // indirect | |||
| github.com/libp2p/go-libp2p v0.26.3 // indirect | |||
| github.com/minio/sha256-simd v1.0.0 // indirect | |||
| github.com/mitchellh/go-homedir v1.1.0 // indirect | |||
| github.com/mitchellh/mapstructure v1.5.0 // indirect | |||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect | |||
| github.com/modern-go/reflect2 v1.0.2 // indirect | |||
| github.com/mr-tron/base58 v1.2.0 // indirect | |||
| github.com/multiformats/go-base32 v0.1.0 // indirect | |||
| github.com/multiformats/go-base36 v0.2.0 // indirect | |||
| github.com/multiformats/go-multiaddr v0.8.0 // indirect | |||
| github.com/multiformats/go-multibase v0.1.1 // indirect | |||
| github.com/multiformats/go-multicodec v0.8.1 // indirect | |||
| github.com/multiformats/go-multihash v0.2.1 // indirect | |||
| github.com/multiformats/go-multistream v0.4.1 // indirect | |||
| github.com/multiformats/go-varint v0.0.7 // indirect | |||
| github.com/sirupsen/logrus v1.9.2 // indirect | |||
| github.com/smartystreets/assertions v1.13.1 // indirect | |||
| github.com/spaolacci/murmur3 v1.1.0 // indirect | |||
| github.com/streadway/amqp v1.1.0 // indirect | |||
| github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c // indirect | |||
| github.com/zyedidia/generic v1.2.1 // indirect | |||
| go.etcd.io/etcd/api/v3 v3.5.9 // indirect | |||
| go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect | |||
| go.etcd.io/etcd/client/v3 v3.5.9 // indirect | |||
| go.uber.org/atomic v1.10.0 // indirect | |||
| go.uber.org/multierr v1.9.0 // indirect | |||
| go.uber.org/zap v1.24.0 // indirect | |||
| golang.org/x/crypto v0.6.0 // indirect | |||
| golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect | |||
| golang.org/x/net v0.8.0 // indirect | |||
| golang.org/x/sync v0.1.0 // indirect | |||
| golang.org/x/sys v0.6.0 // indirect | |||
| golang.org/x/text v0.8.0 // indirect | |||
| google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect | |||
| lukechampine.com/blake3 v1.1.7 // indirect | |||
| ) | |||
| go 1.20 | |||
| replace gitlink.org.cn/cloudream/common v0.0.0 => ../../common | |||
| @@ -0,0 +1,200 @@ | |||
| github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= | |||
| github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= | |||
| github.com/baohan10/reedsolomon v0.0.0-20230406042632-43574cac9fa7 h1:wcvD6enR///dFvb9cRodx5SGbPH4G4jPjw+aVIWkAKE= | |||
| github.com/baohan10/reedsolomon v0.0.0-20230406042632-43574cac9fa7/go.mod h1:rAxMF6pVaFK/s6T4gGczvloccNbtwzuYaP2Y7W6flE8= | |||
| github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw= | |||
| github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= | |||
| github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= | |||
| github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= | |||
| github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= | |||
| github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= | |||
| github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | |||
| github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= | |||
| github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= | |||
| github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= | |||
| github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= | |||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
| github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= | |||
| github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= | |||
| github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= | |||
| github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= | |||
| github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= | |||
| github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | |||
| github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= | |||
| github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | |||
| github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | |||
| github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | |||
| github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | |||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | |||
| github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | |||
| github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | |||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |||
| github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | |||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | |||
| github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | |||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |||
| github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | |||
| github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= | |||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | |||
| github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | |||
| github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | |||
| github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= | |||
| github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= | |||
| github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs= | |||
| github.com/ipfs/boxo v0.8.0/go.mod h1:RIsi4CnTyQ7AUsNn5gXljJYZlQrHBMnJp94p73liFiA= | |||
| github.com/ipfs/go-cid v0.4.0 h1:a4pdZq0sx6ZSxbCizebnKiMCx/xI/aBBFlB73IgH4rA= | |||
| github.com/ipfs/go-cid v0.4.0/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= | |||
| github.com/ipfs/go-ipfs-api v0.6.0 h1:JARgG0VTbjyVhO5ZfesnbXv9wTcMvoKRBLF1SzJqzmg= | |||
| github.com/ipfs/go-ipfs-api v0.6.0/go.mod h1:iDC2VMwN9LUpQV/GzEeZ2zNqd8NUdRmWcFM+K/6odf0= | |||
| github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= | |||
| github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= | |||
| github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | |||
| github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | |||
| github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | |||
| github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | |||
| github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | |||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | |||
| github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | |||
| github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= | |||
| github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= | |||
| github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= | |||
| github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= | |||
| github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
| github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= | |||
| github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= | |||
| github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= | |||
| github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= | |||
| github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= | |||
| github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= | |||
| github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= | |||
| github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= | |||
| github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= | |||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | |||
| github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= | |||
| github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= | |||
| github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= | |||
| github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | |||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | |||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | |||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= | |||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | |||
| github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | |||
| github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | |||
| github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= | |||
| github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= | |||
| github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= | |||
| github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= | |||
| github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= | |||
| github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= | |||
| github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= | |||
| github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= | |||
| github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= | |||
| github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= | |||
| github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= | |||
| github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= | |||
| github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= | |||
| github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= | |||
| github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= | |||
| github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= | |||
| github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= | |||
| github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= | |||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | |||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |||
| github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= | |||
| github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= | |||
| github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= | |||
| github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | |||
| github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= | |||
| github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= | |||
| github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w= | |||
| github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg= | |||
| github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= | |||
| github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | |||
| github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= | |||
| github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= | |||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |||
| github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= | |||
| github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= | |||
| github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c h1:GGsyl0dZ2jJgVT+VvWBf/cNijrHRhkrTjkmp5wg7li0= | |||
| github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs= | |||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
| github.com/zyedidia/generic v1.2.1 h1:Zv5KS/N2m0XZZiuLS82qheRG4X1o5gsWreGb0hR7XDc= | |||
| github.com/zyedidia/generic v1.2.1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis= | |||
| go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= | |||
| go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= | |||
| go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= | |||
| go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= | |||
| go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= | |||
| go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= | |||
| go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= | |||
| go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= | |||
| go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= | |||
| go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= | |||
| go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= | |||
| go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= | |||
| go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= | |||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
| golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= | |||
| golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= | |||
| golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA= | |||
| golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= | |||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
| golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | |||
| golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= | |||
| golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= | |||
| golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= | |||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
| golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
| golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= | |||
| golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
| golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
| golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
| golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= | |||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |||
| golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= | |||
| golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= | |||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
| golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | |||
| golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | |||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
| google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= | |||
| google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= | |||
| google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= | |||
| google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= | |||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | |||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | |||
| google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= | |||
| google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | |||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | |||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | |||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | |||
| lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= | |||
| lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= | |||
| @@ -0,0 +1,17 @@ | |||
| //go:build mage | |||
| package main | |||
| import ( | |||
| "path/filepath" | |||
| "github.com/magefile/mage/sh" | |||
| ) | |||
| func Protos() error { | |||
| return proto("pkgs/grpc/agent", "agent.proto") | |||
| } | |||
| func proto(dir string, fileName string) error { | |||
| return sh.Run("protoc", "--go_out="+dir, "--go-grpc_out="+dir, filepath.Join(dir, fileName)) | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| package models | |||
| import "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| /// TODO 将分散在各处的公共结构体定义集中到这里来 | |||
| type RedundancyData interface{} | |||
| type RedundancyDataConst interface { | |||
| RepRedundancyData | ECRedundancyData | RedundancyData | |||
| } | |||
| type RepRedundancyData struct { | |||
| FileHash string `json:"fileHash"` | |||
| } | |||
| func NewRedundancyRepData(fileHash string) RepRedundancyData { | |||
| return RepRedundancyData{ | |||
| FileHash: fileHash, | |||
| } | |||
| } | |||
| type ECRedundancyData struct { | |||
| Ec EC `json:"ec"` | |||
| Blocks []ObjectBlockData `json:"blocks"` | |||
| } | |||
| func NewRedundancyEcData(ec EC, blocks []ObjectBlockData) ECRedundancyData { | |||
| return ECRedundancyData{ | |||
| Ec: ec, | |||
| Blocks: blocks, | |||
| } | |||
| } | |||
| type EC struct { | |||
| ID int `json:"id"` | |||
| Name string `json:"name"` | |||
| EcK int `json:"ecK"` | |||
| EcN int `json:"ecN"` | |||
| } | |||
| type ObjectBlockData struct { | |||
| Index int `json:"index"` | |||
| FileHash string `json:"fileHash"` | |||
| NodeIDs []int64 `json:"nodeIDs"` | |||
| } | |||
| func NewObjectBlockData(index int, fileHash string, nodeIDs []int64) ObjectBlockData { | |||
| return ObjectBlockData{ | |||
| Index: index, | |||
| FileHash: fileHash, | |||
| NodeIDs: nodeIDs, | |||
| } | |||
| } | |||
| func NewEc(id int, name string, ecK int, ecN int) EC { | |||
| return EC{ | |||
| ID: id, | |||
| Name: name, | |||
| EcK: ecK, | |||
| EcN: ecN, | |||
| } | |||
| } | |||
| type ObjectRepData struct { | |||
| Object model.Object `json:"object"` | |||
| FileHash string `json:"fileHash"` | |||
| NodeIDs []int64 `json:"nodeIDs"` | |||
| } | |||
| func NewObjectRepData(object model.Object, fileHash string, nodeIDs []int64) ObjectRepData { | |||
| return ObjectRepData{ | |||
| Object: object, | |||
| FileHash: fileHash, | |||
| NodeIDs: nodeIDs, | |||
| } | |||
| } | |||
| type ObjectECData struct { | |||
| Object model.Object `json:"object"` | |||
| Blocks []ObjectBlockData `json:"blocks"` | |||
| } | |||
| func NewObjectECData(object model.Object, blocks []ObjectBlockData) ObjectECData { | |||
| return ObjectECData{ | |||
| Object: object, | |||
| Blocks: blocks, | |||
| } | |||
| } | |||
| type LocalMachineInfo struct { | |||
| NodeID *int64 `json:"nodeID"` | |||
| ExternalIP string `json:"externalIP"` | |||
| LocalIP string `json:"localIP"` | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| package cmd | |||
| // 这个包主要存放一些公共的业务逻辑代码 | |||
| @@ -0,0 +1,348 @@ | |||
| package cmd | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "math/rand" | |||
| "os" | |||
| "path/filepath" | |||
| "sync" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/reqbuilder" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/ec" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/iterator" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type CreateECPackage struct { | |||
| userID int64 | |||
| bucketID int64 | |||
| name string | |||
| objectIter iterator.UploadingObjectIterator | |||
| redundancy models.ECRedundancyInfo | |||
| } | |||
| type CreateECPackageResult struct { | |||
| PackageID int64 | |||
| ObjectResults []ECObjectUploadResult | |||
| } | |||
| type ECObjectUploadResult struct { | |||
| Info *iterator.IterUploadingObject | |||
| Error error | |||
| ObjectID int64 | |||
| } | |||
| func NewCreateECPackage(userID int64, bucketID int64, name string, objIter iterator.UploadingObjectIterator, redundancy models.ECRedundancyInfo) *CreateECPackage { | |||
| return &CreateECPackage{ | |||
| userID: userID, | |||
| bucketID: bucketID, | |||
| name: name, | |||
| objectIter: objIter, | |||
| redundancy: redundancy, | |||
| } | |||
| } | |||
| func (t *CreateECPackage) Execute(ctx *UpdatePackageContext) (*CreateECPackageResult, error) { | |||
| defer t.objectIter.Close() | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| mutex, err := reqbuilder.NewBuilder(). | |||
| Metadata(). | |||
| // 用于判断用户是否有桶的权限 | |||
| UserBucket().ReadOne(t.userID, t.bucketID). | |||
| // 用于查询可用的上传节点 | |||
| Node().ReadAny(). | |||
| // 用于创建包信息 | |||
| Package().CreateOne(t.bucketID, t.name). | |||
| // 用于创建包中的文件的信息 | |||
| Object().CreateAny(). | |||
| // 用于设置EC配置 | |||
| ObjectBlock().CreateAny(). | |||
| // 用于创建Cache记录 | |||
| Cache().CreateAny(). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer mutex.Unlock() | |||
| createPkgResp, err := coorCli.CreatePackage(coormq.NewCreatePackage(t.userID, t.bucketID, t.name, | |||
| models.NewTypedRedundancyInfo(t.redundancy))) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("creating package: %w", err) | |||
| } | |||
| getUserNodesResp, err := coorCli.GetUserNodes(coormq.NewGetUserNodes(t.userID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting user nodes: %w", err) | |||
| } | |||
| findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(globals.Local.ExternalIP)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("finding client location: %w", err) | |||
| } | |||
| uploadNodeInfos := lo.Map(getUserNodesResp.Nodes, func(node model.Node, index int) UploadNodeInfo { | |||
| return UploadNodeInfo{ | |||
| Node: node, | |||
| IsSameLocation: node.LocationID == findCliLocResp.Location.LocationID, | |||
| } | |||
| }) | |||
| getECResp, err := coorCli.GetECConfig(coormq.NewGetECConfig(t.redundancy.ECName)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting ec: %w", err) | |||
| } | |||
| // 给上传节点的IPFS加锁 | |||
| ipfsReqBlder := reqbuilder.NewBuilder() | |||
| // 如果本地的IPFS也是存储系统的一个节点,那么从本地上传时,需要加锁 | |||
| if globals.Local.NodeID != nil { | |||
| ipfsReqBlder.IPFS().CreateAnyRep(*globals.Local.NodeID) | |||
| } | |||
| for _, node := range uploadNodeInfos { | |||
| if globals.Local.NodeID != nil && node.Node.NodeID == *globals.Local.NodeID { | |||
| continue | |||
| } | |||
| ipfsReqBlder.IPFS().CreateAnyRep(node.Node.NodeID) | |||
| } | |||
| // 防止上传的副本被清除 | |||
| ipfsMutex, err := ipfsReqBlder.MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer ipfsMutex.Unlock() | |||
| rets, err := uploadAndUpdateECPackage(createPkgResp.PackageID, t.objectIter, uploadNodeInfos, t.redundancy, getECResp.Config) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &CreateECPackageResult{ | |||
| PackageID: createPkgResp.PackageID, | |||
| ObjectResults: rets, | |||
| }, nil | |||
| } | |||
| func uploadAndUpdateECPackage(packageID int64, objectIter iterator.UploadingObjectIterator, uploadNodes []UploadNodeInfo, ecInfo models.ECRedundancyInfo, ec model.Ec) ([]ECObjectUploadResult, error) { | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| var uploadRets []ECObjectUploadResult | |||
| //上传文件夹 | |||
| var adds []coormq.AddECObjectInfo | |||
| for { | |||
| objInfo, err := objectIter.MoveNext() | |||
| if err == iterator.ErrNoMoreItem { | |||
| break | |||
| } | |||
| if err != nil { | |||
| return nil, fmt.Errorf("reading object: %w", err) | |||
| } | |||
| fileHashes, uploadedNodeIDs, err := uploadECObject(objInfo, uploadNodes, ecInfo, ec) | |||
| uploadRets = append(uploadRets, ECObjectUploadResult{ | |||
| Info: objInfo, | |||
| Error: err, | |||
| }) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("uploading object: %w", err) | |||
| } | |||
| adds = append(adds, coormq.NewAddECObjectInfo(objInfo.Path, objInfo.Size, fileHashes, uploadedNodeIDs)) | |||
| } | |||
| _, err = coorCli.UpdateECPackage(coormq.NewUpdateECPackage(packageID, adds, nil)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("updating package: %w", err) | |||
| } | |||
| return uploadRets, nil | |||
| } | |||
| // 上传文件 | |||
| func uploadECObject(obj *iterator.IterUploadingObject, uploadNodes []UploadNodeInfo, ecInfo models.ECRedundancyInfo, ec model.Ec) ([]string, []int64, error) { | |||
| //生成纠删码的写入节点序列 | |||
| nodes := make([]UploadNodeInfo, ec.EcN) | |||
| numNodes := len(uploadNodes) | |||
| startWriteNodeID := rand.Intn(numNodes) | |||
| for i := 0; i < ec.EcN; i++ { | |||
| nodes[i] = uploadNodes[(startWriteNodeID+i)%numNodes] | |||
| } | |||
| hashs, err := ecWrite(obj.File, obj.Size, ecInfo.PacketSize, ec.EcK, ec.EcN, nodes) | |||
| if err != nil { | |||
| return nil, nil, fmt.Errorf("EcWrite failed, err: %w", err) | |||
| } | |||
| nodeIDs := make([]int64, len(nodes)) | |||
| for i := 0; i < len(nodes); i++ { | |||
| nodeIDs[i] = nodes[i].Node.NodeID | |||
| } | |||
| return hashs, nodeIDs, nil | |||
| } | |||
| // chooseUploadNode 选择一个上传文件的节点 | |||
| // 1. 从与当前客户端相同地域的节点中随机选一个 | |||
| // 2. 没有用的话从所有节点中随机选一个 | |||
| func (t *CreateECPackage) chooseUploadNode(nodes []UploadNodeInfo) UploadNodeInfo { | |||
| sameLocationNodes := lo.Filter(nodes, func(e UploadNodeInfo, i int) bool { return e.IsSameLocation }) | |||
| if len(sameLocationNodes) > 0 { | |||
| return sameLocationNodes[rand.Intn(len(sameLocationNodes))] | |||
| } | |||
| return nodes[rand.Intn(len(nodes))] | |||
| } | |||
| func ecWrite(file io.ReadCloser, fileSize int64, packetSize int64, ecK int, ecN int, nodes []UploadNodeInfo) ([]string, error) { | |||
| // TODO 需要参考RepWrite函数的代码逻辑,做好错误处理 | |||
| //获取文件大小 | |||
| var coefs = [][]int64{{1, 1, 1}, {1, 2, 3}} //2应替换为ecK,3应替换为ecN | |||
| //计算每个块的packet数 | |||
| numPacket := (fileSize + int64(ecK)*packetSize - 1) / (int64(ecK) * packetSize) | |||
| //fmt.Println(numPacket) | |||
| //创建channel | |||
| loadBufs := make([]chan []byte, ecN) | |||
| encodeBufs := make([]chan []byte, ecN) | |||
| for i := 0; i < ecN; i++ { | |||
| loadBufs[i] = make(chan []byte) | |||
| } | |||
| for i := 0; i < ecN; i++ { | |||
| encodeBufs[i] = make(chan []byte) | |||
| } | |||
| hashs := make([]string, ecN) | |||
| //正式开始写入 | |||
| go load(file, loadBufs[:ecN], ecK, numPacket*int64(ecK), packetSize) //从本地文件系统加载数据 | |||
| go encode(loadBufs[:ecN], encodeBufs[:ecN], ecK, coefs, numPacket) | |||
| var wg sync.WaitGroup | |||
| wg.Add(ecN) | |||
| for idx := 0; idx < ecN; idx++ { | |||
| i := idx | |||
| reader := channelBytesReader{ | |||
| channel: encodeBufs[idx], | |||
| packetCount: numPacket, | |||
| } | |||
| go func() { | |||
| // TODO 处理错误 | |||
| fileHash, _ := uploadFile(&reader, nodes[i]) | |||
| hashs[i] = fileHash | |||
| wg.Done() | |||
| }() | |||
| } | |||
| wg.Wait() | |||
| return hashs, nil | |||
| } | |||
| func load(file io.ReadCloser, loadBufs []chan []byte, ecK int, totalNumPacket int64, ecPacketSize int64) error { | |||
| for i := 0; int64(i) < totalNumPacket; i++ { | |||
| buf := make([]byte, ecPacketSize) | |||
| idx := i % ecK | |||
| _, err := file.Read(buf) | |||
| if err != nil { | |||
| return fmt.Errorf("read file falied, err:%w", err) | |||
| } | |||
| loadBufs[idx] <- buf | |||
| if idx == ecK-1 { | |||
| for j := ecK; j < len(loadBufs); j++ { | |||
| zeroPkt := make([]byte, ecPacketSize) | |||
| loadBufs[j] <- zeroPkt | |||
| } | |||
| } | |||
| if err != nil && err != io.EOF { | |||
| return fmt.Errorf("load file to buf failed, err:%w", err) | |||
| } | |||
| } | |||
| for i := 0; i < len(loadBufs); i++ { | |||
| close(loadBufs[i]) | |||
| } | |||
| file.Close() | |||
| return nil | |||
| } | |||
| func encode(inBufs []chan []byte, outBufs []chan []byte, ecK int, coefs [][]int64, numPacket int64) { | |||
| var tmpIn [][]byte | |||
| tmpIn = make([][]byte, len(outBufs)) | |||
| enc := ec.NewRsEnc(ecK, len(outBufs)) | |||
| for i := 0; int64(i) < numPacket; i++ { | |||
| for j := 0; j < len(outBufs); j++ { | |||
| tmpIn[j] = <-inBufs[j] | |||
| } | |||
| enc.Encode(tmpIn) | |||
| for j := 0; j < len(outBufs); j++ { | |||
| outBufs[j] <- tmpIn[j] | |||
| } | |||
| } | |||
| for i := 0; i < len(outBufs); i++ { | |||
| close(outBufs[i]) | |||
| } | |||
| } | |||
| type channelBytesReader struct { | |||
| channel chan []byte | |||
| packetCount int64 | |||
| readingData []byte | |||
| } | |||
| func (r *channelBytesReader) Read(buf []byte) (int, error) { | |||
| if len(r.readingData) == 0 { | |||
| if r.packetCount == 0 { | |||
| return 0, io.EOF | |||
| } | |||
| r.readingData = <-r.channel | |||
| r.packetCount-- | |||
| } | |||
| len := copy(buf, r.readingData) | |||
| r.readingData = r.readingData[:len] | |||
| return len, nil | |||
| } | |||
| func persist(inBuf []chan []byte, numPacket int64, localFilePath string, wg *sync.WaitGroup) { | |||
| fDir, err := os.Executable() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| fURL := filepath.Join(filepath.Dir(fDir), "assets") | |||
| _, err = os.Stat(fURL) | |||
| if os.IsNotExist(err) { | |||
| os.MkdirAll(fURL, os.ModePerm) | |||
| } | |||
| file, err := os.Create(filepath.Join(fURL, localFilePath)) | |||
| if err != nil { | |||
| return | |||
| } | |||
| for i := 0; int64(i) < numPacket; i++ { | |||
| for j := 0; j < len(inBuf); j++ { | |||
| tmp := <-inBuf[j] | |||
| fmt.Println(tmp) | |||
| file.Write(tmp) | |||
| } | |||
| } | |||
| file.Close() | |||
| wg.Done() | |||
| } | |||
| @@ -0,0 +1,285 @@ | |||
| package cmd | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "math/rand" | |||
| "time" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| distsvc "gitlink.org.cn/cloudream/common/pkgs/distlock/service" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/reqbuilder" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/iterator" | |||
| agtmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/agent" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type UploadNodeInfo struct { | |||
| Node model.Node | |||
| IsSameLocation bool | |||
| } | |||
| type CreateRepPackage struct { | |||
| userID int64 | |||
| bucketID int64 | |||
| name string | |||
| objectIter iterator.UploadingObjectIterator | |||
| redundancy models.RepRedundancyInfo | |||
| } | |||
| type UpdatePackageContext struct { | |||
| Distlock *distsvc.Service | |||
| } | |||
| type CreateRepPackageResult struct { | |||
| PackageID int64 | |||
| ObjectResults []RepObjectUploadResult | |||
| } | |||
| type RepObjectUploadResult struct { | |||
| Info *iterator.IterUploadingObject | |||
| Error error | |||
| FileHash string | |||
| ObjectID int64 | |||
| } | |||
| func NewCreateRepPackage(userID int64, bucketID int64, name string, objIter iterator.UploadingObjectIterator, redundancy models.RepRedundancyInfo) *CreateRepPackage { | |||
| return &CreateRepPackage{ | |||
| userID: userID, | |||
| bucketID: bucketID, | |||
| name: name, | |||
| objectIter: objIter, | |||
| redundancy: redundancy, | |||
| } | |||
| } | |||
| func (t *CreateRepPackage) Execute(ctx *UpdatePackageContext) (*CreateRepPackageResult, error) { | |||
| defer t.objectIter.Close() | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| reqBlder := reqbuilder.NewBuilder() | |||
| // 如果本地的IPFS也是存储系统的一个节点,那么从本地上传时,需要加锁 | |||
| if globals.Local.NodeID != nil { | |||
| reqBlder.IPFS().CreateAnyRep(*globals.Local.NodeID) | |||
| } | |||
| mutex, err := reqBlder. | |||
| Metadata(). | |||
| // 用于判断用户是否有桶的权限 | |||
| UserBucket().ReadOne(t.userID, t.bucketID). | |||
| // 用于查询可用的上传节点 | |||
| Node().ReadAny(). | |||
| // 用于创建包信息 | |||
| Package().CreateOne(t.bucketID, t.name). | |||
| // 用于创建包中的文件的信息 | |||
| Object().CreateAny(). | |||
| // 用于设置EC配置 | |||
| ObjectBlock().CreateAny(). | |||
| // 用于创建Cache记录 | |||
| Cache().CreateAny(). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer mutex.Unlock() | |||
| createPkgResp, err := coorCli.CreatePackage(coormq.NewCreatePackage(t.userID, t.bucketID, t.name, | |||
| models.NewTypedRedundancyInfo(t.redundancy))) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("creating package: %w", err) | |||
| } | |||
| getUserNodesResp, err := coorCli.GetUserNodes(coormq.NewGetUserNodes(t.userID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting user nodes: %w", err) | |||
| } | |||
| findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(globals.Local.ExternalIP)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("finding client location: %w", err) | |||
| } | |||
| nodeInfos := lo.Map(getUserNodesResp.Nodes, func(node model.Node, index int) UploadNodeInfo { | |||
| return UploadNodeInfo{ | |||
| Node: node, | |||
| IsSameLocation: node.LocationID == findCliLocResp.Location.LocationID, | |||
| } | |||
| }) | |||
| uploadNode := t.chooseUploadNode(nodeInfos) | |||
| // 防止上传的副本被清除 | |||
| ipfsMutex, err := reqbuilder.NewBuilder(). | |||
| IPFS().CreateAnyRep(uploadNode.Node.NodeID). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer ipfsMutex.Unlock() | |||
| rets, err := uploadAndUpdateRepPackage(createPkgResp.PackageID, t.objectIter, uploadNode) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &CreateRepPackageResult{ | |||
| PackageID: createPkgResp.PackageID, | |||
| ObjectResults: rets, | |||
| }, nil | |||
| } | |||
| func uploadAndUpdateRepPackage(packageID int64, objectIter iterator.UploadingObjectIterator, uploadNode UploadNodeInfo) ([]RepObjectUploadResult, error) { | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| var uploadRets []RepObjectUploadResult | |||
| var adds []coormq.AddRepObjectInfo | |||
| for { | |||
| objInfo, err := objectIter.MoveNext() | |||
| if err == iterator.ErrNoMoreItem { | |||
| break | |||
| } | |||
| if err != nil { | |||
| return nil, fmt.Errorf("reading object: %w", err) | |||
| } | |||
| fileHash, err := uploadFile(objInfo.File, uploadNode) | |||
| uploadRets = append(uploadRets, RepObjectUploadResult{ | |||
| Info: objInfo, | |||
| Error: err, | |||
| FileHash: fileHash, | |||
| }) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("uploading object: %w", err) | |||
| } | |||
| adds = append(adds, coormq.NewAddRepObjectInfo(objInfo.Path, objInfo.Size, fileHash, []int64{uploadNode.Node.NodeID})) | |||
| } | |||
| _, err = coorCli.UpdateRepPackage(coormq.NewUpdateRepPackage(packageID, adds, nil)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("updating package: %w", err) | |||
| } | |||
| return uploadRets, nil | |||
| } | |||
| // 上传文件 | |||
| func uploadFile(file io.Reader, uploadNode UploadNodeInfo) (string, error) { | |||
| // 本地有IPFS,则直接从本地IPFS上传 | |||
| if globals.IPFSPool != nil { | |||
| logger.Infof("try to use local IPFS to upload file") | |||
| // 只有本地IPFS不是存储系统中的一个节点,才需要Pin文件 | |||
| fileHash, err := uploadToLocalIPFS(file, uploadNode.Node.NodeID, globals.Local.NodeID == nil) | |||
| if err == nil { | |||
| return fileHash, nil | |||
| } else { | |||
| logger.Warnf("upload to local IPFS failed, so try to upload to node %d, err: %s", uploadNode.Node.NodeID, err.Error()) | |||
| } | |||
| } | |||
| // 否则发送到agent上传 | |||
| // 如果客户端与节点在同一个地域,则使用内网地址连接节点 | |||
| nodeIP := uploadNode.Node.ExternalIP | |||
| if uploadNode.IsSameLocation { | |||
| nodeIP = uploadNode.Node.LocalIP | |||
| logger.Infof("client and node %d are at the same location, use local ip\n", uploadNode.Node.NodeID) | |||
| } | |||
| fileHash, err := uploadToNode(file, nodeIP) | |||
| if err != nil { | |||
| return "", fmt.Errorf("upload to node %s failed, err: %w", nodeIP, err) | |||
| } | |||
| return fileHash, nil | |||
| } | |||
| // chooseUploadNode 选择一个上传文件的节点 | |||
| // 1. 从与当前客户端相同地域的节点中随机选一个 | |||
| // 2. 没有用的话从所有节点中随机选一个 | |||
| func (t *CreateRepPackage) chooseUploadNode(nodes []UploadNodeInfo) UploadNodeInfo { | |||
| sameLocationNodes := lo.Filter(nodes, func(e UploadNodeInfo, i int) bool { return e.IsSameLocation }) | |||
| if len(sameLocationNodes) > 0 { | |||
| return sameLocationNodes[rand.Intn(len(sameLocationNodes))] | |||
| } | |||
| return nodes[rand.Intn(len(nodes))] | |||
| } | |||
| func uploadToNode(file io.Reader, nodeIP string) (string, error) { | |||
| rpcCli, err := globals.AgentRPCPool.Acquire(nodeIP) | |||
| if err != nil { | |||
| return "", fmt.Errorf("new agent rpc client: %w", err) | |||
| } | |||
| defer rpcCli.Close() | |||
| return rpcCli.SendIPFSFile(file) | |||
| } | |||
| func uploadToLocalIPFS(file io.Reader, nodeID int64, shouldPin bool) (string, error) { | |||
| ipfsCli, err := globals.IPFSPool.Acquire() | |||
| if err != nil { | |||
| return "", fmt.Errorf("new ipfs client: %w", err) | |||
| } | |||
| defer ipfsCli.Close() | |||
| // 从本地IPFS上传文件 | |||
| fileHash, err := ipfsCli.CreateFile(file) | |||
| if err != nil { | |||
| return "", fmt.Errorf("creating ipfs file: %w", err) | |||
| } | |||
| if !shouldPin { | |||
| return fileHash, nil | |||
| } | |||
| err = pinIPFSFile(nodeID, fileHash) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| return fileHash, nil | |||
| } | |||
| func pinIPFSFile(nodeID int64, fileHash string) error { | |||
| agtCli, err := globals.AgentMQPool.Acquire(nodeID) | |||
| if err != nil { | |||
| return fmt.Errorf("new agent client: %w", err) | |||
| } | |||
| defer agtCli.Close() | |||
| // 然后让最近节点pin本地上传的文件 | |||
| pinObjResp, err := agtCli.StartPinningObject(agtmq.NewStartPinningObject(fileHash)) | |||
| if err != nil { | |||
| return fmt.Errorf("start pinning object: %w", err) | |||
| } | |||
| for { | |||
| waitResp, err := agtCli.WaitPinningObject(agtmq.NewWaitPinningObject(pinObjResp.TaskID, int64(time.Second)*5)) | |||
| if err != nil { | |||
| return fmt.Errorf("waitting pinning object: %w", err) | |||
| } | |||
| if waitResp.IsComplete { | |||
| if waitResp.Error != "" { | |||
| return fmt.Errorf("agent pinning object: %s", waitResp.Error) | |||
| } | |||
| break | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,151 @@ | |||
| package cmd | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "os" | |||
| "path/filepath" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| distsvc "gitlink.org.cn/cloudream/common/pkgs/distlock/service" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/iterator" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type DownloadPackage struct { | |||
| userID int64 | |||
| packageID int64 | |||
| outputPath string | |||
| } | |||
| type DownloadPackageContext struct { | |||
| Distlock *distsvc.Service | |||
| } | |||
| func NewDownloadPackage(userID int64, packageID int64, outputPath string) *DownloadPackage { | |||
| return &DownloadPackage{ | |||
| userID: userID, | |||
| packageID: packageID, | |||
| outputPath: outputPath, | |||
| } | |||
| } | |||
| func (t *DownloadPackage) Execute(ctx *DownloadPackageContext) error { | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| defer coorCli.Close() | |||
| getPkgResp, err := coorCli.GetPackage(coormq.NewGetPackage(t.userID, t.packageID)) | |||
| if err != nil { | |||
| return fmt.Errorf("getting package: %w", err) | |||
| } | |||
| var objIter iterator.DownloadingObjectIterator | |||
| if getPkgResp.Redundancy.IsRepInfo() { | |||
| objIter, err = t.downloadRep(ctx) | |||
| } else { | |||
| objIter, err = t.downloadEC(ctx, getPkgResp.Package) | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer objIter.Close() | |||
| return t.writeObject(objIter) | |||
| } | |||
| func (t *DownloadPackage) downloadRep(ctx *DownloadPackageContext) (iterator.DownloadingObjectIterator, error) { | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| defer coorCli.Close() | |||
| getObjsResp, err := coorCli.GetPackageObjects(coormq.NewGetPackageObjects(t.userID, t.packageID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting package objects: %w", err) | |||
| } | |||
| getObjRepDataResp, err := coorCli.GetPackageObjectRepData(coormq.NewGetPackageObjectRepData(t.packageID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting package object rep data: %w", err) | |||
| } | |||
| iter := iterator.NewRepObjectIterator(getObjsResp.Objects, getObjRepDataResp.Data, &iterator.DownloadContext{ | |||
| Distlock: ctx.Distlock, | |||
| }) | |||
| return iter, nil | |||
| } | |||
| func (t *DownloadPackage) downloadEC(ctx *DownloadPackageContext, pkg model.Package) (iterator.DownloadingObjectIterator, error) { | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| defer coorCli.Close() | |||
| getObjsResp, err := coorCli.GetPackageObjects(coormq.NewGetPackageObjects(t.userID, t.packageID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting package objects: %w", err) | |||
| } | |||
| getObjECDataResp, err := coorCli.GetPackageObjectECData(coormq.NewGetPackageObjectECData(t.packageID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting package object ec data: %w", err) | |||
| } | |||
| var ecInfo models.ECRedundancyInfo | |||
| if ecInfo, err = pkg.Redundancy.ToECInfo(); err != nil { | |||
| return nil, fmt.Errorf("get ec redundancy info: %w", err) | |||
| } | |||
| getECResp, err := coorCli.GetECConfig(coormq.NewGetECConfig(ecInfo.ECName)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting ec: %w", err) | |||
| } | |||
| iter := iterator.NewECObjectIterator(getObjsResp.Objects, getObjECDataResp.Data, ecInfo, getECResp.Config, &iterator.DownloadContext{ | |||
| Distlock: ctx.Distlock, | |||
| }) | |||
| return iter, nil | |||
| } | |||
| func (t *DownloadPackage) writeObject(objIter iterator.DownloadingObjectIterator) error { | |||
| for { | |||
| objInfo, err := objIter.MoveNext() | |||
| if err == iterator.ErrNoMoreItem { | |||
| break | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer objInfo.File.Close() | |||
| fullPath := filepath.Join(t.outputPath, objInfo.Object.Path) | |||
| dirPath := filepath.Dir(fullPath) | |||
| if err := os.MkdirAll(dirPath, 0755); err != nil { | |||
| return fmt.Errorf("creating object dir: %w", err) | |||
| } | |||
| outputFile, err := os.Create(fullPath) | |||
| if err != nil { | |||
| return fmt.Errorf("creating object file: %w", err) | |||
| } | |||
| defer outputFile.Close() | |||
| _, err = io.Copy(outputFile, objInfo.File) | |||
| if err != nil { | |||
| return fmt.Errorf("copy object data to local file failed, err: %w", err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,120 @@ | |||
| package cmd | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/reqbuilder" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/iterator" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type UpdateECPackage struct { | |||
| userID int64 | |||
| packageID int64 | |||
| objectIter iterator.UploadingObjectIterator | |||
| } | |||
| type UpdateECPackageResult struct { | |||
| ObjectResults []ECObjectUploadResult | |||
| } | |||
| func NewUpdateECPackage(userID int64, packageID int64, objIter iterator.UploadingObjectIterator) *UpdateECPackage { | |||
| return &UpdateECPackage{ | |||
| userID: userID, | |||
| packageID: packageID, | |||
| objectIter: objIter, | |||
| } | |||
| } | |||
| func (t *UpdateECPackage) Execute(ctx *UpdatePackageContext) (*UpdateECPackageResult, error) { | |||
| defer t.objectIter.Close() | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| mutex, err := reqbuilder.NewBuilder(). | |||
| Metadata(). | |||
| // 用于查询可用的上传节点 | |||
| Node().ReadAny(). | |||
| // 用于创建包信息 | |||
| Package().WriteOne(t.packageID). | |||
| // 用于创建包中的文件的信息 | |||
| Object().CreateAny(). | |||
| // 用于设置EC配置 | |||
| ObjectBlock().CreateAny(). | |||
| // 用于创建Cache记录 | |||
| Cache().CreateAny(). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer mutex.Unlock() | |||
| getPkgResp, err := coorCli.GetPackage(coormq.NewGetPackage(t.userID, t.packageID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting package: %w", err) | |||
| } | |||
| getUserNodesResp, err := coorCli.GetUserNodes(coormq.NewGetUserNodes(t.userID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting user nodes: %w", err) | |||
| } | |||
| findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(globals.Local.ExternalIP)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("finding client location: %w", err) | |||
| } | |||
| nodeInfos := lo.Map(getUserNodesResp.Nodes, func(node model.Node, index int) UploadNodeInfo { | |||
| return UploadNodeInfo{ | |||
| Node: node, | |||
| IsSameLocation: node.LocationID == findCliLocResp.Location.LocationID, | |||
| } | |||
| }) | |||
| var ecInfo models.ECRedundancyInfo | |||
| if ecInfo, err = getPkgResp.Package.Redundancy.ToECInfo(); err != nil { | |||
| return nil, fmt.Errorf("get ec redundancy info: %w", err) | |||
| } | |||
| getECResp, err := coorCli.GetECConfig(coormq.NewGetECConfig(ecInfo.ECName)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting ec: %w", err) | |||
| } | |||
| // 给上传节点的IPFS加锁 | |||
| ipfsReqBlder := reqbuilder.NewBuilder() | |||
| // 如果本地的IPFS也是存储系统的一个节点,那么从本地上传时,需要加锁 | |||
| if globals.Local.NodeID != nil { | |||
| ipfsReqBlder.IPFS().CreateAnyRep(*globals.Local.NodeID) | |||
| } | |||
| for _, node := range nodeInfos { | |||
| if globals.Local.NodeID != nil && node.Node.NodeID == *globals.Local.NodeID { | |||
| continue | |||
| } | |||
| ipfsReqBlder.IPFS().CreateAnyRep(node.Node.NodeID) | |||
| } | |||
| // 防止上传的副本被清除 | |||
| ipfsMutex, err := ipfsReqBlder.MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer ipfsMutex.Unlock() | |||
| rets, err := uploadAndUpdateECPackage(t.packageID, t.objectIter, nodeInfos, ecInfo, getECResp.Config) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &UpdateECPackageResult{ | |||
| ObjectResults: rets, | |||
| }, nil | |||
| } | |||
| @@ -0,0 +1,128 @@ | |||
| package cmd | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| mysort "gitlink.org.cn/cloudream/common/utils/sort" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/reqbuilder" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/iterator" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type UpdateRepPackage struct { | |||
| userID int64 | |||
| packageID int64 | |||
| objectIter iterator.UploadingObjectIterator | |||
| } | |||
| type UpdateNodeInfo struct { | |||
| UploadNodeInfo | |||
| HasOldObject bool | |||
| } | |||
| type UpdateRepPackageResult struct { | |||
| ObjectResults []RepObjectUploadResult | |||
| } | |||
| func NewUpdateRepPackage(userID int64, packageID int64, objectIter iterator.UploadingObjectIterator) *UpdateRepPackage { | |||
| return &UpdateRepPackage{ | |||
| userID: userID, | |||
| packageID: packageID, | |||
| objectIter: objectIter, | |||
| } | |||
| } | |||
| func (t *UpdateRepPackage) Execute(ctx *UpdatePackageContext) (*UpdateRepPackageResult, error) { | |||
| defer t.objectIter.Close() | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| reqBlder := reqbuilder.NewBuilder() | |||
| // 如果本地的IPFS也是存储系统的一个节点,那么从本地上传时,需要加锁 | |||
| if globals.Local.NodeID != nil { | |||
| reqBlder.IPFS().CreateAnyRep(*globals.Local.NodeID) | |||
| } | |||
| mutex, err := reqBlder. | |||
| Metadata(). | |||
| // 用于查询可用的上传节点 | |||
| Node().ReadAny(). | |||
| // 用于创建包信息 | |||
| Package().WriteOne(t.packageID). | |||
| // 用于创建包中的文件的信息 | |||
| Object().CreateAny(). | |||
| // 用于设置EC配置 | |||
| ObjectBlock().CreateAny(). | |||
| // 用于创建Cache记录 | |||
| Cache().CreateAny(). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer mutex.Unlock() | |||
| getUserNodesResp, err := coorCli.GetUserNodes(coormq.NewGetUserNodes(t.userID)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting user nodes: %w", err) | |||
| } | |||
| findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(globals.Local.ExternalIP)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("finding client location: %w", err) | |||
| } | |||
| nodeInfos := lo.Map(getUserNodesResp.Nodes, func(node model.Node, index int) UpdateNodeInfo { | |||
| return UpdateNodeInfo{ | |||
| UploadNodeInfo: UploadNodeInfo{ | |||
| Node: node, | |||
| IsSameLocation: node.LocationID == findCliLocResp.Location.LocationID, | |||
| }, | |||
| } | |||
| }) | |||
| // 上传文件的方式优先级: | |||
| // 1. 本地IPFS | |||
| // 2. 包含了旧文件,且与客户端在同地域的节点 | |||
| // 3. 不在同地域,但包含了旧文件的节点 | |||
| // 4. 同地域节点 | |||
| // TODO 需要考虑在多文件的情况下的规则 | |||
| uploadNode := t.chooseUploadNode(nodeInfos) | |||
| // 防止上传的副本被清除 | |||
| ipfsMutex, err := reqbuilder.NewBuilder(). | |||
| IPFS().CreateAnyRep(uploadNode.Node.NodeID). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| defer ipfsMutex.Unlock() | |||
| rets, err := uploadAndUpdateRepPackage(t.packageID, t.objectIter, uploadNode.UploadNodeInfo) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &UpdateRepPackageResult{ | |||
| ObjectResults: rets, | |||
| }, nil | |||
| } | |||
| // chooseUploadNode 选择一个上传文件的节点 | |||
| // 1. 从与当前客户端相同地域的节点中随机选一个 | |||
| // 2. 没有用的话从所有节点中随机选一个 | |||
| func (t *UpdateRepPackage) chooseUploadNode(nodes []UpdateNodeInfo) UpdateNodeInfo { | |||
| mysort.Sort(nodes, func(left, right UpdateNodeInfo) int { | |||
| v := -mysort.CmpBool(left.HasOldObject, right.HasOldObject) | |||
| if v != 0 { | |||
| return v | |||
| } | |||
| return -mysort.CmpBool(left.IsSameLocation, right.IsSameLocation) | |||
| }) | |||
| return nodes[0] | |||
| } | |||
| @@ -0,0 +1,120 @@ | |||
| package db | |||
| import ( | |||
| "database/sql" | |||
| "errors" | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type BucketDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Bucket() *BucketDB { | |||
| return &BucketDB{DB: db} | |||
| } | |||
| // GetIDByName 根据BucketName查询BucketID | |||
| func (db *BucketDB) GetIDByName(bucketName string) (int64, error) { | |||
| //桶结构体 | |||
| var result struct { | |||
| BucketID int64 `db:"BucketID"` | |||
| BucketName string `db:"BucketName"` | |||
| } | |||
| sql := "select BucketID, BucketName from Bucket where BucketName=? " | |||
| if err := db.d.Get(&result, sql, bucketName); err != nil { | |||
| return 0, err | |||
| } | |||
| return result.BucketID, nil | |||
| } | |||
| // IsAvailable 判断用户是否有指定Bucekt的权限 | |||
| func (db *BucketDB) IsAvailable(ctx SQLContext, bucketID int64, userID int64) (bool, error) { | |||
| _, err := db.GetUserBucket(ctx, userID, bucketID) | |||
| if errors.Is(err, sql.ErrNoRows) { | |||
| return false, nil | |||
| } | |||
| if err != nil { | |||
| return false, fmt.Errorf("find bucket failed, err: %w", err) | |||
| } | |||
| return true, nil | |||
| } | |||
| func (*BucketDB) GetUserBucket(ctx SQLContext, userID int64, bucketID int64) (model.Bucket, error) { | |||
| var ret model.Bucket | |||
| err := sqlx.Get(ctx, &ret, | |||
| "select Bucket.* from UserBucket, Bucket where UserID = ? and"+ | |||
| " UserBucket.BucketID = Bucket.BucketID and"+ | |||
| " Bucket.BucketID = ?", userID, bucketID) | |||
| return ret, err | |||
| } | |||
| func (*BucketDB) GetUserBuckets(ctx SQLContext, userID int64) ([]model.Bucket, error) { | |||
| var ret []model.Bucket | |||
| err := sqlx.Select(ctx, &ret, "select Bucket.* from UserBucket, Bucket where UserID = ? and UserBucket.BucketID = Bucket.BucketID", userID) | |||
| return ret, err | |||
| } | |||
| func (db *BucketDB) Create(ctx SQLContext, userID int64, bucketName string) (int64, error) { | |||
| var bucketID int64 | |||
| err := sqlx.Get(ctx, &bucketID, "select Bucket.BucketID from UserBucket, Bucket where UserBucket.UserID = ? and UserBucket.BucketID = Bucket.BucketID and Bucket.Name = ?", userID, bucketName) | |||
| if err == nil { | |||
| return 0, fmt.Errorf("bucket name exsits") | |||
| } | |||
| if err != sql.ErrNoRows { | |||
| return 0, err | |||
| } | |||
| ret, err := ctx.Exec("insert into Bucket(Name,CreatorID) values(?,?)", bucketName, userID) | |||
| if err != nil { | |||
| return 0, fmt.Errorf("insert bucket failed, err: %w", err) | |||
| } | |||
| bucketID, err = ret.LastInsertId() | |||
| if err != nil { | |||
| return 0, fmt.Errorf("get inserted bucket id failed, err: %w", err) | |||
| } | |||
| _, err = ctx.Exec("insert into UserBucket(UserID,BucketID) values(?,?)", userID, bucketID) | |||
| if err != nil { | |||
| return 0, fmt.Errorf("insert into user bucket failed, err: %w", err) | |||
| } | |||
| return bucketID, err | |||
| } | |||
| func (db *BucketDB) Delete(ctx SQLContext, bucketID int64) error { | |||
| _, err := ctx.Exec("delete from UserBucket where BucketID = ?", bucketID) | |||
| if err != nil { | |||
| return fmt.Errorf("delete user bucket failed, err: %w", err) | |||
| } | |||
| _, err = ctx.Exec("delete from Bucket where BucketID = ?", bucketID) | |||
| if err != nil { | |||
| return fmt.Errorf("delete bucket failed, err: %w", err) | |||
| } | |||
| // 删除Bucket内的Package | |||
| var objIDs []int64 | |||
| err = sqlx.Select(ctx, &objIDs, "select PackageID from Package where BucketID = ?", bucketID) | |||
| if err != nil { | |||
| return fmt.Errorf("query package failed, err: %w", err) | |||
| } | |||
| for _, objID := range objIDs { | |||
| // TODO 不一定所有的错误都要中断后续过程 | |||
| err = db.Package().SoftDelete(ctx, objID) | |||
| if err != nil { | |||
| return fmt.Errorf("set package seleted failed, err: %w", err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,117 @@ | |||
| package db | |||
| import ( | |||
| "time" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/consts" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type CacheDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Cache() *CacheDB { | |||
| return &CacheDB{DB: db} | |||
| } | |||
| func (*CacheDB) Get(ctx SQLContext, fileHash string, nodeID int64) (model.Cache, error) { | |||
| var ret model.Cache | |||
| err := sqlx.Get(ctx, &ret, "select * from Cache where FileHash = ? and NodeID = ?", fileHash, nodeID) | |||
| return ret, err | |||
| } | |||
| func (*CacheDB) BatchGetAllFileHashes(ctx SQLContext, start int, count int) ([]string, error) { | |||
| var ret []string | |||
| err := sqlx.Select(ctx, &ret, "select distinct FileHash from Cache limit ?, ?", start, count) | |||
| return ret, err | |||
| } | |||
| func (*CacheDB) GetNodeCaches(ctx SQLContext, nodeID int64) ([]model.Cache, error) { | |||
| var ret []model.Cache | |||
| err := sqlx.Select(ctx, &ret, "select * from Cache where NodeID = ?", nodeID) | |||
| return ret, err | |||
| } | |||
| // CreateNew 创建一条新的缓存记录 | |||
| func (*CacheDB) CreateNew(ctx SQLContext, fileHash string, nodeID int64) error { | |||
| _, err := ctx.Exec("insert into Cache values(?,?,?,?)", fileHash, nodeID, consts.CacheStatePinned, time.Now()) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| // CreatePinned 创建一条缓存记录,如果已存在,但不是pinned状态,则将其设置为pin状态 | |||
| func (*CacheDB) CreatePinned(ctx SQLContext, fileHash string, nodeID int64, priority int) error { | |||
| _, err := ctx.Exec("replace into Cache values(?,?,?,?,?)", fileHash, nodeID, consts.CacheStatePinned, time.Now(), priority) | |||
| return err | |||
| } | |||
| func (*CacheDB) BatchCreatePinned(ctx SQLContext, fileHashes []string, nodeID int64, priority int) error { | |||
| var caches []model.Cache | |||
| var nowTime = time.Now() | |||
| for _, hash := range fileHashes { | |||
| caches = append(caches, model.Cache{ | |||
| FileHash: hash, | |||
| NodeID: nodeID, | |||
| State: consts.CacheStatePinned, | |||
| CacheTime: nowTime, | |||
| Priority: priority, | |||
| }) | |||
| } | |||
| _, err := sqlx.NamedExec(ctx, "insert into Cache(FileHash,NodeID,State,CacheTime,Priority) values(:FileHash,:NodeID,:State,:CacheTime,:Priority)"+ | |||
| " on duplicate key update State=values(State), CacheTime=values(CacheTime), Priority=values(Priority)", | |||
| caches, | |||
| ) | |||
| return err | |||
| } | |||
| // Create 创建一条Temp状态的缓存记录,如果已存在则不产生效果 | |||
| func (*CacheDB) CreateTemp(ctx SQLContext, fileHash string, nodeID int64) error { | |||
| _, err := ctx.Exec("insert ignore into Cache values(?,?,?,?)", fileHash, nodeID, consts.CacheStateTemp, time.Now()) | |||
| return err | |||
| } | |||
| // GetCachingFileNodes 查找缓存了指定文件的节点 | |||
| func (*CacheDB) GetCachingFileNodes(ctx SQLContext, fileHash string) ([]model.Node, error) { | |||
| var x []model.Node | |||
| err := sqlx.Select(ctx, &x, | |||
| "select Node.* from Cache, Node where Cache.FileHash=? and Cache.NodeID = Node.NodeID", fileHash) | |||
| return x, err | |||
| } | |||
| // DeleteTemp 删除一条Temp状态的记录 | |||
| func (*CacheDB) DeleteTemp(ctx SQLContext, fileHash string, nodeID int64) error { | |||
| _, err := ctx.Exec("delete from Cache where FileHash = ? and NodeID = ? and State = ?", fileHash, nodeID, consts.CacheStateTemp) | |||
| return err | |||
| } | |||
| // DeleteNodeAll 删除一个节点所有的记录 | |||
| func (*CacheDB) DeleteNodeAll(ctx SQLContext, nodeID int64) error { | |||
| _, err := ctx.Exec("delete from Cache where NodeID = ?", nodeID) | |||
| return err | |||
| } | |||
| // FindCachingFileUserNodes 在缓存表中查询指定数据所在的节点 | |||
| func (*CacheDB) FindCachingFileUserNodes(ctx SQLContext, userID int64, fileHash string) ([]model.Node, error) { | |||
| var x []model.Node | |||
| err := sqlx.Select(ctx, &x, | |||
| "select Node.* from Cache, UserNode, Node where"+ | |||
| " Cache.FileHash=? and Cache.NodeID = UserNode.NodeID and"+ | |||
| " UserNode.UserID = ? and UserNode.NodeID = Node.NodeID", fileHash, userID) | |||
| return x, err | |||
| } | |||
| func (*CacheDB) SetTemp(ctx SQLContext, fileHash string, nodeID int64) error { | |||
| _, err := ctx.Exec("update Cache set State = ?, CacheTime = ? where FileHash = ? and NodeID = ?", | |||
| consts.CacheStateTemp, | |||
| time.Now(), | |||
| fileHash, | |||
| nodeID, | |||
| ) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package config | |||
| import "fmt" | |||
| type Config struct { | |||
| Address string `json:"address"` | |||
| Account string `json:"account"` | |||
| Password string `json:"password"` | |||
| DatabaseName string `json:"databaseName"` | |||
| } | |||
| func (cfg *Config) MakeSourceString() string { | |||
| return fmt.Sprintf( | |||
| "%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=%s", | |||
| cfg.Account, | |||
| cfg.Password, | |||
| cfg.Address, | |||
| cfg.DatabaseName, | |||
| "Asia%2FShanghai", | |||
| ) | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| package db | |||
| import ( | |||
| "context" | |||
| "database/sql" | |||
| "fmt" | |||
| _ "github.com/go-sql-driver/mysql" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/config" | |||
| ) | |||
| type DB struct { | |||
| d *sqlx.DB | |||
| } | |||
| type SQLContext interface { | |||
| sqlx.Queryer | |||
| sqlx.Execer | |||
| sqlx.Ext | |||
| } | |||
| func NewDB(cfg *config.Config) (*DB, error) { | |||
| db, err := sqlx.Open("mysql", cfg.MakeSourceString()) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("open database connection failed, err: %w", err) | |||
| } | |||
| // 尝试连接一下数据库,如果数据库配置有错误在这里就能报出来 | |||
| err = db.Ping() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &DB{ | |||
| d: db, | |||
| }, nil | |||
| } | |||
| func (db *DB) DoTx(isolation sql.IsolationLevel, fn func(tx *sqlx.Tx) error) error { | |||
| tx, err := db.d.BeginTxx(context.Background(), &sql.TxOptions{Isolation: isolation}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := fn(tx); err != nil { | |||
| tx.Rollback() | |||
| return err | |||
| } | |||
| if err := tx.Commit(); err != nil { | |||
| tx.Rollback() | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func (db *DB) SQLCtx() SQLContext { | |||
| return db.d | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| package db | |||
| import ( | |||
| //"database/sql" | |||
| "github.com/jmoiron/sqlx" | |||
| //"gitlink.org.cn/cloudream/common/consts" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type EcDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Ec() *EcDB { | |||
| return &EcDB{DB: db} | |||
| } | |||
| // GetEc 查询纠删码参数 | |||
| func (db *EcDB) GetEc(ctx SQLContext, ecName string) (model.Ec, error) { | |||
| var ret model.Ec | |||
| err := sqlx.Get(ctx, &ret, "select * from Ec where Name = ?", ecName) | |||
| return ret, err | |||
| } | |||
| func (db *EcDB) GetEcName(ctx SQLContext, objectID int) (string, error) { | |||
| var ret string | |||
| err := sqlx.Get(ctx, &ret, "select Redundancy from Object where ObjectID = ?") | |||
| return ret, err | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| package db | |||
| import ( | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type LocationDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Location() *LocationDB { | |||
| return &LocationDB{DB: db} | |||
| } | |||
| func (*LocationDB) GetByID(ctx SQLContext, id int64) (model.Location, error) { | |||
| var ret model.Location | |||
| err := sqlx.Get(ctx, &ret, "select * from Location where LocationID = ?", id) | |||
| return ret, err | |||
| } | |||
| func (db *LocationDB) FindLocationByExternalIP(ctx SQLContext, ip string) (model.Location, error) { | |||
| var locID int64 | |||
| err := sqlx.Get(ctx, &locID, "select LocationID from Node where ExternalIP = ?", ip) | |||
| if err != nil { | |||
| return model.Location{}, fmt.Errorf("find node by external ip: %w", err) | |||
| } | |||
| return db.GetByID(ctx, locID) | |||
| } | |||
| @@ -0,0 +1,110 @@ | |||
| package model | |||
| import ( | |||
| "time" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| ) | |||
| type Node struct { | |||
| NodeID int64 `db:"NodeID" json:"nodeID"` | |||
| Name string `db:"Name" json:"name"` | |||
| LocalIP string `db:"LocalIP" json:"localIP"` | |||
| ExternalIP string `db:"ExternalIP" json:"externalIP"` | |||
| LocationID int64 `db:"LocationID" json:"locationID"` | |||
| State string `db:"State" json:"state"` | |||
| LastReportTime *time.Time `db:"LastReportTime" json:"lastReportTime"` | |||
| } | |||
| type Storage struct { | |||
| StorageID int64 `db:"StorageID" json:"storageID"` | |||
| Name string `db:"Name" json:"name"` | |||
| NodeID int64 `db:"NodeID" json:"nodeID"` | |||
| Directory string `db:"Directory" json:"directory"` | |||
| State string `db:"State" json:"state"` | |||
| } | |||
| type NodeDelay struct { | |||
| SourceNodeID int64 `db:"SourceNodeID"` | |||
| DestinationNodeID int64 `db:"DestinationNodeID"` | |||
| DelayInMs int `db:"DelayInMs"` | |||
| } | |||
| type User struct { | |||
| UserID int64 `db:"UserID" json:"userID"` | |||
| Password string `db:"PassWord" json:"password"` | |||
| } | |||
| type UserBucket struct { | |||
| UserID int64 `db:"UserID" json:"userID"` | |||
| BucketID int64 `db:"BucketID" json:"bucketID"` | |||
| } | |||
| type UserNode struct { | |||
| UserID int64 `db:"UserID" json:"userID"` | |||
| NodeID int64 `db:"NodeID" json:"nodeID"` | |||
| } | |||
| type UserStorage struct { | |||
| UserID int64 `db:"UserID" json:"userID"` | |||
| StorageID int64 `db:"StorageID" json:"storageID"` | |||
| } | |||
| type Bucket struct { | |||
| BucketID int64 `db:"BucketID" json:"bucketID"` | |||
| Name string `db:"Name" json:"name"` | |||
| CreatorID int64 `db:"CreatorID" json:"creatorID"` | |||
| } | |||
| type Package struct { | |||
| PackageID int64 `db:"PackageID" json:"packageID"` | |||
| Name string `db:"Name" json:"name"` | |||
| BucketID int64 `db:"BucketID" json:"bucketID"` | |||
| State string `db:"State" json:"state"` | |||
| Redundancy models.TypedRedundancyInfo `db:"Redundancy" json:"redundancy"` | |||
| } | |||
| type Object struct { | |||
| ObjectID int64 `db:"ObjectID" json:"objectID"` | |||
| PackageID int64 `db:"PackageID" json:"packageID"` | |||
| Path string `db:"Path" json:"path"` | |||
| Size int64 `db:"Size" json:"size,string"` | |||
| } | |||
| type ObjectRep struct { | |||
| ObjectID int64 `db:"ObjectID" json:"objectID"` | |||
| FileHash string `db:"FileHash" json:"fileHash"` | |||
| } | |||
| type ObjectBlock struct { | |||
| ObjectID int64 `db:"ObjectID" json:"objectID"` | |||
| Index int `db:"Index" json:"index"` | |||
| FileHash string `db:"FileHash" json:"fileHash"` | |||
| } | |||
| type Cache struct { | |||
| FileHash string `db:"FileHash" json:"fileHash"` | |||
| NodeID int64 `db:"NodeID" json:"nodeID"` | |||
| State string `db:"State" json:"state"` | |||
| CacheTime time.Time `db:"CacheTime" json:"cacheTime"` | |||
| Priority int `db:"Priority" json:"priority"` | |||
| } | |||
| type StoragePackage struct { | |||
| PackageID int64 `db:"PackageID" json:"packageID"` | |||
| StorageID int64 `db:"StorageID" json:"storageID"` | |||
| UserID int64 `db:"UserID" json:"userID"` | |||
| State string `db:"State" json:"state"` | |||
| } | |||
| type Location struct { | |||
| LocationID int64 `db:"LocationID" json:"locationID"` | |||
| Name string `db:"Name" json:"name"` | |||
| } | |||
| type Ec struct { | |||
| EcID int `db:"EcID" json:"ecID"` | |||
| Name string `db:"Name" json:"name"` | |||
| EcK int `db:"EcK" json:"ecK"` | |||
| EcN int `db:"EcN" json:"ecN"` | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| package db | |||
| import ( | |||
| "time" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type NodeDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Node() *NodeDB { | |||
| return &NodeDB{DB: db} | |||
| } | |||
| func (db *NodeDB) GetByID(ctx SQLContext, nodeID int64) (model.Node, error) { | |||
| var ret model.Node | |||
| err := sqlx.Get(ctx, &ret, "select * from Node where NodeID = ?", nodeID) | |||
| return ret, err | |||
| } | |||
| func (db *NodeDB) GetAllNodes(ctx SQLContext) ([]model.Node, error) { | |||
| var ret []model.Node | |||
| err := sqlx.Select(ctx, &ret, "select * from Node") | |||
| return ret, err | |||
| } | |||
| // GetUserNodes 根据用户id查询可用node | |||
| func (db *NodeDB) GetUserNodes(ctx SQLContext, userID int64) ([]model.Node, error) { | |||
| var nodes []model.Node | |||
| err := sqlx.Select(ctx, &nodes, "select Node.* from UserNode, Node where UserNode.NodeID = Node.NodeID and UserNode.UserID=?", userID) | |||
| return nodes, err | |||
| } | |||
| // UpdateState 更新状态,并且设置上次上报时间为现在 | |||
| func (db *NodeDB) UpdateState(ctx SQLContext, nodeID int64, state string) error { | |||
| _, err := ctx.Exec("update Node set State = ?, LastReportTime = ? where NodeID = ?", state, time.Now(), nodeID) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,280 @@ | |||
| package db | |||
| import ( | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type ObjectDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Object() *ObjectDB { | |||
| return &ObjectDB{DB: db} | |||
| } | |||
| func (db *ObjectDB) GetByID(ctx SQLContext, objectID int64) (model.Object, error) { | |||
| var ret model.Object | |||
| err := sqlx.Get(ctx, &ret, "select * from Object where ObjectID = ?", objectID) | |||
| return ret, err | |||
| } | |||
| func (db *ObjectDB) Create(ctx SQLContext, packageID int64, path string, size int64) (int64, error) { | |||
| sql := "insert into Object(PackageID, Path, Size) values(?,?,?)" | |||
| ret, err := ctx.Exec(sql, packageID, path, size) | |||
| if err != nil { | |||
| return 0, fmt.Errorf("insert object failed, err: %w", err) | |||
| } | |||
| objectID, err := ret.LastInsertId() | |||
| if err != nil { | |||
| return 0, fmt.Errorf("get id of inserted object failed, err: %w", err) | |||
| } | |||
| return objectID, nil | |||
| } | |||
| // 创建或者更新记录,返回值true代表是创建,false代表是更新 | |||
| func (db *ObjectDB) CreateOrUpdate(ctx SQLContext, packageID int64, path string, size int64) (int64, bool, error) { | |||
| sql := "insert into Object(PackageID, Path, Size) values(?,?,?) on duplicate key update Size = ?" | |||
| ret, err := ctx.Exec(sql, packageID, path, size, size) | |||
| if err != nil { | |||
| return 0, false, fmt.Errorf("insert object failed, err: %w", err) | |||
| } | |||
| affs, err := ret.RowsAffected() | |||
| if err != nil { | |||
| return 0, false, fmt.Errorf("getting affected rows: %w", err) | |||
| } | |||
| // 影响行数为1时是插入,为2时是更新 | |||
| if affs == 1 { | |||
| objectID, err := ret.LastInsertId() | |||
| if err != nil { | |||
| return 0, false, fmt.Errorf("get id of inserted object failed, err: %w", err) | |||
| } | |||
| return objectID, true, nil | |||
| } | |||
| var objID int64 | |||
| if err = sqlx.Get(ctx, &objID, "select ObjectID from Object where PackageID = ? and Path = ?", packageID, path); err != nil { | |||
| return 0, false, fmt.Errorf("getting object id: %w", err) | |||
| } | |||
| return objID, false, nil | |||
| } | |||
| func (db *ObjectDB) UpdateRepObject(ctx SQLContext, objectID int64, fileSize int64, nodeIDs []int64, fileHash string) error { | |||
| _, err := db.UpdateFileInfo(ctx, objectID, fileSize) | |||
| if err != nil { | |||
| if err != nil { | |||
| return fmt.Errorf("update rep object failed, err: %w", err) | |||
| } | |||
| } | |||
| objRep, err := db.ObjectRep().GetByID(ctx, objectID) | |||
| if err != nil { | |||
| return fmt.Errorf("get object rep failed, err: %w", err) | |||
| } | |||
| // 如果新文件与旧文件的Hash不同,则需要更新关联的FileHash,重新插入Cache记录 | |||
| if objRep.FileHash != fileHash { | |||
| _, err := db.ObjectRep().Update(ctx, objectID, fileHash) | |||
| if err != nil { | |||
| return fmt.Errorf("update rep object file hash failed, err: %w", err) | |||
| } | |||
| for _, nodeID := range nodeIDs { | |||
| err := db.Cache().CreatePinned(ctx, fileHash, nodeID, 0) //priority = 0 | |||
| if err != nil { | |||
| return fmt.Errorf("create cache failed, err: %w", err) | |||
| } | |||
| } | |||
| } else { | |||
| // 如果相同,则只增加Cache中不存在的记录 | |||
| cachedNodes, err := db.Cache().GetCachingFileNodes(ctx, fileHash) | |||
| if err != nil { | |||
| return fmt.Errorf("find caching file nodes failed, err: %w", err) | |||
| } | |||
| // 筛选出不在cachedNodes中的id | |||
| newNodeIDs := lo.Filter(nodeIDs, func(id int64, index int) bool { | |||
| return lo.NoneBy(cachedNodes, func(node model.Node) bool { | |||
| return node.NodeID == id | |||
| }) | |||
| }) | |||
| for _, nodeID := range newNodeIDs { | |||
| err := db.Cache().CreatePinned(ctx, fileHash, nodeID, 0) //priority | |||
| if err != nil { | |||
| return fmt.Errorf("create cache failed, err: %w", err) | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (*ObjectDB) BatchGetAllEcObjectIDs(ctx SQLContext, start int, count int) ([]int64, error) { | |||
| var ret []int64 | |||
| rep := "rep" | |||
| err := sqlx.Select(ctx, &ret, "SELECT ObjectID FROM object where Redundancy != ? limit ?, ?", rep, start, count) | |||
| return ret, err | |||
| } | |||
| func (*ObjectDB) UpdateFileInfo(ctx SQLContext, objectID int64, fileSize int64) (bool, error) { | |||
| ret, err := ctx.Exec("update Object set FileSize = ? where ObjectID = ?", fileSize, objectID) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| cnt, err := ret.RowsAffected() | |||
| if err != nil { | |||
| return false, fmt.Errorf("get affected rows failed, err: %w", err) | |||
| } | |||
| return cnt > 0, nil | |||
| } | |||
| func (*ObjectDB) GetPackageObjects(ctx SQLContext, packageID int64) ([]model.Object, error) { | |||
| var ret []model.Object | |||
| err := sqlx.Select(ctx, &ret, "select * from Object where PackageID = ? order by ObjectID asc", packageID) | |||
| return ret, err | |||
| } | |||
| func (db *ObjectDB) BatchAddRep(ctx SQLContext, packageID int64, objs []coormq.AddRepObjectInfo) ([]int64, error) { | |||
| var objIDs []int64 | |||
| for _, obj := range objs { | |||
| // 创建对象的记录 | |||
| objID, isCreate, err := db.CreateOrUpdate(ctx, packageID, obj.Path, obj.Size) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("creating object: %w", err) | |||
| } | |||
| objIDs = append(objIDs, objID) | |||
| if isCreate { | |||
| if err := db.createRep(ctx, objID, obj); err != nil { | |||
| return nil, err | |||
| } | |||
| } else { | |||
| if err := db.updateRep(ctx, objID, obj); err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| } | |||
| return objIDs, nil | |||
| } | |||
| func (db *ObjectDB) createRep(ctx SQLContext, objID int64, obj coormq.AddRepObjectInfo) error { | |||
| // 创建对象副本的记录 | |||
| if err := db.ObjectRep().Create(ctx, objID, obj.FileHash); err != nil { | |||
| return fmt.Errorf("creating object rep: %w", err) | |||
| } | |||
| // 创建缓存记录 | |||
| priority := 0 //优先级暂时设置为0 | |||
| for _, nodeID := range obj.NodeIDs { | |||
| if err := db.Cache().CreatePinned(ctx, obj.FileHash, nodeID, priority); err != nil { | |||
| return fmt.Errorf("creating cache: %w", err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (db *ObjectDB) updateRep(ctx SQLContext, objID int64, obj coormq.AddRepObjectInfo) error { | |||
| objRep, err := db.ObjectRep().GetByID(ctx, objID) | |||
| if err != nil { | |||
| return fmt.Errorf("getting object rep: %w", err) | |||
| } | |||
| // 如果新文件与旧文件的Hash不同,则需要更新关联的FileHash,重新插入Cache记录 | |||
| if objRep.FileHash != obj.FileHash { | |||
| _, err := db.ObjectRep().Update(ctx, objID, obj.FileHash) | |||
| if err != nil { | |||
| return fmt.Errorf("updating rep object file hash: %w", err) | |||
| } | |||
| for _, nodeID := range obj.NodeIDs { | |||
| if err := db.Cache().CreatePinned(ctx, obj.FileHash, nodeID, 0); err != nil { | |||
| return fmt.Errorf("creating cache: %w", err) | |||
| } | |||
| } | |||
| } else { | |||
| // 如果相同,则只增加Cache中不存在的记录 | |||
| cachedNodes, err := db.Cache().GetCachingFileNodes(ctx, obj.FileHash) | |||
| if err != nil { | |||
| return fmt.Errorf("finding caching file nodes: %w", err) | |||
| } | |||
| // 筛选出不在cachedNodes中的id | |||
| newNodeIDs := lo.Filter(obj.NodeIDs, func(id int64, index int) bool { | |||
| return lo.NoneBy(cachedNodes, func(node model.Node) bool { | |||
| return node.NodeID == id | |||
| }) | |||
| }) | |||
| for _, nodeID := range newNodeIDs { | |||
| if err := db.Cache().CreatePinned(ctx, obj.FileHash, nodeID, 0); err != nil { | |||
| return fmt.Errorf("creating cache: %w", err) | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (db *ObjectDB) BatchAddEC(ctx SQLContext, packageID int64, objs []coormq.AddECObjectInfo) ([]int64, error) { | |||
| objIDs := make([]int64, 0, len(objs)) | |||
| for _, obj := range objs { | |||
| // 创建对象的记录 | |||
| objID, isCreate, err := db.CreateOrUpdate(ctx, packageID, obj.Path, obj.Size) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("creating object: %w", err) | |||
| } | |||
| objIDs = append(objIDs, objID) | |||
| if !isCreate { | |||
| // 删除原本所有的编码块记录,重新添加 | |||
| if err = db.ObjectBlock().DeleteObjectAll(ctx, objID); err != nil { | |||
| return nil, fmt.Errorf("deleting all object block: %w", err) | |||
| } | |||
| } | |||
| // 创建编码块的记录 | |||
| for i := 0; i < len(obj.FileHashes); i++ { | |||
| err := db.ObjectBlock().Create(ctx, objID, i, obj.FileHashes[i]) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("creating object block: %w", err) | |||
| } | |||
| } | |||
| // 创建缓存记录 | |||
| priority := 0 //优先级暂时设置为0 | |||
| for i, nodeID := range obj.NodeIDs { | |||
| err = db.Cache().CreatePinned(ctx, obj.FileHashes[i], nodeID, priority) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("creating cache: %w", err) | |||
| } | |||
| } | |||
| } | |||
| return objIDs, nil | |||
| } | |||
| func (*ObjectDB) BatchDelete(ctx SQLContext, ids []int64) error { | |||
| _, err := ctx.Exec("delete from Object where ObjectID in (?)", ids) | |||
| return err | |||
| } | |||
| func (*ObjectDB) DeleteInPackage(ctx SQLContext, packageID int64) error { | |||
| _, err := ctx.Exec("delete from Object where PackageID = ?", packageID) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| package db | |||
| import ( | |||
| "database/sql" | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/consts" | |||
| "gitlink.org.cn/cloudream/storage-common/models" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type ObjectBlockDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) ObjectBlock() *ObjectBlockDB { | |||
| return &ObjectBlockDB{DB: db} | |||
| } | |||
| func (db *ObjectBlockDB) Create(ctx SQLContext, objectID int64, index int, fileHash string) error { | |||
| _, err := ctx.Exec("insert into ObjectBlock(ObjectID, Index, FileHash) values(?,?,?)", objectID, index, fileHash) | |||
| return err | |||
| } | |||
| func (db *ObjectBlockDB) DeleteObjectAll(ctx SQLContext, objectID int64) error { | |||
| _, err := ctx.Exec("delete from ObjectBlock where ObjectID = ?", objectID) | |||
| return err | |||
| } | |||
| func (db *ObjectBlockDB) DeleteInPackage(ctx SQLContext, packageID int64) error { | |||
| _, err := ctx.Exec("delete ObjectBlock from ObjectBlock inner join Object on ObjectBlock.ObjectID = Object.ObjectID where PackageID = ?", packageID) | |||
| return err | |||
| } | |||
| func (db *ObjectBlockDB) CountBlockWithHash(ctx SQLContext, fileHash string) (int, error) { | |||
| var cnt int | |||
| err := sqlx.Get(ctx, &cnt, | |||
| "select count(FileHash) from ObjectBlock, Object, Package where FileHash = ? and"+ | |||
| " ObjectBlock.ObjectID = Object.ObjectID and"+ | |||
| " Object.PackageID = Package.PackageID and"+ | |||
| " Package.State = ?", fileHash, consts.PackageStateNormal) | |||
| if err == sql.ErrNoRows { | |||
| return 0, nil | |||
| } | |||
| return cnt, err | |||
| } | |||
| func (db *ObjectBlockDB) GetBatchObjectBlocks(ctx SQLContext, objectIDs []int64) ([][]string, error) { | |||
| blocks := make([][]string, len(objectIDs)) | |||
| var err error | |||
| for i, objectID := range objectIDs { | |||
| var x []model.ObjectBlock | |||
| sql := "select * from ObjectBlock where ObjectID=?" | |||
| err = db.d.Select(&x, sql, objectID) | |||
| xx := make([]string, len(x)) | |||
| for ii := 0; ii < len(x); ii++ { | |||
| xx[x[ii].Index] = x[ii].FileHash | |||
| } | |||
| blocks[i] = xx | |||
| } | |||
| return blocks, err | |||
| } | |||
| func (db *ObjectBlockDB) GetBatchBlocksNodes(ctx SQLContext, hashs [][]string) ([][][]int64, error) { | |||
| nodes := make([][][]int64, len(hashs)) | |||
| var err error | |||
| for i, hs := range hashs { | |||
| fileNodes := make([][]int64, len(hs)) | |||
| for j, h := range hs { | |||
| var x []model.Node | |||
| err = sqlx.Select(ctx, &x, | |||
| "select Node.* from Cache, Node where"+ | |||
| " Cache.FileHash=? and Cache.NodeID = Node.NodeID and Cache.State=?", h, consts.CacheStatePinned) | |||
| xx := make([]int64, len(x)) | |||
| for ii := 0; ii < len(x); ii++ { | |||
| xx[ii] = x[ii].NodeID | |||
| } | |||
| fileNodes[j] = xx | |||
| } | |||
| nodes[i] = fileNodes | |||
| } | |||
| return nodes, err | |||
| } | |||
| func (db *ObjectBlockDB) GetWithNodeIDInPackage(ctx SQLContext, packageID int64) ([]models.ObjectECData, error) { | |||
| var objs []model.Object | |||
| err := sqlx.Select(ctx, &objs, "select * from Object where PackageID = ? order by ObjectID asc", packageID) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("query objectIDs: %w", err) | |||
| } | |||
| rets := make([]models.ObjectECData, 0, len(objs)) | |||
| for _, obj := range objs { | |||
| var tmpRets []struct { | |||
| Index int `db:"Index"` | |||
| FileHash string `db:"FileHash"` | |||
| NodeIDs *string `db:"NodeIDs"` | |||
| } | |||
| err := sqlx.Select(ctx, | |||
| &tmpRets, | |||
| "select ObjectBlock.Index, ObjectBlock.FileHash, group_concat(NodeID) as NodeIDs from ObjectBlock"+ | |||
| " left join Cache on ObjectBlock.FileHash = Cache.FileHash"+ | |||
| " where ObjectID = ? group by ObjectBlock.Index, ObjectBlock.FileHash", | |||
| obj.ObjectID, | |||
| ) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| blocks := make([]models.ObjectBlockData, 0, len(tmpRets)) | |||
| for _, tmp := range tmpRets { | |||
| var block models.ObjectBlockData | |||
| block.Index = tmp.Index | |||
| block.FileHash = tmp.FileHash | |||
| if tmp.NodeIDs != nil { | |||
| block.NodeIDs = splitIDStringUnsafe(*tmp.NodeIDs) | |||
| } | |||
| blocks = append(blocks, block) | |||
| } | |||
| rets = append(rets, models.NewObjectECData(obj, blocks)) | |||
| } | |||
| return rets, nil | |||
| } | |||
| @@ -0,0 +1,132 @@ | |||
| package db | |||
| import ( | |||
| "database/sql" | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/consts" | |||
| "gitlink.org.cn/cloudream/storage-common/models" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type ObjectRepDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) ObjectRep() *ObjectRepDB { | |||
| return &ObjectRepDB{DB: db} | |||
| } | |||
| // GetObjectRep 查询对象副本表 | |||
| func (db *ObjectRepDB) GetByID(ctx SQLContext, objectID int64) (model.ObjectRep, error) { | |||
| var ret model.ObjectRep | |||
| err := sqlx.Get(ctx, &ret, "select * from ObjectRep where ObjectID = ?", objectID) | |||
| return ret, err | |||
| } | |||
| func (db *ObjectRepDB) Create(ctx SQLContext, objectID int64, fileHash string) error { | |||
| _, err := ctx.Exec("insert into ObjectRep(ObjectID, FileHash) values(?,?)", objectID, fileHash) | |||
| return err | |||
| } | |||
| func (db *ObjectRepDB) Update(ctx SQLContext, objectID int64, fileHash string) (int64, error) { | |||
| ret, err := ctx.Exec("update ObjectRep set FileHash = ? where ObjectID = ?", fileHash, objectID) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| cnt, err := ret.RowsAffected() | |||
| if err != nil { | |||
| return 0, fmt.Errorf("get affected rows failed, err: %w", err) | |||
| } | |||
| return cnt, nil | |||
| } | |||
| func (db *ObjectRepDB) Delete(ctx SQLContext, objectID int64) error { | |||
| _, err := ctx.Exec("delete from ObjectRep where ObjectID = ?", objectID) | |||
| return err | |||
| } | |||
| func (db *ObjectRepDB) DeleteInPackage(ctx SQLContext, packageID int64) error { | |||
| _, err := ctx.Exec("delete ObjectRep from ObjectRep inner join Object on ObjectRep.ObjectID = Object.ObjectID where PackageID = ?", packageID) | |||
| return err | |||
| } | |||
| func (db *ObjectRepDB) GetFileMaxRepCount(ctx SQLContext, fileHash string) (int, error) { | |||
| var maxRepCnt *int | |||
| err := sqlx.Get(ctx, &maxRepCnt, | |||
| "select json_extract(Redundancy, '$.info.repCount') from ObjectRep, Object, Package where FileHash = ? and"+ | |||
| " ObjectRep.ObjectID = Object.ObjectID and"+ | |||
| " Object.PackageID = Package.PackageID and"+ | |||
| " Package.State = ?", fileHash, consts.PackageStateNormal) | |||
| if err == sql.ErrNoRows { | |||
| return 0, nil | |||
| } | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| if maxRepCnt == nil { | |||
| return 0, nil | |||
| } | |||
| return *maxRepCnt, err | |||
| } | |||
| func (db *ObjectRepDB) GetWithNodeIDInPackage(ctx SQLContext, packageID int64) ([]models.ObjectRepData, error) { | |||
| var tmpRets []struct { | |||
| model.Object | |||
| FileHash *string `db:"FileHash"` | |||
| NodeIDs *string `db:"NodeIDs"` | |||
| } | |||
| err := sqlx.Select(ctx, | |||
| &tmpRets, | |||
| "select Object.*, ObjectRep.FileHash, group_concat(NodeID) as NodeIDs from Object"+ | |||
| " left join ObjectRep on Object.ObjectID = ObjectRep.ObjectID"+ | |||
| " left join Cache on ObjectRep.FileHash = Cache.FileHash"+ | |||
| " where PackageID = ? group by Object.ObjectID order by Object.ObjectID asc", | |||
| packageID, | |||
| ) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| rets := make([]models.ObjectRepData, 0, len(tmpRets)) | |||
| for _, tmp := range tmpRets { | |||
| var repData models.ObjectRepData | |||
| repData.Object = tmp.Object | |||
| if tmp.FileHash != nil { | |||
| repData.FileHash = *tmp.FileHash | |||
| } | |||
| if tmp.NodeIDs != nil { | |||
| repData.NodeIDs = splitIDStringUnsafe(*tmp.NodeIDs) | |||
| } | |||
| rets = append(rets, repData) | |||
| } | |||
| return rets, nil | |||
| } | |||
| // 按逗号切割字符串,并将每一个部分解析为一个int64的ID。 | |||
| // 注:需要外部保证分隔的每一个部分都是正确的10进制数字格式 | |||
| func splitIDStringUnsafe(idStr string) []int64 { | |||
| idStrs := strings.Split(idStr, ",") | |||
| ids := make([]int64, 0, len(idStrs)) | |||
| for _, str := range idStrs { | |||
| // 假设传入的ID是正确的数字格式 | |||
| id, _ := strconv.ParseInt(str, 10, 64) | |||
| ids = append(ids, id) | |||
| } | |||
| return ids | |||
| } | |||
| @@ -0,0 +1,170 @@ | |||
| package db | |||
| import ( | |||
| "database/sql" | |||
| "errors" | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||
| "gitlink.org.cn/cloudream/storage-common/consts" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type PackageDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Package() *PackageDB { | |||
| return &PackageDB{DB: db} | |||
| } | |||
| func (db *PackageDB) GetByID(ctx SQLContext, packageID int64) (model.Package, error) { | |||
| var ret model.Package | |||
| err := sqlx.Get(ctx, &ret, "select * from Package where PackageID = ?", packageID) | |||
| return ret, err | |||
| } | |||
| func (db *PackageDB) GetByName(ctx SQLContext, bucketID int64, name string) (model.Package, error) { | |||
| var ret model.Package | |||
| err := sqlx.Get(ctx, &ret, "select * from Package where BucketID = ? and Name = ?", bucketID, name) | |||
| return ret, err | |||
| } | |||
| func (*PackageDB) BatchGetAllPackageIDs(ctx SQLContext, start int, count int) ([]int64, error) { | |||
| var ret []int64 | |||
| err := sqlx.Select(ctx, &ret, "select PackageID from Package limit ?, ?", start, count) | |||
| return ret, err | |||
| } | |||
| func (db *PackageDB) GetBucketPackages(ctx SQLContext, userID int64, bucketID int64) ([]model.Package, error) { | |||
| var ret []model.Package | |||
| err := sqlx.Select(ctx, &ret, "select Package.* from UserBucket, Package where UserID = ? and UserBucket.BucketID = ? and UserBucket.BucketID = Package.BucketID", userID, bucketID) | |||
| return ret, err | |||
| } | |||
| // IsAvailable 判断一个用户是否拥有指定对象 | |||
| func (db *PackageDB) IsAvailable(ctx SQLContext, userID int64, packageID int64) (bool, error) { | |||
| var objID int64 | |||
| // 先根据PackageID找到Package,然后判断此Package所在的Bucket是不是归此用户所有 | |||
| err := sqlx.Get(ctx, &objID, | |||
| "select Package.PackageID from Package, UserBucket where "+ | |||
| "Package.PackageID = ? and "+ | |||
| "Package.BucketID = UserBucket.BucketID and "+ | |||
| "UserBucket.UserID = ?", | |||
| packageID, userID) | |||
| if err == sql.ErrNoRows { | |||
| return false, nil | |||
| } | |||
| if err != nil { | |||
| return false, fmt.Errorf("find package failed, err: %w", err) | |||
| } | |||
| return true, nil | |||
| } | |||
| // GetUserPackage 获得Package,如果用户没有权限访问,则不会获得结果 | |||
| func (db *PackageDB) GetUserPackage(ctx SQLContext, userID int64, packageID int64) (model.Package, error) { | |||
| var ret model.Package | |||
| err := sqlx.Get(ctx, &ret, | |||
| "select Package.* from Package, UserBucket where"+ | |||
| " Package.PackageID = ? and"+ | |||
| " Package.BucketID = UserBucket.BucketID and"+ | |||
| " UserBucket.UserID = ?", | |||
| packageID, userID) | |||
| return ret, err | |||
| } | |||
| func (db *PackageDB) Create(ctx SQLContext, bucketID int64, name string, redundancy models.TypedRedundancyInfo) (int64, error) { | |||
| // 根据packagename和bucketid查询,若不存在则插入,若存在则返回错误 | |||
| var packageID int64 | |||
| err := sqlx.Get(ctx, &packageID, "select PackageID from Package where Name = ? AND BucketID = ?", name, bucketID) | |||
| // 无错误代表存在记录 | |||
| if err == nil { | |||
| return 0, fmt.Errorf("package with given Name and BucketID already exists") | |||
| } | |||
| // 错误不是记录不存在 | |||
| if !errors.Is(err, sql.ErrNoRows) { | |||
| return 0, fmt.Errorf("query Package by PackageName and BucketID failed, err: %w", err) | |||
| } | |||
| redundancyJSON, err := serder.ObjectToJSON(redundancy) | |||
| if err != nil { | |||
| return 0, fmt.Errorf("redundancy to json: %w", err) | |||
| } | |||
| sql := "insert into Package(Name, BucketID, State, Redundancy) values(?,?,?,?)" | |||
| r, err := ctx.Exec(sql, name, bucketID, consts.PackageStateNormal, redundancyJSON) | |||
| if err != nil { | |||
| return 0, fmt.Errorf("insert package failed, err: %w", err) | |||
| } | |||
| packageID, err = r.LastInsertId() | |||
| if err != nil { | |||
| return 0, fmt.Errorf("get id of inserted package failed, err: %w", err) | |||
| } | |||
| return packageID, nil | |||
| } | |||
| // SoftDelete 设置一个对象被删除,并将相关数据删除 | |||
| func (db *PackageDB) SoftDelete(ctx SQLContext, packageID int64) error { | |||
| obj, err := db.GetByID(ctx, packageID) | |||
| if err != nil { | |||
| return fmt.Errorf("get package failed, err: %w", err) | |||
| } | |||
| // 不是正常状态的Package,则不删除 | |||
| // TODO 未来可能有其他状态 | |||
| if obj.State != consts.PackageStateNormal { | |||
| return nil | |||
| } | |||
| err = db.ChangeState(ctx, packageID, consts.PackageStateDeleted) | |||
| if err != nil { | |||
| return fmt.Errorf("change package state failed, err: %w", err) | |||
| } | |||
| if obj.Redundancy.IsRepInfo() { | |||
| err = db.ObjectRep().DeleteInPackage(ctx, packageID) | |||
| if err != nil { | |||
| return fmt.Errorf("delete from object rep failed, err: %w", err) | |||
| } | |||
| } else { | |||
| err = db.ObjectBlock().DeleteInPackage(ctx, packageID) | |||
| if err != nil { | |||
| return fmt.Errorf("delete from object rep failed, err: %w", err) | |||
| } | |||
| } | |||
| if err := db.Object().DeleteInPackage(ctx, packageID); err != nil { | |||
| return fmt.Errorf("deleting objects in package: %w", err) | |||
| } | |||
| _, err = db.StoragePackage().SetAllPackageDeleted(ctx, packageID) | |||
| if err != nil { | |||
| return fmt.Errorf("set storage package deleted failed, err: %w", err) | |||
| } | |||
| return nil | |||
| } | |||
| // DeleteUnused 删除一个已经是Deleted状态,且不再被使用的对象。目前可能被使用的地方只有StoragePackage | |||
| func (PackageDB) DeleteUnused(ctx SQLContext, packageID int64) error { | |||
| _, err := ctx.Exec("delete from Package where PackageID = ? and State = ? and "+ | |||
| "not exists(select StorageID from StoragePackage where PackageID = ?)", | |||
| packageID, | |||
| consts.PackageStateDeleted, | |||
| packageID, | |||
| ) | |||
| return err | |||
| } | |||
| func (*PackageDB) ChangeState(ctx SQLContext, packageID int64, state string) error { | |||
| _, err := ctx.Exec("update Package set State = ? where PackageID = ?", state, packageID) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,64 @@ | |||
| package db | |||
| import ( | |||
| "database/sql" | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type StorageDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) Storage() *StorageDB { | |||
| return &StorageDB{DB: db} | |||
| } | |||
| func (db *StorageDB) GetByID(ctx SQLContext, stgID int64) (model.Storage, error) { | |||
| var stg model.Storage | |||
| err := sqlx.Get(ctx, &stg, "select * from Storage where StorageID = ?", stgID) | |||
| return stg, err | |||
| } | |||
| func (db *StorageDB) BatchGetAllStorageIDs(ctx SQLContext, start int, count int) ([]int64, error) { | |||
| var ret []int64 | |||
| err := sqlx.Select(ctx, &ret, "select StorageID from Storage limit ?, ?", start, count) | |||
| return ret, err | |||
| } | |||
| func (db *StorageDB) IsAvailable(ctx SQLContext, userID int64, storageID int64) (bool, error) { | |||
| var stgID int64 | |||
| err := sqlx.Get(ctx, &stgID, | |||
| "select Storage.StorageID from Storage, UserStorage where"+ | |||
| " Storage.StorageID = ? and"+ | |||
| " Storage.StorageID = UserStorage.StorageID and"+ | |||
| " UserStorage.UserID = ?", | |||
| storageID, userID) | |||
| if err == sql.ErrNoRows { | |||
| return false, nil | |||
| } | |||
| if err != nil { | |||
| return false, fmt.Errorf("find storage failed, err: %w", err) | |||
| } | |||
| return true, nil | |||
| } | |||
| func (db *StorageDB) GetUserStorage(ctx SQLContext, userID int64, storageID int64) (model.Storage, error) { | |||
| var stg model.Storage | |||
| err := sqlx.Get(ctx, &stg, | |||
| "select Storage.* from UserStorage, Storage where UserID = ? and UserStorage.StorageID = ? and UserStorage.StorageID = Storage.StorageID", | |||
| userID, | |||
| storageID) | |||
| return stg, err | |||
| } | |||
| func (db *StorageDB) ChangeState(ctx SQLContext, storageID int64, state string) error { | |||
| _, err := ctx.Exec("update Storage set State = ? where StorageID = ?", state, storageID) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,116 @@ | |||
| package db | |||
| import ( | |||
| "fmt" | |||
| "github.com/jmoiron/sqlx" | |||
| "gitlink.org.cn/cloudream/storage-common/consts" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type StoragePackageDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) StoragePackage() *StoragePackageDB { | |||
| return &StoragePackageDB{DB: db} | |||
| } | |||
| func (*StoragePackageDB) Get(ctx SQLContext, storageID int64, packageID int64, userID int64) (model.StoragePackage, error) { | |||
| var ret model.StoragePackage | |||
| err := sqlx.Get(ctx, &ret, "select * from StoragePackage where StorageID = ? and PackageID = ? and UserID = ?", storageID, packageID, userID) | |||
| return ret, err | |||
| } | |||
| func (*StoragePackageDB) GetAllByStorageAndPackageID(ctx SQLContext, storageID int64, packageID int64) ([]model.StoragePackage, error) { | |||
| var ret []model.StoragePackage | |||
| err := sqlx.Select(ctx, &ret, "select * from StoragePackage where StorageID = ? and PackageID = ?", storageID, packageID) | |||
| return ret, err | |||
| } | |||
| func (*StoragePackageDB) GetAllByStorageID(ctx SQLContext, storageID int64) ([]model.StoragePackage, error) { | |||
| var ret []model.StoragePackage | |||
| err := sqlx.Select(ctx, &ret, "select * from StoragePackage where StorageID = ?", storageID) | |||
| return ret, err | |||
| } | |||
| func (*StoragePackageDB) LoadPackage(ctx SQLContext, packageID int64, storageID int64, userID int64) error { | |||
| _, err := ctx.Exec("insert into StoragePackage values(?,?,?,?)", packageID, storageID, userID, consts.StoragePackageStateNormal) | |||
| return err | |||
| } | |||
| func (*StoragePackageDB) ChangeState(ctx SQLContext, storageID int64, packageID int64, userID int64, state string) error { | |||
| _, err := ctx.Exec("update StoragePackage set State = ? where StorageID = ? and PackageID = ? and UserID = ?", state, storageID, packageID, userID) | |||
| return err | |||
| } | |||
| // SetStateNormal 将状态设置为Normal,如果记录状态是Deleted,则不进行操作 | |||
| func (*StoragePackageDB) SetStateNormal(ctx SQLContext, storageID int64, packageID int64, userID int64) error { | |||
| _, err := ctx.Exec("update StoragePackage set State = ? where StorageID = ? and PackageID = ? and UserID = ? and State <> ?", | |||
| consts.StoragePackageStateNormal, | |||
| storageID, | |||
| packageID, | |||
| userID, | |||
| consts.StoragePackageStateDeleted, | |||
| ) | |||
| return err | |||
| } | |||
| func (*StoragePackageDB) SetAllPackageState(ctx SQLContext, packageID int64, state string) (int64, error) { | |||
| ret, err := ctx.Exec( | |||
| "update StoragePackage set State = ? where PackageID = ?", | |||
| state, | |||
| packageID, | |||
| ) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| cnt, err := ret.RowsAffected() | |||
| if err != nil { | |||
| return 0, fmt.Errorf("get affected rows failed, err: %w", err) | |||
| } | |||
| return cnt, nil | |||
| } | |||
| // SetAllPackageOutdated 将Storage中指定对象设置为已过期。 | |||
| // 注:只会设置Normal状态的对象 | |||
| func (*StoragePackageDB) SetAllPackageOutdated(ctx SQLContext, packageID int64) (int64, error) { | |||
| ret, err := ctx.Exec( | |||
| "update StoragePackage set State = ? where State = ? and PackageID = ?", | |||
| consts.StoragePackageStateOutdated, | |||
| consts.StoragePackageStateNormal, | |||
| packageID, | |||
| ) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| cnt, err := ret.RowsAffected() | |||
| if err != nil { | |||
| return 0, fmt.Errorf("get affected rows failed, err: %w", err) | |||
| } | |||
| return cnt, nil | |||
| } | |||
| func (db *StoragePackageDB) SetAllPackageDeleted(ctx SQLContext, packageID int64) (int64, error) { | |||
| return db.SetAllPackageState(ctx, packageID, consts.StoragePackageStateDeleted) | |||
| } | |||
| func (*StoragePackageDB) Delete(ctx SQLContext, storageID int64, packageID int64, userID int64) error { | |||
| _, err := ctx.Exec("delete from StoragePackage where StorageID = ? and PackageID = ? and UserID = ?", storageID, packageID, userID) | |||
| return err | |||
| } | |||
| // FindPackageStorages 查询存储了指定对象的Storage | |||
| func (*StoragePackageDB) FindPackageStorages(ctx SQLContext, packageID int64) ([]model.Storage, error) { | |||
| var ret []model.Storage | |||
| err := sqlx.Select(ctx, &ret, | |||
| "select Storage.* from StoragePackage, Storage where PackageID = ? and"+ | |||
| " StoragePackage.StorageID = Storage.StorageID", | |||
| packageID, | |||
| ) | |||
| return ret, err | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package db | |||
| type UserBucketDB struct { | |||
| *DB | |||
| } | |||
| func (db *DB) UserBucket() *UserBucketDB { | |||
| return &UserBucketDB{DB: db} | |||
| } | |||
| func (*UserBucketDB) Create(ctx SQLContext, userID int64, bucketID int64) error { | |||
| _, err := ctx.Exec("insert into UserBucket(UserID,BucketID) values(?,?)", userID, bucketID) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,217 @@ | |||
| package lockprovider | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| mylo "gitlink.org.cn/cloudream/common/utils/lo" | |||
| ) | |||
| const ( | |||
| IPFSLockPathPrefix = "IPFS" | |||
| IPFS_SET_READ_LOCK = "SetRead" | |||
| IPFS_SET_WRITE_LOCK = "SetWrite" | |||
| IPFS_SET_CREATE_LOCK = "SetCreate" | |||
| IPFS_ELEMENT_READ_LOCK = "ElementRead" | |||
| IPFS_ELEMENT_WRITE_LOCK = "ElementWrite" | |||
| IPFS_NODE_ID_PATH_INDEX = 1 | |||
| ) | |||
| type IPFSLock struct { | |||
| nodeLocks map[string]*IPFSNodeLock | |||
| dummyLock *IPFSNodeLock | |||
| } | |||
| func NewIPFSLock() *IPFSLock { | |||
| return &IPFSLock{ | |||
| nodeLocks: make(map[string]*IPFSNodeLock), | |||
| dummyLock: NewIPFSNodeLock(), | |||
| } | |||
| } | |||
| // CanLock 判断这个锁能否锁定成功 | |||
| func (l *IPFSLock) CanLock(lock distlock.Lock) error { | |||
| nodeLock, ok := l.nodeLocks[lock.Path[IPFS_NODE_ID_PATH_INDEX]] | |||
| if !ok { | |||
| // 不能直接返回nil,因为如果锁数据的格式不对,也不能获取锁。 | |||
| // 这里使用一个空Provider来进行检查。 | |||
| return l.dummyLock.CanLock(lock) | |||
| } | |||
| return nodeLock.CanLock(lock) | |||
| } | |||
| // 锁定。在内部可以不用判断能否加锁,外部需要保证调用此函数前调用了CanLock进行检查 | |||
| func (l *IPFSLock) Lock(reqID string, lock distlock.Lock) error { | |||
| nodeID := lock.Path[IPFS_NODE_ID_PATH_INDEX] | |||
| nodeLock, ok := l.nodeLocks[nodeID] | |||
| if !ok { | |||
| nodeLock = NewIPFSNodeLock() | |||
| l.nodeLocks[nodeID] = nodeLock | |||
| } | |||
| return nodeLock.Lock(reqID, lock) | |||
| } | |||
| // 解锁 | |||
| func (l *IPFSLock) Unlock(reqID string, lock distlock.Lock) error { | |||
| nodeID := lock.Path[IPFS_NODE_ID_PATH_INDEX] | |||
| nodeLock, ok := l.nodeLocks[nodeID] | |||
| if !ok { | |||
| return nil | |||
| } | |||
| return nodeLock.Unlock(reqID, lock) | |||
| } | |||
| // GetTargetString 将锁对象序列化为字符串,方便存储到ETCD | |||
| func (l *IPFSLock) GetTargetString(target any) (string, error) { | |||
| tar := target.(StringLockTarget) | |||
| return StringLockTargetToString(&tar) | |||
| } | |||
| // ParseTargetString 解析字符串格式的锁对象数据 | |||
| func (l *IPFSLock) ParseTargetString(targetStr string) (any, error) { | |||
| return StringLockTargetFromString(targetStr) | |||
| } | |||
| // Clear 清除内部所有状态 | |||
| func (l *IPFSLock) Clear() { | |||
| l.nodeLocks = make(map[string]*IPFSNodeLock) | |||
| } | |||
| type ipfsElementLock struct { | |||
| target StringLockTarget | |||
| requestIDs []string | |||
| } | |||
| type IPFSNodeLock struct { | |||
| setReadReqIDs []string | |||
| setWriteReqIDs []string | |||
| setCreateReqIDs []string | |||
| elementReadLocks []*ipfsElementLock | |||
| elementWriteLocks []*ipfsElementLock | |||
| lockCompatibilityTable *LockCompatibilityTable | |||
| } | |||
| func NewIPFSNodeLock() *IPFSNodeLock { | |||
| compTable := &LockCompatibilityTable{} | |||
| ipfsLock := IPFSNodeLock{ | |||
| lockCompatibilityTable: compTable, | |||
| } | |||
| compTable. | |||
| Column(IPFS_ELEMENT_READ_LOCK, func() bool { return len(ipfsLock.elementReadLocks) > 0 }). | |||
| Column(IPFS_ELEMENT_WRITE_LOCK, func() bool { return len(ipfsLock.elementWriteLocks) > 0 }). | |||
| Column(IPFS_SET_READ_LOCK, func() bool { return len(ipfsLock.setReadReqIDs) > 0 }). | |||
| Column(IPFS_SET_WRITE_LOCK, func() bool { return len(ipfsLock.setWriteReqIDs) > 0 }). | |||
| Column(IPFS_SET_CREATE_LOCK, func() bool { return len(ipfsLock.setCreateReqIDs) > 0 }) | |||
| comp := LockCompatible() | |||
| uncp := LockUncompatible() | |||
| trgt := LockSpecial(func(lock distlock.Lock, testLockName string) bool { | |||
| strTar := lock.Target.(StringLockTarget) | |||
| if testLockName == IPFS_ELEMENT_READ_LOCK { | |||
| // 如果没有任何锁的锁对象与当前的锁对象冲突,那么这个锁可以加 | |||
| return lo.NoneBy(ipfsLock.elementReadLocks, func(other *ipfsElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| } | |||
| return lo.NoneBy(ipfsLock.elementWriteLocks, func(other *ipfsElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| }) | |||
| compTable.MustRow(comp, trgt, comp, uncp, comp) | |||
| compTable.MustRow(trgt, trgt, uncp, uncp, uncp) | |||
| compTable.MustRow(comp, uncp, comp, uncp, uncp) | |||
| compTable.MustRow(uncp, uncp, uncp, uncp, uncp) | |||
| compTable.MustRow(comp, uncp, uncp, uncp, comp) | |||
| return &ipfsLock | |||
| } | |||
| // CanLock 判断这个锁能否锁定成功 | |||
| func (l *IPFSNodeLock) CanLock(lock distlock.Lock) error { | |||
| return l.lockCompatibilityTable.Test(lock) | |||
| } | |||
| // 锁定 | |||
| func (l *IPFSNodeLock) Lock(reqID string, lock distlock.Lock) error { | |||
| switch lock.Name { | |||
| case IPFS_SET_READ_LOCK: | |||
| l.setReadReqIDs = append(l.setReadReqIDs, reqID) | |||
| case IPFS_SET_WRITE_LOCK: | |||
| l.setWriteReqIDs = append(l.setWriteReqIDs, reqID) | |||
| case IPFS_SET_CREATE_LOCK: | |||
| l.setCreateReqIDs = append(l.setCreateReqIDs, reqID) | |||
| case IPFS_ELEMENT_READ_LOCK: | |||
| l.elementReadLocks = l.addElementLock(lock, l.elementReadLocks, reqID) | |||
| case IPFS_ELEMENT_WRITE_LOCK: | |||
| l.elementWriteLocks = l.addElementLock(lock, l.elementWriteLocks, reqID) | |||
| default: | |||
| return fmt.Errorf("unknow lock name: %s", lock.Name) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *IPFSNodeLock) addElementLock(lock distlock.Lock, locks []*ipfsElementLock, reqID string) []*ipfsElementLock { | |||
| strTarget := lock.Target.(StringLockTarget) | |||
| lck, ok := lo.Find(locks, func(l *ipfsElementLock) bool { return strTarget.IsConflict(&l.target) }) | |||
| if !ok { | |||
| lck = &ipfsElementLock{ | |||
| target: strTarget, | |||
| } | |||
| locks = append(locks, lck) | |||
| } | |||
| lck.requestIDs = append(lck.requestIDs, reqID) | |||
| return locks | |||
| } | |||
| // 解锁 | |||
| func (l *IPFSNodeLock) Unlock(reqID string, lock distlock.Lock) error { | |||
| switch lock.Name { | |||
| case IPFS_SET_READ_LOCK: | |||
| l.setReadReqIDs = mylo.Remove(l.setReadReqIDs, reqID) | |||
| case IPFS_SET_WRITE_LOCK: | |||
| l.setWriteReqIDs = mylo.Remove(l.setWriteReqIDs, reqID) | |||
| case IPFS_SET_CREATE_LOCK: | |||
| l.setCreateReqIDs = mylo.Remove(l.setCreateReqIDs, reqID) | |||
| case IPFS_ELEMENT_READ_LOCK: | |||
| l.elementReadLocks = l.removeElementLock(lock, l.elementReadLocks, reqID) | |||
| case IPFS_ELEMENT_WRITE_LOCK: | |||
| l.elementWriteLocks = l.removeElementLock(lock, l.elementWriteLocks, reqID) | |||
| default: | |||
| return fmt.Errorf("unknow lock name: %s", lock.Name) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *IPFSNodeLock) removeElementLock(lock distlock.Lock, locks []*ipfsElementLock, reqID string) []*ipfsElementLock { | |||
| strTarget := lock.Target.(StringLockTarget) | |||
| lck, index, ok := lo.FindIndexOf(locks, func(l *ipfsElementLock) bool { return strTarget.IsConflict(&l.target) }) | |||
| if !ok { | |||
| return locks | |||
| } | |||
| lck.requestIDs = mylo.Remove(lck.requestIDs, reqID) | |||
| if len(lck.requestIDs) == 0 { | |||
| locks = mylo.RemoveAt(locks, index) | |||
| } | |||
| return locks | |||
| } | |||
| @@ -0,0 +1,113 @@ | |||
| package lockprovider | |||
| import ( | |||
| "testing" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| ) | |||
| func Test_IPFSLock(t *testing.T) { | |||
| cases := []struct { | |||
| title string | |||
| initLocks []distlock.Lock | |||
| doLock distlock.Lock | |||
| wantOK bool | |||
| }{ | |||
| { | |||
| title: "同节点,同一个Read锁", | |||
| initLocks: []distlock.Lock{ | |||
| { | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_SET_READ_LOCK, | |||
| }, | |||
| }, | |||
| doLock: distlock.Lock{ | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_SET_READ_LOCK, | |||
| }, | |||
| wantOK: true, | |||
| }, | |||
| { | |||
| title: "同节点,同一个Write锁", | |||
| initLocks: []distlock.Lock{ | |||
| { | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_SET_WRITE_LOCK, | |||
| }, | |||
| }, | |||
| doLock: distlock.Lock{ | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_SET_WRITE_LOCK, | |||
| }, | |||
| wantOK: false, | |||
| }, | |||
| { | |||
| title: "不同节点,同一个Write锁", | |||
| initLocks: []distlock.Lock{ | |||
| { | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_SET_WRITE_LOCK, | |||
| }, | |||
| }, | |||
| doLock: distlock.Lock{ | |||
| Path: []string{IPFSLockPathPrefix, "node2"}, | |||
| Name: IPFS_SET_WRITE_LOCK, | |||
| }, | |||
| wantOK: true, | |||
| }, | |||
| { | |||
| title: "相同对象的Read、Write锁", | |||
| initLocks: []distlock.Lock{ | |||
| { | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_ELEMENT_WRITE_LOCK, | |||
| Target: *NewStringLockTarget(), | |||
| }, | |||
| }, | |||
| doLock: distlock.Lock{ | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_ELEMENT_WRITE_LOCK, | |||
| Target: *NewStringLockTarget(), | |||
| }, | |||
| wantOK: false, | |||
| }, | |||
| } | |||
| for _, ca := range cases { | |||
| Convey(ca.title, t, func() { | |||
| ipfsLock := NewIPFSLock() | |||
| for _, l := range ca.initLocks { | |||
| ipfsLock.Lock("req1", l) | |||
| } | |||
| err := ipfsLock.CanLock(ca.doLock) | |||
| if ca.wantOK { | |||
| So(err, ShouldBeNil) | |||
| } else { | |||
| So(err, ShouldNotBeNil) | |||
| } | |||
| }) | |||
| } | |||
| Convey("解锁", t, func() { | |||
| ipfsLock := NewIPFSLock() | |||
| lock := distlock.Lock{ | |||
| Path: []string{IPFSLockPathPrefix, "node1"}, | |||
| Name: IPFS_SET_WRITE_LOCK, | |||
| } | |||
| ipfsLock.Lock("req1", lock) | |||
| err := ipfsLock.CanLock(lock) | |||
| So(err, ShouldNotBeNil) | |||
| ipfsLock.Unlock("req1", lock) | |||
| err = ipfsLock.CanLock(lock) | |||
| So(err, ShouldBeNil) | |||
| }) | |||
| } | |||
| @@ -0,0 +1,123 @@ | |||
| package lockprovider | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| ) | |||
| const ( | |||
| LOCK_COMPATIBILITY_COMPATIBLE LockCompatibilityType = "Compatible" | |||
| LOCK_COMPATIBILITY_UNCOMPATIBLE LockCompatibilityType = "Uncompatible" | |||
| LOCK_COMPATIBILITY_SPECIAL LockCompatibilityType = "Special" | |||
| ) | |||
| type HasSuchLockFn = func() bool | |||
| // LockCompatibilitySpecialFn 判断锁与指定的锁名是否兼容 | |||
| type LockCompatibilitySpecialFn func(lock distlock.Lock, testLockName string) bool | |||
| type LockCompatibilityType string | |||
| type LockCompatibility struct { | |||
| Type LockCompatibilityType | |||
| SpecialFn LockCompatibilitySpecialFn | |||
| } | |||
| func LockCompatible() LockCompatibility { | |||
| return LockCompatibility{ | |||
| Type: LOCK_COMPATIBILITY_COMPATIBLE, | |||
| } | |||
| } | |||
| func LockUncompatible() LockCompatibility { | |||
| return LockCompatibility{ | |||
| Type: LOCK_COMPATIBILITY_UNCOMPATIBLE, | |||
| } | |||
| } | |||
| func LockSpecial(specialFn LockCompatibilitySpecialFn) LockCompatibility { | |||
| return LockCompatibility{ | |||
| Type: LOCK_COMPATIBILITY_SPECIAL, | |||
| SpecialFn: specialFn, | |||
| } | |||
| } | |||
| type LockCompatibilityTableRow struct { | |||
| LockName string | |||
| HasSuchLockFn HasSuchLockFn | |||
| Compatibilities []LockCompatibility | |||
| } | |||
| type LockCompatibilityTable struct { | |||
| rows []LockCompatibilityTableRow | |||
| rowIndex int | |||
| } | |||
| func (t *LockCompatibilityTable) Column(lockName string, hasSuchLock HasSuchLockFn) *LockCompatibilityTable { | |||
| t.rows = append(t.rows, LockCompatibilityTableRow{ | |||
| LockName: lockName, | |||
| HasSuchLockFn: hasSuchLock, | |||
| }) | |||
| return t | |||
| } | |||
| func (t *LockCompatibilityTable) MustRow(comps ...LockCompatibility) { | |||
| err := t.Row(comps...) | |||
| if err != nil { | |||
| panic(fmt.Sprintf("build lock compatibility table failed, err: %s", err.Error())) | |||
| } | |||
| } | |||
| func (t *LockCompatibilityTable) Row(comps ...LockCompatibility) error { | |||
| if t.rowIndex >= len(t.rows) { | |||
| return fmt.Errorf("there should be no more rows in the table") | |||
| } | |||
| if len(comps) < len(t.rows) { | |||
| return fmt.Errorf("the columns should equals the rows") | |||
| } | |||
| t.rows[t.rowIndex].Compatibilities = comps | |||
| for i := 0; i < t.rowIndex-1; i++ { | |||
| chkRowCeil := t.rows[t.rowIndex].Compatibilities[i] | |||
| chkColCeil := t.rows[i].Compatibilities[t.rowIndex] | |||
| if chkRowCeil.Type != chkColCeil.Type { | |||
| return fmt.Errorf("value at %d, %d is not equals to at %d, %d", t.rowIndex, i, i, t.rowIndex) | |||
| } | |||
| } | |||
| t.rowIndex++ | |||
| return nil | |||
| } | |||
| func (t *LockCompatibilityTable) Test(lock distlock.Lock) error { | |||
| row, ok := lo.Find(t.rows, func(row LockCompatibilityTableRow) bool { return lock.Name == row.LockName }) | |||
| if !ok { | |||
| return fmt.Errorf("unknow lock name %s", lock.Name) | |||
| } | |||
| for i, c := range row.Compatibilities { | |||
| if c.Type == LOCK_COMPATIBILITY_COMPATIBLE { | |||
| continue | |||
| } | |||
| if c.Type == LOCK_COMPATIBILITY_UNCOMPATIBLE { | |||
| if t.rows[i].HasSuchLockFn() { | |||
| return distlock.NewLockTargetBusyError(t.rows[i].LockName) | |||
| } | |||
| } | |||
| if c.Type == LOCK_COMPATIBILITY_SPECIAL { | |||
| if !c.SpecialFn(lock, t.rows[i].LockName) { | |||
| return distlock.NewLockTargetBusyError(t.rows[i].LockName) | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| package lockprovider | |||
| import ( | |||
| "testing" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| ) | |||
| func Test_LockCompatibilityTable(t *testing.T) { | |||
| Convey("兼容,互斥,特殊比较", t, func() { | |||
| table := LockCompatibilityTable{} | |||
| table. | |||
| Column("l1", func() bool { return true }). | |||
| Column("l2", func() bool { return true }). | |||
| Column("l3", func() bool { return false }) | |||
| comp := LockCompatible() | |||
| uncp := LockUncompatible() | |||
| spcl := LockSpecial(func(lock distlock.Lock, testLockName string) bool { return true }) | |||
| table.Row(comp, comp, comp) | |||
| table.Row(comp, uncp, comp) | |||
| table.Row(comp, comp, spcl) | |||
| err := table.Test(distlock.Lock{ | |||
| Name: "l1", | |||
| }) | |||
| So(err, ShouldBeNil) | |||
| err = table.Test(distlock.Lock{ | |||
| Name: "l2", | |||
| }) | |||
| So(err, ShouldNotBeNil) | |||
| err = table.Test(distlock.Lock{ | |||
| Name: "l3", | |||
| }) | |||
| So(err, ShouldBeNil) | |||
| }) | |||
| } | |||
| @@ -0,0 +1,184 @@ | |||
| package lockprovider | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| mylo "gitlink.org.cn/cloudream/common/utils/lo" | |||
| ) | |||
| const ( | |||
| MetadataLockPathPrefix = "Metadata" | |||
| METADATA_SET_READ_LOCK = "SetRead" | |||
| METADATA_SET_WRITE_LOCK = "SetWrite" | |||
| METADATA_SET_CREATE_LOCK = "SetCreate" | |||
| METADATA_ELEMENT_READ_LOCK = "ElementRead" | |||
| METADATA_ELEMENT_WRITE_LOCK = "ElementWrite" | |||
| METADATA_ELEMENT_CREATE_LOCK = "ElementCreate" | |||
| ) | |||
| type metadataElementLock struct { | |||
| target StringLockTarget | |||
| requestIDs []string | |||
| } | |||
| type MetadataLock struct { | |||
| setReadReqIDs []string | |||
| setWriteReqIDs []string | |||
| setCreateReqIDs []string | |||
| elementReadLocks []*metadataElementLock | |||
| elementWriteLocks []*metadataElementLock | |||
| elementCreateLocks []*metadataElementLock | |||
| lockCompatibilityTable LockCompatibilityTable | |||
| } | |||
| func NewMetadataLock() *MetadataLock { | |||
| metadataLock := MetadataLock{ | |||
| lockCompatibilityTable: LockCompatibilityTable{}, | |||
| } | |||
| compTable := &metadataLock.lockCompatibilityTable | |||
| compTable. | |||
| Column(METADATA_ELEMENT_READ_LOCK, func() bool { return len(metadataLock.elementReadLocks) > 0 }). | |||
| Column(METADATA_ELEMENT_WRITE_LOCK, func() bool { return len(metadataLock.elementWriteLocks) > 0 }). | |||
| Column(METADATA_ELEMENT_CREATE_LOCK, func() bool { return len(metadataLock.elementCreateLocks) > 0 }). | |||
| Column(METADATA_SET_READ_LOCK, func() bool { return len(metadataLock.setReadReqIDs) > 0 }). | |||
| Column(METADATA_SET_WRITE_LOCK, func() bool { return len(metadataLock.setWriteReqIDs) > 0 }). | |||
| Column(METADATA_SET_CREATE_LOCK, func() bool { return len(metadataLock.setCreateReqIDs) > 0 }) | |||
| comp := LockCompatible() | |||
| uncp := LockUncompatible() | |||
| trgt := LockSpecial(func(lock distlock.Lock, testLockName string) bool { | |||
| strTar := lock.Target.(StringLockTarget) | |||
| if testLockName == METADATA_ELEMENT_READ_LOCK { | |||
| // 如果没有任何锁的锁对象与当前的锁对象冲突,那么这个锁可以加 | |||
| return lo.NoneBy(metadataLock.elementReadLocks, func(other *metadataElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| } | |||
| if testLockName == METADATA_ELEMENT_WRITE_LOCK { | |||
| return lo.NoneBy(metadataLock.elementWriteLocks, func(other *metadataElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| } | |||
| return lo.NoneBy(metadataLock.elementCreateLocks, func(other *metadataElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| }) | |||
| compTable.MustRow(comp, trgt, comp, comp, uncp, comp) | |||
| compTable.MustRow(trgt, trgt, comp, uncp, uncp, comp) | |||
| compTable.MustRow(comp, comp, trgt, uncp, uncp, uncp) | |||
| compTable.MustRow(comp, uncp, uncp, comp, uncp, uncp) | |||
| compTable.MustRow(uncp, uncp, uncp, uncp, uncp, uncp) | |||
| compTable.MustRow(comp, comp, uncp, uncp, uncp, uncp) | |||
| return &metadataLock | |||
| } | |||
| // CanLock 判断这个锁能否锁定成功 | |||
| func (l *MetadataLock) CanLock(lock distlock.Lock) error { | |||
| return l.lockCompatibilityTable.Test(lock) | |||
| } | |||
| // 锁定 | |||
| func (l *MetadataLock) Lock(reqID string, lock distlock.Lock) error { | |||
| switch lock.Name { | |||
| case METADATA_SET_READ_LOCK: | |||
| l.setReadReqIDs = append(l.setReadReqIDs, reqID) | |||
| case METADATA_SET_WRITE_LOCK: | |||
| l.setWriteReqIDs = append(l.setWriteReqIDs, reqID) | |||
| case METADATA_SET_CREATE_LOCK: | |||
| l.setCreateReqIDs = append(l.setCreateReqIDs, reqID) | |||
| case METADATA_ELEMENT_READ_LOCK: | |||
| l.elementReadLocks = l.addElementLock(lock, l.elementReadLocks, reqID) | |||
| case METADATA_ELEMENT_WRITE_LOCK: | |||
| l.elementWriteLocks = l.addElementLock(lock, l.elementWriteLocks, reqID) | |||
| case METADATA_ELEMENT_CREATE_LOCK: | |||
| l.elementCreateLocks = l.addElementLock(lock, l.elementCreateLocks, reqID) | |||
| default: | |||
| return fmt.Errorf("unknow lock name: %s", lock.Name) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *MetadataLock) addElementLock(lock distlock.Lock, locks []*metadataElementLock, reqID string) []*metadataElementLock { | |||
| strTarget := lock.Target.(StringLockTarget) | |||
| lck, ok := lo.Find(locks, func(l *metadataElementLock) bool { return strTarget.IsConflict(&l.target) }) | |||
| if !ok { | |||
| lck = &metadataElementLock{ | |||
| target: strTarget, | |||
| } | |||
| locks = append(locks, lck) | |||
| } | |||
| lck.requestIDs = append(lck.requestIDs, reqID) | |||
| return locks | |||
| } | |||
| // 解锁 | |||
| func (l *MetadataLock) Unlock(reqID string, lock distlock.Lock) error { | |||
| switch lock.Name { | |||
| case METADATA_SET_READ_LOCK: | |||
| l.setReadReqIDs = mylo.Remove(l.setReadReqIDs, reqID) | |||
| case METADATA_SET_WRITE_LOCK: | |||
| l.setWriteReqIDs = mylo.Remove(l.setWriteReqIDs, reqID) | |||
| case METADATA_SET_CREATE_LOCK: | |||
| l.setCreateReqIDs = mylo.Remove(l.setCreateReqIDs, reqID) | |||
| case METADATA_ELEMENT_READ_LOCK: | |||
| l.elementReadLocks = l.removeElementLock(lock, l.elementReadLocks, reqID) | |||
| case METADATA_ELEMENT_WRITE_LOCK: | |||
| l.elementWriteLocks = l.removeElementLock(lock, l.elementWriteLocks, reqID) | |||
| case METADATA_ELEMENT_CREATE_LOCK: | |||
| l.elementCreateLocks = l.removeElementLock(lock, l.elementCreateLocks, reqID) | |||
| default: | |||
| return fmt.Errorf("unknow lock name: %s", lock.Name) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *MetadataLock) removeElementLock(lock distlock.Lock, locks []*metadataElementLock, reqID string) []*metadataElementLock { | |||
| strTarget := lock.Target.(StringLockTarget) | |||
| lck, index, ok := lo.FindIndexOf(locks, func(l *metadataElementLock) bool { return strTarget.IsConflict(&l.target) }) | |||
| if !ok { | |||
| return locks | |||
| } | |||
| lck.requestIDs = mylo.Remove(lck.requestIDs, reqID) | |||
| if len(lck.requestIDs) == 0 { | |||
| locks = mylo.RemoveAt(locks, index) | |||
| } | |||
| return locks | |||
| } | |||
| // GetTargetString 将锁对象序列化为字符串,方便存储到ETCD | |||
| func (l *MetadataLock) GetTargetString(target any) (string, error) { | |||
| tar := target.(StringLockTarget) | |||
| return StringLockTargetToString(&tar) | |||
| } | |||
| // ParseTargetString 解析字符串格式的锁对象数据 | |||
| func (l *MetadataLock) ParseTargetString(targetStr string) (any, error) { | |||
| return StringLockTargetFromString(targetStr) | |||
| } | |||
| // Clear 清除内部所有状态 | |||
| func (l *MetadataLock) Clear() { | |||
| l.setReadReqIDs = nil | |||
| l.setWriteReqIDs = nil | |||
| l.setCreateReqIDs = nil | |||
| l.elementReadLocks = nil | |||
| l.elementWriteLocks = nil | |||
| l.elementCreateLocks = nil | |||
| } | |||
| @@ -0,0 +1,226 @@ | |||
| package lockprovider | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| mylo "gitlink.org.cn/cloudream/common/utils/lo" | |||
| ) | |||
| const ( | |||
| StorageLockPathPrefix = "Storage" | |||
| STORAGE_SET_READ_LOCK = "SetRead" | |||
| STORAGE_SET_WRITE_LOCK = "SetWrite" | |||
| STORAGE_SET_CREATE_LOCK = "SetCreate" | |||
| STORAGE_ELEMENT_READ_LOCK = "ElementRead" | |||
| STORAGE_ELEMENT_WRITE_LOCK = "ElementWrite" | |||
| STORAGE_ELEMENT_CREATE_LOCK = "ElementCreate" | |||
| STORAGE_STORAGE_ID_PATH_INDEX = 1 | |||
| ) | |||
| type StorageLock struct { | |||
| nodeLocks map[string]*StorageNodeLock | |||
| dummyLock *StorageNodeLock | |||
| } | |||
| func NewStorageLock() *StorageLock { | |||
| return &StorageLock{ | |||
| nodeLocks: make(map[string]*StorageNodeLock), | |||
| dummyLock: NewStorageNodeLock(), | |||
| } | |||
| } | |||
| // CanLock 判断这个锁能否锁定成功 | |||
| func (l *StorageLock) CanLock(lock distlock.Lock) error { | |||
| nodeLock, ok := l.nodeLocks[lock.Path[STORAGE_STORAGE_ID_PATH_INDEX]] | |||
| if !ok { | |||
| // 不能直接返回nil,因为如果锁数据的格式不对,也不能获取锁。 | |||
| // 这里使用一个空Provider来进行检查。 | |||
| return l.dummyLock.CanLock(lock) | |||
| } | |||
| return nodeLock.CanLock(lock) | |||
| } | |||
| // 锁定。在内部可以不用判断能否加锁,外部需要保证调用此函数前调用了CanLock进行检查 | |||
| func (l *StorageLock) Lock(reqID string, lock distlock.Lock) error { | |||
| nodeID := lock.Path[STORAGE_STORAGE_ID_PATH_INDEX] | |||
| nodeLock, ok := l.nodeLocks[nodeID] | |||
| if !ok { | |||
| nodeLock = NewStorageNodeLock() | |||
| l.nodeLocks[nodeID] = nodeLock | |||
| } | |||
| return nodeLock.Lock(reqID, lock) | |||
| } | |||
| // 解锁 | |||
| func (l *StorageLock) Unlock(reqID string, lock distlock.Lock) error { | |||
| nodeID := lock.Path[STORAGE_STORAGE_ID_PATH_INDEX] | |||
| nodeLock, ok := l.nodeLocks[nodeID] | |||
| if !ok { | |||
| return nil | |||
| } | |||
| return nodeLock.Unlock(reqID, lock) | |||
| } | |||
| // GetTargetString 将锁对象序列化为字符串,方便存储到ETCD | |||
| func (l *StorageLock) GetTargetString(target any) (string, error) { | |||
| tar := target.(StringLockTarget) | |||
| return StringLockTargetToString(&tar) | |||
| } | |||
| // ParseTargetString 解析字符串格式的锁对象数据 | |||
| func (l *StorageLock) ParseTargetString(targetStr string) (any, error) { | |||
| return StringLockTargetFromString(targetStr) | |||
| } | |||
| // Clear 清除内部所有状态 | |||
| func (l *StorageLock) Clear() { | |||
| l.nodeLocks = make(map[string]*StorageNodeLock) | |||
| } | |||
| type storageElementLock struct { | |||
| target StringLockTarget | |||
| requestIDs []string | |||
| } | |||
| type StorageNodeLock struct { | |||
| setReadReqIDs []string | |||
| setWriteReqIDs []string | |||
| setCreateReqIDs []string | |||
| elementReadLocks []*storageElementLock | |||
| elementWriteLocks []*storageElementLock | |||
| elementCreateLocks []*storageElementLock | |||
| lockCompatibilityTable LockCompatibilityTable | |||
| } | |||
| func NewStorageNodeLock() *StorageNodeLock { | |||
| storageLock := StorageNodeLock{ | |||
| lockCompatibilityTable: LockCompatibilityTable{}, | |||
| } | |||
| compTable := &storageLock.lockCompatibilityTable | |||
| compTable. | |||
| Column(STORAGE_ELEMENT_READ_LOCK, func() bool { return len(storageLock.elementReadLocks) > 0 }). | |||
| Column(STORAGE_ELEMENT_WRITE_LOCK, func() bool { return len(storageLock.elementWriteLocks) > 0 }). | |||
| Column(STORAGE_ELEMENT_CREATE_LOCK, func() bool { return len(storageLock.elementCreateLocks) > 0 }). | |||
| Column(STORAGE_SET_READ_LOCK, func() bool { return len(storageLock.setReadReqIDs) > 0 }). | |||
| Column(STORAGE_SET_WRITE_LOCK, func() bool { return len(storageLock.setWriteReqIDs) > 0 }). | |||
| Column(STORAGE_SET_CREATE_LOCK, func() bool { return len(storageLock.setCreateReqIDs) > 0 }) | |||
| comp := LockCompatible() | |||
| uncp := LockUncompatible() | |||
| trgt := LockSpecial(func(lock distlock.Lock, testLockName string) bool { | |||
| strTar := lock.Target.(StringLockTarget) | |||
| if testLockName == STORAGE_ELEMENT_READ_LOCK { | |||
| // 如果没有任何锁的锁对象与当前的锁对象冲突,那么这个锁可以加 | |||
| return lo.NoneBy(storageLock.elementReadLocks, func(other *storageElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| } | |||
| if testLockName == STORAGE_ELEMENT_WRITE_LOCK { | |||
| return lo.NoneBy(storageLock.elementWriteLocks, func(other *storageElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| } | |||
| return lo.NoneBy(storageLock.elementCreateLocks, func(other *storageElementLock) bool { return strTar.IsConflict(&other.target) }) | |||
| }) | |||
| compTable.MustRow(comp, trgt, comp, comp, uncp, comp) | |||
| compTable.MustRow(trgt, trgt, comp, uncp, uncp, comp) | |||
| compTable.MustRow(comp, comp, trgt, uncp, uncp, uncp) | |||
| compTable.MustRow(comp, uncp, uncp, comp, uncp, uncp) | |||
| compTable.MustRow(uncp, uncp, uncp, uncp, uncp, uncp) | |||
| compTable.MustRow(comp, comp, uncp, uncp, uncp, uncp) | |||
| return &storageLock | |||
| } | |||
| // CanLock 判断这个锁能否锁定成功 | |||
| func (l *StorageNodeLock) CanLock(lock distlock.Lock) error { | |||
| return l.lockCompatibilityTable.Test(lock) | |||
| } | |||
| // 锁定 | |||
| func (l *StorageNodeLock) Lock(reqID string, lock distlock.Lock) error { | |||
| switch lock.Name { | |||
| case STORAGE_SET_READ_LOCK: | |||
| l.setReadReqIDs = append(l.setReadReqIDs, reqID) | |||
| case STORAGE_SET_WRITE_LOCK: | |||
| l.setWriteReqIDs = append(l.setWriteReqIDs, reqID) | |||
| case STORAGE_SET_CREATE_LOCK: | |||
| l.setCreateReqIDs = append(l.setCreateReqIDs, reqID) | |||
| case STORAGE_ELEMENT_READ_LOCK: | |||
| l.elementReadLocks = l.addElementLock(lock, l.elementReadLocks, reqID) | |||
| case STORAGE_ELEMENT_WRITE_LOCK: | |||
| l.elementWriteLocks = l.addElementLock(lock, l.elementWriteLocks, reqID) | |||
| default: | |||
| return fmt.Errorf("unknow lock name: %s", lock.Name) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *StorageNodeLock) addElementLock(lock distlock.Lock, locks []*storageElementLock, reqID string) []*storageElementLock { | |||
| strTarget := lock.Target.(StringLockTarget) | |||
| lck, ok := lo.Find(locks, func(l *storageElementLock) bool { return strTarget.IsConflict(&l.target) }) | |||
| if !ok { | |||
| lck = &storageElementLock{ | |||
| target: strTarget, | |||
| } | |||
| locks = append(locks, lck) | |||
| } | |||
| lck.requestIDs = append(lck.requestIDs, reqID) | |||
| return locks | |||
| } | |||
| // 解锁 | |||
| func (l *StorageNodeLock) Unlock(reqID string, lock distlock.Lock) error { | |||
| switch lock.Name { | |||
| case STORAGE_SET_READ_LOCK: | |||
| l.setReadReqIDs = mylo.Remove(l.setReadReqIDs, reqID) | |||
| case STORAGE_SET_WRITE_LOCK: | |||
| l.setWriteReqIDs = mylo.Remove(l.setWriteReqIDs, reqID) | |||
| case STORAGE_SET_CREATE_LOCK: | |||
| l.setCreateReqIDs = mylo.Remove(l.setCreateReqIDs, reqID) | |||
| case STORAGE_ELEMENT_READ_LOCK: | |||
| l.elementReadLocks = l.removeElementLock(lock, l.elementReadLocks, reqID) | |||
| case STORAGE_ELEMENT_WRITE_LOCK: | |||
| l.elementWriteLocks = l.removeElementLock(lock, l.elementWriteLocks, reqID) | |||
| default: | |||
| return fmt.Errorf("unknow lock name: %s", lock.Name) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *StorageNodeLock) removeElementLock(lock distlock.Lock, locks []*storageElementLock, reqID string) []*storageElementLock { | |||
| strTarget := lock.Target.(StringLockTarget) | |||
| lck, index, ok := lo.FindIndexOf(locks, func(l *storageElementLock) bool { return strTarget.IsConflict(&l.target) }) | |||
| if !ok { | |||
| return locks | |||
| } | |||
| lck.requestIDs = mylo.Remove(lck.requestIDs, reqID) | |||
| if len(lck.requestIDs) == 0 { | |||
| locks = mylo.RemoveAt(locks, index) | |||
| } | |||
| return locks | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| package lockprovider | |||
| import ( | |||
| "fmt" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||
| ) | |||
| type StringLockTarget struct { | |||
| Components []StringLockTargetComponet `json:"components"` | |||
| } | |||
| func NewStringLockTarget() *StringLockTarget { | |||
| return &StringLockTarget{} | |||
| } | |||
| // Add 添加一个Component,并将其内容设置为compValues | |||
| func (t *StringLockTarget) Add(compValues ...any) *StringLockTarget { | |||
| t.Components = append(t.Components, StringLockTargetComponet{ | |||
| Values: lo.Map(compValues, func(val any, index int) string { return fmt.Sprintf("%v", val) }), | |||
| }) | |||
| return t | |||
| } | |||
| // IsConflict 判断两个锁对象是否冲突。注:只有相同的结构的Target才有意义 | |||
| func (t *StringLockTarget) IsConflict(other *StringLockTarget) bool { | |||
| if len(t.Components) != len(other.Components) { | |||
| return false | |||
| } | |||
| if len(t.Components) == 0 { | |||
| return true | |||
| } | |||
| for i := 0; i < len(t.Components); i++ { | |||
| if t.Components[i].IsEquals(&other.Components[i]) { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| type StringLockTargetComponet struct { | |||
| Values []string `json:"values"` | |||
| } | |||
| // IsEquals 判断两个Component是否相同。注:只有相同的结构的Component才有意义 | |||
| func (t *StringLockTargetComponet) IsEquals(other *StringLockTargetComponet) bool { | |||
| if len(t.Values) != len(other.Values) { | |||
| return false | |||
| } | |||
| for i := 0; i < len(t.Values); i++ { | |||
| if t.Values[i] != other.Values[i] { | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| func StringLockTargetToString(target *StringLockTarget) (string, error) { | |||
| data, err := serder.ObjectToJSON(target) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| return string(data), nil | |||
| } | |||
| func StringLockTargetFromString(str string) (StringLockTarget, error) { | |||
| var ret StringLockTarget | |||
| err := serder.JSONToObject([]byte(str), &ret) | |||
| return ret, err | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package lockprovider | |||
| import ( | |||
| "testing" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| ) | |||
| func Test_StringLockTarget(t *testing.T) { | |||
| cases := []struct { | |||
| title string | |||
| target1 *StringLockTarget | |||
| target2 *StringLockTarget | |||
| wantIsConflict bool | |||
| }{ | |||
| { | |||
| title: "没有任何段算冲突", | |||
| target1: NewStringLockTarget(), | |||
| target2: NewStringLockTarget(), | |||
| wantIsConflict: true, | |||
| }, | |||
| { | |||
| title: "有段,但段内为空,算冲突", | |||
| target1: NewStringLockTarget().Add(), | |||
| target2: NewStringLockTarget().Add(), | |||
| wantIsConflict: true, | |||
| }, | |||
| { | |||
| title: "每一段不同才不冲突", | |||
| target1: NewStringLockTarget().Add("a").Add("b"), | |||
| target2: NewStringLockTarget().Add("b").Add("c"), | |||
| wantIsConflict: false, | |||
| }, | |||
| { | |||
| title: "只要有一段相同就冲突", | |||
| target1: NewStringLockTarget().Add("a").Add("b"), | |||
| target2: NewStringLockTarget().Add("a").Add("c"), | |||
| wantIsConflict: true, | |||
| }, | |||
| { | |||
| title: "同段内,只要有一个数据不同就不冲突", | |||
| target1: NewStringLockTarget().Add("a", "b"), | |||
| target2: NewStringLockTarget().Add("b", "b"), | |||
| wantIsConflict: false, | |||
| }, | |||
| { | |||
| title: "同段内,只要每个数据都相同才不冲突", | |||
| target1: NewStringLockTarget().Add("a", "b"), | |||
| target2: NewStringLockTarget().Add("a", "b"), | |||
| wantIsConflict: true, | |||
| }, | |||
| } | |||
| for _, ca := range cases { | |||
| Convey(ca.title, t, func() { | |||
| ret := ca.target1.IsConflict(ca.target2) | |||
| So(ret, ShouldEqual, ca.wantIsConflict) | |||
| }) | |||
| } | |||
| } | |||
| @@ -0,0 +1,64 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "strconv" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type IPFSLockReqBuilder struct { | |||
| *LockRequestBuilder | |||
| } | |||
| func (b *LockRequestBuilder) IPFS() *IPFSLockReqBuilder { | |||
| return &IPFSLockReqBuilder{LockRequestBuilder: b} | |||
| } | |||
| func (b *IPFSLockReqBuilder) ReadOneRep(nodeID int64, fileHash string) *IPFSLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(nodeID), | |||
| Name: lockprovider.IPFS_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(fileHash), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *IPFSLockReqBuilder) WriteOneRep(nodeID int64, fileHash string) *IPFSLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(nodeID), | |||
| Name: lockprovider.IPFS_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(fileHash), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *IPFSLockReqBuilder) ReadAnyRep(nodeID int64) *IPFSLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(nodeID), | |||
| Name: lockprovider.IPFS_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *IPFSLockReqBuilder) WriteAnyRep(nodeID int64) *IPFSLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(nodeID), | |||
| Name: lockprovider.IPFS_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *IPFSLockReqBuilder) CreateAnyRep(nodeID int64) *IPFSLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(nodeID), | |||
| Name: lockprovider.IPFS_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *IPFSLockReqBuilder) makePath(nodeID int64) []string { | |||
| return []string{lockprovider.IPFSLockPathPrefix, strconv.FormatInt(nodeID, 10)} | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock/service" | |||
| mylo "gitlink.org.cn/cloudream/common/utils/lo" | |||
| ) | |||
| type LockRequestBuilder struct { | |||
| locks []distlock.Lock | |||
| } | |||
| func NewBuilder() *LockRequestBuilder { | |||
| return &LockRequestBuilder{} | |||
| } | |||
| func (b *LockRequestBuilder) Build() distlock.LockRequest { | |||
| return distlock.LockRequest{ | |||
| Locks: mylo.ArrayClone(b.locks), | |||
| } | |||
| } | |||
| func (b *LockRequestBuilder) MutexLock(svc *service.Service) (*service.Mutex, error) { | |||
| mutex := service.NewMutex(svc, b.Build()) | |||
| err := mutex.Lock() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return mutex, nil | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataLockReqBuilder struct { | |||
| *LockRequestBuilder | |||
| } | |||
| func (b *LockRequestBuilder) Metadata() *MetadataLockReqBuilder { | |||
| return &MetadataLockReqBuilder{LockRequestBuilder: b} | |||
| } | |||
| func (b *MetadataLockReqBuilder) makePath(tableName string) []string { | |||
| return []string{lockprovider.MetadataLockPathPrefix, tableName} | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataBucketLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) Bucket() *MetadataBucketLockReqBuilder { | |||
| return &MetadataBucketLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataBucketLockReqBuilder) ReadOne(bucketID int64) *MetadataBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Bucket"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(bucketID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataBucketLockReqBuilder) WriteOne(bucketID int64) *MetadataBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Bucket"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(bucketID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataBucketLockReqBuilder) CreateOne(userID int64, bucketName string) *MetadataBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Bucket"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, bucketName), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataBucketLockReqBuilder) ReadAny() *MetadataBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Bucket"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataBucketLockReqBuilder) WriteAny() *MetadataBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Bucket"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataBucketLockReqBuilder) CreateAny() *MetadataBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Bucket"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataCacheLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) Cache() *MetadataCacheLockReqBuilder { | |||
| return &MetadataCacheLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataCacheLockReqBuilder) ReadOne(nodeID int64, fileHash string) *MetadataCacheLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Cache"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(nodeID, fileHash), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataCacheLockReqBuilder) WriteOne(nodeID int64, fileHash string) *MetadataCacheLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Cache"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(nodeID, fileHash), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataCacheLockReqBuilder) CreateOne(nodeID int64, fileHash string) *MetadataCacheLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Cache"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(nodeID, fileHash), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataCacheLockReqBuilder) ReadAny() *MetadataCacheLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Cache"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataCacheLockReqBuilder) WriteAny() *MetadataCacheLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Cache"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataCacheLockReqBuilder) CreateAny() *MetadataCacheLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Cache"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataNodeLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) Node() *MetadataNodeLockReqBuilder { | |||
| return &MetadataNodeLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataNodeLockReqBuilder) ReadOne(nodeID int64) *MetadataNodeLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Node"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(nodeID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataNodeLockReqBuilder) WriteOne(nodeID int64) *MetadataNodeLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Node"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(nodeID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataNodeLockReqBuilder) CreateOne() *MetadataNodeLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Node"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataNodeLockReqBuilder) ReadAny() *MetadataNodeLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Node"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataNodeLockReqBuilder) WriteAny() *MetadataNodeLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Node"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataNodeLockReqBuilder) CreateAny() *MetadataNodeLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Node"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,65 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| // TODO 可以考虑增加基于PackageID的锁,让访问不同Package的Object的操作能并行 | |||
| type MetadataObjectLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) Object() *MetadataObjectLockReqBuilder { | |||
| return &MetadataObjectLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataObjectLockReqBuilder) ReadOne(objectID int64) *MetadataObjectLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Object"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(objectID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectLockReqBuilder) WriteOne(objectID int64) *MetadataObjectLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Object"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(objectID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectLockReqBuilder) CreateOne(bucketID int64, objectName string) *MetadataObjectLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Object"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(bucketID, objectName), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectLockReqBuilder) ReadAny() *MetadataObjectLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Object"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectLockReqBuilder) WriteAny() *MetadataObjectLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Object"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectLockReqBuilder) CreateAny() *MetadataObjectLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Object"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataObjectBlockLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) ObjectBlock() *MetadataObjectBlockLockReqBuilder { | |||
| return &MetadataObjectBlockLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataObjectBlockLockReqBuilder) ReadOne(objectID int) *MetadataObjectBlockLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectBlock"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(objectID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectBlockLockReqBuilder) WriteOne(objectID int) *MetadataObjectBlockLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectBlock"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(objectID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectBlockLockReqBuilder) CreateOne() *MetadataObjectBlockLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectBlock"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectBlockLockReqBuilder) ReadAny() *MetadataObjectBlockLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectBlock"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectBlockLockReqBuilder) WriteAny() *MetadataObjectBlockLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectBlock"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectBlockLockReqBuilder) CreateAny() *MetadataObjectBlockLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectBlock"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataObjectRepLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) ObjectRep() *MetadataObjectRepLockReqBuilder { | |||
| return &MetadataObjectRepLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataObjectRepLockReqBuilder) ReadOne(objectID int64) *MetadataObjectRepLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectRep"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(objectID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectRepLockReqBuilder) WriteOne(objectID int64) *MetadataObjectRepLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectRep"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(objectID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectRepLockReqBuilder) CreateOne() *MetadataObjectRepLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectRep"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectRepLockReqBuilder) ReadAny() *MetadataObjectRepLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectRep"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectRepLockReqBuilder) WriteAny() *MetadataObjectRepLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectRep"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataObjectRepLockReqBuilder) CreateAny() *MetadataObjectRepLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("ObjectRep"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataPackageLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) Package() *MetadataPackageLockReqBuilder { | |||
| return &MetadataPackageLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataPackageLockReqBuilder) ReadOne(packageID int64) *MetadataPackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Package"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataPackageLockReqBuilder) WriteOne(packageID int64) *MetadataPackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Package"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataPackageLockReqBuilder) CreateOne(bucketID int64, packageName string) *MetadataPackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Package"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(bucketID, packageName), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataPackageLockReqBuilder) ReadAny() *MetadataPackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Package"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataPackageLockReqBuilder) WriteAny() *MetadataPackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Package"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataPackageLockReqBuilder) CreateAny() *MetadataPackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("Package"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataStoragePackageLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) StoragePackage() *MetadataStoragePackageLockReqBuilder { | |||
| return &MetadataStoragePackageLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataStoragePackageLockReqBuilder) ReadOne(storageID int64, userID int64, packageID int64) *MetadataStoragePackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("StoragePackage"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(storageID, userID, packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataStoragePackageLockReqBuilder) WriteOne(storageID int64, userID int64, packageID int64) *MetadataStoragePackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("StoragePackage"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(storageID, userID, packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataStoragePackageLockReqBuilder) CreateOne(storageID int64, userID int64, packageID int64) *MetadataStoragePackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("StoragePackage"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(storageID, userID, packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataStoragePackageLockReqBuilder) ReadAny() *MetadataStoragePackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("StoragePackage"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataStoragePackageLockReqBuilder) WriteAny() *MetadataStoragePackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("StoragePackage"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataStoragePackageLockReqBuilder) CreateAny() *MetadataStoragePackageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("StoragePackage"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataUserBucketLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) UserBucket() *MetadataUserBucketLockReqBuilder { | |||
| return &MetadataUserBucketLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataUserBucketLockReqBuilder) ReadOne(userID int64, bucketID int64) *MetadataUserBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserBucket"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, bucketID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserBucketLockReqBuilder) WriteOne(userID int64, bucketID int64) *MetadataUserBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserBucket"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, bucketID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserBucketLockReqBuilder) CreateOne(userID int64, bucketID int64) *MetadataUserBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserBucket"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, bucketID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserBucketLockReqBuilder) ReadAny() *MetadataUserBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserBucket"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserBucketLockReqBuilder) WriteAny() *MetadataUserBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserBucket"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserBucketLockReqBuilder) CreateAny() *MetadataUserBucketLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserBucket"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type MetadataUserStorageLockReqBuilder struct { | |||
| *MetadataLockReqBuilder | |||
| } | |||
| func (b *MetadataLockReqBuilder) UserStorage() *MetadataUserStorageLockReqBuilder { | |||
| return &MetadataUserStorageLockReqBuilder{MetadataLockReqBuilder: b} | |||
| } | |||
| func (b *MetadataUserStorageLockReqBuilder) ReadOne(userID int64, storageID int64) *MetadataUserStorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserStorage"), | |||
| Name: lockprovider.METADATA_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, storageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserStorageLockReqBuilder) WriteOne(userID int64, storageID int64) *MetadataUserStorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserStorage"), | |||
| Name: lockprovider.METADATA_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, storageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserStorageLockReqBuilder) CreateOne(userID int64, storageID int64) *MetadataUserStorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserStorage"), | |||
| Name: lockprovider.METADATA_ELEMENT_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, storageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserStorageLockReqBuilder) ReadAny() *MetadataUserStorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserStorage"), | |||
| Name: lockprovider.METADATA_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserStorageLockReqBuilder) WriteAny() *MetadataUserStorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserStorage"), | |||
| Name: lockprovider.METADATA_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *MetadataUserStorageLockReqBuilder) CreateAny() *MetadataUserStorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath("UserStorage"), | |||
| Name: lockprovider.METADATA_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| package reqbuilder | |||
| import ( | |||
| "strconv" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type StorageLockReqBuilder struct { | |||
| *LockRequestBuilder | |||
| } | |||
| func (b *LockRequestBuilder) Storage() *StorageLockReqBuilder { | |||
| return &StorageLockReqBuilder{LockRequestBuilder: b} | |||
| } | |||
| func (b *StorageLockReqBuilder) ReadOnePackage(storageID int64, userID int64, packageID int64) *StorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(storageID), | |||
| Name: lockprovider.STORAGE_ELEMENT_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *StorageLockReqBuilder) WriteOnePackage(storageID int64, userID int64, packageID int64) *StorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(storageID), | |||
| Name: lockprovider.STORAGE_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *StorageLockReqBuilder) CreateOnePackage(storageID int64, userID int64, packageID int64) *StorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(storageID), | |||
| Name: lockprovider.STORAGE_ELEMENT_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget().Add(userID, packageID), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *StorageLockReqBuilder) ReadAnyPackage(storageID int64) *StorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(storageID), | |||
| Name: lockprovider.STORAGE_SET_READ_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *StorageLockReqBuilder) WriteAnyPackage(storageID int64) *StorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(storageID), | |||
| Name: lockprovider.STORAGE_SET_WRITE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *StorageLockReqBuilder) CreateAnyPackage(storageID int64) *StorageLockReqBuilder { | |||
| b.locks = append(b.locks, distlock.Lock{ | |||
| Path: b.makePath(storageID), | |||
| Name: lockprovider.STORAGE_SET_CREATE_LOCK, | |||
| Target: *lockprovider.NewStringLockTarget(), | |||
| }) | |||
| return b | |||
| } | |||
| func (b *StorageLockReqBuilder) makePath(storageID int64) []string { | |||
| return []string{lockprovider.StorageLockPathPrefix, strconv.FormatInt(storageID, 10)} | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| package distlock | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock" | |||
| "gitlink.org.cn/cloudream/common/pkgs/distlock/service" | |||
| "gitlink.org.cn/cloudream/common/pkgs/trie" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/lockprovider" | |||
| ) | |||
| type Service = service.Service | |||
| func NewService(cfg *distlock.Config) (*service.Service, error) { | |||
| srv, err := service.NewService(cfg, initProviders()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return srv, nil | |||
| } | |||
| func initProviders() []service.PathProvider { | |||
| var provs []service.PathProvider | |||
| provs = append(provs, initMetadataLockProviders()...) | |||
| provs = append(provs, initIPFSLockProviders()...) | |||
| provs = append(provs, initStorageLockProviders()...) | |||
| return provs | |||
| } | |||
| func initMetadataLockProviders() []service.PathProvider { | |||
| return []service.PathProvider{ | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Node"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Storage"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "User"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "UserBucket"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "UserNode"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "UserStorage"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Bucket"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Object"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Package"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "ObjectRep"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "ObjectBlock"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Cache"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "StoragePackage"), | |||
| service.NewPathProvider(lockprovider.NewMetadataLock(), lockprovider.MetadataLockPathPrefix, "Location"), | |||
| } | |||
| } | |||
| func initIPFSLockProviders() []service.PathProvider { | |||
| return []service.PathProvider{ | |||
| service.NewPathProvider(lockprovider.NewIPFSLock(), lockprovider.IPFSLockPathPrefix, trie.WORD_ANY), | |||
| } | |||
| } | |||
| func initStorageLockProviders() []service.PathProvider { | |||
| return []service.PathProvider{ | |||
| service.NewPathProvider(lockprovider.NewStorageLock(), lockprovider.StorageLockPathPrefix, trie.WORD_ANY), | |||
| } | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| package ec | |||
| import ( | |||
| "fmt" | |||
| "os" | |||
| "github.com/baohan10/reedsolomon" | |||
| ) | |||
| type rs struct { | |||
| r *(reedsolomon.ReedSolomon) | |||
| ecN int | |||
| ecK int | |||
| ecP int | |||
| } | |||
| func NewRsEnc(ecK int, ecN int) *rs { | |||
| enc := rs{ | |||
| ecN: ecN, | |||
| ecK: ecK, | |||
| ecP: ecN - ecK, | |||
| } | |||
| enc.r = reedsolomon.GetReedSolomonIns(ecK, ecN) | |||
| return &enc | |||
| } | |||
| func (r *rs) Encode(all [][]byte) { | |||
| r.r.Encode(all) | |||
| } | |||
| func (r *rs) Repair(all [][]byte) error { | |||
| return r.r.Reconstruct(all) | |||
| } | |||
| func checkErr(err error) { | |||
| if err != nil { | |||
| fmt.Fprintf(os.Stderr, "Error: %s", err.Error()) | |||
| } | |||
| } | |||
| @@ -0,0 +1,345 @@ | |||
| // 使用的语法版本 | |||
| // Code generated by protoc-gen-go. DO NOT EDIT. | |||
| // versions: | |||
| // protoc-gen-go v1.30.0 | |||
| // protoc v4.22.3 | |||
| // source: pkgs/grpc/agent/agent.proto | |||
| package agent | |||
| import ( | |||
| protoreflect "google.golang.org/protobuf/reflect/protoreflect" | |||
| protoimpl "google.golang.org/protobuf/runtime/protoimpl" | |||
| reflect "reflect" | |||
| sync "sync" | |||
| ) | |||
| const ( | |||
| // Verify that this generated code is sufficiently up-to-date. | |||
| _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | |||
| // Verify that runtime/protoimpl is sufficiently up-to-date. | |||
| _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | |||
| ) | |||
| type FileDataPacketType int32 | |||
| const ( | |||
| FileDataPacketType_Data FileDataPacketType = 0 | |||
| FileDataPacketType_EOF FileDataPacketType = 1 | |||
| ) | |||
| // Enum value maps for FileDataPacketType. | |||
| var ( | |||
| FileDataPacketType_name = map[int32]string{ | |||
| 0: "Data", | |||
| 1: "EOF", | |||
| } | |||
| FileDataPacketType_value = map[string]int32{ | |||
| "Data": 0, | |||
| "EOF": 1, | |||
| } | |||
| ) | |||
| func (x FileDataPacketType) Enum() *FileDataPacketType { | |||
| p := new(FileDataPacketType) | |||
| *p = x | |||
| return p | |||
| } | |||
| func (x FileDataPacketType) String() string { | |||
| return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) | |||
| } | |||
| func (FileDataPacketType) Descriptor() protoreflect.EnumDescriptor { | |||
| return file_pkgs_grpc_agent_agent_proto_enumTypes[0].Descriptor() | |||
| } | |||
| func (FileDataPacketType) Type() protoreflect.EnumType { | |||
| return &file_pkgs_grpc_agent_agent_proto_enumTypes[0] | |||
| } | |||
| func (x FileDataPacketType) Number() protoreflect.EnumNumber { | |||
| return protoreflect.EnumNumber(x) | |||
| } | |||
| // Deprecated: Use FileDataPacketType.Descriptor instead. | |||
| func (FileDataPacketType) EnumDescriptor() ([]byte, []int) { | |||
| return file_pkgs_grpc_agent_agent_proto_rawDescGZIP(), []int{0} | |||
| } | |||
| // 文件数据。注意:只在Type为Data的时候,Data字段才能有数据 | |||
| type FileDataPacket struct { | |||
| state protoimpl.MessageState | |||
| sizeCache protoimpl.SizeCache | |||
| unknownFields protoimpl.UnknownFields | |||
| Type FileDataPacketType `protobuf:"varint,1,opt,name=Type,proto3,enum=FileDataPacketType" json:"Type,omitempty"` | |||
| Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"` | |||
| } | |||
| func (x *FileDataPacket) Reset() { | |||
| *x = FileDataPacket{} | |||
| if protoimpl.UnsafeEnabled { | |||
| mi := &file_pkgs_grpc_agent_agent_proto_msgTypes[0] | |||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| ms.StoreMessageInfo(mi) | |||
| } | |||
| } | |||
| func (x *FileDataPacket) String() string { | |||
| return protoimpl.X.MessageStringOf(x) | |||
| } | |||
| func (*FileDataPacket) ProtoMessage() {} | |||
| func (x *FileDataPacket) ProtoReflect() protoreflect.Message { | |||
| mi := &file_pkgs_grpc_agent_agent_proto_msgTypes[0] | |||
| if protoimpl.UnsafeEnabled && x != nil { | |||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| if ms.LoadMessageInfo() == nil { | |||
| ms.StoreMessageInfo(mi) | |||
| } | |||
| return ms | |||
| } | |||
| return mi.MessageOf(x) | |||
| } | |||
| // Deprecated: Use FileDataPacket.ProtoReflect.Descriptor instead. | |||
| func (*FileDataPacket) Descriptor() ([]byte, []int) { | |||
| return file_pkgs_grpc_agent_agent_proto_rawDescGZIP(), []int{0} | |||
| } | |||
| func (x *FileDataPacket) GetType() FileDataPacketType { | |||
| if x != nil { | |||
| return x.Type | |||
| } | |||
| return FileDataPacketType_Data | |||
| } | |||
| func (x *FileDataPacket) GetData() []byte { | |||
| if x != nil { | |||
| return x.Data | |||
| } | |||
| return nil | |||
| } | |||
| type SendIPFSFileResp struct { | |||
| state protoimpl.MessageState | |||
| sizeCache protoimpl.SizeCache | |||
| unknownFields protoimpl.UnknownFields | |||
| FileHash string `protobuf:"bytes,1,opt,name=FileHash,proto3" json:"FileHash,omitempty"` | |||
| } | |||
| func (x *SendIPFSFileResp) Reset() { | |||
| *x = SendIPFSFileResp{} | |||
| if protoimpl.UnsafeEnabled { | |||
| mi := &file_pkgs_grpc_agent_agent_proto_msgTypes[1] | |||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| ms.StoreMessageInfo(mi) | |||
| } | |||
| } | |||
| func (x *SendIPFSFileResp) String() string { | |||
| return protoimpl.X.MessageStringOf(x) | |||
| } | |||
| func (*SendIPFSFileResp) ProtoMessage() {} | |||
| func (x *SendIPFSFileResp) ProtoReflect() protoreflect.Message { | |||
| mi := &file_pkgs_grpc_agent_agent_proto_msgTypes[1] | |||
| if protoimpl.UnsafeEnabled && x != nil { | |||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| if ms.LoadMessageInfo() == nil { | |||
| ms.StoreMessageInfo(mi) | |||
| } | |||
| return ms | |||
| } | |||
| return mi.MessageOf(x) | |||
| } | |||
| // Deprecated: Use SendIPFSFileResp.ProtoReflect.Descriptor instead. | |||
| func (*SendIPFSFileResp) Descriptor() ([]byte, []int) { | |||
| return file_pkgs_grpc_agent_agent_proto_rawDescGZIP(), []int{1} | |||
| } | |||
| func (x *SendIPFSFileResp) GetFileHash() string { | |||
| if x != nil { | |||
| return x.FileHash | |||
| } | |||
| return "" | |||
| } | |||
| type GetIPFSFileReq struct { | |||
| state protoimpl.MessageState | |||
| sizeCache protoimpl.SizeCache | |||
| unknownFields protoimpl.UnknownFields | |||
| FileHash string `protobuf:"bytes,1,opt,name=FileHash,proto3" json:"FileHash,omitempty"` | |||
| } | |||
| func (x *GetIPFSFileReq) Reset() { | |||
| *x = GetIPFSFileReq{} | |||
| if protoimpl.UnsafeEnabled { | |||
| mi := &file_pkgs_grpc_agent_agent_proto_msgTypes[2] | |||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| ms.StoreMessageInfo(mi) | |||
| } | |||
| } | |||
| func (x *GetIPFSFileReq) String() string { | |||
| return protoimpl.X.MessageStringOf(x) | |||
| } | |||
| func (*GetIPFSFileReq) ProtoMessage() {} | |||
| func (x *GetIPFSFileReq) ProtoReflect() protoreflect.Message { | |||
| mi := &file_pkgs_grpc_agent_agent_proto_msgTypes[2] | |||
| if protoimpl.UnsafeEnabled && x != nil { | |||
| ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| if ms.LoadMessageInfo() == nil { | |||
| ms.StoreMessageInfo(mi) | |||
| } | |||
| return ms | |||
| } | |||
| return mi.MessageOf(x) | |||
| } | |||
| // Deprecated: Use GetIPFSFileReq.ProtoReflect.Descriptor instead. | |||
| func (*GetIPFSFileReq) Descriptor() ([]byte, []int) { | |||
| return file_pkgs_grpc_agent_agent_proto_rawDescGZIP(), []int{2} | |||
| } | |||
| func (x *GetIPFSFileReq) GetFileHash() string { | |||
| if x != nil { | |||
| return x.FileHash | |||
| } | |||
| return "" | |||
| } | |||
| var File_pkgs_grpc_agent_agent_proto protoreflect.FileDescriptor | |||
| var file_pkgs_grpc_agent_agent_proto_rawDesc = []byte{ | |||
| 0x0a, 0x1b, 0x70, 0x6b, 0x67, 0x73, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x61, 0x67, 0x65, 0x6e, | |||
| 0x74, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4d, 0x0a, | |||
| 0x0e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, | |||
| 0x27, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, | |||
| 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, | |||
| 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, | |||
| 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x22, 0x2e, 0x0a, 0x10, | |||
| 0x53, 0x65, 0x6e, 0x64, 0x49, 0x50, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, | |||
| 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, | |||
| 0x28, 0x09, 0x52, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x22, 0x2c, 0x0a, 0x0e, | |||
| 0x47, 0x65, 0x74, 0x49, 0x50, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x12, 0x1a, | |||
| 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, | |||
| 0x52, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x2a, 0x27, 0x0a, 0x12, 0x46, 0x69, | |||
| 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, | |||
| 0x12, 0x08, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x45, 0x4f, | |||
| 0x46, 0x10, 0x01, 0x32, 0x74, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0c, | |||
| 0x53, 0x65, 0x6e, 0x64, 0x49, 0x50, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x0f, 0x2e, 0x46, | |||
| 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x11, 0x2e, | |||
| 0x53, 0x65, 0x6e, 0x64, 0x49, 0x50, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, | |||
| 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x49, 0x50, 0x46, 0x53, 0x46, | |||
| 0x69, 0x6c, 0x65, 0x12, 0x0f, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x50, 0x46, 0x53, 0x46, 0x69, 0x6c, | |||
| 0x65, 0x52, 0x65, 0x71, 0x1a, 0x0f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x50, | |||
| 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x61, | |||
| 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | |||
| } | |||
| var ( | |||
| file_pkgs_grpc_agent_agent_proto_rawDescOnce sync.Once | |||
| file_pkgs_grpc_agent_agent_proto_rawDescData = file_pkgs_grpc_agent_agent_proto_rawDesc | |||
| ) | |||
| func file_pkgs_grpc_agent_agent_proto_rawDescGZIP() []byte { | |||
| file_pkgs_grpc_agent_agent_proto_rawDescOnce.Do(func() { | |||
| file_pkgs_grpc_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkgs_grpc_agent_agent_proto_rawDescData) | |||
| }) | |||
| return file_pkgs_grpc_agent_agent_proto_rawDescData | |||
| } | |||
| var file_pkgs_grpc_agent_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) | |||
| var file_pkgs_grpc_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 3) | |||
| var file_pkgs_grpc_agent_agent_proto_goTypes = []interface{}{ | |||
| (FileDataPacketType)(0), // 0: FileDataPacketType | |||
| (*FileDataPacket)(nil), // 1: FileDataPacket | |||
| (*SendIPFSFileResp)(nil), // 2: SendIPFSFileResp | |||
| (*GetIPFSFileReq)(nil), // 3: GetIPFSFileReq | |||
| } | |||
| var file_pkgs_grpc_agent_agent_proto_depIdxs = []int32{ | |||
| 0, // 0: FileDataPacket.Type:type_name -> FileDataPacketType | |||
| 1, // 1: Agent.SendIPFSFile:input_type -> FileDataPacket | |||
| 3, // 2: Agent.GetIPFSFile:input_type -> GetIPFSFileReq | |||
| 2, // 3: Agent.SendIPFSFile:output_type -> SendIPFSFileResp | |||
| 1, // 4: Agent.GetIPFSFile:output_type -> FileDataPacket | |||
| 3, // [3:5] is the sub-list for method output_type | |||
| 1, // [1:3] is the sub-list for method input_type | |||
| 1, // [1:1] is the sub-list for extension type_name | |||
| 1, // [1:1] is the sub-list for extension extendee | |||
| 0, // [0:1] is the sub-list for field type_name | |||
| } | |||
| func init() { file_pkgs_grpc_agent_agent_proto_init() } | |||
| func file_pkgs_grpc_agent_agent_proto_init() { | |||
| if File_pkgs_grpc_agent_agent_proto != nil { | |||
| return | |||
| } | |||
| if !protoimpl.UnsafeEnabled { | |||
| file_pkgs_grpc_agent_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | |||
| switch v := v.(*FileDataPacket); i { | |||
| case 0: | |||
| return &v.state | |||
| case 1: | |||
| return &v.sizeCache | |||
| case 2: | |||
| return &v.unknownFields | |||
| default: | |||
| return nil | |||
| } | |||
| } | |||
| file_pkgs_grpc_agent_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | |||
| switch v := v.(*SendIPFSFileResp); i { | |||
| case 0: | |||
| return &v.state | |||
| case 1: | |||
| return &v.sizeCache | |||
| case 2: | |||
| return &v.unknownFields | |||
| default: | |||
| return nil | |||
| } | |||
| } | |||
| file_pkgs_grpc_agent_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { | |||
| switch v := v.(*GetIPFSFileReq); i { | |||
| case 0: | |||
| return &v.state | |||
| case 1: | |||
| return &v.sizeCache | |||
| case 2: | |||
| return &v.unknownFields | |||
| default: | |||
| return nil | |||
| } | |||
| } | |||
| } | |||
| type x struct{} | |||
| out := protoimpl.TypeBuilder{ | |||
| File: protoimpl.DescBuilder{ | |||
| GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | |||
| RawDescriptor: file_pkgs_grpc_agent_agent_proto_rawDesc, | |||
| NumEnums: 1, | |||
| NumMessages: 3, | |||
| NumExtensions: 0, | |||
| NumServices: 1, | |||
| }, | |||
| GoTypes: file_pkgs_grpc_agent_agent_proto_goTypes, | |||
| DependencyIndexes: file_pkgs_grpc_agent_agent_proto_depIdxs, | |||
| EnumInfos: file_pkgs_grpc_agent_agent_proto_enumTypes, | |||
| MessageInfos: file_pkgs_grpc_agent_agent_proto_msgTypes, | |||
| }.Build() | |||
| File_pkgs_grpc_agent_agent_proto = out.File | |||
| file_pkgs_grpc_agent_agent_proto_rawDesc = nil | |||
| file_pkgs_grpc_agent_agent_proto_goTypes = nil | |||
| file_pkgs_grpc_agent_agent_proto_depIdxs = nil | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| // 使用的语法版本 | |||
| syntax = "proto3"; | |||
| // 生成的go文件包 | |||
| option go_package = ".;agent";//grpc这里生效了 | |||
| enum FileDataPacketType { | |||
| Data = 0; | |||
| EOF = 1; | |||
| } | |||
| // 文件数据。注意:只在Type为Data的时候,Data字段才能有数据 | |||
| message FileDataPacket { | |||
| FileDataPacketType Type = 1; | |||
| bytes Data = 2; | |||
| } | |||
| message SendIPFSFileResp { | |||
| string FileHash = 1; | |||
| } | |||
| message GetIPFSFileReq { | |||
| string FileHash = 1; | |||
| } | |||
| service Agent { | |||
| rpc SendIPFSFile(stream FileDataPacket)returns(SendIPFSFileResp){} | |||
| rpc GetIPFSFile(GetIPFSFileReq)returns(stream FileDataPacket){} | |||
| } | |||
| @@ -0,0 +1,209 @@ | |||
| // 使用的语法版本 | |||
| // Code generated by protoc-gen-go-grpc. DO NOT EDIT. | |||
| // versions: | |||
| // - protoc-gen-go-grpc v1.3.0 | |||
| // - protoc v4.22.3 | |||
| // source: pkgs/grpc/agent/agent.proto | |||
| package agent | |||
| import ( | |||
| context "context" | |||
| grpc "google.golang.org/grpc" | |||
| codes "google.golang.org/grpc/codes" | |||
| status "google.golang.org/grpc/status" | |||
| ) | |||
| // This is a compile-time assertion to ensure that this generated file | |||
| // is compatible with the grpc package it is being compiled against. | |||
| // Requires gRPC-Go v1.32.0 or later. | |||
| const _ = grpc.SupportPackageIsVersion7 | |||
| const ( | |||
| Agent_SendIPFSFile_FullMethodName = "/Agent/SendIPFSFile" | |||
| Agent_GetIPFSFile_FullMethodName = "/Agent/GetIPFSFile" | |||
| ) | |||
| // AgentClient is the client API for Agent service. | |||
| // | |||
| // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | |||
| type AgentClient interface { | |||
| SendIPFSFile(ctx context.Context, opts ...grpc.CallOption) (Agent_SendIPFSFileClient, error) | |||
| GetIPFSFile(ctx context.Context, in *GetIPFSFileReq, opts ...grpc.CallOption) (Agent_GetIPFSFileClient, error) | |||
| } | |||
| type agentClient struct { | |||
| cc grpc.ClientConnInterface | |||
| } | |||
| func NewAgentClient(cc grpc.ClientConnInterface) AgentClient { | |||
| return &agentClient{cc} | |||
| } | |||
| func (c *agentClient) SendIPFSFile(ctx context.Context, opts ...grpc.CallOption) (Agent_SendIPFSFileClient, error) { | |||
| stream, err := c.cc.NewStream(ctx, &Agent_ServiceDesc.Streams[0], Agent_SendIPFSFile_FullMethodName, opts...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| x := &agentSendIPFSFileClient{stream} | |||
| return x, nil | |||
| } | |||
| type Agent_SendIPFSFileClient interface { | |||
| Send(*FileDataPacket) error | |||
| CloseAndRecv() (*SendIPFSFileResp, error) | |||
| grpc.ClientStream | |||
| } | |||
| type agentSendIPFSFileClient struct { | |||
| grpc.ClientStream | |||
| } | |||
| func (x *agentSendIPFSFileClient) Send(m *FileDataPacket) error { | |||
| return x.ClientStream.SendMsg(m) | |||
| } | |||
| func (x *agentSendIPFSFileClient) CloseAndRecv() (*SendIPFSFileResp, error) { | |||
| if err := x.ClientStream.CloseSend(); err != nil { | |||
| return nil, err | |||
| } | |||
| m := new(SendIPFSFileResp) | |||
| if err := x.ClientStream.RecvMsg(m); err != nil { | |||
| return nil, err | |||
| } | |||
| return m, nil | |||
| } | |||
| func (c *agentClient) GetIPFSFile(ctx context.Context, in *GetIPFSFileReq, opts ...grpc.CallOption) (Agent_GetIPFSFileClient, error) { | |||
| stream, err := c.cc.NewStream(ctx, &Agent_ServiceDesc.Streams[1], Agent_GetIPFSFile_FullMethodName, opts...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| x := &agentGetIPFSFileClient{stream} | |||
| if err := x.ClientStream.SendMsg(in); err != nil { | |||
| return nil, err | |||
| } | |||
| if err := x.ClientStream.CloseSend(); err != nil { | |||
| return nil, err | |||
| } | |||
| return x, nil | |||
| } | |||
| type Agent_GetIPFSFileClient interface { | |||
| Recv() (*FileDataPacket, error) | |||
| grpc.ClientStream | |||
| } | |||
| type agentGetIPFSFileClient struct { | |||
| grpc.ClientStream | |||
| } | |||
| func (x *agentGetIPFSFileClient) Recv() (*FileDataPacket, error) { | |||
| m := new(FileDataPacket) | |||
| if err := x.ClientStream.RecvMsg(m); err != nil { | |||
| return nil, err | |||
| } | |||
| return m, nil | |||
| } | |||
| // AgentServer is the server API for Agent service. | |||
| // All implementations must embed UnimplementedAgentServer | |||
| // for forward compatibility | |||
| type AgentServer interface { | |||
| SendIPFSFile(Agent_SendIPFSFileServer) error | |||
| GetIPFSFile(*GetIPFSFileReq, Agent_GetIPFSFileServer) error | |||
| mustEmbedUnimplementedAgentServer() | |||
| } | |||
| // UnimplementedAgentServer must be embedded to have forward compatible implementations. | |||
| type UnimplementedAgentServer struct { | |||
| } | |||
| func (UnimplementedAgentServer) SendIPFSFile(Agent_SendIPFSFileServer) error { | |||
| return status.Errorf(codes.Unimplemented, "method SendIPFSFile not implemented") | |||
| } | |||
| func (UnimplementedAgentServer) GetIPFSFile(*GetIPFSFileReq, Agent_GetIPFSFileServer) error { | |||
| return status.Errorf(codes.Unimplemented, "method GetIPFSFile not implemented") | |||
| } | |||
| func (UnimplementedAgentServer) mustEmbedUnimplementedAgentServer() {} | |||
| // UnsafeAgentServer may be embedded to opt out of forward compatibility for this service. | |||
| // Use of this interface is not recommended, as added methods to AgentServer will | |||
| // result in compilation errors. | |||
| type UnsafeAgentServer interface { | |||
| mustEmbedUnimplementedAgentServer() | |||
| } | |||
| func RegisterAgentServer(s grpc.ServiceRegistrar, srv AgentServer) { | |||
| s.RegisterService(&Agent_ServiceDesc, srv) | |||
| } | |||
| func _Agent_SendIPFSFile_Handler(srv interface{}, stream grpc.ServerStream) error { | |||
| return srv.(AgentServer).SendIPFSFile(&agentSendIPFSFileServer{stream}) | |||
| } | |||
| type Agent_SendIPFSFileServer interface { | |||
| SendAndClose(*SendIPFSFileResp) error | |||
| Recv() (*FileDataPacket, error) | |||
| grpc.ServerStream | |||
| } | |||
| type agentSendIPFSFileServer struct { | |||
| grpc.ServerStream | |||
| } | |||
| func (x *agentSendIPFSFileServer) SendAndClose(m *SendIPFSFileResp) error { | |||
| return x.ServerStream.SendMsg(m) | |||
| } | |||
| func (x *agentSendIPFSFileServer) Recv() (*FileDataPacket, error) { | |||
| m := new(FileDataPacket) | |||
| if err := x.ServerStream.RecvMsg(m); err != nil { | |||
| return nil, err | |||
| } | |||
| return m, nil | |||
| } | |||
| func _Agent_GetIPFSFile_Handler(srv interface{}, stream grpc.ServerStream) error { | |||
| m := new(GetIPFSFileReq) | |||
| if err := stream.RecvMsg(m); err != nil { | |||
| return err | |||
| } | |||
| return srv.(AgentServer).GetIPFSFile(m, &agentGetIPFSFileServer{stream}) | |||
| } | |||
| type Agent_GetIPFSFileServer interface { | |||
| Send(*FileDataPacket) error | |||
| grpc.ServerStream | |||
| } | |||
| type agentGetIPFSFileServer struct { | |||
| grpc.ServerStream | |||
| } | |||
| func (x *agentGetIPFSFileServer) Send(m *FileDataPacket) error { | |||
| return x.ServerStream.SendMsg(m) | |||
| } | |||
| // Agent_ServiceDesc is the grpc.ServiceDesc for Agent service. | |||
| // It's only intended for direct use with grpc.RegisterService, | |||
| // and not to be introspected or modified (even as a copy) | |||
| var Agent_ServiceDesc = grpc.ServiceDesc{ | |||
| ServiceName: "Agent", | |||
| HandlerType: (*AgentServer)(nil), | |||
| Methods: []grpc.MethodDesc{}, | |||
| Streams: []grpc.StreamDesc{ | |||
| { | |||
| StreamName: "SendIPFSFile", | |||
| Handler: _Agent_SendIPFSFile_Handler, | |||
| ClientStreams: true, | |||
| }, | |||
| { | |||
| StreamName: "GetIPFSFile", | |||
| Handler: _Agent_GetIPFSFile_Handler, | |||
| ServerStreams: true, | |||
| }, | |||
| }, | |||
| Metadata: "pkgs/grpc/agent/agent.proto", | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| package agent | |||
| import ( | |||
| "context" | |||
| "fmt" | |||
| "io" | |||
| "google.golang.org/grpc" | |||
| "google.golang.org/grpc/credentials/insecure" | |||
| ) | |||
| type Client struct { | |||
| con *grpc.ClientConn | |||
| cli AgentClient | |||
| } | |||
| func NewClient(addr string) (*Client, error) { | |||
| con, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &Client{ | |||
| con: con, | |||
| cli: NewAgentClient(con), | |||
| }, nil | |||
| } | |||
| func (c *Client) SendIPFSFile(file io.Reader) (string, error) { | |||
| sendCli, err := c.cli.SendIPFSFile(context.Background()) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| buf := make([]byte, 4096) | |||
| for { | |||
| rd, err := file.Read(buf) | |||
| if err == io.EOF { | |||
| err := sendCli.Send(&FileDataPacket{ | |||
| Type: FileDataPacketType_EOF, | |||
| Data: buf[:rd], | |||
| }) | |||
| if err != nil { | |||
| return "", fmt.Errorf("sending EOF packet: %w", err) | |||
| } | |||
| resp, err := sendCli.CloseAndRecv() | |||
| if err != nil { | |||
| return "", fmt.Errorf("receiving response: %w", err) | |||
| } | |||
| return resp.FileHash, nil | |||
| } | |||
| if err != nil { | |||
| return "", fmt.Errorf("reading file data: %w", err) | |||
| } | |||
| err = sendCli.Send(&FileDataPacket{ | |||
| Type: FileDataPacketType_Data, | |||
| Data: buf[:rd], | |||
| }) | |||
| if err != nil { | |||
| return "", fmt.Errorf("sending data packet: %w", err) | |||
| } | |||
| } | |||
| } | |||
| type fileReadCloser struct { | |||
| io.ReadCloser | |||
| stream Agent_GetIPFSFileClient | |||
| cancelFn context.CancelFunc | |||
| readingData []byte | |||
| recvEOF bool | |||
| } | |||
| func (s *fileReadCloser) Read(p []byte) (int, error) { | |||
| if len(s.readingData) == 0 && !s.recvEOF { | |||
| resp, err := s.stream.Recv() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| if resp.Type == FileDataPacketType_Data { | |||
| s.readingData = resp.Data | |||
| } else if resp.Type == FileDataPacketType_EOF { | |||
| s.readingData = resp.Data | |||
| s.recvEOF = true | |||
| } else { | |||
| return 0, fmt.Errorf("unsupported packt type: %v", resp.Type) | |||
| } | |||
| } | |||
| cnt := copy(p, s.readingData) | |||
| s.readingData = s.readingData[cnt:] | |||
| if len(s.readingData) == 0 && s.recvEOF { | |||
| return cnt, io.EOF | |||
| } | |||
| return cnt, nil | |||
| } | |||
| func (s *fileReadCloser) Close() error { | |||
| s.cancelFn() | |||
| return nil | |||
| } | |||
| func (c *Client) GetIPFSFile(fileHash string) (io.ReadCloser, error) { | |||
| ctx, cancel := context.WithCancel(context.Background()) | |||
| stream, err := c.cli.GetIPFSFile(ctx, &GetIPFSFileReq{ | |||
| FileHash: fileHash, | |||
| }) | |||
| if err != nil { | |||
| cancel() | |||
| return nil, fmt.Errorf("request grpc failed, err: %w", err) | |||
| } | |||
| return &fileReadCloser{ | |||
| stream: stream, | |||
| cancelFn: cancel, | |||
| }, nil | |||
| } | |||
| func (c *Client) Close() { | |||
| c.con.Close() | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| package agent | |||
| import ( | |||
| "fmt" | |||
| ) | |||
| type PoolConfig struct { | |||
| Port int `json:"port"` | |||
| } | |||
| type PoolClient struct { | |||
| *Client | |||
| owner *Pool | |||
| } | |||
| func (c *PoolClient) Close() { | |||
| c.owner.Release(c) | |||
| } | |||
| type Pool struct { | |||
| grpcCfg *PoolConfig | |||
| } | |||
| func NewPool(grpcCfg *PoolConfig) *Pool { | |||
| return &Pool{ | |||
| grpcCfg: grpcCfg, | |||
| } | |||
| } | |||
| func (p *Pool) Acquire(ip string) (*PoolClient, error) { | |||
| cli, err := NewClient(fmt.Sprintf("%s:%d", ip, p.grpcCfg.Port)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &PoolClient{ | |||
| Client: cli, | |||
| owner: p, | |||
| }, nil | |||
| } | |||
| func (p *Pool) Release(cli *PoolClient) { | |||
| cli.Client.Close() | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| package grpc | |||
| import "fmt" | |||
| type Config struct { | |||
| IP string `json:"ip"` | |||
| Port int `json:"port"` | |||
| } | |||
| func (c *Config) MakeListenAddress() string { | |||
| return fmt.Sprintf("%s:%d", c.IP, c.Port) | |||
| } | |||
| @@ -0,0 +1,227 @@ | |||
| package iterator | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "math/rand" | |||
| "os" | |||
| "github.com/samber/lo" | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| stgmodels "gitlink.org.cn/cloudream/storage-common/models" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/ec" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type ECObjectIterator struct { | |||
| OnClosing func() | |||
| objects []model.Object | |||
| objectECData []stgmodels.ObjectECData | |||
| currentIndex int | |||
| inited bool | |||
| ecInfo models.ECRedundancyInfo | |||
| ec model.Ec | |||
| downloadCtx *DownloadContext | |||
| cliLocation model.Location | |||
| } | |||
| func NewECObjectIterator(objects []model.Object, objectECData []stgmodels.ObjectECData, ecInfo models.ECRedundancyInfo, ec model.Ec, downloadCtx *DownloadContext) *ECObjectIterator { | |||
| return &ECObjectIterator{ | |||
| objects: objects, | |||
| objectECData: objectECData, | |||
| ecInfo: ecInfo, | |||
| ec: ec, | |||
| downloadCtx: downloadCtx, | |||
| } | |||
| } | |||
| func (i *ECObjectIterator) MoveNext() (*IterDownloadingObject, error) { | |||
| // TODO 加锁 | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| defer coorCli.Close() | |||
| if !i.inited { | |||
| i.inited = true | |||
| findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(globals.Local.ExternalIP)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("finding client location: %w", err) | |||
| } | |||
| i.cliLocation = findCliLocResp.Location | |||
| } | |||
| if i.currentIndex >= len(i.objects) { | |||
| return nil, ErrNoMoreItem | |||
| } | |||
| item, err := i.doMove(coorCli) | |||
| i.currentIndex++ | |||
| return item, err | |||
| } | |||
| func (iter *ECObjectIterator) doMove(coorCli *coormq.PoolClient) (*IterDownloadingObject, error) { | |||
| obj := iter.objects[iter.currentIndex] | |||
| ecData := iter.objectECData[iter.currentIndex] | |||
| blocks := ecData.Blocks | |||
| ec := iter.ec | |||
| ecK := ec.EcK | |||
| ecN := ec.EcN | |||
| //采取直接读,优先选内网节点 | |||
| hashs := make([]string, ecK) | |||
| nds := make([]DownloadNodeInfo, ecK) | |||
| for i := 0; i < ecK; i++ { | |||
| hashs[i] = blocks[i].FileHash | |||
| getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(blocks[i].NodeIDs)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting nodes: %w", err) | |||
| } | |||
| downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo { | |||
| return DownloadNodeInfo{ | |||
| Node: node, | |||
| IsSameLocation: node.LocationID == iter.cliLocation.LocationID, | |||
| } | |||
| }) | |||
| nds[i] = iter.chooseDownloadNode(downloadNodes) | |||
| } | |||
| //nodeIDs, nodeIPs直接按照第1~ecK个排列 | |||
| nodeIDs := make([]int64, ecK) | |||
| nodeIPs := make([]string, ecK) | |||
| for i := 0; i < ecK; i++ { | |||
| nodeIDs[i] = nds[i].Node.NodeID | |||
| nodeIPs[i] = nds[i].Node.ExternalIP | |||
| if nds[i].IsSameLocation { | |||
| nodeIPs[i] = nds[i].Node.LocalIP | |||
| logger.Infof("client and node %d are at the same location, use local ip\n", nds[i].Node.NodeID) | |||
| } | |||
| } | |||
| fileSize := obj.Size | |||
| blockIDs := make([]int, ecK) | |||
| for i := 0; i < ecK; i++ { | |||
| blockIDs[i] = i | |||
| } | |||
| reader, err := iter.downloadEcObject(fileSize, ecK, ecN, blockIDs, nodeIDs, nodeIPs, hashs) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("ec read failed, err: %w", err) | |||
| } | |||
| return &IterDownloadingObject{ | |||
| File: reader, | |||
| }, nil | |||
| } | |||
| func (i *ECObjectIterator) Close() { | |||
| if i.OnClosing != nil { | |||
| i.OnClosing() | |||
| } | |||
| } | |||
| // chooseDownloadNode 选择一个下载节点 | |||
| // 1. 从与当前客户端相同地域的节点中随机选一个 | |||
| // 2. 没有用的话从所有节点中随机选一个 | |||
| func (i *ECObjectIterator) chooseDownloadNode(entries []DownloadNodeInfo) DownloadNodeInfo { | |||
| sameLocationEntries := lo.Filter(entries, func(e DownloadNodeInfo, i int) bool { return e.IsSameLocation }) | |||
| if len(sameLocationEntries) > 0 { | |||
| return sameLocationEntries[rand.Intn(len(sameLocationEntries))] | |||
| } | |||
| return entries[rand.Intn(len(entries))] | |||
| } | |||
| func (iter *ECObjectIterator) downloadEcObject(fileSize int64, ecK int, ecN int, blockIDs []int, nodeIDs []int64, nodeIPs []string, hashs []string) (io.ReadCloser, error) { | |||
| // TODO zkx 先试用同步方式实现逻辑,做好错误处理。同时也方便下面直接使用uploadToNode和uploadToLocalIPFS来优化代码结构 | |||
| //wg := sync.WaitGroup{} | |||
| numPacket := (fileSize + int64(ecK)*iter.ecInfo.PacketSize - 1) / (int64(ecK) * iter.ecInfo.PacketSize) | |||
| getBufs := make([]chan []byte, ecN) | |||
| decodeBufs := make([]chan []byte, ecK) | |||
| for i := 0; i < ecN; i++ { | |||
| getBufs[i] = make(chan []byte) | |||
| } | |||
| for i := 0; i < ecK; i++ { | |||
| decodeBufs[i] = make(chan []byte) | |||
| } | |||
| for idx := 0; idx < len(blockIDs); idx++ { | |||
| i := idx | |||
| go func() { | |||
| // TODO 处理错误 | |||
| file, _ := downloadFile(iter.downloadCtx, nodeIDs[i], nodeIPs[i], hashs[i]) | |||
| for p := int64(0); p < numPacket; p++ { | |||
| buf := make([]byte, iter.ecInfo.PacketSize) | |||
| // TODO 处理错误 | |||
| io.ReadFull(file, buf) | |||
| getBufs[blockIDs[i]] <- buf | |||
| } | |||
| }() | |||
| } | |||
| print(numPacket) | |||
| go decode(getBufs[:], decodeBufs[:], blockIDs, ecK, numPacket) | |||
| r, w := io.Pipe() | |||
| //persist函数,将解码得到的文件写入pipe | |||
| go func() { | |||
| for i := 0; int64(i) < numPacket; i++ { | |||
| for j := 0; j < len(decodeBufs); j++ { | |||
| tmp := <-decodeBufs[j] | |||
| _, err := w.Write(tmp) | |||
| if err != nil { | |||
| fmt.Errorf("persist file falied, err:%w", err) | |||
| } | |||
| } | |||
| } | |||
| w.Close() | |||
| }() | |||
| return r, nil | |||
| } | |||
| func decode(inBufs []chan []byte, outBufs []chan []byte, blockSeq []int, ecK int, numPacket int64) { | |||
| fmt.Println("decode ") | |||
| var tmpIn [][]byte | |||
| var zeroPkt []byte | |||
| tmpIn = make([][]byte, len(inBufs)) | |||
| hasBlock := map[int]bool{} | |||
| for j := 0; j < len(blockSeq); j++ { | |||
| hasBlock[blockSeq[j]] = true | |||
| } | |||
| needRepair := false //检测是否传入了所有数据块 | |||
| for j := 0; j < len(outBufs); j++ { | |||
| if blockSeq[j] != j { | |||
| needRepair = true | |||
| } | |||
| } | |||
| enc := ec.NewRsEnc(ecK, len(inBufs)) | |||
| for i := 0; int64(i) < numPacket; i++ { | |||
| print("!!!!!") | |||
| for j := 0; j < len(inBufs); j++ { | |||
| if hasBlock[j] { | |||
| tmpIn[j] = <-inBufs[j] | |||
| } else { | |||
| tmpIn[j] = zeroPkt | |||
| } | |||
| } | |||
| if needRepair { | |||
| err := enc.Repair(tmpIn) | |||
| if err != nil { | |||
| fmt.Fprintf(os.Stderr, "Decode Repair Error: %s", err.Error()) | |||
| } | |||
| } | |||
| for j := 0; j < len(outBufs); j++ { | |||
| outBufs[j] <- tmpIn[j] | |||
| } | |||
| } | |||
| for i := 0; i < len(outBufs); i++ { | |||
| close(outBufs[i]) | |||
| } | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| package iterator | |||
| import ( | |||
| "mime/multipart" | |||
| ) | |||
| type HTTPUploadingIterator struct { | |||
| files []*multipart.FileHeader | |||
| currentIndex int | |||
| } | |||
| func NewHTTPObjectIterator(files []*multipart.FileHeader) *HTTPUploadingIterator { | |||
| return &HTTPUploadingIterator{ | |||
| files: files, | |||
| } | |||
| } | |||
| func (i *HTTPUploadingIterator) MoveNext() (*IterUploadingObject, error) { | |||
| if i.currentIndex >= len(i.files) { | |||
| return nil, ErrNoMoreItem | |||
| } | |||
| item, err := i.doMove() | |||
| i.currentIndex++ | |||
| return item, err | |||
| } | |||
| func (i *HTTPUploadingIterator) doMove() (*IterUploadingObject, error) { | |||
| fileInfo := i.files[i.currentIndex] | |||
| file, err := fileInfo.Open() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &IterUploadingObject{ | |||
| Path: fileInfo.Filename, | |||
| Size: fileInfo.Size, | |||
| File: file, | |||
| }, nil | |||
| } | |||
| func (i *HTTPUploadingIterator) Close() { | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| package iterator | |||
| import ( | |||
| "errors" | |||
| ) | |||
| var ErrNoMoreItem = errors.New("no more item") | |||
| type Iterator[T any] interface { | |||
| MoveNext() (T, error) | |||
| Close() | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| package iterator | |||
| import ( | |||
| "io" | |||
| "os" | |||
| "path/filepath" | |||
| "strings" | |||
| ) | |||
| type UploadingObjectIterator = Iterator[*IterUploadingObject] | |||
| type LocalUploadingIterator struct { | |||
| pathRoot string | |||
| filePathes []string | |||
| currentIndex int | |||
| } | |||
| type IterUploadingObject struct { | |||
| Path string | |||
| Size int64 | |||
| File io.ReadCloser | |||
| } | |||
| func NewUploadingObjectIterator(pathRoot string, filePathes []string) *LocalUploadingIterator { | |||
| return &LocalUploadingIterator{ | |||
| pathRoot: filepath.ToSlash(pathRoot), | |||
| filePathes: filePathes, | |||
| } | |||
| } | |||
| func (i *LocalUploadingIterator) MoveNext() (*IterUploadingObject, error) { | |||
| if i.currentIndex >= len(i.filePathes) { | |||
| return nil, ErrNoMoreItem | |||
| } | |||
| item, err := i.doMove() | |||
| i.currentIndex++ | |||
| return item, err | |||
| } | |||
| func (i *LocalUploadingIterator) doMove() (*IterUploadingObject, error) { | |||
| path := i.filePathes[i.currentIndex] | |||
| info, err := os.Stat(path) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| file, err := os.Open(path) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &IterUploadingObject{ | |||
| Path: strings.TrimPrefix(filepath.ToSlash(path), i.pathRoot+"/"), | |||
| Size: info.Size(), | |||
| File: file, | |||
| }, nil | |||
| } | |||
| func (i *LocalUploadingIterator) Close() { | |||
| } | |||
| @@ -0,0 +1,211 @@ | |||
| package iterator | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "math/rand" | |||
| "github.com/samber/lo" | |||
| distsvc "gitlink.org.cn/cloudream/common/pkgs/distlock/service" | |||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||
| myio "gitlink.org.cn/cloudream/common/utils/io" | |||
| "gitlink.org.cn/cloudream/storage-common/globals" | |||
| "gitlink.org.cn/cloudream/storage-common/models" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/distlock/reqbuilder" | |||
| coormq "gitlink.org.cn/cloudream/storage-common/pkgs/mq/coordinator" | |||
| ) | |||
| type DownloadingObjectIterator = Iterator[*IterDownloadingObject] | |||
| type RepObjectIterator struct { | |||
| OnClosing func() | |||
| objects []model.Object | |||
| objectRepData []models.ObjectRepData | |||
| currentIndex int | |||
| inited bool | |||
| downloadCtx *DownloadContext | |||
| cliLocation model.Location | |||
| } | |||
| type IterDownloadingObject struct { | |||
| Object model.Object | |||
| File io.ReadCloser | |||
| } | |||
| type DownloadNodeInfo struct { | |||
| Node model.Node | |||
| IsSameLocation bool | |||
| } | |||
| type DownloadContext struct { | |||
| Distlock *distsvc.Service | |||
| } | |||
| func NewRepObjectIterator(objects []model.Object, objectRepData []models.ObjectRepData, downloadCtx *DownloadContext) *RepObjectIterator { | |||
| return &RepObjectIterator{ | |||
| objects: objects, | |||
| objectRepData: objectRepData, | |||
| downloadCtx: downloadCtx, | |||
| } | |||
| } | |||
| func (i *RepObjectIterator) MoveNext() (*IterDownloadingObject, error) { | |||
| // TODO 加锁 | |||
| coorCli, err := globals.CoordinatorMQPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new coordinator client: %w", err) | |||
| } | |||
| defer coorCli.Close() | |||
| if !i.inited { | |||
| i.inited = true | |||
| findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(globals.Local.ExternalIP)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("finding client location: %w", err) | |||
| } | |||
| i.cliLocation = findCliLocResp.Location | |||
| } | |||
| if i.currentIndex >= len(i.objects) { | |||
| return nil, ErrNoMoreItem | |||
| } | |||
| item, err := i.doMove(coorCli) | |||
| i.currentIndex++ | |||
| return item, err | |||
| } | |||
| func (i *RepObjectIterator) doMove(coorCli *coormq.PoolClient) (*IterDownloadingObject, error) { | |||
| repData := i.objectRepData[i.currentIndex] | |||
| if len(repData.NodeIDs) == 0 { | |||
| return nil, fmt.Errorf("no node has this file %s", repData.FileHash) | |||
| } | |||
| getNodesResp, err := coorCli.GetNodes(coormq.NewGetNodes(repData.NodeIDs)) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting nodes: %w", err) | |||
| } | |||
| downloadNodes := lo.Map(getNodesResp.Nodes, func(node model.Node, index int) DownloadNodeInfo { | |||
| return DownloadNodeInfo{ | |||
| Node: node, | |||
| IsSameLocation: node.LocationID == i.cliLocation.LocationID, | |||
| } | |||
| }) | |||
| // 选择下载节点 | |||
| downloadNode := i.chooseDownloadNode(downloadNodes) | |||
| // 如果客户端与节点在同一个地域,则使用内网地址连接节点 | |||
| nodeIP := downloadNode.Node.ExternalIP | |||
| if downloadNode.IsSameLocation { | |||
| nodeIP = downloadNode.Node.LocalIP | |||
| logger.Infof("client and node %d are at the same location, use local ip\n", downloadNode.Node.NodeID) | |||
| } | |||
| reader, err := downloadFile(i.downloadCtx, downloadNode.Node.NodeID, nodeIP, repData.FileHash) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("rep read failed, err: %w", err) | |||
| } | |||
| return &IterDownloadingObject{ | |||
| Object: i.objects[i.currentIndex], | |||
| File: reader, | |||
| }, nil | |||
| } | |||
| func (i *RepObjectIterator) Close() { | |||
| if i.OnClosing != nil { | |||
| i.OnClosing() | |||
| } | |||
| } | |||
| // chooseDownloadNode 选择一个下载节点 | |||
| // 1. 从与当前客户端相同地域的节点中随机选一个 | |||
| // 2. 没有用的话从所有节点中随机选一个 | |||
| func (i *RepObjectIterator) chooseDownloadNode(entries []DownloadNodeInfo) DownloadNodeInfo { | |||
| sameLocationEntries := lo.Filter(entries, func(e DownloadNodeInfo, i int) bool { return e.IsSameLocation }) | |||
| if len(sameLocationEntries) > 0 { | |||
| return sameLocationEntries[rand.Intn(len(sameLocationEntries))] | |||
| } | |||
| return entries[rand.Intn(len(entries))] | |||
| } | |||
| func downloadFile(ctx *DownloadContext, nodeID int64, nodeIP string, fileHash string) (io.ReadCloser, error) { | |||
| if globals.IPFSPool != nil { | |||
| logger.Infof("try to use local IPFS to download file") | |||
| reader, err := downloadFromLocalIPFS(ctx, fileHash) | |||
| if err == nil { | |||
| return reader, nil | |||
| } | |||
| logger.Warnf("download from local IPFS failed, so try to download from node %s, err: %s", nodeIP, err.Error()) | |||
| } | |||
| return downloadFromNode(ctx, nodeID, nodeIP, fileHash) | |||
| } | |||
| func downloadFromNode(ctx *DownloadContext, nodeID int64, nodeIP string, fileHash string) (io.ReadCloser, error) { | |||
| // 二次获取锁 | |||
| mutex, err := reqbuilder.NewBuilder(). | |||
| // 用于从IPFS下载文件 | |||
| IPFS().ReadOneRep(nodeID, fileHash). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| // 连接grpc | |||
| agtCli, err := globals.AgentRPCPool.Acquire(nodeIP) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new agent grpc client: %w", err) | |||
| } | |||
| reader, err := agtCli.GetIPFSFile(fileHash) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getting ipfs file: %w", err) | |||
| } | |||
| reader = myio.AfterReadClosed(reader, func(io.ReadCloser) { | |||
| mutex.Unlock() | |||
| }) | |||
| return reader, nil | |||
| } | |||
| func downloadFromLocalIPFS(ctx *DownloadContext, fileHash string) (io.ReadCloser, error) { | |||
| onClosed := func() {} | |||
| if globals.Local.NodeID != nil { | |||
| // 二次获取锁 | |||
| mutex, err := reqbuilder.NewBuilder(). | |||
| // 用于从IPFS下载文件 | |||
| IPFS().ReadOneRep(*globals.Local.NodeID, fileHash). | |||
| MutexLock(ctx.Distlock) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("acquire locks failed, err: %w", err) | |||
| } | |||
| onClosed = func() { | |||
| mutex.Unlock() | |||
| } | |||
| } | |||
| ipfsCli, err := globals.IPFSPool.Acquire() | |||
| if err != nil { | |||
| return nil, fmt.Errorf("new ipfs client: %w", err) | |||
| } | |||
| reader, err := ipfsCli.OpenRead(fileHash) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("read ipfs file failed, err: %w", err) | |||
| } | |||
| reader = myio.AfterReadClosed(reader, func(io.ReadCloser) { | |||
| onClosed() | |||
| }) | |||
| return reader, nil | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| package agent | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| ) | |||
| type AgentService interface { | |||
| GetState(msg *GetState) (*GetStateResp, *mq.CodeMessage) | |||
| } | |||
| // 获取agent状态 | |||
| var _ = Register(AgentService.GetState) | |||
| type GetState struct { | |||
| } | |||
| type GetStateResp struct { | |||
| IPFSState string `json:"ipfsState"` | |||
| } | |||
| func NewGetState() GetState { | |||
| return GetState{} | |||
| } | |||
| func NewGetStateResp(ipfsState string) GetStateResp { | |||
| return GetStateResp{ | |||
| IPFSState: ipfsState, | |||
| } | |||
| } | |||
| func (client *Client) GetState(msg GetState, opts ...mq.RequestOption) (*GetStateResp, error) { | |||
| return mq.Request[GetStateResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| @@ -0,0 +1,108 @@ | |||
| package agent | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type CacheService interface { | |||
| CheckCache(msg *CheckCache) (*CheckCacheResp, *mq.CodeMessage) | |||
| StartCacheMovePackage(msg *StartCacheMovePackage) (*StartCacheMovePackageResp, *mq.CodeMessage) | |||
| WaitCacheMovePackage(msg *WaitCacheMovePackage) (*WaitCacheMovePackageResp, *mq.CodeMessage) | |||
| } | |||
| // 检查节点上的IPFS | |||
| var _ = Register(CacheService.CheckCache) | |||
| const ( | |||
| CHECK_IPFS_RESP_OP_DELETE_TEMP = "DeleteTemp" | |||
| CHECK_IPFS_RESP_OP_CREATE_TEMP = "CreateTemp" | |||
| ) | |||
| type CheckCache struct { | |||
| IsComplete bool `json:"isComplete"` | |||
| Caches []model.Cache `json:"caches"` | |||
| } | |||
| type CheckCacheResp struct { | |||
| Entries []CheckIPFSRespEntry `json:"entries"` | |||
| } | |||
| type CheckIPFSRespEntry struct { | |||
| FileHash string `json:"fileHash"` | |||
| Operation string `json:"operation"` | |||
| } | |||
| func NewCheckCache(isComplete bool, caches []model.Cache) CheckCache { | |||
| return CheckCache{ | |||
| IsComplete: isComplete, | |||
| Caches: caches, | |||
| } | |||
| } | |||
| func NewCheckCacheResp(entries []CheckIPFSRespEntry) CheckCacheResp { | |||
| return CheckCacheResp{ | |||
| Entries: entries, | |||
| } | |||
| } | |||
| func NewCheckCacheRespEntry(fileHash string, op string) CheckIPFSRespEntry { | |||
| return CheckIPFSRespEntry{ | |||
| FileHash: fileHash, | |||
| Operation: op, | |||
| } | |||
| } | |||
| func (client *Client) CheckCache(msg CheckCache, opts ...mq.RequestOption) (*CheckCacheResp, error) { | |||
| return mq.Request[CheckCacheResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 将Package的缓存移动到这个节点 | |||
| var _ = Register(CacheService.StartCacheMovePackage) | |||
| type StartCacheMovePackage struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type StartCacheMovePackageResp struct { | |||
| TaskID string `json:"taskID"` | |||
| } | |||
| func NewStartCacheMovePackage(userID int64, packageID int64) StartCacheMovePackage { | |||
| return StartCacheMovePackage{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewStartCacheMovePackageResp(taskID string) StartCacheMovePackageResp { | |||
| return StartCacheMovePackageResp{ | |||
| TaskID: taskID, | |||
| } | |||
| } | |||
| func (client *Client) StartCacheMovePackage(msg StartCacheMovePackage, opts ...mq.RequestOption) (*StartCacheMovePackageResp, error) { | |||
| return mq.Request[StartCacheMovePackageResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 将Package的缓存移动到这个节点 | |||
| var _ = Register(CacheService.WaitCacheMovePackage) | |||
| type WaitCacheMovePackage struct { | |||
| TaskID string `json:"taskID"` | |||
| WaitTimeoutMs int64 `json:"waitTimeout"` | |||
| } | |||
| type WaitCacheMovePackageResp struct { | |||
| IsComplete bool `json:"isComplete"` | |||
| Error string `json:"error"` | |||
| } | |||
| func NewWaitCacheMovePackage(taskID string, waitTimeoutMs int64) WaitCacheMovePackage { | |||
| return WaitCacheMovePackage{ | |||
| TaskID: taskID, | |||
| WaitTimeoutMs: waitTimeoutMs, | |||
| } | |||
| } | |||
| func NewWaitCacheMovePackageResp(isComplete bool, err string) WaitCacheMovePackageResp { | |||
| return WaitCacheMovePackageResp{ | |||
| IsComplete: isComplete, | |||
| Error: err, | |||
| } | |||
| } | |||
| func (client *Client) WaitCacheMovePackage(msg WaitCacheMovePackage, opts ...mq.RequestOption) (*WaitCacheMovePackageResp, error) { | |||
| return mq.Request[WaitCacheMovePackageResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| package agent | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| stgmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| ) | |||
| type Client struct { | |||
| rabbitCli *mq.RabbitMQClient | |||
| id int64 | |||
| } | |||
| func NewClient(id int64, cfg *stgmq.Config) (*Client, error) { | |||
| rabbitCli, err := mq.NewRabbitMQClient(cfg.MakeConnectingURL(), stgmq.MakeAgentQueueName(id), "") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &Client{ | |||
| rabbitCli: rabbitCli, | |||
| id: id, | |||
| }, nil | |||
| } | |||
| func (c *Client) Close() { | |||
| c.rabbitCli.Close() | |||
| } | |||
| type PoolClient struct { | |||
| *Client | |||
| owner *Pool | |||
| } | |||
| func (c *PoolClient) Close() { | |||
| c.owner.Release(c) | |||
| } | |||
| type Pool struct { | |||
| mqcfg *stgmq.Config | |||
| } | |||
| func NewPool(mqcfg *stgmq.Config) *Pool { | |||
| return &Pool{ | |||
| mqcfg: mqcfg, | |||
| } | |||
| } | |||
| func (p *Pool) Acquire(id int64) (*PoolClient, error) { | |||
| cli, err := NewClient(id, p.mqcfg) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &PoolClient{ | |||
| Client: cli, | |||
| owner: p, | |||
| }, nil | |||
| } | |||
| func (p *Pool) Release(cli *PoolClient) { | |||
| cli.Client.Close() | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package agent | |||
| import "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| type ObjectService interface { | |||
| StartPinningObject(msg *StartPinningObject) (*StartPinningObjectResp, *mq.CodeMessage) | |||
| WaitPinningObject(msg *WaitPinningObject) (*WaitPinningObjectResp, *mq.CodeMessage) | |||
| } | |||
| // 启动Pin对象的任务 | |||
| var _ = Register(ObjectService.StartPinningObject) | |||
| type StartPinningObject struct { | |||
| FileHash string `json:"fileHash"` | |||
| } | |||
| type StartPinningObjectResp struct { | |||
| TaskID string `json:"taskID"` | |||
| } | |||
| func NewStartPinningObject(fileHash string) StartPinningObject { | |||
| return StartPinningObject{ | |||
| FileHash: fileHash, | |||
| } | |||
| } | |||
| func NewStartPinningObjectResp(taskID string) StartPinningObjectResp { | |||
| return StartPinningObjectResp{ | |||
| TaskID: taskID, | |||
| } | |||
| } | |||
| func (client *Client) StartPinningObject(msg StartPinningObject, opts ...mq.RequestOption) (*StartPinningObjectResp, error) { | |||
| return mq.Request[StartPinningObjectResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 等待Pin对象的任务 | |||
| var _ = Register(ObjectService.WaitPinningObject) | |||
| type WaitPinningObject struct { | |||
| TaskID string `json:"taskID"` | |||
| WaitTimeoutMs int64 `json:"waitTimeout"` | |||
| } | |||
| type WaitPinningObjectResp struct { | |||
| IsComplete bool `json:"isComplete"` | |||
| Error string `json:"error"` | |||
| } | |||
| func NewWaitPinningObject(taskID string, waitTimeoutMs int64) WaitPinningObject { | |||
| return WaitPinningObject{ | |||
| TaskID: taskID, | |||
| WaitTimeoutMs: waitTimeoutMs, | |||
| } | |||
| } | |||
| func NewWaitPinningObjectResp(isComplete bool, err string) WaitPinningObjectResp { | |||
| return WaitPinningObjectResp{ | |||
| IsComplete: isComplete, | |||
| Error: err, | |||
| } | |||
| } | |||
| func (client *Client) WaitPinningObject(msg WaitPinningObject, opts ...mq.RequestOption) (*WaitPinningObjectResp, error) { | |||
| return mq.Request[WaitPinningObjectResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| @@ -0,0 +1,73 @@ | |||
| package agent | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| mymq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| ) | |||
| type Service interface { | |||
| ObjectService | |||
| StorageService | |||
| CacheService | |||
| AgentService | |||
| } | |||
| type Server struct { | |||
| service Service | |||
| rabbitSvr mq.RabbitMQServer | |||
| OnError func(err error) | |||
| } | |||
| func NewServer(svc Service, id int64, cfg *mymq.Config) (*Server, error) { | |||
| srv := &Server{ | |||
| service: svc, | |||
| } | |||
| rabbitSvr, err := mq.NewRabbitMQServer( | |||
| cfg.MakeConnectingURL(), | |||
| mymq.MakeAgentQueueName(id), | |||
| func(msg *mq.Message) (*mq.Message, error) { | |||
| return msgDispatcher.Handle(srv.service, msg) | |||
| }, | |||
| ) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| srv.rabbitSvr = *rabbitSvr | |||
| return srv, nil | |||
| } | |||
| func (s *Server) Stop() { | |||
| s.rabbitSvr.Close() | |||
| } | |||
| func (s *Server) Serve() error { | |||
| return s.rabbitSvr.Serve() | |||
| } | |||
| var msgDispatcher mq.MessageDispatcher = mq.NewMessageDispatcher() | |||
| // Register 将Service中的一个接口函数作为指定类型消息的处理函数,同时会注册请求和响应的消息类型 | |||
| // TODO 需要约束:Service实现了TSvc接口 | |||
| func Register[TSvc any, TReq any, TResp any](svcFn func(svc TSvc, msg *TReq) (*TResp, *mq.CodeMessage)) any { | |||
| mq.AddServiceFn(&msgDispatcher, svcFn) | |||
| mq.RegisterMessage[TReq]() | |||
| mq.RegisterMessage[TResp]() | |||
| return nil | |||
| } | |||
| // RegisterNoReply 将Service中的一个*没有返回值的*接口函数作为指定类型消息的处理函数,同时会注册请求和响应的消息类型 | |||
| // TODO 需要约束:Service实现了TSvc接口 | |||
| func RegisterNoReply[TSvc any, TReq any](svcFn func(svc TSvc, msg *TReq)) any { | |||
| mq.AddNoRespServiceFn(&msgDispatcher, svcFn) | |||
| mq.RegisterMessage[TReq]() | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,188 @@ | |||
| package agent | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type StorageService interface { | |||
| StartStorageLoadPackage(msg *StartStorageLoadPackage) (*StartStorageLoadPackageResp, *mq.CodeMessage) | |||
| WaitStorageLoadPackage(msg *WaitStorageLoadPackage) (*WaitStorageLoadPackageResp, *mq.CodeMessage) | |||
| StorageCheck(msg *StorageCheck) (*StorageCheckResp, *mq.CodeMessage) | |||
| StartStorageCreatePackage(msg *StartStorageCreatePackage) (*StartStorageCreatePackageResp, *mq.CodeMessage) | |||
| WaitStorageCreatePackage(msg *WaitStorageCreatePackage) (*WaitStorageCreatePackageResp, *mq.CodeMessage) | |||
| } | |||
| // 启动调度Package的任务 | |||
| var _ = Register(StorageService.StartStorageLoadPackage) | |||
| type StartStorageLoadPackage struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| StorageID int64 `json:"storageID"` | |||
| } | |||
| type StartStorageLoadPackageResp struct { | |||
| TaskID string `json:"taskID"` | |||
| } | |||
| func NewStartStorageLoadPackage(userID int64, packageID int64, storageID int64) StartStorageLoadPackage { | |||
| return StartStorageLoadPackage{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| StorageID: storageID, | |||
| } | |||
| } | |||
| func NewStartStorageLoadPackageResp(taskID string) StartStorageLoadPackageResp { | |||
| return StartStorageLoadPackageResp{ | |||
| TaskID: taskID, | |||
| } | |||
| } | |||
| func (client *Client) StartStorageLoadPackage(msg StartStorageLoadPackage, opts ...mq.RequestOption) (*StartStorageLoadPackageResp, error) { | |||
| return mq.Request[StartStorageLoadPackageResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 等待调度Package的任务 | |||
| var _ = Register(StorageService.WaitStorageLoadPackage) | |||
| type WaitStorageLoadPackage struct { | |||
| TaskID string `json:"taskID"` | |||
| WaitTimeoutMs int64 `json:"waitTimeout"` | |||
| } | |||
| type WaitStorageLoadPackageResp struct { | |||
| IsComplete bool `json:"isComplete"` | |||
| Error string `json:"error"` | |||
| } | |||
| func NewWaitStorageLoadPackage(taskID string, waitTimeoutMs int64) WaitStorageLoadPackage { | |||
| return WaitStorageLoadPackage{ | |||
| TaskID: taskID, | |||
| WaitTimeoutMs: waitTimeoutMs, | |||
| } | |||
| } | |||
| func NewWaitStorageLoadPackageResp(isComplete bool, err string) WaitStorageLoadPackageResp { | |||
| return WaitStorageLoadPackageResp{ | |||
| IsComplete: isComplete, | |||
| Error: err, | |||
| } | |||
| } | |||
| func (client *Client) WaitStorageLoadPackage(msg WaitStorageLoadPackage, opts ...mq.RequestOption) (*WaitStorageLoadPackageResp, error) { | |||
| return mq.Request[WaitStorageLoadPackageResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 检查Storage | |||
| var _ = Register(StorageService.StorageCheck) | |||
| const ( | |||
| CHECK_STORAGE_RESP_OP_DELETE = "Delete" | |||
| CHECK_STORAGE_RESP_OP_SET_NORMAL = "SetNormal" | |||
| ) | |||
| type StorageCheck struct { | |||
| StorageID int64 `json:"storageID"` | |||
| Directory string `json:"directory"` | |||
| IsComplete bool `json:"isComplete"` | |||
| Packages []model.StoragePackage `json:"packages"` | |||
| } | |||
| type StorageCheckResp struct { | |||
| DirectoryState string `json:"directoryState"` | |||
| Entries []StorageCheckRespEntry `json:"entries"` | |||
| } | |||
| type StorageCheckRespEntry struct { | |||
| PackageID int64 `json:"packageID"` | |||
| UserID int64 `json:"userID"` | |||
| Operation string `json:"operation"` | |||
| } | |||
| func NewStorageCheck(storageID int64, directory string, isComplete bool, packages []model.StoragePackage) StorageCheck { | |||
| return StorageCheck{ | |||
| StorageID: storageID, | |||
| Directory: directory, | |||
| IsComplete: isComplete, | |||
| Packages: packages, | |||
| } | |||
| } | |||
| func NewStorageCheckResp(dirState string, entries []StorageCheckRespEntry) StorageCheckResp { | |||
| return StorageCheckResp{ | |||
| DirectoryState: dirState, | |||
| Entries: entries, | |||
| } | |||
| } | |||
| func NewStorageCheckRespEntry(packageID int64, userID int64, op string) StorageCheckRespEntry { | |||
| return StorageCheckRespEntry{ | |||
| PackageID: packageID, | |||
| UserID: userID, | |||
| Operation: op, | |||
| } | |||
| } | |||
| func (client *Client) StorageCheck(msg StorageCheck, opts ...mq.RequestOption) (*StorageCheckResp, error) { | |||
| return mq.Request[StorageCheckResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 启动从Storage上传Package的任务 | |||
| var _ = Register(StorageService.StartStorageCreatePackage) | |||
| type StartStorageCreatePackage struct { | |||
| UserID int64 `json:"userID"` | |||
| BucketID int64 `json:"bucketID"` | |||
| Name string `json:"name"` | |||
| StorageID int64 `json:"storageID"` | |||
| Path string `json:"path"` | |||
| Redundancy models.TypedRedundancyInfo `json:"redundancy"` | |||
| } | |||
| type StartStorageCreatePackageResp struct { | |||
| TaskID string `json:"taskID"` | |||
| } | |||
| func NewStartStorageCreatePackage(userID int64, bucketID int64, name string, storageID int64, path string, redundancy models.TypedRedundancyInfo) StartStorageCreatePackage { | |||
| return StartStorageCreatePackage{ | |||
| UserID: userID, | |||
| BucketID: bucketID, | |||
| Name: name, | |||
| StorageID: storageID, | |||
| Path: path, | |||
| Redundancy: redundancy, | |||
| } | |||
| } | |||
| func NewStartStorageCreatePackageResp(taskID string) StartStorageCreatePackageResp { | |||
| return StartStorageCreatePackageResp{ | |||
| TaskID: taskID, | |||
| } | |||
| } | |||
| func (client *Client) StartStorageCreatePackage(msg StartStorageCreatePackage, opts ...mq.RequestOption) (*StartStorageCreatePackageResp, error) { | |||
| return mq.Request[StartStorageCreatePackageResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| // 等待从Storage上传Package的任务 | |||
| var _ = Register(StorageService.WaitStorageCreatePackage) | |||
| type WaitStorageCreatePackage struct { | |||
| TaskID string `json:"taskID"` | |||
| WaitTimeoutMs int64 `json:"waitTimeout"` | |||
| } | |||
| type WaitStorageCreatePackageResp struct { | |||
| IsComplete bool `json:"isComplete"` | |||
| Error string `json:"error"` | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| func NewWaitStorageCreatePackage(taskID string, waitTimeoutMs int64) WaitStorageCreatePackage { | |||
| return WaitStorageCreatePackage{ | |||
| TaskID: taskID, | |||
| WaitTimeoutMs: waitTimeoutMs, | |||
| } | |||
| } | |||
| func NewWaitStorageCreatePackageResp(isComplete bool, err string, packageID int64) WaitStorageCreatePackageResp { | |||
| return WaitStorageCreatePackageResp{ | |||
| IsComplete: isComplete, | |||
| Error: err, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func (client *Client) WaitStorageCreatePackage(msg WaitStorageCreatePackage, opts ...mq.RequestOption) (*WaitStorageCreatePackageResp, error) { | |||
| return mq.Request[WaitStorageCreatePackageResp](client.rabbitCli, msg, opts...) | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package mq | |||
| import "fmt" | |||
| type Config struct { | |||
| Address string `json:"address"` | |||
| Account string `json:"account"` | |||
| Password string `json:"password"` | |||
| VHost string `json:"vhost"` | |||
| } | |||
| func (cfg *Config) MakeConnectingURL() string { | |||
| return fmt.Sprintf("amqp://%s:%s@%s%s", cfg.Account, cfg.Password, cfg.Address, cfg.VHost) | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| package mq | |||
| import "fmt" | |||
| const ( | |||
| COORDINATOR_QUEUE_NAME = "Coordinator" | |||
| SCANNER_QUEUE_NAME = "Scanner" | |||
| ) | |||
| func MakeAgentQueueName(id int64) string { | |||
| return fmt.Sprintf("Agent@%d", id) | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| package coordinator | |||
| import "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| type AgentService interface { | |||
| TempCacheReport(msg *TempCacheReport) | |||
| AgentStatusReport(msg *AgentStatusReport) | |||
| } | |||
| // 代理端发给协调端,告知临时缓存的数据 | |||
| var _ = RegisterNoReply(AgentService.TempCacheReport) | |||
| type TempCacheReport struct { | |||
| NodeID int64 `json:"nodeID"` | |||
| Hashes []string `json:"hashes"` | |||
| } | |||
| func NewTempCacheReportBody(nodeID int64, hashes []string) TempCacheReport { | |||
| return TempCacheReport{ | |||
| NodeID: nodeID, | |||
| Hashes: hashes, | |||
| } | |||
| } | |||
| func (client *Client) TempCacheReport(msg TempCacheReport) error { | |||
| return mq.Send(client.rabbitCli, msg) | |||
| } | |||
| // 代理端发给协调端,告知延迟、ipfs和资源目录的可达性 | |||
| var _ = RegisterNoReply(AgentService.AgentStatusReport) | |||
| type AgentStatusReport struct { | |||
| NodeID int64 `json:"nodeID"` | |||
| NodeDelayIDs []int64 `json:"nodeDelayIDs"` | |||
| NodeDelays []int `json:"nodeDelays"` | |||
| IPFSStatus string `json:"ipfsStatus"` | |||
| LocalDirStatus string `json:"localDirStatus"` | |||
| } | |||
| func NewAgentStatusReportBody(nodeID int64, nodeDelayIDs []int64, nodeDelays []int, ipfsStatus string, localDirStatus string) AgentStatusReport { | |||
| return AgentStatusReport{ | |||
| NodeID: nodeID, | |||
| NodeDelayIDs: nodeDelayIDs, | |||
| NodeDelays: nodeDelays, | |||
| IPFSStatus: ipfsStatus, | |||
| LocalDirStatus: localDirStatus, | |||
| } | |||
| } | |||
| func (client *Client) AgentStatusReport(msg AgentStatusReport) error { | |||
| return mq.Send(client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type BucketService interface { | |||
| GetUserBuckets(msg *GetUserBuckets) (*GetUserBucketsResp, *mq.CodeMessage) | |||
| GetBucketPackages(msg *GetBucketPackages) (*GetBucketPackagesResp, *mq.CodeMessage) | |||
| CreateBucket(msg *CreateBucket) (*CreateBucketResp, *mq.CodeMessage) | |||
| DeleteBucket(msg *DeleteBucket) (*DeleteBucketResp, *mq.CodeMessage) | |||
| } | |||
| // 获取用户所有的桶 | |||
| var _ = Register(BucketService.GetUserBuckets) | |||
| type GetUserBuckets struct { | |||
| UserID int64 `json:"userID"` | |||
| } | |||
| type GetUserBucketsResp struct { | |||
| Buckets []model.Bucket `json:"buckets"` | |||
| } | |||
| func NewGetUserBuckets(userID int64) GetUserBuckets { | |||
| return GetUserBuckets{ | |||
| UserID: userID, | |||
| } | |||
| } | |||
| func NewGetUserBucketsResp(buckets []model.Bucket) GetUserBucketsResp { | |||
| return GetUserBucketsResp{ | |||
| Buckets: buckets, | |||
| } | |||
| } | |||
| func (client *Client) GetUserBuckets(msg GetUserBuckets) (*GetUserBucketsResp, error) { | |||
| return mq.Request[GetUserBucketsResp](client.rabbitCli, msg) | |||
| } | |||
| // 获取桶中的所有Package | |||
| var _ = Register(BucketService.GetBucketPackages) | |||
| type GetBucketPackages struct { | |||
| UserID int64 `json:"userID"` | |||
| BucketID int64 `json:"bucketID"` | |||
| } | |||
| type GetBucketPackagesResp struct { | |||
| Packages []model.Package `json:"packages"` | |||
| } | |||
| func NewGetBucketPackages(userID int64, bucketID int64) GetBucketPackages { | |||
| return GetBucketPackages{ | |||
| UserID: userID, | |||
| BucketID: bucketID, | |||
| } | |||
| } | |||
| func NewGetBucketPackagesResp(packages []model.Package) GetBucketPackagesResp { | |||
| return GetBucketPackagesResp{ | |||
| Packages: packages, | |||
| } | |||
| } | |||
| func (client *Client) GetBucketPackages(msg GetBucketPackages) (*GetBucketPackagesResp, error) { | |||
| return mq.Request[GetBucketPackagesResp](client.rabbitCli, msg) | |||
| } | |||
| // 创建桶 | |||
| var _ = Register(BucketService.CreateBucket) | |||
| type CreateBucket struct { | |||
| UserID int64 `json:"userID"` | |||
| BucketName string `json:"bucketName"` | |||
| } | |||
| type CreateBucketResp struct { | |||
| BucketID int64 `json:"bucketID"` | |||
| } | |||
| func NewCreateBucket(userID int64, bucketName string) CreateBucket { | |||
| return CreateBucket{ | |||
| UserID: userID, | |||
| BucketName: bucketName, | |||
| } | |||
| } | |||
| func NewCreateBucketResp(bucketID int64) CreateBucketResp { | |||
| return CreateBucketResp{ | |||
| BucketID: bucketID, | |||
| } | |||
| } | |||
| func (client *Client) CreateBucket(msg CreateBucket) (*CreateBucketResp, error) { | |||
| return mq.Request[CreateBucketResp](client.rabbitCli, msg) | |||
| } | |||
| // 删除桶 | |||
| var _ = Register(BucketService.DeleteBucket) | |||
| type DeleteBucket struct { | |||
| UserID int64 `json:"userID"` | |||
| BucketID int64 `json:"bucketID"` | |||
| } | |||
| type DeleteBucketResp struct{} | |||
| func NewDeleteBucket(userID int64, bucketID int64) DeleteBucket { | |||
| return DeleteBucket{ | |||
| UserID: userID, | |||
| BucketID: bucketID, | |||
| } | |||
| } | |||
| func NewDeleteBucketResp() DeleteBucketResp { | |||
| return DeleteBucketResp{} | |||
| } | |||
| func (client *Client) DeleteBucket(msg DeleteBucket) (*DeleteBucketResp, error) { | |||
| return mq.Request[DeleteBucketResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| ) | |||
| type CacheService interface { | |||
| CachePackageMoved(msg *CachePackageMoved) (*CachePackageMovedResp, *mq.CodeMessage) | |||
| } | |||
| // Package的Object移动到了节点的Cache中 | |||
| var _ = Register(CacheService.CachePackageMoved) | |||
| type CachePackageMoved struct { | |||
| PackageID int64 `json:"packageID"` | |||
| NodeID int64 `json:"nodeID"` | |||
| FileHashes []string `json:"fileHashes"` | |||
| } | |||
| type CachePackageMovedResp struct{} | |||
| func NewCachePackageMoved(packageID int64, nodeID int64, fileHashes []string) CachePackageMoved { | |||
| return CachePackageMoved{ | |||
| PackageID: packageID, | |||
| NodeID: nodeID, | |||
| FileHashes: fileHashes, | |||
| } | |||
| } | |||
| func NewCachePackageMovedResp() CachePackageMovedResp { | |||
| return CachePackageMovedResp{} | |||
| } | |||
| func (client *Client) CachePackageMoved(msg CachePackageMoved) (*CachePackageMovedResp, error) { | |||
| return mq.Request[CachePackageMovedResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| stgmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| ) | |||
| type Client struct { | |||
| rabbitCli *mq.RabbitMQClient | |||
| } | |||
| func NewClient(cfg *stgmq.Config) (*Client, error) { | |||
| rabbitCli, err := mq.NewRabbitMQClient(cfg.MakeConnectingURL(), stgmq.COORDINATOR_QUEUE_NAME, "") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &Client{ | |||
| rabbitCli: rabbitCli, | |||
| }, nil | |||
| } | |||
| func (c *Client) Close() { | |||
| c.rabbitCli.Close() | |||
| } | |||
| type PoolClient struct { | |||
| *Client | |||
| owner *Pool | |||
| } | |||
| func (c *PoolClient) Close() { | |||
| c.owner.Release(c) | |||
| } | |||
| type Pool struct { | |||
| mqcfg *stgmq.Config | |||
| } | |||
| func NewPool(mqcfg *stgmq.Config) *Pool { | |||
| return &Pool{ | |||
| mqcfg: mqcfg, | |||
| } | |||
| } | |||
| func (p *Pool) Acquire() (*PoolClient, error) { | |||
| cli, err := NewClient(p.mqcfg) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &PoolClient{ | |||
| Client: cli, | |||
| owner: p, | |||
| }, nil | |||
| } | |||
| func (p *Pool) Release(cli *PoolClient) { | |||
| cli.Client.Close() | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type CommonService interface { | |||
| FindClientLocation(msg *FindClientLocation) (*FindClientLocationResp, *mq.CodeMessage) | |||
| GetECConfig(msg *GetECConfig) (*GetECConfigResp, *mq.CodeMessage) | |||
| } | |||
| // 查询指定IP所属的地域 | |||
| var _ = Register(CommonService.FindClientLocation) | |||
| type FindClientLocation struct { | |||
| IP string `json:"ip"` | |||
| } | |||
| type FindClientLocationResp struct { | |||
| Location model.Location `json:"location"` | |||
| } | |||
| func NewFindClientLocation(ip string) FindClientLocation { | |||
| return FindClientLocation{ | |||
| IP: ip, | |||
| } | |||
| } | |||
| func NewFindClientLocationResp(location model.Location) FindClientLocationResp { | |||
| return FindClientLocationResp{ | |||
| Location: location, | |||
| } | |||
| } | |||
| func (client *Client) FindClientLocation(msg FindClientLocation) (*FindClientLocationResp, error) { | |||
| return mq.Request[FindClientLocationResp](client.rabbitCli, msg) | |||
| } | |||
| // 获取EC具体配置 | |||
| var _ = Register(CommonService.GetECConfig) | |||
| type GetECConfig struct { | |||
| ECName string `json:"ecName"` | |||
| } | |||
| type GetECConfigResp struct { | |||
| Config model.Ec `json:"config"` | |||
| } | |||
| func NewGetECConfig(ecName string) GetECConfig { | |||
| return GetECConfig{ | |||
| ECName: ecName, | |||
| } | |||
| } | |||
| func NewGetECConfigResp(config model.Ec) GetECConfigResp { | |||
| return GetECConfigResp{ | |||
| Config: config, | |||
| } | |||
| } | |||
| func (client *Client) GetECConfig(msg GetECConfig) (*GetECConfigResp, error) { | |||
| return mq.Request[GetECConfigResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package coordinator | |||
| import ( | |||
| "testing" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| ) | |||
| func TestSerder(t *testing.T) { | |||
| Convey("输出注册的Handler", t, func() { | |||
| for k, _ := range msgDispatcher.Handlers { | |||
| t.Logf("(%s)", k) | |||
| } | |||
| }) | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type NodeService interface { | |||
| GetUserNodes(msg *GetUserNodes) (*GetUserNodesResp, *mq.CodeMessage) | |||
| GetNodes(msg *GetNodes) (*GetNodesResp, *mq.CodeMessage) | |||
| } | |||
| // 查询用户可用的节点 | |||
| var _ = Register(NodeService.GetUserNodes) | |||
| type GetUserNodes struct { | |||
| UserID int64 `json:"userID"` | |||
| } | |||
| type GetUserNodesResp struct { | |||
| Nodes []model.Node `json:"nodes"` | |||
| } | |||
| func NewGetUserNodes(userID int64) GetUserNodes { | |||
| return GetUserNodes{ | |||
| UserID: userID, | |||
| } | |||
| } | |||
| func NewGetUserNodesResp(nodes []model.Node) GetUserNodesResp { | |||
| return GetUserNodesResp{ | |||
| Nodes: nodes, | |||
| } | |||
| } | |||
| func (client *Client) GetUserNodes(msg GetUserNodes) (*GetUserNodesResp, error) { | |||
| return mq.Request[GetUserNodesResp](client.rabbitCli, msg) | |||
| } | |||
| // 获取指定节点的信息 | |||
| var _ = Register(NodeService.GetNodes) | |||
| type GetNodes struct { | |||
| NodeIDs []int64 `json:"nodeIDs"` | |||
| } | |||
| type GetNodesResp struct { | |||
| Nodes []model.Node `json:"nodes"` | |||
| } | |||
| func NewGetNodes(nodeIDs []int64) GetNodes { | |||
| return GetNodes{ | |||
| NodeIDs: nodeIDs, | |||
| } | |||
| } | |||
| func NewGetNodesResp(nodes []model.Node) GetNodesResp { | |||
| return GetNodesResp{ | |||
| Nodes: nodes, | |||
| } | |||
| } | |||
| func (client *Client) GetNodes(msg GetNodes) (*GetNodesResp, error) { | |||
| return mq.Request[GetNodesResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/models" | |||
| ) | |||
| type ObjectService interface { | |||
| GetPackageObjectRepData(msg *GetPackageObjectRepData) (*GetPackageObjectRepDataResp, *mq.CodeMessage) | |||
| GetPackageObjectECData(msg *GetPackageObjectECData) (*GetPackageObjectECDataResp, *mq.CodeMessage) | |||
| } | |||
| // 获取指定Object的Rep数据,返回的Objects会按照ObjectID升序 | |||
| var _ = Register(ObjectService.GetPackageObjectRepData) | |||
| type GetPackageObjectRepData struct { | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type GetPackageObjectRepDataResp struct { | |||
| Data []models.ObjectRepData `json:"data"` | |||
| } | |||
| func NewGetPackageObjectRepData(packageID int64) GetPackageObjectRepData { | |||
| return GetPackageObjectRepData{ | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewGetPackageObjectRepDataResp(data []models.ObjectRepData) GetPackageObjectRepDataResp { | |||
| return GetPackageObjectRepDataResp{ | |||
| Data: data, | |||
| } | |||
| } | |||
| func (client *Client) GetPackageObjectRepData(msg GetPackageObjectRepData) (*GetPackageObjectRepDataResp, error) { | |||
| return mq.Request[GetPackageObjectRepDataResp](client.rabbitCli, msg) | |||
| } | |||
| // 获取指定Object的EC数据,返回的Objects会按照ObjectID升序 | |||
| var _ = Register(ObjectService.GetPackageObjectECData) | |||
| type GetPackageObjectECData struct { | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type GetPackageObjectECDataResp struct { | |||
| Data []models.ObjectECData `json:"data"` | |||
| } | |||
| func NewGetPackageObjectECData(packageID int64) GetPackageObjectECData { | |||
| return GetPackageObjectECData{ | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewGetPackageObjectECDataResp(data []models.ObjectECData) GetPackageObjectECDataResp { | |||
| return GetPackageObjectECDataResp{ | |||
| Data: data, | |||
| } | |||
| } | |||
| func (client *Client) GetPackageObjectECData(msg GetPackageObjectECData) (*GetPackageObjectECDataResp, error) { | |||
| return mq.Request[GetPackageObjectECDataResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,273 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/models" | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type PackageService interface { | |||
| GetPackage(msg *GetPackage) (*GetPackageResp, *mq.CodeMessage) | |||
| GetPackageObjects(msg *GetPackageObjects) (*GetPackageObjectsResp, *mq.CodeMessage) | |||
| CreatePackage(msg *CreatePackage) (*CreatePackageResp, *mq.CodeMessage) | |||
| UpdateRepPackage(msg *UpdateRepPackage) (*UpdateRepPackageResp, *mq.CodeMessage) | |||
| UpdateECPackage(msg *UpdateECPackage) (*UpdateECPackageResp, *mq.CodeMessage) | |||
| DeletePackage(msg *DeletePackage) (*DeletePackageResp, *mq.CodeMessage) | |||
| GetPackageCachedNodes(msg *GetPackageCachedNodes) (*GetPackageCachedNodesResp, *mq.CodeMessage) | |||
| GetPackageLoadedNodes(msg *GetPackageLoadedNodes) (*GetPackageLoadedNodesResp, *mq.CodeMessage) | |||
| } | |||
| // 获取Package基本信息 | |||
| var _ = Register(PackageService.GetPackage) | |||
| type GetPackage struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type GetPackageResp struct { | |||
| model.Package | |||
| } | |||
| func NewGetPackage(userID int64, packageID int64) GetPackage { | |||
| return GetPackage{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewGetPackageResp(pkg model.Package) GetPackageResp { | |||
| return GetPackageResp{ | |||
| Package: pkg, | |||
| } | |||
| } | |||
| func (client *Client) GetPackage(msg GetPackage) (*GetPackageResp, error) { | |||
| return mq.Request[GetPackageResp](client.rabbitCli, msg) | |||
| } | |||
| // 查询Package中的所有Object,返回的Objects会按照ObjectID升序 | |||
| var _ = Register(PackageService.GetPackageObjects) | |||
| type GetPackageObjects struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type GetPackageObjectsResp struct { | |||
| Objects []model.Object `json:"objects"` | |||
| } | |||
| func NewGetPackageObjects(userID int64, packageID int64) GetPackageObjects { | |||
| return GetPackageObjects{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewGetPackageObjectsResp(objects []model.Object) GetPackageObjectsResp { | |||
| return GetPackageObjectsResp{ | |||
| Objects: objects, | |||
| } | |||
| } | |||
| func (client *Client) GetPackageObjects(msg GetPackageObjects) (*GetPackageObjectsResp, error) { | |||
| return mq.Request[GetPackageObjectsResp](client.rabbitCli, msg) | |||
| } | |||
| // 创建一个Package | |||
| var _ = Register(PackageService.CreatePackage) | |||
| type CreatePackage struct { | |||
| UserID int64 `json:"userID"` | |||
| BucketID int64 `json:"bucketID"` | |||
| Name string `json:"name"` | |||
| Redundancy models.TypedRedundancyInfo `json:"redundancy"` | |||
| } | |||
| type CreatePackageResp struct { | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| func NewCreatePackage(userID int64, bucketID int64, name string, redundancy models.TypedRedundancyInfo) CreatePackage { | |||
| return CreatePackage{ | |||
| UserID: userID, | |||
| BucketID: bucketID, | |||
| Name: name, | |||
| Redundancy: redundancy, | |||
| } | |||
| } | |||
| func NewCreatePackageResp(packageID int64) CreatePackageResp { | |||
| return CreatePackageResp{ | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func (client *Client) CreatePackage(msg CreatePackage) (*CreatePackageResp, error) { | |||
| return mq.Request[CreatePackageResp](client.rabbitCli, msg) | |||
| } | |||
| // 更新Rep备份模式的Package | |||
| var _ = Register(PackageService.UpdateRepPackage) | |||
| type UpdateRepPackage struct { | |||
| PackageID int64 `json:"packageID"` | |||
| Adds []AddRepObjectInfo `json:"objects"` | |||
| Deletes []int64 `json:"deletes"` | |||
| } | |||
| type UpdateRepPackageResp struct{} | |||
| type AddRepObjectInfo struct { | |||
| Path string `json:"path"` | |||
| Size int64 `json:"size,string"` | |||
| FileHash string `json:"fileHash"` | |||
| NodeIDs []int64 `json:"nodeIDs"` | |||
| } | |||
| func NewUpdateRepPackage(packageID int64, adds []AddRepObjectInfo, deletes []int64) UpdateRepPackage { | |||
| return UpdateRepPackage{ | |||
| PackageID: packageID, | |||
| Adds: adds, | |||
| Deletes: deletes, | |||
| } | |||
| } | |||
| func NewUpdateRepPackageResp() UpdateRepPackageResp { | |||
| return UpdateRepPackageResp{} | |||
| } | |||
| func NewAddRepObjectInfo(path string, size int64, fileHash string, nodeIDs []int64) AddRepObjectInfo { | |||
| return AddRepObjectInfo{ | |||
| Path: path, | |||
| Size: size, | |||
| FileHash: fileHash, | |||
| NodeIDs: nodeIDs, | |||
| } | |||
| } | |||
| func (client *Client) UpdateRepPackage(msg UpdateRepPackage) (*UpdateRepPackageResp, error) { | |||
| return mq.Request[UpdateRepPackageResp](client.rabbitCli, msg) | |||
| } | |||
| // 更新EC备份模式的Package | |||
| var _ = Register(PackageService.UpdateECPackage) | |||
| type UpdateECPackage struct { | |||
| PackageID int64 `json:"packageID"` | |||
| Adds []AddECObjectInfo `json:"objects"` | |||
| Deletes []int64 `json:"deletes"` | |||
| } | |||
| type UpdateECPackageResp struct{} | |||
| type AddECObjectInfo struct { | |||
| Path string `json:"path"` | |||
| Size int64 `json:"size,string"` | |||
| FileHashes []string `json:"fileHashes"` | |||
| NodeIDs []int64 `json:"nodeIDs"` | |||
| } | |||
| func NewUpdateECPackage(packageID int64, adds []AddECObjectInfo, deletes []int64) UpdateECPackage { | |||
| return UpdateECPackage{ | |||
| PackageID: packageID, | |||
| Adds: adds, | |||
| Deletes: deletes, | |||
| } | |||
| } | |||
| func NewUpdateECPackageResp() UpdateECPackageResp { | |||
| return UpdateECPackageResp{} | |||
| } | |||
| func NewAddECObjectInfo(path string, size int64, fileHashes []string, nodeIDs []int64) AddECObjectInfo { | |||
| return AddECObjectInfo{ | |||
| Path: path, | |||
| Size: size, | |||
| FileHashes: fileHashes, | |||
| NodeIDs: nodeIDs, | |||
| } | |||
| } | |||
| func (client *Client) UpdateECPackage(msg UpdateECPackage) (*UpdateECPackageResp, error) { | |||
| return mq.Request[UpdateECPackageResp](client.rabbitCli, msg) | |||
| } | |||
| // 删除对象 | |||
| var _ = Register(PackageService.DeletePackage) | |||
| type DeletePackage struct { | |||
| UserID int64 `db:"userID"` | |||
| PackageID int64 `db:"packageID"` | |||
| } | |||
| type DeletePackageResp struct{} | |||
| func NewDeletePackage(userID int64, packageID int64) DeletePackage { | |||
| return DeletePackage{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewDeletePackageResp() DeletePackageResp { | |||
| return DeletePackageResp{} | |||
| } | |||
| func (client *Client) DeletePackage(msg DeletePackage) (*DeletePackageResp, error) { | |||
| return mq.Request[DeletePackageResp](client.rabbitCli, msg) | |||
| } | |||
| // 根据PackageID获取object分布情况 | |||
| var _ = Register(PackageService.GetPackageCachedNodes) | |||
| type GetPackageCachedNodes struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type PackageCachedNodeInfo struct { | |||
| NodeID int64 `json:"nodeID"` | |||
| FileSize int64 `json:"fileSize"` | |||
| ObjectCount int64 `json:"objectCount"` | |||
| } | |||
| type GetPackageCachedNodesResp struct { | |||
| models.PackageCachingInfo | |||
| } | |||
| func NewGetPackageCachedNodes(userID int64, packageID int64) GetPackageCachedNodes { | |||
| return GetPackageCachedNodes{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewGetPackageCachedNodesResp(nodeInfos []models.NodePackageCachingInfo, packageSize int64, redunancyType string) GetPackageCachedNodesResp { | |||
| return GetPackageCachedNodesResp{ | |||
| PackageCachingInfo: models.PackageCachingInfo{ | |||
| NodeInfos: nodeInfos, | |||
| PackageSize: packageSize, | |||
| RedunancyType: redunancyType, | |||
| }, | |||
| } | |||
| } | |||
| func (client *Client) GetPackageCachedNodes(msg GetPackageCachedNodes) (*GetPackageCachedNodesResp, error) { | |||
| return mq.Request[GetPackageCachedNodesResp](client.rabbitCli, msg) | |||
| } | |||
| // 根据PackageID获取storage分布情况 | |||
| var _ = Register(PackageService.GetPackageLoadedNodes) | |||
| type GetPackageLoadedNodes struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| } | |||
| type GetPackageLoadedNodesResp struct { | |||
| NodeIDs []int64 `json:"nodeIDs"` | |||
| } | |||
| func NewGetPackageLoadedNodes(userID int64, packageID int64) GetPackageLoadedNodes { | |||
| return GetPackageLoadedNodes{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| } | |||
| } | |||
| func NewGetPackageLoadedNodesResp(nodeIDs []int64) GetPackageLoadedNodesResp { | |||
| return GetPackageLoadedNodesResp{ | |||
| NodeIDs: nodeIDs, | |||
| } | |||
| } | |||
| func (client *Client) GetPackageLoadedNodes(msg GetPackageLoadedNodes) (*GetPackageLoadedNodesResp, error) { | |||
| return mq.Request[GetPackageLoadedNodesResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,81 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| mymq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| ) | |||
| // Service 协调端接口 | |||
| type Service interface { | |||
| AgentService | |||
| BucketService | |||
| CacheService | |||
| CommonService | |||
| NodeService | |||
| ObjectService | |||
| PackageService | |||
| StorageService | |||
| } | |||
| type Server struct { | |||
| service Service | |||
| rabbitSvr mq.RabbitMQServer | |||
| OnError func(err error) | |||
| } | |||
| func NewServer(svc Service, cfg *mymq.Config) (*Server, error) { | |||
| srv := &Server{ | |||
| service: svc, | |||
| } | |||
| rabbitSvr, err := mq.NewRabbitMQServer( | |||
| cfg.MakeConnectingURL(), | |||
| mymq.COORDINATOR_QUEUE_NAME, | |||
| func(msg *mq.Message) (*mq.Message, error) { | |||
| return msgDispatcher.Handle(srv.service, msg) | |||
| }, | |||
| ) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| srv.rabbitSvr = *rabbitSvr | |||
| return srv, nil | |||
| } | |||
| func (s *Server) Stop() { | |||
| s.rabbitSvr.Close() | |||
| } | |||
| func (s *Server) Serve() error { | |||
| return s.rabbitSvr.Serve() | |||
| } | |||
| var msgDispatcher mq.MessageDispatcher = mq.NewMessageDispatcher() | |||
| // Register 将Service中的一个接口函数作为指定类型消息的处理函数,同时会注册请求和响应的消息类型 | |||
| // TODO 需要约束:Service实现了TSvc接口 | |||
| func Register[TSvc any, TReq any, TResp any](svcFn func(svc TSvc, msg *TReq) (*TResp, *mq.CodeMessage)) any { | |||
| mq.AddServiceFn(&msgDispatcher, svcFn) | |||
| mq.RegisterMessage[TReq]() | |||
| mq.RegisterMessage[TResp]() | |||
| return nil | |||
| } | |||
| // RegisterNoReply 将Service中的一个*没有返回值的*接口函数作为指定类型消息的处理函数,同时会注册请求和响应的消息类型 | |||
| // TODO 需要约束:Service实现了TSvc接口 | |||
| func RegisterNoReply[TSvc any, TReq any](svcFn func(svc TSvc, msg *TReq)) any { | |||
| mq.AddNoRespServiceFn(&msgDispatcher, svcFn) | |||
| mq.RegisterMessage[TReq]() | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| package coordinator | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| "gitlink.org.cn/cloudream/storage-common/pkgs/db/model" | |||
| ) | |||
| type StorageService interface { | |||
| GetStorageInfo(msg *GetStorageInfo) (*GetStorageInfoResp, *mq.CodeMessage) | |||
| StoragePackageLoaded(msg *StoragePackageLoaded) (*StoragePackageLoadedResp, *mq.CodeMessage) | |||
| } | |||
| // 获取Storage信息 | |||
| var _ = Register(StorageService.GetStorageInfo) | |||
| type GetStorageInfo struct { | |||
| UserID int64 `json:"userID"` | |||
| StorageID int64 `json:"storageID"` | |||
| } | |||
| type GetStorageInfoResp struct { | |||
| model.Storage | |||
| } | |||
| func NewGetStorageInfo(userID int64, storageID int64) GetStorageInfo { | |||
| return GetStorageInfo{ | |||
| UserID: userID, | |||
| StorageID: storageID, | |||
| } | |||
| } | |||
| func NewGetStorageInfoResp(storageID int64, name string, nodeID int64, dir string, state string) GetStorageInfoResp { | |||
| return GetStorageInfoResp{ | |||
| model.Storage{ | |||
| StorageID: storageID, | |||
| Name: name, | |||
| NodeID: nodeID, | |||
| Directory: dir, | |||
| State: state, | |||
| }, | |||
| } | |||
| } | |||
| func (client *Client) GetStorageInfo(msg GetStorageInfo) (*GetStorageInfoResp, error) { | |||
| return mq.Request[GetStorageInfoResp](client.rabbitCli, msg) | |||
| } | |||
| // 提交调度记录 | |||
| var _ = Register(StorageService.StoragePackageLoaded) | |||
| type StoragePackageLoaded struct { | |||
| UserID int64 `json:"userID"` | |||
| PackageID int64 `json:"packageID"` | |||
| StorageID int64 `json:"storageID"` | |||
| } | |||
| type StoragePackageLoadedResp struct{} | |||
| func NewStoragePackageLoaded(userID int64, packageID int64, stgID int64) StoragePackageLoaded { | |||
| return StoragePackageLoaded{ | |||
| UserID: userID, | |||
| PackageID: packageID, | |||
| StorageID: stgID, | |||
| } | |||
| } | |||
| func NewStoragePackageLoadedResp() StoragePackageLoadedResp { | |||
| return StoragePackageLoadedResp{} | |||
| } | |||
| func (client *Client) StoragePackageLoaded(msg StoragePackageLoaded) (*StoragePackageLoadedResp, error) { | |||
| return mq.Request[StoragePackageLoadedResp](client.rabbitCli, msg) | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| package scanner | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| stgmq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| ) | |||
| type Client struct { | |||
| rabbitCli *mq.RabbitMQClient | |||
| } | |||
| func NewClient(cfg *stgmq.Config) (*Client, error) { | |||
| rabbitCli, err := mq.NewRabbitMQClient(cfg.MakeConnectingURL(), stgmq.SCANNER_QUEUE_NAME, "") | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &Client{ | |||
| rabbitCli: rabbitCli, | |||
| }, nil | |||
| } | |||
| func (c *Client) Close() { | |||
| c.rabbitCli.Close() | |||
| } | |||
| type PoolClient struct { | |||
| *Client | |||
| owner *Pool | |||
| } | |||
| func (c *PoolClient) Close() { | |||
| c.owner.Release(c) | |||
| } | |||
| type Pool struct { | |||
| mqcfg *stgmq.Config | |||
| } | |||
| func NewPool(mqcfg *stgmq.Config) *Pool { | |||
| return &Pool{ | |||
| mqcfg: mqcfg, | |||
| } | |||
| } | |||
| func (p *Pool) Acquire() (*PoolClient, error) { | |||
| cli, err := NewClient(p.mqcfg) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &PoolClient{ | |||
| Client: cli, | |||
| owner: p, | |||
| }, nil | |||
| } | |||
| func (p *Pool) Release(cli *PoolClient) { | |||
| cli.Client.Close() | |||
| } | |||
| @@ -0,0 +1,50 @@ | |||
| package scanner | |||
| import ( | |||
| "fmt" | |||
| "time" | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| scevt "gitlink.org.cn/cloudream/storage-common/pkgs/mq/scanner/event" | |||
| ) | |||
| type EventService interface { | |||
| PostEvent(event *PostEvent) | |||
| } | |||
| // 投递Event | |||
| var _ = RegisterNoReply(EventService.PostEvent) | |||
| type PostEvent struct { | |||
| Event map[string]any `json:"event"` | |||
| IsEmergency bool `json:"isEmergency"` // 重要消息,优先处理 | |||
| DontMerge bool `json:"dontMerge"` // 不可合并此消息 | |||
| } | |||
| func NewPostEvent(event any, isEmergency bool, dontMerge bool) (PostEvent, error) { | |||
| mp, err := scevt.MessageToMap(event) | |||
| if err != nil { | |||
| return PostEvent{}, fmt.Errorf("message to map failed, err: %w", err) | |||
| } | |||
| return PostEvent{ | |||
| Event: mp, | |||
| IsEmergency: isEmergency, | |||
| DontMerge: dontMerge, | |||
| }, nil | |||
| } | |||
| func (cli *Client) PostEvent(event any, isEmergency bool, dontMerge bool, opts ...mq.SendOption) error { | |||
| opt := mq.SendOption{ | |||
| Timeout: time.Second * 30, | |||
| } | |||
| if len(opts) > 0 { | |||
| opt = opts[0] | |||
| } | |||
| body, err := NewPostEvent(event, isEmergency, dontMerge) | |||
| if err != nil { | |||
| return fmt.Errorf("new post event body failed, err: %w", err) | |||
| } | |||
| return mq.Send(cli.rabbitCli, body, opt) | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package event | |||
| type AgentCheckCache struct { | |||
| NodeID int64 `json:"nodeID"` | |||
| FileHashes []string `json:"fileHashes"` // 需要检查的FileHash列表,如果为nil(不是为空),则代表进行全量检查 | |||
| } | |||
| func NewAgentCheckCache(nodeID int64, fileHashes []string) AgentCheckCache { | |||
| return AgentCheckCache{ | |||
| NodeID: nodeID, | |||
| FileHashes: fileHashes, | |||
| } | |||
| } | |||
| func init() { | |||
| Register[AgentCheckCache]() | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package event | |||
| type AgentCheckState struct { | |||
| NodeID int64 `json:"nodeID"` | |||
| } | |||
| func NewAgentCheckState(nodeID int64) AgentCheckState { | |||
| return AgentCheckState{ | |||
| NodeID: nodeID, | |||
| } | |||
| } | |||
| func init() { | |||
| Register[AgentCheckState]() | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package event | |||
| type AgentCheckStorage struct { | |||
| StorageID int64 `json:"storageID"` | |||
| PackageIDs []int64 `json:"packageIDs"` // 需要检查的Package文件列表,如果为nil(不是为空),则代表进行全量检查 | |||
| } | |||
| func NewAgentCheckStorage(storageID int64, packageIDs []int64) AgentCheckStorage { | |||
| return AgentCheckStorage{ | |||
| StorageID: storageID, | |||
| PackageIDs: packageIDs, | |||
| } | |||
| } | |||
| func init() { | |||
| Register[AgentCheckStorage]() | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package event | |||
| type CheckCache struct { | |||
| NodeID int64 `json:"nodeID"` | |||
| } | |||
| func NewCheckCache(nodeID int64) CheckCache { | |||
| return CheckCache{ | |||
| NodeID: nodeID, | |||
| } | |||
| } | |||
| func init() { | |||
| Register[CheckCache]() | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package event | |||
| type CheckPackage struct { | |||
| PackageIDs []int64 `json:"packageIDs"` | |||
| } | |||
| func NewCheckPackage(packageIDs []int64) CheckPackage { | |||
| return CheckPackage{ | |||
| PackageIDs: packageIDs, | |||
| } | |||
| } | |||
| func init() { | |||
| Register[CheckPackage]() | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package event | |||
| type CheckRepCount struct { | |||
| FileHashes []string `json:"fileHashes"` | |||
| } | |||
| func NewCheckRepCount(fileHashes []string) CheckRepCount { | |||
| return CheckRepCount{ | |||
| FileHashes: fileHashes, | |||
| } | |||
| } | |||
| func init() { | |||
| Register[CheckRepCount]() | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| package event | |||
| import ( | |||
| myreflect "gitlink.org.cn/cloudream/common/utils/reflect" | |||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||
| ) | |||
| var typeResolver = serder.NewTypeNameResolver(true) | |||
| var serderOption = serder.TypedSerderOption{ | |||
| TypeResolver: &typeResolver, | |||
| TypeFieldName: "@type", | |||
| } | |||
| func MapToMessage(m map[string]any) (any, error) { | |||
| return serder.TypedMapToObject(m, serderOption) | |||
| } | |||
| func MessageToMap(msg any) (map[string]any, error) { | |||
| return serder.ObjectToTypedMap(msg, serderOption) | |||
| } | |||
| func Register[T any]() { | |||
| typeResolver.Register(myreflect.TypeOf[T]()) | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| package scanner | |||
| import ( | |||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | |||
| mymq "gitlink.org.cn/cloudream/storage-common/pkgs/mq" | |||
| ) | |||
| // Service 协调端接口 | |||
| type Service interface { | |||
| EventService | |||
| } | |||
| type Server struct { | |||
| service Service | |||
| rabbitSvr mq.RabbitMQServer | |||
| OnError func(err error) | |||
| } | |||
| func NewServer(svc Service, cfg *mymq.Config) (*Server, error) { | |||
| srv := &Server{ | |||
| service: svc, | |||
| } | |||
| rabbitSvr, err := mq.NewRabbitMQServer( | |||
| cfg.MakeConnectingURL(), | |||
| mymq.SCANNER_QUEUE_NAME, | |||
| func(msg *mq.Message) (*mq.Message, error) { | |||
| return msgDispatcher.Handle(srv.service, msg) | |||
| }, | |||
| ) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| srv.rabbitSvr = *rabbitSvr | |||
| return srv, nil | |||
| } | |||
| func (s *Server) Stop() { | |||
| s.rabbitSvr.Close() | |||
| } | |||
| func (s *Server) Serve() error { | |||
| return s.rabbitSvr.Serve() | |||
| } | |||
| var msgDispatcher mq.MessageDispatcher = mq.NewMessageDispatcher() | |||
| // Register 将Service中的一个接口函数作为指定类型消息的处理函数,同时会注册请求和响应的消息类型 | |||
| // TODO 需要约束:Service实现了TSvc接口 | |||
| func Register[TSvc any, TReq any, TResp any](svcFn func(svc TSvc, msg *TReq) (*TResp, *mq.CodeMessage)) any { | |||
| mq.AddServiceFn(&msgDispatcher, svcFn) | |||
| mq.RegisterMessage[TReq]() | |||
| mq.RegisterMessage[TResp]() | |||
| return nil | |||
| } | |||
| // RegisterNoReply 将Service中的一个*没有返回值的*接口函数作为指定类型消息的处理函数,同时会注册请求和响应的消息类型 | |||
| // TODO 需要约束:Service实现了TSvc接口 | |||
| func RegisterNoReply[TSvc any, TReq any](svcFn func(svc TSvc, msg *TReq)) any { | |||
| mq.AddNoRespServiceFn(&msgDispatcher, svcFn) | |||
| mq.RegisterMessage[TReq]() | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| package utils | |||
| import ( | |||
| "fmt" | |||
| "regexp" | |||
| "strconv" | |||
| "github.com/beevik/etree" | |||
| ) | |||
| type EcConfig struct { | |||
| ecid string `xml:"ecid"` | |||
| class string `xml:"class"` | |||
| n int `xml:"n"` | |||
| k int `xml:"k"` | |||
| w int `xml:"w"` | |||
| opt int `xml:"opt"` | |||
| } | |||
| func (r *EcConfig) GetK() int { | |||
| return r.k | |||
| } | |||
| func (r *EcConfig) GetN() int { | |||
| return r.n | |||
| } | |||
| func GetEcPolicy() *map[string]EcConfig { | |||
| doc := etree.NewDocument() | |||
| if err := doc.ReadFromFile("../confs/sysSetting.xml"); err != nil { | |||
| panic(err) | |||
| } | |||
| ecMap := make(map[string]EcConfig, 20) | |||
| root := doc.SelectElement("setting") | |||
| for _, attr := range root.SelectElements("attribute") { | |||
| if name := attr.SelectElement("name"); name.Text() == "ec.policy" { | |||
| for _, eci := range attr.SelectElements("value") { | |||
| tt := EcConfig{} | |||
| tt.ecid = eci.SelectElement("ecid").Text() | |||
| tt.class = eci.SelectElement("class").Text() | |||
| tt.n, _ = strconv.Atoi(eci.SelectElement("n").Text()) | |||
| tt.k, _ = strconv.Atoi(eci.SelectElement("k").Text()) | |||
| tt.w, _ = strconv.Atoi(eci.SelectElement("w").Text()) | |||
| tt.opt, _ = strconv.Atoi(eci.SelectElement("opt").Text()) | |||
| ecMap[tt.ecid] = tt | |||
| } | |||
| } | |||
| } | |||
| fmt.Println(ecMap) | |||
| return &ecMap | |||
| // | |||
| } | |||
| func GetAgentIps() []string { | |||
| doc := etree.NewDocument() | |||
| if err := doc.ReadFromFile("../confs/sysSetting.xml"); err != nil { | |||
| panic(err) | |||
| } | |||
| root := doc.SelectElement("setting") | |||
| var ips []string // 定义存储 IP 的字符串切片 | |||
| for _, attr := range root.SelectElements("attribute") { | |||
| if name := attr.SelectElement("name"); name.Text() == "agents.addr" { | |||
| for _, ip := range attr.SelectElements("value") { | |||
| ipRegex := regexp.MustCompile(`\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b`) | |||
| match := ipRegex.FindString(ip.Text()) | |||
| print(match) | |||
| ips = append(ips, match) | |||
| } | |||
| } | |||
| } | |||
| return ips | |||
| } | |||