Lab
4 Details for MPCS 51205
Each
lab
will consist of a small problem and details of how to
proceed. Each lab is intended to give every student hands-on
experience with the core technologies utilized during the
course. A student may concentrate, as a team member, on
one technology over another for the final project, but labs are
designed to give each and every student exposure to all the
technologies that come into play. You need to submit labs
to the TAs for grading--see submission instructions below.
Generally, unless otherwise specified, you will have one week to
complete each assigned lab.
See
the
syllabus for information on grading. Turning in lab
assignments on time is required, without exception, and all late deliveries will be
penalized, regardless
of cause. Submit your assignments to the
subversion repository according to the directions on the
syllabus page.
You
may
write this solution in Java.
Lab 3
Due: 5:00 pm, Monday, November 13,
2023
Problem
1: Playing With Multiple Dockerized Microservices and
MongoDB:
BACKGROUND:
Like
all programming problems, learning a new technology is not an
exercise in reading but rather and exercise in typing.
This lab is designed to give you hands-on experience in (a)
running multiple dockerized microservices that communicate both
synchronously and asynchronously with each other and finally
write data to a MongoDB database. You will generally find
the References section below helpful in addition to the required
and recommended reading. When we talk about "Docker", we
are talking specifically about the Stable Community Edition of Docker, which is
the version we will be using in this class. The Stable
Community Edition provides the basic container engine and
built-in orchestration, networking, and security.
WHAT YOU NEED TO DO:
STEP 1:
For
this
fourth lab, we are going to run a prototype that consists of
four distinct microservices. This prototype consists of:
1. A dockerized Apache Tomcat web server microservice named WebServer running ubuntu
that will serve up a web page and backing jsp and Java servlet
that will prompt the user to add a new user to the system.
This web page is for adding new users only and is strictly no frills. A
field for a username, and a second field for a password, and an
"Confirm New User" button that, when clicked, does no error
checking and sends the new user request (over synchronous RMI
from the servlet) to the UserManagement
microservice, which has an RMI Server listening for remote
calls.
2.
A dockerized UserManagement
microservice running ubuntu that hosts an RMI Server that
listens for new user requests from the WebServer's servlet-based
RMI client. A synchronous RMI client
running in the servlet on the WebServer
is used to communicate new user requests to the RMI server
running on UserManagement.
3. The RMI server running on UserManagement receives the new user request
and forwards that request asynchronously
to a queue running in a dockerized RabbitMQ microservice called MyWabbit.
4. A dockerized LoggingService,
based on a docker MongoDB image will be listening/subscribed to
the MyWabbit's "new
user" queue, and upon every incoming message (about a new user
to add), will write that new user's information out to a new
MongoDB document. Note that the purpose of the MongoDB
database is not to
store new user information, but to provide a document store for log messages
from the system.
The
big picture here is this:
These
steps will involve running several different docker containers
and installing software inside them. Details are below. After downloading and installing the prototype
in STEPS 2-5, get the prototype running (there will be a few
code and environment changes you will need to make) and examine the code carefully
and observe how it executes.
First,
make
sure docker is running either on your laptop (Option I from the
first lab) or in your VM (Option II from the first lab).
STEP 2:
Get Tomcat working in a Docker
Container (WebService microservice)
First, get down the Apache Tomcat app server into a new
container. Do this by executing the following docker
command:
docker run -di --hostname tomcat --name
WebServer -p 8080:8080 tomcat:8.0
Once
you have that container down (and verify it is running), obtain
a bash shell in that container by running docker exec into the
container. Once you are in your WebService container,
install some software in the container. Install:
apt-get update
apt-get install vim
apt-get install net-tools
apt-get install
openssh-client
apt-get install default-jdk
Once you have the software installed, let's hit the Tomcat
default web page at http://localhost:8080/index.jsp.
Bookmark this page. Then navigate to
http://localhost:8080/docs/index.html. Browse around the
documentation a bit. Next, browse over to http://localhost:8080/examples,
and
go into the Servlets examples, and Execute the Hello World
servlet. Back up two levels and go into the jsp examples
at http://localhost:8080/examples/jsp/.
Try the Numberguess and Calendar examples under the JSP 1.2
Examples heading. Take a look at the source code for these
examples (the "Source" link to the right of "Execute").
Now, we need to modify the setup a bit. Namely, we need to
set you up as an admin so you can run the tomcat manager.
Use vi to edit the file in your tomcat container:
/usr/local/tomcat/conf/tomcat-users.xml. Note that
Tomcat's configuration files are located in the
/usr/local/tomcat/conf directory. Edit the file and at the
very end (right above the closing </tomcat-users>
tag and below the --> comment end"), add these two
lines:
<role rolename="admin"/>
<user username="admin" password="admin"
roles="manager-gui,manager-status,manager-script,manager-jmx,admin-gui"/>
Save the file and exit vi. Now, hop out of your
container's bash shell and have docker restart the Tomcat
server:
docker
restart WebServer
Now, exec a bash shell back into WebServer. Go back to
your browser and hit http://localhost:8080/manager/html (yes,
it's slash html and not .html) and when prompted, log in as
"admin/admin". You should now be in the Tomcat Web
Application Manager. You will become intimate with this.
Now for some sample software. cd to the following
directory: /usr/local/tomcat/webapps. Do an ls and
look at the directories there. This is where Tomcat stores
web applications, one of which you're about to install.
Now, using scp from within your WebServer
container, type
scp userid@linux.cs.uchicago.edu:/home/mark/pub/51205/src.labs/lab4/tomcat.starter.kit.tgz .
[enter your userid and password on the cluster]
Now, untar that tarball under the /usr/local/tomcat/webapps
subdirectory. Do an "ls" again. You should see a new
mpcs subdirectory. Execute:
root@tomcat:/usr/local/tomcat/webapps/#
find mpcs
You will see a number of files under the mpcs directory,
including a WEB-INF subdirectory, a jsps subdirectory, and a
WEB-INF/classes subdirectory. Note especially the
mpcs/WEB-INF/web.xml file, as it is the key to the kingdom as
far as Tomcat applications are concerned.
Now, go back to your Tomcat Web Application Manager, and refresh
the page. Scroll down and you will see a new application
Path for mpcs. Look for options over to the right
including Stop, Reload, and Undeploy. Click on Reload now.
Finally, navigate to this page:
http://localhost:8080/mpcs/jsps/starter.kit.html. You
should see a tidy little web page allowing you to enter a New
Username and a New Password by clicking on a button called
"Confirm New User". Resist the urge to click on the button
quite yet.
Now, in your shell cd over to
/usr/local/tomcat/webapps/mpcs/WEB-INF/classes directory and
execute the command ". set.classpath.sh" (make sure you have a space
after the initial period before the command...). Then echo
out your $CLASSPATH variable:
root@tomcat:/usr/local/tomcat/webapps/mpcs/WEB-INF/classes#
. set.classpath.sh
root@tomcat:/usr/local/tomcat/webapps/mpcs/WEB-INF/classes#
echo $CLASSPATH
/usr/local/tomcat/lib/servlet-api.jar:/opt/rabbitmq-java-client/amqp-client-4.0.2.jar:/opt/rabbitmq-java-client/slf4j-api-1.7.21.jar:/opt/rabbitmq-java-client/slf4j-simple-1.7.22.jar:.
Don't worry that you do not have the rabbitmq jars. That's
ok. They are there in case you wish to use asynchronous
communication to talk to RabbitMQ. Our little prototype does
not do that.
Now, do an "ls" on the classes directory. You will find
several java files, along with several .class files. Using
vi, hop into HelloWorldServlet.java and take a look at its
contents. Then hop out without saving (":q! [Enter]").
Now, let's compile our java source files. In the classes
directory, execute:
root@tomcat:/usr/local/tomcat/webapps/mpcs/WEB-INF/classes#
javac *.java
STEP 3:
Get RabbitMQ working in a
Docker Container (MyWabbit microservice)
You will reuse your MyWabbit microservice
from the previous lab. If you have removed it or it has
otherwise suffered irreperable damage, you may create another
one. Simply refer back to the instructions for Lab 3, STEP
1, in order to do that. (You may need to docker rm your
current MyWabbit container). You will need to have
net-tools installed in your RabbitMQ container. See
STEP 4 below on how to apt-get net-tools.
One thing. docker exec into the container and execute
ifconfig and write down the IP address reported for your
MyWabbit container. It will be something like
"173.17.0.3". You will need to modify some source code in
both the UserManagement and LoggingService containers
with that IP address.
The good news is after all you had to do for STEP 2 above, this
is all you have to do for STEP 3.
STEP 4:
Get ubuntu working in a Docker
Container (UserManagement microservice)
First, get
down the Ubuntu:18.04 container. Do this by executing the
following command:
docker run -di --hostname usrmgr --name UserManagement
ubuntu:18.04
Once you have that container down (and
running), obtain a bash shell in that container by running
docker exec into the container. Once you are in your UserManagement container,
docker exec into the container and install some software in the
container:
apt-get update
apt-get install -y vim
apt-get install -y net-tools
apt-get install -y
openssh-client
apt-get install -y
openjdk-8-jdk-headless
cd to / and create a subdirectory called "/src/RMI" (mkdir -p
/src/RMI) and change to the /src directory. Next, download
~mark/pub/51205/src/lab4/UserManagement.tgz and copy it into
your container and untar the contents into your RMI
subdirectory.
scp userid@linux.cs.uchicago.edu:/home/mark/pub/51205/src.labs/lab4/UserManagement.tgz .
[enter your userid and password on the cluster]
Now, untar the tarball:
root@usrmgr:/src/#
tar xzvf UserManagement.tgz
root@usrmgr:/src/#
find .
root@usrmgr:/src/#
cd RMI
Now, set your classpath (you will need to do this everytime you
log in, or add the call to your .bashrc file):
root@usrmgr:/src/# . set.classpath.sh
root@usrmgr:/src/# echo $CLASSPATH
/opt/rabbitmq-java-client/amqp-client-4.0.2.jar:/opt/rabbitmq-java-client/slf4j-api-1.7.21.jar:/opt/rabbitmq-java-client/slf4j-simple-1.7.22.jar:/opt/mongo-java-client/mongo-java-driver-2.13.3.jar:.
Now, we need to get your jar balls down and into your /opt
directory, which right now should be empty.
Install the RabbitMQ
client tarball (as you did in lab 3) into your
UserManagement container under the /opt directory:
root@usrmgr:/# cd /opt
root@usrmgr:/# scp
userid@linux.cs.uchicago.edu:/home/mark/pub/51205/src.labs/rabbitmq-java-client.tgz .
[enter your password on the cluster]
root@usrmgr:/# ls rabbit*
rabbitmq-java-client.tgz
root@usrmgr:/opt# tar xzvf
rabbitmq-java-client.tgz
root@usrmgr:/opt# find rabbitmq-java-client/
rabbitmq-java-client/
rabbitmq-java-client/slf4j-simple-1.7.22.jar
rabbitmq-java-client/amqp-client-4.0.2.jar
rabbitmq-java-client/slf4j-api-1.7.21.jar
You will not need to install the mongodb jarball in
UserManagement.
This container will serve as your "UserManagement" microservice
container for this lab.
Now, change to your /src/RMI
directory. Type "javac *.java" to compile everything.
Then, execute:
root@usrmgr:/src/RMI# javac *.java
Examine the java source code beginning with RMIServer.java
carefully. Make sure you understand what is going on.
You should not see any errors. Fix any
issues.
STEP 5:
Get MongoDB working in a
Docker Container (LoggingService microservice)
First, get
down the MongoDB database Server into a new container. Do
this by executing the following docker command:
docker run --hostname
mongodb --name LoggingService -p 28017:28017 -p 27017:27017 -e
MONGODB_PASS="password" -d mongo:3.4-xenial
Read the MongoDB
page on the docker hub to make sure you understand the
implication of these port assignments. Once you have that
container down (and running), obtain a bash shell in that
container by running docker exec into the container. Once
you are in your LoggingService container, install some software
in the container:
apt-get update
apt-get install vim
apt-get install net-tools
apt-get install openssh-client
apt-get
install default-jdk
Next, you'll
need to install both
the mongodb and
rabbitmq java client jarballs:
root@mongodb/# cd /opt
root@mongodb:/opt# scp
userid@linux.cs.uchicago.edu:/stage/classes/archive/2019/fall/51205-1/libs/* .
[enter your password on the cluster]
root@mongodb:/opt# ls rabbit*
rabbitmq-java-client.tgz
root@:/opt# tar xzvf rabbitmq-java-client.tgz
root@mongodb:/opt# find rabbitmq-java-client/
rabbitmq-java-client/
rabbitmq-java-client/slf4j-simple-1.7.22.jar
rabbitmq-java-client/amqp-client-4.0.2.jar
rabbitmq-java-client/slf4j-api-1.7.21.jar
root@mongodb:/opt# ls mongo*
mongo-java-client.tgz
root@mongodb:/opt# tar xzvf mongo-java-client.tgz
root@mongodb:/opt# find mongo-java-client
/opt/mongo-java-client/
/opt/mongo-java-client/mongo-java-driver-2.13.3.jar
/opt/mongo-java-client/gson-2.6.2.jar
/opt/mongo-java-client/._mongo-java-driver-2.13.3.jar
/opt/mongo-java-client/._gson-2.6.2.jar
Now, we need to download our java source code.
Now, cd to the
highest-level "/" directory.
root@mongodb:/# scp
userid@linux.cs.uchicago.edu:/home/mark/pub/51205/src.labs/lab4/mongodb.src.tgz .
[enter your password on the cluster]
Now, untar that tarball:
root@mongodb:/opt# tar xzvf mongodb.src.tgz
Then type "cd /src". Now, set your
classpath (you will need to do this everytime you log in, or add
the call to your .bashrc file):
root@mongodb:/src/# . set.classpath.sh
root@mongodb:/src/# echo $CLASSPATH
/opt/rabbitmq-java-client/amqp-client-4.0.2.jar:/opt/rabbitmq-java-client/slf4j-api-1.7.21.jar:/opt/rabbitmq-java-client/slf4j-simple-1.7.22.jar:/opt/mongo-java-client/mongo-java-driver-2.13.3.jar::.
Type "javac *.java" to compile everything.
root@mongodb:/src# javac *.java
Examine the java source code beginning with Recv.java
carefully. Make sure you understand what is going on.
You should not see
any errors. Fix any issues.
STEP 6:
Run it all.
WebServer startup:
The WebServer should still be running. You may need to
update the IP address of UserManagement in the servlet
code. Get into your UserManagement container and type
"ifconfig" and note the inet addr for eth0. It should be
something like "172.17.0.4". Edit the code located at
/usr/local/tomcat/webapps/mpcs/WEB-INF/classes/HelloWorldServlet.java.
Search
for 172.17. Then change that to the correct IP address for
the RMIServer running in UserManagement. Makd sure that
that address matches your actual IP address of
UserManagement. Save the file and recompile.
Now, go back to your Tomcat Web Application
Manager, and refresh the page. Scroll down and you will
see mpcs. Click on Reload.
UserManagement startup:
Now run the server:
root@usrmgr:/src/RMI# java RMIServer
LoggingService startup:
Now run the server:
root@mongodb:/src# java Recv
NOTE: Should you see a connection refused error in the
logs, then drop the mark.queue, and re-run java Recv.
RabbitMQ startup:
Of course, MyWabbit should already be
running, if it's not, start it again (docker start MyWabbit).
Docker exec into your MyWabbit container (if
you're not already there). You will need to create a new
queue. For the source code to work, the queue needs to be
named "mark.queue". Create the queue from the RabbitMQ
Management Console. Make the queue
a direct exchange and make it durable. Do not set it for
auto delete and do not make it internal. You did something
similar in Lab 3. Refer to Lab 3 STEP 5 if you have any
questions on how to do this.
STEP 7:
Execute.
The WebServer should still be running. Go to the webapp
with the create new user page (http://localhost:8080/mpcs/jsps/starter.kit.html)
and enter a new user and a new user password. Click on the
button.
Then, navigate to your LoggingService terminal, and see if you
have new output. You should see a lot of MONGODB:...
output, but the last line should be "Message To Log: {...}" and
you should see that the newPassword is set to the password you
entered and the newUserName field is set to the new user name
you entered.
Hit control-C (^-C) to exit out of the Recv program, and then
run mongo:
root@mongodb:/src# mongo
At the ">" prompt in mongo, type the
following commands:
> show dbs
> use
loggingService
> show
collections
>
db.logging.find().pretty()
> quit()
And you should see the log for the new user you created in
mongodb, as a new document.
Note that the default
logging location for Tomcat is /usr/local/tomcat/logs/*.
You will find that the log calls (for debugging purposes...not your logging calls to
the LoggingService) in the servlet go to a file called
"localhost.2019.11.14.log" (your mileage may vary with the name,
but you can grep the logs directory for "context.log"), and the
log calls inside the servlet code look like this:
...
context.log("in doPost");
context.log("in Preparing to call RMI");
String userName =
request.getParameter( "user_name" );
context.log("userName is: "+userName);
String userPassword =
request.getParameter( "user_password" );
context.log("userPassword is: "+userPassword);
...
A sample of the output of this logging looks like this:
14-Nov-2019 23:19:05.540 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log in doGet
14-Nov-2019 23:19:05.540 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log calling doPost
14-Nov-2019 23:19:05.540 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log in doPost
14-Nov-2019 23:19:05.540 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log in Preparing to call RMI
14-Nov-2019 23:19:05.541 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log userName is: joe
14-Nov-2019 23:19:05.541 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log userPassword is: blow
14-Nov-2019 23:19:05.541 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log in doPost
Preparing to call RabbitMQ
14-Nov-2019 23:19:05.555 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log in doPost
Preparing to call RMI
14-Nov-2019 23:19:05.557 INFO [http-apr-8080-exec-17]
org.apache.catalina.core.ApplicationContext.log RemoteInterface
and registery set up with address and port
You can add context.log() calls to enhance your understanding of
what the servlet code is doing. The other programs
(RMIServer and Recv) all log straight to the console.
STEP 8:
Review.
It might behoove you to now go back "down" through the code,
from the tomcat servlet through the UserManagement service
through the LoggingService code, and make sure you understand
what all is taking place here.
STEP 9:
Your turn (only if you want).
Now, the servlet really doesn't know whether the user and
password were actually successfully logged in the mongodb
database. Frankly, in the real world, that's perfectly
fine, and the cost of doing business "fire and forget".
But what if the servlet did
want to know that the data was successfully logged before it
returned from doPost? Let's imagine that we do care.
Now, create a
Request/Reply queue in the Rabbit management console (one each)
and have the servlet create a CorrelationID (you can hard-code
this CorrelationID) after it has made the call to the RMI server
in UserManagement, and send the CorrelationID via the Request
queue from doPost().
Then, inside the LoggingService
code, after
successfully writing the data to the mongodb database, have the
LoggingService retrieve
the CorrelationID from the Request queue and send it back to the
servlet via the Reply queue, which will be listening to the
Reply queue.
Once the servlet receives the CorrelationID back from the
LoggingService via the Reply queue, the servlet can then
consider its work "done". Create both the Request and
Reply queues as durable.
Hints: You will need to add the amqp jar to the Tomcat's
lib directory and restart the Tomcat WebServer container.
References:
You
may
find the following references helpful (in addition to the links
from previous labs):
Ubuntu
package
commands
The docker page
for MongoDB
MongoDB
Documentation Home Page
Microservices resources at MongoDB
General Docker Tutorial Links
Docker
Cheat
Sheet
Submission:
For Labs:
Each student should submit their LabN files and any supporting
materials to the repo. Please include a README text file that
contains any instructions or additional details for the TAs to
assist with grading.
Setup:
When you open the invitation URL in a browser tab, you will have
to complete the following steps:
Github may ask you to grant permission for GitHub Classroom to
have access to your GitHub account. If you are asked this
question, you should say yes and grant access to Github
Classroom.
You must click “Accept this assignment” or your repository will
not actually be created. Do not skip this step!
If you run into any issues, please ask for help.
Use
the folder named "lab_4" in your Subversion repository (create
it if necessary). Upload your Lab 4 microservice tarballs to
this repo. Please include a README text file that contains
any instructions for the TAs to assist with grading, and design
notes are often the most useful thing you can provide. We do not
usually need any info on how to compile your code unless your
code layout is arcane.