GSoC Week1
Date: June 13, 2022 → June 19, 2022
Overview This week’s tasks
- Review https://github.com/casbin/casbin-rs/pull/293
- Make 2 issue & 2 PRs
- Explore swagger code generator and explain why it doesn’t work
Task 1 Review PR
https://github.com/casbin/casbin-rs/pull/293
Original implementation
Original implementation use Arc<RwLock<Role>>
:
1 | pub struct DefaultRoleManager { |
The key of HashMap<String, HashMap<String, Arc<RwLock<Role>>>>
is domain name (default value is DEFAULT
), the key of HashMap<String, Arc<RwLock<Role>>>
is role name, the entry is Arc<RwLock<Role>>
, which stores role information: name of the role, and all roles it directly has
It is quite expensive as Arc<T>
uses atomic ****operations for its reference counting, and there are many read()
and write()
operations for RwLock<Role>
in current implmentation:
1 | //L185 |
Important functions:
add_link
appendrole2
torole1.roles
delete_link
deleterole2
fromrole1.roles
has_link
check if rolename1
has rolename2
(dosen’t have to be direct role), ifdomain_matching_fn
is speicified, it will find roles in matched domainsget_roles
get all direct roles username
has in matched domainsget_users
get all users that directly have rolename
in matched domains
Novel implementation
This PR introduces 2 new crates:
- https://github.com/petgraph/petgraph graph data structure library, use
StableDiGraph
to store roles - https://github.com/petgraph/fixedbitset a simple bitset container, used in BFS to mark visited nodes
This PR’s implementation is like Go Casbin (https://github.com/casbin/casbin/blob/master/rbac/default-role-manager/role_manager.go)
1 | type Role struct { |
But instead of using map and storing related roles’ pointers in them, it use StableDiGraph
to construct roles graph, and use edges EdgeVariant
to represent relations:
1 | enum EdgeVariant { |
Edges with the EdgeVariant::Link
are relations which in Go are modeled by the roles
and users
map.
Edges with the EdgeVariant::Match
are relations which is Go are modeled by the matched
and matchedBy
map.
This PR gets rid of Arc<RwLock<Role>>
by using StableDiGraph
and NodeIndex
:
1 | pub struct DefaultRoleManager { |
StableGraph<N, E, Ty, Ix>
is a graph datastructure using an adjacency list representation, all_domains
constructs a StableGraph
with directed edges for each domain, all_domains_indices
stores node identifier NodeIndex
of every role node in the graph
Important functions:
get_or_create_role
get or create role;
if the role is new,
graph.add_node()
to add role to grapgh;if
role_matching_fn
is specified, calllink_if_matches
to match existing roles against new role and vice versa, if matched, callgraph.add_edge()
to createEdgeVariant::*Match*
edge between roles.add_link
add link from
role1
torole2
, callgraph.add_edge()
to addEdgeVariant::*Link
edge*delete_link
remove edge from
role1
torole2
has_link
Bfs searching in graph, checking if
role1
is connected torole2
get_roles
Bfs searching in graph, getting all roles the user directly has
get_users
Find all nodes having
Direction::*Incoming
* edge connected to this role, that is, getting all users that directly have this role
Why Performance impoved?
In StableGraph
, nodes (roles) and edges (relations) are each numbered in an interval from 0 to some number m, so we can access nodes and edges using their indices, also, creating a role is just adding a node to graph, link roles is justing add an edge between 2 nodes, we don’t have to modify nodes and edges, so Arc
and RWLock
are no longer needed.
Without atomic operations and lock/unlock, role manager is much faster now.
Tests
The PR add 2 tests:
test_basic_role_matching
test user with wildcard *test_basic_role_matching2
test role with wildcard *
Migrate these tests to Go Casbin:
1 | func TestBasicRoleMatching(t *testing.T) { |
TestBasicRoleMatching
passed, TestBasicRoleMatching2
failed with:
1 | role_manager_test.go:371: alice: [* book_group alice book_group bob pen_group], supposed to be [* alice bob book_group pen_group] |
Note that book_group
appears twice, this is because in (*Role).rangeRoles
:
1 | func (r *Role) rangeRoles(fn func(key, value interface{}) bool) { |
All roles, matched, and matchedBy roles are appended to result, which is a list, whille casbin-rs uses HashSet
, so there are no duplicate in casbin-rs.
Benchmark
Benchmark changes from GitHub workflow:
https://github.com/casbin/casbin-rs/runs/6409294114
1 | group changes master |
Improved above 5%:
- b_benchmark_rbac_model_large -21%
- benchmark_rbac_model_medium -11%
- benchmark_role_manager_medium -24%
- benchmark_role_manager_small -8.3%
Regressed above 2%:
- benchmark_role_manager_large +4.0%
And I found that benchmark workflow doesn’t work well:
https://github.com/casbin/casbin-rs/runs/6409294114#step:5:459
It fails to post comment of benchmark result to current PR with status code 403
Task 2 Make 2 issue & 2 PRs
issue: benchmark workflow is unable to push benchmark result to PR comment
https://github.com/casbin/casbin-rs/issues/294
issue: As I said above, when reviewing PR, I found a bug in Go version casbin. In default-role-manager/role_manager.go
, func (r *Role) getRoles()
returns result with duplicate items
https://github.com/casbin/casbin/issues/1033
PR: there are 3 errors when running cargo clippy -- -D warnings
, I fixed it
https://github.com/casbin/casbin-rs/pull/296
PR: There are many duplicate code in default_role_manager, making it hard to understand and maintain code, I improved it.
https://github.com/casbin/casbin-rs/pull/295
Task 3 Swagger code generator
There are 105 enpoints in casdoor now, so when developing casdoor rust sdk, I have to write many similar and duplicate code. To avoid this, I first looked for code generator.
As Casdoor provide well annotated swagger spec (https://github.com/casdoor/casdoor/blob/master/swagger/swagger.yml), I can generate code using swagger code generator.
First, I found swagger-codegen.
https://github.com/swagger-api/swagger-codegen
It can generate rust code with 2 different implementation.
I use docker image to generate code.
I tried first implementation:
1 | docker run -u 1000:1000 --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate \ |
Code is generated very fast. The output is like:
However, when running cargo build
, I got many warnings like:
After searching a while on the web, I found work around (https://stackoverflow.com/a/57641467): add #![allow(warnings)]
at the first line in src/lib.rs
Now cargo build
doesn’t produce warnings now.
However, as the warnings are still here, this doesn’t help much, there are still many deprecated code and badly named variables.
So I tried second implementation:
1 | docker run -u 1000:1000 --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate \ |
Now cargo build
failes with:
Searching issues of rust-openssl for a while, I found similar issue with me:
https://github.com/sfackler/rust-openssl/issues/1436
It says rust-openssl v0.9.24 is too old and doesn’t support OpenSSL 1.1.1
My openssl version is 1.1.1
Obviously, swagger code generator is not maintained for a while, and we should find another code generator.
Then I found openapi-generator:
https://github.com/OpenAPITools/openapi-generator
The usage is similar to swagger-codegen:
1 | docker run -u 1000:1000 --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate |
Code generator fails with:
It says that because some endpoints in swagger.yml aren’t providing response, so the spec validation failes. For example, GetResources
endpoint is not well annotated (no params, no response)
Then I found paperclip:
https://github.com/paperclip-rs/paperclip
It’s a WIP OpenAPI tooling written in Rust and can also generate rust code.
1 | paperclip --api v2 -o out/paper swagger.yml |
Still, as there are several endpoints not well annotated, it fails with error:
Also, generated code can’t have similar API with Go version Casdoor SDK:
https://github.com/casdoor/casdoor-go-sdk
After I explored these two code generators, I gave up and decide to write code on my own.
Summary:
- generated code is difficult to maintain
- generated code is too old and use deprecated syntax
- generated code is badly written and has many warnings
- generated code can’t have similar API with Go version Casdoor SDK
- swagger.yml is not complete (some endpoint without response), some code generator will report syntax error
Next week Plan
Next week, I will:
- start writing code for casdoor-rust-sdk
- learn how middleware works and contribute to poem-casbin
- maintain projects of casbin-rs