diff --git a/.gitignore b/.gitignore index bebc8118..748588dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ __pycache__/ -target/ .mypy_cache/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..f6243a5c --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# EggsFS + +See the [proposal](https://xtxmarketscom.sharepoint.com/:w:/s/ECN/EdVNBAzB7klPsVw6CxkfAvwB0LGu4pbtf-Gafr0tMnWNKw?e=2LaGl8) for more info on eggsfs. Right now we have: + +* A prototype server implementation in Python; +* A basic command line client in Python; +* A FUSE driver in Python; + +## Starting the Python server + +``` +% ./python/pyeggsfs.py +``` + +The above will run all the processes needed to run EggsFS. This includes: + +* 256 metadata shards; +* 1 cross directory coordinator (CDC) +* 10 block services (this is tunable with `--block_services`) +* 1 shuckle + +A multitude of SQLite databases and directories to store the blocks will be stored in ``. + +The services can also be started separatedly with `./shard.py`, `./cdc.py`, `./block_service.py`, and `./shuckle.py`. They are all needed to operate a filesystem, and the shard _must_ be 256. + +With shuckle running, you can navigate to http://localhost:5000 to see a view of all block services. + +## Mounting EggsFS using FUSE + +``` +% ./python/eggsfuse.py +``` + +Now you can use EggsFS normally. You can use `--debug` and `--debug-fuse` for debugging output. + + +Note that EggsFS' files are immutable! You'll get `EROFS` ("read only filesystem") if you try to modify files. In general, you'll have success writing files by opening them, writing to them in an append-only manner, and then closing them. + +Sometimes you might get errors which are a bit more surprising than `EROFS`: + +``` +% echo blah > foobar +echo: write error: no such file or directory +``` + +The reason for this is that `foobar` is created by the shell and then opened again before being closed. This means that `foobar` does not exist yet, and you get the above. After this failure `foobar` _will_ be close and will materialized, as empty. + +## Using the command line client + + +``` +% ./python/basic_client.py ls / +% ./python/basic_client.py copy_into /cdc.py cdc.py # copies the contents of cdc.py into the EggsFS file /cdc.py +% ./python/basic_client.py cat /cdc.py # prints out the contents of cdc.py +% ./python/basic_client.py mkdir /blah +% ./python/basic_client.py echo_into /blah/baz 'hello' # creates file /blah/baz with string 'hello' in it +``` + +## Testing + +``` +./python/tests.py +``` + +We try to test all operations and all error conditions. The CDC test driver can inject shard failures to test cases where shard requests go wrong. \ No newline at end of file diff --git a/metadata/Cargo.lock b/metadata/Cargo.lock deleted file mode 100644 index 3c8aa98c..00000000 --- a/metadata/Cargo.lock +++ /dev/null @@ -1,314 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.59.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "eggsfs" -version = "0.1.0" -dependencies = [ - "bincode", - "rocksdb", - "serde", -] - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "libloading" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "librocksdb-sys" -version = "0.6.1+6.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" -dependencies = [ - "bindgen", - "bzip2-sys", - "cc", - "glob", - "libc", - "libz-sys", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nom" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - -[[package]] -name = "proc-macro2" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" - -[[package]] -name = "rocksdb" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" -dependencies = [ - "libc", - "librocksdb-sys", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "syn" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml deleted file mode 100644 index af23ed7e..00000000 --- a/metadata/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "eggsfs" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -rocksdb = { version = "0.18.0", default-features = false } -serde = { version = "1.0", features = ["derive"] } -bincode = "1.3.3" diff --git a/metadata/src/bin/basic_client.rs b/metadata/src/bin/basic_client.rs deleted file mode 100644 index c378ad65..00000000 --- a/metadata/src/bin/basic_client.rs +++ /dev/null @@ -1,257 +0,0 @@ -use bincode::Options; -use std::vec::Vec; -use std::string::String; -use std::net::UdpSocket; -use std::time::Duration; - -#[path="../hex.rs"] -mod hex; -#[path="../metadata_msgs.rs"] -mod metadata_msgs; -#[path="../schema.rs"] -mod schema; - - -use metadata_msgs::*; - - -// this root id is ineffecient to encode -const ROOT_ID: u64 = 0; - -const UDP_MTU: usize = 1472; - -const LOCAL_HOST: std::net::IpAddr = std::net::IpAddr::V4( - std::net::Ipv4Addr::new(127, 0, 0, 1)); - -const PROTOCOL_VERSION: u32 = 0; - - -fn generate_request_id() -> u32 { - let now = std::time::SystemTime::now(); - let now_ns = now.duration_since(std::time::SystemTime::UNIX_EPOCH - ).expect("Now is less than UNIX_EPOCH!").as_nanos(); - now_ns as u32 -} - - -fn resolve(parent_id: u64, subname: String, ts: u64 - ) -> MetadataResult> { - - let request_id = generate_request_id(); - - let port = schema::shard(parent_id) as u16 + 22272; - let target = std::net::SocketAddr::new(LOCAL_HOST, port); - let msg = MetadataRequest { - ver: PROTOCOL_VERSION, - request_id: request_id, - body: MetadataRequestBody::Resolve{ - parent_id: parent_id, subname: subname, ts: ts, - } - }; - let encoded = bincode_opt().serialize(&msg).expect("Failed to encode msg"); - let sock = UdpSocket::bind("127.0.0.1:0").expect( - "Could not bind UDP 127.0.0.1:0"); - sock.send_to(&encoded, &target).expect("Failed to send msg"); - - let mut buf = [0u8; UDP_MTU]; - - match sock.recv(&mut buf) { - Ok(len) => { - let res = bincode_opt().deserialize::( - &buf[..len]); - match res { - Ok(response) => { - if response.request_id != request_id { - Err(MetadataError{ - kind: MetadataErrorKind::LogicError, - text: format!("Request id expected: {} got: {}", - request_id, response.request_id) - }) - } else { - let b = response.body?; - match b { - //todo: dis - MetadataResponseBody::Resolve(result) => { - Ok(result) - }, - _ => { - let s = format!( - "Received unexpected response body {:?}", - b); - Err(MetadataError{ - kind: MetadataErrorKind::LogicError, - text: s, - }) - } - } - } - }, - Err(e) => { - let s = format!("Error deserializing {}\n{}", e, - hex::hexdump(&buf[..len])); - Err(MetadataError{ - kind: MetadataErrorKind::BincodeError, - text: s, - }) - }, - } - }, - Err(e) => { - let s = format!("Error receiving reply from {}: {}", target, e); - Err(MetadataError{ - kind: MetadataErrorKind::NetworkError, - text: s, - }) - }, - } -} - - -fn resolve_abs_path(path: &str, ts: u64) -> MetadataResult { - - let bits: Vec<&str> = path.split('/').collect(); - - if bits[0] != "" { - return Err(MetadataError{ - kind: MetadataErrorKind::LogicError, - text: String::from("Path must start with /"), - }); - } - - let mut parent_inode = ResolvedInode { - id: ROOT_ID, - creation_time: 0, - deletion_time: 0, - is_file: false, - }; - for (i, bit) in bits[1..].iter().enumerate() { - if parent_inode.is_file { - let filename = bits[..i+1].join("/"); - let s = format!("{} is a file", filename); - return Err(MetadataError{ - kind: MetadataErrorKind::LogicError, - text: s, - }); - } - - let result = resolve(parent_inode.id, bit.to_string(), ts)?; - - match result { - None => { - let subdirname = bits[..i+2].join("/"); - let s = format!("No such file or directory {}", subdirname); - return Err(MetadataError{ - kind: MetadataErrorKind::LogicError, - text: s, - }); - }, - Some(inode) => { - parent_inode = inode; - } - } - } - - Ok(parent_inode) -} - - -fn main() { - let args: Vec = std::env::args().collect(); - - if args.len() < 3 { - eprintln!("Usage: {} (resolve|ls|mkdir|rmdir) path [creation_ts]", - args[0]); - std::process::exit(1); - } - - let command = &args[1]; - - let path = &args[2]; - let creation_time = if args.len() >= 4 { - Some(args[3].parse::().expect( - "Couldn't parse creation_time as int")) - } else { - None - }; - if command == "rmdir" && creation_time.is_none() { - panic!("rmdir command requires a creation time"); - } - let final_slash = path.rfind('/').expect("Path must contain /"); - let basename = &path[final_slash+1..]; - - let resolve_path = if command == "mkdir" || path == "/" { - // for mkdir need to resolve the parent - &path[..final_slash] - } else { - &path - }; - - let sock = UdpSocket::bind("127.0.0.1:0").expect( - "Could not bind UDP 127.0.0.1:0"); - - sock.set_read_timeout(Some(Duration::new(2, 0))).expect( - "Could not set read_timeout"); - - let result = resolve_abs_path(&resolve_path, 0); - let resolved_inode = match result { - Ok(resolved) => resolved, - Err(e) => { - eprintln!("Failed to resolve {}: {:#?}", resolve_path, e); - std::process::exit(2); - } - }; - - let shard = schema::shard(resolved_inode.id); - - let pretty_resolve_path = if resolve_path == "" { - "/" - } else { - resolve_path - }; - - println!("'{}' resolved to:\n{:#?}", pretty_resolve_path, - resolved_inode); - - let body = match command.as_str() { - "resolve" => { - // nothing else needs doing, we're good - return; - }, - "rmdir" => MetadataRequestBody::RmDir{ - parent_id: resolved_inode.id, - subdirname: String::from(basename), - creation_time: creation_time.unwrap(), - }, - "mkdir" => MetadataRequestBody::MkDir{ - parent_id: resolved_inode.id, - subdirname: String::from(basename), - }, - _ => panic!("Command {} not supported yet", command), - }; - - let msg = MetadataRequest{ - ver: PROTOCOL_VERSION, - request_id: generate_request_id(), - body: body, - }; - - let port = shard as u16 + 22272; - let target = std::net::SocketAddr::new(LOCAL_HOST, port); - - let encoded = bincode_opt().serialize(&msg).expect("Failed to encode msg"); - sock.send_to(&encoded, &target).expect("Failed to send msg"); - - let mut buf = [0u8; UDP_MTU]; - match sock.recv_from(&mut buf) { - Ok((len, src_addr)) => { - let res = bincode_opt().deserialize::( - &buf[..len]); - match res { - Err(e) => println!("Failed to decode reply, reason {} raw:\n{}", - e, hex::hexdump(&buf[..len])), - Ok(r) => println!("Got reply from {}:\n{:#?}", src_addr, r), - } - }, - Err(e) => println!("Error receiving reply from {}: {}", target, e), - } -} diff --git a/metadata/src/bin/raftless_metadata.rs b/metadata/src/bin/raftless_metadata.rs deleted file mode 100644 index b5e93f30..00000000 --- a/metadata/src/bin/raftless_metadata.rs +++ /dev/null @@ -1,328 +0,0 @@ -use bincode::Options; -use rocksdb; -use std::fmt::Debug; -use serde::{Serialize, Deserialize}; - -#[path="../hex.rs"] -mod hex; -#[path="../metadata_msgs.rs"] -mod metadata_msgs; - -use metadata_msgs::*; - -// TODO: support jumbo frames (~8192 bytes) -const UDP_MTU: usize = 1472; - - -const PROTOCOL_VERSION: u32 = 0; - - -#[derive(Debug, Serialize, Deserialize)] -struct InodeTableValue { - id: u64, - deletion_time: u64, - is_file: bool, -} - - -const FIXED_KEY_LEN: usize = 1 + 8 + 1 + 8; - - -fn make_key(parent_id: u64, subname: &str, ts: u64) -> Vec { - // key format: b'D' + parent_id + subname + ts - // subname is stored as a pascal string - // which gives us desirable sorting properties - // e.g. make_key(-1, "a", 0) < make_key(-1, "a", !0u64) < make_key(-1, "ab", 1234) - let key_len = subname.len() + FIXED_KEY_LEN; - let mut key = vec![0u8; key_len]; - key[0] = b'D'; - key[1..9].copy_from_slice(&parent_id.to_le_bytes()); - key[9] = subname.len() as u8; - key[10..key_len-8].copy_from_slice(subname.as_bytes()); - key[key_len-8..].copy_from_slice(&ts.to_le_bytes()); - key -} - - -fn decompose_key<'a>(key: &'a [u8]) -> (u64, &'a str, u64) { - let subname_len = key.len() - FIXED_KEY_LEN; - - assert!(key.len() >= FIXED_KEY_LEN, "Runt key:\n{}", hex::hexdump(key)); - assert!(subname_len == (key[9] as usize), "Inconsistent size ({} vs {})", - subname_len, key[9] as usize); - assert!(key[0] == b'D'); - - let parent_id = u64::from_le_bytes(key[1..9].try_into().unwrap()); - - let subname_raw = &key[10..subname_len+10]; - - debug_assert!(std::str::from_utf8(subname_raw).is_ok(), - "Bad key in db: 0x{}", hex::hexstr(key)); - - let subname = unsafe { std::str::from_utf8_unchecked(subname_raw) }; - - let creation_ts = u64::from_le_bytes( - key[key.len()-8..].try_into().unwrap()); - - (parent_id, subname, creation_ts) -} - - -// on success returns the creation time -fn do_mkdir(parent_id: u64, next_id: &mut u64, subname: &str, db: &rocksdb::DB - ) -> MetadataResult { - - if subname.len() > 255 { - let text = format!("Subname len {} too long, max 255", subname.len()); - return Err(MetadataError{ - kind: MetadataErrorKind::NameTooLong, - text: text, - }); - } - - let this_id = *next_id; - - let now = std::time::SystemTime::now(); - let creation_time = now.duration_since(std::time::SystemTime::UNIX_EPOCH - ).expect("Now is less than UNIX_EPOCH!").as_secs(); - - let mut key = make_key(parent_id, subname, !0u64); - - let key_lower_bound = make_key(parent_id, subname, 0); - - let mut read_options = rocksdb::ReadOptions::default(); - // N.B. "`iterate_lower_bound` is inclusive" - read_options.set_iterate_lower_bound(key_lower_bound); - - let iter_mode = rocksdb::IteratorMode::From(&key, - rocksdb::Direction::Reverse); - - let result = db.iterator_opt(iter_mode, read_options).next(); - - // if there are already an entries with the same subname - // ensure the latest one is deleted - if let Some((key, value)) = result { - let key_len = key.len(); - let existing_creation_ts = u64::from_le_bytes( - key[key_len-8..].try_into().unwrap()); - - // disallow if the existing one was created recently - if existing_creation_ts + 10 > creation_time { - let text = format!("Name {} was created recently ({} seconds ago)", - subname, creation_time as i64 - existing_creation_ts as i64); - return Err(MetadataError{ - kind: MetadataErrorKind::TooSoon, - text: text, - }); - } - - let decoded_val: InodeTableValue = bincode_opt().deserialize(&value - ).expect("Bad value in db"); - - if decoded_val.deletion_time == 0 { - let text = format!("Inode '{}' ts {} exists and is not deleted", - subname, existing_creation_ts); - return Err(MetadataError{ - kind: MetadataErrorKind::InodeAlreadyExists, - text: text, - }); - } - } - - let key_len = key.len(); - key[key_len-8..].copy_from_slice(&creation_time.to_le_bytes()); - - let new_val = InodeTableValue { - id: this_id, - deletion_time: 0, - is_file: false, - }; - - let serialized_val = bincode_opt().serialize(&new_val).expect( - "Could not serialze value"); - - let mut batch = rocksdb::WriteBatch::default(); - batch.put(key, serialized_val); - batch.put(b"M_LAST_INODE_ID", this_id.to_le_bytes()); - if let Err(e) = db.write(batch) { - return Err(MetadataError{ - kind: MetadataErrorKind::RocksDbError, - text: format!("Error writing to rocks: {:?}", e), - }); - } - - // LSB is creator shard - *next_id += 0x100; - - Ok(MetadataResponseBody::MkDir(ResolvedInode{ - id: this_id, - creation_time: creation_time, - deletion_time: 0, - is_file: false, - })) -} - - -// on success returns creation_ts and value bytes -fn do_resolve(parent_id: u64, subname: &str, ts: u64, db: &rocksdb::DB - ) -> MetadataResult { - - // when querying "the present" set snapshot_ts to !0u64, as our upper bound - let creation_ts_upper_bound = if ts == 0 { !0u64 } else { ts }; - - let key_lower_bound = make_key(parent_id, subname, 0); - let key_upper_bound = make_key(parent_id, subname, creation_ts_upper_bound); - - let iter_mode = rocksdb::IteratorMode::From(&key_upper_bound, - rocksdb::Direction::Reverse); - let mut read_options = rocksdb::ReadOptions::default(); - // N.B. "`iterate_lower_bound` is inclusive" - read_options.set_iterate_lower_bound(key_lower_bound); - - let result = db.iterator_opt(iter_mode, read_options).next(); - - let maybe_resolved = match result { - None => { - None - }, - Some((key, raw_value)) => { - let key_len = key.len(); - let creation_time = u64::from_le_bytes( - key[key_len-8..].try_into().unwrap()); - let value: InodeTableValue = bincode_opt().deserialize( - &raw_value).expect("Bad value in db"); - let response = ResolvedInode { - id: value.id, - creation_time: creation_time, - deletion_time: value.deletion_time, - is_file: value.is_file, - }; - Some(response) - }, - }; - - Ok(MetadataResponseBody::Resolve(maybe_resolved)) -} - - -// fn do_lsdir(id: u64, from: &str, ts: u64, db: &rocksdb::DB -// ) -> MetadataResult { - -// let key_lower_bound = make_key(id, from, 0); -// let key_upper_bound = make_key(id + 1, "", 0); - -// let mut read_options = rocksdb::ReadOptions::default(); -// read_options.set_iterate_upper_bound(key_upper_bound); - -// let mut iter = db.raw_iterator_opt(read_options); - -// iter.seek(key_lower_bound); - -// while let Some(key) = iter.key() { - -// } - -// if let Err(e) = iter.status() { -// Err(MetadataError{ -// kind: MetadataErrorKind::RocksDbError, -// text: format!("{}", e), -// }) -// } - - -// } - - -fn main() { - let args: Vec = std::env::args().collect(); - - if args.len() < 2 { - println!("Usage: {} shard", args[0]); - std::process::exit(1); - } - - let shard: u8 = args[1].parse().expect("Failed to parse shard"); - - let db = rocksdb::DB::open_default( - format!("/home/jchicke/playground/fs/{}", shard)).expect( - "Could not load db"); - - let mut next_inode_id = match db.get(b"M_LAST_INODE_ID").unwrap() { - None => { - if shard == 0 { - // id 0 is reserved for root - 1u64 << 8 - } else { - shard as u64 - } - }, - Some(v) => { - let last_id = u64::from_le_bytes(v.try_into().expect( - "Bad format for M_LAST_INODE_ID")); - last_id + 0x100 // LSB is creator shard - } - }; - - println!("Using {:#018X} as next inode id", next_inode_id); - - let port: u16 = shard as u16 + 22272; - - let addr = std::net::SocketAddr::new( - std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), port); - - let sock = std::net::UdpSocket::bind(addr).expect("Could not bind UDP"); - - loop { - let mut buf = [0u8; UDP_MTU]; - let (len, origin) = sock.recv_from(&mut buf).expect("Recv failed"); - match bincode_opt().deserialize::(&buf[..len]) { - Err(e) => eprintln!("Failed to decode message with error {}\n{}", e, - hex::hexdump(&buf[..len])), - Ok(m) => { - println!("Got {:?} from {}", m, origin); - let response_body = if m.ver > PROTOCOL_VERSION { - Err(MetadataError { - kind: MetadataErrorKind::UnsupportedVersion, - text: format!( - "Unsupported ver ({} vs {}) on request", - m.ver, PROTOCOL_VERSION), - }) - } else { - match m.body { - MetadataRequestBody::Resolve { parent_id, subname, ts } - => do_resolve(parent_id, &subname, ts, &db), - MetadataRequestBody::MkDir { parent_id, subdirname } - => do_mkdir(parent_id, &mut next_inode_id, &subdirname, - &db), - _ => { - eprintln!("Command not supported yet, ignoring"); - continue; - }, - } - }; - let response = MetadataResponse{ - request_id: m.request_id, - body: response_body, - }; - let response_sz = bincode_opt().serialized_size(&response - ).unwrap() as usize; - if response_sz > UDP_MTU { - eprintln!("response {:?} is too big for UDP_MTU {}", - response, UDP_MTU); - } - let res0 = bincode_opt().serialize_into(&mut buf[..], - &response); - if let Err(e) = res0 { - eprintln!("Failed to Serialise\n{:#?}, reason: {}", - response, e); - } - let res1 = sock.send_to(&buf[..response_sz], origin); - if let Err(e) = res1 { - eprintln!("Failed to send to {}, reason: {}", origin, - e); - } - }, - }; - - } -} diff --git a/metadata/src/hex.rs b/metadata/src/hex.rs deleted file mode 100644 index 705b01de..00000000 --- a/metadata/src/hex.rs +++ /dev/null @@ -1,107 +0,0 @@ -// adapted from native/core/Hex.cpp - -use std::string::String; - - -const HEX_CHARS: [u16; 256] = [ - 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, 0x4130, 0x4230, 0x4330, 0x4430, 0x4530, 0x4630, - 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, 0x4131, 0x4231, 0x4331, 0x4431, 0x4531, 0x4631, - 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, 0x4132, 0x4232, 0x4332, 0x4432, 0x4532, 0x4632, - 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, 0x4133, 0x4233, 0x4333, 0x4433, 0x4533, 0x4633, - 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, 0x4134, 0x4234, 0x4334, 0x4434, 0x4534, 0x4634, - 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, 0x4135, 0x4235, 0x4335, 0x4435, 0x4535, 0x4635, - 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, 0x4136, 0x4236, 0x4336, 0x4436, 0x4536, 0x4636, - 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, 0x4137, 0x4237, 0x4337, 0x4437, 0x4537, 0x4637, - 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, 0x4138, 0x4238, 0x4338, 0x4438, 0x4538, 0x4638, - 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939, 0x4139, 0x4239, 0x4339, 0x4439, 0x4539, 0x4639, - 0x3041, 0x3141, 0x3241, 0x3341, 0x3441, 0x3541, 0x3641, 0x3741, 0x3841, 0x3941, 0x4141, 0x4241, 0x4341, 0x4441, 0x4541, 0x4641, - 0x3042, 0x3142, 0x3242, 0x3342, 0x3442, 0x3542, 0x3642, 0x3742, 0x3842, 0x3942, 0x4142, 0x4242, 0x4342, 0x4442, 0x4542, 0x4642, - 0x3043, 0x3143, 0x3243, 0x3343, 0x3443, 0x3543, 0x3643, 0x3743, 0x3843, 0x3943, 0x4143, 0x4243, 0x4343, 0x4443, 0x4543, 0x4643, - 0x3044, 0x3144, 0x3244, 0x3344, 0x3444, 0x3544, 0x3644, 0x3744, 0x3844, 0x3944, 0x4144, 0x4244, 0x4344, 0x4444, 0x4544, 0x4644, - 0x3045, 0x3145, 0x3245, 0x3345, 0x3445, 0x3545, 0x3645, 0x3745, 0x3845, 0x3945, 0x4145, 0x4245, 0x4345, 0x4445, 0x4545, 0x4645, - 0x3046, 0x3146, 0x3246, 0x3346, 0x3446, 0x3546, 0x3646, 0x3746, 0x3846, 0x3946, 0x4146, 0x4246, 0x4346, 0x4446, 0x4546, 0x4646, -]; - -const ASCII_CHARS: [u8; 256] = [ - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, -]; - - -pub fn hexstr(data: &[u8]) -> String { - let mut ret = String::with_capacity(2 * data.len()); - - for b in data { - let double_hex_char = HEX_CHARS[*b as usize]; - ret.push((double_hex_char & 0xFF) as u8 as char); - ret.push((double_hex_char >> 8) as u8 as char); - } - - ret -} - - -pub fn hexdump(data: &[u8]) -> String { - let n_lines = (data.len() + 15) >> 4; - let mut retval = vec![0u8; 68 * n_lines]; - - for i in 0..n_lines { - // Write position - retval[i*68+0] = (HEX_CHARS[((i*16) >> 24) & 0xFF]) as u8; - retval[i*68+1] = (HEX_CHARS[((i*16) >> 24) & 0xFF] >> 8) as u8; - retval[i*68+2] = (HEX_CHARS[((i*16) >> 16) & 0xFF]) as u8; - retval[i*68+3] = (HEX_CHARS[((i*16) >> 16) & 0xFF] >> 8) as u8; - retval[i*68+4] = (HEX_CHARS[((i*16) >> 8) & 0xFF]) as u8; - retval[i*68+5] = (HEX_CHARS[((i*16) >> 8) & 0xFF] >> 8) as u8; - retval[i*68+6] = (HEX_CHARS[(i*16) & 0xFF]) as u8; - retval[i*68+7] = (HEX_CHARS[(i*16) & 0xFF] >> 8) as u8; - retval[i*68+8] = b':'; - - // Write hex values - for j in 0..8usize { - retval[i*68+9+j*5] = b' '; - if i*16+j*2 >= data.len() { - retval[i*68+9+j*5+1] = b' '; - retval[i*68+9+j*5+2] = b' '; - } else { - retval[i*68+9+j*5+1] = HEX_CHARS[data[i*16+j*2] as usize] as u8; - retval[i*68+9+j*5+2] = (HEX_CHARS[data[i*16+j*2] as usize] >> 8) as u8; - } - if i*16+j*2+1 >= data.len() { - retval[i*68+9+j*5+3] = b' '; - retval[i*68+9+j*5+4] = b' '; - } else { - retval[i*68+9+j*5+3] = HEX_CHARS[data[i*16+j*2+1] as usize] as u8; - retval[i*68+9+j*5+4] = (HEX_CHARS[data[i*16+j*2+1] as usize] >> 8) as u8; - } - } - retval[i*68+49] = b' '; - retval[i*68+50] = b' '; - - // Write ASCII values - for j in 0..16usize { - if i*16+j >= data.len() { - retval[i*68+j+51] = b' '; - } else { - retval[i*68+j+51] = ASCII_CHARS[data[i*16+j] as usize]; - } - } - retval[i*68+67] = b'\n'; - } - - unsafe { String::from_utf8_unchecked(retval) } -} diff --git a/metadata/src/metadata_msgs.rs b/metadata/src/metadata_msgs.rs deleted file mode 100644 index 8e9791f1..00000000 --- a/metadata/src/metadata_msgs.rs +++ /dev/null @@ -1,115 +0,0 @@ -use serde::{Serialize, Deserialize}; -use std::string::String; -use bincode; - - -pub fn bincode_opt() -> bincode::DefaultOptions { - bincode::DefaultOptions::new() -} - - -#[derive(Serialize, Deserialize, Debug)] -pub enum MetadataRequestBody { - CreateBlock, - DeleteBlockRequest, - DeleteBlockComplete, - ReadBlockMap, - CreateFile, - DeleteFile, - PurgeFileRequest, - PurgeFileComplete, - MvFile, - // LsDir puts as many results as possible into one packet - LsDir{ id: u64, from: String, snapshot_time: u64 }, - MkDir{ parent_id: u64, subdirname: String }, - // creation_time on RmDir ensures it's idempotent - RmDir{ parent_id: u64, subdirname: String, creation_time: u64 }, - MvDir, - // if you're looking at "the present" use 0 as ts - // otherwise populate it with snapshot ts - // (0 chosen due to efficient varint encoding) - Resolve{ parent_id: u64, subname: String, ts: u64 }, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub struct MetadataRequest { - pub ver: u32, - pub request_id: u32, // echoed back to the client in response - // client's responsibiltity to keep unique - pub body: MetadataRequestBody, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub struct ResolvedInode { - pub id: u64, - pub creation_time: u64, - pub deletion_time: u64, // 0 => not deleted - pub is_file: bool, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub enum MetadataErrorKind { - TooSoon, - InodeAlreadyExists, - NameTooLong, - RocksDbError, - NetworkError, - BincodeError, - LogicError, - UnsupportedVersion, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub struct MetadataError { - pub kind: MetadataErrorKind, - pub text: String, -} - - -pub type MetadataResult = Result; - - -#[derive(Serialize, Debug)] -pub struct LsItemRef<'a> { - pub name: &'a str, - pub inode: ResolvedInode, -} - - -#[derive(Serialize, Debug)] -pub struct LsPayloadRef<'a> { - pub have_more: bool, - pub results: Vec>, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub struct LsItem { - pub name: String, - pub inode: ResolvedInode, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub struct LsPayload { - pub have_more: bool, - pub results: Vec, -} - - -#[derive(Serialize, Deserialize, Debug)] -pub enum MetadataResponseBody { - MkDir(ResolvedInode), - Resolve(Option), -} - - -#[derive(Serialize, Deserialize, Debug)] -pub struct MetadataResponse { - pub request_id: u32, // echoed back from request - pub body: MetadataResult, -} diff --git a/metadata/src/schema.rs b/metadata/src/schema.rs deleted file mode 100644 index 15051d72..00000000 --- a/metadata/src/schema.rs +++ /dev/null @@ -1,9 +0,0 @@ -use core::arch::x86_64; - - -pub fn shard(inode_id: u64) -> u8 { - let ret = unsafe { - x86_64::_mm_crc32_u64(0, inode_id) - }; - ret as u8 -} diff --git a/pyeggsfs/README.md b/pyeggsfs/README.md deleted file mode 100644 index 80060d1d..00000000 --- a/pyeggsfs/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# pyeggsfs - -Pyeggsfs is the python prototype of eggsfs. See the [proposal](https://xtxmarketscom.sharepoint.com/:w:/s/ECN/EdVNBAzB7klPsVw6CxkfAvwB0LGu4pbtf-Gafr0tMnWNKw?e=2LaGl8) for more info on eggsfs. - -## Usage - -``` -% ./eggs.py -``` - -The above will run all the processes needed to run EggsFS. This includes: - -* 256 metadata shards; -* 1 cross directory coordinator (CDC) -* 10 block services (this is tunable with `--block_services`) -* 1 shuckle - -A multitude of SQLite databases and directories to store the blocks will be stored in ``. - -Once started, you can start poking at the filesystem using `basic_client.py`, e.g. - -``` -% ./basic_client.py ls / -% ./basic_client.py copy_into /cdc.py cdc.py # copies the contents of cdc.py into the EggsFS file /cdc.py -% ./basic_client.py cat /cdc.py # prints out the contents of cdc.py -% ./basic_client.py mkdir /blah -% ./basic_client.py echo_into /blah/baz 'hello' # creates file /blah/baz with string 'hello' in it - -The services can also be started separatedly with `./shard.py`, `./cdc.py`, `./block_service.py`, and `./shuckle.py`. They are all needed to operate a filesystem, and the shard _must_ be 256. - -With shuckle running, you can navigate to http://localhost:5000 to see a view of all block services. - -## Testing - -``` -./tests.py -``` - -We try to test all operations and all error conditions. The CDC test driver can inject shard failures to test cases where shard requests go wrong. \ No newline at end of file diff --git a/pyeggsfs/.gitignore b/python/.gitignore similarity index 100% rename from pyeggsfs/.gitignore rename to python/.gitignore diff --git a/pyeggsfs/basic_client.py b/python/basic_client.py similarity index 99% rename from pyeggsfs/basic_client.py rename to python/basic_client.py index 6eac72ae..bcdc6857 100755 --- a/pyeggsfs/basic_client.py +++ b/python/basic_client.py @@ -13,6 +13,7 @@ import time from typing import Callable, Dict, List, Optional, Tuple, Union, TypeVar import logging from pathlib import Path +import os import bincode import crypto diff --git a/pyeggsfs/bincode.py b/python/bincode.py similarity index 100% rename from pyeggsfs/bincode.py rename to python/bincode.py diff --git a/pyeggsfs/block_service.py b/python/block_service.py similarity index 100% rename from pyeggsfs/block_service.py rename to python/block_service.py diff --git a/pyeggsfs/cdc.py b/python/cdc.py similarity index 100% rename from pyeggsfs/cdc.py rename to python/cdc.py diff --git a/pyeggsfs/cdc_key.py b/python/cdc_key.py similarity index 100% rename from pyeggsfs/cdc_key.py rename to python/cdc_key.py diff --git a/pyeggsfs/cdc_msgs.py b/python/cdc_msgs.py similarity index 100% rename from pyeggsfs/cdc_msgs.py rename to python/cdc_msgs.py diff --git a/pyeggsfs/common.py b/python/common.py similarity index 100% rename from pyeggsfs/common.py rename to python/common.py diff --git a/pyeggsfs/crc32c.py b/python/crc32c.py similarity index 99% rename from pyeggsfs/crc32c.py rename to python/crc32c.py index e6342e04..0b34779f 100644 --- a/pyeggsfs/crc32c.py +++ b/python/crc32c.py @@ -178,7 +178,7 @@ ffibuilder.set_source("_crc32c", return crc; } ''', - extra_compile_args=['-march=skylake'] + extra_compile_args=['-march=skylake', '-O3'] ) def compile(verbose=False): diff --git a/pyeggsfs/crypto.py b/python/crypto.py similarity index 100% rename from pyeggsfs/crypto.py rename to python/crypto.py diff --git a/pyeggsfs/eggsfuse.py b/python/eggsfuse.py similarity index 99% rename from pyeggsfs/eggsfuse.py rename to python/eggsfuse.py index d6fd0d8f..ee032ed0 100755 --- a/pyeggsfs/eggsfuse.py +++ b/python/eggsfuse.py @@ -13,7 +13,7 @@ import struct from common import * from shard_msgs import * from cdc_msgs import * -import crypto +import crypto as crypto LOCAL_HOST = '127.0.0.1' diff --git a/pyeggsfs/mypy.ini b/python/mypy.ini similarity index 100% rename from pyeggsfs/mypy.ini rename to python/mypy.ini diff --git a/pyeggsfs/eggs.py b/python/pyeggsfs.py similarity index 100% rename from pyeggsfs/eggs.py rename to python/pyeggsfs.py diff --git a/pyeggsfs/shard.py b/python/shard.py similarity index 100% rename from pyeggsfs/shard.py rename to python/shard.py diff --git a/pyeggsfs/shard_msgs.py b/python/shard_msgs.py similarity index 100% rename from pyeggsfs/shard_msgs.py rename to python/shard_msgs.py diff --git a/pyeggsfs/shuckle.py b/python/shuckle.py similarity index 100% rename from pyeggsfs/shuckle.py rename to python/shuckle.py diff --git a/pyeggsfs/templates/index.html b/python/templates/index.html similarity index 100% rename from pyeggsfs/templates/index.html rename to python/templates/index.html diff --git a/pyeggsfs/tests.py b/python/tests.py similarity index 100% rename from pyeggsfs/tests.py rename to python/tests.py