Compare commits
No commits in common. "master" and "develop" have entirely different histories.
242 changed files with 4064 additions and 8871 deletions
6
.github/workflows/daemon-checks.yml
vendored
6
.github/workflows/daemon-checks.yml
vendored
|
@ -4,13 +4,13 @@ on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-18.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.6
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.6
|
||||||
- name: install poetry
|
- name: install poetry
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
|
21
.github/workflows/documentation.yml
vendored
21
.github/workflows/documentation.yml
vendored
|
@ -1,21 +0,0 @@
|
||||||
name: documentation
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: 3.x
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
key: ${{ github.ref }}
|
|
||||||
path: .cache
|
|
||||||
- run: pip install mkdocs-material
|
|
||||||
- run: mkdocs gh-deploy --force
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -14,13 +14,9 @@ config.h.in
|
||||||
config.log
|
config.log
|
||||||
config.status
|
config.status
|
||||||
configure
|
configure
|
||||||
configure~
|
|
||||||
debian
|
debian
|
||||||
stamp-h1
|
stamp-h1
|
||||||
|
|
||||||
# python virtual environments
|
|
||||||
venv
|
|
||||||
|
|
||||||
# generated protobuf files
|
# generated protobuf files
|
||||||
*_pb2.py
|
*_pb2.py
|
||||||
*_pb2_grpc.py
|
*_pb2_grpc.py
|
||||||
|
|
90
CHANGELOG.md
90
CHANGELOG.md
|
@ -1,93 +1,3 @@
|
||||||
## 2023-08-01 CORE 9.0.3
|
|
||||||
|
|
||||||
* Installation
|
|
||||||
* updated various dependencies
|
|
||||||
* Documentation
|
|
||||||
* improved GUI docs to include node interaction and note xhost usage
|
|
||||||
* \#780 - fixed gRPC examples
|
|
||||||
* \#787 - complete documentation revamp to leverage mkdocs material
|
|
||||||
* \#790 - fixed custom emane model example
|
|
||||||
* core-daemon
|
|
||||||
* update type hinting to avoid deprecated imports
|
|
||||||
* updated commands ran within docker based nodes to have proper environment variables
|
|
||||||
* fixed issue improperly setting session options over gRPC
|
|
||||||
* \#668 - add fedora sbin path to frr service
|
|
||||||
* \#774 - fixed pcap configservice
|
|
||||||
* \#805 - fixed radvd configservice template error
|
|
||||||
* core-gui
|
|
||||||
* update type hinting to avoid deprecated imports
|
|
||||||
* fixed issue allowing duplicate named hook scripts
|
|
||||||
* fixed issue joining sessions with RJ45 nodes
|
|
||||||
* utility scripts
|
|
||||||
* fixed issue in core-cleanup for removing devices
|
|
||||||
|
|
||||||
## 2023-03-02 CORE 9.0.2
|
|
||||||
|
|
||||||
* Installation
|
|
||||||
* updated python dependencies, including invoke to resolve python 3.10+ issues
|
|
||||||
* improved example dockerfiles to use less space for built images
|
|
||||||
* Documentation
|
|
||||||
* updated emane install instructions
|
|
||||||
* added Docker related issues to install instructions
|
|
||||||
* core-daemon
|
|
||||||
* fixed issue using invalid device name in sysctl commands
|
|
||||||
* updated PTP nodes to properly disable mac learning for their linux bridge
|
|
||||||
* fixed issue for LXC nodes to properly use a configured image name and write it to XML
|
|
||||||
* \#742 - fixed issue with bad wlan node id being used
|
|
||||||
* \#744 - fixed issue not properly setting broadcast address
|
|
||||||
* core-gui
|
|
||||||
* fixed sample1.xml to remove SSH service
|
|
||||||
* fixed emane demo examples
|
|
||||||
* fixed issue displaying emane configs generally configured for a node
|
|
||||||
|
|
||||||
## 2022-11-28 CORE 9.0.1
|
|
||||||
|
|
||||||
* Installation
|
|
||||||
* updated protobuf and grpcio-tools versions in pyproject.toml to account for bad version mix
|
|
||||||
|
|
||||||
## 2022-11-18 CORE 9.0.0
|
|
||||||
|
|
||||||
* Breaking Changes
|
|
||||||
* removed session nodes file
|
|
||||||
* removed session state file
|
|
||||||
* emane now runs in one process per nem with unique control ports
|
|
||||||
* grpc client has been refactored and updated
|
|
||||||
* removed tcl/legacy gui, imn file support and the tlv api
|
|
||||||
* link configuration is now different, but consistent, for wired links
|
|
||||||
* Installation
|
|
||||||
* added packaging for single file distribution
|
|
||||||
* python3.9 is now the minimum required version
|
|
||||||
* updated Dockerfile examples
|
|
||||||
* updated various python dependencies
|
|
||||||
* virtual environment is now installed to /opt/core/venv
|
|
||||||
* Documentation
|
|
||||||
* updated emane invoke task examples
|
|
||||||
* revamped install documentation
|
|
||||||
* added wireless node notes
|
|
||||||
* core-gui
|
|
||||||
* updated config services to display rendered templated and allow editing
|
|
||||||
* fixed node icon issue when updating preferences
|
|
||||||
* \#89 - throughput widget now works for hubs/switches
|
|
||||||
* \#691 - fixed custom nodes to properly use config services
|
|
||||||
* gRPC API
|
|
||||||
* add linked call to support linking and unlinking interfaces without destroying them
|
|
||||||
* fixed issue during start session clearing out session options
|
|
||||||
* added call to get rendered config service files
|
|
||||||
* removed get_node_links from links from client
|
|
||||||
* nem id and nem port have been added to GetNode and AddLink calls
|
|
||||||
* core-daemon
|
|
||||||
* wired links always create two veth pairs joined by a bridge
|
|
||||||
* node interfaces are now configured within the container to apply to outgoing traffic
|
|
||||||
* session.add_node now uses NodeOptions, allowing for node specific options
|
|
||||||
* fixed issue with xml reading node canvas values
|
|
||||||
* removed Session.add_node_file
|
|
||||||
* fixed get requirements logic
|
|
||||||
* fixed docker/lxd node support terminal commands on remote servers
|
|
||||||
* improved docker node command execution time using nsenter
|
|
||||||
* new wireless node type added to support dynamic loss based on distance
|
|
||||||
* \#513 - add and deleting distributed links during runtime is now supported
|
|
||||||
* \#703 - fixed issue not starting emane event listening service
|
|
||||||
|
|
||||||
## 2022-03-21 CORE 8.2.0
|
## 2022-03-21 CORE 8.2.0
|
||||||
|
|
||||||
* core-gui
|
* core-gui
|
||||||
|
|
126
Dockerfile
126
Dockerfile
|
@ -1,126 +0,0 @@
|
||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM ubuntu:22.04
|
|
||||||
LABEL Description="CORE Docker Ubuntu Image"
|
|
||||||
|
|
||||||
ARG PREFIX=/usr/local
|
|
||||||
ARG BRANCH=master
|
|
||||||
ARG PROTOC_VERSION=3.19.6
|
|
||||||
ARG VENV_PATH=/opt/core/venv
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV PATH="$PATH:${VENV_PATH}/bin"
|
|
||||||
WORKDIR /opt
|
|
||||||
|
|
||||||
# install system dependencies
|
|
||||||
|
|
||||||
RUN apt-get update -y && \
|
|
||||||
apt-get install -y software-properties-common
|
|
||||||
|
|
||||||
RUN add-apt-repository "deb http://archive.ubuntu.com/ubuntu jammy universe"
|
|
||||||
|
|
||||||
RUN apt-get update -y && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
automake \
|
|
||||||
bash \
|
|
||||||
ca-certificates \
|
|
||||||
ethtool \
|
|
||||||
gawk \
|
|
||||||
gcc \
|
|
||||||
g++ \
|
|
||||||
iproute2 \
|
|
||||||
iputils-ping \
|
|
||||||
libc-dev \
|
|
||||||
libev-dev \
|
|
||||||
libreadline-dev \
|
|
||||||
libtool \
|
|
||||||
nftables \
|
|
||||||
python3 \
|
|
||||||
python3-pip \
|
|
||||||
python3-tk \
|
|
||||||
pkg-config \
|
|
||||||
tk \
|
|
||||||
xauth \
|
|
||||||
xterm \
|
|
||||||
wireshark \
|
|
||||||
vim \
|
|
||||||
build-essential \
|
|
||||||
nano \
|
|
||||||
firefox \
|
|
||||||
net-tools \
|
|
||||||
rsync \
|
|
||||||
openssh-server \
|
|
||||||
openssh-client \
|
|
||||||
vsftpd \
|
|
||||||
atftpd \
|
|
||||||
atftp \
|
|
||||||
mini-httpd \
|
|
||||||
lynx \
|
|
||||||
tcpdump \
|
|
||||||
iperf \
|
|
||||||
iperf3 \
|
|
||||||
tshark \
|
|
||||||
openssh-sftp-server \
|
|
||||||
bind9 \
|
|
||||||
bind9-utils \
|
|
||||||
openvpn \
|
|
||||||
isc-dhcp-server \
|
|
||||||
isc-dhcp-client \
|
|
||||||
whois \
|
|
||||||
ipcalc \
|
|
||||||
socat \
|
|
||||||
hping3 \
|
|
||||||
libgtk-3-0 \
|
|
||||||
librest-0.7-0 \
|
|
||||||
libgtk-3-common \
|
|
||||||
dconf-gsettings-backend \
|
|
||||||
libsoup-gnome2.4-1 \
|
|
||||||
libsoup2.4-1 \
|
|
||||||
dconf-service \
|
|
||||||
x11-xserver-utils \
|
|
||||||
ftp \
|
|
||||||
git \
|
|
||||||
sudo \
|
|
||||||
wget \
|
|
||||||
tzdata \
|
|
||||||
libpcap-dev \
|
|
||||||
libpcre3-dev \
|
|
||||||
libprotobuf-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
protobuf-compiler \
|
|
||||||
unzip \
|
|
||||||
uuid-dev \
|
|
||||||
iproute2 \
|
|
||||||
vlc \
|
|
||||||
iputils-ping && \
|
|
||||||
apt-get autoremove -y
|
|
||||||
|
|
||||||
# install core
|
|
||||||
RUN git clone https://github.com/coreemu/core && \
|
|
||||||
cd core && \
|
|
||||||
git checkout ${BRANCH} && \
|
|
||||||
./setup.sh && \
|
|
||||||
PATH=/root/.local/bin:$PATH inv install -v -p ${PREFIX} && \
|
|
||||||
cd /opt && \
|
|
||||||
rm -rf ospf-mdr
|
|
||||||
|
|
||||||
# install emane
|
|
||||||
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip && \
|
|
||||||
mkdir protoc && \
|
|
||||||
unzip protoc-${PROTOC_VERSION}-linux-x86_64.zip -d protoc && \
|
|
||||||
git clone https://github.com/adjacentlink/emane.git && \
|
|
||||||
cd emane && \
|
|
||||||
./autogen.sh && \
|
|
||||||
./configure --prefix=/usr && \
|
|
||||||
make -j$(nproc) && \
|
|
||||||
make install && \
|
|
||||||
cd src/python && \
|
|
||||||
make clean && \
|
|
||||||
PATH=/opt/protoc/bin:$PATH make && \
|
|
||||||
${VENV_PATH}/bin/python -m pip install . && \
|
|
||||||
cd /opt && \
|
|
||||||
rm -rf protoc && \
|
|
||||||
rm -rf emane && \
|
|
||||||
rm -f protoc-${PROTOC_VERSION}-linux-x86_64.zip
|
|
||||||
|
|
||||||
WORKDIR /root
|
|
||||||
|
|
||||||
CMD /opt/core/venv/bin/core-daemon
|
|
39
Dockerfile.centos
Normal file
39
Dockerfile.centos
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM centos:7
|
||||||
|
LABEL Description="CORE Docker CentOS Image"
|
||||||
|
|
||||||
|
# define variables
|
||||||
|
ARG PREFIX=/usr
|
||||||
|
ARG BRANCH=master
|
||||||
|
|
||||||
|
# define environment
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
|
||||||
|
# install core
|
||||||
|
RUN yum -y update && \
|
||||||
|
yum install -y git sudo wget tzdata unzip python3
|
||||||
|
WORKDIR /root
|
||||||
|
RUN git clone https://github.com/coreemu/core
|
||||||
|
WORKDIR /root/core
|
||||||
|
RUN git checkout ${BRANCH}
|
||||||
|
RUN ./setup.sh && python3 -m pip install --upgrade pip
|
||||||
|
RUN . /root/.bashrc && inv install -v -p ${PREFIX}
|
||||||
|
# install emane packages and python bindings
|
||||||
|
WORKDIR /root
|
||||||
|
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.3.3-release-1.el7.x86_64.tar.gz && \
|
||||||
|
tar xf emane-1.3.3-release-1.el7.x86_64.tar.gz && \
|
||||||
|
cd emane-1.3.3-release-1/rpms/el7/x86_64 && \
|
||||||
|
yum install -y epel-release && \
|
||||||
|
yum install -y ./openstatistic*.rpm ./emane*.rpm ./python3-emane_*.rpm && \
|
||||||
|
cd ../../../.. && \
|
||||||
|
rm emane-1.3.3-release-1.el7.x86_64.tar.gz && \
|
||||||
|
rm -rf emane-1.3.3-release-1
|
||||||
|
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-x86_64.zip && \
|
||||||
|
mkdir protoc && \
|
||||||
|
unzip protoc-3.7.1-linux-x86_64.zip -d protoc
|
||||||
|
WORKDIR /root/core
|
||||||
|
RUN . /root/.bashrc && PATH=/root/protoc/bin:$PATH inv install-emane -v -e v1.3.3
|
||||||
|
|
||||||
|
# run daemon
|
||||||
|
CMD ["core-daemon"]
|
37
Dockerfile.oracle
Normal file
37
Dockerfile.oracle
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM oraclelinux:8
|
||||||
|
LABEL Description="CORE Docker Oracle Linux Image"
|
||||||
|
|
||||||
|
# define variables
|
||||||
|
ARG PREFIX=/usr
|
||||||
|
ARG BRANCH=master
|
||||||
|
|
||||||
|
# define environment
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
|
||||||
|
# install core
|
||||||
|
RUN yum -y update && \
|
||||||
|
yum install -y git sudo wget tzdata unzip python39 which iproute-tc hostname xterm
|
||||||
|
WORKDIR /root
|
||||||
|
RUN git clone https://github.com/coreemu/core
|
||||||
|
WORKDIR /root/core
|
||||||
|
RUN git checkout ${BRANCH}
|
||||||
|
RUN PYTHON=python3.9 PYTHON_DEP=python39 ./setup.sh
|
||||||
|
RUN . /root/.bashrc && PYTHON=python3.9 PYTHON_DEP=python39 inv install -v -p ${PREFIX}
|
||||||
|
# install emane packages and python bindings
|
||||||
|
WORKDIR /root
|
||||||
|
RUN yum config-manager --set-enabled ol8_codeready_builder && yum install -y protobuf-devel
|
||||||
|
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.3.3-release-1.el8.x86_64.tar.gz && \
|
||||||
|
tar xf emane-1.3.3-release-1.el8.x86_64.tar.gz && \
|
||||||
|
cd emane-1.3.3-release-1/rpms/el8/x86_64 && \
|
||||||
|
yum install -y epel-release && \
|
||||||
|
yum install -y ./openstatistic*.rpm ./emane*.rpm ./python3-emane-1.3.3*.rpm && \
|
||||||
|
cd ../../../.. && \
|
||||||
|
rm emane-1.3.3-release-1.el8.x86_64.tar.gz && \
|
||||||
|
rm -rf emane-1.3.3-release-1
|
||||||
|
WORKDIR /root/core
|
||||||
|
RUN . /root/.bashrc && PYTHON=python3.9 PATH=/root/protoc/bin:$PATH inv install-emane -v -e v1.3.3
|
||||||
|
|
||||||
|
# run daemon
|
||||||
|
CMD ["core-daemon"]
|
34
Dockerfile.ubuntu
Normal file
34
Dockerfile.ubuntu
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM ubuntu:20.04
|
||||||
|
LABEL Description="CORE Docker Ubuntu Image"
|
||||||
|
|
||||||
|
# define variables
|
||||||
|
ARG PREFIX=/usr/local
|
||||||
|
ARG BRANCH=master
|
||||||
|
|
||||||
|
# define environment
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# install core
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y git sudo wget tzdata
|
||||||
|
WORKDIR /root
|
||||||
|
RUN git clone https://github.com/coreemu/core
|
||||||
|
WORKDIR /root/core
|
||||||
|
RUN git checkout ${BRANCH}
|
||||||
|
RUN ./setup.sh
|
||||||
|
RUN . /root/.bashrc && inv install -v -p ${PREFIX}
|
||||||
|
# install emane packages and python bindings
|
||||||
|
WORKDIR /root
|
||||||
|
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.3.3-release-1.ubuntu-20_04.amd64.tar.gz && \
|
||||||
|
tar xf emane*.tar.gz && \
|
||||||
|
cd emane-1.3.3-release-1/debs/ubuntu-20_04/amd64 && \
|
||||||
|
apt-get install -y ./emane*.deb ./python3-emane_*.deb && \
|
||||||
|
cd ../../../.. && \
|
||||||
|
rm emane-1.3.3-release-1.ubuntu-20_04.amd64.tar.gz && \
|
||||||
|
rm -rf emane-1.3.3-release-1
|
||||||
|
WORKDIR /root/core
|
||||||
|
RUN . /root/.bashrc && inv install-emane -v -e v1.3.3
|
||||||
|
|
||||||
|
# run daemon
|
||||||
|
CMD ["core-daemon"]
|
120
README.md
120
README.md
|
@ -1,13 +1,3 @@
|
||||||
# Index
|
|
||||||
- CORE
|
|
||||||
- Docker Setup
|
|
||||||
- Precompiled container image
|
|
||||||
- Build container image from source
|
|
||||||
- Adding extra packages
|
|
||||||
|
|
||||||
- Useful commands
|
|
||||||
- License
|
|
||||||
|
|
||||||
# CORE
|
# CORE
|
||||||
|
|
||||||
CORE: Common Open Research Emulator
|
CORE: Common Open Research Emulator
|
||||||
|
@ -16,92 +6,40 @@ Copyright (c)2005-2022 the Boeing Company.
|
||||||
|
|
||||||
See the LICENSE file included in this distribution.
|
See the LICENSE file included in this distribution.
|
||||||
|
|
||||||
# Docker Setup
|
## About
|
||||||
|
|
||||||
Here you have 2 choices
|
The Common Open Research Emulator (CORE) is a tool for emulating
|
||||||
|
networks on one or more machines. You can connect these emulated
|
||||||
|
networks to live networks. CORE consists of a GUI for drawing
|
||||||
|
topologies of lightweight virtual machines, and Python modules for
|
||||||
|
scripting network emulation.
|
||||||
|
|
||||||
## Precompiled container image
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
The following should get you up and running on Ubuntu 18+ and CentOS 7+
|
||||||
|
from a clean install, it will prompt you for sudo password. This would
|
||||||
# Start container
|
install CORE into a python3 virtual environment and install
|
||||||
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped git.olympuslab.net/afonso/core-extra:latest
|
[OSPF MDR](https://github.com/USNavalResearchLaboratory/ospf-mdr) from source.
|
||||||
|
For more detailed installation see [here](https://coreemu.github.io/core/install.html).
|
||||||
```
|
|
||||||
## Build container image from source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone the repo
|
|
||||||
git clone https://gitea.olympuslab.net/afonso/core-extra.git
|
|
||||||
|
|
||||||
# cd into the directory
|
|
||||||
cd core-extra
|
|
||||||
|
|
||||||
# build the docker image
|
|
||||||
sudo docker build -t core-extra .
|
|
||||||
|
|
||||||
# start container
|
|
||||||
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped core-extra
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/coreemu/core.git
|
||||||
|
cd core
|
||||||
|
# install dependencies to run installation task
|
||||||
|
./setup.sh
|
||||||
|
# run the following or open a new terminal
|
||||||
|
source ~/.bashrc
|
||||||
|
# Ubuntu
|
||||||
|
inv install
|
||||||
|
# CentOS
|
||||||
|
inv install -p /usr
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding extra packages
|
## Documentation & Support
|
||||||
|
|
||||||
To add extra packages you must modify the Dockerfile and then compile the docker image.
|
We are leveraging GitHub hosted documentation and Discord for persistent
|
||||||
If you install it after starting the container it will, by docker nature, be reverted on the next boot of the container.
|
chat rooms. This allows for more dynamic conversations and the
|
||||||
|
capability to respond faster. Feel free to join us at the link below.
|
||||||
|
|
||||||
# Useful commands
|
* [Documentation](https://coreemu.github.io/core/)
|
||||||
|
* [Discord Channel](https://discord.gg/AKd7kmP)
|
||||||
I have the following functions on my fish shell
|
|
||||||
to help me better use core
|
|
||||||
|
|
||||||
THIS ONLY WORKS ON FISH, MODIFY FOR BASH OR ZSH
|
|
||||||
|
|
||||||
```fish
|
|
||||||
|
|
||||||
# RUN CORE GUI
|
|
||||||
function core
|
|
||||||
xhost +local:root
|
|
||||||
sudo docker exec -it core core-gui
|
|
||||||
end
|
|
||||||
|
|
||||||
# RUN BASH INSIDE THE CONTAINER
|
|
||||||
function core-bash
|
|
||||||
sudo docker exec -it core /bin/bash
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# LAUNCH NODE BASH ON THE HOST MACHINE
|
|
||||||
function launch-term --argument nodename
|
|
||||||
sudo docker exec -it core xterm -bg black -fg white -fa 'DejaVu Sans Mono' -fs 16 -e vcmd -c /tmp/pycore.1/$nodename -- /bin/bash
|
|
||||||
end
|
|
||||||
|
|
||||||
#TO RUN ANY OTHER COMMAND
|
|
||||||
sudo docker exec -it core COMAND_GOES_HERE
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## LICENSE
|
|
||||||
|
|
||||||
Copyright (c) 2005-2018, the Boeing Company.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
||||||
THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Process this file with autoconf to produce a configure script.
|
# Process this file with autoconf to produce a configure script.
|
||||||
|
|
||||||
# this defines the CORE version number, must be static for AC_INIT
|
# this defines the CORE version number, must be static for AC_INIT
|
||||||
AC_INIT(core, 9.0.3)
|
AC_INIT(core, 9.0.0)
|
||||||
|
|
||||||
# autoconf and automake initialization
|
# autoconf and automake initialization
|
||||||
AC_CONFIG_SRCDIR([netns/version.h.in])
|
AC_CONFIG_SRCDIR([netns/version.h.in])
|
||||||
|
@ -83,7 +83,7 @@ if test "x$enable_daemon" = "xyes"; then
|
||||||
want_python=yes
|
want_python=yes
|
||||||
want_linux_netns=yes
|
want_linux_netns=yes
|
||||||
|
|
||||||
AM_PATH_PYTHON(3.9)
|
AM_PATH_PYTHON(3.6)
|
||||||
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])])
|
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])])
|
||||||
|
|
||||||
AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH)
|
AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH)
|
||||||
|
|
|
@ -4,11 +4,10 @@ gRpc client for interfacing with CORE.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from collections.abc import Callable, Generator, Iterable
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import Any, Optional
|
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -161,12 +160,12 @@ def throughput_listener(
|
||||||
stream: Any, handler: Callable[[wrappers.ThroughputsEvent], None]
|
stream: Any, handler: Callable[[wrappers.ThroughputsEvent], None]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Listen for throughput events and provide them to the handler.
|
Listen for throughput events and provide them to the handler.
|
||||||
|
|
||||||
:param stream: grpc stream that will provide events
|
:param stream: grpc stream that will provide events
|
||||||
:param handler: function that handles an event
|
:param handler: function that handles an event
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
for event_proto in stream:
|
for event_proto in stream:
|
||||||
event = wrappers.ThroughputsEvent.from_proto(event_proto)
|
event = wrappers.ThroughputsEvent.from_proto(event_proto)
|
||||||
|
@ -236,7 +235,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def start_session(
|
def start_session(
|
||||||
self, session: wrappers.Session, definition: bool = False
|
self, session: wrappers.Session, definition: bool = False
|
||||||
) -> tuple[bool, list[str]]:
|
) -> Tuple[bool, List[str]]:
|
||||||
"""
|
"""
|
||||||
Start a session.
|
Start a session.
|
||||||
|
|
||||||
|
@ -286,7 +285,7 @@ class CoreGrpcClient:
|
||||||
response = self.stub.DeleteSession(request)
|
response = self.stub.DeleteSession(request)
|
||||||
return response.result
|
return response.result
|
||||||
|
|
||||||
def get_sessions(self) -> list[wrappers.SessionSummary]:
|
def get_sessions(self) -> List[wrappers.SessionSummary]:
|
||||||
"""
|
"""
|
||||||
Retrieves all currently known sessions.
|
Retrieves all currently known sessions.
|
||||||
|
|
||||||
|
@ -355,7 +354,7 @@ class CoreGrpcClient:
|
||||||
self,
|
self,
|
||||||
session_id: int,
|
session_id: int,
|
||||||
handler: Callable[[wrappers.Event], None],
|
handler: Callable[[wrappers.Event], None],
|
||||||
events: list[wrappers.EventType] = None,
|
events: List[wrappers.EventType] = None,
|
||||||
) -> grpc.Future:
|
) -> grpc.Future:
|
||||||
"""
|
"""
|
||||||
Listen for session events.
|
Listen for session events.
|
||||||
|
@ -429,7 +428,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def get_node(
|
def get_node(
|
||||||
self, session_id: int, node_id: int
|
self, session_id: int, node_id: int
|
||||||
) -> tuple[wrappers.Node, list[wrappers.Interface], list[wrappers.Link]]:
|
) -> Tuple[wrappers.Node, List[wrappers.Interface], List[wrappers.Link]]:
|
||||||
"""
|
"""
|
||||||
Get node details.
|
Get node details.
|
||||||
|
|
||||||
|
@ -537,7 +536,7 @@ class CoreGrpcClient:
|
||||||
command: str,
|
command: str,
|
||||||
wait: bool = True,
|
wait: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
) -> tuple[int, str]:
|
) -> Tuple[int, str]:
|
||||||
"""
|
"""
|
||||||
Send command to a node and get the output.
|
Send command to a node and get the output.
|
||||||
|
|
||||||
|
@ -576,7 +575,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def add_link(
|
def add_link(
|
||||||
self, session_id: int, link: wrappers.Link, source: str = None
|
self, session_id: int, link: wrappers.Link, source: str = None
|
||||||
) -> tuple[bool, wrappers.Interface, wrappers.Interface]:
|
) -> Tuple[bool, wrappers.Interface, wrappers.Interface]:
|
||||||
"""
|
"""
|
||||||
Add a link between nodes.
|
Add a link between nodes.
|
||||||
|
|
||||||
|
@ -647,7 +646,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def get_mobility_config(
|
def get_mobility_config(
|
||||||
self, session_id: int, node_id: int
|
self, session_id: int, node_id: int
|
||||||
) -> dict[str, wrappers.ConfigOption]:
|
) -> Dict[str, wrappers.ConfigOption]:
|
||||||
"""
|
"""
|
||||||
Get mobility configuration for a node.
|
Get mobility configuration for a node.
|
||||||
|
|
||||||
|
@ -661,7 +660,7 @@ class CoreGrpcClient:
|
||||||
return wrappers.ConfigOption.from_dict(response.config)
|
return wrappers.ConfigOption.from_dict(response.config)
|
||||||
|
|
||||||
def set_mobility_config(
|
def set_mobility_config(
|
||||||
self, session_id: int, node_id: int, config: dict[str, str]
|
self, session_id: int, node_id: int, config: Dict[str, str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Set mobility configuration for a node.
|
Set mobility configuration for a node.
|
||||||
|
@ -707,7 +706,7 @@ class CoreGrpcClient:
|
||||||
response = self.stub.GetConfig(request)
|
response = self.stub.GetConfig(request)
|
||||||
return wrappers.CoreConfig.from_proto(response)
|
return wrappers.CoreConfig.from_proto(response)
|
||||||
|
|
||||||
def get_service_defaults(self, session_id: int) -> list[wrappers.ServiceDefault]:
|
def get_service_defaults(self, session_id: int) -> List[wrappers.ServiceDefault]:
|
||||||
"""
|
"""
|
||||||
Get default services for different default node models.
|
Get default services for different default node models.
|
||||||
|
|
||||||
|
@ -724,7 +723,7 @@ class CoreGrpcClient:
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
def set_service_defaults(
|
def set_service_defaults(
|
||||||
self, session_id: int, service_defaults: dict[str, list[str]]
|
self, session_id: int, service_defaults: Dict[str, List[str]]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Set default services for node models.
|
Set default services for node models.
|
||||||
|
@ -830,7 +829,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def get_wlan_config(
|
def get_wlan_config(
|
||||||
self, session_id: int, node_id: int
|
self, session_id: int, node_id: int
|
||||||
) -> dict[str, wrappers.ConfigOption]:
|
) -> Dict[str, wrappers.ConfigOption]:
|
||||||
"""
|
"""
|
||||||
Get wlan configuration for a node.
|
Get wlan configuration for a node.
|
||||||
|
|
||||||
|
@ -844,7 +843,7 @@ class CoreGrpcClient:
|
||||||
return wrappers.ConfigOption.from_dict(response.config)
|
return wrappers.ConfigOption.from_dict(response.config)
|
||||||
|
|
||||||
def set_wlan_config(
|
def set_wlan_config(
|
||||||
self, session_id: int, node_id: int, config: dict[str, str]
|
self, session_id: int, node_id: int, config: Dict[str, str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Set wlan configuration for a node.
|
Set wlan configuration for a node.
|
||||||
|
@ -862,7 +861,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def get_emane_model_config(
|
def get_emane_model_config(
|
||||||
self, session_id: int, node_id: int, model: str, iface_id: int = -1
|
self, session_id: int, node_id: int, model: str, iface_id: int = -1
|
||||||
) -> dict[str, wrappers.ConfigOption]:
|
) -> Dict[str, wrappers.ConfigOption]:
|
||||||
"""
|
"""
|
||||||
Get emane model configuration for a node or a node's interface.
|
Get emane model configuration for a node or a node's interface.
|
||||||
|
|
||||||
|
@ -910,7 +909,7 @@ class CoreGrpcClient:
|
||||||
with open(file_path, "w") as xml_file:
|
with open(file_path, "w") as xml_file:
|
||||||
xml_file.write(response.data)
|
xml_file.write(response.data)
|
||||||
|
|
||||||
def open_xml(self, file_path: Path, start: bool = False) -> tuple[bool, int]:
|
def open_xml(self, file_path: Path, start: bool = False) -> Tuple[bool, int]:
|
||||||
"""
|
"""
|
||||||
Load a local scenario XML file to open as a new session.
|
Load a local scenario XML file to open as a new session.
|
||||||
|
|
||||||
|
@ -941,7 +940,7 @@ class CoreGrpcClient:
|
||||||
response = self.stub.EmaneLink(request)
|
response = self.stub.EmaneLink(request)
|
||||||
return response.result
|
return response.result
|
||||||
|
|
||||||
def get_ifaces(self) -> list[str]:
|
def get_ifaces(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieves a list of interfaces available on the host machine that are not
|
Retrieves a list of interfaces available on the host machine that are not
|
||||||
a part of a CORE session.
|
a part of a CORE session.
|
||||||
|
@ -952,26 +951,20 @@ class CoreGrpcClient:
|
||||||
response = self.stub.GetInterfaces(request)
|
response = self.stub.GetInterfaces(request)
|
||||||
return list(response.ifaces)
|
return list(response.ifaces)
|
||||||
|
|
||||||
def get_config_service_defaults(
|
def get_config_service_defaults(self, name: str) -> wrappers.ConfigServiceDefaults:
|
||||||
self, session_id: int, node_id: int, name: str
|
|
||||||
) -> wrappers.ConfigServiceDefaults:
|
|
||||||
"""
|
"""
|
||||||
Retrieves config service default values.
|
Retrieves config service default values.
|
||||||
|
|
||||||
:param session_id: session id to get node from
|
|
||||||
:param node_id: node id to get service data from
|
|
||||||
:param name: name of service to get defaults for
|
:param name: name of service to get defaults for
|
||||||
:return: config service defaults
|
:return: config service defaults
|
||||||
"""
|
"""
|
||||||
request = GetConfigServiceDefaultsRequest(
|
request = GetConfigServiceDefaultsRequest(name=name)
|
||||||
name=name, session_id=session_id, node_id=node_id
|
|
||||||
)
|
|
||||||
response = self.stub.GetConfigServiceDefaults(request)
|
response = self.stub.GetConfigServiceDefaults(request)
|
||||||
return wrappers.ConfigServiceDefaults.from_proto(response)
|
return wrappers.ConfigServiceDefaults.from_proto(response)
|
||||||
|
|
||||||
def get_node_config_service(
|
def get_node_config_service(
|
||||||
self, session_id: int, node_id: int, name: str
|
self, session_id: int, node_id: int, name: str
|
||||||
) -> dict[str, str]:
|
) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Retrieves information for a specific config service on a node.
|
Retrieves information for a specific config service on a node.
|
||||||
|
|
||||||
|
@ -989,7 +982,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def get_config_service_rendered(
|
def get_config_service_rendered(
|
||||||
self, session_id: int, node_id: int, name: str
|
self, session_id: int, node_id: int, name: str
|
||||||
) -> dict[str, str]:
|
) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Retrieve the rendered config service files for a node.
|
Retrieve the rendered config service files for a node.
|
||||||
|
|
||||||
|
@ -1136,7 +1129,7 @@ class CoreGrpcClient:
|
||||||
|
|
||||||
def get_wireless_config(
|
def get_wireless_config(
|
||||||
self, session_id: int, node_id: int
|
self, session_id: int, node_id: int
|
||||||
) -> dict[str, wrappers.ConfigOption]:
|
) -> Dict[str, wrappers.ConfigOption]:
|
||||||
request = GetWirelessConfigRequest(session_id=session_id, node_id=node_id)
|
request = GetWirelessConfigRequest(session_id=session_id, node_id=node_id)
|
||||||
response = self.stub.GetWirelessConfig(request)
|
response = self.stub.GetWirelessConfig(request)
|
||||||
return wrappers.ConfigOption.from_dict(response.config)
|
return wrappers.ConfigOption.from_dict(response.config)
|
||||||
|
@ -1163,7 +1156,7 @@ class CoreGrpcClient:
|
||||||
self.channel = None
|
self.channel = None
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def context_connect(self) -> Generator[None, None, None]:
|
def context_connect(self) -> Generator:
|
||||||
"""
|
"""
|
||||||
Makes a context manager based connection to the server, will close after
|
Makes a context manager based connection to the server, will close after
|
||||||
context ends.
|
context ends.
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Iterable
|
|
||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
from typing import Optional
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
from core.api.grpc import core_pb2, grpcutils
|
from core.api.grpc import core_pb2
|
||||||
from core.api.grpc.grpcutils import convert_link_data
|
from core.api.grpc.grpcutils import convert_link_data
|
||||||
from core.emulator.data import (
|
from core.emulator.data import (
|
||||||
ConfigData,
|
ConfigData,
|
||||||
|
@ -18,18 +17,28 @@ from core.emulator.session import Session
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def handle_node_event(session: Session, node_data: NodeData) -> core_pb2.Event:
|
def handle_node_event(node_data: NodeData) -> core_pb2.Event:
|
||||||
"""
|
"""
|
||||||
Handle node event when there is a node event
|
Handle node event when there is a node event
|
||||||
|
|
||||||
:param session: session node is from
|
|
||||||
:param node_data: node data
|
:param node_data: node data
|
||||||
:return: node event that contains node id, name, model, position, and services
|
:return: node event that contains node id, name, model, position, and services
|
||||||
"""
|
"""
|
||||||
node = node_data.node
|
node = node_data.node
|
||||||
emane_configs = grpcutils.get_emane_model_configs_dict(session)
|
x, y, _ = node.position.get()
|
||||||
node_emane_configs = emane_configs.get(node.id, [])
|
position = core_pb2.Position(x=x, y=y)
|
||||||
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
|
lon, lat, alt = node.position.get_geo()
|
||||||
|
geo = core_pb2.Geo(lon=lon, lat=lat, alt=alt)
|
||||||
|
services = [x.name for x in node.services]
|
||||||
|
node_proto = core_pb2.Node(
|
||||||
|
id=node.id,
|
||||||
|
name=node.name,
|
||||||
|
model=node.model,
|
||||||
|
icon=node.icon,
|
||||||
|
position=position,
|
||||||
|
geo=geo,
|
||||||
|
services=services,
|
||||||
|
)
|
||||||
message_type = node_data.message_type.value
|
message_type = node_data.message_type.value
|
||||||
node_event = core_pb2.NodeEvent(message_type=message_type, node=node_proto)
|
node_event = core_pb2.NodeEvent(message_type=message_type, node=node_proto)
|
||||||
return core_pb2.Event(node_event=node_event, source=node_data.source)
|
return core_pb2.Event(node_event=node_event, source=node_data.source)
|
||||||
|
@ -180,7 +189,7 @@ class EventStreamer:
|
||||||
try:
|
try:
|
||||||
data = self.queue.get(timeout=1)
|
data = self.queue.get(timeout=1)
|
||||||
if isinstance(data, NodeData):
|
if isinstance(data, NodeData):
|
||||||
event = handle_node_event(self.session, data)
|
event = handle_node_event(data)
|
||||||
elif isinstance(data, LinkData):
|
elif isinstance(data, LinkData):
|
||||||
event = handle_link_event(data)
|
event = handle_link_event(data)
|
||||||
elif isinstance(data, EventData):
|
elif isinstance(data, EventData):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from grpc import ServicerContext
|
from grpc import ServicerContext
|
||||||
|
@ -34,9 +34,8 @@ from core.nodes.base import (
|
||||||
)
|
)
|
||||||
from core.nodes.docker import DockerNode, DockerOptions
|
from core.nodes.docker import DockerNode, DockerOptions
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
from core.nodes.lxd import LxcNode, LxcOptions
|
from core.nodes.lxd import LxcNode
|
||||||
from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode
|
from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode
|
||||||
from core.nodes.podman import PodmanNode, PodmanOptions
|
|
||||||
from core.nodes.wireless import WirelessNode
|
from core.nodes.wireless import WirelessNode
|
||||||
from core.services.coreservices import CoreService
|
from core.services.coreservices import CoreService
|
||||||
|
|
||||||
|
@ -64,8 +63,8 @@ class CpuUsage:
|
||||||
|
|
||||||
|
|
||||||
def add_node_data(
|
def add_node_data(
|
||||||
_class: type[NodeBase], node_proto: core_pb2.Node
|
_class: Type[NodeBase], node_proto: core_pb2.Node
|
||||||
) -> tuple[Position, NodeOptions]:
|
) -> Tuple[Position, NodeOptions]:
|
||||||
"""
|
"""
|
||||||
Convert node protobuf message to data for creating a node.
|
Convert node protobuf message to data for creating a node.
|
||||||
|
|
||||||
|
@ -82,7 +81,7 @@ def add_node_data(
|
||||||
options.config_services = node_proto.config_services
|
options.config_services = node_proto.config_services
|
||||||
if isinstance(options, EmaneOptions):
|
if isinstance(options, EmaneOptions):
|
||||||
options.emane_model = node_proto.emane
|
options.emane_model = node_proto.emane
|
||||||
if isinstance(options, (DockerOptions, LxcOptions, PodmanOptions)):
|
if isinstance(options, DockerOptions):
|
||||||
options.image = node_proto.image
|
options.image = node_proto.image
|
||||||
position = Position()
|
position = Position()
|
||||||
position.set(node_proto.position.x, node_proto.position.y)
|
position.set(node_proto.position.x, node_proto.position.y)
|
||||||
|
@ -118,8 +117,8 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
|
||||||
|
|
||||||
|
|
||||||
def add_link_data(
|
def add_link_data(
|
||||||
link_proto: core_pb2.Link,
|
link_proto: core_pb2.Link
|
||||||
) -> tuple[InterfaceData, InterfaceData, LinkOptions]:
|
) -> Tuple[InterfaceData, InterfaceData, LinkOptions]:
|
||||||
"""
|
"""
|
||||||
Convert link proto to link interfaces and options data.
|
Convert link proto to link interfaces and options data.
|
||||||
|
|
||||||
|
@ -146,8 +145,8 @@ def add_link_data(
|
||||||
|
|
||||||
|
|
||||||
def create_nodes(
|
def create_nodes(
|
||||||
session: Session, node_protos: list[core_pb2.Node]
|
session: Session, node_protos: List[core_pb2.Node]
|
||||||
) -> tuple[list[NodeBase], list[Exception]]:
|
) -> Tuple[List[NodeBase], List[Exception]]:
|
||||||
"""
|
"""
|
||||||
Create nodes using a thread pool and wait for completion.
|
Create nodes using a thread pool and wait for completion.
|
||||||
|
|
||||||
|
@ -177,8 +176,8 @@ def create_nodes(
|
||||||
|
|
||||||
|
|
||||||
def create_links(
|
def create_links(
|
||||||
session: Session, link_protos: list[core_pb2.Link]
|
session: Session, link_protos: List[core_pb2.Link]
|
||||||
) -> tuple[list[NodeBase], list[Exception]]:
|
) -> Tuple[List[NodeBase], List[Exception]]:
|
||||||
"""
|
"""
|
||||||
Create links using a thread pool and wait for completion.
|
Create links using a thread pool and wait for completion.
|
||||||
|
|
||||||
|
@ -201,8 +200,8 @@ def create_links(
|
||||||
|
|
||||||
|
|
||||||
def edit_links(
|
def edit_links(
|
||||||
session: Session, link_protos: list[core_pb2.Link]
|
session: Session, link_protos: List[core_pb2.Link]
|
||||||
) -> tuple[list[None], list[Exception]]:
|
) -> Tuple[List[None], List[Exception]]:
|
||||||
"""
|
"""
|
||||||
Edit links using a thread pool and wait for completion.
|
Edit links using a thread pool and wait for completion.
|
||||||
|
|
||||||
|
@ -236,7 +235,7 @@ def convert_value(value: Any) -> str:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def convert_session_options(session: Session) -> dict[str, common_pb2.ConfigOption]:
|
def convert_session_options(session: Session) -> Dict[str, common_pb2.ConfigOption]:
|
||||||
config_options = {}
|
config_options = {}
|
||||||
for option in session.options.options:
|
for option in session.options.options:
|
||||||
value = session.options.get(option.id)
|
value = session.options.get(option.id)
|
||||||
|
@ -253,9 +252,9 @@ def convert_session_options(session: Session) -> dict[str, common_pb2.ConfigOpti
|
||||||
|
|
||||||
|
|
||||||
def get_config_options(
|
def get_config_options(
|
||||||
config: dict[str, str],
|
config: Dict[str, str],
|
||||||
configurable_options: Union[ConfigurableOptions, type[ConfigurableOptions]],
|
configurable_options: Union[ConfigurableOptions, Type[ConfigurableOptions]],
|
||||||
) -> dict[str, common_pb2.ConfigOption]:
|
) -> Dict[str, common_pb2.ConfigOption]:
|
||||||
"""
|
"""
|
||||||
Retrieve configuration options in a form that is used by the grpc server.
|
Retrieve configuration options in a form that is used by the grpc server.
|
||||||
|
|
||||||
|
@ -284,7 +283,7 @@ def get_config_options(
|
||||||
|
|
||||||
|
|
||||||
def get_node_proto(
|
def get_node_proto(
|
||||||
session: Session, node: NodeBase, emane_configs: list[NodeEmaneConfig]
|
session: Session, node: NodeBase, emane_configs: List[NodeEmaneConfig]
|
||||||
) -> core_pb2.Node:
|
) -> core_pb2.Node:
|
||||||
"""
|
"""
|
||||||
Convert CORE node to protobuf representation.
|
Convert CORE node to protobuf representation.
|
||||||
|
@ -314,7 +313,7 @@ def get_node_proto(
|
||||||
if isinstance(node, EmaneNet):
|
if isinstance(node, EmaneNet):
|
||||||
emane_model = node.wireless_model.name
|
emane_model = node.wireless_model.name
|
||||||
image = None
|
image = None
|
||||||
if isinstance(node, (DockerNode, LxcNode, PodmanNode)):
|
if isinstance(node, (DockerNode, LxcNode)):
|
||||||
image = node.image
|
image = node.image
|
||||||
# check for wlan config
|
# check for wlan config
|
||||||
wlan_config = session.mobility.get_configs(
|
wlan_config = session.mobility.get_configs(
|
||||||
|
@ -391,7 +390,7 @@ def get_node_proto(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_links(session: Session, node: NodeBase) -> list[core_pb2.Link]:
|
def get_links(session: Session, node: NodeBase) -> List[core_pb2.Link]:
|
||||||
"""
|
"""
|
||||||
Retrieve a list of links for grpc to use.
|
Retrieve a list of links for grpc to use.
|
||||||
|
|
||||||
|
@ -436,7 +435,7 @@ def convert_iface(iface: CoreInterface) -> core_pb2.Interface:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def convert_core_link(core_link: CoreLink) -> list[core_pb2.Link]:
|
def convert_core_link(core_link: CoreLink) -> List[core_pb2.Link]:
|
||||||
"""
|
"""
|
||||||
Convert core link to protobuf data.
|
Convert core link to protobuf data.
|
||||||
|
|
||||||
|
@ -582,33 +581,25 @@ def convert_link(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_proc_net_dev(lines: list[str]) -> dict[str, dict[str, float]]:
|
def get_net_stats() -> Dict[str, Dict]:
|
||||||
"""
|
|
||||||
Parse lines of output from /proc/net/dev.
|
|
||||||
|
|
||||||
:param lines: lines of /proc/net/dev
|
|
||||||
:return: parsed device to tx/rx values
|
|
||||||
"""
|
|
||||||
stats = {}
|
|
||||||
for line in lines[2:]:
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
line = line.split()
|
|
||||||
line[0] = line[0].strip(":")
|
|
||||||
stats[line[0]] = {"rx": float(line[1]), "tx": float(line[9])}
|
|
||||||
return stats
|
|
||||||
|
|
||||||
|
|
||||||
def get_net_stats() -> dict[str, dict[str, float]]:
|
|
||||||
"""
|
"""
|
||||||
Retrieve status about the current interfaces in the system
|
Retrieve status about the current interfaces in the system
|
||||||
|
|
||||||
:return: send and receive status of the interfaces in the system
|
:return: send and receive status of the interfaces in the system
|
||||||
"""
|
"""
|
||||||
with open("/proc/net/dev", "r") as f:
|
with open("/proc/net/dev", "r") as f:
|
||||||
lines = f.readlines()[2:]
|
data = f.readlines()[2:]
|
||||||
return parse_proc_net_dev(lines)
|
|
||||||
|
stats = {}
|
||||||
|
for line in data:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
line = line.split()
|
||||||
|
line[0] = line[0].strip(":")
|
||||||
|
stats[line[0]] = {"rx": float(line[1]), "tx": float(line[9])}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
|
||||||
def session_location(session: Session, location: core_pb2.SessionLocation) -> None:
|
def session_location(session: Session, location: core_pb2.SessionLocation) -> None:
|
||||||
|
@ -729,7 +720,7 @@ def get_nem_id(
|
||||||
return nem_id
|
return nem_id
|
||||||
|
|
||||||
|
|
||||||
def get_emane_model_configs_dict(session: Session) -> dict[int, list[NodeEmaneConfig]]:
|
def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneConfig]]:
|
||||||
"""
|
"""
|
||||||
Get emane model configuration protobuf data.
|
Get emane model configuration protobuf data.
|
||||||
|
|
||||||
|
@ -752,7 +743,7 @@ def get_emane_model_configs_dict(session: Session) -> dict[int, list[NodeEmaneCo
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
|
|
||||||
def get_hooks(session: Session) -> list[core_pb2.Hook]:
|
def get_hooks(session: Session) -> List[core_pb2.Hook]:
|
||||||
"""
|
"""
|
||||||
Retrieve hook protobuf data for a session.
|
Retrieve hook protobuf data for a session.
|
||||||
|
|
||||||
|
@ -768,7 +759,7 @@ def get_hooks(session: Session) -> list[core_pb2.Hook]:
|
||||||
return hooks
|
return hooks
|
||||||
|
|
||||||
|
|
||||||
def get_default_services(session: Session) -> list[ServiceDefaults]:
|
def get_default_services(session: Session) -> List[ServiceDefaults]:
|
||||||
"""
|
"""
|
||||||
Retrieve the default service sets for a given session.
|
Retrieve the default service sets for a given session.
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from collections.abc import Iterable
|
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from re import Pattern
|
from typing import Iterable, Optional, Pattern, Type
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from grpc import ServicerContext
|
from grpc import ServicerContext
|
||||||
|
@ -107,7 +104,7 @@ from core.services.coreservices import ServiceManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24
|
_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24
|
||||||
_INTERFACE_REGEX: Pattern[str] = re.compile(r"beth(?P<node>[0-9a-fA-F]+)")
|
_INTERFACE_REGEX: Pattern = re.compile(r"veth(?P<node>[0-9a-fA-F]+)")
|
||||||
_MAX_WORKERS = 1000
|
_MAX_WORKERS = 1000
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,20 +120,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
self.coreemu: CoreEmu = coreemu
|
self.coreemu: CoreEmu = coreemu
|
||||||
self.running: bool = True
|
self.running: bool = True
|
||||||
self.server: Optional[grpc.Server] = None
|
self.server: Optional[grpc.Server] = None
|
||||||
# catch signals
|
atexit.register(self._exit_handler)
|
||||||
signal.signal(signal.SIGHUP, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGINT, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGUSR1, self._signal_handler)
|
|
||||||
signal.signal(signal.SIGUSR2, self._signal_handler)
|
|
||||||
|
|
||||||
def _signal_handler(self, signal_number: int, _) -> None:
|
def _exit_handler(self) -> None:
|
||||||
logger.info("caught signal: %s", signal_number)
|
logger.debug("catching exit, stop running")
|
||||||
self.coreemu.shutdown()
|
|
||||||
self.running = False
|
self.running = False
|
||||||
if self.server:
|
|
||||||
self.server.stop(None)
|
|
||||||
sys.exit(signal_number)
|
|
||||||
|
|
||||||
def _is_running(self, context) -> bool:
|
def _is_running(self, context) -> bool:
|
||||||
return self.running and context.is_active()
|
return self.running and context.is_active()
|
||||||
|
@ -173,7 +161,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def get_node(
|
def get_node(
|
||||||
self, session: Session, node_id: int, context: ServicerContext, _class: type[NT]
|
self, session: Session, node_id: int, context: ServicerContext, _class: Type[NT]
|
||||||
) -> NT:
|
) -> NT:
|
||||||
"""
|
"""
|
||||||
Retrieve node given session and node id
|
Retrieve node given session and node id
|
||||||
|
@ -212,7 +200,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
def validate_service(
|
def validate_service(
|
||||||
self, name: str, context: ServicerContext
|
self, name: str, context: ServicerContext
|
||||||
) -> type[ConfigService]:
|
) -> Type[ConfigService]:
|
||||||
"""
|
"""
|
||||||
Validates a configuration service is a valid known service.
|
Validates a configuration service is a valid known service.
|
||||||
|
|
||||||
|
@ -284,8 +272,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
# session options
|
# session options
|
||||||
for option in request.session.options.values():
|
for option in request.session.options.values():
|
||||||
if option.value:
|
session.options.set(option.name, option.value)
|
||||||
session.options.set(option.name, option.value)
|
|
||||||
session.metadata = dict(request.session.metadata)
|
session.metadata = dict(request.session.metadata)
|
||||||
|
|
||||||
# add servers
|
# add servers
|
||||||
|
@ -495,6 +482,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
while self._is_running(context):
|
while self._is_running(context):
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
stats = get_net_stats()
|
stats = get_net_stats()
|
||||||
|
|
||||||
# calculate average
|
# calculate average
|
||||||
if last_check is not None:
|
if last_check is not None:
|
||||||
interval = now - last_check
|
interval = now - last_check
|
||||||
|
@ -511,7 +499,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
(current_rxtx["tx"] - previous_rxtx["tx"]) * 8.0 / interval
|
(current_rxtx["tx"] - previous_rxtx["tx"]) * 8.0 / interval
|
||||||
)
|
)
|
||||||
throughput = rx_kbps + tx_kbps
|
throughput = rx_kbps + tx_kbps
|
||||||
if key.startswith("beth"):
|
if key.startswith("veth"):
|
||||||
key = key.split(".")
|
key = key.split(".")
|
||||||
node_id = _INTERFACE_REGEX.search(key[0]).group("node")
|
node_id = _INTERFACE_REGEX.search(key[0]).group("node")
|
||||||
node_id = int(node_id, base=16)
|
node_id = int(node_id, base=16)
|
||||||
|
@ -537,6 +525,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
bridge_throughput.throughput = throughput
|
bridge_throughput.throughput = throughput
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
yield throughputs_event
|
yield throughputs_event
|
||||||
|
|
||||||
last_check = now
|
last_check = now
|
||||||
|
@ -1106,7 +1095,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
node_id = request.wlan_config.node_id
|
node_id = request.wlan_config.node_id
|
||||||
config = request.wlan_config.config
|
config = request.wlan_config.config
|
||||||
session.mobility.set_model_config(node_id, BasicRangeModel.name, config)
|
session.mobility.set_model_config(node_id, BasicRangeModel.name, config)
|
||||||
if session.is_running():
|
if session.state == EventTypes.RUNTIME_STATE:
|
||||||
node = self.get_node(session, node_id, context, WlanNode)
|
node = self.get_node(session, node_id, context, WlanNode)
|
||||||
node.updatemodel(config)
|
node.updatemodel(config)
|
||||||
return SetWlanConfigResponse(result=True)
|
return SetWlanConfigResponse(result=True)
|
||||||
|
@ -1179,7 +1168,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
logger.debug("open xml: %s", request)
|
logger.debug("open xml: %s", request)
|
||||||
session = self.coreemu.create_session()
|
session = self.coreemu.create_session()
|
||||||
temp = tempfile.NamedTemporaryFile(delete=False)
|
temp = tempfile.NamedTemporaryFile(delete=False)
|
||||||
temp.write(request.data.encode())
|
temp.write(request.data.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
temp_path = Path(temp.name)
|
temp_path = Path(temp.name)
|
||||||
file_path = Path(request.file)
|
file_path = Path(request.file)
|
||||||
|
@ -1285,10 +1274,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
:param context: grpc context
|
:param context: grpc context
|
||||||
:return: get config service defaults response
|
:return: get config service defaults response
|
||||||
"""
|
"""
|
||||||
session = self.get_session(request.session_id, context)
|
|
||||||
node = self.get_node(session, request.node_id, context, CoreNode)
|
|
||||||
service_class = self.validate_service(request.name, context)
|
service_class = self.validate_service(request.name, context)
|
||||||
service = service_class(node)
|
service = service_class(None)
|
||||||
templates = service.get_templates()
|
templates = service.get_templates()
|
||||||
config = {}
|
config = {}
|
||||||
for configuration in service.default_configs:
|
for configuration in service.default_configs:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from core.api.grpc import (
|
from core.api.grpc import (
|
||||||
common_pb2,
|
common_pb2,
|
||||||
|
@ -68,7 +68,6 @@ class NodeType(Enum):
|
||||||
DOCKER = 15
|
DOCKER = 15
|
||||||
LXC = 16
|
LXC = 16
|
||||||
WIRELESS = 17
|
WIRELESS = 17
|
||||||
PODMAN = 18
|
|
||||||
|
|
||||||
|
|
||||||
class LinkType(Enum):
|
class LinkType(Enum):
|
||||||
|
@ -115,13 +114,13 @@ class EventType:
|
||||||
class ConfigService:
|
class ConfigService:
|
||||||
group: str
|
group: str
|
||||||
name: str
|
name: str
|
||||||
executables: list[str]
|
executables: List[str]
|
||||||
dependencies: list[str]
|
dependencies: List[str]
|
||||||
directories: list[str]
|
directories: List[str]
|
||||||
files: list[str]
|
files: List[str]
|
||||||
startup: list[str]
|
startup: List[str]
|
||||||
validate: list[str]
|
validate: List[str]
|
||||||
shutdown: list[str]
|
shutdown: List[str]
|
||||||
validation_mode: ConfigServiceValidationMode
|
validation_mode: ConfigServiceValidationMode
|
||||||
validation_timer: int
|
validation_timer: int
|
||||||
validation_period: float
|
validation_period: float
|
||||||
|
@ -148,8 +147,8 @@ class ConfigService:
|
||||||
class ConfigServiceConfig:
|
class ConfigServiceConfig:
|
||||||
node_id: int
|
node_id: int
|
||||||
name: str
|
name: str
|
||||||
templates: dict[str, str]
|
templates: Dict[str, str]
|
||||||
config: dict[str, str]
|
config: Dict[str, str]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(
|
def from_proto(
|
||||||
|
@ -165,15 +164,15 @@ class ConfigServiceConfig:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ConfigServiceData:
|
class ConfigServiceData:
|
||||||
templates: dict[str, str] = field(default_factory=dict)
|
templates: Dict[str, str] = field(default_factory=dict)
|
||||||
config: dict[str, str] = field(default_factory=dict)
|
config: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ConfigServiceDefaults:
|
class ConfigServiceDefaults:
|
||||||
templates: dict[str, str]
|
templates: Dict[str, str]
|
||||||
config: dict[str, "ConfigOption"]
|
config: Dict[str, "ConfigOption"]
|
||||||
modes: dict[str, dict[str, str]]
|
modes: Dict[str, Dict[str, str]]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(
|
def from_proto(
|
||||||
|
@ -212,7 +211,7 @@ class Service:
|
||||||
@dataclass
|
@dataclass
|
||||||
class ServiceDefault:
|
class ServiceDefault:
|
||||||
model: str
|
model: str
|
||||||
services: list[str]
|
services: List[str]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: services_pb2.ServiceDefaults) -> "ServiceDefault":
|
def from_proto(cls, proto: services_pb2.ServiceDefaults) -> "ServiceDefault":
|
||||||
|
@ -221,15 +220,15 @@ class ServiceDefault:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NodeServiceData:
|
class NodeServiceData:
|
||||||
executables: list[str] = field(default_factory=list)
|
executables: List[str] = field(default_factory=list)
|
||||||
dependencies: list[str] = field(default_factory=list)
|
dependencies: List[str] = field(default_factory=list)
|
||||||
dirs: list[str] = field(default_factory=list)
|
dirs: List[str] = field(default_factory=list)
|
||||||
configs: list[str] = field(default_factory=list)
|
configs: List[str] = field(default_factory=list)
|
||||||
startup: list[str] = field(default_factory=list)
|
startup: List[str] = field(default_factory=list)
|
||||||
validate: list[str] = field(default_factory=list)
|
validate: List[str] = field(default_factory=list)
|
||||||
validation_mode: ServiceValidationMode = ServiceValidationMode.NON_BLOCKING
|
validation_mode: ServiceValidationMode = ServiceValidationMode.NON_BLOCKING
|
||||||
validation_timer: int = 5
|
validation_timer: int = 5
|
||||||
shutdown: list[str] = field(default_factory=list)
|
shutdown: List[str] = field(default_factory=list)
|
||||||
meta: str = None
|
meta: str = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -267,7 +266,7 @@ class NodeServiceConfig:
|
||||||
node_id: int
|
node_id: int
|
||||||
service: str
|
service: str
|
||||||
data: NodeServiceData
|
data: NodeServiceData
|
||||||
files: dict[str, str] = field(default_factory=dict)
|
files: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: services_pb2.NodeServiceConfig) -> "NodeServiceConfig":
|
def from_proto(cls, proto: services_pb2.NodeServiceConfig) -> "NodeServiceConfig":
|
||||||
|
@ -283,11 +282,11 @@ class NodeServiceConfig:
|
||||||
class ServiceConfig:
|
class ServiceConfig:
|
||||||
node_id: int
|
node_id: int
|
||||||
service: str
|
service: str
|
||||||
files: list[str] = None
|
files: List[str] = None
|
||||||
directories: list[str] = None
|
directories: List[str] = None
|
||||||
startup: list[str] = None
|
startup: List[str] = None
|
||||||
validate: list[str] = None
|
validate: List[str] = None
|
||||||
shutdown: list[str] = None
|
shutdown: List[str] = None
|
||||||
|
|
||||||
def to_proto(self) -> services_pb2.ServiceConfig:
|
def to_proto(self) -> services_pb2.ServiceConfig:
|
||||||
return services_pb2.ServiceConfig(
|
return services_pb2.ServiceConfig(
|
||||||
|
@ -340,8 +339,8 @@ class InterfaceThroughput:
|
||||||
@dataclass
|
@dataclass
|
||||||
class ThroughputsEvent:
|
class ThroughputsEvent:
|
||||||
session_id: int
|
session_id: int
|
||||||
bridge_throughputs: list[BridgeThroughput]
|
bridge_throughputs: List[BridgeThroughput]
|
||||||
iface_throughputs: list[InterfaceThroughput]
|
iface_throughputs: List[InterfaceThroughput]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: core_pb2.ThroughputsEvent) -> "ThroughputsEvent":
|
def from_proto(cls, proto: core_pb2.ThroughputsEvent) -> "ThroughputsEvent":
|
||||||
|
@ -429,19 +428,19 @@ class ConfigOption:
|
||||||
label: str = None
|
label: str = None
|
||||||
type: ConfigOptionType = None
|
type: ConfigOptionType = None
|
||||||
group: str = None
|
group: str = None
|
||||||
select: list[str] = None
|
select: List[str] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(
|
def from_dict(
|
||||||
cls, config: dict[str, common_pb2.ConfigOption]
|
cls, config: Dict[str, common_pb2.ConfigOption]
|
||||||
) -> dict[str, "ConfigOption"]:
|
) -> Dict[str, "ConfigOption"]:
|
||||||
d = {}
|
d = {}
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
d[key] = ConfigOption.from_proto(value)
|
d[key] = ConfigOption.from_proto(value)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_dict(cls, config: dict[str, "ConfigOption"]) -> dict[str, str]:
|
def to_dict(cls, config: Dict[str, "ConfigOption"]) -> Dict[str, str]:
|
||||||
return {k: v.value for k, v in config.items()}
|
return {k: v.value for k, v in config.items()}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -672,7 +671,7 @@ class EmaneModelConfig:
|
||||||
node_id: int
|
node_id: int
|
||||||
model: str
|
model: str
|
||||||
iface_id: int = -1
|
iface_id: int = -1
|
||||||
config: dict[str, ConfigOption] = None
|
config: Dict[str, ConfigOption] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: emane_pb2.GetEmaneModelConfig) -> "EmaneModelConfig":
|
def from_proto(cls, proto: emane_pb2.GetEmaneModelConfig) -> "EmaneModelConfig":
|
||||||
|
@ -726,8 +725,8 @@ class Node:
|
||||||
type: NodeType = NodeType.DEFAULT
|
type: NodeType = NodeType.DEFAULT
|
||||||
model: str = None
|
model: str = None
|
||||||
position: Position = Position(x=0, y=0)
|
position: Position = Position(x=0, y=0)
|
||||||
services: set[str] = field(default_factory=set)
|
services: Set[str] = field(default_factory=set)
|
||||||
config_services: set[str] = field(default_factory=set)
|
config_services: Set[str] = field(default_factory=set)
|
||||||
emane: str = None
|
emane: str = None
|
||||||
icon: str = None
|
icon: str = None
|
||||||
image: str = None
|
image: str = None
|
||||||
|
@ -738,19 +737,19 @@ class Node:
|
||||||
canvas: int = None
|
canvas: int = None
|
||||||
|
|
||||||
# configurations
|
# configurations
|
||||||
emane_model_configs: dict[
|
emane_model_configs: Dict[
|
||||||
tuple[str, Optional[int]], dict[str, ConfigOption]
|
Tuple[str, Optional[int]], Dict[str, ConfigOption]
|
||||||
] = field(default_factory=dict, repr=False)
|
] = field(default_factory=dict, repr=False)
|
||||||
wlan_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
wlan_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||||
wireless_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
wireless_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||||
mobility_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
mobility_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||||
service_configs: dict[str, NodeServiceData] = field(
|
service_configs: Dict[str, NodeServiceData] = field(
|
||||||
default_factory=dict, repr=False
|
default_factory=dict, repr=False
|
||||||
)
|
)
|
||||||
service_file_configs: dict[str, dict[str, str]] = field(
|
service_file_configs: Dict[str, Dict[str, str]] = field(
|
||||||
default_factory=dict, repr=False
|
default_factory=dict, repr=False
|
||||||
)
|
)
|
||||||
config_service_configs: dict[str, ConfigServiceData] = field(
|
config_service_configs: Dict[str, ConfigServiceData] = field(
|
||||||
default_factory=dict, repr=False
|
default_factory=dict, repr=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -850,18 +849,18 @@ class Node:
|
||||||
wireless_config={k: v.to_proto() for k, v in self.wireless_config.items()},
|
wireless_config={k: v.to_proto() for k, v in self.wireless_config.items()},
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_wlan(self, config: dict[str, str]) -> None:
|
def set_wlan(self, config: Dict[str, str]) -> None:
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
option = ConfigOption(name=key, value=value)
|
option = ConfigOption(name=key, value=value)
|
||||||
self.wlan_config[key] = option
|
self.wlan_config[key] = option
|
||||||
|
|
||||||
def set_mobility(self, config: dict[str, str]) -> None:
|
def set_mobility(self, config: Dict[str, str]) -> None:
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
option = ConfigOption(name=key, value=value)
|
option = ConfigOption(name=key, value=value)
|
||||||
self.mobility_config[key] = option
|
self.mobility_config[key] = option
|
||||||
|
|
||||||
def set_emane_model(
|
def set_emane_model(
|
||||||
self, model: str, config: dict[str, str], iface_id: int = None
|
self, model: str, config: Dict[str, str], iface_id: int = None
|
||||||
) -> None:
|
) -> None:
|
||||||
key = (model, iface_id)
|
key = (model, iface_id)
|
||||||
config_options = self.emane_model_configs.setdefault(key, {})
|
config_options = self.emane_model_configs.setdefault(key, {})
|
||||||
|
@ -874,23 +873,23 @@ class Node:
|
||||||
class Session:
|
class Session:
|
||||||
id: int = None
|
id: int = None
|
||||||
state: SessionState = SessionState.DEFINITION
|
state: SessionState = SessionState.DEFINITION
|
||||||
nodes: dict[int, Node] = field(default_factory=dict)
|
nodes: Dict[int, Node] = field(default_factory=dict)
|
||||||
links: list[Link] = field(default_factory=list)
|
links: List[Link] = field(default_factory=list)
|
||||||
dir: str = None
|
dir: str = None
|
||||||
user: str = None
|
user: str = None
|
||||||
default_services: dict[str, set[str]] = field(default_factory=dict)
|
default_services: Dict[str, Set[str]] = field(default_factory=dict)
|
||||||
location: SessionLocation = SessionLocation(
|
location: SessionLocation = SessionLocation(
|
||||||
x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0
|
x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0
|
||||||
)
|
)
|
||||||
hooks: dict[str, Hook] = field(default_factory=dict)
|
hooks: Dict[str, Hook] = field(default_factory=dict)
|
||||||
metadata: dict[str, str] = field(default_factory=dict)
|
metadata: Dict[str, str] = field(default_factory=dict)
|
||||||
file: Path = None
|
file: Path = None
|
||||||
options: dict[str, ConfigOption] = field(default_factory=dict)
|
options: Dict[str, ConfigOption] = field(default_factory=dict)
|
||||||
servers: list[Server] = field(default_factory=list)
|
servers: List[Server] = field(default_factory=list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: core_pb2.Session) -> "Session":
|
def from_proto(cls, proto: core_pb2.Session) -> "Session":
|
||||||
nodes: dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes}
|
nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes}
|
||||||
links = [Link.from_proto(x) for x in proto.links]
|
links = [Link.from_proto(x) for x in proto.links]
|
||||||
default_services = {x.model: set(x.services) for x in proto.default_services}
|
default_services = {x.model: set(x.services) for x in proto.default_services}
|
||||||
hooks = {x.file: Hook.from_proto(x) for x in proto.hooks}
|
hooks = {x.file: Hook.from_proto(x) for x in proto.hooks}
|
||||||
|
@ -988,7 +987,7 @@ class Session:
|
||||||
self.links.append(link)
|
self.links.append(link)
|
||||||
return link
|
return link
|
||||||
|
|
||||||
def set_options(self, config: dict[str, str]) -> None:
|
def set_options(self, config: Dict[str, str]) -> None:
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
option = ConfigOption(name=key, value=value)
|
option = ConfigOption(name=key, value=value)
|
||||||
self.options[key] = option
|
self.options[key] = option
|
||||||
|
@ -996,9 +995,9 @@ class Session:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CoreConfig:
|
class CoreConfig:
|
||||||
services: list[Service] = field(default_factory=list)
|
services: List[Service] = field(default_factory=list)
|
||||||
config_services: list[ConfigService] = field(default_factory=list)
|
config_services: List[ConfigService] = field(default_factory=list)
|
||||||
emane_models: list[str] = field(default_factory=list)
|
emane_models: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: core_pb2.GetConfigResponse) -> "CoreConfig":
|
def from_proto(cls, proto: core_pb2.GetConfigResponse) -> "CoreConfig":
|
||||||
|
@ -1089,7 +1088,7 @@ class ConfigEvent:
|
||||||
node_id: int
|
node_id: int
|
||||||
object: str
|
object: str
|
||||||
type: int
|
type: int
|
||||||
data_types: list[int]
|
data_types: List[int]
|
||||||
data_values: str
|
data_values: str
|
||||||
captions: str
|
captions: str
|
||||||
bitmap: str
|
bitmap: str
|
||||||
|
|
|
@ -5,7 +5,7 @@ Common support for configurable CORE objects.
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type, Union
|
||||||
|
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
|
@ -17,9 +17,9 @@ logger = logging.getLogger(__name__)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.location.mobility import WirelessModel
|
from core.location.mobility import WirelessModel
|
||||||
|
|
||||||
WirelessModelType = type[WirelessModel]
|
WirelessModelType = Type[WirelessModel]
|
||||||
|
|
||||||
_BOOL_OPTIONS: set[str] = {"0", "1"}
|
_BOOL_OPTIONS: Set[str] = {"0", "1"}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -43,7 +43,7 @@ class Configuration:
|
||||||
type: ConfigDataTypes
|
type: ConfigDataTypes
|
||||||
label: str = None
|
label: str = None
|
||||||
default: str = ""
|
default: str = ""
|
||||||
options: list[str] = field(default_factory=list)
|
options: List[str] = field(default_factory=list)
|
||||||
group: str = "Configuration"
|
group: str = "Configuration"
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
|
@ -118,10 +118,10 @@ class ConfigurableOptions:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
options: list[Configuration] = []
|
options: List[Configuration] = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls) -> list[Configuration]:
|
def configurations(cls) -> List[Configuration]:
|
||||||
"""
|
"""
|
||||||
Provides the configurations for this class.
|
Provides the configurations for this class.
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ class ConfigurableOptions:
|
||||||
return cls.options
|
return cls.options
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls) -> list[ConfigGroup]:
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
"""
|
"""
|
||||||
Defines how configurations are grouped.
|
Defines how configurations are grouped.
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ class ConfigurableOptions:
|
||||||
return [ConfigGroup("Options", 1, len(cls.configurations()))]
|
return [ConfigGroup("Options", 1, len(cls.configurations()))]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_values(cls) -> dict[str, str]:
|
def default_values(cls) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Provides an ordered mapping of configuration keys to default values.
|
Provides an ordered mapping of configuration keys to default values.
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ class ConfigurableManager:
|
||||||
"""
|
"""
|
||||||
self.node_configurations = {}
|
self.node_configurations = {}
|
||||||
|
|
||||||
def nodes(self) -> list[int]:
|
def nodes(self) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Retrieves the ids of all node configurations known by this manager.
|
Retrieves the ids of all node configurations known by this manager.
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ class ConfigurableManager:
|
||||||
|
|
||||||
def set_configs(
|
def set_configs(
|
||||||
self,
|
self,
|
||||||
config: dict[str, str],
|
config: Dict[str, str],
|
||||||
node_id: int = _default_node,
|
node_id: int = _default_node,
|
||||||
config_type: str = _default_type,
|
config_type: str = _default_type,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -250,7 +250,7 @@ class ConfigurableManager:
|
||||||
|
|
||||||
def get_configs(
|
def get_configs(
|
||||||
self, node_id: int = _default_node, config_type: str = _default_type
|
self, node_id: int = _default_node, config_type: str = _default_type
|
||||||
) -> Optional[dict[str, str]]:
|
) -> Optional[Dict[str, str]]:
|
||||||
"""
|
"""
|
||||||
Retrieve configurations for a node and configuration type.
|
Retrieve configurations for a node and configuration type.
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ class ConfigurableManager:
|
||||||
result = node_configs.get(config_type)
|
result = node_configs.get(config_type)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_all_configs(self, node_id: int = _default_node) -> dict[str, Any]:
|
def get_all_configs(self, node_id: int = _default_node) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Retrieve all current configuration types for a node.
|
Retrieve all current configuration types for a node.
|
||||||
|
|
||||||
|
@ -284,11 +284,11 @@ class ModelManager(ConfigurableManager):
|
||||||
Creates a ModelManager object.
|
Creates a ModelManager object.
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.models: dict[str, Any] = {}
|
self.models: Dict[str, Any] = {}
|
||||||
self.node_models: dict[int, str] = {}
|
self.node_models: Dict[int, str] = {}
|
||||||
|
|
||||||
def set_model_config(
|
def set_model_config(
|
||||||
self, node_id: int, model_name: str, config: dict[str, str] = None
|
self, node_id: int, model_name: str, config: Dict[str, str] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Set configuration data for a model.
|
Set configuration data for a model.
|
||||||
|
@ -317,7 +317,7 @@ class ModelManager(ConfigurableManager):
|
||||||
# set configuration
|
# set configuration
|
||||||
self.set_configs(model_config, node_id=node_id, config_type=model_name)
|
self.set_configs(model_config, node_id=node_id, config_type=model_name)
|
||||||
|
|
||||||
def get_model_config(self, node_id: int, model_name: str) -> dict[str, str]:
|
def get_model_config(self, node_id: int, model_name: str) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Retrieve configuration data for a model.
|
Retrieve configuration data for a model.
|
||||||
|
|
||||||
|
@ -342,7 +342,7 @@ class ModelManager(ConfigurableManager):
|
||||||
self,
|
self,
|
||||||
node: Union[WlanNode, EmaneNet],
|
node: Union[WlanNode, EmaneNet],
|
||||||
model_class: "WirelessModelType",
|
model_class: "WirelessModelType",
|
||||||
config: dict[str, str] = None,
|
config: Dict[str, str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Set model and model configuration for node.
|
Set model and model configuration for node.
|
||||||
|
@ -361,7 +361,7 @@ class ModelManager(ConfigurableManager):
|
||||||
|
|
||||||
def get_models(
|
def get_models(
|
||||||
self, node: Union[WlanNode, EmaneNet]
|
self, node: Union[WlanNode, EmaneNet]
|
||||||
) -> list[tuple[type, dict[str, str]]]:
|
) -> List[Tuple[Type, Dict[str, str]]]:
|
||||||
"""
|
"""
|
||||||
Return a list of model classes and values for a net if one has been
|
Return a list of model classes and values for a net if one has been
|
||||||
configured. This is invoked when exporting a session to XML.
|
configured. This is invoked when exporting a session to XML.
|
||||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from mako import exceptions
|
from mako import exceptions
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
|
@ -67,7 +67,7 @@ class ConfigService(abc.ABC):
|
||||||
validation_timer: int = 5
|
validation_timer: int = 5
|
||||||
|
|
||||||
# directories to shadow and copy files from
|
# directories to shadow and copy files from
|
||||||
shadow_directories: list[ShadowDir] = []
|
shadow_directories: List[ShadowDir] = []
|
||||||
|
|
||||||
def __init__(self, node: CoreNode) -> None:
|
def __init__(self, node: CoreNode) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -79,9 +79,9 @@ class ConfigService(abc.ABC):
|
||||||
class_file = inspect.getfile(self.__class__)
|
class_file = inspect.getfile(self.__class__)
|
||||||
templates_path = Path(class_file).parent.joinpath(TEMPLATES_DIR)
|
templates_path = Path(class_file).parent.joinpath(TEMPLATES_DIR)
|
||||||
self.templates: TemplateLookup = TemplateLookup(directories=templates_path)
|
self.templates: TemplateLookup = TemplateLookup(directories=templates_path)
|
||||||
self.config: dict[str, Configuration] = {}
|
self.config: Dict[str, Configuration] = {}
|
||||||
self.custom_templates: dict[str, str] = {}
|
self.custom_templates: Dict[str, str] = {}
|
||||||
self.custom_config: dict[str, str] = {}
|
self.custom_config: Dict[str, str] = {}
|
||||||
configs = self.default_configs[:]
|
configs = self.default_configs[:]
|
||||||
self._define_config(configs)
|
self._define_config(configs)
|
||||||
|
|
||||||
|
@ -108,47 +108,47 @@ class ConfigService(abc.ABC):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def directories(self) -> list[str]:
|
def directories(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def files(self) -> list[str]:
|
def files(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def default_configs(self) -> list[Configuration]:
|
def default_configs(self) -> List[Configuration]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def modes(self) -> dict[str, dict[str, str]]:
|
def modes(self) -> Dict[str, Dict[str, str]]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def executables(self) -> list[str]:
|
def executables(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def dependencies(self) -> list[str]:
|
def dependencies(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def startup(self) -> list[str]:
|
def startup(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def validate(self) -> list[str]:
|
def validate(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def shutdown(self) -> list[str]:
|
def shutdown(self) -> List[str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -276,7 +276,7 @@ class ConfigService(abc.ABC):
|
||||||
f"failure to create service directory: {directory}"
|
f"failure to create service directory: {directory}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Returns key/value data, used when rendering file templates.
|
Returns key/value data, used when rendering file templates.
|
||||||
|
|
||||||
|
@ -303,7 +303,7 @@ class ConfigService(abc.ABC):
|
||||||
"""
|
"""
|
||||||
raise CoreError(f"service({self.name}) unknown template({name})")
|
raise CoreError(f"service({self.name}) unknown template({name})")
|
||||||
|
|
||||||
def get_templates(self) -> dict[str, str]:
|
def get_templates(self) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Retrieves mapping of file names to templates for all cases, which
|
Retrieves mapping of file names to templates for all cases, which
|
||||||
includes custom templates, file templates, and text templates.
|
includes custom templates, file templates, and text templates.
|
||||||
|
@ -331,7 +331,7 @@ class ConfigService(abc.ABC):
|
||||||
templates[file] = template
|
templates[file] = template
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
def get_rendered_templates(self) -> dict[str, str]:
|
def get_rendered_templates(self) -> Dict[str, str]:
|
||||||
templates = {}
|
templates = {}
|
||||||
data = self.data()
|
data = self.data()
|
||||||
for file in sorted(self.files):
|
for file in sorted(self.files):
|
||||||
|
@ -339,7 +339,7 @@ class ConfigService(abc.ABC):
|
||||||
templates[file] = rendered
|
templates[file] = rendered
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
def _get_rendered_template(self, file: str, data: dict[str, Any]) -> str:
|
def _get_rendered_template(self, file: str, data: Dict[str, Any]) -> str:
|
||||||
file_path = Path(file)
|
file_path = Path(file)
|
||||||
template_path = get_template_path(file_path)
|
template_path = get_template_path(file_path)
|
||||||
if file in self.custom_templates:
|
if file in self.custom_templates:
|
||||||
|
@ -426,7 +426,7 @@ class ConfigService(abc.ABC):
|
||||||
f"node({self.node.name}) service({self.name}) failed to validate"
|
f"node({self.node.name}) service({self.name}) failed to validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _render(self, template: Template, data: dict[str, Any] = None) -> str:
|
def _render(self, template: Template, data: Dict[str, Any] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Renders template providing all associated data to template.
|
Renders template providing all associated data to template.
|
||||||
|
|
||||||
|
@ -440,7 +440,7 @@ class ConfigService(abc.ABC):
|
||||||
node=self.node, config=self.render_config(), **data
|
node=self.node, config=self.render_config(), **data
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_text(self, text: str, data: dict[str, Any] = None) -> str:
|
def render_text(self, text: str, data: Dict[str, Any] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Renders text based template providing all associated data to template.
|
Renders text based template providing all associated data to template.
|
||||||
|
|
||||||
|
@ -458,7 +458,7 @@ class ConfigService(abc.ABC):
|
||||||
f"{exceptions.text_error_template().render_unicode()}"
|
f"{exceptions.text_error_template().render_unicode()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_template(self, template_path: str, data: dict[str, Any] = None) -> str:
|
def render_template(self, template_path: str, data: Dict[str, Any] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Renders file based template providing all associated data to template.
|
Renders file based template providing all associated data to template.
|
||||||
|
|
||||||
|
@ -475,7 +475,7 @@ class ConfigService(abc.ABC):
|
||||||
f"{exceptions.text_error_template().render_unicode()}"
|
f"{exceptions.text_error_template().render_unicode()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _define_config(self, configs: list[Configuration]) -> None:
|
def _define_config(self, configs: List[Configuration]) -> None:
|
||||||
"""
|
"""
|
||||||
Initializes default configuration data.
|
Initializes default configuration data.
|
||||||
|
|
||||||
|
@ -485,7 +485,7 @@ class ConfigService(abc.ABC):
|
||||||
for config in configs:
|
for config in configs:
|
||||||
self.config[config.id] = config
|
self.config[config.id] = config
|
||||||
|
|
||||||
def render_config(self) -> dict[str, str]:
|
def render_config(self) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Returns configuration data key/value pairs for rendering a template.
|
Returns configuration data key/value pairs for rendering a template.
|
||||||
|
|
||||||
|
@ -496,7 +496,7 @@ class ConfigService(abc.ABC):
|
||||||
else:
|
else:
|
||||||
return {k: v.default for k, v in self.config.items()}
|
return {k: v.default for k, v in self.config.items()}
|
||||||
|
|
||||||
def set_config(self, data: dict[str, str]) -> None:
|
def set_config(self, data: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Set configuration data from key/value pairs.
|
Set configuration data from key/value pairs.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Dict, List, Set
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -12,16 +12,16 @@ class ConfigServiceDependencies:
|
||||||
Generates sets of services to start in order of their dependencies.
|
Generates sets of services to start in order of their dependencies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, services: dict[str, "ConfigService"]) -> None:
|
def __init__(self, services: Dict[str, "ConfigService"]) -> None:
|
||||||
"""
|
"""
|
||||||
Create a ConfigServiceDependencies instance.
|
Create a ConfigServiceDependencies instance.
|
||||||
|
|
||||||
:param services: services for determining dependency sets
|
:param services: services for determining dependency sets
|
||||||
"""
|
"""
|
||||||
# helpers to check validity
|
# helpers to check validity
|
||||||
self.dependents: dict[str, set[str]] = {}
|
self.dependents: Dict[str, Set[str]] = {}
|
||||||
self.started: set[str] = set()
|
self.started: Set[str] = set()
|
||||||
self.node_services: dict[str, "ConfigService"] = {}
|
self.node_services: Dict[str, "ConfigService"] = {}
|
||||||
for service in services.values():
|
for service in services.values():
|
||||||
self.node_services[service.name] = service
|
self.node_services[service.name] = service
|
||||||
for dependency in service.dependencies:
|
for dependency in service.dependencies:
|
||||||
|
@ -29,11 +29,11 @@ class ConfigServiceDependencies:
|
||||||
dependents.add(service.name)
|
dependents.add(service.name)
|
||||||
|
|
||||||
# used to find paths
|
# used to find paths
|
||||||
self.path: list["ConfigService"] = []
|
self.path: List["ConfigService"] = []
|
||||||
self.visited: set[str] = set()
|
self.visited: Set[str] = set()
|
||||||
self.visiting: set[str] = set()
|
self.visiting: Set[str] = set()
|
||||||
|
|
||||||
def startup_paths(self) -> list[list["ConfigService"]]:
|
def startup_paths(self) -> List[List["ConfigService"]]:
|
||||||
"""
|
"""
|
||||||
Find startup path sets based on service dependencies.
|
Find startup path sets based on service dependencies.
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ class ConfigServiceDependencies:
|
||||||
|
|
||||||
if self.started != set(self.node_services):
|
if self.started != set(self.node_services):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"failure to start all services: {self.started} != "
|
"failure to start all services: %s != %s"
|
||||||
f"{self.node_services.keys()}"
|
% (self.started, self.node_services.keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
@ -70,7 +70,7 @@ class ConfigServiceDependencies:
|
||||||
self.visited.clear()
|
self.visited.clear()
|
||||||
self.visiting.clear()
|
self.visiting.clear()
|
||||||
|
|
||||||
def _start(self, service: "ConfigService") -> list["ConfigService"]:
|
def _start(self, service: "ConfigService") -> List["ConfigService"]:
|
||||||
"""
|
"""
|
||||||
Starts a oath for checking dependencies for a given service.
|
Starts a oath for checking dependencies for a given service.
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class ConfigServiceDependencies:
|
||||||
self._reset()
|
self._reset()
|
||||||
return self._visit(service)
|
return self._visit(service)
|
||||||
|
|
||||||
def _visit(self, current_service: "ConfigService") -> list["ConfigService"]:
|
def _visit(self, current_service: "ConfigService") -> List["ConfigService"]:
|
||||||
"""
|
"""
|
||||||
Visits a service when discovering dependency chains for service.
|
Visits a service when discovering dependency chains for service.
|
||||||
|
|
||||||
|
@ -96,14 +96,14 @@ class ConfigServiceDependencies:
|
||||||
for service_name in current_service.dependencies:
|
for service_name in current_service.dependencies:
|
||||||
if service_name not in self.node_services:
|
if service_name not in self.node_services:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"required dependency was not included in node "
|
"required dependency was not included in node services: %s"
|
||||||
f"services: {service_name}"
|
% service_name
|
||||||
)
|
)
|
||||||
|
|
||||||
if service_name in self.visiting:
|
if service_name in self.visiting:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"cyclic dependency at service({current_service.name}): "
|
"cyclic dependency at service(%s): %s"
|
||||||
f"{service_name}"
|
% (current_service.name, service_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
if service_name not in self.visited:
|
if service_name not in self.visited:
|
||||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import pkgutil
|
import pkgutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
from core import configservices, utils
|
from core import configservices, utils
|
||||||
from core.configservice.base import ConfigService
|
from core.configservice.base import ConfigService
|
||||||
|
@ -19,9 +20,9 @@ class ConfigServiceManager:
|
||||||
"""
|
"""
|
||||||
Create a ConfigServiceManager instance.
|
Create a ConfigServiceManager instance.
|
||||||
"""
|
"""
|
||||||
self.services: dict[str, type[ConfigService]] = {}
|
self.services: Dict[str, Type[ConfigService]] = {}
|
||||||
|
|
||||||
def get_service(self, name: str) -> type[ConfigService]:
|
def get_service(self, name: str) -> Type[ConfigService]:
|
||||||
"""
|
"""
|
||||||
Retrieve a service by name.
|
Retrieve a service by name.
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ class ConfigServiceManager:
|
||||||
raise CoreError(f"service does not exist {name}")
|
raise CoreError(f"service does not exist {name}")
|
||||||
return service_class
|
return service_class
|
||||||
|
|
||||||
def add(self, service: type[ConfigService]) -> None:
|
def add(self, service: Type[ConfigService]) -> None:
|
||||||
"""
|
"""
|
||||||
Add service to manager, checking service requirements have been met.
|
Add service to manager, checking service requirements have been met.
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ class ConfigServiceManager:
|
||||||
# make service available
|
# make service available
|
||||||
self.services[name] = service
|
self.services[name] = service
|
||||||
|
|
||||||
def load_locals(self) -> list[str]:
|
def load_locals(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Search and add config service from local core module.
|
Search and add config service from local core module.
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ class ConfigServiceManager:
|
||||||
logger.debug("not loading config service(%s): %s", service.name, e)
|
logger.debug("not loading config service(%s): %s", service.name, e)
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def load(self, path: Path) -> list[str]:
|
def load(self, path: Path) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Search path provided for config services and add them for being managed.
|
Search path provided for config services and add them for being managed.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import abc
|
import abc
|
||||||
from typing import Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
|
@ -82,30 +82,29 @@ def rj45_check(iface: CoreInterface) -> bool:
|
||||||
class FRRZebra(ConfigService):
|
class FRRZebra(ConfigService):
|
||||||
name: str = "FRRzebra"
|
name: str = "FRRzebra"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"]
|
directories: List[str] = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"]
|
||||||
files: list[str] = [
|
files: List[str] = [
|
||||||
"/usr/local/etc/frr/frr.conf",
|
"/usr/local/etc/frr/frr.conf",
|
||||||
"frrboot.sh",
|
"frrboot.sh",
|
||||||
"/usr/local/etc/frr/vtysh.conf",
|
"/usr/local/etc/frr/vtysh.conf",
|
||||||
"/usr/local/etc/frr/daemons",
|
"/usr/local/etc/frr/daemons",
|
||||||
]
|
]
|
||||||
executables: list[str] = ["zebra"]
|
executables: List[str] = ["zebra"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash frrboot.sh zebra"]
|
startup: List[str] = ["bash frrboot.sh zebra"]
|
||||||
validate: list[str] = ["pidof zebra"]
|
validate: List[str] = ["pidof zebra"]
|
||||||
shutdown: list[str] = ["killall zebra"]
|
shutdown: List[str] = ["killall zebra"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
frr_conf = self.files[0]
|
frr_conf = self.files[0]
|
||||||
frr_bin_search = self.node.session.options.get(
|
frr_bin_search = self.node.session.options.get(
|
||||||
"frr_bin_search", default="/usr/local/bin /usr/bin /usr/lib/frr"
|
"frr_bin_search", default="/usr/local/bin /usr/bin /usr/lib/frr"
|
||||||
).strip('"')
|
).strip('"')
|
||||||
frr_sbin_search = self.node.session.options.get(
|
frr_sbin_search = self.node.session.options.get(
|
||||||
"frr_sbin_search",
|
"frr_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/frr"
|
||||||
default="/usr/local/sbin /usr/sbin /usr/lib/frr /usr/libexec/frr",
|
|
||||||
).strip('"')
|
).strip('"')
|
||||||
|
|
||||||
services = []
|
services = []
|
||||||
|
@ -146,16 +145,16 @@ class FRRZebra(ConfigService):
|
||||||
|
|
||||||
class FrrService(abc.ABC):
|
class FrrService(abc.ABC):
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = []
|
files: List[str] = []
|
||||||
executables: list[str] = []
|
executables: List[str] = []
|
||||||
dependencies: list[str] = ["FRRzebra"]
|
dependencies: List[str] = ["FRRzebra"]
|
||||||
startup: list[str] = []
|
startup: List[str] = []
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
ipv4_routing: bool = False
|
ipv4_routing: bool = False
|
||||||
ipv6_routing: bool = False
|
ipv6_routing: bool = False
|
||||||
|
|
||||||
|
@ -176,8 +175,8 @@ class FRROspfv2(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRROSPFv2"
|
name: str = "FRROSPFv2"
|
||||||
shutdown: list[str] = ["killall ospfd"]
|
shutdown: List[str] = ["killall ospfd"]
|
||||||
validate: list[str] = ["pidof ospfd"]
|
validate: List[str] = ["pidof ospfd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
|
|
||||||
def frr_config(self) -> str:
|
def frr_config(self) -> str:
|
||||||
|
@ -228,8 +227,8 @@ class FRROspfv3(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRROSPFv3"
|
name: str = "FRROSPFv3"
|
||||||
shutdown: list[str] = ["killall ospf6d"]
|
shutdown: List[str] = ["killall ospf6d"]
|
||||||
validate: list[str] = ["pidof ospf6d"]
|
validate: List[str] = ["pidof ospf6d"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
|
@ -265,8 +264,8 @@ class FRRBgp(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRRBGP"
|
name: str = "FRRBGP"
|
||||||
shutdown: list[str] = ["killall bgpd"]
|
shutdown: List[str] = ["killall bgpd"]
|
||||||
validate: list[str] = ["pidof bgpd"]
|
validate: List[str] = ["pidof bgpd"]
|
||||||
custom_needed: bool = True
|
custom_needed: bool = True
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
@ -295,8 +294,8 @@ class FRRRip(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRRRIP"
|
name: str = "FRRRIP"
|
||||||
shutdown: list[str] = ["killall ripd"]
|
shutdown: List[str] = ["killall ripd"]
|
||||||
validate: list[str] = ["pidof ripd"]
|
validate: List[str] = ["pidof ripd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
|
|
||||||
def frr_config(self) -> str:
|
def frr_config(self) -> str:
|
||||||
|
@ -320,8 +319,8 @@ class FRRRipng(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRRRIPNG"
|
name: str = "FRRRIPNG"
|
||||||
shutdown: list[str] = ["killall ripngd"]
|
shutdown: List[str] = ["killall ripngd"]
|
||||||
validate: list[str] = ["pidof ripngd"]
|
validate: List[str] = ["pidof ripngd"]
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
def frr_config(self) -> str:
|
def frr_config(self) -> str:
|
||||||
|
@ -346,8 +345,8 @@ class FRRBabel(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRRBabel"
|
name: str = "FRRBabel"
|
||||||
shutdown: list[str] = ["killall babeld"]
|
shutdown: List[str] = ["killall babeld"]
|
||||||
validate: list[str] = ["pidof babeld"]
|
validate: List[str] = ["pidof babeld"]
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
def frr_config(self) -> str:
|
def frr_config(self) -> str:
|
||||||
|
@ -386,8 +385,8 @@ class FRRpimd(FrrService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "FRRpimd"
|
name: str = "FRRpimd"
|
||||||
shutdown: list[str] = ["killall pimd"]
|
shutdown: List[str] = ["killall pimd"]
|
||||||
validate: list[str] = ["pidof pimd"]
|
validate: List[str] = ["pidof pimd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
|
|
||||||
def frr_config(self) -> str:
|
def frr_config(self) -> str:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
|
@ -10,18 +10,18 @@ GROUP: str = "ProtoSvc"
|
||||||
class MgenSinkService(ConfigService):
|
class MgenSinkService(ConfigService):
|
||||||
name: str = "MGEN_Sink"
|
name: str = "MGEN_Sink"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["mgensink.sh", "sink.mgen"]
|
files: List[str] = ["mgensink.sh", "sink.mgen"]
|
||||||
executables: list[str] = ["mgen"]
|
executables: List[str] = ["mgen"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash mgensink.sh"]
|
startup: List[str] = ["bash mgensink.sh"]
|
||||||
validate: list[str] = ["pidof mgen"]
|
validate: List[str] = ["pidof mgen"]
|
||||||
shutdown: list[str] = ["killall mgen"]
|
shutdown: List[str] = ["killall mgen"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces():
|
for iface in self.node.get_ifaces():
|
||||||
name = utils.sysctl_devname(iface.name)
|
name = utils.sysctl_devname(iface.name)
|
||||||
|
@ -32,18 +32,18 @@ class MgenSinkService(ConfigService):
|
||||||
class NrlNhdp(ConfigService):
|
class NrlNhdp(ConfigService):
|
||||||
name: str = "NHDP"
|
name: str = "NHDP"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["nrlnhdp.sh"]
|
files: List[str] = ["nrlnhdp.sh"]
|
||||||
executables: list[str] = ["nrlnhdp"]
|
executables: List[str] = ["nrlnhdp"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash nrlnhdp.sh"]
|
startup: List[str] = ["bash nrlnhdp.sh"]
|
||||||
validate: list[str] = ["pidof nrlnhdp"]
|
validate: List[str] = ["pidof nrlnhdp"]
|
||||||
shutdown: list[str] = ["killall nrlnhdp"]
|
shutdown: List[str] = ["killall nrlnhdp"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
has_smf = "SMF" in self.node.config_services
|
has_smf = "SMF" in self.node.config_services
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
|
@ -54,18 +54,18 @@ class NrlNhdp(ConfigService):
|
||||||
class NrlSmf(ConfigService):
|
class NrlSmf(ConfigService):
|
||||||
name: str = "SMF"
|
name: str = "SMF"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["startsmf.sh"]
|
files: List[str] = ["startsmf.sh"]
|
||||||
executables: list[str] = ["nrlsmf", "killall"]
|
executables: List[str] = ["nrlsmf", "killall"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash startsmf.sh"]
|
startup: List[str] = ["bash startsmf.sh"]
|
||||||
validate: list[str] = ["pidof nrlsmf"]
|
validate: List[str] = ["pidof nrlsmf"]
|
||||||
shutdown: list[str] = ["killall nrlsmf"]
|
shutdown: List[str] = ["killall nrlsmf"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
has_nhdp = "NHDP" in self.node.config_services
|
has_nhdp = "NHDP" in self.node.config_services
|
||||||
has_olsr = "OLSR" in self.node.config_services
|
has_olsr = "OLSR" in self.node.config_services
|
||||||
ifnames = []
|
ifnames = []
|
||||||
|
@ -84,18 +84,18 @@ class NrlSmf(ConfigService):
|
||||||
class NrlOlsr(ConfigService):
|
class NrlOlsr(ConfigService):
|
||||||
name: str = "OLSR"
|
name: str = "OLSR"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["nrlolsrd.sh"]
|
files: List[str] = ["nrlolsrd.sh"]
|
||||||
executables: list[str] = ["nrlolsrd"]
|
executables: List[str] = ["nrlolsrd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash nrlolsrd.sh"]
|
startup: List[str] = ["bash nrlolsrd.sh"]
|
||||||
validate: list[str] = ["pidof nrlolsrd"]
|
validate: List[str] = ["pidof nrlolsrd"]
|
||||||
shutdown: list[str] = ["killall nrlolsrd"]
|
shutdown: List[str] = ["killall nrlolsrd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
has_smf = "SMF" in self.node.config_services
|
has_smf = "SMF" in self.node.config_services
|
||||||
has_zebra = "zebra" in self.node.config_services
|
has_zebra = "zebra" in self.node.config_services
|
||||||
ifname = None
|
ifname = None
|
||||||
|
@ -108,18 +108,18 @@ class NrlOlsr(ConfigService):
|
||||||
class NrlOlsrv2(ConfigService):
|
class NrlOlsrv2(ConfigService):
|
||||||
name: str = "OLSRv2"
|
name: str = "OLSRv2"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["nrlolsrv2.sh"]
|
files: List[str] = ["nrlolsrv2.sh"]
|
||||||
executables: list[str] = ["nrlolsrv2"]
|
executables: List[str] = ["nrlolsrv2"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash nrlolsrv2.sh"]
|
startup: List[str] = ["bash nrlolsrv2.sh"]
|
||||||
validate: list[str] = ["pidof nrlolsrv2"]
|
validate: List[str] = ["pidof nrlolsrv2"]
|
||||||
shutdown: list[str] = ["killall nrlolsrv2"]
|
shutdown: List[str] = ["killall nrlolsrv2"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
has_smf = "SMF" in self.node.config_services
|
has_smf = "SMF" in self.node.config_services
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
|
@ -130,18 +130,18 @@ class NrlOlsrv2(ConfigService):
|
||||||
class OlsrOrg(ConfigService):
|
class OlsrOrg(ConfigService):
|
||||||
name: str = "OLSRORG"
|
name: str = "OLSRORG"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = ["/etc/olsrd"]
|
directories: List[str] = ["/etc/olsrd"]
|
||||||
files: list[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"]
|
files: List[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"]
|
||||||
executables: list[str] = ["olsrd"]
|
executables: List[str] = ["olsrd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash olsrd.sh"]
|
startup: List[str] = ["bash olsrd.sh"]
|
||||||
validate: list[str] = ["pidof olsrd"]
|
validate: List[str] = ["pidof olsrd"]
|
||||||
shutdown: list[str] = ["killall olsrd"]
|
shutdown: List[str] = ["killall olsrd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
has_smf = "SMF" in self.node.config_services
|
has_smf = "SMF" in self.node.config_services
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
|
@ -152,13 +152,13 @@ class OlsrOrg(ConfigService):
|
||||||
class MgenActor(ConfigService):
|
class MgenActor(ConfigService):
|
||||||
name: str = "MgenActor"
|
name: str = "MgenActor"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["start_mgen_actor.sh"]
|
files: List[str] = ["start_mgen_actor.sh"]
|
||||||
executables: list[str] = ["mgen"]
|
executables: List[str] = ["mgen"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash start_mgen_actor.sh"]
|
startup: List[str] = ["bash start_mgen_actor.sh"]
|
||||||
validate: list[str] = ["pidof mgen"]
|
validate: List[str] = ["pidof mgen"]
|
||||||
shutdown: list[str] = ["killall mgen"]
|
shutdown: List[str] = ["killall mgen"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
|
@ -84,22 +84,22 @@ def rj45_check(iface: CoreInterface) -> bool:
|
||||||
class Zebra(ConfigService):
|
class Zebra(ConfigService):
|
||||||
name: str = "zebra"
|
name: str = "zebra"
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = ["/usr/local/etc/quagga", "/var/run/quagga"]
|
directories: List[str] = ["/usr/local/etc/quagga", "/var/run/quagga"]
|
||||||
files: list[str] = [
|
files: List[str] = [
|
||||||
"/usr/local/etc/quagga/Quagga.conf",
|
"/usr/local/etc/quagga/Quagga.conf",
|
||||||
"quaggaboot.sh",
|
"quaggaboot.sh",
|
||||||
"/usr/local/etc/quagga/vtysh.conf",
|
"/usr/local/etc/quagga/vtysh.conf",
|
||||||
]
|
]
|
||||||
executables: list[str] = ["zebra"]
|
executables: List[str] = ["zebra"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash quaggaboot.sh zebra"]
|
startup: List[str] = ["bash quaggaboot.sh zebra"]
|
||||||
validate: list[str] = ["pidof zebra"]
|
validate: List[str] = ["pidof zebra"]
|
||||||
shutdown: list[str] = ["killall zebra"]
|
shutdown: List[str] = ["killall zebra"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
quagga_bin_search = self.node.session.options.get(
|
quagga_bin_search = self.node.session.options.get(
|
||||||
"quagga_bin_search", default="/usr/local/bin /usr/bin /usr/lib/quagga"
|
"quagga_bin_search", default="/usr/local/bin /usr/bin /usr/lib/quagga"
|
||||||
).strip('"')
|
).strip('"')
|
||||||
|
@ -153,16 +153,16 @@ class Zebra(ConfigService):
|
||||||
|
|
||||||
class QuaggaService(abc.ABC):
|
class QuaggaService(abc.ABC):
|
||||||
group: str = GROUP
|
group: str = GROUP
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = []
|
files: List[str] = []
|
||||||
executables: list[str] = []
|
executables: List[str] = []
|
||||||
dependencies: list[str] = ["zebra"]
|
dependencies: List[str] = ["zebra"]
|
||||||
startup: list[str] = []
|
startup: List[str] = []
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
ipv4_routing: bool = False
|
ipv4_routing: bool = False
|
||||||
ipv6_routing: bool = False
|
ipv6_routing: bool = False
|
||||||
|
|
||||||
|
@ -183,8 +183,8 @@ class Ospfv2(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "OSPFv2"
|
name: str = "OSPFv2"
|
||||||
validate: list[str] = ["pidof ospfd"]
|
validate: List[str] = ["pidof ospfd"]
|
||||||
shutdown: list[str] = ["killall ospfd"]
|
shutdown: List[str] = ["killall ospfd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
|
|
||||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||||
|
@ -234,8 +234,8 @@ class Ospfv3(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "OSPFv3"
|
name: str = "OSPFv3"
|
||||||
shutdown: list[str] = ["killall ospf6d"]
|
shutdown: List[str] = ["killall ospf6d"]
|
||||||
validate: list[str] = ["pidof ospf6d"]
|
validate: List[str] = ["pidof ospf6d"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
|
@ -300,8 +300,8 @@ class Bgp(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "BGP"
|
name: str = "BGP"
|
||||||
shutdown: list[str] = ["killall bgpd"]
|
shutdown: List[str] = ["killall bgpd"]
|
||||||
validate: list[str] = ["pidof bgpd"]
|
validate: List[str] = ["pidof bgpd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
|
@ -329,8 +329,8 @@ class Rip(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "RIP"
|
name: str = "RIP"
|
||||||
shutdown: list[str] = ["killall ripd"]
|
shutdown: List[str] = ["killall ripd"]
|
||||||
validate: list[str] = ["pidof ripd"]
|
validate: List[str] = ["pidof ripd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
|
|
||||||
def quagga_config(self) -> str:
|
def quagga_config(self) -> str:
|
||||||
|
@ -354,8 +354,8 @@ class Ripng(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "RIPNG"
|
name: str = "RIPNG"
|
||||||
shutdown: list[str] = ["killall ripngd"]
|
shutdown: List[str] = ["killall ripngd"]
|
||||||
validate: list[str] = ["pidof ripngd"]
|
validate: List[str] = ["pidof ripngd"]
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
def quagga_config(self) -> str:
|
def quagga_config(self) -> str:
|
||||||
|
@ -380,8 +380,8 @@ class Babel(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "Babel"
|
name: str = "Babel"
|
||||||
shutdown: list[str] = ["killall babeld"]
|
shutdown: List[str] = ["killall babeld"]
|
||||||
validate: list[str] = ["pidof babeld"]
|
validate: List[str] = ["pidof babeld"]
|
||||||
ipv6_routing: bool = True
|
ipv6_routing: bool = True
|
||||||
|
|
||||||
def quagga_config(self) -> str:
|
def quagga_config(self) -> str:
|
||||||
|
@ -420,8 +420,8 @@ class Xpimd(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "Xpimd"
|
name: str = "Xpimd"
|
||||||
shutdown: list[str] = ["killall xpimd"]
|
shutdown: List[str] = ["killall xpimd"]
|
||||||
validate: list[str] = ["pidof xpimd"]
|
validate: List[str] = ["pidof xpimd"]
|
||||||
ipv4_routing: bool = True
|
ipv4_routing: bool = True
|
||||||
|
|
||||||
def quagga_config(self) -> str:
|
def quagga_config(self) -> str:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from core.config import ConfigString, Configuration
|
from core.config import ConfigString, Configuration
|
||||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
|
@ -9,41 +9,41 @@ GROUP_NAME: str = "Security"
|
||||||
class VpnClient(ConfigService):
|
class VpnClient(ConfigService):
|
||||||
name: str = "VPNClient"
|
name: str = "VPNClient"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["vpnclient.sh"]
|
files: List[str] = ["vpnclient.sh"]
|
||||||
executables: list[str] = ["openvpn", "ip", "killall"]
|
executables: List[str] = ["openvpn", "ip", "killall"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash vpnclient.sh"]
|
startup: List[str] = ["bash vpnclient.sh"]
|
||||||
validate: list[str] = ["pidof openvpn"]
|
validate: List[str] = ["pidof openvpn"]
|
||||||
shutdown: list[str] = ["killall openvpn"]
|
shutdown: List[str] = ["killall openvpn"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = [
|
default_configs: List[Configuration] = [
|
||||||
ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"),
|
ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"),
|
||||||
ConfigString(id="keyname", label="Key Name", default="client1"),
|
ConfigString(id="keyname", label="Key Name", default="client1"),
|
||||||
ConfigString(id="server", label="Server", default="10.0.2.10"),
|
ConfigString(id="server", label="Server", default="10.0.2.10"),
|
||||||
]
|
]
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
|
|
||||||
class VpnServer(ConfigService):
|
class VpnServer(ConfigService):
|
||||||
name: str = "VPNServer"
|
name: str = "VPNServer"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["vpnserver.sh"]
|
files: List[str] = ["vpnserver.sh"]
|
||||||
executables: list[str] = ["openvpn", "ip", "killall"]
|
executables: List[str] = ["openvpn", "ip", "killall"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash vpnserver.sh"]
|
startup: List[str] = ["bash vpnserver.sh"]
|
||||||
validate: list[str] = ["pidof openvpn"]
|
validate: List[str] = ["pidof openvpn"]
|
||||||
shutdown: list[str] = ["killall openvpn"]
|
shutdown: List[str] = ["killall openvpn"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = [
|
default_configs: List[Configuration] = [
|
||||||
ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"),
|
ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"),
|
||||||
ConfigString(id="keyname", label="Key Name", default="server"),
|
ConfigString(id="keyname", label="Key Name", default="server"),
|
||||||
ConfigString(id="subnet", label="Subnet", default="10.0.200.0"),
|
ConfigString(id="subnet", label="Subnet", default="10.0.200.0"),
|
||||||
]
|
]
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
address = None
|
address = None
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
ip4 = iface.get_ip4()
|
ip4 = iface.get_ip4()
|
||||||
|
@ -56,48 +56,48 @@ class VpnServer(ConfigService):
|
||||||
class IPsec(ConfigService):
|
class IPsec(ConfigService):
|
||||||
name: str = "IPsec"
|
name: str = "IPsec"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["ipsec.sh"]
|
files: List[str] = ["ipsec.sh"]
|
||||||
executables: list[str] = ["racoon", "ip", "setkey", "killall"]
|
executables: List[str] = ["racoon", "ip", "setkey", "killall"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash ipsec.sh"]
|
startup: List[str] = ["bash ipsec.sh"]
|
||||||
validate: list[str] = ["pidof racoon"]
|
validate: List[str] = ["pidof racoon"]
|
||||||
shutdown: list[str] = ["killall racoon"]
|
shutdown: List[str] = ["killall racoon"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
|
|
||||||
class Firewall(ConfigService):
|
class Firewall(ConfigService):
|
||||||
name: str = "Firewall"
|
name: str = "Firewall"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["firewall.sh"]
|
files: List[str] = ["firewall.sh"]
|
||||||
executables: list[str] = ["iptables"]
|
executables: List[str] = ["iptables"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash firewall.sh"]
|
startup: List[str] = ["bash firewall.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
|
|
||||||
class Nat(ConfigService):
|
class Nat(ConfigService):
|
||||||
name: str = "NAT"
|
name: str = "NAT"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["nat.sh"]
|
files: List[str] = ["nat.sh"]
|
||||||
executables: list[str] = ["iptables"]
|
executables: List[str] = ["iptables"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash nat.sh"]
|
startup: List[str] = ["bash nat.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
ifnames.append(iface.name)
|
ifnames.append(iface.name)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
@ -12,18 +12,18 @@ GROUP_NAME = "Utility"
|
||||||
class DefaultRouteService(ConfigService):
|
class DefaultRouteService(ConfigService):
|
||||||
name: str = "DefaultRoute"
|
name: str = "DefaultRoute"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["defaultroute.sh"]
|
files: List[str] = ["defaultroute.sh"]
|
||||||
executables: list[str] = ["ip"]
|
executables: List[str] = ["ip"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash defaultroute.sh"]
|
startup: List[str] = ["bash defaultroute.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
# only add default routes for linked routing nodes
|
# only add default routes for linked routing nodes
|
||||||
routes = []
|
routes = []
|
||||||
ifaces = self.node.get_ifaces()
|
ifaces = self.node.get_ifaces()
|
||||||
|
@ -40,18 +40,18 @@ class DefaultRouteService(ConfigService):
|
||||||
class DefaultMulticastRouteService(ConfigService):
|
class DefaultMulticastRouteService(ConfigService):
|
||||||
name: str = "DefaultMulticastRoute"
|
name: str = "DefaultMulticastRoute"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["defaultmroute.sh"]
|
files: List[str] = ["defaultmroute.sh"]
|
||||||
executables: list[str] = []
|
executables: List[str] = []
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash defaultmroute.sh"]
|
startup: List[str] = ["bash defaultmroute.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifname = None
|
ifname = None
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
ifname = iface.name
|
ifname = iface.name
|
||||||
|
@ -62,18 +62,18 @@ class DefaultMulticastRouteService(ConfigService):
|
||||||
class StaticRouteService(ConfigService):
|
class StaticRouteService(ConfigService):
|
||||||
name: str = "StaticRoute"
|
name: str = "StaticRoute"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["staticroute.sh"]
|
files: List[str] = ["staticroute.sh"]
|
||||||
executables: list[str] = []
|
executables: List[str] = []
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash staticroute.sh"]
|
startup: List[str] = ["bash staticroute.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
routes = []
|
routes = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
for ip in iface.ips():
|
for ip in iface.ips():
|
||||||
|
@ -90,18 +90,18 @@ class StaticRouteService(ConfigService):
|
||||||
class IpForwardService(ConfigService):
|
class IpForwardService(ConfigService):
|
||||||
name: str = "IPForward"
|
name: str = "IPForward"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["ipforward.sh"]
|
files: List[str] = ["ipforward.sh"]
|
||||||
executables: list[str] = ["sysctl"]
|
executables: List[str] = ["sysctl"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash ipforward.sh"]
|
startup: List[str] = ["bash ipforward.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = []
|
shutdown: List[str] = []
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
devnames = []
|
devnames = []
|
||||||
for iface in self.node.get_ifaces():
|
for iface in self.node.get_ifaces():
|
||||||
devname = utils.sysctl_devname(iface.name)
|
devname = utils.sysctl_devname(iface.name)
|
||||||
|
@ -112,18 +112,18 @@ class IpForwardService(ConfigService):
|
||||||
class SshService(ConfigService):
|
class SshService(ConfigService):
|
||||||
name: str = "SSH"
|
name: str = "SSH"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = ["/etc/ssh", "/var/run/sshd"]
|
directories: List[str] = ["/etc/ssh", "/var/run/sshd"]
|
||||||
files: list[str] = ["startsshd.sh", "/etc/ssh/sshd_config"]
|
files: List[str] = ["startsshd.sh", "/etc/ssh/sshd_config"]
|
||||||
executables: list[str] = ["sshd"]
|
executables: List[str] = ["sshd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash startsshd.sh"]
|
startup: List[str] = ["bash startsshd.sh"]
|
||||||
validate: list[str] = []
|
validate: List[str] = []
|
||||||
shutdown: list[str] = ["killall sshd"]
|
shutdown: List[str] = ["killall sshd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
return dict(
|
return dict(
|
||||||
sshcfgdir=self.directories[0],
|
sshcfgdir=self.directories[0],
|
||||||
sshstatedir=self.directories[1],
|
sshstatedir=self.directories[1],
|
||||||
|
@ -134,18 +134,18 @@ class SshService(ConfigService):
|
||||||
class DhcpService(ConfigService):
|
class DhcpService(ConfigService):
|
||||||
name: str = "DHCP"
|
name: str = "DHCP"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = ["/etc/dhcp", "/var/lib/dhcp"]
|
directories: List[str] = ["/etc/dhcp", "/var/lib/dhcp"]
|
||||||
files: list[str] = ["/etc/dhcp/dhcpd.conf"]
|
files: List[str] = ["/etc/dhcp/dhcpd.conf"]
|
||||||
executables: list[str] = ["dhcpd"]
|
executables: List[str] = ["dhcpd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"]
|
startup: List[str] = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"]
|
||||||
validate: list[str] = ["pidof dhcpd"]
|
validate: List[str] = ["pidof dhcpd"]
|
||||||
shutdown: list[str] = ["killall dhcpd"]
|
shutdown: List[str] = ["killall dhcpd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
subnets = []
|
subnets = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
for ip4 in iface.ip4s:
|
for ip4 in iface.ip4s:
|
||||||
|
@ -162,18 +162,18 @@ class DhcpService(ConfigService):
|
||||||
class DhcpClientService(ConfigService):
|
class DhcpClientService(ConfigService):
|
||||||
name: str = "DHCPClient"
|
name: str = "DHCPClient"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["startdhcpclient.sh"]
|
files: List[str] = ["startdhcpclient.sh"]
|
||||||
executables: list[str] = ["dhclient"]
|
executables: List[str] = ["dhclient"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash startdhcpclient.sh"]
|
startup: List[str] = ["bash startdhcpclient.sh"]
|
||||||
validate: list[str] = ["pidof dhclient"]
|
validate: List[str] = ["pidof dhclient"]
|
||||||
shutdown: list[str] = ["killall dhclient"]
|
shutdown: List[str] = ["killall dhclient"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
ifnames.append(iface.name)
|
ifnames.append(iface.name)
|
||||||
|
@ -183,56 +183,56 @@ class DhcpClientService(ConfigService):
|
||||||
class FtpService(ConfigService):
|
class FtpService(ConfigService):
|
||||||
name: str = "FTP"
|
name: str = "FTP"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = ["/var/run/vsftpd/empty", "/var/ftp"]
|
directories: List[str] = ["/var/run/vsftpd/empty", "/var/ftp"]
|
||||||
files: list[str] = ["vsftpd.conf"]
|
files: List[str] = ["vsftpd.conf"]
|
||||||
executables: list[str] = ["vsftpd"]
|
executables: List[str] = ["vsftpd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["vsftpd ./vsftpd.conf"]
|
startup: List[str] = ["vsftpd ./vsftpd.conf"]
|
||||||
validate: list[str] = ["pidof vsftpd"]
|
validate: List[str] = ["pidof vsftpd"]
|
||||||
shutdown: list[str] = ["killall vsftpd"]
|
shutdown: List[str] = ["killall vsftpd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
|
|
||||||
class PcapService(ConfigService):
|
class PcapService(ConfigService):
|
||||||
name: str = "pcap"
|
name: str = "pcap"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = []
|
directories: List[str] = []
|
||||||
files: list[str] = ["pcap.sh"]
|
files: List[str] = ["pcap.sh"]
|
||||||
executables: list[str] = ["tcpdump"]
|
executables: List[str] = ["tcpdump"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash pcap.sh start"]
|
startup: List[str] = ["bash pcap.sh start"]
|
||||||
validate: list[str] = ["pidof tcpdump"]
|
validate: List[str] = ["pidof tcpdump"]
|
||||||
shutdown: list[str] = ["bash pcap.sh stop"]
|
shutdown: List[str] = ["bash pcap.sh stop"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifnames = []
|
ifnames = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
ifnames.append(iface.name)
|
ifnames.append(iface.name)
|
||||||
return dict(ifnames=ifnames)
|
return dict()
|
||||||
|
|
||||||
|
|
||||||
class RadvdService(ConfigService):
|
class RadvdService(ConfigService):
|
||||||
name: str = "radvd"
|
name: str = "radvd"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = ["/etc/radvd", "/var/run/radvd"]
|
directories: List[str] = ["/etc/radvd", "/var/run/radvd"]
|
||||||
files: list[str] = ["/etc/radvd/radvd.conf"]
|
files: List[str] = ["/etc/radvd/radvd.conf"]
|
||||||
executables: list[str] = ["radvd"]
|
executables: List[str] = ["radvd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = [
|
startup: List[str] = [
|
||||||
"radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log"
|
"radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log"
|
||||||
]
|
]
|
||||||
validate: list[str] = ["pidof radvd"]
|
validate: List[str] = ["pidof radvd"]
|
||||||
shutdown: list[str] = ["pkill radvd"]
|
shutdown: List[str] = ["pkill radvd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifaces = []
|
ifaces = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
prefixes = []
|
prefixes = []
|
||||||
|
@ -247,22 +247,22 @@ class RadvdService(ConfigService):
|
||||||
class AtdService(ConfigService):
|
class AtdService(ConfigService):
|
||||||
name: str = "atd"
|
name: str = "atd"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"]
|
directories: List[str] = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"]
|
||||||
files: list[str] = ["startatd.sh"]
|
files: List[str] = ["startatd.sh"]
|
||||||
executables: list[str] = ["atd"]
|
executables: List[str] = ["atd"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["bash startatd.sh"]
|
startup: List[str] = ["bash startatd.sh"]
|
||||||
validate: list[str] = ["pidof atd"]
|
validate: List[str] = ["pidof atd"]
|
||||||
shutdown: list[str] = ["pkill atd"]
|
shutdown: List[str] = ["pkill atd"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
|
|
||||||
class HttpService(ConfigService):
|
class HttpService(ConfigService):
|
||||||
name: str = "HTTP"
|
name: str = "HTTP"
|
||||||
group: str = GROUP_NAME
|
group: str = GROUP_NAME
|
||||||
directories: list[str] = [
|
directories: List[str] = [
|
||||||
"/etc/apache2",
|
"/etc/apache2",
|
||||||
"/var/run/apache2",
|
"/var/run/apache2",
|
||||||
"/var/log/apache2",
|
"/var/log/apache2",
|
||||||
|
@ -270,21 +270,21 @@ class HttpService(ConfigService):
|
||||||
"/var/lock/apache2",
|
"/var/lock/apache2",
|
||||||
"/var/www",
|
"/var/www",
|
||||||
]
|
]
|
||||||
files: list[str] = [
|
files: List[str] = [
|
||||||
"/etc/apache2/apache2.conf",
|
"/etc/apache2/apache2.conf",
|
||||||
"/etc/apache2/envvars",
|
"/etc/apache2/envvars",
|
||||||
"/var/www/index.html",
|
"/var/www/index.html",
|
||||||
]
|
]
|
||||||
executables: list[str] = ["apache2ctl"]
|
executables: List[str] = ["apache2ctl"]
|
||||||
dependencies: list[str] = []
|
dependencies: List[str] = []
|
||||||
startup: list[str] = ["chown www-data /var/lock/apache2", "apache2ctl start"]
|
startup: List[str] = ["chown www-data /var/lock/apache2", "apache2ctl start"]
|
||||||
validate: list[str] = ["pidof apache2"]
|
validate: List[str] = ["pidof apache2"]
|
||||||
shutdown: list[str] = ["apache2ctl stop"]
|
shutdown: List[str] = ["apache2ctl stop"]
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
||||||
default_configs: list[Configuration] = []
|
default_configs: List[Configuration] = []
|
||||||
modes: dict[str, dict[str, str]] = {}
|
modes: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def data(self) -> dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
ifaces = []
|
ifaces = []
|
||||||
for iface in self.node.get_ifaces(control=False):
|
for iface in self.node.get_ifaces(control=False):
|
||||||
ifaces.append(iface)
|
ifaces.append(iface)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# auto-generated by RADVD service (utility.py)
|
# auto-generated by RADVD service (utility.py)
|
||||||
% for ifname, prefixes in ifaces:
|
% for ifname, prefixes in values:
|
||||||
interface ${ifname}
|
interface ${ifname}
|
||||||
{
|
{
|
||||||
AdvSendAdvert on;
|
AdvSendAdvert on;
|
||||||
|
|
|
@ -13,5 +13,4 @@ sysctl -w net.ipv4.conf.default.rp_filter=0
|
||||||
sysctl -w net.ipv4.conf.${devname}.forwarding=1
|
sysctl -w net.ipv4.conf.${devname}.forwarding=1
|
||||||
sysctl -w net.ipv4.conf.${devname}.send_redirects=0
|
sysctl -w net.ipv4.conf.${devname}.send_redirects=0
|
||||||
sysctl -w net.ipv4.conf.${devname}.rp_filter=0
|
sysctl -w net.ipv4.conf.${devname}.rp_filter=0
|
||||||
sysctl -w net.ipv6.conf.${devname}.forwarding=1
|
|
||||||
% endfor
|
% endfor
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# (-s snap length, -C limit pcap file length, -n disable name resolution)
|
# (-s snap length, -C limit pcap file length, -n disable name resolution)
|
||||||
if [ "x$1" = "xstart" ]; then
|
if [ "x$1" = "xstart" ]; then
|
||||||
% for ifname in ifnames:
|
% for ifname in ifnames:
|
||||||
tcpdump -s 12288 -C 10 -n -w ${node.name}.${ifname}.pcap -i ${ifname} > /dev/null 2>&1 &
|
tcpdump -s 12288 -C 10 -n -w ${node.name}.${ifname}.pcap -i ${ifname} < /dev/null &
|
||||||
% endfor
|
% endfor
|
||||||
elif [ "x$1" = "xstop" ]; then
|
elif [ "x$1" = "xstop" ]; then
|
||||||
mkdir -p $SESSION_DIR/pcap
|
mkdir -p $SESSION_DIR/pcap
|
||||||
|
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.emane.emanemodel import EmaneModel
|
from core.emane.emanemodel import EmaneModel
|
||||||
|
@ -126,9 +126,9 @@ class EmaneManager:
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.session: "Session" = session
|
self.session: "Session" = session
|
||||||
self.nems_to_ifaces: dict[int, CoreInterface] = {}
|
self.nems_to_ifaces: Dict[int, CoreInterface] = {}
|
||||||
self.ifaces_to_nems: dict[CoreInterface, int] = {}
|
self.ifaces_to_nems: Dict[CoreInterface, int] = {}
|
||||||
self._emane_nets: dict[int, EmaneNet] = {}
|
self._emane_nets: Dict[int, EmaneNet] = {}
|
||||||
self._emane_node_lock: threading.Lock = threading.Lock()
|
self._emane_node_lock: threading.Lock = threading.Lock()
|
||||||
# port numbers are allocated from these counters
|
# port numbers are allocated from these counters
|
||||||
self.platformport: int = self.session.options.get_int(
|
self.platformport: int = self.session.options.get_int(
|
||||||
|
@ -141,14 +141,14 @@ class EmaneManager:
|
||||||
self.eventmonthread: Optional[threading.Thread] = None
|
self.eventmonthread: Optional[threading.Thread] = None
|
||||||
|
|
||||||
# model for global EMANE configuration options
|
# model for global EMANE configuration options
|
||||||
self.node_configs: dict[int, dict[str, dict[str, str]]] = {}
|
self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {}
|
||||||
self.node_models: dict[int, str] = {}
|
self.node_models: Dict[int, str] = {}
|
||||||
|
|
||||||
# link monitor
|
# link monitor
|
||||||
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self)
|
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self)
|
||||||
# emane event monitoring
|
# emane event monitoring
|
||||||
self.services: dict[str, EmaneEventService] = {}
|
self.services: Dict[str, EmaneEventService] = {}
|
||||||
self.nem_service: dict[int, EmaneEventService] = {}
|
self.nem_service: Dict[int, EmaneEventService] = {}
|
||||||
|
|
||||||
def next_nem_id(self, iface: CoreInterface) -> int:
|
def next_nem_id(self, iface: CoreInterface) -> int:
|
||||||
nem_id = self.session.options.get_int("nem_id_start")
|
nem_id = self.session.options.get_int("nem_id_start")
|
||||||
|
@ -161,7 +161,7 @@ class EmaneManager:
|
||||||
|
|
||||||
def get_config(
|
def get_config(
|
||||||
self, key: int, model: str, default: bool = True
|
self, key: int, model: str, default: bool = True
|
||||||
) -> Optional[dict[str, str]]:
|
) -> Optional[Dict[str, str]]:
|
||||||
"""
|
"""
|
||||||
Get the current or default configuration for an emane model.
|
Get the current or default configuration for an emane model.
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class EmaneManager:
|
||||||
config = model_class.default_values()
|
config = model_class.default_values()
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def set_config(self, key: int, model: str, config: dict[str, str] = None) -> None:
|
def set_config(self, key: int, model: str, config: Dict[str, str] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Sets and update the provided configuration against the default model
|
Sets and update the provided configuration against the default model
|
||||||
or currently set emane model configuration.
|
or currently set emane model configuration.
|
||||||
|
@ -199,7 +199,7 @@ class EmaneManager:
|
||||||
model_configs = self.node_configs.setdefault(key, {})
|
model_configs = self.node_configs.setdefault(key, {})
|
||||||
model_configs[model] = model_config
|
model_configs[model] = model_config
|
||||||
|
|
||||||
def get_model(self, model_name: str) -> type[EmaneModel]:
|
def get_model(self, model_name: str) -> Type[EmaneModel]:
|
||||||
"""
|
"""
|
||||||
Convenience method for getting globally loaded emane models.
|
Convenience method for getting globally loaded emane models.
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ class EmaneManager:
|
||||||
|
|
||||||
def get_iface_config(
|
def get_iface_config(
|
||||||
self, emane_net: EmaneNet, iface: CoreInterface
|
self, emane_net: EmaneNet, iface: CoreInterface
|
||||||
) -> dict[str, str]:
|
) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Retrieve configuration for a given interface, first checking for interface
|
Retrieve configuration for a given interface, first checking for interface
|
||||||
specific config, node specific config, network specific config, and finally
|
specific config, node specific config, network specific config, and finally
|
||||||
|
@ -260,7 +260,7 @@ class EmaneManager:
|
||||||
)
|
)
|
||||||
self._emane_nets[emane_net.id] = emane_net
|
self._emane_nets[emane_net.id] = emane_net
|
||||||
|
|
||||||
def getnodes(self) -> set[CoreNode]:
|
def getnodes(self) -> Set[CoreNode]:
|
||||||
"""
|
"""
|
||||||
Return a set of CoreNodes that are linked to an EMANE network,
|
Return a set of CoreNodes that are linked to an EMANE network,
|
||||||
e.g. containers having one or more radio interfaces.
|
e.g. containers having one or more radio interfaces.
|
||||||
|
@ -335,7 +335,7 @@ class EmaneManager:
|
||||||
self.start_daemon(iface)
|
self.start_daemon(iface)
|
||||||
self.install_iface(iface, config)
|
self.install_iface(iface, config)
|
||||||
|
|
||||||
def get_ifaces(self) -> list[tuple[EmaneNet, TunTap]]:
|
def get_ifaces(self) -> List[Tuple[EmaneNet, TunTap]]:
|
||||||
ifaces = []
|
ifaces = []
|
||||||
for emane_net in self._emane_nets.values():
|
for emane_net in self._emane_nets.values():
|
||||||
if not emane_net.wireless_model:
|
if not emane_net.wireless_model:
|
||||||
|
@ -354,7 +354,7 @@ class EmaneManager:
|
||||||
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].id))
|
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].id))
|
||||||
|
|
||||||
def setup_control_channels(
|
def setup_control_channels(
|
||||||
self, nem_id: int, iface: CoreInterface, config: dict[str, str]
|
self, nem_id: int, iface: CoreInterface, config: Dict[str, str]
|
||||||
) -> None:
|
) -> None:
|
||||||
node = iface.node
|
node = iface.node
|
||||||
# setup ota device
|
# setup ota device
|
||||||
|
@ -419,7 +419,7 @@ class EmaneManager:
|
||||||
|
|
||||||
def get_nem_position(
|
def get_nem_position(
|
||||||
self, iface: CoreInterface
|
self, iface: CoreInterface
|
||||||
) -> Optional[tuple[int, float, float, int]]:
|
) -> Optional[Tuple[int, float, float, int]]:
|
||||||
"""
|
"""
|
||||||
Retrieves nem position for a given interface.
|
Retrieves nem position for a given interface.
|
||||||
|
|
||||||
|
@ -453,7 +453,7 @@ class EmaneManager:
|
||||||
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
|
||||||
self.publish_event(nemid, event, send_all=True)
|
self.publish_event(nemid, event, send_all=True)
|
||||||
|
|
||||||
def set_nem_positions(self, moved_ifaces: list[CoreInterface]) -> None:
|
def set_nem_positions(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||||
"""
|
"""
|
||||||
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
Several NEMs have moved, from e.g. a WaypointMobilityModel
|
||||||
calculation. Generate an EMANE Location Event having several
|
calculation. Generate an EMANE Location Event having several
|
||||||
|
@ -480,7 +480,7 @@ class EmaneManager:
|
||||||
try:
|
try:
|
||||||
with path.open("a") as f:
|
with path.open("a") as f:
|
||||||
f.write(f"{iface.node.name} {iface.name} {nem_id}\n")
|
f.write(f"{iface.node.name} {iface.name} {nem_id}\n")
|
||||||
except OSError:
|
except IOError:
|
||||||
logger.exception("error writing to emane nem file")
|
logger.exception("error writing to emane nem file")
|
||||||
|
|
||||||
def links_enabled(self) -> bool:
|
def links_enabled(self) -> bool:
|
||||||
|
@ -624,7 +624,7 @@ class EmaneManager:
|
||||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||||
node.host_cmd(args, cwd=self.session.directory)
|
node.host_cmd(args, cwd=self.session.directory)
|
||||||
|
|
||||||
def install_iface(self, iface: TunTap, config: dict[str, str]) -> None:
|
def install_iface(self, iface: TunTap, config: Dict[str, str]) -> None:
|
||||||
external = config.get("external", "0")
|
external = config.get("external", "0")
|
||||||
if external == "0":
|
if external == "0":
|
||||||
iface.set_ips()
|
iface.set_ips()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
from core.emulator.enumerations import ConfigDataTypes
|
||||||
|
@ -32,7 +33,7 @@ def _type_value(config_type: str) -> ConfigDataTypes:
|
||||||
return ConfigDataTypes[config_type]
|
return ConfigDataTypes[config_type]
|
||||||
|
|
||||||
|
|
||||||
def _get_possible(config_type: str, config_regex: str) -> list[str]:
|
def _get_possible(config_type: str, config_regex: str) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve possible config value options based on emane regexes.
|
Retrieve possible config value options based on emane regexes.
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ def _get_possible(config_type: str, config_regex: str) -> list[str]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _get_default(config_type_name: str, config_value: list[str]) -> str:
|
def _get_default(config_type_name: str, config_value: List[str]) -> str:
|
||||||
"""
|
"""
|
||||||
Convert default configuration values to one used by core.
|
Convert default configuration values to one used by core.
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ def _get_default(config_type_name: str, config_value: list[str]) -> str:
|
||||||
return config_default
|
return config_default
|
||||||
|
|
||||||
|
|
||||||
def parse(manifest_path: Path, defaults: dict[str, str]) -> list[Configuration]:
|
def parse(manifest_path: Path, defaults: Dict[str, str]) -> List[Configuration]:
|
||||||
"""
|
"""
|
||||||
Parses a valid emane manifest file and converts the provided configuration values
|
Parses a valid emane manifest file and converts the provided configuration values
|
||||||
into ones used by core.
|
into ones used by core.
|
||||||
|
|
|
@ -3,7 +3,7 @@ Defines Emane Models used within CORE.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Dict, List, Optional, Set
|
||||||
|
|
||||||
from core.config import ConfigBool, ConfigGroup, ConfigString, Configuration
|
from core.config import ConfigBool, ConfigGroup, ConfigString, Configuration
|
||||||
from core.emane import emanemanifest
|
from core.emane import emanemanifest
|
||||||
|
@ -28,38 +28,38 @@ class EmaneModel(WirelessModel):
|
||||||
# default platform configuration settings
|
# default platform configuration settings
|
||||||
platform_controlport: str = "controlportendpoint"
|
platform_controlport: str = "controlportendpoint"
|
||||||
platform_xml: str = "nemmanager.xml"
|
platform_xml: str = "nemmanager.xml"
|
||||||
platform_defaults: dict[str, str] = {
|
platform_defaults: Dict[str, str] = {
|
||||||
"eventservicedevice": DEFAULT_DEV,
|
"eventservicedevice": DEFAULT_DEV,
|
||||||
"eventservicegroup": "224.1.2.8:45703",
|
"eventservicegroup": "224.1.2.8:45703",
|
||||||
"otamanagerdevice": DEFAULT_DEV,
|
"otamanagerdevice": DEFAULT_DEV,
|
||||||
"otamanagergroup": "224.1.2.8:45702",
|
"otamanagergroup": "224.1.2.8:45702",
|
||||||
}
|
}
|
||||||
platform_config: list[Configuration] = []
|
platform_config: List[Configuration] = []
|
||||||
|
|
||||||
# default mac configuration settings
|
# default mac configuration settings
|
||||||
mac_library: Optional[str] = None
|
mac_library: Optional[str] = None
|
||||||
mac_xml: Optional[str] = None
|
mac_xml: Optional[str] = None
|
||||||
mac_defaults: dict[str, str] = {}
|
mac_defaults: Dict[str, str] = {}
|
||||||
mac_config: list[Configuration] = []
|
mac_config: List[Configuration] = []
|
||||||
|
|
||||||
# default phy configuration settings, using the universal model
|
# default phy configuration settings, using the universal model
|
||||||
phy_library: Optional[str] = None
|
phy_library: Optional[str] = None
|
||||||
phy_xml: str = "emanephy.xml"
|
phy_xml: str = "emanephy.xml"
|
||||||
phy_defaults: dict[str, str] = {
|
phy_defaults: Dict[str, str] = {
|
||||||
"subid": "1",
|
"subid": "1",
|
||||||
"propagationmodel": "2ray",
|
"propagationmodel": "2ray",
|
||||||
"noisemode": "none",
|
"noisemode": "none",
|
||||||
}
|
}
|
||||||
phy_config: list[Configuration] = []
|
phy_config: List[Configuration] = []
|
||||||
|
|
||||||
# support for external configurations
|
# support for external configurations
|
||||||
external_config: list[Configuration] = [
|
external_config: List[Configuration] = [
|
||||||
ConfigBool(id="external", default="0"),
|
ConfigBool(id="external", default="0"),
|
||||||
ConfigString(id="platformendpoint", default="127.0.0.1:40001"),
|
ConfigString(id="platformendpoint", default="127.0.0.1:40001"),
|
||||||
ConfigString(id="transportendpoint", default="127.0.0.1:50002"),
|
ConfigString(id="transportendpoint", default="127.0.0.1:50002"),
|
||||||
]
|
]
|
||||||
|
|
||||||
config_ignore: set[str] = set()
|
config_ignore: Set[str] = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: Path) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
|
@ -94,7 +94,7 @@ class EmaneModel(WirelessModel):
|
||||||
cls.platform_config.pop(controlport_index)
|
cls.platform_config.pop(controlport_index)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls) -> list[Configuration]:
|
def configurations(cls) -> List[Configuration]:
|
||||||
"""
|
"""
|
||||||
Returns the combination all all configurations (mac, phy, and external).
|
Returns the combination all all configurations (mac, phy, and external).
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class EmaneModel(WirelessModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls) -> list[ConfigGroup]:
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
"""
|
"""
|
||||||
Returns the defined configuration groups.
|
Returns the defined configuration groups.
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ class EmaneModel(WirelessModel):
|
||||||
ConfigGroup("External Parameters", phy_len + 1, config_len),
|
ConfigGroup("External Parameters", phy_len + 1, config_len),
|
||||||
]
|
]
|
||||||
|
|
||||||
def build_xml_files(self, config: dict[str, str], iface: CoreInterface) -> None:
|
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Builds xml files for this emane model. Creates a nem.xml file that points to
|
Builds xml files for this emane model. Creates a nem.xml file that points to
|
||||||
both mac.xml and phy.xml definitions.
|
both mac.xml and phy.xml definitions.
|
||||||
|
@ -146,7 +146,7 @@ class EmaneModel(WirelessModel):
|
||||||
"""
|
"""
|
||||||
logger.debug("emane model(%s) has no post setup tasks", self.name)
|
logger.debug("emane model(%s) has no post setup tasks", self.name)
|
||||||
|
|
||||||
def update(self, moved_ifaces: list[CoreInterface]) -> None:
|
def update(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||||
"""
|
"""
|
||||||
Invoked from MobilityModel when nodes are moved; this causes
|
Invoked from MobilityModel when nodes are moved; this causes
|
||||||
emane location events to be generated for the nodes in the moved
|
emane location events to be generated for the nodes in the moved
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import sched
|
import sched
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
@ -34,10 +34,10 @@ NEM_SELF: int = 65535
|
||||||
|
|
||||||
|
|
||||||
class LossTable:
|
class LossTable:
|
||||||
def __init__(self, losses: dict[float, float]) -> None:
|
def __init__(self, losses: Dict[float, float]) -> None:
|
||||||
self.losses: dict[float, float] = losses
|
self.losses: Dict[float, float] = losses
|
||||||
self.sinrs: list[float] = sorted(self.losses.keys())
|
self.sinrs: List[float] = sorted(self.losses.keys())
|
||||||
self.loss_lookup: dict[int, float] = {}
|
self.loss_lookup: Dict[int, float] = {}
|
||||||
for index, value in enumerate(self.sinrs):
|
for index, value in enumerate(self.sinrs):
|
||||||
self.loss_lookup[index] = self.losses[value]
|
self.loss_lookup[index] = self.losses[value]
|
||||||
self.mac_id: Optional[str] = None
|
self.mac_id: Optional[str] = None
|
||||||
|
@ -84,7 +84,7 @@ class EmaneClient:
|
||||||
self.client: shell.ControlPortClient = shell.ControlPortClient(
|
self.client: shell.ControlPortClient = shell.ControlPortClient(
|
||||||
self.address, port
|
self.address, port
|
||||||
)
|
)
|
||||||
self.nems: dict[int, LossTable] = {}
|
self.nems: Dict[int, LossTable] = {}
|
||||||
self.setup()
|
self.setup()
|
||||||
|
|
||||||
def setup(self) -> None:
|
def setup(self) -> None:
|
||||||
|
@ -110,7 +110,7 @@ class EmaneClient:
|
||||||
self.nems[nem_id] = loss_table
|
self.nems[nem_id] = loss_table
|
||||||
|
|
||||||
def check_links(
|
def check_links(
|
||||||
self, links: dict[tuple[int, int], EmaneLink], loss_threshold: int
|
self, links: Dict[Tuple[int, int], EmaneLink], loss_threshold: int
|
||||||
) -> None:
|
) -> None:
|
||||||
for from_nem, loss_table in self.nems.items():
|
for from_nem, loss_table in self.nems.items():
|
||||||
tables = self.client.getStatisticTable(loss_table.mac_id, (SINR_TABLE,))
|
tables = self.client.getStatisticTable(loss_table.mac_id, (SINR_TABLE,))
|
||||||
|
@ -138,11 +138,11 @@ class EmaneClient:
|
||||||
link = EmaneLink(from_nem, to_nem, sinr)
|
link = EmaneLink(from_nem, to_nem, sinr)
|
||||||
links[link_key] = link
|
links[link_key] = link
|
||||||
|
|
||||||
def handle_tdma(self, config: dict[str, tuple]):
|
def handle_tdma(self, config: Dict[str, Tuple]):
|
||||||
pcr = config["pcrcurveuri"][0][0]
|
pcr = config["pcrcurveuri"][0][0]
|
||||||
logger.debug("tdma pcr: %s", pcr)
|
logger.debug("tdma pcr: %s", pcr)
|
||||||
|
|
||||||
def handle_80211(self, config: dict[str, tuple]) -> LossTable:
|
def handle_80211(self, config: Dict[str, Tuple]) -> LossTable:
|
||||||
unicastrate = config["unicastrate"][0][0]
|
unicastrate = config["unicastrate"][0][0]
|
||||||
pcr = config["pcrcurveuri"][0][0]
|
pcr = config["pcrcurveuri"][0][0]
|
||||||
logger.debug("80211 pcr: %s", pcr)
|
logger.debug("80211 pcr: %s", pcr)
|
||||||
|
@ -159,7 +159,7 @@ class EmaneClient:
|
||||||
losses[sinr] = por
|
losses[sinr] = por
|
||||||
return LossTable(losses)
|
return LossTable(losses)
|
||||||
|
|
||||||
def handle_rfpipe(self, config: dict[str, tuple]) -> LossTable:
|
def handle_rfpipe(self, config: Dict[str, Tuple]) -> LossTable:
|
||||||
pcr = config["pcrcurveuri"][0][0]
|
pcr = config["pcrcurveuri"][0][0]
|
||||||
logger.debug("rfpipe pcr: %s", pcr)
|
logger.debug("rfpipe pcr: %s", pcr)
|
||||||
tree = etree.parse(pcr)
|
tree = etree.parse(pcr)
|
||||||
|
@ -179,9 +179,9 @@ class EmaneClient:
|
||||||
class EmaneLinkMonitor:
|
class EmaneLinkMonitor:
|
||||||
def __init__(self, emane_manager: "EmaneManager") -> None:
|
def __init__(self, emane_manager: "EmaneManager") -> None:
|
||||||
self.emane_manager: "EmaneManager" = emane_manager
|
self.emane_manager: "EmaneManager" = emane_manager
|
||||||
self.clients: list[EmaneClient] = []
|
self.clients: List[EmaneClient] = []
|
||||||
self.links: dict[tuple[int, int], EmaneLink] = {}
|
self.links: Dict[Tuple[int, int], EmaneLink] = {}
|
||||||
self.complete_links: set[tuple[int, int]] = set()
|
self.complete_links: Set[Tuple[int, int]] = set()
|
||||||
self.loss_threshold: Optional[int] = None
|
self.loss_threshold: Optional[int] = None
|
||||||
self.link_interval: Optional[int] = None
|
self.link_interval: Optional[int] = None
|
||||||
self.link_timeout: Optional[int] = None
|
self.link_timeout: Optional[int] = None
|
||||||
|
@ -210,7 +210,7 @@ class EmaneLinkMonitor:
|
||||||
if client.nems:
|
if client.nems:
|
||||||
self.clients.append(client)
|
self.clients.append(client)
|
||||||
|
|
||||||
def get_addresses(self) -> list[tuple[str, int]]:
|
def get_addresses(self) -> List[Tuple[str, int]]:
|
||||||
addresses = []
|
addresses = []
|
||||||
nodes = self.emane_manager.getnodes()
|
nodes = self.emane_manager.getnodes()
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
@ -273,25 +273,25 @@ class EmaneLinkMonitor:
|
||||||
if self.running:
|
if self.running:
|
||||||
self.scheduler.enter(self.link_interval, 0, self.check_links)
|
self.scheduler.enter(self.link_interval, 0, self.check_links)
|
||||||
|
|
||||||
def get_complete_id(self, link_id: tuple[int, int]) -> tuple[int, int]:
|
def get_complete_id(self, link_id: Tuple[int, int]) -> Tuple[int, int]:
|
||||||
value1, value2 = link_id
|
value1, value2 = link_id
|
||||||
if value1 < value2:
|
if value1 < value2:
|
||||||
return value1, value2
|
return value1, value2
|
||||||
else:
|
else:
|
||||||
return value2, value1
|
return value2, value1
|
||||||
|
|
||||||
def is_complete_link(self, link_id: tuple[int, int]) -> bool:
|
def is_complete_link(self, link_id: Tuple[int, int]) -> bool:
|
||||||
reverse_id = link_id[1], link_id[0]
|
reverse_id = link_id[1], link_id[0]
|
||||||
return link_id in self.links and reverse_id in self.links
|
return link_id in self.links and reverse_id in self.links
|
||||||
|
|
||||||
def get_link_label(self, link_id: tuple[int, int]) -> str:
|
def get_link_label(self, link_id: Tuple[int, int]) -> str:
|
||||||
source_id = tuple(sorted(link_id))
|
source_id = tuple(sorted(link_id))
|
||||||
source_link = self.links[source_id]
|
source_link = self.links[source_id]
|
||||||
dest_id = link_id[::-1]
|
dest_id = link_id[::-1]
|
||||||
dest_link = self.links[dest_id]
|
dest_link = self.links[dest_id]
|
||||||
return f"{source_link.sinr:.1f} / {dest_link.sinr:.1f}"
|
return f"{source_link.sinr:.1f} / {dest_link.sinr:.1f}"
|
||||||
|
|
||||||
def send_link(self, message_type: MessageFlags, link_id: tuple[int, int]) -> None:
|
def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None:
|
||||||
nem1, nem2 = link_id
|
nem1, nem2 = link_id
|
||||||
link = self.emane_manager.get_nem_link(nem1, nem2, message_type)
|
link = self.emane_manager.get_nem_link(nem1, nem2, message_type)
|
||||||
if link:
|
if link:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import pkgutil
|
import pkgutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.emane import models as emane_models
|
from core.emane import models as emane_models
|
||||||
|
@ -11,10 +12,10 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EmaneModelManager:
|
class EmaneModelManager:
|
||||||
models: dict[str, type[EmaneModel]] = {}
|
models: Dict[str, Type[EmaneModel]] = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_locals(cls, emane_prefix: Path) -> list[str]:
|
def load_locals(cls, emane_prefix: Path) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Load local core emane models and make them available.
|
Load local core emane models and make them available.
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ class EmaneModelManager:
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, path: Path, emane_prefix: Path) -> list[str]:
|
def load(cls, path: Path, emane_prefix: Path) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Search and load custom emane models and make them available.
|
Search and load custom emane models and make them available.
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class EmaneModelManager:
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, name: str) -> type[EmaneModel]:
|
def get(cls, name: str) -> Type[EmaneModel]:
|
||||||
model = cls.models.get(name)
|
model = cls.models.get(name)
|
||||||
if model is None:
|
if model is None:
|
||||||
raise CoreError(f"emame model does not exist {name}")
|
raise CoreError(f"emame model does not exist {name}")
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
EMANE Bypass model for CORE
|
EMANE Bypass model for CORE
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List, Set
|
||||||
|
|
||||||
from core.config import ConfigBool, Configuration
|
from core.config import ConfigBool, Configuration
|
||||||
from core.emane import emanemodel
|
from core.emane import emanemodel
|
||||||
|
@ -11,11 +12,11 @@ class EmaneBypassModel(emanemodel.EmaneModel):
|
||||||
name: str = "emane_bypass"
|
name: str = "emane_bypass"
|
||||||
|
|
||||||
# values to ignore, when writing xml files
|
# values to ignore, when writing xml files
|
||||||
config_ignore: set[str] = {"none"}
|
config_ignore: Set[str] = {"none"}
|
||||||
|
|
||||||
# mac definitions
|
# mac definitions
|
||||||
mac_library: str = "bypassmaclayer"
|
mac_library: str = "bypassmaclayer"
|
||||||
mac_config: list[Configuration] = [
|
mac_config: List[Configuration] = [
|
||||||
ConfigBool(
|
ConfigBool(
|
||||||
id="none",
|
id="none",
|
||||||
default="0",
|
default="0",
|
||||||
|
@ -25,7 +26,7 @@ class EmaneBypassModel(emanemodel.EmaneModel):
|
||||||
|
|
||||||
# phy definitions
|
# phy definitions
|
||||||
phy_library: str = "bypassphylayer"
|
phy_library: str = "bypassphylayer"
|
||||||
phy_config: list[Configuration] = []
|
phy_config: List[Configuration] = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: Path) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
|
|
|
@ -4,6 +4,7 @@ commeffect.py: EMANE CommEffect model for CORE
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
@ -41,12 +42,12 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
name: str = "emane_commeffect"
|
name: str = "emane_commeffect"
|
||||||
shim_library: str = "commeffectshim"
|
shim_library: str = "commeffectshim"
|
||||||
shim_xml: str = "commeffectshim.xml"
|
shim_xml: str = "commeffectshim.xml"
|
||||||
shim_defaults: dict[str, str] = {}
|
shim_defaults: Dict[str, str] = {}
|
||||||
config_shim: list[Configuration] = []
|
config_shim: List[Configuration] = []
|
||||||
|
|
||||||
# comm effect does not need the default phy and external configurations
|
# comm effect does not need the default phy and external configurations
|
||||||
phy_config: list[Configuration] = []
|
phy_config: List[Configuration] = []
|
||||||
external_config: list[Configuration] = []
|
external_config: List[Configuration] = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: Path) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
|
@ -55,11 +56,11 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurations(cls) -> list[Configuration]:
|
def configurations(cls) -> List[Configuration]:
|
||||||
return cls.platform_config + cls.config_shim
|
return cls.platform_config + cls.config_shim
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls) -> list[ConfigGroup]:
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
platform_len = len(cls.platform_config)
|
platform_len = len(cls.platform_config)
|
||||||
return [
|
return [
|
||||||
ConfigGroup("Platform Parameters", 1, platform_len),
|
ConfigGroup("Platform Parameters", 1, platform_len),
|
||||||
|
@ -70,7 +71,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def build_xml_files(self, config: dict[str, str], iface: CoreInterface) -> None:
|
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
|
||||||
"""
|
"""
|
||||||
Build the necessary nem and commeffect XMLs in the given path.
|
Build the necessary nem and commeffect XMLs in the given path.
|
||||||
If an individual NEM has a nonstandard config, we need to build
|
If an individual NEM has a nonstandard config, we need to build
|
||||||
|
|
|
@ -4,6 +4,7 @@ tdma.py: EMANE TDMA model bindings for CORE
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
from core import constants, utils
|
from core import constants, utils
|
||||||
from core.config import ConfigString
|
from core.config import ConfigString
|
||||||
|
@ -27,7 +28,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
|
||||||
default_schedule: Path = (
|
default_schedule: Path = (
|
||||||
constants.CORE_DATA_DIR / "examples" / "tdma" / "schedule.xml"
|
constants.CORE_DATA_DIR / "examples" / "tdma" / "schedule.xml"
|
||||||
)
|
)
|
||||||
config_ignore: set[str] = {schedule_name}
|
config_ignore: Set[str] = {schedule_name}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: Path) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
|
|
|
@ -6,11 +6,11 @@ share the same MAC+PHY model.
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type, Union
|
||||||
|
|
||||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||||
from core.emulator.distributed import DistributedServer
|
from core.emulator.distributed import DistributedServer
|
||||||
from core.emulator.enumerations import MessageFlags, RegisterTlvs
|
from core.emulator.enumerations import EventTypes, MessageFlags, RegisterTlvs
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.nodes.base import CoreNetworkBase, CoreNode, NodeOptions
|
from core.nodes.base import CoreNetworkBase, CoreNode, NodeOptions
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
|
@ -167,7 +167,7 @@ class EmaneNet(CoreNetworkBase):
|
||||||
self.mobility: Optional[WayPointMobility] = None
|
self.mobility: Optional[WayPointMobility] = None
|
||||||
model_class = self.session.emane.get_model(options.emane_model)
|
model_class = self.session.emane.get_model(options.emane_model)
|
||||||
self.wireless_model: Optional["EmaneModel"] = model_class(self.session, self.id)
|
self.wireless_model: Optional["EmaneModel"] = model_class(self.session, self.id)
|
||||||
if self.session.is_running():
|
if self.session.state == EventTypes.RUNTIME_STATE:
|
||||||
self.session.emane.add_node(self)
|
self.session.emane.add_node(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -196,7 +196,7 @@ class EmaneNet(CoreNetworkBase):
|
||||||
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def updatemodel(self, config: dict[str, str]) -> None:
|
def updatemodel(self, config: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Update configuration for the current model.
|
Update configuration for the current model.
|
||||||
|
|
||||||
|
@ -212,8 +212,8 @@ class EmaneNet(CoreNetworkBase):
|
||||||
|
|
||||||
def setmodel(
|
def setmodel(
|
||||||
self,
|
self,
|
||||||
model: Union[type["EmaneModel"], type["WayPointMobility"]],
|
model: Union[Type["EmaneModel"], Type["WayPointMobility"]],
|
||||||
config: dict[str, str],
|
config: Dict[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
set the EmaneModel associated with this node
|
set the EmaneModel associated with this node
|
||||||
|
@ -225,7 +225,7 @@ class EmaneNet(CoreNetworkBase):
|
||||||
self.mobility = model(session=self.session, _id=self.id)
|
self.mobility = model(session=self.session, _id=self.id)
|
||||||
self.mobility.update_config(config)
|
self.mobility.update_config(config)
|
||||||
|
|
||||||
def links(self, flags: MessageFlags = MessageFlags.NONE) -> list[LinkData]:
|
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||||
links = []
|
links = []
|
||||||
emane_manager = self.session.emane
|
emane_manager = self.session.emane
|
||||||
# gather current emane links
|
# gather current emane links
|
||||||
|
@ -280,7 +280,7 @@ class EmaneNet(CoreNetworkBase):
|
||||||
self.attach(iface)
|
self.attach(iface)
|
||||||
if self.up:
|
if self.up:
|
||||||
iface.startup()
|
iface.startup()
|
||||||
if self.session.is_running():
|
if self.session.state == EventTypes.RUNTIME_STATE:
|
||||||
self.session.emane.start_iface(self, iface)
|
self.session.emane.start_iface(self, iface)
|
||||||
return iface
|
return iface
|
||||||
|
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import TypeVar, Union
|
|
||||||
|
|
||||||
from core.emulator.data import (
|
|
||||||
ConfigData,
|
|
||||||
EventData,
|
|
||||||
ExceptionData,
|
|
||||||
FileData,
|
|
||||||
LinkData,
|
|
||||||
NodeData,
|
|
||||||
)
|
|
||||||
from core.errors import CoreError
|
|
||||||
|
|
||||||
T = TypeVar(
|
|
||||||
"T", bound=Union[EventData, ExceptionData, NodeData, LinkData, FileData, ConfigData]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BroadcastManager:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""
|
|
||||||
Creates a BroadcastManager instance.
|
|
||||||
"""
|
|
||||||
self.handlers: dict[type[T], set[Callable[[T], None]]] = {}
|
|
||||||
|
|
||||||
def send(self, data: T) -> None:
|
|
||||||
"""
|
|
||||||
Retrieve handlers for data, and run all current handlers.
|
|
||||||
|
|
||||||
:param data: data to provide to handlers
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
handlers = self.handlers.get(type(data), set())
|
|
||||||
for handler in handlers:
|
|
||||||
handler(data)
|
|
||||||
|
|
||||||
def add_handler(self, data_type: type[T], handler: Callable[[T], None]) -> None:
|
|
||||||
"""
|
|
||||||
Add a handler for a given data type.
|
|
||||||
|
|
||||||
:param data_type: type of data to add handler for
|
|
||||||
:param handler: handler to add
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
handlers = self.handlers.setdefault(data_type, set())
|
|
||||||
if handler in handlers:
|
|
||||||
raise CoreError(
|
|
||||||
f"cannot add data({data_type}) handler({repr(handler)}), "
|
|
||||||
f"already exists"
|
|
||||||
)
|
|
||||||
handlers.add(handler)
|
|
||||||
|
|
||||||
def remove_handler(self, data_type: type[T], handler: Callable[[T], None]) -> None:
|
|
||||||
"""
|
|
||||||
Remove a handler for a given data type.
|
|
||||||
|
|
||||||
:param data_type: type of data to remove handler for
|
|
||||||
:param handler: handler to remove
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
handlers = self.handlers.get(data_type, set())
|
|
||||||
if handler not in handlers:
|
|
||||||
raise CoreError(
|
|
||||||
f"cannot remove data({data_type}) handler({repr(handler)}), "
|
|
||||||
f"does not exist"
|
|
||||||
)
|
|
||||||
handlers.remove(handler)
|
|
|
@ -1,239 +0,0 @@
|
||||||
import logging
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
|
||||||
|
|
||||||
from core import utils
|
|
||||||
from core.emulator.data import InterfaceData
|
|
||||||
from core.errors import CoreError
|
|
||||||
from core.nodes.base import CoreNode
|
|
||||||
from core.nodes.interface import DEFAULT_MTU
|
|
||||||
from core.nodes.network import CtrlNet
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from core.emulator.session import Session
|
|
||||||
|
|
||||||
CTRL_NET_ID: int = 9001
|
|
||||||
ETC_HOSTS_PATH: str = "/etc/hosts"
|
|
||||||
|
|
||||||
|
|
||||||
class ControlNetManager:
|
|
||||||
def __init__(self, session: "Session") -> None:
|
|
||||||
self.session: "Session" = session
|
|
||||||
self.etc_hosts_header: str = f"CORE session {self.session.id} host entries"
|
|
||||||
|
|
||||||
def _etc_hosts_enabled(self) -> bool:
|
|
||||||
"""
|
|
||||||
Determines if /etc/hosts should be configured.
|
|
||||||
|
|
||||||
:return: True if /etc/hosts should be configured, False otherwise
|
|
||||||
"""
|
|
||||||
return self.session.options.get_bool("update_etc_hosts", False)
|
|
||||||
|
|
||||||
def _get_server_ifaces(
|
|
||||||
self,
|
|
||||||
) -> tuple[None, Optional[str], Optional[str], Optional[str]]:
|
|
||||||
"""
|
|
||||||
Retrieve control net server interfaces.
|
|
||||||
|
|
||||||
:return: control net server interfaces
|
|
||||||
"""
|
|
||||||
d0 = self.session.options.get("controlnetif0")
|
|
||||||
if d0:
|
|
||||||
logger.error("controlnet0 cannot be assigned with a host interface")
|
|
||||||
d1 = self.session.options.get("controlnetif1")
|
|
||||||
d2 = self.session.options.get("controlnetif2")
|
|
||||||
d3 = self.session.options.get("controlnetif3")
|
|
||||||
return None, d1, d2, d3
|
|
||||||
|
|
||||||
def _get_prefixes(
|
|
||||||
self,
|
|
||||||
) -> tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
|
|
||||||
"""
|
|
||||||
Retrieve control net prefixes.
|
|
||||||
|
|
||||||
:return: control net prefixes
|
|
||||||
"""
|
|
||||||
p = self.session.options.get("controlnet")
|
|
||||||
p0 = self.session.options.get("controlnet0")
|
|
||||||
p1 = self.session.options.get("controlnet1")
|
|
||||||
p2 = self.session.options.get("controlnet2")
|
|
||||||
p3 = self.session.options.get("controlnet3")
|
|
||||||
if not p0 and p:
|
|
||||||
p0 = p
|
|
||||||
return p0, p1, p2, p3
|
|
||||||
|
|
||||||
def update_etc_hosts(self) -> None:
|
|
||||||
"""
|
|
||||||
Add the IP addresses of control interfaces to the /etc/hosts file.
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
if not self._etc_hosts_enabled():
|
|
||||||
return
|
|
||||||
control_net = self.get_control_net(0)
|
|
||||||
entries = ""
|
|
||||||
for iface in control_net.get_ifaces():
|
|
||||||
name = iface.node.name
|
|
||||||
for ip in iface.ips():
|
|
||||||
entries += f"{ip.ip} {name}\n"
|
|
||||||
logger.info("adding entries to /etc/hosts")
|
|
||||||
utils.file_munge(ETC_HOSTS_PATH, self.etc_hosts_header, entries)
|
|
||||||
|
|
||||||
def clear_etc_hosts(self) -> None:
|
|
||||||
"""
|
|
||||||
Clear IP addresses of control interfaces from the /etc/hosts file.
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
if not self._etc_hosts_enabled():
|
|
||||||
return
|
|
||||||
logger.info("removing /etc/hosts file entries")
|
|
||||||
utils.file_demunge(ETC_HOSTS_PATH, self.etc_hosts_header)
|
|
||||||
|
|
||||||
def get_control_net_index(self, dev: str) -> int:
|
|
||||||
"""
|
|
||||||
Retrieve control net index.
|
|
||||||
|
|
||||||
:param dev: device to get control net index for
|
|
||||||
:return: control net index, -1 otherwise
|
|
||||||
"""
|
|
||||||
if dev[0:4] == "ctrl" and int(dev[4]) in (0, 1, 2, 3):
|
|
||||||
index = int(dev[4])
|
|
||||||
if index == 0:
|
|
||||||
return index
|
|
||||||
if index < 4 and self._get_prefixes()[index] is not None:
|
|
||||||
return index
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def get_control_net(self, index: int) -> Optional[CtrlNet]:
|
|
||||||
"""
|
|
||||||
Retrieve a control net based on index.
|
|
||||||
|
|
||||||
:param index: control net index
|
|
||||||
:return: control net when available, None otherwise
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.session.get_node(CTRL_NET_ID + index, CtrlNet)
|
|
||||||
except CoreError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def add_control_net(
|
|
||||||
self, index: int, conf_required: bool = True
|
|
||||||
) -> Optional[CtrlNet]:
|
|
||||||
"""
|
|
||||||
Create a control network bridge as necessary. The conf_reqd flag,
|
|
||||||
when False, causes a control network bridge to be added even if
|
|
||||||
one has not been configured.
|
|
||||||
|
|
||||||
:param index: network index to add
|
|
||||||
:param conf_required: flag to check if conf is required
|
|
||||||
:return: control net node
|
|
||||||
"""
|
|
||||||
logger.info(
|
|
||||||
"checking to add control net index(%s) conf_required(%s)",
|
|
||||||
index,
|
|
||||||
conf_required,
|
|
||||||
)
|
|
||||||
# check for valid index
|
|
||||||
if not (0 <= index <= 3):
|
|
||||||
raise CoreError(f"invalid control net index({index})")
|
|
||||||
# return any existing control net bridge
|
|
||||||
control_net = self.get_control_net(index)
|
|
||||||
if control_net:
|
|
||||||
logger.info("control net index(%s) already exists", index)
|
|
||||||
return control_net
|
|
||||||
# retrieve prefix for current index
|
|
||||||
index_prefix = self._get_prefixes()[index]
|
|
||||||
if not index_prefix:
|
|
||||||
if conf_required:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
index_prefix = CtrlNet.DEFAULT_PREFIX_LIST[index]
|
|
||||||
# retrieve valid prefix from old style values
|
|
||||||
prefixes = index_prefix.split()
|
|
||||||
if len(prefixes) > 1:
|
|
||||||
# a list of per-host prefixes is provided
|
|
||||||
try:
|
|
||||||
prefix = prefixes[0].split(":", 1)[1]
|
|
||||||
except IndexError:
|
|
||||||
prefix = prefixes[0]
|
|
||||||
else:
|
|
||||||
prefix = prefixes[0]
|
|
||||||
# use the updown script for control net 0 only
|
|
||||||
updown_script = None
|
|
||||||
if index == 0:
|
|
||||||
updown_script = self.session.options.get("controlnet_updown_script")
|
|
||||||
# build a new controlnet bridge
|
|
||||||
_id = CTRL_NET_ID + index
|
|
||||||
server_iface = self._get_server_ifaces()[index]
|
|
||||||
logger.info(
|
|
||||||
"adding controlnet(%s) prefix(%s) updown(%s) server interface(%s)",
|
|
||||||
_id,
|
|
||||||
prefix,
|
|
||||||
updown_script,
|
|
||||||
server_iface,
|
|
||||||
)
|
|
||||||
options = CtrlNet.create_options()
|
|
||||||
options.prefix = prefix
|
|
||||||
options.updown_script = updown_script
|
|
||||||
options.serverintf = server_iface
|
|
||||||
control_net = self.session.create_node(CtrlNet, False, _id, options=options)
|
|
||||||
control_net.brname = f"ctrl{index}.{self.session.short_session_id()}"
|
|
||||||
control_net.startup()
|
|
||||||
return control_net
|
|
||||||
|
|
||||||
def remove_control_net(self, index: int) -> None:
|
|
||||||
"""
|
|
||||||
Removes control net.
|
|
||||||
|
|
||||||
:param index: index of control net to remove
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
control_net = self.get_control_net(index)
|
|
||||||
if control_net:
|
|
||||||
logger.info("removing control net index(%s)", index)
|
|
||||||
self.session.delete_node(control_net.id)
|
|
||||||
|
|
||||||
def add_control_iface(self, node: CoreNode, index: int) -> None:
|
|
||||||
"""
|
|
||||||
Adds a control net interface to a node.
|
|
||||||
|
|
||||||
:param node: node to add control net interface to
|
|
||||||
:param index: index of control net to add interface to
|
|
||||||
:return: nothing
|
|
||||||
:raises CoreError: if control net doesn't exist, interface already exists,
|
|
||||||
or there is an error creating the interface
|
|
||||||
"""
|
|
||||||
control_net = self.get_control_net(index)
|
|
||||||
if not control_net:
|
|
||||||
raise CoreError(f"control net index({index}) does not exist")
|
|
||||||
iface_id = control_net.CTRLIF_IDX_BASE + index
|
|
||||||
if node.ifaces.get(iface_id):
|
|
||||||
raise CoreError(f"control iface({iface_id}) already exists")
|
|
||||||
try:
|
|
||||||
logger.info(
|
|
||||||
"node(%s) adding control net index(%s) interface(%s)",
|
|
||||||
node.name,
|
|
||||||
index,
|
|
||||||
iface_id,
|
|
||||||
)
|
|
||||||
ip4 = control_net.prefix[node.id]
|
|
||||||
ip4_mask = control_net.prefix.prefixlen
|
|
||||||
iface_data = InterfaceData(
|
|
||||||
id=iface_id,
|
|
||||||
name=f"ctrl{index}",
|
|
||||||
mac=utils.random_mac(),
|
|
||||||
ip4=ip4,
|
|
||||||
ip4_mask=ip4_mask,
|
|
||||||
mtu=DEFAULT_MTU,
|
|
||||||
)
|
|
||||||
iface = node.create_iface(iface_data)
|
|
||||||
control_net.attach(iface)
|
|
||||||
iface.control = True
|
|
||||||
except ValueError:
|
|
||||||
raise CoreError(
|
|
||||||
f"error adding control net interface to node({node.id}), "
|
|
||||||
f"invalid control net prefix({control_net.prefix}), "
|
|
||||||
"a longer prefix length may be required"
|
|
||||||
)
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.configservice.manager import ConfigServiceManager
|
from core.configservice.manager import ConfigServiceManager
|
||||||
|
@ -14,12 +18,31 @@ logger = logging.getLogger(__name__)
|
||||||
DEFAULT_EMANE_PREFIX: str = "/usr"
|
DEFAULT_EMANE_PREFIX: str = "/usr"
|
||||||
|
|
||||||
|
|
||||||
|
def signal_handler(signal_number: int, _) -> None:
|
||||||
|
"""
|
||||||
|
Handle signals and force an exit with cleanup.
|
||||||
|
|
||||||
|
:param signal_number: signal number
|
||||||
|
:param _: ignored
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
logger.info("caught signal: %s", signal_number)
|
||||||
|
sys.exit(signal_number)
|
||||||
|
|
||||||
|
|
||||||
|
signal.signal(signal.SIGHUP, signal_handler)
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
signal.signal(signal.SIGUSR1, signal_handler)
|
||||||
|
signal.signal(signal.SIGUSR2, signal_handler)
|
||||||
|
|
||||||
|
|
||||||
class CoreEmu:
|
class CoreEmu:
|
||||||
"""
|
"""
|
||||||
Provides logic for creating and configuring CORE sessions and the nodes within them.
|
Provides logic for creating and configuring CORE sessions and the nodes within them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: dict[str, str] = None) -> None:
|
def __init__(self, config: Dict[str, str] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Create a CoreEmu object.
|
Create a CoreEmu object.
|
||||||
|
|
||||||
|
@ -30,13 +53,13 @@ class CoreEmu:
|
||||||
|
|
||||||
# configuration
|
# configuration
|
||||||
config = config if config else {}
|
config = config if config else {}
|
||||||
self.config: dict[str, str] = config
|
self.config: Dict[str, str] = config
|
||||||
|
|
||||||
# session management
|
# session management
|
||||||
self.sessions: dict[int, Session] = {}
|
self.sessions: Dict[int, Session] = {}
|
||||||
|
|
||||||
# load services
|
# load services
|
||||||
self.service_errors: list[str] = []
|
self.service_errors: List[str] = []
|
||||||
self.service_manager: ConfigServiceManager = ConfigServiceManager()
|
self.service_manager: ConfigServiceManager = ConfigServiceManager()
|
||||||
self._load_services()
|
self._load_services()
|
||||||
|
|
||||||
|
@ -47,6 +70,9 @@ class CoreEmu:
|
||||||
# check executables exist on path
|
# check executables exist on path
|
||||||
self._validate_env()
|
self._validate_env()
|
||||||
|
|
||||||
|
# catch exit event
|
||||||
|
atexit.register(self.shutdown)
|
||||||
|
|
||||||
def _validate_env(self) -> None:
|
def _validate_env(self) -> None:
|
||||||
"""
|
"""
|
||||||
Validates executables CORE depends on exist on path.
|
Validates executables CORE depends on exist on path.
|
||||||
|
@ -118,7 +144,7 @@ class CoreEmu:
|
||||||
_, session = self.sessions.popitem()
|
_, session = self.sessions.popitem()
|
||||||
session.shutdown()
|
session.shutdown()
|
||||||
|
|
||||||
def create_session(self, _id: int = None, _cls: type[Session] = Session) -> Session:
|
def create_session(self, _id: int = None, _cls: Type[Session] = Session) -> Session:
|
||||||
"""
|
"""
|
||||||
Create a new CORE session.
|
Create a new CORE session.
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
CORE data objects.
|
CORE data objects.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class ConfigData:
|
||||||
node: int = None
|
node: int = None
|
||||||
object: str = None
|
object: str = None
|
||||||
type: int = None
|
type: int = None
|
||||||
data_types: tuple[int] = None
|
data_types: Tuple[int] = None
|
||||||
data_values: str = None
|
data_values: str = None
|
||||||
captions: str = None
|
captions: str = None
|
||||||
bitmap: str = None
|
bitmap: str = None
|
||||||
|
@ -81,8 +81,8 @@ class NodeOptions:
|
||||||
model: Optional[str] = "PC"
|
model: Optional[str] = "PC"
|
||||||
canvas: int = None
|
canvas: int = None
|
||||||
icon: str = None
|
icon: str = None
|
||||||
services: list[str] = field(default_factory=list)
|
services: List[str] = field(default_factory=list)
|
||||||
config_services: list[str] = field(default_factory=list)
|
config_services: List[str] = field(default_factory=list)
|
||||||
x: float = None
|
x: float = None
|
||||||
y: float = None
|
y: float = None
|
||||||
lat: float = None
|
lat: float = None
|
||||||
|
@ -93,9 +93,9 @@ class NodeOptions:
|
||||||
emane: str = None
|
emane: str = None
|
||||||
legacy: bool = False
|
legacy: bool = False
|
||||||
# src, dst
|
# src, dst
|
||||||
binds: list[tuple[str, str]] = field(default_factory=list)
|
binds: List[Tuple[str, str]] = field(default_factory=list)
|
||||||
# src, dst, unique, delete
|
# src, dst, unique, delete
|
||||||
volumes: list[tuple[str, str, bool, bool]] = field(default_factory=list)
|
volumes: List[Tuple[str, str, bool, bool]] = field(default_factory=list)
|
||||||
|
|
||||||
def set_position(self, x: float, y: float) -> None:
|
def set_position(self, x: float, y: float) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -148,7 +148,7 @@ class InterfaceData:
|
||||||
ip6_mask: int = None
|
ip6_mask: int = None
|
||||||
mtu: int = None
|
mtu: int = None
|
||||||
|
|
||||||
def get_ips(self) -> list[str]:
|
def get_ips(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Returns a list of ip4 and ip6 addresses when present.
|
Returns a list of ip4 and ip6 addresses when present.
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING, Callable, Dict, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from fabric import Connection
|
from fabric import Connection
|
||||||
|
@ -48,7 +48,7 @@ class DistributedServer:
|
||||||
self.lock: threading.Lock = threading.Lock()
|
self.lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
def remote_cmd(
|
def remote_cmd(
|
||||||
self, cmd: str, env: dict[str, str] = None, cwd: str = None, wait: bool = True
|
self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Run command remotely using server connection.
|
Run command remotely using server connection.
|
||||||
|
@ -105,7 +105,7 @@ class DistributedServer:
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(data.encode())
|
temp.write(data.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
self.conn.put(temp.name, str(dst_path))
|
self.conn.put(temp.name, str(dst_path))
|
||||||
os.unlink(temp.name)
|
os.unlink(temp.name)
|
||||||
|
@ -123,8 +123,8 @@ class DistributedController:
|
||||||
:param session: session
|
:param session: session
|
||||||
"""
|
"""
|
||||||
self.session: "Session" = session
|
self.session: "Session" = session
|
||||||
self.servers: dict[str, DistributedServer] = OrderedDict()
|
self.servers: Dict[str, DistributedServer] = OrderedDict()
|
||||||
self.tunnels: dict[int, tuple[GreTap, GreTap]] = {}
|
self.tunnels: Dict[int, Tuple[GreTap, GreTap]] = {}
|
||||||
self.address: str = self.session.options.get("distributed_address")
|
self.address: str = self.session.options.get("distributed_address")
|
||||||
|
|
||||||
def add_server(self, name: str, host: str) -> None:
|
def add_server(self, name: str, host: str) -> None:
|
||||||
|
@ -187,7 +187,8 @@ class DistributedController:
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
mtu = self.session.options.get_int("mtu")
|
mtu = self.session.options.get_int("mtu")
|
||||||
for node in self.session.nodes.values():
|
for node_id in self.session.nodes:
|
||||||
|
node = self.session.nodes[node_id]
|
||||||
if not isinstance(node, CtrlNet) or node.serverintf is not None:
|
if not isinstance(node, CtrlNet) or node.serverintf is not None:
|
||||||
continue
|
continue
|
||||||
for name in self.servers:
|
for name in self.servers:
|
||||||
|
@ -213,7 +214,7 @@ class DistributedController:
|
||||||
|
|
||||||
def create_gre_tunnel(
|
def create_gre_tunnel(
|
||||||
self, node: CoreNetwork, server: DistributedServer, mtu: int, start: bool
|
self, node: CoreNetwork, server: DistributedServer, mtu: int, start: bool
|
||||||
) -> tuple[GreTap, GreTap]:
|
) -> Tuple[GreTap, GreTap]:
|
||||||
"""
|
"""
|
||||||
Create gre tunnel using a pair of gre taps between the local and remote server.
|
Create gre tunnel using a pair of gre taps between the local and remote server.
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,6 @@ class NodeTypes(Enum):
|
||||||
DOCKER = 15
|
DOCKER = 15
|
||||||
LXC = 16
|
LXC = 16
|
||||||
WIRELESS = 17
|
WIRELESS = 17
|
||||||
PODMAN = 18
|
|
||||||
|
|
||||||
|
|
||||||
class LinkTypes(Enum):
|
class LinkTypes(Enum):
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
from collections.abc import Callable
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from core.emulator.enumerations import EventTypes
|
|
||||||
from core.errors import CoreError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class HookManager:
|
|
||||||
"""
|
|
||||||
Provides functionality for managing and running script/callback hooks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""
|
|
||||||
Create a HookManager instance.
|
|
||||||
"""
|
|
||||||
self.script_hooks: dict[EventTypes, dict[str, str]] = {}
|
|
||||||
self.callback_hooks: dict[EventTypes, list[Callable[[], None]]] = {}
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
|
||||||
"""
|
|
||||||
Clear all current hooks.
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
self.script_hooks.clear()
|
|
||||||
self.callback_hooks.clear()
|
|
||||||
|
|
||||||
def add_script_hook(self, state: EventTypes, file_name: str, data: str) -> None:
|
|
||||||
"""
|
|
||||||
Add a hook script to run for a given state.
|
|
||||||
|
|
||||||
:param state: state to run hook on
|
|
||||||
:param file_name: hook file name
|
|
||||||
:param data: file data
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
logger.info("setting state hook: %s - %s", state, file_name)
|
|
||||||
state_hooks = self.script_hooks.setdefault(state, {})
|
|
||||||
if file_name in state_hooks:
|
|
||||||
raise CoreError(
|
|
||||||
f"adding duplicate state({state.name}) hook script({file_name})"
|
|
||||||
)
|
|
||||||
state_hooks[file_name] = data
|
|
||||||
|
|
||||||
def delete_script_hook(self, state: EventTypes, file_name: str) -> None:
|
|
||||||
"""
|
|
||||||
Delete a script hook from a given state.
|
|
||||||
|
|
||||||
:param state: state to delete script hook from
|
|
||||||
:param file_name: name of script to delete
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
state_hooks = self.script_hooks.get(state, {})
|
|
||||||
if file_name not in state_hooks:
|
|
||||||
raise CoreError(
|
|
||||||
f"deleting state({state.name}) hook script({file_name}) "
|
|
||||||
"that does not exist"
|
|
||||||
)
|
|
||||||
del state_hooks[file_name]
|
|
||||||
|
|
||||||
def add_callback_hook(
|
|
||||||
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Add a hook callback to run for a state.
|
|
||||||
|
|
||||||
:param state: state to add hook for
|
|
||||||
:param hook: callback to run
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
hooks = self.callback_hooks.setdefault(state, [])
|
|
||||||
if hook in hooks:
|
|
||||||
name = getattr(callable, "__name__", repr(hook))
|
|
||||||
raise CoreError(
|
|
||||||
f"adding duplicate state({state.name}) hook callback({name})"
|
|
||||||
)
|
|
||||||
hooks.append(hook)
|
|
||||||
|
|
||||||
def delete_callback_hook(
|
|
||||||
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Delete a state hook.
|
|
||||||
|
|
||||||
:param state: state to delete hook for
|
|
||||||
:param hook: hook to delete
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
hooks = self.callback_hooks.get(state, [])
|
|
||||||
if hook not in hooks:
|
|
||||||
name = getattr(callable, "__name__", repr(hook))
|
|
||||||
raise CoreError(
|
|
||||||
f"deleting state({state.name}) hook callback({name}) "
|
|
||||||
"that does not exist"
|
|
||||||
)
|
|
||||||
hooks.remove(hook)
|
|
||||||
|
|
||||||
def run_hooks(
|
|
||||||
self, state: EventTypes, directory: Path, env: dict[str, str]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Run all hooks for the current state.
|
|
||||||
|
|
||||||
:param state: state to run hooks for
|
|
||||||
:param directory: directory to run script hooks within
|
|
||||||
:param env: environment to run script hooks with
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
for state_hooks in self.script_hooks.get(state, {}):
|
|
||||||
for file_name, data in state_hooks.items():
|
|
||||||
logger.info("running hook %s", file_name)
|
|
||||||
file_path = directory / file_name
|
|
||||||
log_path = directory / f"{file_name}.log"
|
|
||||||
try:
|
|
||||||
with file_path.open("w") as f:
|
|
||||||
f.write(data)
|
|
||||||
with log_path.open("w") as f:
|
|
||||||
args = ["/bin/sh", file_name]
|
|
||||||
subprocess.check_call(
|
|
||||||
args,
|
|
||||||
stdout=f,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
close_fds=True,
|
|
||||||
cwd=directory,
|
|
||||||
env=env,
|
|
||||||
)
|
|
||||||
except (OSError, subprocess.CalledProcessError) as e:
|
|
||||||
raise CoreError(
|
|
||||||
f"failure running state({state.name}) "
|
|
||||||
f"hook script({file_name}): {e}"
|
|
||||||
)
|
|
||||||
for hook in self.callback_hooks.get(state, []):
|
|
||||||
try:
|
|
||||||
hook()
|
|
||||||
except Exception as e:
|
|
||||||
name = getattr(callable, "__name__", repr(hook))
|
|
||||||
raise CoreError(
|
|
||||||
f"failure running state({state.name}) "
|
|
||||||
f"hook callback({name}): {e}"
|
|
||||||
)
|
|
|
@ -4,9 +4,8 @@ for a session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import ValuesView
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Dict, Optional, Tuple, ValuesView
|
||||||
|
|
||||||
from core.emulator.data import LinkData, LinkOptions
|
from core.emulator.data import LinkData, LinkOptions
|
||||||
from core.emulator.enumerations import LinkTypes, MessageFlags
|
from core.emulator.enumerations import LinkTypes, MessageFlags
|
||||||
|
@ -16,7 +15,7 @@ from core.nodes.interface import CoreInterface
|
||||||
from core.nodes.network import PtpNet
|
from core.nodes.network import PtpNet
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
LinkKeyType = tuple[int, Optional[int], int, Optional[int]]
|
LinkKeyType = Tuple[int, Optional[int], int, Optional[int]]
|
||||||
|
|
||||||
|
|
||||||
def create_key(
|
def create_key(
|
||||||
|
@ -146,8 +145,8 @@ class LinkManager:
|
||||||
"""
|
"""
|
||||||
Create a LinkManager instance.
|
Create a LinkManager instance.
|
||||||
"""
|
"""
|
||||||
self._links: dict[LinkKeyType, CoreLink] = {}
|
self._links: Dict[LinkKeyType, CoreLink] = {}
|
||||||
self._node_links: dict[int, dict[LinkKeyType, CoreLink]] = {}
|
self._node_links: Dict[int, Dict[LinkKeyType, CoreLink]] = {}
|
||||||
|
|
||||||
def add(self, core_link: CoreLink) -> None:
|
def add(self, core_link: CoreLink) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -14,7 +14,7 @@ import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Optional, TypeVar, Union
|
from typing import Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union
|
||||||
|
|
||||||
from core import constants, utils
|
from core import constants, utils
|
||||||
from core.configservice.manager import ConfigServiceManager
|
from core.configservice.manager import ConfigServiceManager
|
||||||
|
@ -57,7 +57,6 @@ from core.nodes.network import (
|
||||||
WlanNode,
|
WlanNode,
|
||||||
)
|
)
|
||||||
from core.nodes.physical import PhysicalNode, Rj45Node
|
from core.nodes.physical import PhysicalNode, Rj45Node
|
||||||
from core.nodes.podman import PodmanNode
|
|
||||||
from core.nodes.wireless import WirelessNode
|
from core.nodes.wireless import WirelessNode
|
||||||
from core.plugins.sdt import Sdt
|
from core.plugins.sdt import Sdt
|
||||||
from core.services.coreservices import CoreServices
|
from core.services.coreservices import CoreServices
|
||||||
|
@ -67,7 +66,7 @@ from core.xml.corexml import CoreXmlReader, CoreXmlWriter
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# maps for converting from API call node type values to classes and vice versa
|
# maps for converting from API call node type values to classes and vice versa
|
||||||
NODES: dict[NodeTypes, type[NodeBase]] = {
|
NODES: Dict[NodeTypes, Type[NodeBase]] = {
|
||||||
NodeTypes.DEFAULT: CoreNode,
|
NodeTypes.DEFAULT: CoreNode,
|
||||||
NodeTypes.PHYSICAL: PhysicalNode,
|
NodeTypes.PHYSICAL: PhysicalNode,
|
||||||
NodeTypes.SWITCH: SwitchNode,
|
NodeTypes.SWITCH: SwitchNode,
|
||||||
|
@ -82,13 +81,13 @@ NODES: dict[NodeTypes, type[NodeBase]] = {
|
||||||
NodeTypes.DOCKER: DockerNode,
|
NodeTypes.DOCKER: DockerNode,
|
||||||
NodeTypes.LXC: LxcNode,
|
NodeTypes.LXC: LxcNode,
|
||||||
NodeTypes.WIRELESS: WirelessNode,
|
NodeTypes.WIRELESS: WirelessNode,
|
||||||
NodeTypes.PODMAN: PodmanNode,
|
|
||||||
}
|
}
|
||||||
NODES_TYPE: dict[type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES}
|
NODES_TYPE: Dict[Type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES}
|
||||||
|
CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode}
|
||||||
CTRL_NET_ID: int = 9001
|
CTRL_NET_ID: int = 9001
|
||||||
LINK_COLORS: list[str] = ["green", "blue", "orange", "purple", "turquoise"]
|
LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"]
|
||||||
NT: TypeVar = TypeVar("NT", bound=NodeBase)
|
NT: TypeVar = TypeVar("NT", bound=NodeBase)
|
||||||
WIRELESS_TYPE: tuple[type[WlanNode], type[EmaneNet], type[WirelessNode]] = (
|
WIRELESS_TYPE: Tuple[Type[WlanNode], Type[EmaneNet], Type[WirelessNode]] = (
|
||||||
WlanNode,
|
WlanNode,
|
||||||
EmaneNet,
|
EmaneNet,
|
||||||
WirelessNode,
|
WirelessNode,
|
||||||
|
@ -101,7 +100,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, _id: int, config: dict[str, str] = None, mkdir: bool = True
|
self, _id: int, config: Dict[str, str] = None, mkdir: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a Session instance.
|
Create a Session instance.
|
||||||
|
@ -122,33 +121,33 @@ class Session:
|
||||||
self.thumbnail: Optional[Path] = None
|
self.thumbnail: Optional[Path] = None
|
||||||
self.user: Optional[str] = None
|
self.user: Optional[str] = None
|
||||||
self.event_loop: EventLoop = EventLoop()
|
self.event_loop: EventLoop = EventLoop()
|
||||||
self.link_colors: dict[int, str] = {}
|
self.link_colors: Dict[int, str] = {}
|
||||||
|
|
||||||
# dict of nodes: all nodes and nets
|
# dict of nodes: all nodes and nets
|
||||||
self.nodes: dict[int, NodeBase] = {}
|
self.nodes: Dict[int, NodeBase] = {}
|
||||||
self.nodes_lock: threading.Lock = threading.Lock()
|
self.nodes_lock: threading.Lock = threading.Lock()
|
||||||
self.link_manager: LinkManager = LinkManager()
|
self.link_manager: LinkManager = LinkManager()
|
||||||
|
|
||||||
# states and hooks handlers
|
# states and hooks handlers
|
||||||
self.state: EventTypes = EventTypes.DEFINITION_STATE
|
self.state: EventTypes = EventTypes.DEFINITION_STATE
|
||||||
self.state_time: float = time.monotonic()
|
self.state_time: float = time.monotonic()
|
||||||
self.hooks: dict[EventTypes, list[tuple[str, str]]] = {}
|
self.hooks: Dict[EventTypes, List[Tuple[str, str]]] = {}
|
||||||
self.state_hooks: dict[EventTypes, list[Callable[[EventTypes], None]]] = {}
|
self.state_hooks: Dict[EventTypes, List[Callable[[EventTypes], None]]] = {}
|
||||||
self.add_state_hook(
|
self.add_state_hook(
|
||||||
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
|
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
|
||||||
)
|
)
|
||||||
|
|
||||||
# handlers for broadcasting information
|
# handlers for broadcasting information
|
||||||
self.event_handlers: list[Callable[[EventData], None]] = []
|
self.event_handlers: List[Callable[[EventData], None]] = []
|
||||||
self.exception_handlers: list[Callable[[ExceptionData], None]] = []
|
self.exception_handlers: List[Callable[[ExceptionData], None]] = []
|
||||||
self.node_handlers: list[Callable[[NodeData], None]] = []
|
self.node_handlers: List[Callable[[NodeData], None]] = []
|
||||||
self.link_handlers: list[Callable[[LinkData], None]] = []
|
self.link_handlers: List[Callable[[LinkData], None]] = []
|
||||||
self.file_handlers: list[Callable[[FileData], None]] = []
|
self.file_handlers: List[Callable[[FileData], None]] = []
|
||||||
self.config_handlers: list[Callable[[ConfigData], None]] = []
|
self.config_handlers: List[Callable[[ConfigData], None]] = []
|
||||||
|
|
||||||
# session options/metadata
|
# session options/metadata
|
||||||
self.options: SessionConfig = SessionConfig(config)
|
self.options: SessionConfig = SessionConfig(config)
|
||||||
self.metadata: dict[str, str] = {}
|
self.metadata: Dict[str, str] = {}
|
||||||
|
|
||||||
# distributed support and logic
|
# distributed support and logic
|
||||||
self.distributed: DistributedController = DistributedController(self)
|
self.distributed: DistributedController = DistributedController(self)
|
||||||
|
@ -164,7 +163,7 @@ class Session:
|
||||||
self.service_manager: Optional[ConfigServiceManager] = None
|
self.service_manager: Optional[ConfigServiceManager] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_class(cls, _type: NodeTypes) -> type[NodeBase]:
|
def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]:
|
||||||
"""
|
"""
|
||||||
Retrieve the class for a given node type.
|
Retrieve the class for a given node type.
|
||||||
|
|
||||||
|
@ -177,7 +176,7 @@ class Session:
|
||||||
return node_class
|
return node_class
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_type(cls, _class: type[NodeBase]) -> NodeTypes:
|
def get_node_type(cls, _class: Type[NodeBase]) -> NodeTypes:
|
||||||
"""
|
"""
|
||||||
Retrieve node type for a given node class.
|
Retrieve node type for a given node class.
|
||||||
|
|
||||||
|
@ -239,7 +238,7 @@ class Session:
|
||||||
iface1_data: InterfaceData = None,
|
iface1_data: InterfaceData = None,
|
||||||
iface2_data: InterfaceData = None,
|
iface2_data: InterfaceData = None,
|
||||||
options: LinkOptions = None,
|
options: LinkOptions = None,
|
||||||
) -> tuple[Optional[CoreInterface], Optional[CoreInterface]]:
|
) -> Tuple[Optional[CoreInterface], Optional[CoreInterface]]:
|
||||||
"""
|
"""
|
||||||
Add a link between nodes.
|
Add a link between nodes.
|
||||||
|
|
||||||
|
@ -346,7 +345,7 @@ class Session:
|
||||||
iface1_data: InterfaceData = None,
|
iface1_data: InterfaceData = None,
|
||||||
iface2_data: InterfaceData = None,
|
iface2_data: InterfaceData = None,
|
||||||
options: LinkOptions = None,
|
options: LinkOptions = None,
|
||||||
) -> tuple[CoreInterface, CoreInterface]:
|
) -> Tuple[CoreInterface, CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Create a wired link between two nodes.
|
Create a wired link between two nodes.
|
||||||
|
|
||||||
|
@ -477,7 +476,7 @@ class Session:
|
||||||
|
|
||||||
def add_node(
|
def add_node(
|
||||||
self,
|
self,
|
||||||
_class: type[NT],
|
_class: Type[NT],
|
||||||
_id: int = None,
|
_id: int = None,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
server: str = None,
|
server: str = None,
|
||||||
|
@ -519,9 +518,10 @@ class Session:
|
||||||
self.set_node_pos(node, position.x, position.y)
|
self.set_node_pos(node, position.x, position.y)
|
||||||
# setup default wlan
|
# setup default wlan
|
||||||
if isinstance(node, WlanNode):
|
if isinstance(node, WlanNode):
|
||||||
self.mobility.set_model_config(node.id, BasicRangeModel.name)
|
self.mobility.set_model_config(self.id, BasicRangeModel.name)
|
||||||
# boot core nodes after runtime
|
# boot core nodes after runtime
|
||||||
if self.is_running() and isinstance(node, CoreNode):
|
is_runtime = self.state == EventTypes.RUNTIME_STATE
|
||||||
|
if is_runtime and isinstance(node, CoreNode):
|
||||||
self.add_remove_control_iface(node, remove=False)
|
self.add_remove_control_iface(node, remove=False)
|
||||||
self.boot_node(node)
|
self.boot_node(node)
|
||||||
self.sdt.add_node(node)
|
self.sdt.add_node(node)
|
||||||
|
@ -746,7 +746,7 @@ class Session:
|
||||||
for hook in hooks:
|
for hook in hooks:
|
||||||
self.run_hook(hook)
|
self.run_hook(hook)
|
||||||
|
|
||||||
def run_hook(self, hook: tuple[str, str]) -> None:
|
def run_hook(self, hook: Tuple[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Run a hook.
|
Run a hook.
|
||||||
|
|
||||||
|
@ -770,7 +770,7 @@ class Session:
|
||||||
cwd=self.directory,
|
cwd=self.directory,
|
||||||
env=self.get_environment(),
|
env=self.get_environment(),
|
||||||
)
|
)
|
||||||
except (OSError, subprocess.CalledProcessError):
|
except (IOError, subprocess.CalledProcessError):
|
||||||
logger.exception("error running hook: %s", file_path)
|
logger.exception("error running hook: %s", file_path)
|
||||||
|
|
||||||
def run_state_hooks(self, state: EventTypes) -> None:
|
def run_state_hooks(self, state: EventTypes) -> None:
|
||||||
|
@ -836,7 +836,7 @@ class Session:
|
||||||
xml_file_path = self.directory / "session-deployed.xml"
|
xml_file_path = self.directory / "session-deployed.xml"
|
||||||
xml_writer.write(xml_file_path)
|
xml_writer.write(xml_file_path)
|
||||||
|
|
||||||
def get_environment(self, state: bool = True) -> dict[str, str]:
|
def get_environment(self, state: bool = True) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Get an environment suitable for a subprocess.Popen call.
|
Get an environment suitable for a subprocess.Popen call.
|
||||||
This is the current process environment with some session-specific
|
This is the current process environment with some session-specific
|
||||||
|
@ -871,7 +871,7 @@ class Session:
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
try:
|
try:
|
||||||
utils.load_config(path, env)
|
utils.load_config(path, env)
|
||||||
except OSError:
|
except IOError:
|
||||||
logger.exception("error reading environment file: %s", path)
|
logger.exception("error reading environment file: %s", path)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
@ -888,12 +888,12 @@ class Session:
|
||||||
uid = pwd.getpwnam(user).pw_uid
|
uid = pwd.getpwnam(user).pw_uid
|
||||||
gid = self.directory.stat().st_gid
|
gid = self.directory.stat().st_gid
|
||||||
os.chown(self.directory, uid, gid)
|
os.chown(self.directory, uid, gid)
|
||||||
except OSError:
|
except IOError:
|
||||||
logger.exception("failed to set permission on %s", self.directory)
|
logger.exception("failed to set permission on %s", self.directory)
|
||||||
|
|
||||||
def create_node(
|
def create_node(
|
||||||
self,
|
self,
|
||||||
_class: type[NT],
|
_class: Type[NT],
|
||||||
start: bool,
|
start: bool,
|
||||||
_id: int = None,
|
_id: int = None,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
|
@ -929,7 +929,7 @@ class Session:
|
||||||
node.startup()
|
node.startup()
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def get_node(self, _id: int, _class: type[NT]) -> NT:
|
def get_node(self, _id: int, _class: Type[NT]) -> NT:
|
||||||
"""
|
"""
|
||||||
Get a session node.
|
Get a session node.
|
||||||
|
|
||||||
|
@ -1003,7 +1003,7 @@ class Session:
|
||||||
)
|
)
|
||||||
self.broadcast_exception(exception_data)
|
self.broadcast_exception(exception_data)
|
||||||
|
|
||||||
def instantiate(self) -> list[Exception]:
|
def instantiate(self) -> List[Exception]:
|
||||||
"""
|
"""
|
||||||
We have entered the instantiation state, invoke startup methods
|
We have entered the instantiation state, invoke startup methods
|
||||||
of various managers and boot the nodes. Validate nodes and check
|
of various managers and boot the nodes. Validate nodes and check
|
||||||
|
@ -1011,7 +1011,7 @@ class Session:
|
||||||
|
|
||||||
:return: list of service boot errors during startup
|
:return: list of service boot errors during startup
|
||||||
"""
|
"""
|
||||||
if self.is_running():
|
if self.state == EventTypes.RUNTIME_STATE:
|
||||||
logger.warning("ignoring instantiate, already in runtime state")
|
logger.warning("ignoring instantiate, already in runtime state")
|
||||||
return []
|
return []
|
||||||
# create control net interfaces and network tunnels
|
# create control net interfaces and network tunnels
|
||||||
|
@ -1083,7 +1083,6 @@ class Session:
|
||||||
if isinstance(node, CoreNodeBase) and node.up:
|
if isinstance(node, CoreNodeBase) and node.up:
|
||||||
args = (node,)
|
args = (node,)
|
||||||
funcs.append((self.services.stop_services, args, {}))
|
funcs.append((self.services.stop_services, args, {}))
|
||||||
funcs.append((node.stop_config_services, (), {}))
|
|
||||||
utils.threadpool(funcs)
|
utils.threadpool(funcs)
|
||||||
|
|
||||||
# shutdown emane
|
# shutdown emane
|
||||||
|
@ -1114,16 +1113,11 @@ class Session:
|
||||||
:param node: node to boot
|
:param node: node to boot
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.info(
|
logger.info("booting node(%s): %s", node.name, [x.name for x in node.services])
|
||||||
"booting node(%s): config services(%s) services(%s)",
|
|
||||||
node.name,
|
|
||||||
", ".join(node.config_services.keys()),
|
|
||||||
", ".join(x.name for x in node.services),
|
|
||||||
)
|
|
||||||
self.services.boot_services(node)
|
self.services.boot_services(node)
|
||||||
node.start_config_services()
|
node.start_config_services()
|
||||||
|
|
||||||
def boot_nodes(self) -> list[Exception]:
|
def boot_nodes(self) -> List[Exception]:
|
||||||
"""
|
"""
|
||||||
Invoke the boot() procedure for all nodes and send back node
|
Invoke the boot() procedure for all nodes and send back node
|
||||||
messages to the GUI for node messages that had the status
|
messages to the GUI for node messages that had the status
|
||||||
|
@ -1145,7 +1139,7 @@ class Session:
|
||||||
self.update_control_iface_hosts()
|
self.update_control_iface_hosts()
|
||||||
return exceptions
|
return exceptions
|
||||||
|
|
||||||
def get_control_net_prefixes(self) -> list[str]:
|
def get_control_net_prefixes(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve control net prefixes.
|
Retrieve control net prefixes.
|
||||||
|
|
||||||
|
@ -1160,7 +1154,7 @@ class Session:
|
||||||
p0 = p
|
p0 = p
|
||||||
return [p0, p1, p2, p3]
|
return [p0, p1, p2, p3]
|
||||||
|
|
||||||
def get_control_net_server_ifaces(self) -> list[str]:
|
def get_control_net_server_ifaces(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve control net server interfaces.
|
Retrieve control net server interfaces.
|
||||||
|
|
||||||
|
@ -1366,7 +1360,7 @@ class Session:
|
||||||
Return the current time we have been in the runtime state, or zero
|
Return the current time we have been in the runtime state, or zero
|
||||||
if not in runtime.
|
if not in runtime.
|
||||||
"""
|
"""
|
||||||
if self.is_running():
|
if self.state == EventTypes.RUNTIME_STATE:
|
||||||
return time.monotonic() - self.state_time
|
return time.monotonic() - self.state_time
|
||||||
else:
|
else:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
@ -1443,11 +1437,3 @@ class Session:
|
||||||
color = LINK_COLORS[index]
|
color = LINK_COLORS[index]
|
||||||
self.link_colors[network_id] = color
|
self.link_colors[network_id] = color
|
||||||
return color
|
return color
|
||||||
|
|
||||||
def is_running(self) -> bool:
|
|
||||||
"""
|
|
||||||
Convenience for checking if this session is in the runtime state.
|
|
||||||
|
|
||||||
:return: True if in the runtime state, False otherwise
|
|
||||||
"""
|
|
||||||
return self.state == EventTypes.RUNTIME_STATE
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from core.config import ConfigBool, ConfigInt, ConfigString, Configuration
|
from core.config import ConfigBool, ConfigInt, ConfigString, Configuration
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
|
@ -10,7 +10,7 @@ class SessionConfig:
|
||||||
Provides session configuration.
|
Provides session configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
options: list[Configuration] = [
|
options: List[Configuration] = [
|
||||||
ConfigString(id="controlnet", label="Control Network"),
|
ConfigString(id="controlnet", label="Control Network"),
|
||||||
ConfigString(id="controlnet0", label="Control Network 0"),
|
ConfigString(id="controlnet0", label="Control Network 0"),
|
||||||
ConfigString(id="controlnet1", label="Control Network 1"),
|
ConfigString(id="controlnet1", label="Control Network 1"),
|
||||||
|
@ -35,16 +35,16 @@ class SessionConfig:
|
||||||
ConfigInt(id="mtu", default="0", label="MTU for All Devices"),
|
ConfigInt(id="mtu", default="0", label="MTU for All Devices"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, config: dict[str, str] = None) -> None:
|
def __init__(self, config: Dict[str, str] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Create a SessionConfig instance.
|
Create a SessionConfig instance.
|
||||||
|
|
||||||
:param config: configuration to initialize with
|
:param config: configuration to initialize with
|
||||||
"""
|
"""
|
||||||
self._config: dict[str, str] = {x.id: x.default for x in self.options}
|
self._config: Dict[str, str] = {x.id: x.default for x in self.options}
|
||||||
self._config.update(config or {})
|
self._config.update(config or {})
|
||||||
|
|
||||||
def update(self, config: dict[str, str]) -> None:
|
def update(self, config: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Update current configuration with provided values.
|
Update current configuration with provided values.
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class SessionConfig:
|
||||||
"""
|
"""
|
||||||
return self._config.get(name, default)
|
return self._config.get(name, default)
|
||||||
|
|
||||||
def all(self) -> dict[str, str]:
|
def all(self) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Retrieve all configuration options.
|
Retrieve all configuration options.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
BASH: str = "bash"
|
BASH: str = "bash"
|
||||||
ETHTOOL: str = "ethtool"
|
ETHTOOL: str = "ethtool"
|
||||||
IP: str = "ip"
|
IP: str = "ip"
|
||||||
|
@ -11,7 +13,7 @@ UMOUNT: str = "umount"
|
||||||
VCMD: str = "vcmd"
|
VCMD: str = "vcmd"
|
||||||
VNODED: str = "vnoded"
|
VNODED: str = "vnoded"
|
||||||
|
|
||||||
COMMON_REQUIREMENTS: list[str] = [
|
COMMON_REQUIREMENTS: List[str] = [
|
||||||
BASH,
|
BASH,
|
||||||
ETHTOOL,
|
ETHTOOL,
|
||||||
IP,
|
IP,
|
||||||
|
@ -24,10 +26,10 @@ COMMON_REQUIREMENTS: list[str] = [
|
||||||
VCMD,
|
VCMD,
|
||||||
VNODED,
|
VNODED,
|
||||||
]
|
]
|
||||||
OVS_REQUIREMENTS: list[str] = [OVS_VSCTL]
|
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]
|
||||||
|
|
||||||
|
|
||||||
def get_requirements(use_ovs: bool) -> list[str]:
|
def get_requirements(use_ovs: bool) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve executable requirements needed to run CORE.
|
Retrieve executable requirements needed to run CORE.
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import math
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import PhotoImage, font, messagebox, ttk
|
from tkinter import PhotoImage, font, messagebox, ttk
|
||||||
from tkinter.ttk import Progressbar
|
from tkinter.ttk import Progressbar
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, Optional, Type
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class Application(ttk.Frame):
|
||||||
self.show_infobar: tk.BooleanVar = tk.BooleanVar(value=False)
|
self.show_infobar: tk.BooleanVar = tk.BooleanVar(value=False)
|
||||||
|
|
||||||
# fonts
|
# fonts
|
||||||
self.fonts_size: dict[str, int] = {}
|
self.fonts_size: Dict[str, int] = {}
|
||||||
self.icon_text_font: Optional[font.Font] = None
|
self.icon_text_font: Optional[font.Font] = None
|
||||||
self.edge_font: Optional[font.Font] = None
|
self.edge_font: Optional[font.Font] = None
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class Application(ttk.Frame):
|
||||||
self.statusbar = StatusBar(self.right_frame, self)
|
self.statusbar = StatusBar(self.right_frame, self)
|
||||||
self.statusbar.grid(sticky=tk.EW, columnspan=2)
|
self.statusbar.grid(sticky=tk.EW, columnspan=2)
|
||||||
|
|
||||||
def display_info(self, frame_class: type[InfoFrameBase], **kwargs: Any) -> None:
|
def display_info(self, frame_class: Type[InfoFrameBase], **kwargs: Any) -> None:
|
||||||
if not self.show_infobar.get():
|
if not self.show_infobar.get():
|
||||||
return
|
return
|
||||||
self.clear_info()
|
self.clear_info()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ LOCAL_XMLS_PATH: Path = DATA_PATH.joinpath("xmls").absolute()
|
||||||
LOCAL_MOBILITY_PATH: Path = DATA_PATH.joinpath("mobility").absolute()
|
LOCAL_MOBILITY_PATH: Path = DATA_PATH.joinpath("mobility").absolute()
|
||||||
|
|
||||||
# configuration data
|
# configuration data
|
||||||
TERMINALS: dict[str, str] = {
|
TERMINALS: Dict[str, str] = {
|
||||||
"xterm": "xterm -e",
|
"xterm": "xterm -e",
|
||||||
"aterm": "aterm -e",
|
"aterm": "aterm -e",
|
||||||
"eterm": "eterm -e",
|
"eterm": "eterm -e",
|
||||||
|
@ -36,7 +36,7 @@ TERMINALS: dict[str, str] = {
|
||||||
"xfce4-terminal": "xfce4-terminal -x",
|
"xfce4-terminal": "xfce4-terminal -x",
|
||||||
"gnome-terminal": "gnome-terminal --window --",
|
"gnome-terminal": "gnome-terminal --window --",
|
||||||
}
|
}
|
||||||
EDITORS: list[str] = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"]
|
EDITORS: List[str] = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"]
|
||||||
|
|
||||||
|
|
||||||
class IndentDumper(yaml.Dumper):
|
class IndentDumper(yaml.Dumper):
|
||||||
|
@ -46,17 +46,17 @@ class IndentDumper(yaml.Dumper):
|
||||||
|
|
||||||
class CustomNode(yaml.YAMLObject):
|
class CustomNode(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!CustomNode"
|
yaml_tag: str = "!CustomNode"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(self, name: str, image: str, services: list[str]) -> None:
|
def __init__(self, name: str, image: str, services: List[str]) -> None:
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.image: str = image
|
self.image: str = image
|
||||||
self.services: list[str] = services
|
self.services: List[str] = services
|
||||||
|
|
||||||
|
|
||||||
class CoreServer(yaml.YAMLObject):
|
class CoreServer(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!CoreServer"
|
yaml_tag: str = "!CoreServer"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(self, name: str, address: str) -> None:
|
def __init__(self, name: str, address: str) -> None:
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
@ -65,7 +65,7 @@ class CoreServer(yaml.YAMLObject):
|
||||||
|
|
||||||
class Observer(yaml.YAMLObject):
|
class Observer(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!Observer"
|
yaml_tag: str = "!Observer"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(self, name: str, cmd: str) -> None:
|
def __init__(self, name: str, cmd: str) -> None:
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
@ -74,7 +74,7 @@ class Observer(yaml.YAMLObject):
|
||||||
|
|
||||||
class PreferencesConfig(yaml.YAMLObject):
|
class PreferencesConfig(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!PreferencesConfig"
|
yaml_tag: str = "!PreferencesConfig"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -95,7 +95,7 @@ class PreferencesConfig(yaml.YAMLObject):
|
||||||
|
|
||||||
class LocationConfig(yaml.YAMLObject):
|
class LocationConfig(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!LocationConfig"
|
yaml_tag: str = "!LocationConfig"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -118,17 +118,17 @@ class LocationConfig(yaml.YAMLObject):
|
||||||
|
|
||||||
class IpConfigs(yaml.YAMLObject):
|
class IpConfigs(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!IpConfigs"
|
yaml_tag: str = "!IpConfigs"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
self.__setstate__(kwargs)
|
self.__setstate__(kwargs)
|
||||||
|
|
||||||
def __setstate__(self, kwargs):
|
def __setstate__(self, kwargs):
|
||||||
self.ip4s: list[str] = kwargs.get(
|
self.ip4s: List[str] = kwargs.get(
|
||||||
"ip4s", ["10.0.0.0", "192.168.0.0", "172.16.0.0"]
|
"ip4s", ["10.0.0.0", "192.168.0.0", "172.16.0.0"]
|
||||||
)
|
)
|
||||||
self.ip4: str = kwargs.get("ip4", self.ip4s[0])
|
self.ip4: str = kwargs.get("ip4", self.ip4s[0])
|
||||||
self.ip6s: list[str] = kwargs.get("ip6s", ["2001::", "2002::", "a::"])
|
self.ip6s: List[str] = kwargs.get("ip6s", ["2001::", "2002::", "a::"])
|
||||||
self.ip6: str = kwargs.get("ip6", self.ip6s[0])
|
self.ip6: str = kwargs.get("ip6", self.ip6s[0])
|
||||||
self.enable_ip4: bool = kwargs.get("enable_ip4", True)
|
self.enable_ip4: bool = kwargs.get("enable_ip4", True)
|
||||||
self.enable_ip6: bool = kwargs.get("enable_ip6", True)
|
self.enable_ip6: bool = kwargs.get("enable_ip6", True)
|
||||||
|
@ -136,16 +136,16 @@ class IpConfigs(yaml.YAMLObject):
|
||||||
|
|
||||||
class GuiConfig(yaml.YAMLObject):
|
class GuiConfig(yaml.YAMLObject):
|
||||||
yaml_tag: str = "!GuiConfig"
|
yaml_tag: str = "!GuiConfig"
|
||||||
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
|
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
preferences: PreferencesConfig = None,
|
preferences: PreferencesConfig = None,
|
||||||
location: LocationConfig = None,
|
location: LocationConfig = None,
|
||||||
servers: list[CoreServer] = None,
|
servers: List[CoreServer] = None,
|
||||||
nodes: list[CustomNode] = None,
|
nodes: List[CustomNode] = None,
|
||||||
recentfiles: list[str] = None,
|
recentfiles: List[str] = None,
|
||||||
observers: list[Observer] = None,
|
observers: List[Observer] = None,
|
||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
ips: IpConfigs = None,
|
ips: IpConfigs = None,
|
||||||
mac: str = "00:00:00:aa:00:00",
|
mac: str = "00:00:00:aa:00:00",
|
||||||
|
@ -158,16 +158,16 @@ class GuiConfig(yaml.YAMLObject):
|
||||||
self.location: LocationConfig = location
|
self.location: LocationConfig = location
|
||||||
if servers is None:
|
if servers is None:
|
||||||
servers = []
|
servers = []
|
||||||
self.servers: list[CoreServer] = servers
|
self.servers: List[CoreServer] = servers
|
||||||
if nodes is None:
|
if nodes is None:
|
||||||
nodes = []
|
nodes = []
|
||||||
self.nodes: list[CustomNode] = nodes
|
self.nodes: List[CustomNode] = nodes
|
||||||
if recentfiles is None:
|
if recentfiles is None:
|
||||||
recentfiles = []
|
recentfiles = []
|
||||||
self.recentfiles: list[str] = recentfiles
|
self.recentfiles: List[str] = recentfiles
|
||||||
if observers is None:
|
if observers is None:
|
||||||
observers = []
|
observers = []
|
||||||
self.observers: list[Observer] = observers
|
self.observers: List[Observer] = observers
|
||||||
self.scale: float = scale
|
self.scale: float = scale
|
||||||
if ips is None:
|
if ips is None:
|
||||||
ips = IpConfigs()
|
ips = IpConfigs()
|
||||||
|
|
|
@ -6,10 +6,9 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from collections.abc import Iterable
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -17,7 +16,6 @@ from core.api.grpc import client, configservices_pb2, core_pb2
|
||||||
from core.api.grpc.wrappers import (
|
from core.api.grpc.wrappers import (
|
||||||
ConfigOption,
|
ConfigOption,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
ConfigServiceDefaults,
|
|
||||||
EmaneModelConfig,
|
EmaneModelConfig,
|
||||||
Event,
|
Event,
|
||||||
ExceptionEvent,
|
ExceptionEvent,
|
||||||
|
@ -57,7 +55,7 @@ GUI_SOURCE = "gui"
|
||||||
CPU_USAGE_DELAY = 3
|
CPU_USAGE_DELAY = 3
|
||||||
|
|
||||||
|
|
||||||
def to_dict(config: dict[str, ConfigOption]) -> dict[str, str]:
|
def to_dict(config: Dict[str, ConfigOption]) -> Dict[str, str]:
|
||||||
return {x: y.value for x, y in config.items()}
|
return {x: y.value for x, y in config.items()}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,30 +70,27 @@ class CoreClient:
|
||||||
self.session: Optional[Session] = None
|
self.session: Optional[Session] = None
|
||||||
self.user = getpass.getuser()
|
self.user = getpass.getuser()
|
||||||
|
|
||||||
# menu options
|
|
||||||
self.show_throughputs: tk.BooleanVar = tk.BooleanVar(value=False)
|
|
||||||
|
|
||||||
# global service settings
|
# global service settings
|
||||||
self.services: dict[str, set[str]] = {}
|
self.services: Dict[str, Set[str]] = {}
|
||||||
self.config_services_groups: dict[str, set[str]] = {}
|
self.config_services_groups: Dict[str, Set[str]] = {}
|
||||||
self.config_services: dict[str, ConfigService] = {}
|
self.config_services: Dict[str, ConfigService] = {}
|
||||||
|
|
||||||
# loaded configuration data
|
# loaded configuration data
|
||||||
self.emane_models: list[str] = []
|
self.emane_models: List[str] = []
|
||||||
self.servers: dict[str, CoreServer] = {}
|
self.servers: Dict[str, CoreServer] = {}
|
||||||
self.custom_nodes: dict[str, NodeDraw] = {}
|
self.custom_nodes: Dict[str, NodeDraw] = {}
|
||||||
self.custom_observers: dict[str, Observer] = {}
|
self.custom_observers: Dict[str, Observer] = {}
|
||||||
self.read_config()
|
self.read_config()
|
||||||
|
|
||||||
# helpers
|
# helpers
|
||||||
self.iface_to_edge: dict[tuple[int, ...], CanvasEdge] = {}
|
self.iface_to_edge: Dict[Tuple[int, ...], CanvasEdge] = {}
|
||||||
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
|
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
|
||||||
self.observer: Optional[str] = None
|
self.observer: Optional[str] = None
|
||||||
|
|
||||||
# session data
|
# session data
|
||||||
self.mobility_players: dict[int, MobilityPlayer] = {}
|
self.mobility_players: Dict[int, MobilityPlayer] = {}
|
||||||
self.canvas_nodes: dict[int, CanvasNode] = {}
|
self.canvas_nodes: Dict[int, CanvasNode] = {}
|
||||||
self.links: dict[str, CanvasEdge] = {}
|
self.links: Dict[str, CanvasEdge] = {}
|
||||||
self.handling_throughputs: Optional[grpc.Future] = None
|
self.handling_throughputs: Optional[grpc.Future] = None
|
||||||
self.handling_cpu_usage: Optional[grpc.Future] = None
|
self.handling_cpu_usage: Optional[grpc.Future] = None
|
||||||
self.handling_events: Optional[grpc.Future] = None
|
self.handling_events: Optional[grpc.Future] = None
|
||||||
|
@ -247,10 +242,9 @@ class CoreClient:
|
||||||
logger.warning("unknown node event: %s", event)
|
logger.warning("unknown node event: %s", event)
|
||||||
|
|
||||||
def enable_throughputs(self) -> None:
|
def enable_throughputs(self) -> None:
|
||||||
if not self.handling_throughputs:
|
self.handling_throughputs = self.client.throughputs(
|
||||||
self.handling_throughputs = self.client.throughputs(
|
self.session.id, self.handle_throughputs
|
||||||
self.session.id, self.handle_throughputs
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def cancel_throughputs(self) -> None:
|
def cancel_throughputs(self) -> None:
|
||||||
if self.handling_throughputs:
|
if self.handling_throughputs:
|
||||||
|
@ -374,7 +368,7 @@ class CoreClient:
|
||||||
# existing session
|
# existing session
|
||||||
sessions = self.client.get_sessions()
|
sessions = self.client.get_sessions()
|
||||||
if session_id:
|
if session_id:
|
||||||
session_ids = {x.id for x in sessions}
|
session_ids = set(x.id for x in sessions)
|
||||||
if session_id not in session_ids:
|
if session_id not in session_ids:
|
||||||
self.app.show_error(
|
self.app.show_error(
|
||||||
"Join Session Error",
|
"Join Session Error",
|
||||||
|
@ -403,7 +397,7 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.show_grpc_exception("Edit Node Error", e)
|
self.app.show_grpc_exception("Edit Node Error", e)
|
||||||
|
|
||||||
def get_links(self, definition: bool = False) -> list[Link]:
|
def get_links(self, definition: bool = False) -> List[Link]:
|
||||||
if not definition:
|
if not definition:
|
||||||
self.ifaces_manager.set_macs([x.link for x in self.links.values()])
|
self.ifaces_manager.set_macs([x.link for x in self.links.values()])
|
||||||
links = []
|
links = []
|
||||||
|
@ -421,7 +415,7 @@ class CoreClient:
|
||||||
links.append(edge.asymmetric_link)
|
links.append(edge.asymmetric_link)
|
||||||
return links
|
return links
|
||||||
|
|
||||||
def start_session(self, definition: bool = False) -> tuple[bool, list[str]]:
|
def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]:
|
||||||
self.session.links = self.get_links(definition)
|
self.session.links = self.get_links(definition)
|
||||||
self.session.metadata = self.get_metadata()
|
self.session.metadata = self.get_metadata()
|
||||||
self.session.servers.clear()
|
self.session.servers.clear()
|
||||||
|
@ -437,15 +431,13 @@ class CoreClient:
|
||||||
definition,
|
definition,
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
if self.show_throughputs.get():
|
|
||||||
self.enable_throughputs()
|
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.show_grpc_exception("Start Session Error", e)
|
self.app.show_grpc_exception("Start Session Error", e)
|
||||||
return result, exceptions
|
return result, exceptions
|
||||||
|
|
||||||
def stop_session(self, session_id: int = None) -> bool:
|
def stop_session(self, session_id: int = None) -> bool:
|
||||||
session_id = session_id or self.session.id
|
if not session_id:
|
||||||
self.cancel_throughputs()
|
session_id = self.session.id
|
||||||
result = False
|
result = False
|
||||||
try:
|
try:
|
||||||
result = self.client.stop_session(session_id)
|
result = self.client.stop_session(session_id)
|
||||||
|
@ -463,7 +455,7 @@ class CoreClient:
|
||||||
self.mobility_players[node.id] = mobility_player
|
self.mobility_players[node.id] = mobility_player
|
||||||
mobility_player.show()
|
mobility_player.show()
|
||||||
|
|
||||||
def get_metadata(self) -> dict[str, str]:
|
def get_metadata(self) -> Dict[str, str]:
|
||||||
# create canvas data
|
# create canvas data
|
||||||
canvas_config = self.app.manager.get_metadata()
|
canvas_config = self.app.manager.get_metadata()
|
||||||
canvas_config = json.dumps(canvas_config)
|
canvas_config = json.dumps(canvas_config)
|
||||||
|
@ -654,7 +646,7 @@ class CoreClient:
|
||||||
self.session.nodes[node.id] = node
|
self.session.nodes[node.id] = node
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def deleted_canvas_nodes(self, canvas_nodes: list[CanvasNode]) -> None:
|
def deleted_canvas_nodes(self, canvas_nodes: List[CanvasNode]) -> None:
|
||||||
"""
|
"""
|
||||||
remove the nodes selected by the user and anything related to that node
|
remove the nodes selected by the user and anything related to that node
|
||||||
such as link, configurations, interfaces
|
such as link, configurations, interfaces
|
||||||
|
@ -675,14 +667,14 @@ class CoreClient:
|
||||||
self.links[edge.token] = edge
|
self.links[edge.token] = edge
|
||||||
src_node = edge.src.core_node
|
src_node = edge.src.core_node
|
||||||
dst_node = edge.dst.core_node
|
dst_node = edge.dst.core_node
|
||||||
if edge.link.iface1:
|
if nutils.is_container(src_node):
|
||||||
src_iface_id = edge.link.iface1.id
|
src_iface_id = edge.link.iface1.id
|
||||||
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
|
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
|
||||||
if edge.link.iface2:
|
if nutils.is_container(dst_node):
|
||||||
dst_iface_id = edge.link.iface2.id
|
dst_iface_id = edge.link.iface2.id
|
||||||
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
|
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
|
||||||
|
|
||||||
def get_wlan_configs(self) -> list[tuple[int, dict[str, str]]]:
|
def get_wlan_configs(self) -> List[Tuple[int, Dict[str, str]]]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if node.type != NodeType.WIRELESS_LAN:
|
if node.type != NodeType.WIRELESS_LAN:
|
||||||
|
@ -693,7 +685,7 @@ class CoreClient:
|
||||||
configs.append((node.id, config))
|
configs.append((node.id, config))
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_mobility_configs(self) -> list[tuple[int, dict[str, str]]]:
|
def get_mobility_configs(self) -> List[Tuple[int, Dict[str, str]]]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if not nutils.is_mobility(node):
|
if not nutils.is_mobility(node):
|
||||||
|
@ -704,7 +696,7 @@ class CoreClient:
|
||||||
configs.append((node.id, config))
|
configs.append((node.id, config))
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_emane_model_configs(self) -> list[EmaneModelConfig]:
|
def get_emane_model_configs(self) -> List[EmaneModelConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
for key, config in node.emane_model_configs.items():
|
for key, config in node.emane_model_configs.items():
|
||||||
|
@ -718,7 +710,7 @@ class CoreClient:
|
||||||
configs.append(config)
|
configs.append(config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_service_configs(self) -> list[ServiceConfig]:
|
def get_service_configs(self) -> List[ServiceConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if not nutils.is_container(node):
|
if not nutils.is_container(node):
|
||||||
|
@ -738,7 +730,7 @@ class CoreClient:
|
||||||
configs.append(config)
|
configs.append(config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_service_file_configs(self) -> list[ServiceFileConfig]:
|
def get_service_file_configs(self) -> List[ServiceFileConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if not nutils.is_container(node):
|
if not nutils.is_container(node):
|
||||||
|
@ -751,17 +743,12 @@ class CoreClient:
|
||||||
configs.append(config)
|
configs.append(config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_config_service_rendered(self, node_id: int, name: str) -> dict[str, str]:
|
def get_config_service_rendered(self, node_id: int, name: str) -> Dict[str, str]:
|
||||||
return self.client.get_config_service_rendered(self.session.id, node_id, name)
|
return self.client.get_config_service_rendered(self.session.id, node_id, name)
|
||||||
|
|
||||||
def get_config_service_defaults(
|
|
||||||
self, node_id: int, name: str
|
|
||||||
) -> ConfigServiceDefaults:
|
|
||||||
return self.client.get_config_service_defaults(self.session.id, node_id, name)
|
|
||||||
|
|
||||||
def get_config_service_configs_proto(
|
def get_config_service_configs_proto(
|
||||||
self,
|
self
|
||||||
) -> list[configservices_pb2.ConfigServiceConfig]:
|
) -> List[configservices_pb2.ConfigServiceConfig]:
|
||||||
config_service_protos = []
|
config_service_protos = []
|
||||||
for node in self.session.nodes.values():
|
for node in self.session.nodes.values():
|
||||||
if not nutils.is_container(node):
|
if not nutils.is_container(node):
|
||||||
|
@ -783,7 +770,7 @@ class CoreClient:
|
||||||
_, output = self.client.node_command(self.session.id, node_id, self.observer)
|
_, output = self.client.node_command(self.session.id, node_id, self.observer)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def get_wlan_config(self, node_id: int) -> dict[str, ConfigOption]:
|
def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]:
|
||||||
config = self.client.get_wlan_config(self.session.id, node_id)
|
config = self.client.get_wlan_config(self.session.id, node_id)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"get wlan configuration from node %s, result configuration: %s",
|
"get wlan configuration from node %s, result configuration: %s",
|
||||||
|
@ -792,10 +779,10 @@ class CoreClient:
|
||||||
)
|
)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_wireless_config(self, node_id: int) -> dict[str, ConfigOption]:
|
def get_wireless_config(self, node_id: int) -> Dict[str, ConfigOption]:
|
||||||
return self.client.get_wireless_config(self.session.id, node_id)
|
return self.client.get_wireless_config(self.session.id, node_id)
|
||||||
|
|
||||||
def get_mobility_config(self, node_id: int) -> dict[str, ConfigOption]:
|
def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]:
|
||||||
config = self.client.get_mobility_config(self.session.id, node_id)
|
config = self.client.get_mobility_config(self.session.id, node_id)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"get mobility config from node %s, result configuration: %s",
|
"get mobility config from node %s, result configuration: %s",
|
||||||
|
@ -806,7 +793,7 @@ class CoreClient:
|
||||||
|
|
||||||
def get_emane_model_config(
|
def get_emane_model_config(
|
||||||
self, node_id: int, model: str, iface_id: int = None
|
self, node_id: int, model: str, iface_id: int = None
|
||||||
) -> dict[str, ConfigOption]:
|
) -> Dict[str, ConfigOption]:
|
||||||
if iface_id is None:
|
if iface_id is None:
|
||||||
iface_id = -1
|
iface_id = -1
|
||||||
config = self.client.get_emane_model_config(
|
config = self.client.get_emane_model_config(
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
|
@ -1,373 +1,88 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/tmp/tmpd4t2sxy2">
|
<scenario name="/home/developer/.core/configs/emane-demo-antenna.xml">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="5" name="wlan5" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
<network id="5" name="wlan5" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="388.0" y="555.0" lat="47.574121408201655" lon="-122.12709602379641" alt="2.0"/>
|
<position x="388" y="555" lat="47.57412169587584" lon="-122.12709380504643" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="1" name="n1" type="mdr" class="" image="">
|
||||||
<position x="258.0" y="147.0" lat="47.57783021533393" lon="-122.12884773860046" alt="2.0"/>
|
<position x="258" y="147" lat="47.577830502987744" lon="-122.12884551985047" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="2" name="n2" type="mdr" class="" image="">
|
||||||
<position x="526.0" y="147.0" lat="47.57783021533393" lon="-122.12523651115826" alt="2.0"/>
|
<position x="526" y="147" lat="47.577830502987744" lon="-122.12523429240828" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="3" name="n3" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="3" name="n3" type="mdr" class="" image="">
|
||||||
<position x="241.0" y="387.0" lat="47.575648595893774" lon="-122.1290768089979" alt="2.0"/>
|
<position x="241" y="387" lat="47.57564888355958" lon="-122.12907459024791" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="4" name="n4" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="4" name="n4" type="mdr" class="" image="">
|
||||||
<position x="529.0" y="385.0" lat="47.57566677643136" lon="-122.12519608697049" alt="2.0"/>
|
<position x="529" y="385" lat="47.57566706409707" lon="-122.1251938682205" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="1" node2="5">
|
<link node1="5" node2="1">
|
||||||
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="2" node2="5">
|
<link node1="5" node2="2">
|
||||||
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="3" node2="5">
|
<link node1="5" node2="3">
|
||||||
<iface1 nem="3" id="0" name="eth0" mac="02:02:00:00:00:03" ip4="10.0.0.3" ip4_mask="32" ip6="2001::3" ip6_mask="128"/>
|
<iface2 nem="3" id="0" name="eth0" mac="02:02:00:00:00:03" ip4="10.0.0.3" ip4_mask="32" ip6="2001::3" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="4" node2="5">
|
<link node1="5" node2="4">
|
||||||
<iface1 nem="4" id="0" name="eth0" mac="02:02:00:00:00:04" ip4="10.0.0.4" ip4_mask="32" ip6="2001::4" ip6_mask="128"/>
|
<iface2 nem="4" id="0" name="eth0" mac="02:02:00:00:00:04" ip4="10.0.0.4" ip4_mask="32" ip6="2001::4" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
</links>
|
</links>
|
||||||
|
<emane_global_configuration>
|
||||||
|
<emulator>
|
||||||
|
<configuration name="antennaprofilemanifesturi" value="/tmp/emane/antennaprofile.xml"/>
|
||||||
|
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
|
||||||
|
<configuration name="eventservicedevice" value="ctrl0"/>
|
||||||
|
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
||||||
|
<configuration name="eventservicettl" value="1"/>
|
||||||
|
<configuration name="otamanagerchannelenable" value="1"/>
|
||||||
|
<configuration name="otamanagerdevice" value="ctrl0"/>
|
||||||
|
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
||||||
|
<configuration name="otamanagerloopback" value="0"/>
|
||||||
|
<configuration name="otamanagermtu" value="0"/>
|
||||||
|
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
||||||
|
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
||||||
|
<configuration name="otamanagerttl" value="1"/>
|
||||||
|
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
||||||
|
</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>
|
||||||
<emane_configurations>
|
<emane_configurations>
|
||||||
<emane_configuration node="1" model="emane_rfpipe">
|
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
|
||||||
<configuration name="datarate" value="1000000"/>
|
|
||||||
<configuration name="delay" value="0.000000"/>
|
|
||||||
<configuration name="enablepromiscuousmode" value="0"/>
|
|
||||||
<configuration name="flowcontrolenable" value="0"/>
|
|
||||||
<configuration name="flowcontroltokens" value="10"/>
|
|
||||||
<configuration name="jitter" value="0.000000"/>
|
|
||||||
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
|
||||||
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
|
||||||
<configuration name="radiometricenable" value="0"/>
|
|
||||||
<configuration name="radiometricreportinterval" value="1.000000"/>
|
|
||||||
</mac>
|
|
||||||
<phy>
|
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
|
||||||
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
|
||||||
<configuration name="fixedantennagain" value="0.000000"/>
|
|
||||||
<configuration name="fixedantennagainenable" value="0"/>
|
|
||||||
<configuration name="frequency" value="2347000000"/>
|
|
||||||
<configuration name="frequencyofinterest" value="2347000000"/>
|
|
||||||
<configuration name="noisebinsize" value="20"/>
|
|
||||||
<configuration name="noisemaxclampenable" value="0"/>
|
|
||||||
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
|
||||||
<configuration name="noisemode" value="outofband"/>
|
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="precomputed"/>
|
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
|
||||||
<configuration name="txpower" value="0.000000"/>
|
|
||||||
</phy>
|
|
||||||
<external>
|
|
||||||
<configuration name="external" value="0"/>
|
|
||||||
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
|
||||||
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
|
||||||
</external>
|
|
||||||
</emane_configuration>
|
|
||||||
<emane_configuration node="2" model="emane_rfpipe">
|
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
|
||||||
<configuration name="datarate" value="1000000"/>
|
|
||||||
<configuration name="delay" value="0.000000"/>
|
|
||||||
<configuration name="enablepromiscuousmode" value="0"/>
|
|
||||||
<configuration name="flowcontrolenable" value="0"/>
|
|
||||||
<configuration name="flowcontroltokens" value="10"/>
|
|
||||||
<configuration name="jitter" value="0.000000"/>
|
|
||||||
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
|
||||||
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
|
||||||
<configuration name="radiometricenable" value="0"/>
|
|
||||||
<configuration name="radiometricreportinterval" value="1.000000"/>
|
|
||||||
</mac>
|
|
||||||
<phy>
|
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
|
||||||
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
|
||||||
<configuration name="fixedantennagain" value="0.000000"/>
|
|
||||||
<configuration name="fixedantennagainenable" value="1"/>
|
|
||||||
<configuration name="frequency" value="2347000000"/>
|
|
||||||
<configuration name="frequencyofinterest" value="2347000000"/>
|
|
||||||
<configuration name="noisebinsize" value="20"/>
|
|
||||||
<configuration name="noisemaxclampenable" value="0"/>
|
|
||||||
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
|
||||||
<configuration name="noisemode" value="outofband"/>
|
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="precomputed"/>
|
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
|
||||||
<configuration name="txpower" value="0.000000"/>
|
|
||||||
</phy>
|
|
||||||
<external>
|
|
||||||
<configuration name="external" value="0"/>
|
|
||||||
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
|
||||||
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
|
||||||
</external>
|
|
||||||
</emane_configuration>
|
|
||||||
<emane_configuration node="3" model="emane_rfpipe">
|
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
|
||||||
<configuration name="datarate" value="1000000"/>
|
|
||||||
<configuration name="delay" value="0.000000"/>
|
|
||||||
<configuration name="enablepromiscuousmode" value="0"/>
|
|
||||||
<configuration name="flowcontrolenable" value="0"/>
|
|
||||||
<configuration name="flowcontroltokens" value="10"/>
|
|
||||||
<configuration name="jitter" value="0.000000"/>
|
|
||||||
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
|
||||||
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
|
||||||
<configuration name="radiometricenable" value="0"/>
|
|
||||||
<configuration name="radiometricreportinterval" value="1.000000"/>
|
|
||||||
</mac>
|
|
||||||
<phy>
|
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
|
||||||
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
|
||||||
<configuration name="fixedantennagain" value="5.000000"/>
|
|
||||||
<configuration name="fixedantennagainenable" value="1"/>
|
|
||||||
<configuration name="frequency" value="2347000000"/>
|
|
||||||
<configuration name="frequencyofinterest" value="2347000000"/>
|
|
||||||
<configuration name="noisebinsize" value="20"/>
|
|
||||||
<configuration name="noisemaxclampenable" value="0"/>
|
|
||||||
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
|
||||||
<configuration name="noisemode" value="outofband"/>
|
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="precomputed"/>
|
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
|
||||||
<configuration name="txpower" value="0.000000"/>
|
|
||||||
</phy>
|
|
||||||
<external>
|
|
||||||
<configuration name="external" value="0"/>
|
|
||||||
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
|
||||||
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
|
||||||
</external>
|
|
||||||
</emane_configuration>
|
|
||||||
<emane_configuration node="4" model="emane_rfpipe">
|
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
|
||||||
<configuration name="datarate" value="1000000"/>
|
|
||||||
<configuration name="delay" value="0.000000"/>
|
|
||||||
<configuration name="enablepromiscuousmode" value="0"/>
|
|
||||||
<configuration name="flowcontrolenable" value="0"/>
|
|
||||||
<configuration name="flowcontroltokens" value="10"/>
|
|
||||||
<configuration name="jitter" value="0.000000"/>
|
|
||||||
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
|
||||||
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
|
||||||
<configuration name="radiometricenable" value="0"/>
|
|
||||||
<configuration name="radiometricreportinterval" value="1.000000"/>
|
|
||||||
</mac>
|
|
||||||
<phy>
|
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
|
||||||
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
|
||||||
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
|
||||||
<configuration name="fixedantennagain" value="0.000000"/>
|
|
||||||
<configuration name="fixedantennagainenable" value="0"/>
|
|
||||||
<configuration name="frequency" value="2347000000"/>
|
|
||||||
<configuration name="frequencyofinterest" value="2347000000"/>
|
|
||||||
<configuration name="noisebinsize" value="20"/>
|
|
||||||
<configuration name="noisemaxclampenable" value="0"/>
|
|
||||||
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
|
||||||
<configuration name="noisemode" value="outofband"/>
|
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="precomputed"/>
|
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
|
||||||
<configuration name="txpower" value="0.000000"/>
|
|
||||||
</phy>
|
|
||||||
<external>
|
|
||||||
<configuration name="external" value="0"/>
|
|
||||||
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
|
||||||
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
|
||||||
</external>
|
|
||||||
</emane_configuration>
|
|
||||||
<emane_configuration node="5" model="emane_rfpipe">
|
<emane_configuration node="5" model="emane_rfpipe">
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -382,17 +97,6 @@
|
||||||
</mac>
|
</mac>
|
||||||
<phy>
|
<phy>
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
<configuration name="fading.model" value="none"/>
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
@ -409,10 +113,179 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="none"/>
|
<configuration name="noisemode" value="none"/>
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="2ray"/>
|
<configuration name="propagationmodel" value="2ray"/>
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
<configuration name="subid" value="1"/>
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
<configuration name="txpower" value="0.000000"/>
|
||||||
|
</phy>
|
||||||
|
<external>
|
||||||
|
<configuration name="external" value="0"/>
|
||||||
|
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
||||||
|
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
||||||
|
</external>
|
||||||
|
</emane_configuration>
|
||||||
|
<emane_configuration node="1" model="emane_rfpipe">
|
||||||
|
<mac>
|
||||||
|
<configuration name="datarate" value="1000000"/>
|
||||||
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
<configuration name="enablepromiscuousmode" value="0"/>
|
||||||
|
<configuration name="flowcontrolenable" value="0"/>
|
||||||
|
<configuration name="flowcontroltokens" value="10"/>
|
||||||
|
<configuration name="jitter" value="0.000000"/>
|
||||||
|
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
||||||
|
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
||||||
|
<configuration name="radiometricenable" value="0"/>
|
||||||
|
<configuration name="radiometricreportinterval" value="1.000000"/>
|
||||||
|
</mac>
|
||||||
|
<phy>
|
||||||
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
|
<configuration name="fading.model" value="none"/>
|
||||||
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
||||||
|
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
||||||
|
<configuration name="fixedantennagain" value="0.000000"/>
|
||||||
|
<configuration name="fixedantennagainenable" value="0"/>
|
||||||
|
<configuration name="frequency" value="2347000000"/>
|
||||||
|
<configuration name="frequencyofinterest" value="2347000000"/>
|
||||||
|
<configuration name="noisebinsize" value="20"/>
|
||||||
|
<configuration name="noisemaxclampenable" value="0"/>
|
||||||
|
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
||||||
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<configuration name="propagationmodel" value="precomputed"/>
|
||||||
|
<configuration name="subid" value="1"/>
|
||||||
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
<configuration name="txpower" value="0.000000"/>
|
||||||
|
</phy>
|
||||||
|
<external>
|
||||||
|
<configuration name="external" value="0"/>
|
||||||
|
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
||||||
|
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
||||||
|
</external>
|
||||||
|
</emane_configuration>
|
||||||
|
<emane_configuration node="2" model="emane_rfpipe">
|
||||||
|
<mac>
|
||||||
|
<configuration name="datarate" value="1000000"/>
|
||||||
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
<configuration name="enablepromiscuousmode" value="0"/>
|
||||||
|
<configuration name="flowcontrolenable" value="0"/>
|
||||||
|
<configuration name="flowcontroltokens" value="10"/>
|
||||||
|
<configuration name="jitter" value="0.000000"/>
|
||||||
|
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
||||||
|
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
||||||
|
<configuration name="radiometricenable" value="0"/>
|
||||||
|
<configuration name="radiometricreportinterval" value="1.000000"/>
|
||||||
|
</mac>
|
||||||
|
<phy>
|
||||||
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
|
<configuration name="fading.model" value="none"/>
|
||||||
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
||||||
|
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
||||||
|
<configuration name="fixedantennagain" value="0.000000"/>
|
||||||
|
<configuration name="fixedantennagainenable" value="1"/>
|
||||||
|
<configuration name="frequency" value="2347000000"/>
|
||||||
|
<configuration name="frequencyofinterest" value="2347000000"/>
|
||||||
|
<configuration name="noisebinsize" value="20"/>
|
||||||
|
<configuration name="noisemaxclampenable" value="0"/>
|
||||||
|
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
||||||
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<configuration name="propagationmodel" value="precomputed"/>
|
||||||
|
<configuration name="subid" value="1"/>
|
||||||
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
<configuration name="txpower" value="0.000000"/>
|
||||||
|
</phy>
|
||||||
|
<external>
|
||||||
|
<configuration name="external" value="0"/>
|
||||||
|
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
||||||
|
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
||||||
|
</external>
|
||||||
|
</emane_configuration>
|
||||||
|
<emane_configuration node="3" model="emane_rfpipe">
|
||||||
|
<mac>
|
||||||
|
<configuration name="datarate" value="1000000"/>
|
||||||
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
<configuration name="enablepromiscuousmode" value="0"/>
|
||||||
|
<configuration name="flowcontrolenable" value="0"/>
|
||||||
|
<configuration name="flowcontroltokens" value="10"/>
|
||||||
|
<configuration name="jitter" value="0.000000"/>
|
||||||
|
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
||||||
|
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
||||||
|
<configuration name="radiometricenable" value="0"/>
|
||||||
|
<configuration name="radiometricreportinterval" value="1.000000"/>
|
||||||
|
</mac>
|
||||||
|
<phy>
|
||||||
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
|
<configuration name="fading.model" value="none"/>
|
||||||
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
||||||
|
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
||||||
|
<configuration name="fixedantennagain" value="5.000000"/>
|
||||||
|
<configuration name="fixedantennagainenable" value="1"/>
|
||||||
|
<configuration name="frequency" value="2347000000"/>
|
||||||
|
<configuration name="frequencyofinterest" value="2347000000"/>
|
||||||
|
<configuration name="noisebinsize" value="20"/>
|
||||||
|
<configuration name="noisemaxclampenable" value="0"/>
|
||||||
|
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
||||||
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<configuration name="propagationmodel" value="precomputed"/>
|
||||||
|
<configuration name="subid" value="1"/>
|
||||||
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
<configuration name="txpower" value="0.000000"/>
|
||||||
|
</phy>
|
||||||
|
<external>
|
||||||
|
<configuration name="external" value="0"/>
|
||||||
|
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
|
||||||
|
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
|
||||||
|
</external>
|
||||||
|
</emane_configuration>
|
||||||
|
<emane_configuration node="4" model="emane_rfpipe">
|
||||||
|
<mac>
|
||||||
|
<configuration name="datarate" value="1000000"/>
|
||||||
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
<configuration name="enablepromiscuousmode" value="0"/>
|
||||||
|
<configuration name="flowcontrolenable" value="0"/>
|
||||||
|
<configuration name="flowcontroltokens" value="10"/>
|
||||||
|
<configuration name="jitter" value="0.000000"/>
|
||||||
|
<configuration name="neighbormetricdeletetime" value="60.000000"/>
|
||||||
|
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
|
||||||
|
<configuration name="radiometricenable" value="0"/>
|
||||||
|
<configuration name="radiometricreportinterval" value="1.000000"/>
|
||||||
|
</mac>
|
||||||
|
<phy>
|
||||||
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
|
<configuration name="fading.model" value="none"/>
|
||||||
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m0" value="0.750000"/>
|
||||||
|
<configuration name="fading.nakagami.m1" value="1.000000"/>
|
||||||
|
<configuration name="fading.nakagami.m2" value="200.000000"/>
|
||||||
|
<configuration name="fixedantennagain" value="0.000000"/>
|
||||||
|
<configuration name="fixedantennagainenable" value="0"/>
|
||||||
|
<configuration name="frequency" value="2347000000"/>
|
||||||
|
<configuration name="frequencyofinterest" value="2347000000"/>
|
||||||
|
<configuration name="noisebinsize" value="20"/>
|
||||||
|
<configuration name="noisemaxclampenable" value="0"/>
|
||||||
|
<configuration name="noisemaxmessagepropagation" value="200000"/>
|
||||||
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<configuration name="propagationmodel" value="precomputed"/>
|
||||||
<configuration name="subid" value="1"/>
|
<configuration name="subid" value="1"/>
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
@ -425,7 +298,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
||||||
<session_options>
|
<session_options>
|
||||||
<configuration name="controlnet" value="172.16.0.0/24"/>
|
<configuration name="controlnet" value="172.16.0.0/24"/>
|
||||||
<configuration name="controlnet0" value=""/>
|
<configuration name="controlnet0" value=""/>
|
||||||
|
@ -438,19 +311,10 @@
|
||||||
<configuration name="enablesdt" value="0"/>
|
<configuration name="enablesdt" value="0"/>
|
||||||
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
||||||
<configuration name="ovs" value="0"/>
|
<configuration name="ovs" value="0"/>
|
||||||
<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"/>
|
|
||||||
<configuration name="mtu" value="0"/>
|
|
||||||
</session_options>
|
</session_options>
|
||||||
<session_metadata>
|
<session_metadata>
|
||||||
<configuration name="shapes" value="[]"/>
|
<configuration name="canvas c1" value="{name {Canvas1}}"/>
|
||||||
<configuration name="edges" value="[]"/>
|
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
|
||||||
<configuration name="hidden" value="[]"/>
|
|
||||||
<configuration name="canvas" value="{"gridlines": true, "canvases": [{"id": 1, "wallpaper": null, "wallpaper_style": 1, "fit_image": false, "dimensions": [1000, 750]}]}"/>
|
|
||||||
</session_metadata>
|
</session_metadata>
|
||||||
<default_services>
|
<default_services>
|
||||||
<node type="mdr">
|
<node type="mdr">
|
||||||
|
|
|
@ -1,55 +1,66 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/tmp/tmp2mkcwn17">
|
<scenario name="/home/developer/.core/configs/emane-demo-eel.xml">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="1" name="n1" type="mdr" class="" image="">
|
||||||
<position x="153.0" y="172.0" lat="47.577602967549986" lon="-122.13026258517293" alt="2.0"/>
|
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="2" name="n2" type="mdr" class="" image="">
|
||||||
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="1" node2="3">
|
<link node1="3" node2="1">
|
||||||
<iface1 id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="2" node2="3">
|
<link node1="3" node2="2">
|
||||||
<iface1 id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
</links>
|
</links>
|
||||||
|
<emane_global_configuration>
|
||||||
|
<emulator>
|
||||||
|
<configuration name="antennaprofilemanifesturi" value=""/>
|
||||||
|
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
|
||||||
|
<configuration name="eventservicedevice" value="ctrl0"/>
|
||||||
|
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
||||||
|
<configuration name="eventservicettl" value="1"/>
|
||||||
|
<configuration name="otamanagerchannelenable" value="1"/>
|
||||||
|
<configuration name="otamanagerdevice" value="ctrl0"/>
|
||||||
|
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
||||||
|
<configuration name="otamanagerloopback" value="0"/>
|
||||||
|
<configuration name="otamanagermtu" value="0"/>
|
||||||
|
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
||||||
|
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
||||||
|
<configuration name="otamanagerttl" value="1"/>
|
||||||
|
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
||||||
|
</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>
|
||||||
<emane_configurations>
|
<emane_configurations>
|
||||||
<emane_configuration node="3" model="emane_rfpipe">
|
<emane_configuration node="3" model="emane_rfpipe">
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -64,17 +75,6 @@
|
||||||
</mac>
|
</mac>
|
||||||
<phy>
|
<phy>
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
<configuration name="fading.model" value="none"/>
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
@ -91,10 +91,7 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="none"/>
|
<configuration name="noisemode" value="none"/>
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="precomputed"/>
|
<configuration name="propagationmodel" value="precomputed"/>
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
<configuration name="subid" value="1"/>
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
@ -107,7 +104,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
||||||
<session_options>
|
<session_options>
|
||||||
<configuration name="controlnet" value="172.16.0.0/24"/>
|
<configuration name="controlnet" value="172.16.0.0/24"/>
|
||||||
<configuration name="controlnet0" value=""/>
|
<configuration name="controlnet0" value=""/>
|
||||||
|
@ -120,19 +117,10 @@
|
||||||
<configuration name="enablesdt" value="0"/>
|
<configuration name="enablesdt" value="0"/>
|
||||||
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
||||||
<configuration name="ovs" value="0"/>
|
<configuration name="ovs" value="0"/>
|
||||||
<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"/>
|
|
||||||
<configuration name="mtu" value="0"/>
|
|
||||||
</session_options>
|
</session_options>
|
||||||
<session_metadata>
|
<session_metadata>
|
||||||
<configuration name="shapes" value="[]"/>
|
<configuration name="canvas c1" value="{name {Canvas1}}"/>
|
||||||
<configuration name="edges" value="[]"/>
|
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
|
||||||
<configuration name="hidden" value="[]"/>
|
|
||||||
<configuration name="canvas" value="{"gridlines": true, "canvases": [{"id": 1, "wallpaper": null, "wallpaper_style": 1, "fit_image": false, "dimensions": [1000, 750]}]}"/>
|
|
||||||
</session_metadata>
|
</session_metadata>
|
||||||
<default_services>
|
<default_services>
|
||||||
<node type="mdr">
|
<node type="mdr">
|
||||||
|
|
|
@ -1,55 +1,66 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/tmp/tmpsj4dhmce">
|
<scenario name="/home/developer/.core/configs/emane-demo-files.xml">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="1" name="n1" type="mdr" class="" image="">
|
||||||
<position x="153.0" y="173.0" lat="47.57759387761812" lon="-122.13026258517293" alt="2.0"/>
|
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="2" name="n2" type="mdr" class="" image="">
|
||||||
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="1" node2="3">
|
<link node1="3" node2="1">
|
||||||
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="2" node2="3">
|
<link node1="3" node2="2">
|
||||||
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
</links>
|
</links>
|
||||||
|
<emane_global_configuration>
|
||||||
|
<emulator>
|
||||||
|
<configuration name="antennaprofilemanifesturi" value=""/>
|
||||||
|
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
|
||||||
|
<configuration name="eventservicedevice" value="ctrl0"/>
|
||||||
|
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
||||||
|
<configuration name="eventservicettl" value="1"/>
|
||||||
|
<configuration name="otamanagerchannelenable" value="1"/>
|
||||||
|
<configuration name="otamanagerdevice" value="ctrl0"/>
|
||||||
|
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
||||||
|
<configuration name="otamanagerloopback" value="0"/>
|
||||||
|
<configuration name="otamanagermtu" value="0"/>
|
||||||
|
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
||||||
|
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
||||||
|
<configuration name="otamanagerttl" value="1"/>
|
||||||
|
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
||||||
|
</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>
|
||||||
<emane_configurations>
|
<emane_configurations>
|
||||||
<emane_configuration node="3" model="emane_rfpipe">
|
<emane_configuration node="3" model="emane_rfpipe">
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -64,17 +75,6 @@
|
||||||
</mac>
|
</mac>
|
||||||
<phy>
|
<phy>
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
<configuration name="fading.model" value="none"/>
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
@ -91,10 +91,7 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="none"/>
|
<configuration name="noisemode" value="none"/>
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="2ray"/>
|
<configuration name="propagationmodel" value="2ray"/>
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
<configuration name="subid" value="1"/>
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
@ -107,7 +104,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
||||||
<session_options>
|
<session_options>
|
||||||
<configuration name="controlnet" value="172.16.0.0/24"/>
|
<configuration name="controlnet" value="172.16.0.0/24"/>
|
||||||
<configuration name="controlnet0" value=""/>
|
<configuration name="controlnet0" value=""/>
|
||||||
|
@ -120,19 +117,10 @@
|
||||||
<configuration name="enablesdt" value="0"/>
|
<configuration name="enablesdt" value="0"/>
|
||||||
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
||||||
<configuration name="ovs" value="0"/>
|
<configuration name="ovs" value="0"/>
|
||||||
<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"/>
|
|
||||||
<configuration name="mtu" value="0"/>
|
|
||||||
</session_options>
|
</session_options>
|
||||||
<session_metadata>
|
<session_metadata>
|
||||||
<configuration name="shapes" value="[]"/>
|
<configuration name="canvas c1" value="{name {Canvas1}}"/>
|
||||||
<configuration name="edges" value="[]"/>
|
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
|
||||||
<configuration name="hidden" value="[]"/>
|
|
||||||
<configuration name="canvas" value="{"gridlines": true, "canvases": [{"id": 1, "wallpaper": null, "wallpaper_style": 1, "fit_image": false, "dimensions": [1000, 750]}]}"/>
|
|
||||||
</session_metadata>
|
</session_metadata>
|
||||||
<default_services>
|
<default_services>
|
||||||
<node type="mdr">
|
<node type="mdr">
|
||||||
|
|
|
@ -1,55 +1,66 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/tmp/tmp081pn3j9">
|
<scenario name="/home/developer/.core/configs/emane-demo-gpsd.xml">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="1" name="n1" type="mdr" class="" image="">
|
||||||
<position x="153.0" y="173.0" lat="47.57759387761812" lon="-122.13026258517293" alt="2.0"/>
|
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="2" name="n2" type="mdr" class="" image="">
|
||||||
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="1" node2="3">
|
<link node1="3" node2="1">
|
||||||
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="2" node2="3">
|
<link node1="3" node2="2">
|
||||||
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
</links>
|
</links>
|
||||||
|
<emane_global_configuration>
|
||||||
|
<emulator>
|
||||||
|
<configuration name="antennaprofilemanifesturi" value=""/>
|
||||||
|
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
|
||||||
|
<configuration name="eventservicedevice" value="ctrl0"/>
|
||||||
|
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
||||||
|
<configuration name="eventservicettl" value="1"/>
|
||||||
|
<configuration name="otamanagerchannelenable" value="1"/>
|
||||||
|
<configuration name="otamanagerdevice" value="ctrl0"/>
|
||||||
|
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
||||||
|
<configuration name="otamanagerloopback" value="0"/>
|
||||||
|
<configuration name="otamanagermtu" value="0"/>
|
||||||
|
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
||||||
|
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
||||||
|
<configuration name="otamanagerttl" value="1"/>
|
||||||
|
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
||||||
|
</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>
|
||||||
<emane_configurations>
|
<emane_configurations>
|
||||||
<emane_configuration node="3" model="emane_rfpipe">
|
<emane_configuration node="3" model="emane_rfpipe">
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -64,17 +75,6 @@
|
||||||
</mac>
|
</mac>
|
||||||
<phy>
|
<phy>
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
<configuration name="fading.model" value="none"/>
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
@ -91,10 +91,7 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="none"/>
|
<configuration name="noisemode" value="none"/>
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="2ray"/>
|
<configuration name="propagationmodel" value="2ray"/>
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
<configuration name="subid" value="1"/>
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
@ -107,7 +104,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
||||||
<session_options>
|
<session_options>
|
||||||
<configuration name="controlnet" value="172.16.0.0/24"/>
|
<configuration name="controlnet" value="172.16.0.0/24"/>
|
||||||
<configuration name="controlnet0" value=""/>
|
<configuration name="controlnet0" value=""/>
|
||||||
|
@ -120,19 +117,10 @@
|
||||||
<configuration name="enablesdt" value="0"/>
|
<configuration name="enablesdt" value="0"/>
|
||||||
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
||||||
<configuration name="ovs" value="0"/>
|
<configuration name="ovs" value="0"/>
|
||||||
<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"/>
|
|
||||||
<configuration name="mtu" value="0"/>
|
|
||||||
</session_options>
|
</session_options>
|
||||||
<session_metadata>
|
<session_metadata>
|
||||||
<configuration name="shapes" value="[]"/>
|
<configuration name="canvas c1" value="{name {Canvas1}}"/>
|
||||||
<configuration name="edges" value="[]"/>
|
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
|
||||||
<configuration name="hidden" value="[]"/>
|
|
||||||
<configuration name="canvas" value="{"gridlines": true, "canvases": [{"id": 1, "wallpaper": null, "wallpaper_style": 1, "fit_image": false, "dimensions": [1000, 750]}]}"/>
|
|
||||||
</session_metadata>
|
</session_metadata>
|
||||||
<default_services>
|
<default_services>
|
||||||
<node type="mdr">
|
<node type="mdr">
|
||||||
|
|
|
@ -1,55 +1,66 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/tmp/tmpfokvqh5k">
|
<scenario name="/home/developer/.core/configs/emane-demo-precomputed.xml">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="1" name="n1" type="mdr" class="" image="">
|
||||||
<position x="153.0" y="172.0" lat="47.577602967549986" lon="-122.13026258517293" alt="2.0"/>
|
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
<device id="2" name="n2" type="mdr" class="" image="">
|
||||||
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="IPForward"/>
|
|
||||||
<service name="OSPFv3MDR"/>
|
<service name="OSPFv3MDR"/>
|
||||||
|
<service name="IPForward"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="1" node2="3">
|
<link node1="3" node2="1">
|
||||||
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="2" node2="3">
|
<link node1="3" node2="2">
|
||||||
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
|
||||||
</link>
|
</link>
|
||||||
</links>
|
</links>
|
||||||
|
<emane_global_configuration>
|
||||||
|
<emulator>
|
||||||
|
<configuration name="antennaprofilemanifesturi" value=""/>
|
||||||
|
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
|
||||||
|
<configuration name="eventservicedevice" value="ctrl0"/>
|
||||||
|
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
||||||
|
<configuration name="eventservicettl" value="1"/>
|
||||||
|
<configuration name="otamanagerchannelenable" value="1"/>
|
||||||
|
<configuration name="otamanagerdevice" value="ctrl0"/>
|
||||||
|
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
||||||
|
<configuration name="otamanagerloopback" value="0"/>
|
||||||
|
<configuration name="otamanagermtu" value="0"/>
|
||||||
|
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
||||||
|
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
||||||
|
<configuration name="otamanagerttl" value="1"/>
|
||||||
|
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
||||||
|
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
||||||
|
</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>
|
||||||
<emane_configurations>
|
<emane_configurations>
|
||||||
<emane_configuration node="3" model="emane_rfpipe">
|
<emane_configuration node="3" model="emane_rfpipe">
|
||||||
<platform>
|
|
||||||
<configuration name="antennaprofilemanifesturi" value=""/>
|
|
||||||
<configuration name="eventservicedevice" value="ctrl0"/>
|
|
||||||
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
|
|
||||||
<configuration name="eventservicettl" value="1"/>
|
|
||||||
<configuration name="otamanagerchannelenable" value="1"/>
|
|
||||||
<configuration name="otamanagerdevice" value="ctrl0"/>
|
|
||||||
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
|
|
||||||
<configuration name="otamanagerloopback" value="0"/>
|
|
||||||
<configuration name="otamanagermtu" value="0"/>
|
|
||||||
<configuration name="otamanagerpartcheckthreshold" value="2"/>
|
|
||||||
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
|
|
||||||
<configuration name="otamanagerttl" value="1"/>
|
|
||||||
<configuration name="stats.event.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxeventcountrows" value="0"/>
|
|
||||||
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
|
|
||||||
</platform>
|
|
||||||
<mac>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -64,17 +75,6 @@
|
||||||
</mac>
|
</mac>
|
||||||
<phy>
|
<phy>
|
||||||
<configuration name="bandwidth" value="1000000"/>
|
<configuration name="bandwidth" value="1000000"/>
|
||||||
<configuration name="compatibilitymode" value="1"/>
|
|
||||||
<configuration name="dopplershiftenable" value="1"/>
|
|
||||||
<configuration name="excludesamesubidfromfilterenable" value="1"/>
|
|
||||||
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
|
|
||||||
<configuration name="fading.lognormal.dmu" value="5.000000"/>
|
|
||||||
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
|
|
||||||
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
|
|
||||||
<configuration name="fading.lognormal.lmean" value="0.005000"/>
|
|
||||||
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
|
|
||||||
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
|
|
||||||
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
|
|
||||||
<configuration name="fading.model" value="none"/>
|
<configuration name="fading.model" value="none"/>
|
||||||
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
<configuration name="fading.nakagami.distance0" value="100.000000"/>
|
||||||
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
<configuration name="fading.nakagami.distance1" value="250.000000"/>
|
||||||
|
@ -91,10 +91,7 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="none"/>
|
<configuration name="noisemode" value="none"/>
|
||||||
<configuration name="processingpoolsize" value="0"/>
|
|
||||||
<configuration name="propagationmodel" value="precomputed"/>
|
<configuration name="propagationmodel" value="precomputed"/>
|
||||||
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
|
|
||||||
<configuration name="stats.receivepowertableenable" value="1"/>
|
|
||||||
<configuration name="subid" value="1"/>
|
<configuration name="subid" value="1"/>
|
||||||
<configuration name="systemnoisefigure" value="4.000000"/>
|
<configuration name="systemnoisefigure" value="4.000000"/>
|
||||||
<configuration name="timesyncthreshold" value="10000"/>
|
<configuration name="timesyncthreshold" value="10000"/>
|
||||||
|
@ -107,7 +104,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
||||||
<session_options>
|
<session_options>
|
||||||
<configuration name="controlnet" value="172.16.0.0/24"/>
|
<configuration name="controlnet" value="172.16.0.0/24"/>
|
||||||
<configuration name="controlnet0" value=""/>
|
<configuration name="controlnet0" value=""/>
|
||||||
|
@ -120,19 +117,10 @@
|
||||||
<configuration name="enablesdt" value="0"/>
|
<configuration name="enablesdt" value="0"/>
|
||||||
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
|
||||||
<configuration name="ovs" value="0"/>
|
<configuration name="ovs" value="0"/>
|
||||||
<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"/>
|
|
||||||
<configuration name="mtu" value="0"/>
|
|
||||||
</session_options>
|
</session_options>
|
||||||
<session_metadata>
|
<session_metadata>
|
||||||
<configuration name="shapes" value="[]"/>
|
<configuration name="canvas c1" value="{name {Canvas1}}"/>
|
||||||
<configuration name="edges" value="[]"/>
|
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
|
||||||
<configuration name="hidden" value="[]"/>
|
|
||||||
<configuration name="canvas" value="{"gridlines": true, "canvases": [{"id": 1, "wallpaper": null, "wallpaper_style": 1, "fit_image": false, "dimensions": [1000, 750]}]}"/>
|
|
||||||
</session_metadata>
|
</session_metadata>
|
||||||
<default_services>
|
<default_services>
|
||||||
<node type="mdr">
|
<node type="mdr">
|
||||||
|
|
|
@ -95,9 +95,10 @@
|
||||||
<service name="DefaultRoute"/>
|
<service name="DefaultRoute"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="14" name="n14" icon="" canvas="0" type="PC" class="" image="">
|
<device id="14" name="n14" icon="" canvas="0" type="host" class="" image="">
|
||||||
<position x="348.0" y="228.0" lat="47.57709392893491" lon="-122.12763501296686" alt="2.0"/>
|
<position x="348.0" y="228.0" lat="47.57709392893491" lon="-122.12763501296686" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
|
<service name="SSH"/>
|
||||||
<service name="DefaultRoute"/>
|
<service name="DefaultRoute"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
|
@ -259,5 +260,9 @@
|
||||||
<service name="OSPFv3"/>
|
<service name="OSPFv3"/>
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
</node>
|
</node>
|
||||||
|
<node type="host">
|
||||||
|
<service name="DefaultRoute"/>
|
||||||
|
<service name="SSH"/>
|
||||||
|
</node>
|
||||||
</default_services>
|
</default_services>
|
||||||
</scenario>
|
</scenario>
|
||||||
|
|
|
@ -3,7 +3,7 @@ check engine light
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
|
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -19,7 +19,7 @@ class AlertsDialog(Dialog):
|
||||||
super().__init__(app, "Alerts")
|
super().__init__(app, "Alerts")
|
||||||
self.tree: Optional[ttk.Treeview] = None
|
self.tree: Optional[ttk.Treeview] = None
|
||||||
self.codetext: Optional[CodeText] = None
|
self.codetext: Optional[CodeText] = None
|
||||||
self.alarm_map: dict[int, ExceptionEvent] = {}
|
self.alarm_map: Dict[int, ExceptionEvent] = {}
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
|
|
@ -4,7 +4,7 @@ set wallpaper
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
from core.gui import images
|
from core.gui import images
|
||||||
from core.gui.appconfig import BACKGROUNDS_PATH
|
from core.gui.appconfig import BACKGROUNDS_PATH
|
||||||
|
@ -32,7 +32,7 @@ class CanvasWallpaperDialog(Dialog):
|
||||||
)
|
)
|
||||||
self.filename: tk.StringVar = tk.StringVar(value=self.canvas.wallpaper_file)
|
self.filename: tk.StringVar = tk.StringVar(value=self.canvas.wallpaper_file)
|
||||||
self.image_label: Optional[ttk.Label] = None
|
self.image_label: Optional[ttk.Label] = None
|
||||||
self.options: list[ttk.Radiobutton] = []
|
self.options: List[ttk.Radiobutton] = []
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
|
|
@ -3,7 +3,7 @@ custom color picker
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, Tuple
|
||||||
|
|
||||||
from core.gui import validation
|
from core.gui import validation
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -13,36 +13,6 @@ if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
def get_rgb(red: int, green: int, blue: int) -> str:
|
|
||||||
"""
|
|
||||||
Convert rgb integers to an rgb hex code (#<red><green><blue>).
|
|
||||||
|
|
||||||
:param red: red value
|
|
||||||
:param green: green value
|
|
||||||
:param blue: blue value
|
|
||||||
:return: rgb hex code
|
|
||||||
"""
|
|
||||||
return f"#{red:02x}{green:02x}{blue:02x}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_rgb_values(hex_code: str) -> tuple[int, int, int]:
|
|
||||||
"""
|
|
||||||
Convert a valid rgb hex code (#<red><green><blue>) to rgb integers.
|
|
||||||
|
|
||||||
:param hex_code: valid rgb hex code
|
|
||||||
:return: a tuple of red, blue, and green values
|
|
||||||
"""
|
|
||||||
if len(hex_code) == 4:
|
|
||||||
red = hex_code[1]
|
|
||||||
green = hex_code[2]
|
|
||||||
blue = hex_code[3]
|
|
||||||
else:
|
|
||||||
red = hex_code[1:3]
|
|
||||||
green = hex_code[3:5]
|
|
||||||
blue = hex_code[5:]
|
|
||||||
return int(red, 16), int(green, 16), int(blue, 16)
|
|
||||||
|
|
||||||
|
|
||||||
class ColorPickerDialog(Dialog):
|
class ColorPickerDialog(Dialog):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000"
|
self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000"
|
||||||
|
@ -57,7 +27,7 @@ class ColorPickerDialog(Dialog):
|
||||||
self.blue_label: Optional[ttk.Label] = None
|
self.blue_label: Optional[ttk.Label] = None
|
||||||
self.display: Optional[tk.Frame] = None
|
self.display: Optional[tk.Frame] = None
|
||||||
self.color: str = initcolor
|
self.color: str = initcolor
|
||||||
red, green, blue = get_rgb_values(initcolor)
|
red, green, blue = self.get_rgb(initcolor)
|
||||||
self.red: tk.IntVar = tk.IntVar(value=red)
|
self.red: tk.IntVar = tk.IntVar(value=red)
|
||||||
self.blue: tk.IntVar = tk.IntVar(value=blue)
|
self.blue: tk.IntVar = tk.IntVar(value=blue)
|
||||||
self.green: tk.IntVar = tk.IntVar(value=green)
|
self.green: tk.IntVar = tk.IntVar(value=green)
|
||||||
|
@ -96,7 +66,7 @@ class ColorPickerDialog(Dialog):
|
||||||
)
|
)
|
||||||
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
|
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
|
||||||
self.red_label = ttk.Label(
|
self.red_label = ttk.Label(
|
||||||
frame, background=get_rgb(self.red.get(), 0, 0), width=5
|
frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
|
||||||
)
|
)
|
||||||
self.red_label.grid(row=0, column=3, sticky=tk.EW)
|
self.red_label.grid(row=0, column=3, sticky=tk.EW)
|
||||||
|
|
||||||
|
@ -119,7 +89,7 @@ class ColorPickerDialog(Dialog):
|
||||||
)
|
)
|
||||||
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
|
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
|
||||||
self.green_label = ttk.Label(
|
self.green_label = ttk.Label(
|
||||||
frame, background=get_rgb(0, self.green.get(), 0), width=5
|
frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
|
||||||
)
|
)
|
||||||
self.green_label.grid(row=0, column=3, sticky=tk.EW)
|
self.green_label.grid(row=0, column=3, sticky=tk.EW)
|
||||||
|
|
||||||
|
@ -142,7 +112,7 @@ class ColorPickerDialog(Dialog):
|
||||||
)
|
)
|
||||||
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
|
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
|
||||||
self.blue_label = ttk.Label(
|
self.blue_label = ttk.Label(
|
||||||
frame, background=get_rgb(0, 0, self.blue.get()), width=5
|
frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
|
||||||
)
|
)
|
||||||
self.blue_label.grid(row=0, column=3, sticky=tk.EW)
|
self.blue_label.grid(row=0, column=3, sticky=tk.EW)
|
||||||
|
|
||||||
|
@ -180,27 +150,39 @@ class ColorPickerDialog(Dialog):
|
||||||
self.color = self.hex.get()
|
self.color = self.hex.get()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
def get_hex(self) -> str:
|
||||||
|
"""
|
||||||
|
convert current RGB values into hex color
|
||||||
|
"""
|
||||||
|
red = self.red_entry.get()
|
||||||
|
blue = self.blue_entry.get()
|
||||||
|
green = self.green_entry.get()
|
||||||
|
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
||||||
|
|
||||||
def current_focus(self, focus: str) -> None:
|
def current_focus(self, focus: str) -> None:
|
||||||
self.focus = focus
|
self.focus = focus
|
||||||
|
|
||||||
def update_color(self, arg1=None, arg2=None, arg3=None) -> None:
|
def update_color(self, arg1=None, arg2=None, arg3=None) -> None:
|
||||||
if self.focus == "rgb":
|
if self.focus == "rgb":
|
||||||
red = int(self.red_entry.get() or 0)
|
red = self.red_entry.get()
|
||||||
blue = int(self.blue_entry.get() or 0)
|
blue = self.blue_entry.get()
|
||||||
green = int(self.green_entry.get() or 0)
|
green = self.green_entry.get()
|
||||||
self.set_scale(red, green, blue)
|
self.set_scale(red, green, blue)
|
||||||
hex_code = get_rgb(red, green, blue)
|
if red and blue and green:
|
||||||
self.hex.set(hex_code)
|
hex_code = "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
||||||
self.display.config(background=hex_code)
|
self.hex.set(hex_code)
|
||||||
self.set_label(red, green, blue)
|
self.display.config(background=hex_code)
|
||||||
|
self.set_label(red, green, blue)
|
||||||
elif self.focus == "hex":
|
elif self.focus == "hex":
|
||||||
hex_code = self.hex.get()
|
hex_code = self.hex.get()
|
||||||
if len(hex_code) == 4 or len(hex_code) == 7:
|
if len(hex_code) == 4 or len(hex_code) == 7:
|
||||||
red, green, blue = get_rgb_values(hex_code)
|
red, green, blue = self.get_rgb(hex_code)
|
||||||
self.set_entry(red, green, blue)
|
else:
|
||||||
self.set_scale(red, green, blue)
|
return
|
||||||
self.display.config(background=hex_code)
|
self.set_entry(red, green, blue)
|
||||||
self.set_label(red, green, blue)
|
self.set_scale(red, green, blue)
|
||||||
|
self.display.config(background=hex_code)
|
||||||
|
self.set_label(str(red), str(green), str(blue))
|
||||||
|
|
||||||
def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar) -> None:
|
def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar) -> None:
|
||||||
color_var.set(var.get())
|
color_var.set(var.get())
|
||||||
|
@ -217,7 +199,21 @@ class ColorPickerDialog(Dialog):
|
||||||
self.green.set(green)
|
self.green.set(green)
|
||||||
self.blue.set(blue)
|
self.blue.set(blue)
|
||||||
|
|
||||||
def set_label(self, red: int, green: int, blue: int) -> None:
|
def set_label(self, red: str, green: str, blue: str) -> None:
|
||||||
self.red_label.configure(background=get_rgb(red, 0, 0))
|
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
||||||
self.green_label.configure(background=get_rgb(0, green, 0))
|
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
||||||
self.blue_label.configure(background=get_rgb(0, 0, blue))
|
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
||||||
|
|
||||||
|
def get_rgb(self, hex_code: str) -> Tuple[int, int, int]:
|
||||||
|
"""
|
||||||
|
convert a valid hex code to RGB values
|
||||||
|
"""
|
||||||
|
if len(hex_code) == 4:
|
||||||
|
red = hex_code[1]
|
||||||
|
green = hex_code[2]
|
||||||
|
blue = hex_code[3]
|
||||||
|
else:
|
||||||
|
red = hex_code[1:3]
|
||||||
|
green = hex_code[3:5]
|
||||||
|
blue = hex_code[5:]
|
||||||
|
return int(red, 16), int(green, 16), int(blue, 16)
|
||||||
|
|
|
@ -4,7 +4,7 @@ Service configuration dialog
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -35,22 +35,22 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.node: Node = node
|
self.node: Node = node
|
||||||
self.service_name: str = service_name
|
self.service_name: str = service_name
|
||||||
self.radiovar: tk.IntVar = tk.IntVar(value=2)
|
self.radiovar: tk.IntVar = tk.IntVar(value=2)
|
||||||
self.directories: list[str] = []
|
self.directories: List[str] = []
|
||||||
self.templates: list[str] = []
|
self.templates: List[str] = []
|
||||||
self.rendered: dict[str, str] = {}
|
self.rendered: Dict[str, str] = {}
|
||||||
self.dependencies: list[str] = []
|
self.dependencies: List[str] = []
|
||||||
self.executables: list[str] = []
|
self.executables: List[str] = []
|
||||||
self.startup_commands: list[str] = []
|
self.startup_commands: List[str] = []
|
||||||
self.validation_commands: list[str] = []
|
self.validation_commands: List[str] = []
|
||||||
self.shutdown_commands: list[str] = []
|
self.shutdown_commands: List[str] = []
|
||||||
self.default_startup: list[str] = []
|
self.default_startup: List[str] = []
|
||||||
self.default_validate: list[str] = []
|
self.default_validate: List[str] = []
|
||||||
self.default_shutdown: list[str] = []
|
self.default_shutdown: List[str] = []
|
||||||
self.validation_mode: Optional[ServiceValidationMode] = None
|
self.validation_mode: Optional[ServiceValidationMode] = None
|
||||||
self.validation_time: Optional[int] = None
|
self.validation_time: Optional[int] = None
|
||||||
self.validation_period: tk.DoubleVar = tk.DoubleVar()
|
self.validation_period: tk.DoubleVar = tk.DoubleVar()
|
||||||
self.modes: list[str] = []
|
self.modes: List[str] = []
|
||||||
self.mode_configs: dict[str, dict[str, str]] = {}
|
self.mode_configs: Dict[str, Dict[str, str]] = {}
|
||||||
self.notebook: Optional[ttk.Notebook] = None
|
self.notebook: Optional[ttk.Notebook] = None
|
||||||
self.templates_combobox: Optional[ttk.Combobox] = None
|
self.templates_combobox: Optional[ttk.Combobox] = None
|
||||||
self.modes_combobox: Optional[ttk.Combobox] = None
|
self.modes_combobox: Optional[ttk.Combobox] = None
|
||||||
|
@ -62,12 +62,12 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.template_text: Optional[CodeText] = None
|
self.template_text: Optional[CodeText] = None
|
||||||
self.rendered_text: Optional[CodeText] = None
|
self.rendered_text: Optional[CodeText] = None
|
||||||
self.validation_period_entry: Optional[ttk.Entry] = None
|
self.validation_period_entry: Optional[ttk.Entry] = None
|
||||||
self.original_service_files: dict[str, str] = {}
|
self.original_service_files: Dict[str, str] = {}
|
||||||
self.temp_service_files: dict[str, str] = {}
|
self.temp_service_files: Dict[str, str] = {}
|
||||||
self.modified_files: set[str] = set()
|
self.modified_files: Set[str] = set()
|
||||||
self.config_frame: Optional[ConfigFrame] = None
|
self.config_frame: Optional[ConfigFrame] = None
|
||||||
self.default_config: dict[str, str] = {}
|
self.default_config: Dict[str, str] = {}
|
||||||
self.config: dict[str, ConfigOption] = {}
|
self.config: Dict[str, ConfigOption] = {}
|
||||||
self.has_error: bool = False
|
self.has_error: bool = False
|
||||||
self.load()
|
self.load()
|
||||||
if not self.has_error:
|
if not self.has_error:
|
||||||
|
@ -87,9 +87,7 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.validation_mode = service.validation_mode
|
self.validation_mode = service.validation_mode
|
||||||
self.validation_time = service.validation_timer
|
self.validation_time = service.validation_timer
|
||||||
self.validation_period.set(service.validation_period)
|
self.validation_period.set(service.validation_period)
|
||||||
defaults = self.core.get_config_service_defaults(
|
defaults = self.core.client.get_config_service_defaults(self.service_name)
|
||||||
self.node.id, self.service_name
|
|
||||||
)
|
|
||||||
self.original_service_files = defaults.templates
|
self.original_service_files = defaults.templates
|
||||||
self.temp_service_files = dict(self.original_service_files)
|
self.temp_service_files = dict(self.original_service_files)
|
||||||
self.modes = sorted(defaults.modes)
|
self.modes = sorted(defaults.modes)
|
||||||
|
@ -407,7 +405,7 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def append_commands(
|
def append_commands(
|
||||||
self, commands: list[str], listbox: tk.Listbox, to_add: list[str]
|
self, commands: List[str], listbox: tk.Listbox, to_add: List[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
for cmd in to_add:
|
for cmd in to_add:
|
||||||
commands.append(cmd)
|
commands.append(cmd)
|
||||||
|
|
|
@ -4,7 +4,7 @@ copy service config dialog
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
@ -29,7 +29,7 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
self.service: str = service
|
self.service: str = service
|
||||||
self.file_name: str = file_name
|
self.file_name: str = file_name
|
||||||
self.listbox: Optional[tk.Listbox] = None
|
self.listbox: Optional[tk.Listbox] = None
|
||||||
self.nodes: dict[str, int] = {}
|
self.nodes: Dict[str, int] = {}
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
|
||||||
|
@ -21,13 +21,13 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class ServicesSelectDialog(Dialog):
|
class ServicesSelectDialog(Dialog):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, master: tk.BaseWidget, app: "Application", current_services: set[str]
|
self, master: tk.BaseWidget, app: "Application", current_services: Set[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(app, "Node Config Services", master=master)
|
super().__init__(app, "Node Services", master=master)
|
||||||
self.groups: Optional[ListboxScroll] = None
|
self.groups: Optional[ListboxScroll] = None
|
||||||
self.services: Optional[CheckboxList] = None
|
self.services: Optional[CheckboxList] = None
|
||||||
self.current: Optional[ListboxScroll] = None
|
self.current: Optional[ListboxScroll] = None
|
||||||
self.current_services: set[str] = current_services
|
self.current_services: Set[str] = current_services
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
@ -45,7 +45,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
label_frame.columnconfigure(0, weight=1)
|
label_frame.columnconfigure(0, weight=1)
|
||||||
self.groups = ListboxScroll(label_frame)
|
self.groups = ListboxScroll(label_frame)
|
||||||
self.groups.grid(sticky=tk.NSEW)
|
self.groups.grid(sticky=tk.NSEW)
|
||||||
for group in sorted(self.app.core.config_services_groups):
|
for group in sorted(self.app.core.services):
|
||||||
self.groups.listbox.insert(tk.END, group)
|
self.groups.listbox.insert(tk.END, group)
|
||||||
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
|
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
|
||||||
self.groups.listbox.selection_set(0)
|
self.groups.listbox.selection_set(0)
|
||||||
|
@ -86,7 +86,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
index = selection[0]
|
index = selection[0]
|
||||||
group = self.groups.listbox.get(index)
|
group = self.groups.listbox.get(index)
|
||||||
self.services.clear()
|
self.services.clear()
|
||||||
for name in sorted(self.app.core.config_services_groups[group]):
|
for name in sorted(self.app.core.services[group]):
|
||||||
checked = name in self.current_services
|
checked = name in self.current_services
|
||||||
self.services.add(name, checked)
|
self.services.add(name, checked)
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ class CustomNodesDialog(Dialog):
|
||||||
self.image_button: Optional[ttk.Button] = None
|
self.image_button: Optional[ttk.Button] = None
|
||||||
self.image: Optional[PhotoImage] = None
|
self.image: Optional[PhotoImage] = None
|
||||||
self.image_file: Optional[str] = None
|
self.image_file: Optional[str] = None
|
||||||
self.services: set[str] = set()
|
self.services: Set[str] = set()
|
||||||
self.selected: Optional[str] = None
|
self.selected: Optional[str] = None
|
||||||
self.selected_index: Optional[int] = None
|
self.selected_index: Optional[int] = None
|
||||||
self.draw()
|
self.draw()
|
||||||
|
@ -147,7 +147,7 @@ class CustomNodesDialog(Dialog):
|
||||||
frame, text="Icon", compound=tk.LEFT, command=self.click_icon
|
frame, text="Icon", compound=tk.LEFT, command=self.click_icon
|
||||||
)
|
)
|
||||||
self.image_button.grid(sticky=tk.EW, pady=PADY)
|
self.image_button.grid(sticky=tk.EW, pady=PADY)
|
||||||
button = ttk.Button(frame, text="Config Services", command=self.click_services)
|
button = ttk.Button(frame, text="Services", command=self.click_services)
|
||||||
button.grid(sticky=tk.EW)
|
button.grid(sticky=tk.EW)
|
||||||
|
|
||||||
def draw_node_buttons(self) -> None:
|
def draw_node_buttons(self) -> None:
|
||||||
|
|
|
@ -4,7 +4,7 @@ emane configuration
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -37,13 +37,11 @@ class EmaneModelDialog(Dialog):
|
||||||
self.has_error: bool = False
|
self.has_error: bool = False
|
||||||
try:
|
try:
|
||||||
config = self.node.emane_model_configs.get((self.model, self.iface_id))
|
config = self.node.emane_model_configs.get((self.model, self.iface_id))
|
||||||
if not config:
|
|
||||||
config = self.node.emane_model_configs.get((self.model, None))
|
|
||||||
if not config:
|
if not config:
|
||||||
config = self.app.core.get_emane_model_config(
|
config = self.app.core.get_emane_model_config(
|
||||||
self.node.id, self.model, self.iface_id
|
self.node.id, self.model, self.iface_id
|
||||||
)
|
)
|
||||||
self.config: dict[str, ConfigOption] = config
|
self.config: Dict[str, ConfigOption] = config
|
||||||
self.draw()
|
self.draw()
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.show_grpc_exception("Get EMANE Config Error", e)
|
self.app.show_grpc_exception("Get EMANE Config Error", e)
|
||||||
|
@ -82,7 +80,7 @@ class EmaneConfigDialog(Dialog):
|
||||||
self.node: Node = node
|
self.node: Node = node
|
||||||
self.radiovar: tk.IntVar = tk.IntVar()
|
self.radiovar: tk.IntVar = tk.IntVar()
|
||||||
self.radiovar.set(1)
|
self.radiovar.set(1)
|
||||||
self.emane_models: list[str] = [
|
self.emane_models: List[str] = [
|
||||||
x.split("_")[1] for x in self.app.core.emane_models
|
x.split("_")[1] for x in self.app.core.emane_models
|
||||||
]
|
]
|
||||||
model = self.node.emane.split("_")[1]
|
model = self.node.emane.split("_")[1]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from core.api.grpc.wrappers import Hook, SessionState
|
from core.api.grpc.wrappers import Hook, SessionState
|
||||||
|
@ -91,13 +91,6 @@ class HookDialog(Dialog):
|
||||||
self.hook.file = file_name
|
self.hook.file = file_name
|
||||||
self.hook.data = data
|
self.hook.data = data
|
||||||
else:
|
else:
|
||||||
if file_name in self.app.core.session.hooks:
|
|
||||||
messagebox.showerror(
|
|
||||||
"Hook Error",
|
|
||||||
f"Hook {file_name} already exists!",
|
|
||||||
parent=self.master,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.hook = Hook(state=state, file=file_name, data=data)
|
self.hook = Hook(state=state, file=file_name, data=data)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ class IpConfigDialog(Dialog):
|
||||||
super().__init__(app, "IP Configuration")
|
super().__init__(app, "IP Configuration")
|
||||||
self.ip4: str = self.app.guiconfig.ips.ip4
|
self.ip4: str = self.app.guiconfig.ips.ip4
|
||||||
self.ip6: str = self.app.guiconfig.ips.ip6
|
self.ip6: str = self.app.guiconfig.ips.ip6
|
||||||
self.ip4s: list[str] = self.app.guiconfig.ips.ip4s
|
self.ip4s: List[str] = self.app.guiconfig.ips.ip4s
|
||||||
self.ip6s: list[str] = self.app.guiconfig.ips.ip6s
|
self.ip6s: List[str] = self.app.guiconfig.ips.ip6s
|
||||||
self.ip4_entry: Optional[ttk.Entry] = None
|
self.ip4_entry: Optional[ttk.Entry] = None
|
||||||
self.ip4_listbox: Optional[ListboxScroll] = None
|
self.ip4_listbox: Optional[ListboxScroll] = None
|
||||||
self.ip6_entry: Optional[ttk.Entry] = None
|
self.ip6_entry: Optional[ttk.Entry] = None
|
||||||
|
|
|
@ -3,7 +3,7 @@ mobility configuration
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class MobilityConfigDialog(Dialog):
|
||||||
config = self.node.mobility_config
|
config = self.node.mobility_config
|
||||||
if not config:
|
if not config:
|
||||||
config = self.app.core.get_mobility_config(self.node.id)
|
config = self.app.core.get_mobility_config(self.node.id)
|
||||||
self.config: dict[str, ConfigOption] = config
|
self.config: Dict[str, ConfigOption] = config
|
||||||
self.draw()
|
self.draw()
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.show_grpc_exception("Get Mobility Config Error", e)
|
self.app.show_grpc_exception("Get Mobility Config Error", e)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
@ -190,7 +190,7 @@ class NodeConfigDialog(Dialog):
|
||||||
if self.node.server:
|
if self.node.server:
|
||||||
server = self.node.server
|
server = self.node.server
|
||||||
self.server: tk.StringVar = tk.StringVar(value=server)
|
self.server: tk.StringVar = tk.StringVar(value=server)
|
||||||
self.ifaces: dict[int, InterfaceData] = {}
|
self.ifaces: Dict[int, InterfaceData] = {}
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
|
|
@ -4,7 +4,7 @@ core node services
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from core.api.grpc.wrappers import Node
|
from core.api.grpc.wrappers import Node
|
||||||
from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog
|
from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog
|
||||||
|
@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class NodeConfigServiceDialog(Dialog):
|
class NodeConfigServiceDialog(Dialog):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, app: "Application", node: Node, services: set[str] = None
|
self, app: "Application", node: Node, services: Set[str] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
title = f"{node.name} Config Services"
|
title = f"{node.name} Config Services"
|
||||||
super().__init__(app, title)
|
super().__init__(app, title)
|
||||||
|
@ -30,7 +30,7 @@ class NodeConfigServiceDialog(Dialog):
|
||||||
self.current: Optional[ListboxScroll] = None
|
self.current: Optional[ListboxScroll] = None
|
||||||
if services is None:
|
if services is None:
|
||||||
services = set(node.config_services)
|
services = set(node.config_services)
|
||||||
self.current_services: set[str] = services
|
self.current_services: Set[str] = services
|
||||||
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
|
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ core node services
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from core.api.grpc.wrappers import Node
|
from core.api.grpc.wrappers import Node
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -24,7 +24,7 @@ class NodeServiceDialog(Dialog):
|
||||||
self.services: Optional[CheckboxList] = None
|
self.services: Optional[CheckboxList] = None
|
||||||
self.current: Optional[ListboxScroll] = None
|
self.current: Optional[ListboxScroll] = None
|
||||||
services = set(node.services)
|
services = set(node.services)
|
||||||
self.current_services: set[str] = services
|
self.current_services: Set[str] = services
|
||||||
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
|
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
from core.gui import nodeutils as nutils
|
from core.gui import nodeutils as nutils
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -17,7 +17,7 @@ class RunToolDialog(Dialog):
|
||||||
self.cmd: tk.StringVar = tk.StringVar(value="ps ax")
|
self.cmd: tk.StringVar = tk.StringVar(value="ps ax")
|
||||||
self.result: Optional[CodeText] = None
|
self.result: Optional[CodeText] = None
|
||||||
self.node_list: Optional[ListboxScroll] = None
|
self.node_list: Optional[ListboxScroll] = None
|
||||||
self.executable_nodes: dict[str, int] = {}
|
self.executable_nodes: Dict[str, int] = {}
|
||||||
self.store_nodes()
|
self.store_nodes()
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import filedialog, messagebox, ttk
|
from tkinter import filedialog, messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
@ -35,21 +35,21 @@ class ServiceConfigDialog(Dialog):
|
||||||
self.service_name: str = service_name
|
self.service_name: str = service_name
|
||||||
self.radiovar: tk.IntVar = tk.IntVar(value=2)
|
self.radiovar: tk.IntVar = tk.IntVar(value=2)
|
||||||
self.metadata: str = ""
|
self.metadata: str = ""
|
||||||
self.filenames: list[str] = []
|
self.filenames: List[str] = []
|
||||||
self.dependencies: list[str] = []
|
self.dependencies: List[str] = []
|
||||||
self.executables: list[str] = []
|
self.executables: List[str] = []
|
||||||
self.startup_commands: list[str] = []
|
self.startup_commands: List[str] = []
|
||||||
self.validation_commands: list[str] = []
|
self.validation_commands: List[str] = []
|
||||||
self.shutdown_commands: list[str] = []
|
self.shutdown_commands: List[str] = []
|
||||||
self.default_startup: list[str] = []
|
self.default_startup: List[str] = []
|
||||||
self.default_validate: list[str] = []
|
self.default_validate: List[str] = []
|
||||||
self.default_shutdown: list[str] = []
|
self.default_shutdown: List[str] = []
|
||||||
self.validation_mode: Optional[ServiceValidationMode] = None
|
self.validation_mode: Optional[ServiceValidationMode] = None
|
||||||
self.validation_time: Optional[int] = None
|
self.validation_time: Optional[int] = None
|
||||||
self.validation_period: Optional[float] = None
|
self.validation_period: Optional[float] = None
|
||||||
self.directory_entry: Optional[ttk.Entry] = None
|
self.directory_entry: Optional[ttk.Entry] = None
|
||||||
self.default_directories: list[str] = []
|
self.default_directories: List[str] = []
|
||||||
self.temp_directories: list[str] = []
|
self.temp_directories: List[str] = []
|
||||||
self.documentnew_img: PhotoImage = self.app.get_enum_icon(
|
self.documentnew_img: PhotoImage = self.app.get_enum_icon(
|
||||||
ImageEnum.DOCUMENTNEW, width=ICON_SIZE
|
ImageEnum.DOCUMENTNEW, width=ICON_SIZE
|
||||||
)
|
)
|
||||||
|
@ -67,10 +67,10 @@ class ServiceConfigDialog(Dialog):
|
||||||
self.validation_mode_entry: Optional[ttk.Entry] = None
|
self.validation_mode_entry: Optional[ttk.Entry] = None
|
||||||
self.service_file_data: Optional[CodeText] = None
|
self.service_file_data: Optional[CodeText] = None
|
||||||
self.validation_period_entry: Optional[ttk.Entry] = None
|
self.validation_period_entry: Optional[ttk.Entry] = None
|
||||||
self.original_service_files: dict[str, str] = {}
|
self.original_service_files: Dict[str, str] = {}
|
||||||
self.default_config: Optional[NodeServiceData] = None
|
self.default_config: Optional[NodeServiceData] = None
|
||||||
self.temp_service_files: dict[str, str] = {}
|
self.temp_service_files: Dict[str, str] = {}
|
||||||
self.modified_files: set[str] = set()
|
self.modified_files: Set[str] = set()
|
||||||
self.has_error: bool = False
|
self.has_error: bool = False
|
||||||
self.load()
|
self.load()
|
||||||
if not self.has_error:
|
if not self.has_error:
|
||||||
|
@ -558,13 +558,13 @@ class ServiceConfigDialog(Dialog):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def append_commands(
|
def append_commands(
|
||||||
cls, commands: list[str], listbox: tk.Listbox, to_add: list[str]
|
cls, commands: List[str], listbox: tk.Listbox, to_add: List[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
for cmd in to_add:
|
for cmd in to_add:
|
||||||
commands.append(cmd)
|
commands.append(cmd)
|
||||||
listbox.insert(tk.END, cmd)
|
listbox.insert(tk.END, cmd)
|
||||||
|
|
||||||
def get_commands(self) -> tuple[list[str], list[str], list[str]]:
|
def get_commands(self) -> Tuple[List[str], List[str], List[str]]:
|
||||||
startup = self.startup_commands_listbox.get(0, "end")
|
startup = self.startup_commands_listbox.get(0, "end")
|
||||||
shutdown = self.shutdown_commands_listbox.get(0, "end")
|
shutdown = self.shutdown_commands_listbox.get(0, "end")
|
||||||
validate = self.validate_commands_listbox.get(0, "end")
|
validate = self.validate_commands_listbox.get(0, "end")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class SessionsDialog(Dialog):
|
||||||
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def get_sessions(self) -> list[SessionSummary]:
|
def get_sessions(self) -> List[SessionSummary]:
|
||||||
try:
|
try:
|
||||||
sessions = self.app.core.client.get_sessions()
|
sessions = self.app.core.client.get_sessions()
|
||||||
logger.info("sessions: %s", sessions)
|
logger.info("sessions: %s", sessions)
|
||||||
|
|
|
@ -3,7 +3,7 @@ shape input dialog
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -16,8 +16,8 @@ if TYPE_CHECKING:
|
||||||
from core.gui.graph.graph import CanvasGraph
|
from core.gui.graph.graph import CanvasGraph
|
||||||
from core.gui.graph.shape import Shape
|
from core.gui.graph.shape import Shape
|
||||||
|
|
||||||
FONT_SIZES: list[int] = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
FONT_SIZES: List[int] = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
||||||
BORDER_WIDTH: list[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
BORDER_WIDTH: List[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
|
||||||
class ShapeDialog(Dialog):
|
class ShapeDialog(Dialog):
|
||||||
|
@ -168,7 +168,7 @@ class ShapeDialog(Dialog):
|
||||||
self.add_text()
|
self.add_text()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def make_font(self) -> list[Union[int, str]]:
|
def make_font(self) -> List[Union[int, str]]:
|
||||||
"""
|
"""
|
||||||
create font for text or shape label
|
create font for text or shape label
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ class WirelessConfigDialog(Dialog):
|
||||||
super().__init__(app, f"Wireless Configuration - {canvas_node.core_node.name}")
|
super().__init__(app, f"Wireless Configuration - {canvas_node.core_node.name}")
|
||||||
self.node: Node = canvas_node.core_node
|
self.node: Node = canvas_node.core_node
|
||||||
self.config_frame: Optional[ConfigFrame] = None
|
self.config_frame: Optional[ConfigFrame] = None
|
||||||
self.config: dict[str, ConfigOption] = {}
|
self.config: Dict[str, ConfigOption] = {}
|
||||||
try:
|
try:
|
||||||
config = self.node.wireless_config
|
config = self.node.wireless_config
|
||||||
if not config:
|
if not config:
|
||||||
config = self.app.core.get_wireless_config(self.node.id)
|
config = self.app.core.get_wireless_config(self.node.id)
|
||||||
self.config: dict[str, ConfigOption] = config
|
self.config: Dict[str, ConfigOption] = config
|
||||||
self.draw()
|
self.draw()
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.show_grpc_exception("Wireless Config Error", e)
|
self.app.show_grpc_exception("Wireless Config Error", e)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -27,13 +27,13 @@ class WlanConfigDialog(Dialog):
|
||||||
self.config_frame: Optional[ConfigFrame] = None
|
self.config_frame: Optional[ConfigFrame] = None
|
||||||
self.range_entry: Optional[ttk.Entry] = None
|
self.range_entry: Optional[ttk.Entry] = None
|
||||||
self.has_error: bool = False
|
self.has_error: bool = False
|
||||||
self.ranges: dict[int, int] = {}
|
self.ranges: Dict[int, int] = {}
|
||||||
self.positive_int: int = self.app.master.register(self.validate_and_update)
|
self.positive_int: int = self.app.master.register(self.validate_and_update)
|
||||||
try:
|
try:
|
||||||
config = self.node.wlan_config
|
config = self.node.wlan_config
|
||||||
if not config:
|
if not config:
|
||||||
config = self.app.core.get_wlan_config(self.node.id)
|
config = self.app.core.get_wlan_config(self.node.id)
|
||||||
self.config: dict[str, ConfigOption] = config
|
self.config: Dict[str, ConfigOption] = config
|
||||||
self.init_draw_range()
|
self.init_draw_range()
|
||||||
self.draw()
|
self.draw()
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
|
|
|
@ -2,10 +2,10 @@ import functools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, Optional, Tuple, Union
|
||||||
|
|
||||||
from core.api.grpc.wrappers import Interface, Link
|
from core.api.grpc.wrappers import Interface, Link
|
||||||
from core.gui import nodeutils, themes
|
from core.gui import themes
|
||||||
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
||||||
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
|
from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
|
@ -54,9 +54,9 @@ def create_edge_token(link: Link) -> str:
|
||||||
|
|
||||||
def node_label_positions(
|
def node_label_positions(
|
||||||
src_x: int, src_y: int, dst_x: int, dst_y: int
|
src_x: int, src_y: int, dst_x: int, dst_y: int
|
||||||
) -> tuple[tuple[float, float], tuple[float, float]]:
|
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
||||||
v_x, v_y = dst_x - src_x, dst_y - src_y
|
v_x, v_y = dst_x - src_x, dst_y - src_y
|
||||||
v_len = math.sqrt(v_x**2 + v_y**2)
|
v_len = math.sqrt(v_x ** 2 + v_y ** 2)
|
||||||
if v_len == 0:
|
if v_len == 0:
|
||||||
u_x, u_y = 0.0, 0.0
|
u_x, u_y = 0.0, 0.0
|
||||||
else:
|
else:
|
||||||
|
@ -128,8 +128,8 @@ class Edge:
|
||||||
return self.width * self.app.app_scale
|
return self.width * self.app.app_scale
|
||||||
|
|
||||||
def _get_arcpoint(
|
def _get_arcpoint(
|
||||||
self, src_pos: tuple[float, float], dst_pos: tuple[float, float]
|
self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]
|
||||||
) -> tuple[float, float]:
|
) -> Tuple[float, float]:
|
||||||
src_x, src_y = src_pos
|
src_x, src_y = src_pos
|
||||||
dst_x, dst_y = dst_pos
|
dst_x, dst_y = dst_pos
|
||||||
mp_x = (src_x + dst_x) / 2
|
mp_x = (src_x + dst_x) / 2
|
||||||
|
@ -147,7 +147,7 @@ class Edge:
|
||||||
perp_m = -1 / m
|
perp_m = -1 / m
|
||||||
b = mp_y - (perp_m * mp_x)
|
b = mp_y - (perp_m * mp_x)
|
||||||
# get arc x and y
|
# get arc x and y
|
||||||
offset = math.sqrt(self.arc**2 / (1 + (1 / m**2)))
|
offset = math.sqrt(self.arc ** 2 / (1 + (1 / m ** 2)))
|
||||||
arc_x = mp_x
|
arc_x = mp_x
|
||||||
if self.arc >= 0:
|
if self.arc >= 0:
|
||||||
arc_x += offset
|
arc_x += offset
|
||||||
|
@ -317,7 +317,7 @@ class Edge:
|
||||||
if self.dst_label2:
|
if self.dst_label2:
|
||||||
self.dst.canvas.itemconfig(self.dst_label2, text=text)
|
self.dst.canvas.itemconfig(self.dst_label2, text=text)
|
||||||
|
|
||||||
def drawing(self, pos: tuple[float, float]) -> None:
|
def drawing(self, pos: Tuple[float, float]) -> None:
|
||||||
src_x, src_y, _, _, _, _ = self.src.canvas.coords(self.id)
|
src_x, src_y, _, _, _, _ = self.src.canvas.coords(self.id)
|
||||||
src_pos = src_x, src_y
|
src_pos = src_x, src_y
|
||||||
self.moved(src_pos, pos)
|
self.moved(src_pos, pos)
|
||||||
|
@ -368,7 +368,7 @@ class Edge:
|
||||||
dst_pos = dst_x, dst_y
|
dst_pos = dst_x, dst_y
|
||||||
self.moved(self.src.position(), dst_pos)
|
self.moved(self.src.position(), dst_pos)
|
||||||
|
|
||||||
def moved(self, src_pos: tuple[float, float], dst_pos: tuple[float, float]) -> None:
|
def moved(self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]) -> None:
|
||||||
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
||||||
self.src.canvas.coords(self.id, *src_pos, *arc_pos, *dst_pos)
|
self.src.canvas.coords(self.id, *src_pos, *arc_pos, *dst_pos)
|
||||||
if self.middle_label:
|
if self.middle_label:
|
||||||
|
@ -381,7 +381,7 @@ class Edge:
|
||||||
self.src.canvas.coords(self.dst_label, *dst_pos)
|
self.src.canvas.coords(self.dst_label, *dst_pos)
|
||||||
|
|
||||||
def moved2(
|
def moved2(
|
||||||
self, src_pos: tuple[float, float], dst_pos: tuple[float, float]
|
self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]
|
||||||
) -> None:
|
) -> None:
|
||||||
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
||||||
self.dst.canvas.coords(self.id2, *src_pos, *arc_pos, *dst_pos)
|
self.dst.canvas.coords(self.id2, *src_pos, *arc_pos, *dst_pos)
|
||||||
|
@ -568,7 +568,7 @@ class CanvasEdge(Edge):
|
||||||
label += f"{iface.ip6}/{iface.ip6_mask}"
|
label += f"{iface.ip6}/{iface.ip6_mask}"
|
||||||
return label
|
return label
|
||||||
|
|
||||||
def create_node_labels(self) -> tuple[str, str]:
|
def create_node_labels(self) -> Tuple[str, str]:
|
||||||
label1 = None
|
label1 = None
|
||||||
if self.link.iface1:
|
if self.link.iface1:
|
||||||
label1 = self.iface_label(self.link.iface1)
|
label1 = self.iface_label(self.link.iface1)
|
||||||
|
@ -638,10 +638,10 @@ class CanvasEdge(Edge):
|
||||||
self.check_wireless()
|
self.check_wireless()
|
||||||
if link is None:
|
if link is None:
|
||||||
link = self.app.core.ifaces_manager.create_link(self)
|
link = self.app.core.ifaces_manager.create_link(self)
|
||||||
if link.iface1 and not nodeutils.is_rj45(self.src.core_node):
|
if link.iface1:
|
||||||
iface1 = link.iface1
|
iface1 = link.iface1
|
||||||
self.src.ifaces[iface1.id] = iface1
|
self.src.ifaces[iface1.id] = iface1
|
||||||
if link.iface2 and not nodeutils.is_rj45(self.dst.core_node):
|
if link.iface2:
|
||||||
iface2 = link.iface2
|
iface2 = link.iface2
|
||||||
self.dst.ifaces[iface2.id] = iface2
|
self.dst.ifaces[iface2.id] = iface2
|
||||||
self.token = create_edge_token(link)
|
self.token = create_edge_token(link)
|
||||||
|
@ -751,9 +751,9 @@ class CanvasEdge(Edge):
|
||||||
self.src.edges.discard(self)
|
self.src.edges.discard(self)
|
||||||
if self.dst:
|
if self.dst:
|
||||||
self.dst.edges.discard(self)
|
self.dst.edges.discard(self)
|
||||||
if self.link.iface1 and not nodeutils.is_rj45(self.src.core_node):
|
if self.link.iface1:
|
||||||
del self.src.ifaces[self.link.iface1.id]
|
del self.src.ifaces[self.link.iface1.id]
|
||||||
if self.link.iface2 and not nodeutils.is_rj45(self.dst.core_node):
|
if self.link.iface2:
|
||||||
del self.dst.ifaces[self.link.iface2.id]
|
del self.dst.ifaces[self.link.iface2.id]
|
||||||
if self.src.is_wireless():
|
if self.src.is_wireless():
|
||||||
self.dst.delete_antenna()
|
self.dst.delete_antenna()
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
@ -27,8 +27,8 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
ZOOM_IN: float = 1.1
|
ZOOM_IN: float = 1.1
|
||||||
ZOOM_OUT: float = 0.9
|
ZOOM_OUT: float = 0.9
|
||||||
MOVE_NODE_MODES: set[GraphMode] = {GraphMode.NODE, GraphMode.SELECT}
|
MOVE_NODE_MODES: Set[GraphMode] = {GraphMode.NODE, GraphMode.SELECT}
|
||||||
MOVE_SHAPE_MODES: set[GraphMode] = {GraphMode.ANNOTATION, GraphMode.SELECT}
|
MOVE_SHAPE_MODES: Set[GraphMode] = {GraphMode.ANNOTATION, GraphMode.SELECT}
|
||||||
BACKGROUND_COLOR: str = "#cccccc"
|
BACKGROUND_COLOR: str = "#cccccc"
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,32 +40,32 @@ class CanvasGraph(tk.Canvas):
|
||||||
manager: "CanvasManager",
|
manager: "CanvasManager",
|
||||||
core: "CoreClient",
|
core: "CoreClient",
|
||||||
_id: int,
|
_id: int,
|
||||||
dimensions: tuple[int, int],
|
dimensions: Tuple[int, int],
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(master, highlightthickness=0, background=BACKGROUND_COLOR)
|
super().__init__(master, highlightthickness=0, background=BACKGROUND_COLOR)
|
||||||
self.id: int = _id
|
self.id: int = _id
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
self.manager: "CanvasManager" = manager
|
self.manager: "CanvasManager" = manager
|
||||||
self.core: "CoreClient" = core
|
self.core: "CoreClient" = core
|
||||||
self.selection: dict[int, int] = {}
|
self.selection: Dict[int, int] = {}
|
||||||
self.select_box: Optional[Shape] = None
|
self.select_box: Optional[Shape] = None
|
||||||
self.selected: Optional[int] = None
|
self.selected: Optional[int] = None
|
||||||
self.nodes: dict[int, CanvasNode] = {}
|
self.nodes: Dict[int, CanvasNode] = {}
|
||||||
self.shadow_nodes: dict[int, ShadowNode] = {}
|
self.shadow_nodes: Dict[int, ShadowNode] = {}
|
||||||
self.shapes: dict[int, Shape] = {}
|
self.shapes: Dict[int, Shape] = {}
|
||||||
self.shadow_core_nodes: dict[int, ShadowNode] = {}
|
self.shadow_core_nodes: Dict[int, ShadowNode] = {}
|
||||||
|
|
||||||
# map wireless/EMANE node to the set of MDRs connected to that node
|
# map wireless/EMANE node to the set of MDRs connected to that node
|
||||||
self.wireless_network: dict[int, set[int]] = {}
|
self.wireless_network: Dict[int, Set[int]] = {}
|
||||||
|
|
||||||
self.drawing_edge: Optional[CanvasEdge] = None
|
self.drawing_edge: Optional[CanvasEdge] = None
|
||||||
self.rect: Optional[int] = None
|
self.rect: Optional[int] = None
|
||||||
self.shape_drawing: bool = False
|
self.shape_drawing: bool = False
|
||||||
self.current_dimensions: tuple[int, int] = dimensions
|
self.current_dimensions: Tuple[int, int] = dimensions
|
||||||
self.ratio: float = 1.0
|
self.ratio: float = 1.0
|
||||||
self.offset: tuple[int, int] = (0, 0)
|
self.offset: Tuple[int, int] = (0, 0)
|
||||||
self.cursor: tuple[int, int] = (0, 0)
|
self.cursor: Tuple[int, int] = (0, 0)
|
||||||
self.to_copy: list[CanvasNode] = []
|
self.to_copy: List[CanvasNode] = []
|
||||||
|
|
||||||
# background related
|
# background related
|
||||||
self.wallpaper_id: Optional[int] = None
|
self.wallpaper_id: Optional[int] = None
|
||||||
|
@ -82,7 +82,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.draw_canvas()
|
self.draw_canvas()
|
||||||
self.draw_grid()
|
self.draw_grid()
|
||||||
|
|
||||||
def draw_canvas(self, dimensions: tuple[int, int] = None) -> None:
|
def draw_canvas(self, dimensions: Tuple[int, int] = None) -> None:
|
||||||
if self.rect is not None:
|
if self.rect is not None:
|
||||||
self.delete(self.rect)
|
self.delete(self.rect)
|
||||||
if not dimensions:
|
if not dimensions:
|
||||||
|
@ -126,23 +126,23 @@ class CanvasGraph(tk.Canvas):
|
||||||
shadow_node = ShadowNode(self.app, self, node)
|
shadow_node = ShadowNode(self.app, self, node)
|
||||||
return shadow_node
|
return shadow_node
|
||||||
|
|
||||||
def get_actual_coords(self, x: float, y: float) -> tuple[float, float]:
|
def get_actual_coords(self, x: float, y: float) -> Tuple[float, float]:
|
||||||
actual_x = (x - self.offset[0]) / self.ratio
|
actual_x = (x - self.offset[0]) / self.ratio
|
||||||
actual_y = (y - self.offset[1]) / self.ratio
|
actual_y = (y - self.offset[1]) / self.ratio
|
||||||
return actual_x, actual_y
|
return actual_x, actual_y
|
||||||
|
|
||||||
def get_scaled_coords(self, x: float, y: float) -> tuple[float, float]:
|
def get_scaled_coords(self, x: float, y: float) -> Tuple[float, float]:
|
||||||
scaled_x = (x * self.ratio) + self.offset[0]
|
scaled_x = (x * self.ratio) + self.offset[0]
|
||||||
scaled_y = (y * self.ratio) + self.offset[1]
|
scaled_y = (y * self.ratio) + self.offset[1]
|
||||||
return scaled_x, scaled_y
|
return scaled_x, scaled_y
|
||||||
|
|
||||||
def inside_canvas(self, x: float, y: float) -> tuple[bool, bool]:
|
def inside_canvas(self, x: float, y: float) -> Tuple[bool, bool]:
|
||||||
x1, y1, x2, y2 = self.bbox(self.rect)
|
x1, y1, x2, y2 = self.bbox(self.rect)
|
||||||
valid_x = x1 <= x <= x2
|
valid_x = x1 <= x <= x2
|
||||||
valid_y = y1 <= y <= y2
|
valid_y = y1 <= y <= y2
|
||||||
return valid_x and valid_y
|
return valid_x and valid_y
|
||||||
|
|
||||||
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> tuple[bool, bool]:
|
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> Tuple[bool, bool]:
|
||||||
valid_topleft = self.inside_canvas(x1, y1)
|
valid_topleft = self.inside_canvas(x1, y1)
|
||||||
valid_bottomright = self.inside_canvas(x2, y2)
|
valid_bottomright = self.inside_canvas(x2, y2)
|
||||||
return valid_topleft and valid_bottomright
|
return valid_topleft and valid_bottomright
|
||||||
|
@ -161,7 +161,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.tag_lower(tags.GRIDLINE)
|
self.tag_lower(tags.GRIDLINE)
|
||||||
self.tag_lower(self.rect)
|
self.tag_lower(self.rect)
|
||||||
|
|
||||||
def canvas_xy(self, event: tk.Event) -> tuple[float, float]:
|
def canvas_xy(self, event: tk.Event) -> Tuple[float, float]:
|
||||||
"""
|
"""
|
||||||
Convert window coordinate to canvas coordinate
|
Convert window coordinate to canvas coordinate
|
||||||
"""
|
"""
|
||||||
|
@ -516,7 +516,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.nodes[node.id] = node
|
self.nodes[node.id] = node
|
||||||
self.core.set_canvas_node(core_node, node)
|
self.core.set_canvas_node(core_node, node)
|
||||||
|
|
||||||
def width_and_height(self) -> tuple[int, int]:
|
def width_and_height(self) -> Tuple[int, int]:
|
||||||
"""
|
"""
|
||||||
retrieve canvas width and height in pixels
|
retrieve canvas width and height in pixels
|
||||||
"""
|
"""
|
||||||
|
@ -601,7 +601,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.redraw_canvas((image.width(), image.height()))
|
self.redraw_canvas((image.width(), image.height()))
|
||||||
self.draw_wallpaper(image)
|
self.draw_wallpaper(image)
|
||||||
|
|
||||||
def redraw_canvas(self, dimensions: tuple[int, int] = None) -> None:
|
def redraw_canvas(self, dimensions: Tuple[int, int] = None) -> None:
|
||||||
logger.debug("redrawing canvas to dimensions: %s", dimensions)
|
logger.debug("redrawing canvas to dimensions: %s", dimensions)
|
||||||
|
|
||||||
# reset scale and move back to original position
|
# reset scale and move back to original position
|
||||||
|
@ -814,7 +814,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
for edge_id in self.find_withtag(tags.EDGE):
|
for edge_id in self.find_withtag(tags.EDGE):
|
||||||
self.itemconfig(edge_id, width=int(EDGE_WIDTH * self.app.app_scale))
|
self.itemconfig(edge_id, width=int(EDGE_WIDTH * self.app.app_scale))
|
||||||
|
|
||||||
def get_metadata(self) -> dict[str, Any]:
|
def get_metadata(self) -> Dict[str, Any]:
|
||||||
wallpaper_path = None
|
wallpaper_path = None
|
||||||
if self.wallpaper_file:
|
if self.wallpaper_file:
|
||||||
wallpaper = Path(self.wallpaper_file)
|
wallpaper = Path(self.wallpaper_file)
|
||||||
|
@ -830,7 +830,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
dimensions=self.current_dimensions,
|
dimensions=self.current_dimensions,
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_metadata(self, config: dict[str, Any]) -> None:
|
def parse_metadata(self, config: Dict[str, Any]) -> None:
|
||||||
fit_image = config.get("fit_image", False)
|
fit_image = config.get("fit_image", False)
|
||||||
self.adjust_to_dim.set(fit_image)
|
self.adjust_to_dim.set(fit_image)
|
||||||
wallpaper_style = config.get("wallpaper_style", 1)
|
wallpaper_style = config.get("wallpaper_style", 1)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from collections.abc import ValuesView
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from tkinter import BooleanVar, messagebox, ttk
|
from tkinter import BooleanVar, messagebox, ttk
|
||||||
from typing import TYPE_CHECKING, Any, Literal, Optional
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView
|
||||||
|
|
||||||
from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent
|
from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent
|
||||||
from core.gui import nodeutils as nutils
|
from core.gui import nodeutils as nutils
|
||||||
|
@ -35,7 +34,7 @@ class ShowVar(BooleanVar):
|
||||||
self.manager: "CanvasManager" = manager
|
self.manager: "CanvasManager" = manager
|
||||||
self.tag: str = tag
|
self.tag: str = tag
|
||||||
|
|
||||||
def state(self) -> Literal["normal", "hidden"]:
|
def state(self) -> str:
|
||||||
return tk.NORMAL if self.get() else tk.HIDDEN
|
return tk.NORMAL if self.get() else tk.HIDDEN
|
||||||
|
|
||||||
def click_handler(self) -> None:
|
def click_handler(self) -> None:
|
||||||
|
@ -79,14 +78,14 @@ class CanvasManager:
|
||||||
self.mode: GraphMode = GraphMode.SELECT
|
self.mode: GraphMode = GraphMode.SELECT
|
||||||
self.annotation_type: Optional[ShapeType] = None
|
self.annotation_type: Optional[ShapeType] = None
|
||||||
self.node_draw: Optional[NodeDraw] = None
|
self.node_draw: Optional[NodeDraw] = None
|
||||||
self.canvases: dict[int, CanvasGraph] = {}
|
self.canvases: Dict[int, CanvasGraph] = {}
|
||||||
|
|
||||||
# global edge management
|
# global edge management
|
||||||
self.edges: dict[str, CanvasEdge] = {}
|
self.edges: Dict[str, CanvasEdge] = {}
|
||||||
self.wireless_edges: dict[str, CanvasWirelessEdge] = {}
|
self.wireless_edges: Dict[str, CanvasWirelessEdge] = {}
|
||||||
|
|
||||||
# global canvas settings
|
# global canvas settings
|
||||||
self.default_dimensions: tuple[int, int] = (
|
self.default_dimensions: Tuple[int, int] = (
|
||||||
self.app.guiconfig.preferences.width,
|
self.app.guiconfig.preferences.width,
|
||||||
self.app.guiconfig.preferences.height,
|
self.app.guiconfig.preferences.height,
|
||||||
)
|
)
|
||||||
|
@ -112,8 +111,8 @@ class CanvasManager:
|
||||||
|
|
||||||
# widget
|
# widget
|
||||||
self.notebook: Optional[ttk.Notebook] = None
|
self.notebook: Optional[ttk.Notebook] = None
|
||||||
self.canvas_ids: dict[str, int] = {}
|
self.canvas_ids: Dict[str, int] = {}
|
||||||
self.unique_ids: dict[int, str] = {}
|
self.unique_ids: Dict[int, str] = {}
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
self.setup_bindings()
|
self.setup_bindings()
|
||||||
|
@ -274,17 +273,17 @@ class CanvasManager:
|
||||||
if not self.canvases:
|
if not self.canvases:
|
||||||
self.add_canvas()
|
self.add_canvas()
|
||||||
|
|
||||||
def redraw_canvas(self, dimensions: tuple[int, int]) -> None:
|
def redraw_canvas(self, dimensions: Tuple[int, int]) -> None:
|
||||||
canvas = self.current()
|
canvas = self.current()
|
||||||
canvas.redraw_canvas(dimensions)
|
canvas.redraw_canvas(dimensions)
|
||||||
if canvas.wallpaper:
|
if canvas.wallpaper:
|
||||||
canvas.redraw_wallpaper()
|
canvas.redraw_wallpaper()
|
||||||
|
|
||||||
def get_metadata(self) -> dict[str, Any]:
|
def get_metadata(self) -> Dict[str, Any]:
|
||||||
canvases = [x.get_metadata() for x in self.all()]
|
canvases = [x.get_metadata() for x in self.all()]
|
||||||
return dict(gridlines=self.show_grid.get(), canvases=canvases)
|
return dict(gridlines=self.show_grid.get(), canvases=canvases)
|
||||||
|
|
||||||
def parse_metadata_canvas(self, metadata: dict[str, Any]) -> None:
|
def parse_metadata_canvas(self, metadata: Dict[str, Any]) -> None:
|
||||||
# canvas setting
|
# canvas setting
|
||||||
canvas_config = metadata.get("canvas")
|
canvas_config = metadata.get("canvas")
|
||||||
logger.debug("canvas metadata: %s", canvas_config)
|
logger.debug("canvas metadata: %s", canvas_config)
|
||||||
|
@ -304,7 +303,7 @@ class CanvasManager:
|
||||||
canvas = self.get(canvas_id)
|
canvas = self.get(canvas_id)
|
||||||
canvas.parse_metadata(canvas_config)
|
canvas.parse_metadata(canvas_config)
|
||||||
|
|
||||||
def parse_metadata_shapes(self, metadata: dict[str, Any]) -> None:
|
def parse_metadata_shapes(self, metadata: Dict[str, Any]) -> None:
|
||||||
# load saved shapes
|
# load saved shapes
|
||||||
shapes_config = metadata.get("shapes")
|
shapes_config = metadata.get("shapes")
|
||||||
if not shapes_config:
|
if not shapes_config:
|
||||||
|
@ -314,7 +313,7 @@ class CanvasManager:
|
||||||
logger.debug("loading shape: %s", shape_config)
|
logger.debug("loading shape: %s", shape_config)
|
||||||
Shape.from_metadata(self.app, shape_config)
|
Shape.from_metadata(self.app, shape_config)
|
||||||
|
|
||||||
def parse_metadata_edges(self, metadata: dict[str, Any]) -> None:
|
def parse_metadata_edges(self, metadata: Dict[str, Any]) -> None:
|
||||||
# load edges config
|
# load edges config
|
||||||
edges_config = metadata.get("edges")
|
edges_config = metadata.get("edges")
|
||||||
if not edges_config:
|
if not edges_config:
|
||||||
|
@ -331,7 +330,7 @@ class CanvasManager:
|
||||||
else:
|
else:
|
||||||
logger.warning("invalid edge token to configure: %s", edge_token)
|
logger.warning("invalid edge token to configure: %s", edge_token)
|
||||||
|
|
||||||
def parse_metadata_hidden(self, metadata: dict[str, Any]) -> None:
|
def parse_metadata_hidden(self, metadata: Dict[str, Any]) -> None:
|
||||||
# read hidden nodes
|
# read hidden nodes
|
||||||
hidden_config = metadata.get("hidden")
|
hidden_config = metadata.get("hidden")
|
||||||
if not hidden_config:
|
if not hidden_config:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import functools
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
@ -62,17 +62,17 @@ class CanvasNode:
|
||||||
state=self.app.manager.show_node_labels.state(),
|
state=self.app.manager.show_node_labels.state(),
|
||||||
)
|
)
|
||||||
self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas)
|
self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas)
|
||||||
self.edges: set[CanvasEdge] = set()
|
self.edges: Set[CanvasEdge] = set()
|
||||||
self.ifaces: dict[int, Interface] = {}
|
self.ifaces: Dict[int, Interface] = {}
|
||||||
self.wireless_edges: set[CanvasWirelessEdge] = set()
|
self.wireless_edges: Set[CanvasWirelessEdge] = set()
|
||||||
self.antennas: list[int] = []
|
self.antennas: List[int] = []
|
||||||
self.antenna_images: dict[int, PhotoImage] = {}
|
self.antenna_images: Dict[int, PhotoImage] = {}
|
||||||
self.hidden: bool = False
|
self.hidden: bool = False
|
||||||
self.setup_bindings()
|
self.setup_bindings()
|
||||||
self.context: tk.Menu = tk.Menu(self.canvas)
|
self.context: tk.Menu = tk.Menu(self.canvas)
|
||||||
themes.style_menu(self.context)
|
themes.style_menu(self.context)
|
||||||
|
|
||||||
def position(self) -> tuple[int, int]:
|
def position(self) -> Tuple[int, int]:
|
||||||
return self.canvas.coords(self.id)
|
return self.canvas.coords(self.id)
|
||||||
|
|
||||||
def next_iface_id(self) -> int:
|
def next_iface_id(self) -> int:
|
||||||
|
@ -543,7 +543,7 @@ class ShadowNode:
|
||||||
self.canvas.shadow_nodes[self.id] = self
|
self.canvas.shadow_nodes[self.id] = self
|
||||||
self.canvas.shadow_core_nodes[self.node.core_node.id] = self
|
self.canvas.shadow_core_nodes[self.node.core_node.id] = self
|
||||||
|
|
||||||
def position(self) -> tuple[int, int]:
|
def position(self) -> Tuple[int, int]:
|
||||||
return self.canvas.coords(self.id)
|
return self.canvas.coords(self.id)
|
||||||
|
|
||||||
def should_delete(self) -> bool:
|
def should_delete(self) -> bool:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from core.gui.dialogs.shapemod import ShapeDialog
|
from core.gui.dialogs.shapemod import ShapeDialog
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
|
@ -72,7 +72,7 @@ class Shape:
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_metadata(cls, app: "Application", config: dict[str, Any]) -> None:
|
def from_metadata(cls, app: "Application", config: Dict[str, Any]) -> None:
|
||||||
shape_type = config["type"]
|
shape_type = config["type"]
|
||||||
try:
|
try:
|
||||||
shape_type = ShapeType(shape_type)
|
shape_type = ShapeType(shape_type)
|
||||||
|
@ -144,7 +144,7 @@ class Shape:
|
||||||
logger.error("unknown shape type: %s", self.shape_type)
|
logger.error("unknown shape type: %s", self.shape_type)
|
||||||
self.created = True
|
self.created = True
|
||||||
|
|
||||||
def get_font(self) -> list[Union[int, str]]:
|
def get_font(self) -> List[Union[int, str]]:
|
||||||
font = [self.shape_data.font, self.shape_data.font_size]
|
font = [self.shape_data.font, self.shape_data.font_size]
|
||||||
if self.shape_data.bold:
|
if self.shape_data.bold:
|
||||||
font.append("bold")
|
font.append("bold")
|
||||||
|
@ -198,7 +198,7 @@ class Shape:
|
||||||
self.canvas.delete(self.id)
|
self.canvas.delete(self.id)
|
||||||
self.canvas.delete(self.text_id)
|
self.canvas.delete(self.text_id)
|
||||||
|
|
||||||
def metadata(self) -> dict[str, Union[str, int, bool]]:
|
def metadata(self) -> Dict[str, Union[str, int, bool]]:
|
||||||
coords = self.canvas.coords(self.id)
|
coords = self.canvas.coords(self.id)
|
||||||
# update coords to actual positions
|
# update coords to actual positions
|
||||||
if len(coords) == 4:
|
if len(coords) == 4:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import enum
|
import enum
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
|
||||||
class ShapeType(enum.Enum):
|
class ShapeType(enum.Enum):
|
||||||
|
@ -8,7 +9,7 @@ class ShapeType(enum.Enum):
|
||||||
TEXT = "text"
|
TEXT = "text"
|
||||||
|
|
||||||
|
|
||||||
SHAPES: set[ShapeType] = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
SHAPES: Set[ShapeType] = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
||||||
|
|
||||||
|
|
||||||
def is_draw_shape(shape_type: ShapeType) -> bool:
|
def is_draw_shape(shape_type: ShapeType) -> bool:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
ANNOTATION: str = "annotation"
|
ANNOTATION: str = "annotation"
|
||||||
GRIDLINE: str = "gridline"
|
GRIDLINE: str = "gridline"
|
||||||
SHAPE: str = "shape"
|
SHAPE: str = "shape"
|
||||||
|
@ -13,7 +15,7 @@ WALLPAPER: str = "wallpaper"
|
||||||
SELECTION: str = "selectednodes"
|
SELECTION: str = "selectednodes"
|
||||||
MARKER: str = "marker"
|
MARKER: str = "marker"
|
||||||
HIDDEN: str = "hidden"
|
HIDDEN: str = "hidden"
|
||||||
ORGANIZE_TAGS: list[str] = [
|
ORGANIZE_TAGS: List[str] = [
|
||||||
WALLPAPER,
|
WALLPAPER,
|
||||||
GRIDLINE,
|
GRIDLINE,
|
||||||
SHAPE,
|
SHAPE,
|
||||||
|
@ -27,7 +29,7 @@ ORGANIZE_TAGS: list[str] = [
|
||||||
SELECTION,
|
SELECTION,
|
||||||
MARKER,
|
MARKER,
|
||||||
]
|
]
|
||||||
RESET_TAGS: list[str] = [
|
RESET_TAGS: List[str] = [
|
||||||
EDGE,
|
EDGE,
|
||||||
NODE,
|
NODE,
|
||||||
NODE_LABEL,
|
NODE_LABEL,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, Tuple
|
||||||
|
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ class CanvasTooltip:
|
||||||
self,
|
self,
|
||||||
canvas: "CanvasGraph",
|
canvas: "CanvasGraph",
|
||||||
*,
|
*,
|
||||||
pad: tuple[int, int, int, int] = (5, 3, 5, 3),
|
pad: Tuple[int, int, int, int] = (5, 3, 5, 3),
|
||||||
waittime: int = 400,
|
waittime: int = 400,
|
||||||
wraplength: int = 600,
|
wraplength: int = 600
|
||||||
) -> None:
|
) -> None:
|
||||||
# in miliseconds, originally 500
|
# in miliseconds, originally 500
|
||||||
self.waittime: int = waittime
|
self.waittime: int = waittime
|
||||||
|
@ -37,7 +37,7 @@ class CanvasTooltip:
|
||||||
self.wraplength: int = wraplength
|
self.wraplength: int = wraplength
|
||||||
self.canvas: "CanvasGraph" = canvas
|
self.canvas: "CanvasGraph" = canvas
|
||||||
self.text: tk.StringVar = tk.StringVar()
|
self.text: tk.StringVar = tk.StringVar()
|
||||||
self.pad: tuple[int, int, int, int] = pad
|
self.pad: Tuple[int, int, int, int] = pad
|
||||||
self.id: Optional[str] = None
|
self.id: Optional[str] = None
|
||||||
self.tw: Optional[tk.Toplevel] = None
|
self.tw: Optional[tk.Toplevel] = None
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ class CanvasTooltip:
|
||||||
canvas: "CanvasGraph",
|
canvas: "CanvasGraph",
|
||||||
label: ttk.Label,
|
label: ttk.Label,
|
||||||
*,
|
*,
|
||||||
tip_delta: tuple[int, int] = (10, 5),
|
tip_delta: Tuple[int, int] = (10, 5),
|
||||||
pad: tuple[int, int, int, int] = (5, 3, 5, 3),
|
pad: Tuple[int, int, int, int] = (5, 3, 5, 3)
|
||||||
):
|
):
|
||||||
c = canvas
|
c = canvas
|
||||||
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
||||||
|
@ -112,7 +112,7 @@ class CanvasTooltip:
|
||||||
)
|
)
|
||||||
label.grid(padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW)
|
label.grid(padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW)
|
||||||
x, y = tip_pos_calculator(canvas, label, pad=pad)
|
x, y = tip_pos_calculator(canvas, label, pad=pad)
|
||||||
self.tw.wm_geometry(f"+{x:d}+{y:d}")
|
self.tw.wm_geometry("+%d+%d" % (x, y))
|
||||||
|
|
||||||
def hide(self) -> None:
|
def hide(self) -> None:
|
||||||
if self.tw:
|
if self.tw:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
@ -12,7 +12,7 @@ ANTENNA_SIZE: int = 32
|
||||||
BUTTON_SIZE: int = 16
|
BUTTON_SIZE: int = 16
|
||||||
ERROR_SIZE: int = 24
|
ERROR_SIZE: int = 24
|
||||||
DIALOG_SIZE: int = 16
|
DIALOG_SIZE: int = 16
|
||||||
IMAGES: dict[str, str] = {}
|
IMAGES: Dict[str, str] = {}
|
||||||
|
|
||||||
|
|
||||||
def load_all() -> None:
|
def load_all() -> None:
|
||||||
|
@ -78,7 +78,6 @@ class ImageEnum(Enum):
|
||||||
EDITDELETE = "edit-delete"
|
EDITDELETE = "edit-delete"
|
||||||
ANTENNA = "antenna"
|
ANTENNA = "antenna"
|
||||||
DOCKER = "docker"
|
DOCKER = "docker"
|
||||||
PODMAN = "podman"
|
|
||||||
LXC = "lxc"
|
LXC = "lxc"
|
||||||
ALERT = "alert"
|
ALERT = "alert"
|
||||||
DELETE = "delete"
|
DELETE = "delete"
|
||||||
|
@ -88,7 +87,7 @@ class ImageEnum(Enum):
|
||||||
SHADOW = "shadow"
|
SHADOW = "shadow"
|
||||||
|
|
||||||
|
|
||||||
TYPE_MAP: dict[tuple[NodeType, str], ImageEnum] = {
|
TYPE_MAP: Dict[Tuple[NodeType, str], ImageEnum] = {
|
||||||
(NodeType.DEFAULT, "router"): ImageEnum.ROUTER,
|
(NodeType.DEFAULT, "router"): ImageEnum.ROUTER,
|
||||||
(NodeType.DEFAULT, "PC"): ImageEnum.PC,
|
(NodeType.DEFAULT, "PC"): ImageEnum.PC,
|
||||||
(NodeType.DEFAULT, "host"): ImageEnum.HOST,
|
(NodeType.DEFAULT, "host"): ImageEnum.HOST,
|
||||||
|
@ -102,7 +101,6 @@ TYPE_MAP: dict[tuple[NodeType, str], ImageEnum] = {
|
||||||
(NodeType.RJ45, None): ImageEnum.RJ45,
|
(NodeType.RJ45, None): ImageEnum.RJ45,
|
||||||
(NodeType.TUNNEL, None): ImageEnum.TUNNEL,
|
(NodeType.TUNNEL, None): ImageEnum.TUNNEL,
|
||||||
(NodeType.DOCKER, None): ImageEnum.DOCKER,
|
(NodeType.DOCKER, None): ImageEnum.DOCKER,
|
||||||
(NodeType.PODMAN, None): ImageEnum.PODMAN,
|
|
||||||
(NodeType.LXC, None): ImageEnum.LXC,
|
(NodeType.LXC, None): ImageEnum.LXC,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from netaddr import EUI, IPNetwork
|
from netaddr import EUI, IPNetwork
|
||||||
|
@ -43,7 +43,7 @@ class Subnets:
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(self.key())
|
return hash(self.key())
|
||||||
|
|
||||||
def key(self) -> tuple[IPNetwork, IPNetwork]:
|
def key(self) -> Tuple[IPNetwork, IPNetwork]:
|
||||||
return self.ip4, self.ip6
|
return self.ip4, self.ip6
|
||||||
|
|
||||||
def next(self) -> "Subnets":
|
def next(self) -> "Subnets":
|
||||||
|
@ -61,8 +61,8 @@ class InterfaceManager:
|
||||||
self.mac: EUI = EUI(mac, dialect=netaddr.mac_unix_expanded)
|
self.mac: EUI = EUI(mac, dialect=netaddr.mac_unix_expanded)
|
||||||
self.current_mac: Optional[EUI] = None
|
self.current_mac: Optional[EUI] = None
|
||||||
self.current_subnets: Optional[Subnets] = None
|
self.current_subnets: Optional[Subnets] = None
|
||||||
self.used_subnets: dict[tuple[IPNetwork, IPNetwork], Subnets] = {}
|
self.used_subnets: Dict[Tuple[IPNetwork, IPNetwork], Subnets] = {}
|
||||||
self.used_macs: set[str] = set()
|
self.used_macs: Set[str] = set()
|
||||||
|
|
||||||
def update_ips(self, ip4: str, ip6: str) -> None:
|
def update_ips(self, ip4: str, ip6: str) -> None:
|
||||||
self.reset()
|
self.reset()
|
||||||
|
@ -91,7 +91,7 @@ class InterfaceManager:
|
||||||
self.current_subnets = None
|
self.current_subnets = None
|
||||||
self.used_subnets.clear()
|
self.used_subnets.clear()
|
||||||
|
|
||||||
def removed(self, links: list[Link]) -> None:
|
def removed(self, links: List[Link]) -> None:
|
||||||
# get remaining subnets
|
# get remaining subnets
|
||||||
remaining_subnets = set()
|
remaining_subnets = set()
|
||||||
for edge in self.app.core.links.values():
|
for edge in self.app.core.links.values():
|
||||||
|
@ -121,7 +121,7 @@ class InterfaceManager:
|
||||||
subnets.used_indexes.discard(index)
|
subnets.used_indexes.discard(index)
|
||||||
self.current_subnets = None
|
self.current_subnets = None
|
||||||
|
|
||||||
def set_macs(self, links: list[Link]) -> None:
|
def set_macs(self, links: List[Link]) -> None:
|
||||||
self.current_mac = self.mac
|
self.current_mac = self.mac
|
||||||
self.used_macs.clear()
|
self.used_macs.clear()
|
||||||
for link in links:
|
for link in links:
|
||||||
|
@ -130,7 +130,7 @@ class InterfaceManager:
|
||||||
if link.iface2:
|
if link.iface2:
|
||||||
self.used_macs.add(link.iface2.mac)
|
self.used_macs.add(link.iface2.mac)
|
||||||
|
|
||||||
def joined(self, links: list[Link]) -> None:
|
def joined(self, links: List[Link]) -> None:
|
||||||
ifaces = []
|
ifaces = []
|
||||||
for link in links:
|
for link in links:
|
||||||
if link.iface1:
|
if link.iface1:
|
||||||
|
@ -208,7 +208,7 @@ class InterfaceManager:
|
||||||
logger.info("ignoring subnet change for link between network nodes")
|
logger.info("ignoring subnet change for link between network nodes")
|
||||||
|
|
||||||
def find_subnets(
|
def find_subnets(
|
||||||
self, canvas_node: CanvasNode, visited: set[int] = None
|
self, canvas_node: CanvasNode, visited: Set[int] = None
|
||||||
) -> Optional[IPNetwork]:
|
) -> Optional[IPNetwork]:
|
||||||
logger.info("finding subnet for node: %s", canvas_node.core_node.name)
|
logger.info("finding subnet for node: %s", canvas_node.core_node.name)
|
||||||
subnets = None
|
subnets = None
|
||||||
|
|
|
@ -235,11 +235,7 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
label="Configure Throughput", command=self.click_config_throughput
|
label="Configure Throughput", command=self.click_config_throughput
|
||||||
)
|
)
|
||||||
menu.add_checkbutton(
|
menu.add_checkbutton(label="Enable Throughput?", command=self.click_throughput)
|
||||||
label="Enable Throughput?",
|
|
||||||
command=self.click_throughput,
|
|
||||||
variable=self.core.show_throughputs,
|
|
||||||
)
|
|
||||||
widget_menu.add_cascade(label="Throughput", menu=menu)
|
widget_menu.add_cascade(label="Throughput", menu=menu)
|
||||||
|
|
||||||
def draw_widgets_menu(self) -> None:
|
def draw_widgets_menu(self) -> None:
|
||||||
|
@ -397,7 +393,7 @@ class Menubar(tk.Menu):
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def click_throughput(self) -> None:
|
def click_throughput(self) -> None:
|
||||||
if self.core.show_throughputs.get():
|
if not self.core.handling_throughputs:
|
||||||
self.core.enable_throughputs()
|
self.core.enable_throughputs()
|
||||||
else:
|
else:
|
||||||
self.core.cancel_throughputs()
|
self.core.cancel_throughputs()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, List, Optional, Set
|
||||||
|
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
|
||||||
|
@ -13,27 +13,22 @@ logger = logging.getLogger(__name__)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
|
||||||
NODES: list["NodeDraw"] = []
|
NODES: List["NodeDraw"] = []
|
||||||
NETWORK_NODES: list["NodeDraw"] = []
|
NETWORK_NODES: List["NodeDraw"] = []
|
||||||
NODE_ICONS = {}
|
NODE_ICONS = {}
|
||||||
CONTAINER_NODES: set[NodeType] = {
|
CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
|
||||||
NodeType.DEFAULT,
|
IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC}
|
||||||
NodeType.DOCKER,
|
WIRELESS_NODES: Set[NodeType] = {
|
||||||
NodeType.LXC,
|
|
||||||
NodeType.PODMAN,
|
|
||||||
}
|
|
||||||
IMAGE_NODES: set[NodeType] = {NodeType.DOCKER, NodeType.LXC, NodeType.PODMAN}
|
|
||||||
WIRELESS_NODES: set[NodeType] = {
|
|
||||||
NodeType.WIRELESS_LAN,
|
NodeType.WIRELESS_LAN,
|
||||||
NodeType.EMANE,
|
NodeType.EMANE,
|
||||||
NodeType.WIRELESS,
|
NodeType.WIRELESS,
|
||||||
}
|
}
|
||||||
RJ45_NODES: set[NodeType] = {NodeType.RJ45}
|
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
|
||||||
BRIDGE_NODES: set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
|
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
|
||||||
IGNORE_NODES: set[NodeType] = {NodeType.CONTROL_NET}
|
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
|
||||||
MOBILITY_NODES: set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
||||||
NODE_MODELS: set[str] = {"router", "PC", "mdr", "prouter"}
|
NODE_MODELS: Set[str] = {"router", "PC", "mdr", "prouter"}
|
||||||
ROUTER_NODES: set[str] = {"router", "mdr"}
|
ROUTER_NODES: Set[str] = {"router", "mdr"}
|
||||||
ANTENNA_ICON: Optional[PhotoImage] = None
|
ANTENNA_ICON: Optional[PhotoImage] = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +41,6 @@ def setup() -> None:
|
||||||
(ImageEnum.PROUTER, NodeType.DEFAULT, "PRouter", "prouter"),
|
(ImageEnum.PROUTER, NodeType.DEFAULT, "PRouter", "prouter"),
|
||||||
(ImageEnum.DOCKER, NodeType.DOCKER, "Docker", None),
|
(ImageEnum.DOCKER, NodeType.DOCKER, "Docker", None),
|
||||||
(ImageEnum.LXC, NodeType.LXC, "LXC", None),
|
(ImageEnum.LXC, NodeType.LXC, "LXC", None),
|
||||||
(ImageEnum.PODMAN, NodeType.PODMAN, "Podman", None),
|
|
||||||
]
|
]
|
||||||
for image_enum, node_type, label, model in nodes:
|
for image_enum, node_type, label, model in nodes:
|
||||||
node_draw = NodeDraw.from_setup(image_enum, node_type, label, model)
|
node_draw = NodeDraw.from_setup(image_enum, node_type, label, model)
|
||||||
|
@ -112,7 +106,7 @@ def is_iface_node(node: Node) -> bool:
|
||||||
return is_container(node) or is_bridge(node)
|
return is_container(node) or is_bridge(node)
|
||||||
|
|
||||||
|
|
||||||
def get_custom_services(gui_config: GuiConfig, name: str) -> list[str]:
|
def get_custom_services(gui_config: GuiConfig, name: str) -> List[str]:
|
||||||
for custom_node in gui_config.nodes:
|
for custom_node in gui_config.nodes:
|
||||||
if custom_node.name == name:
|
if custom_node.name == name:
|
||||||
return custom_node.services
|
return custom_node.services
|
||||||
|
@ -160,7 +154,7 @@ class NodeDraw:
|
||||||
self.image_file: Optional[str] = None
|
self.image_file: Optional[str] = None
|
||||||
self.node_type: Optional[NodeType] = None
|
self.node_type: Optional[NodeType] = None
|
||||||
self.model: Optional[str] = None
|
self.model: Optional[str] = None
|
||||||
self.services: set[str] = set()
|
self.services: Set[str] = set()
|
||||||
self.label: Optional[str] = None
|
self.label: Optional[str] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from core.gui.dialogs.observers import ObserverDialog
|
from core.gui.dialogs.observers import ObserverDialog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
|
||||||
OBSERVERS: dict[str, str] = {
|
OBSERVERS: Dict[str, str] = {
|
||||||
"List Processes": "ps",
|
"List Processes": "ps",
|
||||||
"Show Interfaces": "ip address",
|
"Show Interfaces": "ip address",
|
||||||
"IPV4 Routes": "ip -4 route",
|
"IPV4 Routes": "ip -4 route",
|
||||||
|
|
|
@ -3,7 +3,7 @@ status bar
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
|
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
|
||||||
from core.gui.dialogs.alerts import AlertsDialog
|
from core.gui.dialogs.alerts import AlertsDialog
|
||||||
|
@ -24,7 +24,7 @@ class StatusBar(ttk.Frame):
|
||||||
self.alerts_button: Optional[ttk.Button] = None
|
self.alerts_button: Optional[ttk.Button] = None
|
||||||
self.alert_style = Styles.no_alert
|
self.alert_style = Styles.no_alert
|
||||||
self.running: bool = False
|
self.running: bool = False
|
||||||
self.core_alarms: list[ExceptionEvent] = []
|
self.core_alarms: List[ExceptionEvent] = []
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Optional
|
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class ProgressTask:
|
||||||
title: str,
|
title: str,
|
||||||
task: Callable,
|
task: Callable,
|
||||||
callback: Callable = None,
|
callback: Callable = None,
|
||||||
args: tuple[Any] = None,
|
args: Tuple[Any] = None,
|
||||||
):
|
):
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
self.title: str = title
|
self.title: str = title
|
||||||
|
@ -25,7 +25,7 @@ class ProgressTask:
|
||||||
self.callback: Callable = callback
|
self.callback: Callable = callback
|
||||||
if args is None:
|
if args is None:
|
||||||
args = ()
|
args = ()
|
||||||
self.args: tuple[Any] = args
|
self.args: Tuple[Any] = args
|
||||||
self.time: Optional[float] = None
|
self.time: Optional[float] = None
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
THEME_DARK: str = "black"
|
THEME_DARK: str = "black"
|
||||||
PADX: tuple[int, int] = (0, 5)
|
PADX: Tuple[int, int] = (0, 5)
|
||||||
PADY: tuple[int, int] = (0, 5)
|
PADY: Tuple[int, int] = (0, 5)
|
||||||
FRAME_PAD: int = 5
|
FRAME_PAD: int = 5
|
||||||
DIALOG_PAD: int = 5
|
DIALOG_PAD: int = 5
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ def theme_change(event: tk.Event) -> None:
|
||||||
_alert_style(style, Styles.red_alert, "red")
|
_alert_style(style, Styles.red_alert, "red")
|
||||||
|
|
||||||
|
|
||||||
def scale_fonts(fonts_size: dict[str, int], scale: float) -> None:
|
def scale_fonts(fonts_size: Dict[str, int], scale: float) -> None:
|
||||||
for name in font.names():
|
for name in font.names():
|
||||||
f = font.nametofont(name)
|
f = font.nametofont(name)
|
||||||
if name in fonts_size:
|
if name in fonts_size:
|
||||||
|
|
|
@ -3,7 +3,7 @@ import tkinter as tk
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import TYPE_CHECKING, Callable, Optional
|
from typing import TYPE_CHECKING, Callable, List, Optional
|
||||||
|
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class ButtonBar(ttk.Frame):
|
||||||
def __init__(self, master: tk.Widget, app: "Application") -> None:
|
def __init__(self, master: tk.Widget, app: "Application") -> None:
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
self.radio_buttons: list[ttk.Button] = []
|
self.radio_buttons: List[ttk.Button] = []
|
||||||
|
|
||||||
def create_button(
|
def create_button(
|
||||||
self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False
|
self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False
|
||||||
|
@ -303,7 +303,7 @@ class Toolbar(ttk.Frame):
|
||||||
)
|
)
|
||||||
task.start()
|
task.start()
|
||||||
|
|
||||||
def start_callback(self, result: bool, exceptions: list[str]) -> None:
|
def start_callback(self, result: bool, exceptions: List[str]) -> None:
|
||||||
self.set_runtime()
|
self.set_runtime()
|
||||||
self.app.core.show_mobility_players()
|
self.app.core.show_mobility_players()
|
||||||
if not result and exceptions:
|
if not result and exceptions:
|
||||||
|
|
|
@ -5,7 +5,7 @@ from typing import Optional
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
|
|
||||||
|
|
||||||
class Tooltip:
|
class Tooltip(object):
|
||||||
"""
|
"""
|
||||||
Create tool tip for a given widget
|
Create tool tip for a given widget
|
||||||
"""
|
"""
|
||||||
|
@ -42,7 +42,7 @@ class Tooltip:
|
||||||
y += self.widget.winfo_rooty() + 32
|
y += self.widget.winfo_rooty() + 32
|
||||||
self.tw = tk.Toplevel(self.widget)
|
self.tw = tk.Toplevel(self.widget)
|
||||||
self.tw.wm_overrideredirect(True)
|
self.tw.wm_overrideredirect(True)
|
||||||
self.tw.wm_geometry(f"+{x:d}+{y:d}")
|
self.tw.wm_geometry("+%d+%d" % (x, y))
|
||||||
self.tw.rowconfigure(0, weight=1)
|
self.tw.rowconfigure(0, weight=1)
|
||||||
self.tw.columnconfigure(0, weight=1)
|
self.tw.columnconfigure(0, weight=1)
|
||||||
frame = ttk.Frame(self.tw, style=Styles.tooltip_frame, padding=3)
|
frame = ttk.Frame(self.tw, style=Styles.tooltip_frame, padding=3)
|
||||||
|
|
|
@ -3,9 +3,8 @@ input validation
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from re import Pattern
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional, Pattern
|
||||||
|
|
||||||
SMALLEST_SCALE: float = 0.5
|
SMALLEST_SCALE: float = 0.5
|
||||||
LARGEST_SCALE: float = 5.0
|
LARGEST_SCALE: float = 5.0
|
||||||
|
|
|
@ -3,7 +3,7 @@ import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import filedialog, font, ttk
|
from tkinter import filedialog, font, ttk
|
||||||
from typing import TYPE_CHECKING, Any, Callable
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type
|
||||||
|
|
||||||
from core.api.grpc.wrappers import ConfigOption, ConfigOptionType
|
from core.api.grpc.wrappers import ConfigOption, ConfigOptionType
|
||||||
from core.gui import appconfig, themes, validation
|
from core.gui import appconfig, themes, validation
|
||||||
|
@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
|
||||||
INT_TYPES: set[ConfigOptionType] = {
|
INT_TYPES: Set[ConfigOptionType] = {
|
||||||
ConfigOptionType.UINT8,
|
ConfigOptionType.UINT8,
|
||||||
ConfigOptionType.UINT16,
|
ConfigOptionType.UINT16,
|
||||||
ConfigOptionType.UINT32,
|
ConfigOptionType.UINT32,
|
||||||
|
@ -40,7 +40,7 @@ class FrameScroll(ttk.Frame):
|
||||||
self,
|
self,
|
||||||
master: tk.Widget,
|
master: tk.Widget,
|
||||||
app: "Application",
|
app: "Application",
|
||||||
_cls: type[ttk.Frame] = ttk.Frame,
|
_cls: Type[ttk.Frame] = ttk.Frame,
|
||||||
**kw: Any
|
**kw: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
|
@ -86,14 +86,14 @@ class ConfigFrame(ttk.Notebook):
|
||||||
self,
|
self,
|
||||||
master: tk.Widget,
|
master: tk.Widget,
|
||||||
app: "Application",
|
app: "Application",
|
||||||
config: dict[str, ConfigOption],
|
config: Dict[str, ConfigOption],
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
**kw: Any
|
**kw: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.app: "Application" = app
|
self.app: "Application" = app
|
||||||
self.config: dict[str, ConfigOption] = config
|
self.config: Dict[str, ConfigOption] = config
|
||||||
self.values: dict[str, tk.StringVar] = {}
|
self.values: Dict[str, tk.StringVar] = {}
|
||||||
self.enabled: bool = enabled
|
self.enabled: bool = enabled
|
||||||
|
|
||||||
def draw_config(self) -> None:
|
def draw_config(self) -> None:
|
||||||
|
@ -166,7 +166,7 @@ class ConfigFrame(ttk.Notebook):
|
||||||
logger.error("unhandled config option type: %s", option.type)
|
logger.error("unhandled config option type: %s", option.type)
|
||||||
self.values[option.name] = value
|
self.values[option.name] = value
|
||||||
|
|
||||||
def parse_config(self) -> dict[str, str]:
|
def parse_config(self) -> Dict[str, str]:
|
||||||
for key in self.config:
|
for key in self.config:
|
||||||
option = self.config[key]
|
option = self.config[key]
|
||||||
value = self.values[key]
|
value = self.values[key]
|
||||||
|
@ -180,7 +180,7 @@ class ConfigFrame(ttk.Notebook):
|
||||||
option.value = config_value
|
option.value = config_value
|
||||||
return {x: self.config[x].value for x in self.config}
|
return {x: self.config[x].value for x in self.config}
|
||||||
|
|
||||||
def set_values(self, config: dict[str, str]) -> None:
|
def set_values(self, config: Dict[str, str]) -> None:
|
||||||
for name, data in config.items():
|
for name, data in config.items():
|
||||||
option = self.config[name]
|
option = self.config[name]
|
||||||
value = self.values[name]
|
value = self.values[name]
|
||||||
|
|
|
@ -6,7 +6,7 @@ import heapq
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
class Timer(threading.Thread):
|
class Timer(threading.Thread):
|
||||||
|
@ -19,8 +19,8 @@ class Timer(threading.Thread):
|
||||||
self,
|
self,
|
||||||
interval: float,
|
interval: float,
|
||||||
func: Callable[..., None],
|
func: Callable[..., None],
|
||||||
args: tuple[Any] = None,
|
args: Tuple[Any] = None,
|
||||||
kwargs: dict[Any, Any] = None,
|
kwargs: Dict[Any, Any] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a Timer instance.
|
Create a Timer instance.
|
||||||
|
@ -38,11 +38,11 @@ class Timer(threading.Thread):
|
||||||
# validate arguments were provided
|
# validate arguments were provided
|
||||||
if args is None:
|
if args is None:
|
||||||
args = ()
|
args = ()
|
||||||
self.args: tuple[Any] = args
|
self.args: Tuple[Any] = args
|
||||||
# validate keyword arguments were provided
|
# validate keyword arguments were provided
|
||||||
if kwargs is None:
|
if kwargs is None:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
self.kwargs: dict[Any, Any] = kwargs
|
self.kwargs: Dict[Any, Any] = kwargs
|
||||||
|
|
||||||
def cancel(self) -> bool:
|
def cancel(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -96,8 +96,8 @@ class Event:
|
||||||
self.eventnum: int = eventnum
|
self.eventnum: int = eventnum
|
||||||
self.time: float = event_time
|
self.time: float = event_time
|
||||||
self.func: Callable[..., None] = func
|
self.func: Callable[..., None] = func
|
||||||
self.args: tuple[Any] = args
|
self.args: Tuple[Any] = args
|
||||||
self.kwds: dict[Any, Any] = kwds
|
self.kwds: Dict[Any, Any] = kwds
|
||||||
self.canceled: bool = False
|
self.canceled: bool = False
|
||||||
|
|
||||||
def __lt__(self, other: "Event") -> bool:
|
def __lt__(self, other: "Event") -> bool:
|
||||||
|
@ -135,7 +135,7 @@ class EventLoop:
|
||||||
Creates a EventLoop instance.
|
Creates a EventLoop instance.
|
||||||
"""
|
"""
|
||||||
self.lock: threading.RLock = threading.RLock()
|
self.lock: threading.RLock = threading.RLock()
|
||||||
self.queue: list[Event] = []
|
self.queue: List[Event] = []
|
||||||
self.eventnum: int = 0
|
self.eventnum: int = 0
|
||||||
self.timer: Optional[Timer] = None
|
self.timer: Optional[Timer] = None
|
||||||
self.running: bool = False
|
self.running: bool = False
|
||||||
|
|
|
@ -3,6 +3,7 @@ Provides conversions from x,y,z to lon,lat,alt.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
import pyproj
|
import pyproj
|
||||||
from pyproj import Transformer
|
from pyproj import Transformer
|
||||||
|
@ -34,9 +35,9 @@ class GeoLocation:
|
||||||
self.to_geo: Transformer = pyproj.Transformer.from_crs(
|
self.to_geo: Transformer = pyproj.Transformer.from_crs(
|
||||||
CRS_PROJ, CRS_WGS84, always_xy=True
|
CRS_PROJ, CRS_WGS84, always_xy=True
|
||||||
)
|
)
|
||||||
self.refproj: tuple[float, float, float] = (0.0, 0.0, 0.0)
|
self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||||
self.refgeo: tuple[float, float, float] = (0.0, 0.0, 0.0)
|
self.refgeo: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||||
self.refxyz: tuple[float, float, float] = (0.0, 0.0, 0.0)
|
self.refxyz: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||||
self.refscale: float = 1.0
|
self.refscale: float = 1.0
|
||||||
|
|
||||||
def setrefgeo(self, lat: float, lon: float, alt: float) -> None:
|
def setrefgeo(self, lat: float, lon: float, alt: float) -> None:
|
||||||
|
@ -83,7 +84,7 @@ class GeoLocation:
|
||||||
return 0.0
|
return 0.0
|
||||||
return SCALE_FACTOR * (value / self.refscale)
|
return SCALE_FACTOR * (value / self.refscale)
|
||||||
|
|
||||||
def getxyz(self, lat: float, lon: float, alt: float) -> tuple[float, float, float]:
|
def getxyz(self, lat: float, lon: float, alt: float) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Convert provided lon,lat,alt to x,y,z.
|
Convert provided lon,lat,alt to x,y,z.
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ class GeoLocation:
|
||||||
logger.debug("result x,y,z(%s, %s, %s)", x, y, z)
|
logger.debug("result x,y,z(%s, %s, %s)", x, y, z)
|
||||||
return x, y, z
|
return x, y, z
|
||||||
|
|
||||||
def getgeo(self, x: float, y: float, z: float) -> tuple[float, float, float]:
|
def getgeo(self, x: float, y: float, z: float) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Convert provided x,y,z to lon,lat,alt.
|
Convert provided x,y,z to lon,lat,alt.
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import threading
|
||||||
import time
|
import time
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.config import (
|
from core.config import (
|
||||||
|
@ -47,7 +47,7 @@ def get_mobility_node(session: "Session", node_id: int) -> Union[WlanNode, Emane
|
||||||
return session.get_node(node_id, EmaneNet)
|
return session.get_node(node_id, EmaneNet)
|
||||||
|
|
||||||
|
|
||||||
def get_config_int(current: int, config: dict[str, str], name: str) -> Optional[int]:
|
def get_config_int(current: int, config: Dict[str, str], name: str) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Convenience function to get config values as int.
|
Convenience function to get config values as int.
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ def get_config_int(current: int, config: dict[str, str], name: str) -> Optional[
|
||||||
|
|
||||||
|
|
||||||
def get_config_float(
|
def get_config_float(
|
||||||
current: Union[int, float], config: dict[str, str], name: str
|
current: Union[int, float], config: Dict[str, str], name: str
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
"""
|
"""
|
||||||
Convenience function to get config values as float.
|
Convenience function to get config values as float.
|
||||||
|
@ -112,7 +112,7 @@ class MobilityManager(ModelManager):
|
||||||
"""
|
"""
|
||||||
self.config_reset()
|
self.config_reset()
|
||||||
|
|
||||||
def startup(self, node_ids: list[int] = None) -> None:
|
def startup(self, node_ids: List[int] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Session is transitioning from instantiation to runtime state.
|
Session is transitioning from instantiation to runtime state.
|
||||||
Instantiate any mobility models that have been configured for a WLAN.
|
Instantiate any mobility models that have been configured for a WLAN.
|
||||||
|
@ -237,7 +237,7 @@ class WirelessModel(ConfigurableOptions):
|
||||||
self.session: "Session" = session
|
self.session: "Session" = session
|
||||||
self.id: int = _id
|
self.id: int = _id
|
||||||
|
|
||||||
def links(self, flags: MessageFlags = MessageFlags.NONE) -> list[LinkData]:
|
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||||
"""
|
"""
|
||||||
May be used if the model can populate the GUI with wireless (green)
|
May be used if the model can populate the GUI with wireless (green)
|
||||||
link lines.
|
link lines.
|
||||||
|
@ -247,7 +247,7 @@ class WirelessModel(ConfigurableOptions):
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def update(self, moved_ifaces: list[CoreInterface]) -> None:
|
def update(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||||
"""
|
"""
|
||||||
Update this wireless model.
|
Update this wireless model.
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ class WirelessModel(ConfigurableOptions):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def update_config(self, config: dict[str, str]) -> None:
|
def update_config(self, config: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
For run-time updates of model config. Returns True when position callback and
|
For run-time updates of model config. Returns True when position callback and
|
||||||
set link parameters should be invoked.
|
set link parameters should be invoked.
|
||||||
|
@ -275,7 +275,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "basic_range"
|
name: str = "basic_range"
|
||||||
options: list[Configuration] = [
|
options: List[Configuration] = [
|
||||||
ConfigInt(id="range", default="275", label="wireless range (pixels)"),
|
ConfigInt(id="range", default="275", label="wireless range (pixels)"),
|
||||||
ConfigInt(id="bandwidth", default="54000000", label="bandwidth (bps)"),
|
ConfigInt(id="bandwidth", default="54000000", label="bandwidth (bps)"),
|
||||||
ConfigInt(id="jitter", default="0", label="transmission jitter (usec)"),
|
ConfigInt(id="jitter", default="0", label="transmission jitter (usec)"),
|
||||||
|
@ -298,7 +298,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
super().__init__(session, _id)
|
super().__init__(session, _id)
|
||||||
self.session: "Session" = session
|
self.session: "Session" = session
|
||||||
self.wlan: WlanNode = session.get_node(_id, WlanNode)
|
self.wlan: WlanNode = session.get_node(_id, WlanNode)
|
||||||
self.iface_to_pos: dict[CoreInterface, tuple[float, float, float]] = {}
|
self.iface_to_pos: Dict[CoreInterface, Tuple[float, float, float]] = {}
|
||||||
self.iface_lock: threading.Lock = threading.Lock()
|
self.iface_lock: threading.Lock = threading.Lock()
|
||||||
self.range: int = 0
|
self.range: int = 0
|
||||||
self.bw: Optional[int] = None
|
self.bw: Optional[int] = None
|
||||||
|
@ -323,7 +323,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
iface.options.update(options)
|
iface.options.update(options)
|
||||||
iface.set_config()
|
iface.set_config()
|
||||||
|
|
||||||
def get_position(self, iface: CoreInterface) -> tuple[float, float, float]:
|
def get_position(self, iface: CoreInterface) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Retrieve network interface position.
|
Retrieve network interface position.
|
||||||
|
|
||||||
|
@ -352,7 +352,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
|
|
||||||
position_callback = set_position
|
position_callback = set_position
|
||||||
|
|
||||||
def update(self, moved_ifaces: list[CoreInterface]) -> None:
|
def update(self, moved_ifaces: List[CoreInterface]) -> None:
|
||||||
"""
|
"""
|
||||||
Node positions have changed without recalc. Update positions from
|
Node positions have changed without recalc. Update positions from
|
||||||
node.position, then re-calculate links for those that have moved.
|
node.position, then re-calculate links for those that have moved.
|
||||||
|
@ -412,7 +412,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calcdistance(
|
def calcdistance(
|
||||||
p1: tuple[float, float, float], p2: tuple[float, float, float]
|
p1: Tuple[float, float, float], p2: Tuple[float, float, float]
|
||||||
) -> float:
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Calculate the distance between two three-dimensional points.
|
Calculate the distance between two three-dimensional points.
|
||||||
|
@ -428,7 +428,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
c = p1[2] - p2[2]
|
c = p1[2] - p2[2]
|
||||||
return math.hypot(math.hypot(a, b), c)
|
return math.hypot(math.hypot(a, b), c)
|
||||||
|
|
||||||
def update_config(self, config: dict[str, str]) -> None:
|
def update_config(self, config: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Configuration has changed during runtime.
|
Configuration has changed during runtime.
|
||||||
|
|
||||||
|
@ -487,7 +487,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
link_data = self.create_link_data(iface, iface2, message_type)
|
link_data = self.create_link_data(iface, iface2, message_type)
|
||||||
self.session.broadcast_link(link_data)
|
self.session.broadcast_link(link_data)
|
||||||
|
|
||||||
def links(self, flags: MessageFlags = MessageFlags.NONE) -> list[LinkData]:
|
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
||||||
"""
|
"""
|
||||||
Return a list of wireless link messages for when the GUI reconnects.
|
Return a list of wireless link messages for when the GUI reconnects.
|
||||||
|
|
||||||
|
@ -513,7 +513,7 @@ class WayPoint:
|
||||||
self,
|
self,
|
||||||
_time: float,
|
_time: float,
|
||||||
node_id: int,
|
node_id: int,
|
||||||
coords: tuple[float, float, Optional[float]],
|
coords: Tuple[float, float, Optional[float]],
|
||||||
speed: float,
|
speed: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -526,7 +526,7 @@ class WayPoint:
|
||||||
"""
|
"""
|
||||||
self.time: float = _time
|
self.time: float = _time
|
||||||
self.node_id: int = node_id
|
self.node_id: int = node_id
|
||||||
self.coords: tuple[float, float, Optional[float]] = coords
|
self.coords: Tuple[float, float, Optional[float]] = coords
|
||||||
self.speed: float = speed
|
self.speed: float = speed
|
||||||
|
|
||||||
def __eq__(self, other: "WayPoint") -> bool:
|
def __eq__(self, other: "WayPoint") -> bool:
|
||||||
|
@ -563,10 +563,10 @@ class WayPointMobility(WirelessModel):
|
||||||
"""
|
"""
|
||||||
super().__init__(session=session, _id=_id)
|
super().__init__(session=session, _id=_id)
|
||||||
self.state: int = self.STATE_STOPPED
|
self.state: int = self.STATE_STOPPED
|
||||||
self.queue: list[WayPoint] = []
|
self.queue: List[WayPoint] = []
|
||||||
self.queue_copy: list[WayPoint] = []
|
self.queue_copy: List[WayPoint] = []
|
||||||
self.points: dict[int, WayPoint] = {}
|
self.points: Dict[int, WayPoint] = {}
|
||||||
self.initial: dict[int, WayPoint] = {}
|
self.initial: Dict[int, WayPoint] = {}
|
||||||
self.lasttime: Optional[float] = None
|
self.lasttime: Optional[float] = None
|
||||||
self.endtime: Optional[int] = None
|
self.endtime: Optional[int] = None
|
||||||
self.timezero: float = 0.0
|
self.timezero: float = 0.0
|
||||||
|
@ -855,7 +855,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "ns2script"
|
name: str = "ns2script"
|
||||||
options: list[Configuration] = [
|
options: List[Configuration] = [
|
||||||
ConfigString(id="file", label="mobility script file"),
|
ConfigString(id="file", label="mobility script file"),
|
||||||
ConfigInt(id="refresh_ms", default="50", label="refresh time (ms)"),
|
ConfigInt(id="refresh_ms", default="50", label="refresh time (ms)"),
|
||||||
ConfigBool(id="loop", default="1", label="loop"),
|
ConfigBool(id="loop", default="1", label="loop"),
|
||||||
|
@ -867,7 +867,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def config_groups(cls) -> list[ConfigGroup]:
|
def config_groups(cls) -> List[ConfigGroup]:
|
||||||
return [
|
return [
|
||||||
ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations()))
|
ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations()))
|
||||||
]
|
]
|
||||||
|
@ -882,12 +882,12 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
super().__init__(session, _id)
|
super().__init__(session, _id)
|
||||||
self.file: Optional[Path] = None
|
self.file: Optional[Path] = None
|
||||||
self.autostart: Optional[str] = None
|
self.autostart: Optional[str] = None
|
||||||
self.nodemap: dict[int, int] = {}
|
self.nodemap: Dict[int, int] = {}
|
||||||
self.script_start: Optional[str] = None
|
self.script_start: Optional[str] = None
|
||||||
self.script_pause: Optional[str] = None
|
self.script_pause: Optional[str] = None
|
||||||
self.script_stop: Optional[str] = None
|
self.script_stop: Optional[str] = None
|
||||||
|
|
||||||
def update_config(self, config: dict[str, str]) -> None:
|
def update_config(self, config: Dict[str, str]) -> None:
|
||||||
self.file = Path(config["file"])
|
self.file = Path(config["file"])
|
||||||
logger.info(
|
logger.info(
|
||||||
"ns-2 scripted mobility configured for WLAN %d using file: %s",
|
"ns-2 scripted mobility configured for WLAN %d using file: %s",
|
||||||
|
@ -916,7 +916,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
file_path = self.findfile(self.file)
|
file_path = self.findfile(self.file)
|
||||||
try:
|
try:
|
||||||
f = file_path.open("r")
|
f = file_path.open("r")
|
||||||
except OSError:
|
except IOError:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"ns-2 scripted mobility failed to load file: %s", self.file
|
"ns-2 scripted mobility failed to load file: %s", self.file
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import threading
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
@ -29,10 +29,10 @@ if TYPE_CHECKING:
|
||||||
from core.configservice.base import ConfigService
|
from core.configservice.base import ConfigService
|
||||||
from core.services.coreservices import CoreService
|
from core.services.coreservices import CoreService
|
||||||
|
|
||||||
CoreServices = list[Union[CoreService, type[CoreService]]]
|
CoreServices = List[Union[CoreService, Type[CoreService]]]
|
||||||
ConfigServiceType = type[ConfigService]
|
ConfigServiceType = Type[ConfigService]
|
||||||
|
|
||||||
PRIVATE_DIRS: list[Path] = [Path("/var/run"), Path("/var/log")]
|
PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -64,7 +64,7 @@ class Position:
|
||||||
self.z = z
|
self.z = z
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get(self) -> tuple[float, float, float]:
|
def get(self) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Retrieve x,y,z position.
|
Retrieve x,y,z position.
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ class Position:
|
||||||
self.lat = lat
|
self.lat = lat
|
||||||
self.alt = alt
|
self.alt = alt
|
||||||
|
|
||||||
def get_geo(self) -> tuple[float, float, float]:
|
def get_geo(self) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Retrieve current geo position lon, lat, alt.
|
Retrieve current geo position lon, lat, alt.
|
||||||
|
|
||||||
|
@ -113,9 +113,9 @@ class NodeOptions:
|
||||||
class CoreNodeOptions(NodeOptions):
|
class CoreNodeOptions(NodeOptions):
|
||||||
model: str = "PC"
|
model: str = "PC"
|
||||||
"""model is used for providing a default set of services"""
|
"""model is used for providing a default set of services"""
|
||||||
services: list[str] = field(default_factory=list)
|
services: List[str] = field(default_factory=list)
|
||||||
"""services to start within node"""
|
"""services to start within node"""
|
||||||
config_services: list[str] = field(default_factory=list)
|
config_services: List[str] = field(default_factory=list)
|
||||||
"""config services to start within node"""
|
"""config services to start within node"""
|
||||||
directory: Path = None
|
directory: Path = None
|
||||||
"""directory to define node, defaults to path under the session directory"""
|
"""directory to define node, defaults to path under the session directory"""
|
||||||
|
@ -152,7 +152,7 @@ class NodeBase(abc.ABC):
|
||||||
self.server: "DistributedServer" = server
|
self.server: "DistributedServer" = server
|
||||||
self.model: Optional[str] = None
|
self.model: Optional[str] = None
|
||||||
self.services: CoreServices = []
|
self.services: CoreServices = []
|
||||||
self.ifaces: dict[int, CoreInterface] = {}
|
self.ifaces: Dict[int, CoreInterface] = {}
|
||||||
self.iface_id: int = 0
|
self.iface_id: int = 0
|
||||||
self.position: Position = Position()
|
self.position: Position = Position()
|
||||||
self.up: bool = False
|
self.up: bool = False
|
||||||
|
@ -201,7 +201,7 @@ class NodeBase(abc.ABC):
|
||||||
def host_cmd(
|
def host_cmd(
|
||||||
self,
|
self,
|
||||||
args: str,
|
args: str,
|
||||||
env: dict[str, str] = None,
|
env: Dict[str, str] = None,
|
||||||
cwd: Path = None,
|
cwd: Path = None,
|
||||||
wait: bool = True,
|
wait: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
|
@ -246,7 +246,7 @@ class NodeBase(abc.ABC):
|
||||||
"""
|
"""
|
||||||
return self.position.set(x=x, y=y, z=z)
|
return self.position.set(x=x, y=y, z=z)
|
||||||
|
|
||||||
def getposition(self) -> tuple[float, float, float]:
|
def getposition(self) -> Tuple[float, float, float]:
|
||||||
"""
|
"""
|
||||||
Return an (x,y,z) tuple representing this object's position.
|
Return an (x,y,z) tuple representing this object's position.
|
||||||
|
|
||||||
|
@ -276,9 +276,8 @@ class NodeBase(abc.ABC):
|
||||||
mtu = DEFAULT_MTU
|
mtu = DEFAULT_MTU
|
||||||
if iface_data and iface_data.mtu is not None:
|
if iface_data and iface_data.mtu is not None:
|
||||||
mtu = iface_data.mtu
|
mtu = iface_data.mtu
|
||||||
unique_name = f"{self.id}.{iface_id}.{self.session.short_session_id()}"
|
name = f"veth{self.id}.{iface_id}.{self.session.short_session_id()}"
|
||||||
name = f"veth{unique_name}"
|
localname = f"{name}p"
|
||||||
localname = f"beth{unique_name}"
|
|
||||||
iface = CoreInterface(
|
iface = CoreInterface(
|
||||||
iface_id,
|
iface_id,
|
||||||
name,
|
name,
|
||||||
|
@ -331,7 +330,7 @@ class NodeBase(abc.ABC):
|
||||||
raise CoreError(f"node({self.name}) does not have interface({iface_id})")
|
raise CoreError(f"node({self.name}) does not have interface({iface_id})")
|
||||||
return self.ifaces[iface_id]
|
return self.ifaces[iface_id]
|
||||||
|
|
||||||
def get_ifaces(self, control: bool = True) -> list[CoreInterface]:
|
def get_ifaces(self, control: bool = True) -> List[CoreInterface]:
|
||||||
"""
|
"""
|
||||||
Retrieve sorted list of interfaces, optionally do not include control
|
Retrieve sorted list of interfaces, optionally do not include control
|
||||||
interfaces.
|
interfaces.
|
||||||
|
@ -395,7 +394,7 @@ class CoreNodeBase(NodeBase):
|
||||||
will run on, default is None for localhost
|
will run on, default is None for localhost
|
||||||
"""
|
"""
|
||||||
super().__init__(session, _id, name, server, options)
|
super().__init__(session, _id, name, server, options)
|
||||||
self.config_services: dict[str, "ConfigService"] = {}
|
self.config_services: Dict[str, "ConfigService"] = {}
|
||||||
self.directory: Optional[Path] = None
|
self.directory: Optional[Path] = None
|
||||||
self.tmpnodedir: bool = False
|
self.tmpnodedir: bool = False
|
||||||
|
|
||||||
|
@ -481,7 +480,7 @@ class CoreNodeBase(NodeBase):
|
||||||
raise CoreError(f"node({self.name}) already has service({name})")
|
raise CoreError(f"node({self.name}) already has service({name})")
|
||||||
self.config_services[name] = service_class(self)
|
self.config_services[name] = service_class(self)
|
||||||
|
|
||||||
def set_service_config(self, name: str, data: dict[str, str]) -> None:
|
def set_service_config(self, name: str, data: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Sets configuration service custom config data.
|
Sets configuration service custom config data.
|
||||||
|
|
||||||
|
@ -506,15 +505,6 @@ class CoreNodeBase(NodeBase):
|
||||||
for service in startup_path:
|
for service in startup_path:
|
||||||
service.start()
|
service.start()
|
||||||
|
|
||||||
def stop_config_services(self) -> None:
|
|
||||||
"""
|
|
||||||
Stop all configuration services.
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
for service in self.config_services.values():
|
|
||||||
service.stop()
|
|
||||||
|
|
||||||
def makenodedir(self) -> None:
|
def makenodedir(self) -> None:
|
||||||
"""
|
"""
|
||||||
Create the node directory.
|
Create the node directory.
|
||||||
|
@ -583,7 +573,7 @@ class CoreNode(CoreNodeBase):
|
||||||
self.directory: Optional[Path] = options.directory
|
self.directory: Optional[Path] = options.directory
|
||||||
self.ctrlchnlname: Path = self.session.directory / self.name
|
self.ctrlchnlname: Path = self.session.directory / self.name
|
||||||
self.pid: Optional[int] = None
|
self.pid: Optional[int] = None
|
||||||
self._mounts: list[tuple[Path, Path]] = []
|
self._mounts: List[Tuple[Path, Path]] = []
|
||||||
self.node_net_client: LinuxNetClient = self.create_node_net_client(
|
self.node_net_client: LinuxNetClient = self.create_node_net_client(
|
||||||
self.session.use_ovs()
|
self.session.use_ovs()
|
||||||
)
|
)
|
||||||
|
@ -901,7 +891,7 @@ class CoreNode(CoreNodeBase):
|
||||||
for ip in iface.ips():
|
for ip in iface.ips():
|
||||||
# ipv4 check
|
# ipv4 check
|
||||||
broadcast = None
|
broadcast = None
|
||||||
if netaddr.valid_ipv4(str(ip.ip)):
|
if netaddr.valid_ipv4(ip):
|
||||||
broadcast = "+"
|
broadcast = "+"
|
||||||
self.node_net_client.create_address(iface.name, str(ip), broadcast)
|
self.node_net_client.create_address(iface.name, str(ip), broadcast)
|
||||||
# configure iface options
|
# configure iface options
|
||||||
|
@ -937,7 +927,7 @@ class CoreNetworkBase(NodeBase):
|
||||||
mtu = self.session.options.get_int("mtu")
|
mtu = self.session.options.get_int("mtu")
|
||||||
self.mtu: int = mtu if mtu > 0 else DEFAULT_MTU
|
self.mtu: int = mtu if mtu > 0 else DEFAULT_MTU
|
||||||
self.brname: Optional[str] = None
|
self.brname: Optional[str] = None
|
||||||
self.linked: dict[CoreInterface, dict[CoreInterface, bool]] = {}
|
self.linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {}
|
||||||
self.linked_lock: threading.Lock = threading.Lock()
|
self.linked_lock: threading.Lock = threading.Lock()
|
||||||
|
|
||||||
def attach(self, iface: CoreInterface) -> None:
|
def attach(self, iface: CoreInterface) -> None:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue