全局设置
Dataserver
定义 proto 远程调用函数
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))
Nameserver
定义 proto 远程调用函数
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;
}
模块类
定义leveldb中存储的对象
class User(object):
def __init__(self, username, password):
self.username = username
self.password = password
def __repr__(self):
return f'<User username={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'<File absolute_path={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'<DataServer did={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
Trie
- Trie 类是前缀树的主要类,包含一个根节点 root。
- insert(path, is_dir=False):将一个路径插入到前缀树中。它从根节点开始,逐个字符遍历路径,如果字符不存在于当前节点的子节点中,则创建一个新的子节点。
- check_dir(path):检查指定路径是否代表一个目录。它从根节点开始,逐个字符遍历路径,如果路径存在于前缀树中并且最后一个节点表示一个目录,则返回 True,否则返回 False。
- delete(path):删除指定路径。它从根节点开始,逐个字符遍历路径,找到路径的最后一个字符,然后将该节点的 end_of_path 属性设置为 False,表示不再代表一个路径的结束。如果节点没有子节点并且不代表其他路径的结束,则可以删除该节点。
- search(path):搜索指定路径是否存在于前缀树中。它从根节点开始,逐个字符遍历路径,如果路径存在于前缀树中并且最后一个节点的 end_of_path 属性为 True,则返回 True,表示路径存在。
- get_children(path):获取指定路径的所有子节点的字符。它从根节点开始,逐个字符遍历路径,然后返回最后一个节点的所有子节点的字符列表。
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 开发。这个脚本提供了多种与用户和文件相关的操作,例如用户注册、登录、文件的创建、删除、更新等。
- 初始化数据库,如果数据库里没有 trie 树记录,则新建。
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))
get_user(username)
def get_user(username):
key = pickle.dumps(f'USER:{username}')
try:
value = db.Get(key)
return pickle.loads(value)
except KeyError:
return None
register_user(username, password)
# 判断用户名是否合法,只包含字母、数字、下划线,长度为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!'
login(username, password)
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!'
create_file(absolute_path, size, is_dir, ctime, mtime)
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!'
delete_file(absolute_path)
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!'
update_file(absolute_path, size, is_dir, mtime)
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!'
get_file(absolute_path)
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
get_trie() & update_trie(trie)
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)
Client
-
__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)