JointCloud Storage is a storage service model built on a peer-to-peer JointCloud collaboration mechanism. It manages storage resources across multiple clouds and provides users with a unified data storage service. The core idea emphasizes the independent and equal status of each cloud, connecting them in a non-intrusive manner. It also promotes intercloud collaboration by integrating computing, storage, and networking resources across clouds to deliver high-quality storage services.
This project aims to turn JointCloud Storage into a public infrastructure that is easy to use for individuals and enterprises alike. By simply using a JCS-client, users can quickly access efficient jointcloud storage without the need to deploy additional components. At the same time, the system allows flexible customization of its features to meet diverse needs.
song-jc@foxmail.com. The application process is illustrated below.
The following components must be installed manually:
MySQL: Version 8.0 or above. Please create a database and user account for the JCS client to use.Currently, only the JCS client is available as a Docker image. The jcsctl tool must be used as a precompiled executable — Download here.
Pull the Docker image
docker pull jcs:latest
If you already have your configuration and certificate files prepared, simply mount the directory containing these files into the container using the -v flag, and use the -c flag to specify the path to the configuration file (inside the container).
If you haven’t prepared the configuration yet and want to generate it using the client’s init command, you should still mount a local directory using -v so that the generated configuration and certificate files can be saved to it.
Here is an example command:
# Assuming the config files are on the host at /etc/jcsconfs
docker run -v /etc/jcsconfs:/opt/confs \
jcs serve -c /opt/confs/config.json # Note: the config path is inside the container
Before compiling, make sure you have the following installed:
Go: Version 1.23 or abovemage: A Makefile-like build tool. GitHub repository: MageAfter installing dependencies, clone this repository and run the following command in the project root:
mage bin
After successful execution, a build directory will be created in the project root containing the compiled executables:
jcs: The main JCS clientjcsctl: Command-line tool for the client (add it to your PATH for convenience)coordinator: Coordination node for the JCS system (can be ignored if using the public JCS infrastructure)hub: Central node of the JCS system (can also be ignored if using the public infrastructure)Download and extract the binaries that match your system environment — Download here.
Use the init command of the JCS client to start the configuration process and follow the prompts to fill in the required information.
Note: If you plan to run the JCS client using the Docker image, all paths in the configuration should refer to locations inside the container.
After completing the command, the following files will be generated:
config.json: The full configuration file for the client.ca_cert.pem: Root certificate for the HTTP service.ca_key.pem: Root private key for the HTTP service(must be securely stored by the user).server_cert.pem, server_key.pem: Server certificate and key signed by the root key.client_cert.pem, client_key.pem: Client certificate and key signed by the root key, used by jcsctl or third-party programs.All files except ca_key.pem will be used during client operation.
The configuration file fields are explained as follows:
{
"hubRPC": {
"rootCA": "" // Path to root certificate for communication with the Hub service
},
"coordinatorRPC": {
"address": "127.0.0.1:5009", // Address of the Coordinator service
"rootCA": "" // Path to root certificate for communication with the Coordinator service, usually same as Hub's rootCA
},
"logger": {
"output": "stdout", // Log output mode: stdout or file
"outputFileName": "client", // Log file name (effective if output is file)
"outputDirectory": "log", // Directory for log files (effective if output is file)
"level": "debug" // Log level: debug, info, warn, error
},
"db": {
"address": "127.0.0.1:3306", // MySQL database address
"account": "root", // Database username
"password": "123456", // Database password
"databaseName": "cloudream" // Database name
},
"connectivity": {
"testInterval": 300 // Interval in seconds to test connectivity with the Hub
},
"downloader": {
"maxStripCacheCount": 100, // Maximum number of EC stripes cached during file reads
"ecStripPrefetchCount": 1 // Number of EC stripes to prefetch during read operations
},
"downloadStrategy": {
"highLatencyHub": 35 // Latency threshold in ms; Hubs above this latency are considered high latency
},
"tickTock": {
"ecFileSizeThreshold": 5242880, // Minimum file size in bytes to apply EC encoding
"accessStatHistoryWeight": 0.8 // Weight of previous day's access count when updating today's access statistics (range 0~1)
},
"http": {
"enabled": true, // Enable HTTP service
"listen": "127.0.0.1:7890", // HTTP service listen address
"rootCA": "", // Path to root certificate
"serverCert": "", // Path to server certificate
"serverKey": "", // Path to server private key
"clientCerts": [], // List of client certificates signed by the root CA
"maxBodySize": 5242880 // Maximum HTTP request body size in bytes
},
"mount": {
"enabled": false, // Enable FUSE mount
"mountPoint": "", // Mount directory path
"gid": 0, // GID for files and folders in the mount directory
"uid": 0, // UID for files and folders in the mount directory
"dataDir": "", // Cache data directory
"metaDir": "", // Metadata directory
"maxCacheSize": 0, // Max cache size in bytes (0 means unlimited; not enforced in real-time)
"attrTimeout": "10s", // OS cache timeout for file attributes
"uploadPendingTime": "30s", // Delay after file modification before upload starts
"cacheActiveTime": "1m", // Time cached data remains active in memory before cleanup
"cacheExpireTime": "1m", // Time cached files remain on disk after being removed from memory (effective only for fully uploaded files)
"scanDataDirInterval": "10m" // Interval to scan the cache directory
},
"accessToken": {
"account": "", // Account for logging into the public JCS system
"password": "" // Password for the public JCS system
}
}
Note: If manually editing this JSON file, remove all comments as JSON does not support comments.
jcsctl is the command-line tool for managing the JCS client. It requires certificates for authentication, generated during client initialization. The required files are ca_cert.pem, client_cert.pem, and client_key.pem.
When starting, jcsctl searches for these files in the following order:
--ca, --cert, and --key.jcsctl executable resides.By default, jcsctl attempts to connect to the client at https://127.0.0.1:7890. Use the --endpoint option to specify a different client address.
See the API documentation: Access here
Test framework under development. Stay tuned for updates or join us in contributing!
Redundancy strategies significantly impact the read/write process and are closely integrated with various parts of the codebase. When implementing your own strategy, it's highly recommended to review and reference existing implementations to avoid omissions.
The following explanation is organized to prioritizes clarity and understanding — it may not reflect the most optimal implementation flow. Please read through the entire section before deciding where to begin.
When an object is first uploaded, it exists as a complete file in a single storage space, with a redundancy strategy of None. The transformation of its redundancy strategy is handled by the ChangeRedundancy task in the JCS client, which is triggered daily at midnight.
The transformation generally involves two steps:
See the implementation in the change_redundancy.go and redundancy_recover.go files under gitlink.org.cn/cloudream/jcs-pub/client/internal/ticktock.
Redundancy shrinking uses a simulated annealing algorithm to optimize redundancy from three dimensions: resilience, redundancy level, and access efficiency.
Currently, it supports redundancy shrinking for replication and erasure coding strategies.
This feature is optional. If needed, refer to the redundancy_shrink.go file in gitlink.org.cn/cloudream/jcs-pub/client/internal/ticktock.
Any functionality that involves downloading objects (not just HTTP-based downloads) must be adapted to support the new redundancy strategies. The download process includes:
The code for strategy selection is mainly located in gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader/strategy package.
The code for strategy execution is distributed across multiple parts of the project, including but not limited to gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader package.
We recommend using your IDE's search functionality to ensure full coverage when modifying related logic.
Most of the currently implemented instructions can be found in the gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2 package and serve as good references during custom instruction development.
Each instruction must implement the following interface:
type Op interface {
Execute(ctx *ExecContext, e *Executor) error
String() string
}
String(): Used for debugging, returns a description of the instruction.Execute(ctx *ExecContext, e *Executor)error: The core execution logic.
ctx: Execution context, which includes a cancelable context(Context) and custom values(Values) that can be accessed using functions like GetValueByType and SetValueByType from the gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/exec package.e: The executor, which provides access to a variable table through functions like BindVar, PutVar, or generic variants such as BindVar and BindArray from the gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/exec package.If Execute returns a non-nil error, the entire plan is considered failed and all ongoing instructions will be aborted.
Typical implementation steps:
BindVar or related functions to read required inputs. The VarIDs should be defined when the instruction is created.PutVar or similar to write results back to the variable table for use by subsequent instructions.If you understand how the module works internally, you are free to implement any custom logic as needed.
The data structure used with PutVar/BindVar must implement:
type VarValue interface {
Clone() VarValue
}
Note: All data transferred between instructions is serialized/deserialized using JSON. Avoid using non-serializable fields in custom structs.
If you want to pass a stream (io.ReadCloser) between instructions, use the built-in StreamValue type instead of defining your own, as streams require special handling.
If an instruction produces a stream, consider using WaitGroup or similar mechanisms to ensure the stream has been fully consumed before Execute returns.
Finally, register your new instruction and data types using UseOp and UseVarValue from the gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/exec package.
The ioswitch module expresses execution plans as DAGs (Directed Acyclic Graphs), where each node corresponds to an instruction and edges represent data dependencies.
To define a custom node, implement the following interface:
type Node interface {
Graph() *Graph
SetGraph(graph *Graph)
Env() *NodeEnv
InputStreams() *StreamInputSlots
OutputStreams() *StreamOutputSlots
InputValues() *ValueInputSlots
OutputValues() *ValueOutputSlots
GenerateOp() (exec.Op, error)
}
Key concepts:
Env() specifies the environment where the instruction should run (e.g., Driver, Hub, Any). Most instructions can use Any.You may embed the NodeBase struct from the gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/dag package, which implements all functions except GenerateOp.
The lifecycle of an execution plan includes the following stages: writing FromTo definitions, parsing FromTo into a DAG, optimizing the DAG, generating instructions from the DAG. You can extend any of the first three stages to integrate your custom instructions.
The FromTo model is used to describe the structure of an execution plan:
From component defines where the data originatesTo component defines where the data is intended to goThe plan parser uses predefined rules to convert the FromTo description into a DAG (Directed Acyclic Graph), which represents data dependencies and logical flow between operations. This DAG will eventually be translated into a sequence of executable instructions.
If necessary, you can define your own custom From and To types, as long as they implement the following interfaces:
type From interface {
GetStreamIndex() StreamIndex
}
type To interface {
// The range of the file stream required by this To node.
// The specific meaning of this value depends on DataIndex:
// If DataIndex == -1, it refers to the entire file range.
// If DataIndex >= 0, it refers to a specific shard (chunk) of the file.
GetRange() math2.Range
GetStreamIndex() StreamIndex
}
You’ll also need to modify the DAG parsing logic in gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser/gen package.
Note: Ensure that your custom DAG nodes implement:
type FromNode interface {
dag.Node
GetFrom() ioswitch2.From
Output() dag.StreamOutputSlot
}
type ToNode interface {
dag.Node
GetTo() ioswitch2.To
Input() dag.StreamInputSlot
}
This ensures compatibility with DAG optimization algorithms that rely on these interfaces.
In the optimization phase, you can add your own steps to match certain patterns and replace or merge nodes to improve efficiency or reduce instruction count.
After implementing your optimizer, integrate it into the Parse function in gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser package. Carefully consider the interaction between your logic and other optimization steps.
MulanPSL v2