docs: adding tutorial 1

This commit is contained in:
Blake J. Harnden 2023-06-05 11:11:00 -07:00
parent 01585b6ec5
commit a5727e3355
12 changed files with 764 additions and 0 deletions

View file

@ -0,0 +1,22 @@
## gRPC Python Scripts
You can also run the same steps above, using the provided gRPC script versions of scenarios.
Below are the steps to run and join one of these scenario, then you can continue with
the remaining steps of a given section.
1. Make sure the CORE daemon is running a terminal, if not already
``` shell
sudop core-daemon
```
2. From another terminal run the tutorial python script, which will create a session to join
``` shell
/opt/core/venv/bin/python scenario.py
```
3. In another terminal run the CORE GUI
``` shell
core-gui
```
4. You will be presented with sessions to join, select the one created by the script
<p align="center">
<img src="images/tutorial-common/running-join.png" width="75%">
</p>

82
docs/tutorials/setup.md Normal file
View file

@ -0,0 +1,82 @@
# Tutorial Setup
## Setup for CORE
We assume the prior installation of CORE, using a virtual environment. You can
then adjust your PATH and add an alias to help more conveniently run CORE
commands.
This can be setup in your **.bashrc**
```shell
export PATH=$PATH:/opt/core/venv/bin
alias sudop='sudo env PATH=$PATH'
```
## Setup for Chat App
There is a simple TCP chat app provided as example software to use and run within
the tutorials provided.
### Installation
The following will install chatapp and its scripts into **/usr/local**, which you
may need to add to PATH within node to be able to use command directly.
``` shell
sudo python3 -m pip install .
```
!!! note
Some Linux distros will not have **/usr/local** in their PATH and you
will need to compensate.
``` shell
export PATH=$PATH:/usr/local
```
### Running the Server
The server will print and log connected clients and their messages.
``` shell
usage: chatapp-server [-h] [-a ADDRESS] [-p PORT]
chat app server
optional arguments:
-h, --help show this help message and exit
-a ADDRESS, --address ADDRESS
address to listen on (default: )
-p PORT, --port PORT port to listen on (default: 9001)
```
### Running the Client
The client will print and log messages from other clients and their join/leave status.
``` shell
usage: chatapp-client [-h] -a ADDRESS [-p PORT]
chat app client
optional arguments:
-h, --help show this help message and exit
-a ADDRESS, --address ADDRESS
address to listen on (default: None)
-p PORT, --port PORT port to listen on (default: 9001)
```
### Installing the Chat App Service
1. You will first need to edit **/etc/core/core.conf** to update the config
service path to pick up your service
``` shell
custom_config_services_dir = <path for service>
```
2. Then you will need to copy/move **chatapp/chatapp_service.py** to the directory
configured above
3. Then you will need to restart the **core-daemon** to pick up this new service
4. Now the service will be an available option under the group **ChatApp** with
the name **ChatApp Server**

250
docs/tutorials/tutorial1.md Normal file
View file

@ -0,0 +1,250 @@
# Tutorial 1 - Wired Network
This tutorial will cover some use cases when using a wired 2 node
scenario in CORE.
<p align="center">
<img src="images/tutorial1/scenario.png" width="75%">
</p>
## Files
Below is the list of files used for this tutorial.
* 2 node wired scenario
* scenario.xml
* scenario.py
* 2 node wired scenario, with **n1** running the "Chat App Server" service
* scenario_service.xml
* scenario_service.py
## Running this Tutorial
This section covers interactions that can be carried out for this scenario.
Our scenario has the following nodes and addresses:
* n1 - 10.0.0.20
* n2 - 10.0.0.21
All usages below assume a clean scenario start.
### Using Ping
Using the command line utility **ping** can be a good way to verify connectivity
between nodes in CORE.
* Make sure the CORE daemon is running a terminal, if not already
``` shell
sudop core-daemon
```
* In another terminal run the GUI
``` shell
core-gui
```
* In the GUI menu bar select **File->Open...**, then navigate to and select **scenario.xml**
<p align="center">
<img src="images/tutorial-common/running-open.png" width="75%">
</p>
* You can now click on the **Start Session** button to run the scenario
<p align="center">
<img src="images/tutorial1/scenario.png" width="75%">
</p>
* Open a terminal on **n1** by double clicking it in the GUI
* Run the following in **n1** terminal
``` shell
ping -c 3 10.0.0.21
```
* You should see the following output
``` shell
PING 10.0.0.21 (10.0.0.21) 56(84) bytes of data.
64 bytes from 10.0.0.21: icmp_seq=1 ttl=64 time=0.085 ms
64 bytes from 10.0.0.21: icmp_seq=2 ttl=64 time=0.079 ms
64 bytes from 10.0.0.21: icmp_seq=3 ttl=64 time=0.072 ms
--- 10.0.0.21 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.072/0.078/0.085/0.011 ms
```
### Using Tcpdump
Using **tcpdump** can be very beneficial for examining a network. You can verify
traffic being sent/received among many other uses.
* Make sure the CORE daemon is running a terminal, if not already
``` shell
sudop core-daemon
```
* In another terminal run the GUI
``` shell
core-gui
```
* In the GUI menu bar select **File->Open...**, then navigate to and select **scenario.xml**
<p align="center">
<img src="images/tutorial-common/running-open.png" width="75%">
</p>
* You can now click on the **Start Session** button to run the scenario
<p align="center">
<img src="images/tutorial1/scenario.png" width="75%">
</p>
* Open a terminal on **n1** by double clicking it in the GUI
* Open a terminal on **n2** by double clicking it in the GUI
* Run the following in **n2** terminal
``` shell
tcpdump -lenni eth0
```
* Run the following in **n1** terminal
``` shell
ping -c 1 10.0.0.21
```
* You should see the following in **n2** terminal
``` shell
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:23:04.685292 00:00:00:aa:00:00 > 00:00:00:aa:00:01, ethertype IPv4 (0x0800), length 98: 10.0.0.20 > 10.0.0.21: ICMP echo request, id 67, seq 1, length 64
10:23:04.685329 00:00:00:aa:00:01 > 00:00:00:aa:00:00, ethertype IPv4 (0x0800), length 98: 10.0.0.21 > 10.0.0.20: ICMP echo reply, id 67, seq 1, length 64
```
### Editing a Link
You can edit links between nodes in CORE to modify loss, delay, bandwidth, and more. This can be
beneficial for understanding how software will behave in adverse conditions.
* Make sure the CORE daemon is running a terminal, if not already
``` shell
sudop core-daemon
```
* In another terminal run the GUI
``` shell
core-gui
```
* In the GUI menu bar select **File->Open...**, then navigate to and select **scenario.xml**
<p align="center">
<img src="images/tutorial-common/running-open.png" width="75%">
</p>
* You can now click on the **Start Session** button to run the scenario
<p align="center">
<img src="images/tutorial1/scenario.png" width="75%">
</p>
* Right click the link between **n1** and **n2**
* Select **Configure**
<p align="center">
<img src="images/tutorial1/link-config.png" width="75%">
</p>
* Update the loss to **25**
<p align="center">
<img src="images/tutorial1/link-config-dialog.png" width="50%">
</p>
* Open a terminal on **n1** by double clicking it in the GUI
* Run the following in **n1** terminal
``` shell
ping -c 10 10.0.0.21
```
* You should see something similar for the summary output, reflecting the change in loss
``` shell
--- 10.0.0.21 ping statistics ---
10 packets transmitted, 6 received, 40% packet loss, time 9000ms
rtt min/avg/max/mdev = 0.077/0.093/0.108/0.016 ms
```
* Remember that the loss above is compounded, since a ping and the loss applied occurs in both directions
### Running Software
We will now leverage the installed Chat App software to stand up a server and client
within the nodes of our scenario.
* Make sure the CORE daemon is running a terminal, if not already
``` shell
sudop core-daemon
```
* In another terminal run the GUI
``` shell
core-gui
```
* In the GUI menu bar select **File->Open...**, then navigate to and select **scenario.xml**
<p align="center">
<img src="images/tutorial-common/running-open.png" width="75%">
</p>
* You can now click on the **Start Session** button to run the scenario
<p align="center">
<img src="images/tutorial1/scenario.png" width="75%">
</p>
* Open a terminal on **n1** by double clicking it in the GUI
* Run the following in **n1** terminal
``` shell
export PATH=$PATH:/usr/local/bin
chatapp-server
```
* Open a terminal on **n2** by double clicking it in the GUI
* Run the following in **n2** terminal
``` shell
export PATH=$PATH:/usr/local/bin
chatapp-client -a 10.0.0.20
```
* You will see the following output in **n1** terminal
``` shell
chat server listening on: :9001
[server] 10.0.0.21:44362 joining
```
* Type the following in **n2** terminal and hit enter
``` shell
hello world
```
* You will see the following output in **n1** terminal
``` shell
chat server listening on: :9001
[server] 10.0.0.21:44362 joining
[10.0.0.21:44362] hello world
```
### Tailing a Log
In this case we are using the service based scenario. This will automatically start
and run the Chat App Server on **n1** and log to a file. This case will demonstrate
using `tail -f` to observe the output of running software.
* Make sure the CORE daemon is running a terminal, if not already
``` shell
sudop core-daemon
```
* In another terminal run the GUI
``` shell
core-gui
```
* In the GUI menu bar select **File->Open...**, then navigate to and select **scenario_service.xml**
<p align="center">
<img src="images/tutorial-common/running-open.png" width="75%">
</p>
* You can now click on the **Start Session** button to run the scenario
<p align="center">
<img src="images/tutorial1/scenario.png" width="75%">
</p>
* Open a terminal on **n1** by double clicking it in the GUI
* Run the following in **n1** terminal
``` shell
tail -f chatapp.log
```
* Open a terminal on **n2** by double clicking it in the GUI
* Run the following in **n2** terminal
``` shell
export PATH=$PATH:/usr/local/bin
chatapp-client -a 10.0.0.20
```
* You will see the following output in **n1** terminal
``` shell
chat server listening on: :9001
[server] 10.0.0.21:44362 joining
```
* Type the following in **n2** terminal and hit enter
``` shell
hello world
```
* You will see the following output in **n1** terminal
``` shell
chat server listening on: :9001
[server] 10.0.0.21:44362 joining
[10.0.0.21:44362] hello world
```
--8<-- "common/grpc.md"

View file

@ -0,0 +1,63 @@
import argparse
import select
import socket
import sys
import termios
DEFAULT_PORT: int = 9001
READ_SIZE: int = 4096
def prompt():
sys.stdout.write(">> ")
sys.stdout.flush()
class ChatClient:
def __init__(self, address, port):
self.address = address
self.port = port
def run(self):
server = socket.create_connection((self.address, self.port))
sockname = server.getsockname()
print(f"connected to server({self.address}:{self.port}) as client({sockname[0]}:{sockname[1]})")
sockets = [sys.stdin, server]
prompt()
try:
while True:
read_sockets, write_socket, error_socket = select.select(sockets, [], [])
for sock in read_sockets:
if sock == server:
message = server.recv(READ_SIZE)
if not message:
print("server closed")
sys.exit(1)
else:
termios.tcflush(sys.stdin, termios.TCIOFLUSH)
print("\x1b[2K\r", end="")
print(message.decode().strip())
prompt()
else:
message = sys.stdin.readline().strip()
server.sendall(f"{message}\n".encode())
prompt()
except KeyboardInterrupt:
print("client exiting")
server.close()
def main():
parser = argparse.ArgumentParser(
description="chat app client",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("-a", "--address", help="address to listen on", required=True)
parser.add_argument("-p", "--port", type=int, help="port to listen on", default=DEFAULT_PORT)
args = parser.parse_args()
client = ChatClient(args.address, args.port)
client.run()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,73 @@
import argparse
import select
import socket
DEFAULT_ADDRESS: str = ""
DEFAULT_PORT: int = 9001
READ_SIZE: int = 4096
class ChatServer:
def __init__(self, address, port):
self.address = address
self.port = port
self.sockets = []
def broadcast(self, ignore, message):
for sock in self.sockets:
if sock not in ignore:
sock.sendall(message.encode())
def run(self):
print(f"chat server listening on: {self.address}:{self.port}")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((self.address, self.port))
server.listen()
self.sockets.append(server)
try:
while True:
read_sockets, write_sockets, error_sockets = select.select(self.sockets, [], [])
for sock in read_sockets:
if sock == server:
client_sock, addr = server.accept()
self.sockets.append(client_sock)
name = f"{addr[0]}:{addr[1]}"
print(f"[server] {name} joining")
self.broadcast({server, client_sock}, f"[server] {name} entered room\n")
else:
peer = sock.getpeername()
name = f"{peer[0]}:{peer[1]}"
try:
data = sock.recv(READ_SIZE).decode().strip()
if data:
print(f"[{name}] {data}")
self.broadcast({server, sock}, f"[{name}] {data}\n")
else:
print(f"[server] {name} leaving")
self.broadcast({server, sock}, f"[server] {name} leaving\n")
sock.close()
self.sockets.remove(sock)
except socket.error:
print(f"[server] {name} leaving")
self.broadcast({server, sock}, f"[server] {name} leaving\n")
sock.close()
self.sockets.remove(sock)
except KeyboardInterrupt:
print("closing server")
def main():
parser = argparse.ArgumentParser(
description="chat app server",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("-a", "--address", help="address to listen on", default=DEFAULT_ADDRESS)
parser.add_argument("-p", "--port", type=int, help="port to listen on", default=DEFAULT_PORT)
args = parser.parse_args()
server = ChatServer(args.address, args.port)
server.run()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,26 @@
from typing import Dict, List
from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode, ShadowDir
class ChatAppService(ConfigService):
name: str = "ChatApp Server"
group: str = "ChatApp"
directories: List[str] = []
files: List[str] = ["chatapp.sh"]
executables: List[str] = []
dependencies: List[str] = []
startup: List[str] = [f"bash {files[0]}"]
validate: List[str] = []
shutdown: List[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
shadow_directories: List[ShadowDir] = []
def get_text_template(self, _name: str) -> str:
return """
export PATH=$PATH:/usr/local/bin
PYTHONUNBUFFERED=1 chatapp-server > chatapp.log 2>&1 &
"""

View file

@ -0,0 +1,17 @@
from setuptools import setup, find_packages
setup(
name="chatapp",
version="0.1.0",
packages=find_packages(),
description="Chat App",
entry_points={
"console_scripts": [
"chatapp-client = chatapp.client:main",
"chatapp-server = chatapp.server:main",
],
},
include_package_data=True,
zip_safe=False,
python_requires=">=3.6",
)

View file

@ -0,0 +1,36 @@
from core.api.grpc import client
from core.api.grpc.wrappers import Position
def main():
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session
session = core.create_session()
# create nodes
position = Position(x=250, y=250)
node1 = session.add_node(_id=1, name="n1", position=position)
position = Position(x=500, y=250)
node2 = session.add_node(_id=2, name="n2", position=position)
# create link
node1_iface = iface_helper.create_iface(node_id=node1.id, iface_id=0)
node1_iface.ip4 = "10.0.0.20"
node1_iface.ip6 = "2001::14"
node2_iface = iface_helper.create_iface(node_id=node2.id, iface_id=0)
node2_iface.ip4 = "10.0.0.21"
node2_iface.ip6 = "2001::15"
session.add_link(node1=node1, node2=node2, iface1=node1_iface, iface2=node2_iface)
# start session
core.start_session(session=session)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,77 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/tmp/tmpa_gpqzdf">
<networks/>
<devices>
<device id="1" name="n1" icon="" canvas="1" type="PC" class="" image="">
<position x="250.0" y="250.0" lat="47.57679395743362" lon="-122.12891511224676" alt="2.0"/>
<configservices>
<service name="DefaultRoute"/>
</configservices>
</device>
<device id="2" name="n2" icon="" canvas="1" type="PC" class="" image="">
<position x="500.0" y="250.0" lat="47.576775777287395" lon="-122.12534430899237" alt="2.0"/>
<configservices>
<service name="DefaultRoute"/>
</configservices>
</device>
</devices>
<links>
<link node1="1" node2="2">
<iface1 id="0" name="eth0" mac="00:00:00:aa:00:00" ip4="10.0.0.20" ip4_mask="24" ip6="2001::14" ip6_mask="64"/>
<iface2 id="0" name="eth0" mac="00:00:00:aa:00:01" ip4="10.0.0.21" ip4_mask="24" ip6="2001::15" ip6_mask="64"/>
<options delay="0" bandwidth="0" loss="0.0" dup="0" jitter="0" unidirectional="0" buffer="0"/>
</link>
</links>
<emane_global_configuration>
<emulator/>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value=""/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="shapes" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;dimensions&quot;: [1000, 750], &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false}]}"/>
<configuration name="edges" value="[]"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -0,0 +1,37 @@
from core.api.grpc import client
from core.api.grpc.wrappers import Position
def main():
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# create session
session = core.create_session()
# create nodes
position = Position(x=250, y=250)
node1 = session.add_node(_id=1, name="n1", position=position)
node1.config_services.add("ChatApp Server")
position = Position(x=500, y=250)
node2 = session.add_node(_id=2, name="n2", position=position)
# create link
node1_iface = iface_helper.create_iface(node_id=node1.id, iface_id=0)
node1_iface.ip4 = "10.0.0.20"
node1_iface.ip6 = "2001::14"
node2_iface = iface_helper.create_iface(node_id=node2.id, iface_id=0)
node2_iface.ip4 = "10.0.0.21"
node2_iface.ip6 = "2001::15"
session.add_link(node1=node1, node2=node2, iface1=node1_iface, iface2=node2_iface)
# start session
core.start_session(session=session)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,81 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/tmp/tmp2exmy1y9">
<networks/>
<devices>
<device id="1" name="n1" icon="" canvas="0" type="PC" class="" image="">
<position x="250.0" y="250.0" lat="47.576893948125004" lon="-122.12895553643455" alt="2.0"/>
<configservices>
<service name="ChatApp Server"/>
<service name="DefaultRoute"/>
</configservices>
</device>
<device id="2" name="n2" icon="" canvas="0" type="PC" class="" image="">
<position x="500.0" y="250.0" lat="47.576893948125004" lon="-122.12558685411909" alt="2.0"/>
<configservices>
<service name="DefaultRoute"/>
</configservices>
</device>
</devices>
<links>
<link node1="1" node2="2">
<iface1 id="0" name="eth0" mac="00:00:00:aa:00:00" ip4="10.0.0.20" ip4_mask="24" ip6="2001::14" ip6_mask="64"/>
<iface2 id="0" name="eth0" mac="00:00:00:aa:00:01" ip4="10.0.0.21" ip4_mask="24" ip6="2001::15" ip6_mask="64"/>
<options delay="0" bandwidth="0" loss="0.0" dup="0" jitter="0" unidirectional="0" buffer="0"/>
</link>
</links>
<emane_global_configuration>
<emulator/>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<configservice_configurations>
<service name="ChatApp Server" node="1"/>
</configservice_configurations>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value=""/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="shapes" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;dimensions&quot;: [1000, 750], &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false}]}"/>
<configuration name="edges" value="[]"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>