The objective of this assignment is to understand the workings of a simple network file system based on the remote procedure calling (RPC) mechanism between a network file system client and a (stateless) network file system server. The network file system is completely transparent to the file system, that you implemented in Project 3, by introducing a new derived Disk class: the NDisk network disk.
This laboratory assignment consist of multiple programming assignments defined by tasks. Each task adds more functionality to your network file system manager. You should submit only one program with all tasks completed. When you have not completed all tasks, you can still submit your code and earn partial credit. You need to write a report of two pages about your implementation.
You are allowed to work on this assignment in a team of at most two students.
Submit the source codes of the programming assignments to the TA. Use zip or tar formats. Include your name and last four digits of your SSN in all source codes.
You need to be able to write, compile, and debug C++ programs, have a basic undestanding of NFS and distributed file systems as discussed in the textbook Chapters 11.9, 16, and 17. Familiarity with socket programming is a pro, but not required.
Use the man command to access the manual pages. If you cannot access the man pages, add this line to your .tcshrc file and then logoff/logon:
setenv MANPATH /usr/man:/usr/share/man:/usr/local/man:/usr/local/share/man
The RPC client and server code is already implemented for you. However, you may want to familiarize yourself with the socket library functions that were used to implement the RPC client proxy and server object:
Consult the manual pages to find more information on these library functions.
Download the source code for this project from http://www.cs.fsu.edu/~engelen/courses/COP4610/pr4.zip.
The zip archive contains the following files:
You need Project 3 to complete the build of fsclient and fsserver. To do so, copy your project 3 files, but be careful not to overwite the new Makefile for Project 4.
Before you can build your network file system, edit RPC.h and change the NDISK_DEFAULT_PORT to (1abcd), where abcd are the four last digits of your SSN. Thus, the port number will be five digits long starting with '1' followed by the last four digits of your SSN.
Compile fsclient and fsserver with 'make'.
The fsclient program tests the basic functionality of the network file system server. To test your system, run the fsserver program in one window and the fsclient in another:
Study the NDisk.h and NDisk.cpp files. The NDisk class is derived from Disk and provides a low-level transparent interface to a network-mounted disk (a VDisk maintained by fsserver). The NDisk uses an RPC proxy to invoke remote services.
Study the RPC.h and RPC.cpp files. The RPC class provides a client-side proxy call interface:
Each disk command is dispatched to the server using a message packet with the following format:
| command (4 bytes) |
block_num (4 bytes) |
block data (NDISK_BLOCK_SIZE bytes) |
A command is a 4-byte string issued by the client and send to the server:
The RPC class provides a server-side iterative and stateless server loop:
The remote disk in fsserver is a VDisk.
This approach is very low level. Real NFS systems usually implement such interfaces at a higher level (e.g. the FS level). However, the basic principles remain the same.
When you ran fsclient and fsserver you will notice a large number of "NUMB" commands that query the server disk for the number of blocks that the disk was formatted with. This is a waste of bandwidth, because the size of the remote disk will not change unless it is re-formatted.
For this task you have to reduce the number of "NUMB" commands by optimizing the NDisk object by locally storing (caching) the number of blocks of the remote disk (i.e. the num_blocks() value). As a result, only the minimum number of "NUMB" messages should be issued, which is when NDisk is initially created on the client side and when the remote disk is (re-)formatted.
Note: fsclient and fsserver generate messages. You can turn them off and also disable the assertions by modifying the Makefile to enable CFLAGS=-DNDEBUG. But obviously you should not do so when testing the system to determine a reduction in "NUMB" messages has occurred.
The NDisk and RPC classes use fixed-size blocks of NDISK_BLOCK_SIZE that must match the block size of the remote disk exactly. In our code, NDISK_BLOCK_SIZE == VDISK_BLOCK_SIZE, so only a VDisk can be remotely served but not a RAMDisk, because the RAMDisk block size does not match. This unnecessary restriction can be lifted.
For this task you have to modify the NDisk and RPC classes to accommodate different disk block sizes of the remote disk. Therefore, the message packet size will be determined by the block size of the remote disk. A new command "BSIZ" should be implemented in NDisk.cpp and RPC.cpp to query the server for the block size of the remote disk. Note that the message packets should fit different block sizes. To this end, you can assume that the maximum block size to be stored in a packet is NDISK_BLOCK_SIZE == 1024 and this determines the fixed packet size that contains blocks of up to 1024 bytes (which will prevent the server from mounting disks with larger blocks). Similar to Task 1, you should locally store the block size information in the client-side NDisk object so the server does not have to be frequently queried for block size info.
Note: you cannot use Disk::block_size() to set the internal (cached) size attribute, since this is a const method. You need to query the size on NDisk instantiation and NDisk::format.
Test your implementation with a server-side mounted RAMDisk.
A disadvantage of our current NDisk implementation is that the instantiation of an NDisk object creates a persistent connection to the server for the entire lifetime of the NDisk object. This means that the iterative (i.e. non-multi-threaded) server cannot serve other clients as long as it is serving one client (i.e. between accept and close). Therefore, other clients have to wait until the first client deletes its NDisk object. In real systems this is undesirable, because the exclusive reservation of one resource increases the chances of deadlock.
To improve our fairness policy and alleviate resource reservation requirements, we should explicitly acquire and release the remote resource by letting the client connect only when it needs to operate on a file and disconnect when it is done with the file(s), so the main server loop can serve requests from multiple clients.
For this task you need to implement two new methods in the abstract Disk class and all of its derived classes:
These operations are not meaningful for RAMDisk and VDisk, because they are used by a single client, but are important for NDisk to establish a connection to the server with RPC::connect and close the connection with RPC::disconnect. Therefore, the NDisk class must be changed to remote the implicit connect/disconnect in the constructor and destructor, and instead use the new methods to connect and disconnect with Disk::acquire and Disk::release, respectively.
Note that RPC::connect will stall when the connection cannot be immediately established. Therefore, NDisk::acquire may stall until the connection is successfully established. In case RPC::connect fails, Disk::acquire should fail as well.
The FS::open method must be changed to acquire access to a disk when a file is opened until the file is closed in FS::close. Note that multiple files can be open on the server, so only when all remote files are closed we disconnect with Disk::release.
All other FS operations, such as FS::ls, FS::rm, FS::mkdir, and FS::rmdir must also acquire and release the resource if not already acquired.
You can test the implementation with your Project 3 shell: create two shells and see if you can manipulate files and directories on the remote disk from each shell.
- End -