service DataServer
{
// 创建文件,对应touch命令
rpc CreateFile(CreateFileRequest) returns (BaseResponse) {}
// 创建文件夹,对应mkdir命令
rpc CreateDirectory(CreateDirectoryRequest) returns (BaseResponse) {}
// 删除文件,对应rm命令
rpc DeleteFile(DeleteFileRequest) returns (BaseResponse) {}
// 重命名或移动文件,对应mv命令
rpc RenameFile(RenameFileRequest) returns (BaseResponse) {}
// 读取文件,对应cat命令
rpc ReadFile(ReadFileRequest) returns (ReadFileResponse) {}
// 上传文件
rpc UploadFile(UploadFileRequest) returns (BaseResponse) {}
rpc UploadFileWithoutSync(UploadFileRequest) returns (BaseResponse) {}
// 下载文件
rpc DownloadFile(DownloadFileRequest) returns (DownloadFileResponse) {}
// 复制文件或文件夹
rpc CopyFile(CopyFileRequest) returns (BaseResponse) {}
// 写文件
rpc WriteFile(WriteFileRequest) returns (BaseResponse) {}
// 打开文件
rpc OpenFile(OpenFileRequest) returns (BaseResponse) {}
// 关闭文件
rpc CloseFile(CloseFileRequest) returns (BaseResponse) {}
// 通知下线
rpc NotifyOffline(NotifyOfflineRequest) returns (BaseResponse) {}
// 列出文件,对应ls命令
rpc ListFile(ListFileRequest) returns (ListFileResponse) {}
// ChangeDir
rpc ChangeDir(ChangeDirRequest) returns (BaseResponse) {}
}
message BaseResponse
{
// 用于追踪请求,是由客户端生成的Snowflake ID
int64 sequence_id = 1;
int32 success = 2;
string message = 3;
}
message CreateFileRequest
{
int64 sequence_id = 1;
string path = 2;
float ctime = 3;
float mtime = 4;
}
message CreateDirectoryRequest
{
int64 sequence_id = 1;
string path = 2;
bool parent = 4;
}
message DeleteFileRequest
{
int64 sequence_id = 1;
string path = 2;
bool recursive = 3;
}
message RenameFileRequest
{
int64 sequence_id = 1;
string src = 2;
string dst = 3;
}
message ReadFileRequest
{
int64 sequence_id = 1;
string path = 2;
}
message ReadFileResponse
{
int64 sequence_id = 1;
int32 success = 2;
string content = 3;
}
message UploadFileRequest
{
int64 sequence_id = 1;
string path = 2;
bytes content = 3;
}
message DownloadFileRequest
{
int64 sequence_id = 1;
string path = 2;
}
message DownloadFileResponse
{
int64 sequence_id = 1;
int32 success = 2;
bytes content = 3;
}
message CopyFileRequest
{
int64 sequence_id = 1;
string src = 2;
string dst = 3;
bool recursive = 4;
}
message NotifyOfflineRequest
{
int32 e = 1;
}
message OpenFileRequest
{
int64 sequence_id = 1;
string path = 2;
}
message WriteFileRequest
{
int64 sequence_id = 1;
string path = 2;
bytes content = 3;
}
message ListFileRequest
{
int64 sequence_id = 1;
string path = 2;
}
message ListFileResponse
{
int64 sequence_id = 1;
int32 success = 2;
repeated string files = 3;
}
message ChangeDirRequest
{
int64 sequence_id = 1;
string path = 2;
}
message CloseFileRequest
{
int64 sequence_id = 1;
string path = 2;
}
__init__(self, id=None, host=None, port=None, data_dir=None)
注册dataserver信息,初始化一个nameserver客户端实例,远程调用 RegisterDataServer 方法,将自己的host和port注册。
ListFile(self, request, context)
获取request的headers,提取出jwt,调用stub.VerifyJWT,验证是否成功登录。若成功登录,则返回f’{self.data_dir}{request.path}'路径下的文件列表。
CreateFile(self, request, context)
从元数据中获取JWT,验证JWT。创建f’{self.data_dir}{request.path}'文件,nameserver客户端实例添加这个文件信息。返回成功信息。
DeleteFile(self, request, context)
文件路径f'{self.data_dir}{request.path}'
,若可递归删除,则os.removedirs(file_path)
,否则仅可删除文件。返回删除成功响应。
ReadFile(self, request, context)
文件路径f'{self.data_dir}{request.path}'
,返回content给request.sequence_id。
CreateDirectory(self, request, context)
文件夹路径f'{self.data_dir}{request.path}'
,如果指定parent=False
,则只会建立最后一层的文件夹,否则会递归建立。返回是否创建成功。
RenameFile(self, request, context)
src = f'{self.data_dir}{request.src}'
dst = f'{self.data_dir}{request.dst}'
os.rename(src, dst),返回是否成功状态信息。
CopyFile(self, request, context)
src = f'{self.data_dir}{request.src}'
dst = f'{self.data_dir}{request.dst}'
recursive = True 复制文件夹;recursive = False 复制文件
返回是否成功的状态信息。
UploadFile(self, request, context)
文件路径 f'{self.data_dir}{request.path}'
文件内容 request.content
写入文件,获取所有副本服务器的地址,将文件同步到所有副本服务器上。
返回是否成功的状态信息。
UploadFileWithoutSync(self, request, context)
文件路径 f'{self.data_dir}{request.path}'
文件内容 request.content
直接写入不需要同步,用于UploadFile()调用。
返回是否成功的状态信息。
DownloadFile(self, request, context)
文件路径 f'{self.data_dir}{request.path}'
以二进制读取该文件,并将内容发送给sequence_id。
返回文件内容。
NotifyOffline(self, request, context)
收到消息后,关闭服务器
WriteFile(self, request, context)
文件路径 f'{self.data_dir}{request.path}'
采用覆盖写方式写入文件。
返回是否成功的状态信息。
OpenFile(self, request, context)
文件路径 f'{self.data_dir}{request.path}'
判断该文件路径是否是文件,向NameServer请求加锁
CloseFile(self, request, context)
文件路径 f'{self.data_dir}{request.path}'
向 NameServer 请求解锁
返回是否成功的状态信息
ChangeDir(self, request, context)
目录路径:f'{self.data_dir}{request.path}'
返回是否成功的状态信息。
def start_server():
id = getId()
host = DFS_SETTINGS['DATASERVER']['HOST']
port = DFS_SETTINGS['DATASERVER']['PORT']
data_dir = DFS_SETTINGS['DATASERVER']['DATA_DIR']
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
ds_grpc.add_DataServerServicer_to_server(
DataServerServicer(id, host, port, data_dir), server)
server.add_insecure_port(f'[::]:{port}')
server.start()
try:
server.wait_for_termination()
except KeyboardInterrupt:
# 下线通知
channel = grpc.insecure_channel(
f'{DFS_SETTINGS["NAMESERVER"]["HOST"]}:{DFS_SETTINGS["NAMESERVER"]["PORT"]}')
stub = ns_grpc.NameServerStub(channel)
stub.LogoutDataServer(ns_pb2.DataServerInfo(
id=id, host=host, port=port))
service NameServer
{
// 注册数据服务器
rpc RegisterDataServer(DataServerInfo) returns (Response);
// 获取数据服务器列表
rpc GetDataServerList(empty) returns (GetDataServerListResponse);
// 下线数据服务器
rpc LogoutDataServer(DataServerInfo) returns (Response);
// 注册用户
rpc RegisterUser(RegisterRequest) returns (Response);
// 登录
rpc Login(LoginRequest) returns (LoginResponse);
// 文件锁,lock_type: 0:读锁,1:写锁
rpc LockFile(LockFileRequest) returns (Response);
rpc UnlockFile(UnlockFileRequest) returns (Response);
// 检查缓存
rpc CheckCache(CheckCacheRequest) returns (Response);
// 添加新文件(夹),提供完整文件信息
rpc AddFile(FileInfo) returns (Response);
// 删除文件(夹),提供文件路径
rpc DeleteFile(DeleteRequest) returns (Response);
// 修改文件(夹),提供原始文件路径,新文件路径,新文件大小,修改时间
rpc ModifyFile(ModifyFileRequest) returns (Response);
// 获取文件信息, 提供文件路径
rpc GetFileInfo(GetFileInfoRequest) returns (FileInfoResponse);
// 验证JWT
rpc VerifyJWT(VerifyJWTRequest) returns (Response) {}
}
message Response
{
int32 success = 1;
string message = 2;
}
message DataServerInfo
{
int64 id = 1;
string host = 2;
int32 port = 3;
}
message empty
{
int32 e = 1;
}
message GetDataServerListResponse
{
int32 success = 1;
string message = 2;
repeated DataServerInfo dataServerInfoList = 3;
}
message RegisterRequest
{
string username = 1;
string password = 2;
}
message LoginRequest
{
string username = 1;
string password = 2;
}
message LoginResponse
{
int32 success = 1;
string message = 2;
string jwt = 3;
}
message LockFileRequest
{
int32 lock_type = 1;
string filepath = 2;
}
message UnlockFileRequest
{
int32 lock_type = 1;
string filepath = 2;
}
message CheckCacheRequest
{
string absolute_path = 1;
float mtime = 2;
}
message GetFileInfoRequest
{
string absolute_path = 1;
}
message FileInfo
{
string absolute_path = 1;
int64 size = 2;
bool is_dir = 3;
float ctime = 4;
float mtime = 5;
}
message DeleteRequest
{
string absolute_path = 1;
}
message ModifyFileRequest
{
string old_absolute_path = 1;
string new_absolute_path = 2;
int64 new_size = 3;
float mtime = 4;
}
message FileInfoResponse
{
int64 size = 1;
bool is_dir = 2;
float ctime = 3;
float mtime = 4;
int32 success = 5;
string message = 6;
}
message VerifyJWTRequest
{
string jwt = 1;
}
class User(object):
def __init__(self, username, password):
self.username = username
self.password = password
def __repr__(self):
return f'{self.username} password={self.password}>'
class File(object):
def __init__(self, absolute_path, size, is_dir, ctime, mtime):
self.absolute_path = absolute_path
self.size = size
self.is_dir = is_dir
self.ctime = ctime
self.mtime = mtime
def __repr__(self):
return f'{self.absolute_path} size={self.size} is_dir={self.is_dir} ctime={self.ctime} mtime={self.mtime}>'
class DataServer(object):
def __init__(self, did, host, port):
self.did = did
self.host = host
self.port = port
def __repr__(self):
return f'{self.did} host={self.host} port={self.port}>'
class TrieNode(object):
def __init__(self):
self.children = {}
self.is_dir = False
self.end_of_path = False
class Trie(object):
def __init__(self):
self.root = TrieNode()
def insert(self, path, is_dir=False):
node = self.root
for part in path:
if part not in node.children:
node.children[part] = TrieNode()
node = node.children[part]
node.end_of_path = True
node.is_dir = is_dir
def check_dir(self, path):
node = self.root
for part in path:
if part not in node.children:
return False
node = node.children[part]
return node.is_dir
def delete(self, path):
return self._delete(self.root, path, 0)
def _delete(self, node, path, depth):
if not node:
return None
# 如果已经到达路径的最后一个部分
if depth == len(path):
# 如果该节点是一个路径的结束
if node.end_of_path:
node.end_of_path = False
# 如果该节点没有子节点,那么可以删除该节点
return node if node.children else None
# 如果还没有到达路径的最后一个部分
else:
part = path[depth]
node.children[part] = self._delete(
node.children.get(part), path, depth + 1)
# 如果该节点不是一个路径的结束,并且没有子节点,那么可以删除该节点
if not node.end_of_path and not node.children:
return None
return node
def search(self, path):
node = self.root
for part in path:
if part not in node.children:
return False
node = node.children[part]
return node.end_of_path
def get_children(self, path):
node = self.root
for part in path:
if part not in node.children:
return []
node = node.children[part]
return list(node.children.keys())
db.py 文件是一个使用 LevelDB 数据库的 Python 脚本,专门设计用于一个名为 nameserver 的系统。LevelDB 是一个轻量级的键值存储数据库,由 Google 开发。这个脚本提供了多种与用户和文件相关的操作,例如用户注册、登录、文件的创建、删除、更新等。
nameserver_data_dir = DFS_SETTINGS['NAMESERVER']['DATA_DIR']
# 数据库路径
db_path = f'{nameserver_data_dir}/nameserver.db'
# 创建数据库文件的父目录,如果它不存在的话
os.makedirs(os.path.dirname(db_path), exist_ok=True)
# 初始化数据库
db = leveldb.LevelDB(db_path, create_if_missing=True)
key = pickle.dumps('TRIE')
try:
value = db.Get(key)
except KeyError:
# 创建TRIE树
trie = Trie()
db.Put(key, pickle.dumps(trie))
def get_user(username):
key = pickle.dumps(f'USER:{username}')
try:
value = db.Get(key)
return pickle.loads(value)
except KeyError:
return None
# 判断用户名是否合法,只包含字母、数字、下划线,长度为1-20
if not re.match(r'^\w{1,20}$', username):
return False, 'Username is invalid!'
# 判断用户是否存在
key = pickle.dumps(f'USER:{username}')
user = get_user(username)
if user:
return False, 'User already exists!'
# 创建用户
password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
user = User(username=username, password=password)
value = pickle.dumps(user)
try:
db.Put(key, value)
except Exception as e:
return False, "System Error: Register failed!"
return True, 'Register successfully!'
def login(username, password):
# 判断输入是否合法
if not re.match(r'^\w{1,20}$', username):
return False, 'Username is invalid!'
if not re.match(r'^.{1,20}$', password):
return False, 'Password is invalid!'
user = get_user(username)
if not user:
return False, 'User not exists!'
if not bcrypt.checkpw(password.encode(), user.password):
return False, 'Password is wrong!'
return True, 'Login successfully!'
def create_file(absolute_path, size, is_dir, ctime, mtime):
# 判断文件是否存在
key = pickle.dumps(f'FILE:{absolute_path}')
try:
value = db.Get(key)
return False, 'File already exists!'
except KeyError:
pass
# 创建文件
file = File(absolute_path=absolute_path, size=size,
is_dir=is_dir, ctime=ctime, mtime=mtime)
value = pickle.dumps(file)
try:
db.Put(key, value)
except KeyError:
return False, 'System Error: Trie not exists!'
except Exception as e:
return False, "System Error: Create file failed!"
return True, 'Create file successfully!'
def delete_file(absolute_path):
key = pickle.dumps(f'FILE:{absolute_path}')
try:
db.Delete(key)
trie = pickle.loads(db.Get(pickle.dumps('TRIE')))
trie.delete(absolute_path.split('/'))
except KeyError:
return False, 'System Error: Trie not exists!'
except Exception as e:
return False, "System Error: Delete file failed!"
return True, 'Delete file successfully!'
def update_file(absolute_path, size, is_dir, mtime):
key = pickle.dumps(f'FILE:{absolute_path}')
try:
value = db.Get(key)
except KeyError:
return False, 'File not exists!'
file = pickle.loads(value)
if size != None and size > 0 or size < 1024 * 1024 * 10:
file.size = size
if is_dir != None and is_dir in [True, False]:
file.is_dir = is_dir
file.mtime = mtime
value = pickle.dumps(file)
try:
db.Put(key, value)
except Exception as e:
return False, "System Error: Update file failed!"
return True, 'Update file successfully!'
def get_file(absolute_path):
key = pickle.dumps(f'FILE:{absolute_path}')
try:
value = db.Get(key)
return pickle.loads(value)
except KeyError:
return None
def get_trie():
key = pickle.dumps('TRIE')
try:
value = db.Get(key)
return pickle.loads(value)
except KeyError:
return None
def update_trie(trie):
key = pickle.dumps('TRIE')
try:
db.Put(key, pickle.dumps(trie))
return True, 'Update trie successfully!'
except Exception as e:
return False, "System Error: Update trie failed!"
__init__(self, id, host, port)
初始化nameserver信息
self.trie = db.get_trie()
,获得 trie 树。TRIE 树用于快速检索和管理文件路径。
stop(self)
更新self.trie,通知所有DataServer停止
RegisterDataServer(self, request, context)
self.DataServerList添加元素,id、host、port。
GetDataServerList(self, request, context)
返回self.DataServerList列表给request
LogoutDataServer(self, request, context)
从 self.DataServerList 中移除所有 id 与 request.id 相等的元素。
返回是否成功的状态信息。
RegisterUser(self, request, context)
在数据库中添加一条信息保存用户名和密码。
在trie树中根据username添加一个节点。
返回是否成功的状态信息。
Login(self, request, context)
首先检查用户是否已经登录,然后尝试使用用户名和密码登录,如果登录失败,它会检查是否存在一个与用户名相对应的目录,如果不存在,则在数据服务器上创建这个目录。
LockFile(self, request, context) & UnlockFile(self, request, context)
首先判断用户是否登录,获取文件路径和锁的类型,根据不同类型,更改self.ReadLockDist和self.WriteLockDist
AddFile(self, request, context)
添加File元数据到数据库,将文件路径添加到trie树中
DeleteFile(self, request, context)
在db数据库中,删除信息
ModifyFile(self, request, context)
首先判断old_absolute_path和new_absolute_path是否相同,若相同,则更新数据库中old_absolute_path的值;若不同,删除old_absolute_path信息,并添加new_absolute_path信息。
GetFileInfo(self, request, context)
根据request.absolute_path在数据库中获取指定信息,返回size,is_dir,ctime,mtime
CheckCache(self, request, context)
检查文件是否被修改。如果file.mtime > request.mtime 表示文件已经被修改。
VerifyJWT(self, request, context)
判断jwt是否有效
gen_token(self, username)
针对用户名和密码生成token
verify_token(self, token)
token解码为user,查看数据库中是否存在这个用户
id = getId()
host = DFS_SETTINGS['NAMESERVER']['HOST']
port = DFS_SETTINGS['NAMESERVER']['PORT']
Nserver = NameServerServicer(id, host, port)
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
ns_pb2_grpc.add_NameServerServicer_to_server(Nserver, server)
server.add_insecure_port(f"{host}:{port}")
server.start()
try:
server.wait_for_termination()
except KeyboardInterrupt:
Nserver.stop()
server.stop(0)
__init__(self):
初始化
self.current_dir = '/'
self.username = None
self.jwt = None
self.cache_path = DFS_SETTINGS['CLIENT']['DATA_DIR']
ns_host = DFS_SETTINGS['NAMESERVER']['HOST']
ns_post = DFS_SETTINGS['NAMESERVER']['PORT']
ns_channel = grpc.insecure_channel(f'{ns_host}:{ns_post}')
self.ns_stub = ns_grpc.NameServerStub(ns_channel)
用户选择一个dataserver,然后连接到这个dataserver
register(self, username, password)
向nameserver请求注册用户
login(self, username, password)
向nameserver请求登录,会收到jwt,将jwt设为自身值。
touch(self, path)
在client上创建一个文件,并将这个空文件上传到dataserver中
ls(self, path)
向data_server请求获取path下的文件
mkdir(self, path, parent=False)
请求dataserver创建文件夹,然后client本地创建文件夹
cat(self, path)
如果本地有该文件则直接在本地读取,否则从dataserver中读取,并保存文件
rm(self, path, recursive=False)
删除本地和dataserver中的文件。
mv(self, src, dst)
cp(self, src, dst, recursive=False)
download(self, path)
openfile(self, path)
closefile(self, path)
cd(self, path)