updated to 9.0.3 merge
This commit is contained in:
commit
3f2db35e68
534 changed files with 13841 additions and 56536 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-18.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Set up Python 3.6
|
- name: Set up Python 3.9
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: 3.6
|
python-version: 3.9
|
||||||
- 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
Normal file
21
.github/workflows/documentation.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -14,9 +14,13 @@ 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
|
||||||
|
@ -58,3 +62,6 @@ daemon/setup.py
|
||||||
|
|
||||||
# python
|
# python
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
|
# ignore core player files
|
||||||
|
*.core
|
||||||
|
|
90
CHANGELOG.md
90
CHANGELOG.md
|
@ -1,3 +1,93 @@
|
||||||
|
## 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
|
||||||
|
|
139
Dockerfile
139
Dockerfile
|
@ -1,16 +1,23 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:22.04
|
||||||
LABEL Description="CORE Docker Image"
|
LABEL Description="CORE Docker Ubuntu Image"
|
||||||
|
|
||||||
# define variables
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
ARG PREFIX=/usr/local
|
ARG PREFIX=/usr/local
|
||||||
ARG BRANCH=master
|
ARG BRANCH=master
|
||||||
ARG CORE_TARBALL=core.tar.gz
|
ARG PROTOC_VERSION=3.19.6
|
||||||
ARG OSPF_TARBALL=ospf.tar.gz
|
ARG VENV_PATH=/opt/core/venv
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV PATH="$PATH:${VENV_PATH}/bin"
|
||||||
|
WORKDIR /opt
|
||||||
|
|
||||||
# install system dependencies
|
# install system dependencies
|
||||||
RUN apt-get update && \
|
|
||||||
|
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 \
|
apt-get install -y --no-install-recommends \
|
||||||
automake \
|
automake \
|
||||||
bash \
|
bash \
|
||||||
|
@ -24,20 +31,19 @@ RUN apt-get update && \
|
||||||
libc-dev \
|
libc-dev \
|
||||||
libev-dev \
|
libev-dev \
|
||||||
libreadline-dev \
|
libreadline-dev \
|
||||||
libtool \ libtk-img \ make \
|
libtool \
|
||||||
nftables \
|
nftables \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3-tk \
|
python3-tk \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
systemctl \
|
|
||||||
tk \
|
tk \
|
||||||
wget \
|
|
||||||
xauth \
|
xauth \
|
||||||
xterm \
|
xterm \
|
||||||
vim \
|
|
||||||
nano \
|
|
||||||
wireshark \
|
wireshark \
|
||||||
|
vim \
|
||||||
|
build-essential \
|
||||||
|
nano \
|
||||||
firefox \
|
firefox \
|
||||||
net-tools \
|
net-tools \
|
||||||
rsync \
|
rsync \
|
||||||
|
@ -49,7 +55,6 @@ RUN apt-get update && \
|
||||||
mini-httpd \
|
mini-httpd \
|
||||||
lynx \
|
lynx \
|
||||||
tcpdump \
|
tcpdump \
|
||||||
wireshark \
|
|
||||||
iperf \
|
iperf \
|
||||||
iperf3 \
|
iperf3 \
|
||||||
tshark \
|
tshark \
|
||||||
|
@ -72,68 +77,50 @@ RUN apt-get update && \
|
||||||
dconf-service \
|
dconf-service \
|
||||||
x11-xserver-utils \
|
x11-xserver-utils \
|
||||||
ftp \
|
ftp \
|
||||||
&& apt-get clean
|
git \
|
||||||
# install python dependencies
|
sudo \
|
||||||
RUN python3 -m pip install \
|
wget \
|
||||||
grpcio==1.27.2 \
|
tzdata \
|
||||||
grpcio-tools==1.27.2 \
|
libpcap-dev \
|
||||||
poetry==1.1.7
|
libpcre3-dev \
|
||||||
# retrieve, build, and install core
|
libprotobuf-dev \
|
||||||
RUN wget -q -O ${CORE_TARBALL} https://api.github.com/repos/coreemu/core/tarball/${BRANCH} && \
|
libxml2-dev \
|
||||||
tar xf ${CORE_TARBALL} && \
|
protobuf-compiler \
|
||||||
cd coreemu-core* && \
|
unzip \
|
||||||
./bootstrap.sh && \
|
uuid-dev \
|
||||||
./configure && \
|
iproute2 \
|
||||||
make -j $(nproc) && \
|
vlc \
|
||||||
make install && \
|
iputils-ping && \
|
||||||
cd daemon && \
|
apt-get autoremove -y
|
||||||
python3 -m poetry build -f wheel && \
|
|
||||||
python3 -m pip install dist/* && \
|
|
||||||
cp scripts/* ${PREFIX}/bin && \
|
|
||||||
mkdir /etc/core && \
|
|
||||||
cp -n data/core.conf /etc/core && \
|
|
||||||
cp -n data/logging.conf /etc/core && \
|
|
||||||
mkdir -p ${PREFIX}/share/core && \
|
|
||||||
cp -r examples ${PREFIX}/share/core && \
|
|
||||||
echo '\
|
|
||||||
[Unit]\n\
|
|
||||||
Description=Common Open Research Emulator Service\n\
|
|
||||||
After=network.target\n\
|
|
||||||
\n\
|
|
||||||
[Service]\n\
|
|
||||||
Type=simple\n\
|
|
||||||
ExecStart=/usr/local/bin/core-daemon\n\
|
|
||||||
TasksMax=infinity\n\
|
|
||||||
\n\
|
|
||||||
[Install]\n\
|
|
||||||
WantedBy=multi-user.target\
|
|
||||||
' > /lib/systemd/system/core-daemon.service && \
|
|
||||||
cd ../.. && \
|
|
||||||
rm ${CORE_TARBALL} && \
|
|
||||||
rm -rf coreemu-core*
|
|
||||||
# retrieve, build, and install ospf mdr
|
|
||||||
RUN wget -q -O ${OSPF_TARBALL} https://github.com/USNavalResearchLaboratory/ospf-mdr/tarball/master && \
|
|
||||||
tar xf ${OSPF_TARBALL} && \
|
|
||||||
cd USNavalResearchLaboratory-ospf-mdr* && \
|
|
||||||
./bootstrap.sh && \
|
|
||||||
./configure --disable-doc --enable-user=root --enable-group=root \
|
|
||||||
--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh \
|
|
||||||
--localstatedir=/var/run/quagga && \
|
|
||||||
make -j $(nproc) && \
|
|
||||||
make install && \
|
|
||||||
cd .. && \
|
|
||||||
rm ${OSPF_TARBALL} && \
|
|
||||||
rm -rf USNavalResearchLaboratory-ospf-mdr*
|
|
||||||
# retrieve and install emane packages
|
|
||||||
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \
|
|
||||||
tar xf emane*.tar.gz && \
|
|
||||||
cd emane-1.2.7-release-1/debs/ubuntu-20_04/amd64 && \
|
|
||||||
apt-get install -y ./emane*.deb ./python3-emane_*.deb && \
|
|
||||||
cd ../../../.. && \
|
|
||||||
rm emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \
|
|
||||||
rm -rf emane-1.2.7-release-1
|
|
||||||
CMD ["systemctl", "start", "core-daemon"]
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y && \
|
# install emane
|
||||||
echo 'source $HOME/.cargo/env' >> $HOME/.bashrc
|
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
|
||||||
|
|
87
Makefile.am
87
Makefile.am
|
@ -6,10 +6,6 @@ if WANT_DOCS
|
||||||
DOCS = docs man
|
DOCS = docs man
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if WANT_GUI
|
|
||||||
GUI = gui
|
|
||||||
endif
|
|
||||||
|
|
||||||
if WANT_DAEMON
|
if WANT_DAEMON
|
||||||
DAEMON = daemon
|
DAEMON = daemon
|
||||||
endif
|
endif
|
||||||
|
@ -19,12 +15,13 @@ if WANT_NETNS
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# keep docs last due to dependencies on binaries
|
# keep docs last due to dependencies on binaries
|
||||||
SUBDIRS = $(GUI) $(DAEMON) $(NETNS) $(DOCS)
|
SUBDIRS = $(DAEMON) $(NETNS) $(DOCS)
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I config
|
ACLOCAL_AMFLAGS = -I config
|
||||||
|
|
||||||
# extra files to include with distribution tarball
|
# extra files to include with distribution tarball
|
||||||
EXTRA_DIST = bootstrap.sh \
|
EXTRA_DIST = bootstrap.sh \
|
||||||
|
package \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
README.md \
|
README.md \
|
||||||
ASSIGNMENT_OF_COPYRIGHT.pdf \
|
ASSIGNMENT_OF_COPYRIGHT.pdf \
|
||||||
|
@ -51,7 +48,7 @@ fpm -s dir -t deb -n core-distributed \
|
||||||
--description "Common Open Research Emulator Distributed Package" \
|
--description "Common Open Research Emulator Distributed Package" \
|
||||||
--url https://github.com/coreemu/core \
|
--url https://github.com/coreemu/core \
|
||||||
--vendor "$(PACKAGE_VENDOR)" \
|
--vendor "$(PACKAGE_VENDOR)" \
|
||||||
-p core_distributed_VERSION_ARCH.deb \
|
-p core-distributed_VERSION_ARCH.deb \
|
||||||
-v $(PACKAGE_VERSION) \
|
-v $(PACKAGE_VERSION) \
|
||||||
-d "ethtool" \
|
-d "ethtool" \
|
||||||
-d "procps" \
|
-d "procps" \
|
||||||
|
@ -62,7 +59,8 @@ fpm -s dir -t deb -n core-distributed \
|
||||||
-d "libev4" \
|
-d "libev4" \
|
||||||
-d "openssh-server" \
|
-d "openssh-server" \
|
||||||
-d "xterm" \
|
-d "xterm" \
|
||||||
-C $(DESTDIR)
|
netns/vnoded=/usr/bin/ \
|
||||||
|
netns/vcmd=/usr/bin/
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define fpm-distributed-rpm =
|
define fpm-distributed-rpm =
|
||||||
|
@ -72,7 +70,7 @@ fpm -s dir -t rpm -n core-distributed \
|
||||||
--description "Common Open Research Emulator Distributed Package" \
|
--description "Common Open Research Emulator Distributed Package" \
|
||||||
--url https://github.com/coreemu/core \
|
--url https://github.com/coreemu/core \
|
||||||
--vendor "$(PACKAGE_VENDOR)" \
|
--vendor "$(PACKAGE_VENDOR)" \
|
||||||
-p core_distributed_VERSION_ARCH.rpm \
|
-p core-distributed_VERSION_ARCH.rpm \
|
||||||
-v $(PACKAGE_VERSION) \
|
-v $(PACKAGE_VERSION) \
|
||||||
-d "ethtool" \
|
-d "ethtool" \
|
||||||
-d "procps-ng" \
|
-d "procps-ng" \
|
||||||
|
@ -83,12 +81,75 @@ fpm -s dir -t rpm -n core-distributed \
|
||||||
-d "net-tools" \
|
-d "net-tools" \
|
||||||
-d "openssh-server" \
|
-d "openssh-server" \
|
||||||
-d "xterm" \
|
-d "xterm" \
|
||||||
-C $(DESTDIR)
|
netns/vnoded=/usr/bin/ \
|
||||||
|
netns/vcmd=/usr/bin/
|
||||||
endef
|
endef
|
||||||
|
|
||||||
.PHONY: fpm-distributed
|
define fpm-rpm =
|
||||||
fpm-distributed: clean-local-fpm
|
fpm -s dir -t rpm -n core \
|
||||||
$(MAKE) -C netns install DESTDIR=$(DESTDIR)
|
-m "$(PACKAGE_MAINTAINERS)" \
|
||||||
|
--license "BSD" \
|
||||||
|
--description "core vnoded/vcmd and system dependencies" \
|
||||||
|
--url https://github.com/coreemu/core \
|
||||||
|
--vendor "$(PACKAGE_VENDOR)" \
|
||||||
|
-p core_VERSION_ARCH.rpm \
|
||||||
|
-v $(PACKAGE_VERSION) \
|
||||||
|
--rpm-init package/core-daemon \
|
||||||
|
--after-install package/after-install.sh \
|
||||||
|
--after-remove package/after-remove.sh \
|
||||||
|
-d "ethtool" \
|
||||||
|
-d "tk" \
|
||||||
|
-d "procps-ng" \
|
||||||
|
-d "bash >= 3.0" \
|
||||||
|
-d "ebtables" \
|
||||||
|
-d "iproute" \
|
||||||
|
-d "libev" \
|
||||||
|
-d "net-tools" \
|
||||||
|
-d "nftables" \
|
||||||
|
netns/vnoded=/usr/bin/ \
|
||||||
|
netns/vcmd=/usr/bin/ \
|
||||||
|
package/etc/core.conf=/etc/core/ \
|
||||||
|
package/etc/logging.conf=/etc/core/ \
|
||||||
|
package/examples=/opt/core/ \
|
||||||
|
daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/
|
||||||
|
endef
|
||||||
|
|
||||||
|
define fpm-deb =
|
||||||
|
fpm -s dir -t deb -n core \
|
||||||
|
-m "$(PACKAGE_MAINTAINERS)" \
|
||||||
|
--license "BSD" \
|
||||||
|
--description "core vnoded/vcmd and system dependencies" \
|
||||||
|
--url https://github.com/coreemu/core \
|
||||||
|
--vendor "$(PACKAGE_VENDOR)" \
|
||||||
|
-p core_VERSION_ARCH.deb \
|
||||||
|
-v $(PACKAGE_VERSION) \
|
||||||
|
--deb-systemd package/core-daemon.service \
|
||||||
|
--deb-no-default-config-files \
|
||||||
|
--after-install package/after-install.sh \
|
||||||
|
--after-remove package/after-remove.sh \
|
||||||
|
-d "ethtool" \
|
||||||
|
-d "tk" \
|
||||||
|
-d "libtk-img" \
|
||||||
|
-d "procps" \
|
||||||
|
-d "libc6 >= 2.14" \
|
||||||
|
-d "bash >= 3.0" \
|
||||||
|
-d "ebtables" \
|
||||||
|
-d "iproute2" \
|
||||||
|
-d "libev4" \
|
||||||
|
-d "nftables" \
|
||||||
|
netns/vnoded=/usr/bin/ \
|
||||||
|
netns/vcmd=/usr/bin/ \
|
||||||
|
package/etc/core.conf=/etc/core/ \
|
||||||
|
package/etc/logging.conf=/etc/core/ \
|
||||||
|
package/examples=/opt/core/ \
|
||||||
|
daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/
|
||||||
|
endef
|
||||||
|
|
||||||
|
.PHONY: fpm
|
||||||
|
fpm: clean-local-fpm
|
||||||
|
cd daemon && poetry build -f wheel
|
||||||
|
$(call fpm-deb)
|
||||||
|
$(call fpm-rpm)
|
||||||
$(call fpm-distributed-deb)
|
$(call fpm-distributed-deb)
|
||||||
$(call fpm-distributed-rpm)
|
$(call fpm-distributed-rpm)
|
||||||
|
|
||||||
|
@ -115,7 +176,6 @@ $(info creating file $1 from $1.in)
|
||||||
-e 's,[@]CORE_STATE_DIR[@],$(CORE_STATE_DIR),g' \
|
-e 's,[@]CORE_STATE_DIR[@],$(CORE_STATE_DIR),g' \
|
||||||
-e 's,[@]CORE_DATA_DIR[@],$(CORE_DATA_DIR),g' \
|
-e 's,[@]CORE_DATA_DIR[@],$(CORE_DATA_DIR),g' \
|
||||||
-e 's,[@]CORE_CONF_DIR[@],$(CORE_CONF_DIR),g' \
|
-e 's,[@]CORE_CONF_DIR[@],$(CORE_CONF_DIR),g' \
|
||||||
-e 's,[@]CORE_GUI_CONF_DIR[@],$(CORE_GUI_CONF_DIR),g' \
|
|
||||||
< $1.in > $1
|
< $1.in > $1
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
@ -123,7 +183,6 @@ all: change-files
|
||||||
|
|
||||||
.PHONY: change-files
|
.PHONY: change-files
|
||||||
change-files:
|
change-files:
|
||||||
$(call change-files,gui/core-gui-legacy)
|
|
||||||
$(call change-files,daemon/core/constants.py)
|
$(call change-files,daemon/core/constants.py)
|
||||||
$(call change-files,netns/setup.py)
|
$(call change-files,netns/setup.py)
|
||||||
|
|
||||||
|
|
65
README.md
65
README.md
|
@ -1,3 +1,13 @@
|
||||||
|
# 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
|
||||||
|
@ -6,28 +16,69 @@ 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
|
# Docker Setup
|
||||||
|
|
||||||
|
Here you have 2 choices
|
||||||
|
|
||||||
```shell
|
## Precompiled container image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
# Start container
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
## Build container image from source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repo
|
||||||
git clone https://gitea.olympuslab.net/afonso/core-extra.git
|
git clone https://gitea.olympuslab.net/afonso/core-extra.git
|
||||||
|
|
||||||
|
# cd into the directory
|
||||||
cd core-extra
|
cd core-extra
|
||||||
|
|
||||||
# build image
|
# build the docker image
|
||||||
sudo docker build -t core-extra .
|
sudo docker build -t core-extra .
|
||||||
|
|
||||||
# start container
|
# start container
|
||||||
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped core-extra
|
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped core-extra
|
||||||
|
|
||||||
# enable xhost access to the root user
|
```
|
||||||
xhost +local:root
|
|
||||||
# launch core-gui
|
|
||||||
sudo docker exec -it core core-gui
|
|
||||||
|
|
||||||
|
### Adding extra packages
|
||||||
|
|
||||||
|
To add extra packages you must modify the Dockerfile and then compile the docker image.
|
||||||
|
If you install it after starting the container it will, by docker nature, be reverted on the next boot of the container.
|
||||||
|
|
||||||
|
# Useful commands
|
||||||
|
|
||||||
|
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
|
#TO RUN ANY OTHER COMMAND
|
||||||
sudo docker exec -it core COMAND_GOES_HERE
|
sudo docker exec -it core COMAND_GOES_HERE
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
# (c)2010-2012 the Boeing Company
|
|
||||||
#
|
|
||||||
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
|
||||||
#
|
|
||||||
# Bootstrap the autoconf system.
|
# Bootstrap the autoconf system.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
68
configure.ac
68
configure.ac
|
@ -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, 8.2.0)
|
AC_INIT(core, 9.0.3)
|
||||||
|
|
||||||
# autoconf and automake initialization
|
# autoconf and automake initialization
|
||||||
AC_CONFIG_SRCDIR([netns/version.h.in])
|
AC_CONFIG_SRCDIR([netns/version.h.in])
|
||||||
|
@ -30,25 +30,14 @@ AC_SUBST(CORE_CONF_DIR)
|
||||||
AC_SUBST(CORE_DATA_DIR)
|
AC_SUBST(CORE_DATA_DIR)
|
||||||
AC_SUBST(CORE_STATE_DIR)
|
AC_SUBST(CORE_STATE_DIR)
|
||||||
|
|
||||||
# CORE GUI configuration files and preferences in CORE_GUI_CONF_DIR
|
# documentation option
|
||||||
# scenario files in ~/.core/configs/
|
|
||||||
AC_ARG_WITH([guiconfdir],
|
|
||||||
[AS_HELP_STRING([--with-guiconfdir=dir],
|
|
||||||
[specify GUI configuration directory])],
|
|
||||||
[CORE_GUI_CONF_DIR="$with_guiconfdir"],
|
|
||||||
[CORE_GUI_CONF_DIR="\$\${HOME}/.core"])
|
|
||||||
AC_SUBST(CORE_GUI_CONF_DIR)
|
|
||||||
AC_ARG_ENABLE([gui],
|
|
||||||
[AS_HELP_STRING([--enable-gui[=ARG]],
|
|
||||||
[build and install the GUI (default is yes)])],
|
|
||||||
[], [enable_gui=yes])
|
|
||||||
AC_SUBST(enable_gui)
|
|
||||||
AC_ARG_ENABLE([docs],
|
AC_ARG_ENABLE([docs],
|
||||||
[AS_HELP_STRING([--enable-docs[=ARG]],
|
[AS_HELP_STRING([--enable-docs[=ARG]],
|
||||||
[build python documentation (default is no)])],
|
[build python documentation (default is no)])],
|
||||||
[], [enable_docs=no])
|
[], [enable_docs=no])
|
||||||
AC_SUBST(enable_docs)
|
AC_SUBST(enable_docs)
|
||||||
|
|
||||||
|
# python option
|
||||||
AC_ARG_ENABLE([python],
|
AC_ARG_ENABLE([python],
|
||||||
[AS_HELP_STRING([--enable-python[=ARG]],
|
[AS_HELP_STRING([--enable-python[=ARG]],
|
||||||
[build and install the python bindings (default is yes)])],
|
[build and install the python bindings (default is yes)])],
|
||||||
|
@ -94,28 +83,7 @@ if test "x$enable_daemon" = "xyes"; then
|
||||||
want_python=yes
|
want_python=yes
|
||||||
want_linux_netns=yes
|
want_linux_netns=yes
|
||||||
|
|
||||||
# Checks for libraries.
|
AM_PATH_PYTHON(3.9)
|
||||||
AC_CHECK_LIB([netgraph], [NgMkSockNode])
|
|
||||||
|
|
||||||
# Checks for header files.
|
|
||||||
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h])
|
|
||||||
|
|
||||||
# Checks for typedefs, structures, and compiler characteristics.
|
|
||||||
AC_C_INLINE
|
|
||||||
AC_TYPE_INT32_T
|
|
||||||
AC_TYPE_PID_T
|
|
||||||
AC_TYPE_SIZE_T
|
|
||||||
AC_TYPE_SSIZE_T
|
|
||||||
AC_TYPE_UINT32_T
|
|
||||||
AC_TYPE_UINT8_T
|
|
||||||
|
|
||||||
# Checks for library functions.
|
|
||||||
AC_FUNC_FORK
|
|
||||||
AC_FUNC_MALLOC
|
|
||||||
AC_FUNC_REALLOC
|
|
||||||
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -171,6 +139,25 @@ fi
|
||||||
|
|
||||||
if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then
|
if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then
|
||||||
want_linux_netns=yes
|
want_linux_netns=yes
|
||||||
|
|
||||||
|
# Checks for header files.
|
||||||
|
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h])
|
||||||
|
|
||||||
|
# Checks for typedefs, structures, and compiler characteristics.
|
||||||
|
AC_C_INLINE
|
||||||
|
AC_TYPE_INT32_T
|
||||||
|
AC_TYPE_PID_T
|
||||||
|
AC_TYPE_SIZE_T
|
||||||
|
AC_TYPE_SSIZE_T
|
||||||
|
AC_TYPE_UINT32_T
|
||||||
|
AC_TYPE_UINT8_T
|
||||||
|
|
||||||
|
# Checks for library functions.
|
||||||
|
AC_FUNC_FORK
|
||||||
|
AC_FUNC_MALLOC
|
||||||
|
AC_FUNC_REALLOC
|
||||||
|
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
|
||||||
|
|
||||||
PKG_CHECK_MODULES(libev, libev,
|
PKG_CHECK_MODULES(libev, libev,
|
||||||
AC_MSG_RESULT([found libev using pkgconfig OK])
|
AC_MSG_RESULT([found libev using pkgconfig OK])
|
||||||
AC_SUBST(libev_CFLAGS)
|
AC_SUBST(libev_CFLAGS)
|
||||||
|
@ -209,7 +196,6 @@ if [test "x$want_python" = "xyes" && test "x$enable_docs" = "xyes"] ; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Variable substitutions
|
# Variable substitutions
|
||||||
AM_CONDITIONAL(WANT_GUI, test x$enable_gui = xyes)
|
|
||||||
AM_CONDITIONAL(WANT_DAEMON, test x$enable_daemon = xyes)
|
AM_CONDITIONAL(WANT_DAEMON, test x$enable_daemon = xyes)
|
||||||
AM_CONDITIONAL(WANT_DOCS, test x$want_docs = xyes)
|
AM_CONDITIONAL(WANT_DOCS, test x$want_docs = xyes)
|
||||||
AM_CONDITIONAL(WANT_PYTHON, test x$want_python = xyes)
|
AM_CONDITIONAL(WANT_PYTHON, test x$want_python = xyes)
|
||||||
|
@ -224,9 +210,6 @@ fi
|
||||||
|
|
||||||
# Output files
|
# Output files
|
||||||
AC_CONFIG_FILES([Makefile
|
AC_CONFIG_FILES([Makefile
|
||||||
gui/version.tcl
|
|
||||||
gui/Makefile
|
|
||||||
gui/icons/Makefile
|
|
||||||
man/Makefile
|
man/Makefile
|
||||||
docs/Makefile
|
docs/Makefile
|
||||||
daemon/Makefile
|
daemon/Makefile
|
||||||
|
@ -248,17 +231,12 @@ Build:
|
||||||
Prefix: ${prefix}
|
Prefix: ${prefix}
|
||||||
Exec Prefix: ${exec_prefix}
|
Exec Prefix: ${exec_prefix}
|
||||||
|
|
||||||
GUI:
|
|
||||||
GUI path: ${CORE_LIB_DIR}
|
|
||||||
GUI config: ${CORE_GUI_CONF_DIR}
|
|
||||||
|
|
||||||
Daemon:
|
Daemon:
|
||||||
Daemon path: ${bindir}
|
Daemon path: ${bindir}
|
||||||
Daemon config: ${CORE_CONF_DIR}
|
Daemon config: ${CORE_CONF_DIR}
|
||||||
Python: ${PYTHON}
|
Python: ${PYTHON}
|
||||||
|
|
||||||
Features to build:
|
Features to build:
|
||||||
Build GUI: ${enable_gui}
|
|
||||||
Build Daemon: ${enable_daemon}
|
Build Daemon: ${enable_daemon}
|
||||||
Documentation: ${want_docs}
|
Documentation: ${want_docs}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
# CORE
|
# CORE
|
||||||
# (c)2010-2012 the Boeing Company.
|
|
||||||
# See the LICENSE file included in this distribution.
|
|
||||||
#
|
|
||||||
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
|
||||||
#
|
#
|
||||||
# Makefile for building netns components.
|
# Makefile for building netns components.
|
||||||
#
|
#
|
||||||
|
@ -25,10 +21,7 @@ DISTCLEANFILES = Makefile.in
|
||||||
|
|
||||||
# files to include with distribution tarball
|
# files to include with distribution tarball
|
||||||
EXTRA_DIST = core \
|
EXTRA_DIST = core \
|
||||||
data \
|
|
||||||
doc/conf.py.in \
|
doc/conf.py.in \
|
||||||
examples \
|
|
||||||
scripts \
|
|
||||||
tests \
|
tests \
|
||||||
setup.cfg \
|
setup.cfg \
|
||||||
poetry.lock \
|
poetry.lock \
|
||||||
|
|
|
@ -4,19 +4,28 @@ 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, Callable, Dict, Generator, Iterable, List, Optional, Tuple
|
from typing import Any, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
from core.api.grpc import core_pb2, core_pb2_grpc, emane_pb2, wrappers
|
from core.api.grpc import core_pb2, core_pb2_grpc, emane_pb2, wrappers
|
||||||
from core.api.grpc.configservices_pb2 import (
|
from core.api.grpc.configservices_pb2 import (
|
||||||
GetConfigServiceDefaultsRequest,
|
GetConfigServiceDefaultsRequest,
|
||||||
|
GetConfigServiceRenderedRequest,
|
||||||
GetNodeConfigServiceRequest,
|
GetNodeConfigServiceRequest,
|
||||||
)
|
)
|
||||||
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
|
from core.api.grpc.core_pb2 import (
|
||||||
|
ExecuteScriptRequest,
|
||||||
|
GetConfigRequest,
|
||||||
|
GetWirelessConfigRequest,
|
||||||
|
LinkedRequest,
|
||||||
|
WirelessConfigRequest,
|
||||||
|
WirelessLinkedRequest,
|
||||||
|
)
|
||||||
from core.api.grpc.emane_pb2 import (
|
from core.api.grpc.emane_pb2 import (
|
||||||
EmaneLinkRequest,
|
EmaneLinkRequest,
|
||||||
GetEmaneEventChannelRequest,
|
GetEmaneEventChannelRequest,
|
||||||
|
@ -43,17 +52,19 @@ from core.api.grpc.wlan_pb2 import (
|
||||||
WlanConfig,
|
WlanConfig,
|
||||||
WlanLinkRequest,
|
WlanLinkRequest,
|
||||||
)
|
)
|
||||||
|
from core.api.grpc.wrappers import LinkOptions
|
||||||
from core.emulator.data import IpPrefixes
|
from core.emulator.data import IpPrefixes
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
|
from core.utils import SetQueue
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MoveNodesStreamer:
|
class MoveNodesStreamer:
|
||||||
def __init__(self, session_id: int = None, source: str = None) -> None:
|
def __init__(self, session_id: int, source: str = None) -> None:
|
||||||
self.session_id = session_id
|
self.session_id: int = session_id
|
||||||
self.source = source
|
self.source: Optional[str] = source
|
||||||
self.queue: Queue = Queue()
|
self.queue: SetQueue = SetQueue()
|
||||||
|
|
||||||
def send_position(self, node_id: int, x: float, y: float) -> None:
|
def send_position(self, node_id: int, x: float, y: float) -> None:
|
||||||
position = wrappers.Position(x=x, y=y)
|
position = wrappers.Position(x=x, y=y)
|
||||||
|
@ -150,12 +161,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)
|
||||||
|
@ -225,7 +236,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.
|
||||||
|
|
||||||
|
@ -275,7 +286,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.
|
||||||
|
|
||||||
|
@ -344,7 +355,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.
|
||||||
|
@ -418,7 +429,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.
|
||||||
|
|
||||||
|
@ -526,7 +537,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.
|
||||||
|
|
||||||
|
@ -563,26 +574,9 @@ class CoreGrpcClient:
|
||||||
response = self.stub.GetNodeTerminal(request)
|
response = self.stub.GetNodeTerminal(request)
|
||||||
return response.terminal
|
return response.terminal
|
||||||
|
|
||||||
def get_node_links(self, session_id: int, node_id: int) -> List[wrappers.Link]:
|
|
||||||
"""
|
|
||||||
Get current links for a node.
|
|
||||||
|
|
||||||
:param session_id: session id
|
|
||||||
:param node_id: node id
|
|
||||||
:return: list of links
|
|
||||||
:raises grpc.RpcError: when session or node doesn't exist
|
|
||||||
"""
|
|
||||||
request = core_pb2.GetNodeLinksRequest(session_id=session_id, node_id=node_id)
|
|
||||||
response = self.stub.GetNodeLinks(request)
|
|
||||||
links = []
|
|
||||||
for link_proto in response.links:
|
|
||||||
link = wrappers.Link.from_proto(link_proto)
|
|
||||||
links.append(link)
|
|
||||||
return links
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -653,7 +647,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.
|
||||||
|
|
||||||
|
@ -667,7 +661,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.
|
||||||
|
@ -713,7 +707,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.
|
||||||
|
|
||||||
|
@ -730,7 +724,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.
|
||||||
|
@ -741,9 +735,9 @@ class CoreGrpcClient:
|
||||||
:raises grpc.RpcError: when session doesn't exist
|
:raises grpc.RpcError: when session doesn't exist
|
||||||
"""
|
"""
|
||||||
defaults = []
|
defaults = []
|
||||||
for node_type in service_defaults:
|
for model in service_defaults:
|
||||||
services = service_defaults[node_type]
|
services = service_defaults[model]
|
||||||
default = ServiceDefaults(node_type=node_type, services=services)
|
default = ServiceDefaults(model=model, services=services)
|
||||||
defaults.append(default)
|
defaults.append(default)
|
||||||
request = SetServiceDefaultsRequest(session_id=session_id, defaults=defaults)
|
request = SetServiceDefaultsRequest(session_id=session_id, defaults=defaults)
|
||||||
response = self.stub.SetServiceDefaults(request)
|
response = self.stub.SetServiceDefaults(request)
|
||||||
|
@ -836,7 +830,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.
|
||||||
|
|
||||||
|
@ -850,7 +844,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.
|
||||||
|
@ -868,7 +862,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.
|
||||||
|
|
||||||
|
@ -916,7 +910,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.
|
||||||
|
|
||||||
|
@ -947,7 +941,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.
|
||||||
|
@ -958,20 +952,26 @@ 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(self, name: str) -> wrappers.ConfigServiceDefaults:
|
def get_config_service_defaults(
|
||||||
|
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(name=name)
|
request = GetConfigServiceDefaultsRequest(
|
||||||
|
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.
|
||||||
|
|
||||||
|
@ -987,6 +987,23 @@ class CoreGrpcClient:
|
||||||
response = self.stub.GetNodeConfigService(request)
|
response = self.stub.GetNodeConfigService(request)
|
||||||
return dict(response.config)
|
return dict(response.config)
|
||||||
|
|
||||||
|
def get_config_service_rendered(
|
||||||
|
self, session_id: int, node_id: int, name: str
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""
|
||||||
|
Retrieve the rendered config service files for a node.
|
||||||
|
|
||||||
|
:param session_id: id of session
|
||||||
|
:param node_id: id of node
|
||||||
|
:param name: name of service
|
||||||
|
:return: dict mapping names of files to rendered data
|
||||||
|
"""
|
||||||
|
request = GetConfigServiceRenderedRequest(
|
||||||
|
session_id=session_id, node_id=node_id, name=name
|
||||||
|
)
|
||||||
|
response = self.stub.GetConfigServiceRendered(request)
|
||||||
|
return dict(response.rendered)
|
||||||
|
|
||||||
def get_emane_event_channel(
|
def get_emane_event_channel(
|
||||||
self, session_id: int, nem_id: int
|
self, session_id: int, nem_id: int
|
||||||
) -> wrappers.EmaneEventChannel:
|
) -> wrappers.EmaneEventChannel:
|
||||||
|
@ -1049,6 +1066,81 @@ class CoreGrpcClient:
|
||||||
"""
|
"""
|
||||||
self.stub.EmanePathlosses(streamer.iter())
|
self.stub.EmanePathlosses(streamer.iter())
|
||||||
|
|
||||||
|
def linked(
|
||||||
|
self,
|
||||||
|
session_id: int,
|
||||||
|
node1_id: int,
|
||||||
|
node2_id: int,
|
||||||
|
iface1_id: int,
|
||||||
|
iface2_id: int,
|
||||||
|
linked: bool,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Link or unlink an existing core wired link.
|
||||||
|
|
||||||
|
:param session_id: session containing the link
|
||||||
|
:param node1_id: first node in link
|
||||||
|
:param node2_id: second node in link
|
||||||
|
:param iface1_id: node1 interface
|
||||||
|
:param iface2_id: node2 interface
|
||||||
|
:param linked: True to connect link, False to disconnect
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
request = LinkedRequest(
|
||||||
|
session_id=session_id,
|
||||||
|
node1_id=node1_id,
|
||||||
|
node2_id=node2_id,
|
||||||
|
iface1_id=iface1_id,
|
||||||
|
iface2_id=iface2_id,
|
||||||
|
linked=linked,
|
||||||
|
)
|
||||||
|
self.stub.Linked(request)
|
||||||
|
|
||||||
|
def wireless_linked(
|
||||||
|
self,
|
||||||
|
session_id: int,
|
||||||
|
wireless_id: int,
|
||||||
|
node1_id: int,
|
||||||
|
node2_id: int,
|
||||||
|
linked: bool,
|
||||||
|
) -> None:
|
||||||
|
request = WirelessLinkedRequest(
|
||||||
|
session_id=session_id,
|
||||||
|
wireless_id=wireless_id,
|
||||||
|
node1_id=node1_id,
|
||||||
|
node2_id=node2_id,
|
||||||
|
linked=linked,
|
||||||
|
)
|
||||||
|
self.stub.WirelessLinked(request)
|
||||||
|
|
||||||
|
def wireless_config(
|
||||||
|
self,
|
||||||
|
session_id: int,
|
||||||
|
wireless_id: int,
|
||||||
|
node1_id: int,
|
||||||
|
node2_id: int,
|
||||||
|
options1: LinkOptions,
|
||||||
|
options2: LinkOptions = None,
|
||||||
|
) -> None:
|
||||||
|
if options2 is None:
|
||||||
|
options2 = options1
|
||||||
|
request = WirelessConfigRequest(
|
||||||
|
session_id=session_id,
|
||||||
|
wireless_id=wireless_id,
|
||||||
|
node1_id=node1_id,
|
||||||
|
node2_id=node2_id,
|
||||||
|
options1=options1.to_proto(),
|
||||||
|
options2=options2.to_proto(),
|
||||||
|
)
|
||||||
|
self.stub.WirelessConfig(request)
|
||||||
|
|
||||||
|
def get_wireless_config(
|
||||||
|
self, session_id: int, node_id: int
|
||||||
|
) -> dict[str, wrappers.ConfigOption]:
|
||||||
|
request = GetWirelessConfigRequest(session_id=session_id, node_id=node_id)
|
||||||
|
response = self.stub.GetWirelessConfig(request)
|
||||||
|
return wrappers.ConfigOption.from_dict(response.config)
|
||||||
|
|
||||||
def connect(self) -> None:
|
def connect(self) -> None:
|
||||||
"""
|
"""
|
||||||
Open connection to server, must be closed manually.
|
Open connection to server, must be closed manually.
|
||||||
|
@ -1071,7 +1163,7 @@ class CoreGrpcClient:
|
||||||
self.channel = None
|
self.channel = None
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def context_connect(self) -> Generator:
|
def context_connect(self) -> Generator[None, None, None]:
|
||||||
"""
|
"""
|
||||||
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,10 @@
|
||||||
import logging
|
import logging
|
||||||
|
from collections.abc import Iterable
|
||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
from typing import Iterable, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2, grpcutils
|
||||||
from core.api.grpc.grpcutils import convert_link
|
from core.api.grpc.grpcutils import convert_link_data
|
||||||
from core.emulator.data import (
|
from core.emulator.data import (
|
||||||
ConfigData,
|
ConfigData,
|
||||||
EventData,
|
EventData,
|
||||||
|
@ -17,28 +18,18 @@ from core.emulator.session import Session
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def handle_node_event(node_data: NodeData) -> core_pb2.Event:
|
def handle_node_event(session: Session, 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
|
||||||
x, y, _ = node.position.get()
|
emane_configs = grpcutils.get_emane_model_configs_dict(session)
|
||||||
position = core_pb2.Position(x=x, y=y)
|
node_emane_configs = emane_configs.get(node.id, [])
|
||||||
lon, lat, alt = node.position.get_geo()
|
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
|
||||||
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.type,
|
|
||||||
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)
|
||||||
|
@ -51,7 +42,7 @@ def handle_link_event(link_data: LinkData) -> core_pb2.Event:
|
||||||
:param link_data: link data
|
:param link_data: link data
|
||||||
:return: link event that has message type and link information
|
:return: link event that has message type and link information
|
||||||
"""
|
"""
|
||||||
link = convert_link(link_data)
|
link = convert_link_data(link_data)
|
||||||
message_type = link_data.message_type.value
|
message_type = link_data.message_type.value
|
||||||
link_event = core_pb2.LinkEvent(message_type=message_type, link=link)
|
link_event = core_pb2.LinkEvent(message_type=message_type, link=link)
|
||||||
return core_pb2.Event(link_event=link_event, source=link_data.source)
|
return core_pb2.Event(link_event=link_event, source=link_data.source)
|
||||||
|
@ -189,7 +180,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(data)
|
event = handle_node_event(self.session, 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, Dict, List, Tuple, Type, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from grpc import ServicerContext
|
from grpc import ServicerContext
|
||||||
|
@ -17,17 +17,27 @@ from core.api.grpc.services_pb2 import (
|
||||||
ServiceDefaults,
|
ServiceDefaults,
|
||||||
)
|
)
|
||||||
from core.config import ConfigurableOptions
|
from core.config import ConfigurableOptions
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet, EmaneOptions
|
||||||
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
|
from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||||
from core.emulator.enumerations import LinkTypes, NodeTypes
|
from core.emulator.enumerations import LinkTypes, NodeTypes
|
||||||
|
from core.emulator.links import CoreLink
|
||||||
from core.emulator.session import Session
|
from core.emulator.session import Session
|
||||||
from core.errors import CoreError
|
from core.errors import CoreError
|
||||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||||
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
from core.nodes.base import (
|
||||||
from core.nodes.docker import DockerNode
|
CoreNode,
|
||||||
|
CoreNodeBase,
|
||||||
|
CoreNodeOptions,
|
||||||
|
NodeBase,
|
||||||
|
NodeOptions,
|
||||||
|
Position,
|
||||||
|
)
|
||||||
|
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
|
from core.nodes.lxd import LxcNode, LxcOptions
|
||||||
from core.nodes.network import 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.services.coreservices import CoreService
|
from core.services.coreservices import CoreService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -53,34 +63,33 @@ class CpuUsage:
|
||||||
return (total_diff - idle_diff) / total_diff
|
return (total_diff - idle_diff) / total_diff
|
||||||
|
|
||||||
|
|
||||||
def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]:
|
def add_node_data(
|
||||||
|
_class: type[NodeBase], node_proto: core_pb2.Node
|
||||||
|
) -> tuple[Position, NodeOptions]:
|
||||||
"""
|
"""
|
||||||
Convert node protobuf message to data for creating a node.
|
Convert node protobuf message to data for creating a node.
|
||||||
|
|
||||||
|
:param _class: node class to create options from
|
||||||
:param node_proto: node proto message
|
:param node_proto: node proto message
|
||||||
:return: node type, id, and options
|
:return: node type, id, and options
|
||||||
"""
|
"""
|
||||||
_id = node_proto.id
|
options = _class.create_options()
|
||||||
_type = NodeTypes(node_proto.type)
|
options.icon = node_proto.icon
|
||||||
options = NodeOptions(
|
options.canvas = node_proto.canvas
|
||||||
name=node_proto.name,
|
if isinstance(options, CoreNodeOptions):
|
||||||
model=node_proto.model,
|
options.model = node_proto.model
|
||||||
icon=node_proto.icon,
|
options.services = node_proto.services
|
||||||
image=node_proto.image,
|
options.config_services = node_proto.config_services
|
||||||
services=node_proto.services,
|
if isinstance(options, EmaneOptions):
|
||||||
config_services=node_proto.config_services,
|
options.emane_model = node_proto.emane
|
||||||
canvas=node_proto.canvas,
|
if isinstance(options, (DockerOptions, LxcOptions, PodmanOptions)):
|
||||||
)
|
options.image = node_proto.image
|
||||||
if node_proto.emane:
|
position = Position()
|
||||||
options.emane = node_proto.emane
|
position.set(node_proto.position.x, node_proto.position.y)
|
||||||
if node_proto.server:
|
|
||||||
options.server = node_proto.server
|
|
||||||
position = node_proto.position
|
|
||||||
options.set_position(position.x, position.y)
|
|
||||||
if node_proto.HasField("geo"):
|
if node_proto.HasField("geo"):
|
||||||
geo = node_proto.geo
|
geo = node_proto.geo
|
||||||
options.set_location(geo.lat, geo.lon, geo.alt)
|
position.set_geo(geo.lon, geo.lat, geo.alt)
|
||||||
return _type, _id, options
|
return position, options
|
||||||
|
|
||||||
|
|
||||||
def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
|
def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
|
||||||
|
@ -109,8 +118,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, LinkTypes]:
|
) -> tuple[InterfaceData, InterfaceData, LinkOptions]:
|
||||||
"""
|
"""
|
||||||
Convert link proto to link interfaces and options data.
|
Convert link proto to link interfaces and options data.
|
||||||
|
|
||||||
|
@ -119,7 +128,6 @@ def add_link_data(
|
||||||
"""
|
"""
|
||||||
iface1_data = link_iface(link_proto.iface1)
|
iface1_data = link_iface(link_proto.iface1)
|
||||||
iface2_data = link_iface(link_proto.iface2)
|
iface2_data = link_iface(link_proto.iface2)
|
||||||
link_type = LinkTypes(link_proto.type)
|
|
||||||
options = LinkOptions()
|
options = LinkOptions()
|
||||||
options_proto = link_proto.options
|
options_proto = link_proto.options
|
||||||
if options_proto:
|
if options_proto:
|
||||||
|
@ -134,12 +142,12 @@ def add_link_data(
|
||||||
options.buffer = options_proto.buffer
|
options.buffer = options_proto.buffer
|
||||||
options.unidirectional = options_proto.unidirectional
|
options.unidirectional = options_proto.unidirectional
|
||||||
options.key = options_proto.key
|
options.key = options_proto.key
|
||||||
return iface1_data, iface2_data, options, link_type
|
return iface1_data, iface2_data, options
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -149,9 +157,17 @@ def create_nodes(
|
||||||
"""
|
"""
|
||||||
funcs = []
|
funcs = []
|
||||||
for node_proto in node_protos:
|
for node_proto in node_protos:
|
||||||
_type, _id, options = add_node_data(node_proto)
|
_type = NodeTypes(node_proto.type)
|
||||||
_class = session.get_node_class(_type)
|
_class = session.get_node_class(_type)
|
||||||
args = (_class, _id, options)
|
position, options = add_node_data(_class, node_proto)
|
||||||
|
args = (
|
||||||
|
_class,
|
||||||
|
node_proto.id or None,
|
||||||
|
node_proto.name or None,
|
||||||
|
node_proto.server or None,
|
||||||
|
position,
|
||||||
|
options,
|
||||||
|
)
|
||||||
funcs.append((session.add_node, args, {}))
|
funcs.append((session.add_node, args, {}))
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
results, exceptions = utils.threadpool(funcs)
|
results, exceptions = utils.threadpool(funcs)
|
||||||
|
@ -161,8 +177,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.
|
||||||
|
|
||||||
|
@ -174,8 +190,8 @@ def create_links(
|
||||||
for link_proto in link_protos:
|
for link_proto in link_protos:
|
||||||
node1_id = link_proto.node1_id
|
node1_id = link_proto.node1_id
|
||||||
node2_id = link_proto.node2_id
|
node2_id = link_proto.node2_id
|
||||||
iface1, iface2, options, link_type = add_link_data(link_proto)
|
iface1, iface2, options = add_link_data(link_proto)
|
||||||
args = (node1_id, node2_id, iface1, iface2, options, link_type)
|
args = (node1_id, node2_id, iface1, iface2, options)
|
||||||
funcs.append((session.add_link, args, {}))
|
funcs.append((session.add_link, args, {}))
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
results, exceptions = utils.threadpool(funcs)
|
results, exceptions = utils.threadpool(funcs)
|
||||||
|
@ -185,8 +201,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.
|
||||||
|
|
||||||
|
@ -198,8 +214,8 @@ def edit_links(
|
||||||
for link_proto in link_protos:
|
for link_proto in link_protos:
|
||||||
node1_id = link_proto.node1_id
|
node1_id = link_proto.node1_id
|
||||||
node2_id = link_proto.node2_id
|
node2_id = link_proto.node2_id
|
||||||
iface1, iface2, options, link_type = add_link_data(link_proto)
|
iface1, iface2, options = add_link_data(link_proto)
|
||||||
args = (node1_id, node2_id, iface1.id, iface2.id, options, link_type)
|
args = (node1_id, node2_id, iface1.id, iface2.id, options)
|
||||||
funcs.append((session.update_link, args, {}))
|
funcs.append((session.update_link, args, {}))
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
results, exceptions = utils.threadpool(funcs)
|
results, exceptions = utils.threadpool(funcs)
|
||||||
|
@ -220,10 +236,26 @@ def convert_value(value: Any) -> str:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def convert_session_options(session: Session) -> dict[str, common_pb2.ConfigOption]:
|
||||||
|
config_options = {}
|
||||||
|
for option in session.options.options:
|
||||||
|
value = session.options.get(option.id)
|
||||||
|
config_option = common_pb2.ConfigOption(
|
||||||
|
label=option.label,
|
||||||
|
name=option.id,
|
||||||
|
value=value,
|
||||||
|
type=option.type.value,
|
||||||
|
select=option.options,
|
||||||
|
group="Options",
|
||||||
|
)
|
||||||
|
config_options[option.id] = config_option
|
||||||
|
return config_options
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -252,7 +284,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.
|
||||||
|
@ -270,7 +302,6 @@ def get_node_proto(
|
||||||
lat=node.position.lat, lon=node.position.lon, alt=node.position.alt
|
lat=node.position.lat, lon=node.position.lon, alt=node.position.alt
|
||||||
)
|
)
|
||||||
services = [x.name for x in node.services]
|
services = [x.name for x in node.services]
|
||||||
model = node.type
|
|
||||||
node_dir = None
|
node_dir = None
|
||||||
config_services = []
|
config_services = []
|
||||||
if isinstance(node, CoreNodeBase):
|
if isinstance(node, CoreNodeBase):
|
||||||
|
@ -281,9 +312,9 @@ def get_node_proto(
|
||||||
channel = str(node.ctrlchnlname)
|
channel = str(node.ctrlchnlname)
|
||||||
emane_model = None
|
emane_model = None
|
||||||
if isinstance(node, EmaneNet):
|
if isinstance(node, EmaneNet):
|
||||||
emane_model = node.model.name
|
emane_model = node.wireless_model.name
|
||||||
image = None
|
image = None
|
||||||
if isinstance(node, (DockerNode, LxcNode)):
|
if isinstance(node, (DockerNode, LxcNode, PodmanNode)):
|
||||||
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(
|
||||||
|
@ -291,6 +322,21 @@ def get_node_proto(
|
||||||
)
|
)
|
||||||
if wlan_config:
|
if wlan_config:
|
||||||
wlan_config = get_config_options(wlan_config, BasicRangeModel)
|
wlan_config = get_config_options(wlan_config, BasicRangeModel)
|
||||||
|
# check for wireless config
|
||||||
|
wireless_config = None
|
||||||
|
if isinstance(node, WirelessNode):
|
||||||
|
configs = node.get_config()
|
||||||
|
wireless_config = {}
|
||||||
|
for config in configs.values():
|
||||||
|
config_option = common_pb2.ConfigOption(
|
||||||
|
label=config.label,
|
||||||
|
name=config.id,
|
||||||
|
value=config.default,
|
||||||
|
type=config.type.value,
|
||||||
|
select=config.options,
|
||||||
|
group=config.group,
|
||||||
|
)
|
||||||
|
wireless_config[config.id] = config_option
|
||||||
# check for mobility config
|
# check for mobility config
|
||||||
mobility_config = session.mobility.get_configs(
|
mobility_config = session.mobility.get_configs(
|
||||||
node.id, config_type=Ns2ScriptedMobility.name
|
node.id, config_type=Ns2ScriptedMobility.name
|
||||||
|
@ -325,7 +371,7 @@ def get_node_proto(
|
||||||
id=node.id,
|
id=node.id,
|
||||||
name=node.name,
|
name=node.name,
|
||||||
emane=emane_model,
|
emane=emane_model,
|
||||||
model=model,
|
model=node.model,
|
||||||
type=node_type.value,
|
type=node_type.value,
|
||||||
position=position,
|
position=position,
|
||||||
geo=geo,
|
geo=geo,
|
||||||
|
@ -337,6 +383,7 @@ def get_node_proto(
|
||||||
channel=channel,
|
channel=channel,
|
||||||
canvas=node.canvas,
|
canvas=node.canvas,
|
||||||
wlan_config=wlan_config,
|
wlan_config=wlan_config,
|
||||||
|
wireless_config=wireless_config,
|
||||||
mobility_config=mobility_config,
|
mobility_config=mobility_config,
|
||||||
service_configs=service_configs,
|
service_configs=service_configs,
|
||||||
config_service_configs=config_service_configs,
|
config_service_configs=config_service_configs,
|
||||||
|
@ -344,61 +391,84 @@ def get_node_proto(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_links(node: NodeBase):
|
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.
|
||||||
|
|
||||||
|
:param session: session to get links for node
|
||||||
:param node: node to get links from
|
:param node: node to get links from
|
||||||
:return: protobuf links
|
:return: protobuf links
|
||||||
"""
|
"""
|
||||||
|
link_protos = []
|
||||||
|
for core_link in session.link_manager.node_links(node):
|
||||||
|
link_protos.extend(convert_core_link(core_link))
|
||||||
|
if isinstance(node, (WlanNode, EmaneNet)):
|
||||||
|
for link_data in node.links():
|
||||||
|
link_protos.append(convert_link_data(link_data))
|
||||||
|
return link_protos
|
||||||
|
|
||||||
|
|
||||||
|
def convert_iface(iface: CoreInterface) -> core_pb2.Interface:
|
||||||
|
"""
|
||||||
|
Convert interface to protobuf.
|
||||||
|
|
||||||
|
:param iface: interface to convert
|
||||||
|
:return: protobuf interface
|
||||||
|
"""
|
||||||
|
if isinstance(iface.node, CoreNetwork):
|
||||||
|
return core_pb2.Interface(id=iface.id)
|
||||||
|
else:
|
||||||
|
ip4 = iface.get_ip4()
|
||||||
|
ip4_mask = ip4.prefixlen if ip4 else None
|
||||||
|
ip4 = str(ip4.ip) if ip4 else None
|
||||||
|
ip6 = iface.get_ip6()
|
||||||
|
ip6_mask = ip6.prefixlen if ip6 else None
|
||||||
|
ip6 = str(ip6.ip) if ip6 else None
|
||||||
|
mac = str(iface.mac) if iface.mac else None
|
||||||
|
return core_pb2.Interface(
|
||||||
|
id=iface.id,
|
||||||
|
name=iface.name,
|
||||||
|
mac=mac,
|
||||||
|
ip4=ip4,
|
||||||
|
ip4_mask=ip4_mask,
|
||||||
|
ip6=ip6,
|
||||||
|
ip6_mask=ip6_mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_core_link(core_link: CoreLink) -> list[core_pb2.Link]:
|
||||||
|
"""
|
||||||
|
Convert core link to protobuf data.
|
||||||
|
|
||||||
|
:param core_link: core link to convert
|
||||||
|
:return: protobuf link data
|
||||||
|
"""
|
||||||
links = []
|
links = []
|
||||||
for link in node.links():
|
node1, iface1 = core_link.node1, core_link.iface1
|
||||||
link_proto = convert_link(link)
|
node2, iface2 = core_link.node2, core_link.iface2
|
||||||
links.append(link_proto)
|
unidirectional = core_link.is_unidirectional()
|
||||||
|
link = convert_link(node1, iface1, node2, iface2, iface1.options, unidirectional)
|
||||||
|
links.append(link)
|
||||||
|
if unidirectional:
|
||||||
|
link = convert_link(
|
||||||
|
node2, iface2, node1, iface1, iface2.options, unidirectional
|
||||||
|
)
|
||||||
|
links.append(link)
|
||||||
return links
|
return links
|
||||||
|
|
||||||
|
|
||||||
def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface:
|
def convert_link_data(link_data: LinkData) -> core_pb2.Link:
|
||||||
return core_pb2.Interface(
|
|
||||||
id=iface_data.id,
|
|
||||||
name=iface_data.name,
|
|
||||||
mac=iface_data.mac,
|
|
||||||
ip4=iface_data.ip4,
|
|
||||||
ip4_mask=iface_data.ip4_mask,
|
|
||||||
ip6=iface_data.ip6,
|
|
||||||
ip6_mask=iface_data.ip6_mask,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions:
|
|
||||||
return core_pb2.LinkOptions(
|
|
||||||
jitter=options_data.jitter,
|
|
||||||
key=options_data.key,
|
|
||||||
mburst=options_data.mburst,
|
|
||||||
mer=options_data.mer,
|
|
||||||
loss=options_data.loss,
|
|
||||||
bandwidth=options_data.bandwidth,
|
|
||||||
burst=options_data.burst,
|
|
||||||
delay=options_data.delay,
|
|
||||||
dup=options_data.dup,
|
|
||||||
buffer=options_data.buffer,
|
|
||||||
unidirectional=options_data.unidirectional,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_link(link_data: LinkData) -> core_pb2.Link:
|
|
||||||
"""
|
"""
|
||||||
Convert link_data into core protobuf link.
|
Convert link_data into core protobuf link.
|
||||||
|
|
||||||
:param link_data: link to convert
|
:param link_data: link to convert
|
||||||
:return: core protobuf Link
|
:return: core protobuf Link
|
||||||
"""
|
"""
|
||||||
iface1 = None
|
iface1 = None
|
||||||
if link_data.iface1 is not None:
|
if link_data.iface1 is not None:
|
||||||
iface1 = convert_iface(link_data.iface1)
|
iface1 = convert_iface_data(link_data.iface1)
|
||||||
iface2 = None
|
iface2 = None
|
||||||
if link_data.iface2 is not None:
|
if link_data.iface2 is not None:
|
||||||
iface2 = convert_iface(link_data.iface2)
|
iface2 = convert_iface_data(link_data.iface2)
|
||||||
options = convert_link_options(link_data.options)
|
options = convert_link_options(link_data.options)
|
||||||
return core_pb2.Link(
|
return core_pb2.Link(
|
||||||
type=link_data.type.value,
|
type=link_data.type.value,
|
||||||
|
@ -413,27 +483,134 @@ def convert_link(link_data: LinkData) -> core_pb2.Link:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_net_stats() -> Dict[str, Dict]:
|
def convert_iface_data(iface_data: InterfaceData) -> core_pb2.Interface:
|
||||||
"""
|
"""
|
||||||
Retrieve status about the current interfaces in the system
|
Convert interface data to protobuf.
|
||||||
|
|
||||||
:return: send and receive status of the interfaces in the system
|
:param iface_data: interface data to convert
|
||||||
|
:return: interface protobuf
|
||||||
"""
|
"""
|
||||||
with open("/proc/net/dev", "r") as f:
|
return core_pb2.Interface(
|
||||||
data = f.readlines()[2:]
|
id=iface_data.id,
|
||||||
|
name=iface_data.name,
|
||||||
|
mac=iface_data.mac,
|
||||||
|
ip4=iface_data.ip4,
|
||||||
|
ip4_mask=iface_data.ip4_mask,
|
||||||
|
ip6=iface_data.ip6,
|
||||||
|
ip6_mask=iface_data.ip6_mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_link_options(options: LinkOptions) -> core_pb2.LinkOptions:
|
||||||
|
"""
|
||||||
|
Convert link options to protobuf.
|
||||||
|
|
||||||
|
:param options: link options to convert
|
||||||
|
:return: link options protobuf
|
||||||
|
"""
|
||||||
|
return core_pb2.LinkOptions(
|
||||||
|
jitter=options.jitter,
|
||||||
|
key=options.key,
|
||||||
|
mburst=options.mburst,
|
||||||
|
mer=options.mer,
|
||||||
|
loss=options.loss,
|
||||||
|
bandwidth=options.bandwidth,
|
||||||
|
burst=options.burst,
|
||||||
|
delay=options.delay,
|
||||||
|
dup=options.dup,
|
||||||
|
buffer=options.buffer,
|
||||||
|
unidirectional=options.unidirectional,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_options_proto(options: core_pb2.LinkOptions) -> LinkOptions:
|
||||||
|
return LinkOptions(
|
||||||
|
delay=options.delay,
|
||||||
|
bandwidth=options.bandwidth,
|
||||||
|
loss=options.loss,
|
||||||
|
dup=options.dup,
|
||||||
|
jitter=options.jitter,
|
||||||
|
mer=options.mer,
|
||||||
|
burst=options.burst,
|
||||||
|
mburst=options.mburst,
|
||||||
|
buffer=options.buffer,
|
||||||
|
unidirectional=options.unidirectional,
|
||||||
|
key=options.key,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_link(
|
||||||
|
node1: NodeBase,
|
||||||
|
iface1: Optional[CoreInterface],
|
||||||
|
node2: NodeBase,
|
||||||
|
iface2: Optional[CoreInterface],
|
||||||
|
options: LinkOptions,
|
||||||
|
unidirectional: bool,
|
||||||
|
) -> core_pb2.Link:
|
||||||
|
"""
|
||||||
|
Convert link objects to link protobuf.
|
||||||
|
|
||||||
|
:param node1: first node in link
|
||||||
|
:param iface1: node1 interface
|
||||||
|
:param node2: second node in link
|
||||||
|
:param iface2: node2 interface
|
||||||
|
:param options: link options
|
||||||
|
:param unidirectional: if this link is considered unidirectional
|
||||||
|
:return: protobuf link
|
||||||
|
"""
|
||||||
|
if iface1 is not None:
|
||||||
|
iface1 = convert_iface(iface1)
|
||||||
|
if iface2 is not None:
|
||||||
|
iface2 = convert_iface(iface2)
|
||||||
|
is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet))
|
||||||
|
is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet))
|
||||||
|
if not (is_node1_wireless or is_node2_wireless):
|
||||||
|
options = convert_link_options(options)
|
||||||
|
options.unidirectional = unidirectional
|
||||||
|
else:
|
||||||
|
options = None
|
||||||
|
return core_pb2.Link(
|
||||||
|
type=LinkTypes.WIRED.value,
|
||||||
|
node1_id=node1.id,
|
||||||
|
node2_id=node2.id,
|
||||||
|
iface1=iface1,
|
||||||
|
iface2=iface2,
|
||||||
|
options=options,
|
||||||
|
network_id=None,
|
||||||
|
label=None,
|
||||||
|
color=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_proc_net_dev(lines: list[str]) -> dict[str, dict[str, float]]:
|
||||||
|
"""
|
||||||
|
Parse lines of output from /proc/net/dev.
|
||||||
|
|
||||||
|
:param lines: lines of /proc/net/dev
|
||||||
|
:return: parsed device to tx/rx values
|
||||||
|
"""
|
||||||
stats = {}
|
stats = {}
|
||||||
for line in data:
|
for line in lines[2:]:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
line = line.split()
|
line = line.split()
|
||||||
line[0] = line[0].strip(":")
|
line[0] = line[0].strip(":")
|
||||||
stats[line[0]] = {"rx": float(line[1]), "tx": float(line[9])}
|
stats[line[0]] = {"rx": float(line[1]), "tx": float(line[9])}
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
def get_net_stats() -> dict[str, dict[str, float]]:
|
||||||
|
"""
|
||||||
|
Retrieve status about the current interfaces in the system
|
||||||
|
|
||||||
|
:return: send and receive status of the interfaces in the system
|
||||||
|
"""
|
||||||
|
with open("/proc/net/dev", "r") as f:
|
||||||
|
lines = f.readlines()[2:]
|
||||||
|
return parse_proc_net_dev(lines)
|
||||||
|
|
||||||
|
|
||||||
def session_location(session: Session, location: core_pb2.SessionLocation) -> None:
|
def session_location(session: Session, location: core_pb2.SessionLocation) -> None:
|
||||||
"""
|
"""
|
||||||
Set session location based on location proto.
|
Set session location based on location proto.
|
||||||
|
@ -490,39 +667,14 @@ def get_service_configuration(service: CoreService) -> NodeServiceData:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def iface_to_data(iface: CoreInterface) -> InterfaceData:
|
def iface_to_proto(session: Session, iface: CoreInterface) -> core_pb2.Interface:
|
||||||
ip4 = iface.get_ip4()
|
|
||||||
ip4_addr = str(ip4.ip) if ip4 else None
|
|
||||||
ip4_mask = ip4.prefixlen if ip4 else None
|
|
||||||
ip6 = iface.get_ip6()
|
|
||||||
ip6_addr = str(ip6.ip) if ip6 else None
|
|
||||||
ip6_mask = ip6.prefixlen if ip6 else None
|
|
||||||
return InterfaceData(
|
|
||||||
id=iface.node_id,
|
|
||||||
name=iface.name,
|
|
||||||
mac=str(iface.mac),
|
|
||||||
ip4=ip4_addr,
|
|
||||||
ip4_mask=ip4_mask,
|
|
||||||
ip6=ip6_addr,
|
|
||||||
ip6_mask=ip6_mask,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
|
|
||||||
"""
|
"""
|
||||||
Convenience for converting a core interface to the protobuf representation.
|
Convenience for converting a core interface to the protobuf representation.
|
||||||
|
|
||||||
:param node_id: id of node to convert interface for
|
:param session: session interface belongs to
|
||||||
:param iface: interface to convert
|
:param iface: interface to convert
|
||||||
:return: interface proto
|
:return: interface proto
|
||||||
"""
|
"""
|
||||||
if iface.node and iface.node.id == node_id:
|
|
||||||
_id = iface.node_id
|
|
||||||
else:
|
|
||||||
_id = iface.net_id
|
|
||||||
net_id = iface.net.id if iface.net else None
|
|
||||||
node_id = iface.node.id if iface.node else None
|
|
||||||
net2_id = iface.othernet.id if iface.othernet else None
|
|
||||||
ip4_net = iface.get_ip4()
|
ip4_net = iface.get_ip4()
|
||||||
ip4 = str(ip4_net.ip) if ip4_net else None
|
ip4 = str(ip4_net.ip) if ip4_net else None
|
||||||
ip4_mask = ip4_net.prefixlen if ip4_net else None
|
ip4_mask = ip4_net.prefixlen if ip4_net else None
|
||||||
|
@ -530,11 +682,13 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
|
||||||
ip6 = str(ip6_net.ip) if ip6_net else None
|
ip6 = str(ip6_net.ip) if ip6_net else None
|
||||||
ip6_mask = ip6_net.prefixlen if ip6_net else None
|
ip6_mask = ip6_net.prefixlen if ip6_net else None
|
||||||
mac = str(iface.mac) if iface.mac else None
|
mac = str(iface.mac) if iface.mac else None
|
||||||
|
nem_id = None
|
||||||
|
nem_port = None
|
||||||
|
if isinstance(iface.net, EmaneNet):
|
||||||
|
nem_id = session.emane.get_nem_id(iface)
|
||||||
|
nem_port = session.emane.get_nem_port(iface)
|
||||||
return core_pb2.Interface(
|
return core_pb2.Interface(
|
||||||
id=_id,
|
id=iface.id,
|
||||||
net_id=net_id,
|
|
||||||
net2_id=net2_id,
|
|
||||||
node_id=node_id,
|
|
||||||
name=iface.name,
|
name=iface.name,
|
||||||
mac=mac,
|
mac=mac,
|
||||||
mtu=iface.mtu,
|
mtu=iface.mtu,
|
||||||
|
@ -543,6 +697,8 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
|
||||||
ip4_mask=ip4_mask,
|
ip4_mask=ip4_mask,
|
||||||
ip6=ip6,
|
ip6=ip6,
|
||||||
ip6_mask=ip6_mask,
|
ip6_mask=ip6_mask,
|
||||||
|
nem_id=nem_id,
|
||||||
|
nem_port=nem_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -573,7 +729,13 @@ 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.
|
||||||
|
|
||||||
|
:param session: session to get emane model configuration for
|
||||||
|
:return: dict of emane model protobuf configurations
|
||||||
|
"""
|
||||||
configs = {}
|
configs = {}
|
||||||
for _id, model_configs in session.emane.node_configs.items():
|
for _id, model_configs in session.emane.node_configs.items():
|
||||||
for model_name in model_configs:
|
for model_name in model_configs:
|
||||||
|
@ -590,7 +752,13 @@ 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.
|
||||||
|
|
||||||
|
:param session: session to get hooks for
|
||||||
|
:return: list of hook protobufs
|
||||||
|
"""
|
||||||
hooks = []
|
hooks = []
|
||||||
for state in session.hooks:
|
for state in session.hooks:
|
||||||
state_hooks = session.hooks[state]
|
state_hooks = session.hooks[state]
|
||||||
|
@ -600,10 +768,16 @@ 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.
|
||||||
|
|
||||||
|
:param session: session to get default service sets for
|
||||||
|
:return: list of default service sets
|
||||||
|
"""
|
||||||
default_services = []
|
default_services = []
|
||||||
for name, services in session.services.default_services.items():
|
for model, services in session.services.default_services.items():
|
||||||
default_service = ServiceDefaults(node_type=name, services=services)
|
default_service = ServiceDefaults(model=model, services=services)
|
||||||
default_services.append(default_service)
|
default_services.append(default_service)
|
||||||
return default_services
|
return default_services
|
||||||
|
|
||||||
|
@ -611,6 +785,14 @@ def get_default_services(session: Session) -> List[ServiceDefaults]:
|
||||||
def get_mobility_node(
|
def get_mobility_node(
|
||||||
session: Session, node_id: int, context: ServicerContext
|
session: Session, node_id: int, context: ServicerContext
|
||||||
) -> Union[WlanNode, EmaneNet]:
|
) -> Union[WlanNode, EmaneNet]:
|
||||||
|
"""
|
||||||
|
Get mobility node.
|
||||||
|
|
||||||
|
:param session: session to get node from
|
||||||
|
:param node_id: id of node to get
|
||||||
|
:param context: grpc context
|
||||||
|
:return: wlan or emane node
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return session.get_node(node_id, WlanNode)
|
return session.get_node(node_id, WlanNode)
|
||||||
except CoreError:
|
except CoreError:
|
||||||
|
@ -621,17 +803,26 @@ def get_mobility_node(
|
||||||
|
|
||||||
|
|
||||||
def convert_session(session: Session) -> wrappers.Session:
|
def convert_session(session: Session) -> wrappers.Session:
|
||||||
links = []
|
"""
|
||||||
nodes = []
|
Convert session to its wrapped version.
|
||||||
|
|
||||||
|
:param session: session to convert
|
||||||
|
:return: wrapped session data
|
||||||
|
"""
|
||||||
emane_configs = get_emane_model_configs_dict(session)
|
emane_configs = get_emane_model_configs_dict(session)
|
||||||
|
nodes = []
|
||||||
|
links = []
|
||||||
for _id in session.nodes:
|
for _id in session.nodes:
|
||||||
node = session.nodes[_id]
|
node = session.nodes[_id]
|
||||||
if not isinstance(node, (PtpNet, CtrlNet)):
|
if not isinstance(node, (PtpNet, CtrlNet)):
|
||||||
node_emane_configs = emane_configs.get(node.id, [])
|
node_emane_configs = emane_configs.get(node.id, [])
|
||||||
node_proto = get_node_proto(session, node, node_emane_configs)
|
node_proto = get_node_proto(session, node, node_emane_configs)
|
||||||
nodes.append(node_proto)
|
nodes.append(node_proto)
|
||||||
node_links = get_links(node)
|
if isinstance(node, (WlanNode, EmaneNet)):
|
||||||
links.extend(node_links)
|
for link_data in node.links():
|
||||||
|
links.append(convert_link_data(link_data))
|
||||||
|
for core_link in session.link_manager.links():
|
||||||
|
links.extend(convert_core_link(core_link))
|
||||||
default_services = get_default_services(session)
|
default_services = get_default_services(session)
|
||||||
x, y, z = session.location.refxyz
|
x, y, z = session.location.refxyz
|
||||||
lat, lon, alt = session.location.refgeo
|
lat, lon, alt = session.location.refgeo
|
||||||
|
@ -640,7 +831,7 @@ def convert_session(session: Session) -> wrappers.Session:
|
||||||
)
|
)
|
||||||
hooks = get_hooks(session)
|
hooks = get_hooks(session)
|
||||||
session_file = str(session.file_path) if session.file_path else None
|
session_file = str(session.file_path) if session.file_path else None
|
||||||
options = get_config_options(session.options.get_configs(), session.options)
|
options = convert_session_options(session)
|
||||||
servers = [
|
servers = [
|
||||||
core_pb2.Server(name=x.name, host=x.host)
|
core_pb2.Server(name=x.name, host=x.host)
|
||||||
for x in session.distributed.servers.values()
|
for x in session.distributed.servers.values()
|
||||||
|
@ -665,6 +856,15 @@ def convert_session(session: Session) -> wrappers.Session:
|
||||||
def configure_node(
|
def configure_node(
|
||||||
session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext
|
session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Configure a node using all provided protobuf data.
|
||||||
|
|
||||||
|
:param session: session for node
|
||||||
|
:param node: node protobuf data
|
||||||
|
:param core_node: session node
|
||||||
|
:param context: grpc context
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
for emane_config in node.emane_configs:
|
for emane_config in node.emane_configs:
|
||||||
_id = utils.iface_config_id(node.id, emane_config.iface_id)
|
_id = utils.iface_config_id(node.id, emane_config.iface_id)
|
||||||
config = {k: v.value for k, v in emane_config.config.items()}
|
config = {k: v.value for k, v in emane_config.config.items()}
|
||||||
|
@ -675,6 +875,9 @@ def configure_node(
|
||||||
if node.mobility_config:
|
if node.mobility_config:
|
||||||
config = {k: v.value for k, v in node.mobility_config.items()}
|
config = {k: v.value for k, v in node.mobility_config.items()}
|
||||||
session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config)
|
session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config)
|
||||||
|
if isinstance(core_node, WirelessNode) and node.wireless_config:
|
||||||
|
config = {k: v.value for k, v in node.wireless_config.items()}
|
||||||
|
core_node.set_config(config)
|
||||||
for service_name, service_config in node.service_configs.items():
|
for service_name, service_config in node.service_configs.items():
|
||||||
data = service_config.data
|
data = service_config.data
|
||||||
config = ServiceConfig(
|
config = ServiceConfig(
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
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 typing import Iterable, Optional, Pattern, Type
|
from re import Pattern
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from grpc import ServicerContext
|
from grpc import ServicerContext
|
||||||
|
@ -23,10 +26,22 @@ from core.api.grpc.configservices_pb2 import (
|
||||||
ConfigService,
|
ConfigService,
|
||||||
GetConfigServiceDefaultsRequest,
|
GetConfigServiceDefaultsRequest,
|
||||||
GetConfigServiceDefaultsResponse,
|
GetConfigServiceDefaultsResponse,
|
||||||
|
GetConfigServiceRenderedRequest,
|
||||||
|
GetConfigServiceRenderedResponse,
|
||||||
GetNodeConfigServiceRequest,
|
GetNodeConfigServiceRequest,
|
||||||
GetNodeConfigServiceResponse,
|
GetNodeConfigServiceResponse,
|
||||||
)
|
)
|
||||||
from core.api.grpc.core_pb2 import ExecuteScriptResponse
|
from core.api.grpc.core_pb2 import (
|
||||||
|
ExecuteScriptResponse,
|
||||||
|
GetWirelessConfigRequest,
|
||||||
|
GetWirelessConfigResponse,
|
||||||
|
LinkedRequest,
|
||||||
|
LinkedResponse,
|
||||||
|
WirelessConfigRequest,
|
||||||
|
WirelessConfigResponse,
|
||||||
|
WirelessLinkedRequest,
|
||||||
|
WirelessLinkedResponse,
|
||||||
|
)
|
||||||
from core.api.grpc.emane_pb2 import (
|
from core.api.grpc.emane_pb2 import (
|
||||||
EmaneLinkRequest,
|
EmaneLinkRequest,
|
||||||
EmaneLinkResponse,
|
EmaneLinkResponse,
|
||||||
|
@ -79,19 +94,20 @@ from core.emulator.data import InterfaceData, LinkData, LinkOptions
|
||||||
from core.emulator.enumerations import (
|
from core.emulator.enumerations import (
|
||||||
EventTypes,
|
EventTypes,
|
||||||
ExceptionLevels,
|
ExceptionLevels,
|
||||||
LinkTypes,
|
|
||||||
MessageFlags,
|
MessageFlags,
|
||||||
|
NodeTypes,
|
||||||
)
|
)
|
||||||
from core.emulator.session import NT, Session
|
from core.emulator.session import NT, Session
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||||
from core.nodes.base import CoreNode, NodeBase
|
from core.nodes.base import CoreNode, NodeBase
|
||||||
from core.nodes.network import WlanNode
|
from core.nodes.network import CoreNetwork, WlanNode
|
||||||
|
from core.nodes.wireless import WirelessNode
|
||||||
from core.services.coreservices import ServiceManager
|
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 = re.compile(r"veth(?P<node>[0-9a-fA-F]+)")
|
_INTERFACE_REGEX: Pattern[str] = re.compile(r"beth(?P<node>[0-9a-fA-F]+)")
|
||||||
_MAX_WORKERS = 1000
|
_MAX_WORKERS = 1000
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,11 +123,20 @@ 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
|
||||||
atexit.register(self._exit_handler)
|
# catch signals
|
||||||
|
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 _exit_handler(self) -> None:
|
def _signal_handler(self, signal_number: int, _) -> None:
|
||||||
logger.debug("catching exit, stop running")
|
logger.info("caught signal: %s", signal_number)
|
||||||
|
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()
|
||||||
|
@ -148,7 +173,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
|
||||||
|
@ -187,7 +212,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.
|
||||||
|
|
||||||
|
@ -248,18 +273,19 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
# clear previous state and setup for creation
|
# clear previous state and setup for creation
|
||||||
session.clear()
|
session.clear()
|
||||||
|
session.directory.mkdir(exist_ok=True)
|
||||||
if request.definition:
|
if request.definition:
|
||||||
state = EventTypes.DEFINITION_STATE
|
state = EventTypes.DEFINITION_STATE
|
||||||
else:
|
else:
|
||||||
state = EventTypes.CONFIGURATION_STATE
|
state = EventTypes.CONFIGURATION_STATE
|
||||||
session.directory.mkdir(exist_ok=True)
|
|
||||||
session.set_state(state)
|
session.set_state(state)
|
||||||
session.user = request.session.user
|
if request.session.user:
|
||||||
|
session.set_user(request.session.user)
|
||||||
|
|
||||||
# session options
|
# session options
|
||||||
session.options.config_reset()
|
|
||||||
for option in request.session.options.values():
|
for option in request.session.options.values():
|
||||||
session.options.set_config(option.name, option.value)
|
if 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
|
||||||
|
@ -378,11 +404,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
self, request: core_pb2.GetSessionsRequest, context: ServicerContext
|
self, request: core_pb2.GetSessionsRequest, context: ServicerContext
|
||||||
) -> core_pb2.GetSessionsResponse:
|
) -> core_pb2.GetSessionsResponse:
|
||||||
"""
|
"""
|
||||||
Delete the session
|
Get all currently known session overviews.
|
||||||
|
|
||||||
:param request: get-session request
|
:param request: get sessions request
|
||||||
:param context: context object
|
:param context: context object
|
||||||
:return: a delete-session response
|
:return: a get sessions response
|
||||||
"""
|
"""
|
||||||
logger.debug("get sessions: %s", request)
|
logger.debug("get sessions: %s", request)
|
||||||
sessions = []
|
sessions = []
|
||||||
|
@ -469,7 +495,6 @@ 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
|
||||||
|
@ -486,7 +511,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("veth"):
|
if key.startswith("beth"):
|
||||||
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)
|
||||||
|
@ -512,7 +537,6 @@ 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
|
||||||
|
@ -540,9 +564,17 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
"""
|
"""
|
||||||
logger.debug("add node: %s", request)
|
logger.debug("add node: %s", request)
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
_type, _id, options = grpcutils.add_node_data(request.node)
|
_type = NodeTypes(request.node.type)
|
||||||
_class = session.get_node_class(_type)
|
_class = session.get_node_class(_type)
|
||||||
node = session.add_node(_class, _id, options)
|
position, options = grpcutils.add_node_data(_class, request.node)
|
||||||
|
node = session.add_node(
|
||||||
|
_class,
|
||||||
|
request.node.id or None,
|
||||||
|
request.node.name or None,
|
||||||
|
request.node.server or None,
|
||||||
|
position,
|
||||||
|
options,
|
||||||
|
)
|
||||||
grpcutils.configure_node(session, request.node, node, context)
|
grpcutils.configure_node(session, request.node, node, context)
|
||||||
source = request.source if request.source else None
|
source = request.source if request.source else None
|
||||||
session.broadcast_node(node, MessageFlags.ADD, source)
|
session.broadcast_node(node, MessageFlags.ADD, source)
|
||||||
|
@ -564,12 +596,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
ifaces = []
|
ifaces = []
|
||||||
for iface_id in node.ifaces:
|
for iface_id in node.ifaces:
|
||||||
iface = node.ifaces[iface_id]
|
iface = node.ifaces[iface_id]
|
||||||
iface_proto = grpcutils.iface_to_proto(request.node_id, iface)
|
iface_proto = grpcutils.iface_to_proto(session, iface)
|
||||||
ifaces.append(iface_proto)
|
ifaces.append(iface_proto)
|
||||||
emane_configs = grpcutils.get_emane_model_configs_dict(session)
|
emane_configs = grpcutils.get_emane_model_configs_dict(session)
|
||||||
node_emane_configs = emane_configs.get(node.id, [])
|
node_emane_configs = emane_configs.get(node.id, [])
|
||||||
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
|
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
|
||||||
links = get_links(node)
|
links = get_links(session, node)
|
||||||
return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links)
|
return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links)
|
||||||
|
|
||||||
def MoveNode(
|
def MoveNode(
|
||||||
|
@ -705,18 +737,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
node2_id = request.link.node2_id
|
node2_id = request.link.node2_id
|
||||||
self.get_node(session, node1_id, context, NodeBase)
|
self.get_node(session, node1_id, context, NodeBase)
|
||||||
self.get_node(session, node2_id, context, NodeBase)
|
self.get_node(session, node2_id, context, NodeBase)
|
||||||
iface1_data, iface2_data, options, link_type = grpcutils.add_link_data(
|
iface1_data, iface2_data, options = grpcutils.add_link_data(request.link)
|
||||||
request.link
|
|
||||||
)
|
|
||||||
node1_iface, node2_iface = session.add_link(
|
node1_iface, node2_iface = session.add_link(
|
||||||
node1_id, node2_id, iface1_data, iface2_data, options, link_type
|
node1_id, node2_id, iface1_data, iface2_data, options
|
||||||
)
|
)
|
||||||
iface1_data = None
|
iface1_data = None
|
||||||
if node1_iface:
|
if node1_iface:
|
||||||
iface1_data = grpcutils.iface_to_data(node1_iface)
|
if isinstance(node1_iface.node, CoreNetwork):
|
||||||
|
iface1_data = InterfaceData(id=node1_iface.id)
|
||||||
|
else:
|
||||||
|
iface1_data = node1_iface.get_data()
|
||||||
iface2_data = None
|
iface2_data = None
|
||||||
if node2_iface:
|
if node2_iface:
|
||||||
iface2_data = grpcutils.iface_to_data(node2_iface)
|
if isinstance(node2_iface.node, CoreNetwork):
|
||||||
|
iface2_data = InterfaceData(id=node2_iface.id)
|
||||||
|
else:
|
||||||
|
iface2_data = node2_iface.get_data()
|
||||||
source = request.source if request.source else None
|
source = request.source if request.source else None
|
||||||
link_data = LinkData(
|
link_data = LinkData(
|
||||||
message_type=MessageFlags.ADD,
|
message_type=MessageFlags.ADD,
|
||||||
|
@ -731,9 +767,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
iface1_proto = None
|
iface1_proto = None
|
||||||
iface2_proto = None
|
iface2_proto = None
|
||||||
if node1_iface:
|
if node1_iface:
|
||||||
iface1_proto = grpcutils.iface_to_proto(node1_id, node1_iface)
|
iface1_proto = grpcutils.iface_to_proto(session, node1_iface)
|
||||||
if node2_iface:
|
if node2_iface:
|
||||||
iface2_proto = grpcutils.iface_to_proto(node2_id, node2_iface)
|
iface2_proto = grpcutils.iface_to_proto(session, node2_iface)
|
||||||
return core_pb2.AddLinkResponse(
|
return core_pb2.AddLinkResponse(
|
||||||
result=True, iface1=iface1_proto, iface2=iface2_proto
|
result=True, iface1=iface1_proto, iface2=iface2_proto
|
||||||
)
|
)
|
||||||
|
@ -912,7 +948,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session.services.default_services.clear()
|
session.services.default_services.clear()
|
||||||
for service_defaults in request.defaults:
|
for service_defaults in request.defaults:
|
||||||
session.services.default_services[
|
session.services.default_services[
|
||||||
service_defaults.node_type
|
service_defaults.model
|
||||||
] = service_defaults.services
|
] = service_defaults.services
|
||||||
return SetServiceDefaultsResponse(result=True)
|
return SetServiceDefaultsResponse(result=True)
|
||||||
|
|
||||||
|
@ -1070,7 +1106,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.state == EventTypes.RUNTIME_STATE:
|
if session.is_running():
|
||||||
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)
|
||||||
|
@ -1143,7 +1179,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("utf-8"))
|
temp.write(request.data.encode())
|
||||||
temp.close()
|
temp.close()
|
||||||
temp_path = Path(temp.name)
|
temp_path = Path(temp.name)
|
||||||
file_path = Path(request.file)
|
file_path = Path(request.file)
|
||||||
|
@ -1163,7 +1199,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
self, request: core_pb2.GetInterfacesRequest, context: ServicerContext
|
self, request: core_pb2.GetInterfacesRequest, context: ServicerContext
|
||||||
) -> core_pb2.GetInterfacesResponse:
|
) -> core_pb2.GetInterfacesResponse:
|
||||||
"""
|
"""
|
||||||
Retrieve all the interfaces of the system including bridges, virtual ethernet, and loopback
|
Retrieve all the interfaces of the system including bridges, virtual ethernet,
|
||||||
|
and loopback.
|
||||||
|
|
||||||
:param request: get-interfaces request
|
:param request: get-interfaces request
|
||||||
:param context: context object
|
:param context: context object
|
||||||
|
@ -1188,32 +1225,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
"""
|
"""
|
||||||
logger.debug("emane link: %s", request)
|
logger.debug("emane link: %s", request)
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
nem1 = request.nem1
|
flag = MessageFlags.ADD if request.linked else MessageFlags.DELETE
|
||||||
iface1 = session.emane.get_iface(nem1)
|
link = session.emane.get_nem_link(request.nem1, request.nem2, flag)
|
||||||
if not iface1:
|
if link:
|
||||||
context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found")
|
|
||||||
node1 = iface1.node
|
|
||||||
|
|
||||||
nem2 = request.nem2
|
|
||||||
iface2 = session.emane.get_iface(nem2)
|
|
||||||
if not iface2:
|
|
||||||
context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found")
|
|
||||||
node2 = iface2.node
|
|
||||||
|
|
||||||
if iface1.net == iface2.net:
|
|
||||||
if request.linked:
|
|
||||||
flag = MessageFlags.ADD
|
|
||||||
else:
|
|
||||||
flag = MessageFlags.DELETE
|
|
||||||
color = session.get_link_color(iface1.net.id)
|
|
||||||
link = LinkData(
|
|
||||||
message_type=flag,
|
|
||||||
type=LinkTypes.WIRELESS,
|
|
||||||
node1_id=node1.id,
|
|
||||||
node2_id=node2.id,
|
|
||||||
network_id=iface1.net.id,
|
|
||||||
color=color,
|
|
||||||
)
|
|
||||||
session.broadcast_link(link)
|
session.broadcast_link(link)
|
||||||
return EmaneLinkResponse(result=True)
|
return EmaneLinkResponse(result=True)
|
||||||
else:
|
else:
|
||||||
|
@ -1240,6 +1254,27 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config = {x.id: x.default for x in service.default_configs}
|
config = {x.id: x.default for x in service.default_configs}
|
||||||
return GetNodeConfigServiceResponse(config=config)
|
return GetNodeConfigServiceResponse(config=config)
|
||||||
|
|
||||||
|
def GetConfigServiceRendered(
|
||||||
|
self, request: GetConfigServiceRenderedRequest, context: ServicerContext
|
||||||
|
) -> GetConfigServiceRenderedResponse:
|
||||||
|
"""
|
||||||
|
Retrieves the rendered file data for a given config service on a node.
|
||||||
|
|
||||||
|
:param request: config service render request
|
||||||
|
:param context: grpc context
|
||||||
|
:return: rendered config service files
|
||||||
|
"""
|
||||||
|
session = self.get_session(request.session_id, context)
|
||||||
|
node = self.get_node(session, request.node_id, context, CoreNode)
|
||||||
|
self.validate_service(request.name, context)
|
||||||
|
service = node.config_services.get(request.name)
|
||||||
|
if not service:
|
||||||
|
context.abort(
|
||||||
|
grpc.StatusCode.NOT_FOUND, f"unknown node service {request.name}"
|
||||||
|
)
|
||||||
|
rendered = service.get_rendered_templates()
|
||||||
|
return GetConfigServiceRenderedResponse(rendered=rendered)
|
||||||
|
|
||||||
def GetConfigServiceDefaults(
|
def GetConfigServiceDefaults(
|
||||||
self, request: GetConfigServiceDefaultsRequest, context: ServicerContext
|
self, request: GetConfigServiceDefaultsRequest, context: ServicerContext
|
||||||
) -> GetConfigServiceDefaultsResponse:
|
) -> GetConfigServiceDefaultsResponse:
|
||||||
|
@ -1250,8 +1285,10 @@ 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(None)
|
service = service_class(node)
|
||||||
templates = service.get_templates()
|
templates = service.get_templates()
|
||||||
config = {}
|
config = {}
|
||||||
for configuration in service.default_configs:
|
for configuration in service.default_configs:
|
||||||
|
@ -1299,18 +1336,21 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
) -> WlanLinkResponse:
|
) -> WlanLinkResponse:
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
wlan = self.get_node(session, request.wlan, context, WlanNode)
|
wlan = self.get_node(session, request.wlan, context, WlanNode)
|
||||||
if not isinstance(wlan.model, BasicRangeModel):
|
if not isinstance(wlan.wireless_model, BasicRangeModel):
|
||||||
context.abort(
|
context.abort(
|
||||||
grpc.StatusCode.NOT_FOUND,
|
grpc.StatusCode.NOT_FOUND,
|
||||||
f"wlan node {request.wlan} does not using BasicRangeModel",
|
f"wlan node {request.wlan} is not using BasicRangeModel",
|
||||||
)
|
)
|
||||||
node1 = self.get_node(session, request.node1_id, context, CoreNode)
|
node1 = self.get_node(session, request.node1_id, context, CoreNode)
|
||||||
node2 = self.get_node(session, request.node2_id, context, CoreNode)
|
node2 = self.get_node(session, request.node2_id, context, CoreNode)
|
||||||
node1_iface, node2_iface = None, None
|
node1_iface, node2_iface = None, None
|
||||||
for net, iface1, iface2 in node1.commonnets(node2):
|
for iface in node1.get_ifaces(control=False):
|
||||||
if net == wlan:
|
if iface.net == wlan:
|
||||||
node1_iface = iface1
|
node1_iface = iface
|
||||||
node2_iface = iface2
|
break
|
||||||
|
for iface in node2.get_ifaces(control=False):
|
||||||
|
if iface.net == wlan:
|
||||||
|
node2_iface = iface
|
||||||
break
|
break
|
||||||
result = False
|
result = False
|
||||||
if node1_iface and node2_iface:
|
if node1_iface and node2_iface:
|
||||||
|
@ -1318,7 +1358,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
wlan.link(node1_iface, node2_iface)
|
wlan.link(node1_iface, node2_iface)
|
||||||
else:
|
else:
|
||||||
wlan.unlink(node1_iface, node2_iface)
|
wlan.unlink(node1_iface, node2_iface)
|
||||||
wlan.model.sendlinkmsg(node1_iface, node2_iface, unlink=not request.linked)
|
wlan.wireless_model.sendlinkmsg(
|
||||||
|
node1_iface, node2_iface, unlink=not request.linked
|
||||||
|
)
|
||||||
result = True
|
result = True
|
||||||
return WlanLinkResponse(result=result)
|
return WlanLinkResponse(result=result)
|
||||||
|
|
||||||
|
@ -1335,3 +1377,60 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context)
|
nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context)
|
||||||
session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2)
|
session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2)
|
||||||
return EmanePathlossesResponse()
|
return EmanePathlossesResponse()
|
||||||
|
|
||||||
|
def Linked(
|
||||||
|
self, request: LinkedRequest, context: ServicerContext
|
||||||
|
) -> LinkedResponse:
|
||||||
|
session = self.get_session(request.session_id, context)
|
||||||
|
session.linked(
|
||||||
|
request.node1_id,
|
||||||
|
request.node2_id,
|
||||||
|
request.iface1_id,
|
||||||
|
request.iface2_id,
|
||||||
|
request.linked,
|
||||||
|
)
|
||||||
|
return LinkedResponse()
|
||||||
|
|
||||||
|
def WirelessLinked(
|
||||||
|
self, request: WirelessLinkedRequest, context: ServicerContext
|
||||||
|
) -> WirelessLinkedResponse:
|
||||||
|
session = self.get_session(request.session_id, context)
|
||||||
|
wireless = self.get_node(session, request.wireless_id, context, WirelessNode)
|
||||||
|
wireless.link_control(request.node1_id, request.node2_id, request.linked)
|
||||||
|
return WirelessLinkedResponse()
|
||||||
|
|
||||||
|
def WirelessConfig(
|
||||||
|
self, request: WirelessConfigRequest, context: ServicerContext
|
||||||
|
) -> WirelessConfigResponse:
|
||||||
|
session = self.get_session(request.session_id, context)
|
||||||
|
wireless = self.get_node(session, request.wireless_id, context, WirelessNode)
|
||||||
|
options1 = request.options1
|
||||||
|
options2 = options1
|
||||||
|
if request.HasField("options2"):
|
||||||
|
options2 = request.options2
|
||||||
|
options1 = grpcutils.convert_options_proto(options1)
|
||||||
|
options2 = grpcutils.convert_options_proto(options2)
|
||||||
|
wireless.link_config(request.node1_id, request.node2_id, options1, options2)
|
||||||
|
return WirelessConfigResponse()
|
||||||
|
|
||||||
|
def GetWirelessConfig(
|
||||||
|
self, request: GetWirelessConfigRequest, context: ServicerContext
|
||||||
|
) -> GetWirelessConfigResponse:
|
||||||
|
session = self.get_session(request.session_id, context)
|
||||||
|
try:
|
||||||
|
wireless = session.get_node(request.node_id, WirelessNode)
|
||||||
|
configs = wireless.get_config()
|
||||||
|
except CoreError:
|
||||||
|
configs = {x.id: x for x in WirelessNode.options}
|
||||||
|
config_options = {}
|
||||||
|
for config in configs.values():
|
||||||
|
config_option = common_pb2.ConfigOption(
|
||||||
|
label=config.label,
|
||||||
|
name=config.id,
|
||||||
|
value=config.default,
|
||||||
|
type=config.type.value,
|
||||||
|
select=config.options,
|
||||||
|
group=config.group,
|
||||||
|
)
|
||||||
|
config_options[config.id] = config_option
|
||||||
|
return GetWirelessConfigResponse(config=config_options)
|
||||||
|
|
|
@ -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, Dict, List, Optional, Set, Tuple
|
from typing import Any, Optional
|
||||||
|
|
||||||
from core.api.grpc import (
|
from core.api.grpc import (
|
||||||
common_pb2,
|
common_pb2,
|
||||||
|
@ -67,6 +67,8 @@ class NodeType(Enum):
|
||||||
CONTROL_NET = 13
|
CONTROL_NET = 13
|
||||||
DOCKER = 15
|
DOCKER = 15
|
||||||
LXC = 16
|
LXC = 16
|
||||||
|
WIRELESS = 17
|
||||||
|
PODMAN = 18
|
||||||
|
|
||||||
|
|
||||||
class LinkType(Enum):
|
class LinkType(Enum):
|
||||||
|
@ -113,13 +115,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
|
||||||
|
@ -146,8 +148,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(
|
||||||
|
@ -163,15 +165,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(
|
||||||
|
@ -209,25 +211,25 @@ class Service:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ServiceDefault:
|
class ServiceDefault:
|
||||||
node_type: 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":
|
||||||
return ServiceDefault(node_type=proto.node_type, services=list(proto.services))
|
return ServiceDefault(model=proto.model, services=list(proto.services))
|
||||||
|
|
||||||
|
|
||||||
@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
|
||||||
|
@ -265,7 +267,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":
|
||||||
|
@ -281,11 +283,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(
|
||||||
|
@ -338,8 +340,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":
|
||||||
|
@ -427,19 +429,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
|
||||||
|
@ -480,6 +482,8 @@ class Interface:
|
||||||
mtu: int = None
|
mtu: int = None
|
||||||
node_id: int = None
|
node_id: int = None
|
||||||
net2_id: int = None
|
net2_id: int = None
|
||||||
|
nem_id: int = None
|
||||||
|
nem_port: int = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_proto(cls, proto: core_pb2.Interface) -> "Interface":
|
def from_proto(cls, proto: core_pb2.Interface) -> "Interface":
|
||||||
|
@ -496,6 +500,8 @@ class Interface:
|
||||||
mtu=proto.mtu,
|
mtu=proto.mtu,
|
||||||
node_id=proto.node_id,
|
node_id=proto.node_id,
|
||||||
net2_id=proto.net2_id,
|
net2_id=proto.net2_id,
|
||||||
|
nem_id=proto.nem_id,
|
||||||
|
nem_port=proto.nem_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_proto(self) -> core_pb2.Interface:
|
def to_proto(self) -> core_pb2.Interface:
|
||||||
|
@ -666,7 +672,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":
|
||||||
|
@ -720,8 +726,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
|
||||||
|
@ -732,18 +738,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)
|
||||||
mobility_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
wireless_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||||
service_configs: Dict[str, NodeServiceData] = field(
|
mobility_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -770,7 +777,7 @@ class Node:
|
||||||
id=proto.id,
|
id=proto.id,
|
||||||
name=proto.name,
|
name=proto.name,
|
||||||
type=NodeType(proto.type),
|
type=NodeType(proto.type),
|
||||||
model=proto.model,
|
model=proto.model or None,
|
||||||
position=Position.from_proto(proto.position),
|
position=Position.from_proto(proto.position),
|
||||||
services=set(proto.services),
|
services=set(proto.services),
|
||||||
config_services=set(proto.config_services),
|
config_services=set(proto.config_services),
|
||||||
|
@ -788,6 +795,7 @@ class Node:
|
||||||
service_file_configs=service_file_configs,
|
service_file_configs=service_file_configs,
|
||||||
config_service_configs=config_service_configs,
|
config_service_configs=config_service_configs,
|
||||||
emane_model_configs=emane_configs,
|
emane_model_configs=emane_configs,
|
||||||
|
wireless_config=ConfigOption.from_dict(proto.wireless_config),
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_proto(self) -> core_pb2.Node:
|
def to_proto(self) -> core_pb2.Node:
|
||||||
|
@ -839,20 +847,21 @@ class Node:
|
||||||
service_configs=service_configs,
|
service_configs=service_configs,
|
||||||
config_service_configs=config_service_configs,
|
config_service_configs=config_service_configs,
|
||||||
emane_configs=emane_configs,
|
emane_configs=emane_configs,
|
||||||
|
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, {})
|
||||||
|
@ -865,27 +874,25 @@ 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 = {
|
default_services = {x.model: set(x.services) for x in proto.default_services}
|
||||||
x.node_type: 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}
|
||||||
file_path = Path(proto.file) if proto.file else None
|
file_path = Path(proto.file) if proto.file else None
|
||||||
options = ConfigOption.from_dict(proto.options)
|
options = ConfigOption.from_dict(proto.options)
|
||||||
|
@ -913,9 +920,9 @@ class Session:
|
||||||
options = {k: v.to_proto() for k, v in self.options.items()}
|
options = {k: v.to_proto() for k, v in self.options.items()}
|
||||||
servers = [x.to_proto() for x in self.servers]
|
servers = [x.to_proto() for x in self.servers]
|
||||||
default_services = []
|
default_services = []
|
||||||
for node_type, services in self.default_services.items():
|
for model, services in self.default_services.items():
|
||||||
default_service = services_pb2.ServiceDefaults(
|
default_service = services_pb2.ServiceDefaults(
|
||||||
node_type=node_type, services=services
|
model=model, services=services
|
||||||
)
|
)
|
||||||
default_services.append(default_service)
|
default_services.append(default_service)
|
||||||
file = str(self.file) if self.file else None
|
file = str(self.file) if self.file else None
|
||||||
|
@ -981,7 +988,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
|
||||||
|
@ -989,9 +996,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":
|
||||||
|
@ -1082,7 +1089,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
|
||||||
|
@ -1102,7 +1109,6 @@ class ConfigEvent:
|
||||||
data_types=list(proto.data_types),
|
data_types=list(proto.data_types),
|
||||||
data_values=proto.data_values,
|
data_values=proto.data_values,
|
||||||
captions=proto.captions,
|
captions=proto.captions,
|
||||||
bitmap=proto.bitmap,
|
|
||||||
possible_values=proto.possible_values,
|
possible_values=proto.possible_values,
|
||||||
groups=proto.groups,
|
groups=proto.groups,
|
||||||
iface_id=proto.iface_id,
|
iface_id=proto.iface_id,
|
||||||
|
@ -1194,13 +1200,13 @@ class EmanePathlossesRequest:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(frozen=True)
|
||||||
class MoveNodesRequest:
|
class MoveNodesRequest:
|
||||||
session_id: int
|
session_id: int
|
||||||
node_id: int
|
node_id: int
|
||||||
source: str = None
|
source: str = field(compare=False, default=None)
|
||||||
position: Position = None
|
position: Position = field(compare=False, default=None)
|
||||||
geo: Geo = None
|
geo: Geo = field(compare=False, default=None)
|
||||||
|
|
||||||
def to_proto(self) -> core_pb2.MoveNodesRequest:
|
def to_proto(self) -> core_pb2.MoveNodesRequest:
|
||||||
position = self.position.to_proto() if self.position else None
|
position = self.position.to_proto() if self.position else None
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,60 +0,0 @@
|
||||||
"""
|
|
||||||
Defines core server for handling TCP connections.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import socketserver
|
|
||||||
|
|
||||||
from core.emulator.coreemu import CoreEmu
|
|
||||||
|
|
||||||
|
|
||||||
class CoreServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
||||||
"""
|
|
||||||
TCP server class, manages sessions and spawns request handlers for
|
|
||||||
incoming connections.
|
|
||||||
"""
|
|
||||||
|
|
||||||
daemon_threads = True
|
|
||||||
allow_reuse_address = True
|
|
||||||
|
|
||||||
def __init__(self, server_address, handler_class, config=None):
|
|
||||||
"""
|
|
||||||
Server class initialization takes configuration data and calls
|
|
||||||
the socketserver constructor.
|
|
||||||
|
|
||||||
:param tuple[str, int] server_address: server host and port to use
|
|
||||||
:param class handler_class: request handler
|
|
||||||
:param dict config: configuration setting
|
|
||||||
"""
|
|
||||||
self.coreemu = CoreEmu(config)
|
|
||||||
self.config = config
|
|
||||||
socketserver.TCPServer.__init__(self, server_address, handler_class)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreUdpServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
|
|
||||||
"""
|
|
||||||
UDP server class, manages sessions and spawns request handlers for
|
|
||||||
incoming connections.
|
|
||||||
"""
|
|
||||||
|
|
||||||
daemon_threads = True
|
|
||||||
allow_reuse_address = True
|
|
||||||
|
|
||||||
def __init__(self, server_address, handler_class, mainserver):
|
|
||||||
"""
|
|
||||||
Server class initialization takes configuration data and calls
|
|
||||||
the SocketServer constructor
|
|
||||||
|
|
||||||
:param server_address:
|
|
||||||
:param class handler_class: request handler
|
|
||||||
:param mainserver:
|
|
||||||
"""
|
|
||||||
self.mainserver = mainserver
|
|
||||||
socketserver.UDPServer.__init__(self, server_address, handler_class)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""
|
|
||||||
Thread target to run concurrently with the TCP server.
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
self.serve_forever()
|
|
|
@ -1,178 +0,0 @@
|
||||||
"""
|
|
||||||
Converts CORE data objects into legacy API messages.
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
from collections import OrderedDict
|
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from core.api.tlv import coreapi, structutils
|
|
||||||
from core.api.tlv.enumerations import ConfigTlvs, NodeTlvs
|
|
||||||
from core.config import ConfigGroup, ConfigurableOptions
|
|
||||||
from core.emulator.data import ConfigData, NodeData
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_node(node_data: NodeData):
|
|
||||||
"""
|
|
||||||
Convenience method for converting NodeData to a packed TLV message.
|
|
||||||
|
|
||||||
:param core.emulator.data.NodeData node_data: node data to convert
|
|
||||||
:return: packed node message
|
|
||||||
"""
|
|
||||||
node = node_data.node
|
|
||||||
services = None
|
|
||||||
if node.services is not None:
|
|
||||||
services = "|".join([x.name for x in node.services])
|
|
||||||
server = None
|
|
||||||
if node.server is not None:
|
|
||||||
server = node.server.name
|
|
||||||
tlv_data = structutils.pack_values(
|
|
||||||
coreapi.CoreNodeTlv,
|
|
||||||
[
|
|
||||||
(NodeTlvs.NUMBER, node.id),
|
|
||||||
(NodeTlvs.TYPE, node.apitype.value),
|
|
||||||
(NodeTlvs.NAME, node.name),
|
|
||||||
(NodeTlvs.MODEL, node.type),
|
|
||||||
(NodeTlvs.EMULATION_SERVER, server),
|
|
||||||
(NodeTlvs.X_POSITION, int(node.position.x)),
|
|
||||||
(NodeTlvs.Y_POSITION, int(node.position.y)),
|
|
||||||
(NodeTlvs.CANVAS, node.canvas),
|
|
||||||
(NodeTlvs.SERVICES, services),
|
|
||||||
(NodeTlvs.LATITUDE, str(node.position.lat)),
|
|
||||||
(NodeTlvs.LONGITUDE, str(node.position.lon)),
|
|
||||||
(NodeTlvs.ALTITUDE, str(node.position.alt)),
|
|
||||||
(NodeTlvs.ICON, node.icon),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return coreapi.CoreNodeMessage.pack(node_data.message_type.value, tlv_data)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_config(config_data):
|
|
||||||
"""
|
|
||||||
Convenience method for converting ConfigData to a packed TLV message.
|
|
||||||
|
|
||||||
:param core.emulator.data.ConfigData config_data: config data to convert
|
|
||||||
:return: packed message
|
|
||||||
"""
|
|
||||||
session = None
|
|
||||||
if config_data.session is not None:
|
|
||||||
session = str(config_data.session)
|
|
||||||
tlv_data = structutils.pack_values(
|
|
||||||
coreapi.CoreConfigTlv,
|
|
||||||
[
|
|
||||||
(ConfigTlvs.NODE, config_data.node),
|
|
||||||
(ConfigTlvs.OBJECT, config_data.object),
|
|
||||||
(ConfigTlvs.TYPE, config_data.type),
|
|
||||||
(ConfigTlvs.DATA_TYPES, config_data.data_types),
|
|
||||||
(ConfigTlvs.VALUES, config_data.data_values),
|
|
||||||
(ConfigTlvs.CAPTIONS, config_data.captions),
|
|
||||||
(ConfigTlvs.BITMAP, config_data.bitmap),
|
|
||||||
(ConfigTlvs.POSSIBLE_VALUES, config_data.possible_values),
|
|
||||||
(ConfigTlvs.GROUPS, config_data.groups),
|
|
||||||
(ConfigTlvs.SESSION, session),
|
|
||||||
(ConfigTlvs.IFACE_ID, config_data.iface_id),
|
|
||||||
(ConfigTlvs.NETWORK_ID, config_data.network_id),
|
|
||||||
(ConfigTlvs.OPAQUE, config_data.opaque),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return coreapi.CoreConfMessage.pack(config_data.message_type, tlv_data)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigShim:
|
|
||||||
"""
|
|
||||||
Provides helper methods for converting newer configuration values into TLV
|
|
||||||
compatible formats.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def str_to_dict(cls, key_values: str) -> Dict[str, str]:
|
|
||||||
"""
|
|
||||||
Converts a TLV key/value string into an ordered mapping.
|
|
||||||
|
|
||||||
:param key_values:
|
|
||||||
:return: ordered mapping of key/value pairs
|
|
||||||
"""
|
|
||||||
key_values = key_values.split("|")
|
|
||||||
values = OrderedDict()
|
|
||||||
for key_value in key_values:
|
|
||||||
key, value = key_value.split("=", 1)
|
|
||||||
values[key] = value
|
|
||||||
return values
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def groups_to_str(cls, config_groups: List[ConfigGroup]) -> str:
|
|
||||||
"""
|
|
||||||
Converts configuration groups to a TLV formatted string.
|
|
||||||
|
|
||||||
:param config_groups: configuration groups to format
|
|
||||||
:return: TLV configuration group string
|
|
||||||
"""
|
|
||||||
group_strings = []
|
|
||||||
for config_group in config_groups:
|
|
||||||
group_string = (
|
|
||||||
f"{config_group.name}:{config_group.start}-{config_group.stop}"
|
|
||||||
)
|
|
||||||
group_strings.append(group_string)
|
|
||||||
return "|".join(group_strings)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def config_data(
|
|
||||||
cls,
|
|
||||||
flags: int,
|
|
||||||
node_id: int,
|
|
||||||
type_flags: int,
|
|
||||||
configurable_options: ConfigurableOptions,
|
|
||||||
config: Dict[str, str],
|
|
||||||
) -> ConfigData:
|
|
||||||
"""
|
|
||||||
Convert this class to a Config API message. Some TLVs are defined
|
|
||||||
by the class, but node number, conf type flags, and values must
|
|
||||||
be passed in.
|
|
||||||
|
|
||||||
:param flags: message flags
|
|
||||||
:param node_id: node id
|
|
||||||
:param type_flags: type flags
|
|
||||||
:param configurable_options: options to create config data for
|
|
||||||
:param config: configuration values for options
|
|
||||||
:return: configuration data object
|
|
||||||
"""
|
|
||||||
key_values = None
|
|
||||||
captions = None
|
|
||||||
data_types = []
|
|
||||||
possible_values = []
|
|
||||||
logger.debug("configurable: %s", configurable_options)
|
|
||||||
logger.debug("configuration options: %s", configurable_options.configurations)
|
|
||||||
logger.debug("configuration data: %s", config)
|
|
||||||
for configuration in configurable_options.configurations():
|
|
||||||
if not captions:
|
|
||||||
captions = configuration.label
|
|
||||||
else:
|
|
||||||
captions += f"|{configuration.label}"
|
|
||||||
|
|
||||||
data_types.append(configuration.type.value)
|
|
||||||
|
|
||||||
options = ",".join(configuration.options)
|
|
||||||
possible_values.append(options)
|
|
||||||
|
|
||||||
_id = configuration.id
|
|
||||||
config_value = config.get(_id, configuration.default)
|
|
||||||
key_value = f"{_id}={config_value}"
|
|
||||||
if not key_values:
|
|
||||||
key_values = key_value
|
|
||||||
else:
|
|
||||||
key_values += f"|{key_value}"
|
|
||||||
|
|
||||||
groups_str = cls.groups_to_str(configurable_options.config_groups())
|
|
||||||
return ConfigData(
|
|
||||||
message_type=flags,
|
|
||||||
node=node_id,
|
|
||||||
object=configurable_options.name,
|
|
||||||
type=type_flags,
|
|
||||||
data_types=tuple(data_types),
|
|
||||||
data_values=key_values,
|
|
||||||
captions=captions,
|
|
||||||
possible_values="|".join(possible_values),
|
|
||||||
bitmap=configurable_options.bitmap,
|
|
||||||
groups=groups_str,
|
|
||||||
)
|
|
|
@ -1,212 +0,0 @@
|
||||||
"""
|
|
||||||
Enumerations specific to the CORE TLV API.
|
|
||||||
"""
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
CORE_API_PORT = 4038
|
|
||||||
|
|
||||||
|
|
||||||
class MessageTypes(Enum):
|
|
||||||
"""
|
|
||||||
CORE message types.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
LINK = 0x02
|
|
||||||
EXECUTE = 0x03
|
|
||||||
REGISTER = 0x04
|
|
||||||
CONFIG = 0x05
|
|
||||||
FILE = 0x06
|
|
||||||
INTERFACE = 0x07
|
|
||||||
EVENT = 0x08
|
|
||||||
SESSION = 0x09
|
|
||||||
EXCEPTION = 0x0A
|
|
||||||
|
|
||||||
|
|
||||||
class NodeTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Node type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NUMBER = 0x01
|
|
||||||
TYPE = 0x02
|
|
||||||
NAME = 0x03
|
|
||||||
IP_ADDRESS = 0x04
|
|
||||||
MAC_ADDRESS = 0x05
|
|
||||||
IP6_ADDRESS = 0x06
|
|
||||||
MODEL = 0x07
|
|
||||||
EMULATION_SERVER = 0x08
|
|
||||||
SESSION = 0x0A
|
|
||||||
X_POSITION = 0x20
|
|
||||||
Y_POSITION = 0x21
|
|
||||||
CANVAS = 0x22
|
|
||||||
EMULATION_ID = 0x23
|
|
||||||
NETWORK_ID = 0x24
|
|
||||||
SERVICES = 0x25
|
|
||||||
LATITUDE = 0x30
|
|
||||||
LONGITUDE = 0x31
|
|
||||||
ALTITUDE = 0x32
|
|
||||||
ICON = 0x42
|
|
||||||
OPAQUE = 0x50
|
|
||||||
|
|
||||||
|
|
||||||
class LinkTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Link type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
N1_NUMBER = 0x01
|
|
||||||
N2_NUMBER = 0x02
|
|
||||||
DELAY = 0x03
|
|
||||||
BANDWIDTH = 0x04
|
|
||||||
LOSS = 0x05
|
|
||||||
DUP = 0x06
|
|
||||||
JITTER = 0x07
|
|
||||||
MER = 0x08
|
|
||||||
BURST = 0x09
|
|
||||||
SESSION = 0x0A
|
|
||||||
MBURST = 0x10
|
|
||||||
TYPE = 0x20
|
|
||||||
GUI_ATTRIBUTES = 0x21
|
|
||||||
UNIDIRECTIONAL = 0x22
|
|
||||||
EMULATION_ID = 0x23
|
|
||||||
NETWORK_ID = 0x24
|
|
||||||
KEY = 0x25
|
|
||||||
IFACE1_NUMBER = 0x30
|
|
||||||
IFACE1_IP4 = 0x31
|
|
||||||
IFACE1_IP4_MASK = 0x32
|
|
||||||
IFACE1_MAC = 0x33
|
|
||||||
IFACE1_IP6 = 0x34
|
|
||||||
IFACE1_IP6_MASK = 0x35
|
|
||||||
IFACE2_NUMBER = 0x36
|
|
||||||
IFACE2_IP4 = 0x37
|
|
||||||
IFACE2_IP4_MASK = 0x38
|
|
||||||
IFACE2_MAC = 0x39
|
|
||||||
IFACE2_IP6 = 0x40
|
|
||||||
IFACE2_IP6_MASK = 0x41
|
|
||||||
IFACE1_NAME = 0x42
|
|
||||||
IFACE2_NAME = 0x43
|
|
||||||
OPAQUE = 0x50
|
|
||||||
|
|
||||||
|
|
||||||
class ExecuteTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Execute type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
NUMBER = 0x02
|
|
||||||
TIME = 0x03
|
|
||||||
COMMAND = 0x04
|
|
||||||
RESULT = 0x05
|
|
||||||
STATUS = 0x06
|
|
||||||
SESSION = 0x0A
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Configuration type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
OBJECT = 0x02
|
|
||||||
TYPE = 0x03
|
|
||||||
DATA_TYPES = 0x04
|
|
||||||
VALUES = 0x05
|
|
||||||
CAPTIONS = 0x06
|
|
||||||
BITMAP = 0x07
|
|
||||||
POSSIBLE_VALUES = 0x08
|
|
||||||
GROUPS = 0x09
|
|
||||||
SESSION = 0x0A
|
|
||||||
IFACE_ID = 0x0B
|
|
||||||
NETWORK_ID = 0x24
|
|
||||||
OPAQUE = 0x50
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFlags(Enum):
|
|
||||||
"""
|
|
||||||
Configuration flags.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NONE = 0x00
|
|
||||||
REQUEST = 0x01
|
|
||||||
UPDATE = 0x02
|
|
||||||
RESET = 0x03
|
|
||||||
|
|
||||||
|
|
||||||
class FileTlvs(Enum):
|
|
||||||
"""
|
|
||||||
File type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
NAME = 0x02
|
|
||||||
MODE = 0x03
|
|
||||||
NUMBER = 0x04
|
|
||||||
TYPE = 0x05
|
|
||||||
SOURCE_NAME = 0x06
|
|
||||||
SESSION = 0x0A
|
|
||||||
DATA = 0x10
|
|
||||||
COMPRESSED_DATA = 0x11
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Interface type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
NUMBER = 0x02
|
|
||||||
NAME = 0x03
|
|
||||||
IP_ADDRESS = 0x04
|
|
||||||
MASK = 0x05
|
|
||||||
MAC_ADDRESS = 0x06
|
|
||||||
IP6_ADDRESS = 0x07
|
|
||||||
IP6_MASK = 0x08
|
|
||||||
TYPE = 0x09
|
|
||||||
SESSION = 0x0A
|
|
||||||
STATE = 0x0B
|
|
||||||
EMULATION_ID = 0x23
|
|
||||||
NETWORK_ID = 0x24
|
|
||||||
|
|
||||||
|
|
||||||
class EventTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Event type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
TYPE = 0x02
|
|
||||||
NAME = 0x03
|
|
||||||
DATA = 0x04
|
|
||||||
TIME = 0x05
|
|
||||||
SESSION = 0x0A
|
|
||||||
|
|
||||||
|
|
||||||
class SessionTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Session type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NUMBER = 0x01
|
|
||||||
NAME = 0x02
|
|
||||||
FILE = 0x03
|
|
||||||
NODE_COUNT = 0x04
|
|
||||||
DATE = 0x05
|
|
||||||
THUMB = 0x06
|
|
||||||
USER = 0x07
|
|
||||||
OPAQUE = 0x0A
|
|
||||||
|
|
||||||
|
|
||||||
class ExceptionTlvs(Enum):
|
|
||||||
"""
|
|
||||||
Exception type, length, value enumerations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NODE = 0x01
|
|
||||||
SESSION = 0x02
|
|
||||||
LEVEL = 0x03
|
|
||||||
SOURCE = 0x04
|
|
||||||
DATE = 0x05
|
|
||||||
TEXT = 0x06
|
|
||||||
OPAQUE = 0x0A
|
|
|
@ -1,45 +0,0 @@
|
||||||
"""
|
|
||||||
Utilities for working with python struct data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def pack_values(clazz, packers):
|
|
||||||
"""
|
|
||||||
Pack values for a given legacy class.
|
|
||||||
|
|
||||||
:param class clazz: class that will provide a pack method
|
|
||||||
:param list packers: a list of tuples that are used to pack values and transform them
|
|
||||||
:return: packed data string of all values
|
|
||||||
"""
|
|
||||||
|
|
||||||
# iterate through tuples of values to pack
|
|
||||||
logger.debug("packing: %s", packers)
|
|
||||||
data = b""
|
|
||||||
for packer in packers:
|
|
||||||
# check if a transformer was provided for valid values
|
|
||||||
transformer = None
|
|
||||||
if len(packer) == 2:
|
|
||||||
tlv_type, value = packer
|
|
||||||
elif len(packer) == 3:
|
|
||||||
tlv_type, value, transformer = packer
|
|
||||||
else:
|
|
||||||
raise RuntimeError("packer had more than 3 arguments")
|
|
||||||
|
|
||||||
# only pack actual values and avoid packing empty strings
|
|
||||||
# protobuf defaults to empty strings and does no imply a value to set
|
|
||||||
if value is None or (isinstance(value, str) and not value):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# transform values as needed
|
|
||||||
if transformer:
|
|
||||||
value = transformer(value)
|
|
||||||
|
|
||||||
# pack and add to existing data
|
|
||||||
logger.debug("packing: %s - %s type(%s)", tlv_type, value, type(value))
|
|
||||||
data += clazz.pack(tlv_type.value, value)
|
|
||||||
|
|
||||||
return data
|
|
|
@ -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, Dict, List, Optional, Set, Tuple, Type, Union
|
from typing import TYPE_CHECKING, Any, Optional, 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,8 @@ 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"
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
self.label = self.label if self.label else self.id
|
self.label = self.label if self.label else self.id
|
||||||
|
@ -78,6 +79,7 @@ class ConfigBool(Configuration):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: ConfigDataTypes = ConfigDataTypes.BOOL
|
type: ConfigDataTypes = ConfigDataTypes.BOOL
|
||||||
|
value: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -87,6 +89,7 @@ class ConfigFloat(Configuration):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: ConfigDataTypes = ConfigDataTypes.FLOAT
|
type: ConfigDataTypes = ConfigDataTypes.FLOAT
|
||||||
|
value: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -96,6 +99,7 @@ class ConfigInt(Configuration):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: ConfigDataTypes = ConfigDataTypes.INT32
|
type: ConfigDataTypes = ConfigDataTypes.INT32
|
||||||
|
value: int = 0
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -105,6 +109,7 @@ class ConfigString(Configuration):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: ConfigDataTypes = ConfigDataTypes.STRING
|
type: ConfigDataTypes = ConfigDataTypes.STRING
|
||||||
|
value: str = ""
|
||||||
|
|
||||||
|
|
||||||
class ConfigurableOptions:
|
class ConfigurableOptions:
|
||||||
|
@ -113,11 +118,10 @@ class ConfigurableOptions:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
bitmap: 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.
|
||||||
|
|
||||||
|
@ -126,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.
|
||||||
|
|
||||||
|
@ -135,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.
|
||||||
|
|
||||||
|
@ -161,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.
|
||||||
|
|
||||||
|
@ -204,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:
|
||||||
|
@ -246,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.
|
||||||
|
|
||||||
|
@ -260,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.
|
||||||
|
|
||||||
|
@ -280,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.
|
||||||
|
@ -313,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.
|
||||||
|
|
||||||
|
@ -338,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.
|
||||||
|
@ -357,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, Dict, List, Optional
|
from typing import Any, 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,6 +331,33 @@ class ConfigService(abc.ABC):
|
||||||
templates[file] = template
|
templates[file] = template
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
|
def get_rendered_templates(self) -> dict[str, str]:
|
||||||
|
templates = {}
|
||||||
|
data = self.data()
|
||||||
|
for file in sorted(self.files):
|
||||||
|
rendered = self._get_rendered_template(file, data)
|
||||||
|
templates[file] = rendered
|
||||||
|
return templates
|
||||||
|
|
||||||
|
def _get_rendered_template(self, file: str, data: dict[str, Any]) -> str:
|
||||||
|
file_path = Path(file)
|
||||||
|
template_path = get_template_path(file_path)
|
||||||
|
if file in self.custom_templates:
|
||||||
|
text = self.custom_templates[file]
|
||||||
|
rendered = self.render_text(text, data)
|
||||||
|
elif self.templates.has_template(template_path):
|
||||||
|
rendered = self.render_template(template_path, data)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
text = self.get_text_template(file)
|
||||||
|
except Exception as e:
|
||||||
|
raise ConfigServiceTemplateError(
|
||||||
|
f"node({self.node.name}) service({self.name}) file({file}) "
|
||||||
|
f"failure getting template: {e}"
|
||||||
|
)
|
||||||
|
rendered = self.render_text(text, data)
|
||||||
|
return rendered
|
||||||
|
|
||||||
def create_files(self) -> None:
|
def create_files(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates service files inside associated node.
|
Creates service files inside associated node.
|
||||||
|
@ -342,22 +369,8 @@ class ConfigService(abc.ABC):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"node(%s) service(%s) template(%s)", self.node.name, self.name, file
|
"node(%s) service(%s) template(%s)", self.node.name, self.name, file
|
||||||
)
|
)
|
||||||
|
rendered = self._get_rendered_template(file, data)
|
||||||
file_path = Path(file)
|
file_path = Path(file)
|
||||||
template_path = get_template_path(file_path)
|
|
||||||
if file in self.custom_templates:
|
|
||||||
text = self.custom_templates[file]
|
|
||||||
rendered = self.render_text(text, data)
|
|
||||||
elif self.templates.has_template(template_path):
|
|
||||||
rendered = self.render_template(template_path, data)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
text = self.get_text_template(file)
|
|
||||||
except Exception as e:
|
|
||||||
raise ConfigServiceTemplateError(
|
|
||||||
f"node({self.node.name}) service({self.name}) file({file}) "
|
|
||||||
f"failure getting template: {e}"
|
|
||||||
)
|
|
||||||
rendered = self.render_text(text, data)
|
|
||||||
self.node.create_file(file_path, rendered)
|
self.node.create_file(file_path, rendered)
|
||||||
|
|
||||||
def run_startup(self, wait: bool) -> None:
|
def run_startup(self, wait: bool) -> None:
|
||||||
|
@ -413,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.
|
||||||
|
|
||||||
|
@ -427,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.
|
||||||
|
|
||||||
|
@ -445,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.
|
||||||
|
|
||||||
|
@ -459,10 +472,10 @@ class ConfigService(abc.ABC):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise CoreError(
|
raise CoreError(
|
||||||
f"node({self.node.name}) service({self.name}) file({template_path})"
|
f"node({self.node.name}) service({self.name}) file({template_path})"
|
||||||
f"{exceptions.text_error_template().render_template()}"
|
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.
|
||||||
|
|
||||||
|
@ -472,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.
|
||||||
|
|
||||||
|
@ -483,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, Dict, List, Set
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
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(
|
||||||
"failure to start all services: %s != %s"
|
f"failure to start all services: {self.started} != "
|
||||||
% (self.started, self.node_services.keys())
|
f"{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 services: %s"
|
"required dependency was not included in node "
|
||||||
% service_name
|
f"services: {service_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if service_name in self.visiting:
|
if service_name in self.visiting:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"cyclic dependency at service(%s): %s"
|
f"cyclic dependency at service({current_service.name}): "
|
||||||
% (current_service.name, service_name)
|
f"{service_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if service_name not in self.visited:
|
if service_name not in self.visited:
|
||||||
|
|
|
@ -2,7 +2,6 @@ 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
|
||||||
|
@ -20,9 +19,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.
|
||||||
|
|
||||||
|
@ -35,7 +34,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.
|
||||||
|
|
||||||
|
@ -62,7 +61,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.
|
||||||
|
|
||||||
|
@ -81,7 +80,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,17 +1,29 @@
|
||||||
import abc
|
import abc
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
from core.nodes.base import CoreNodeBase
|
from core.nodes.base import CoreNodeBase, NodeBase
|
||||||
from core.nodes.interface import DEFAULT_MTU, CoreInterface
|
from core.nodes.interface import DEFAULT_MTU, CoreInterface
|
||||||
from core.nodes.network import WlanNode
|
from core.nodes.network import PtpNet, WlanNode
|
||||||
|
from core.nodes.physical import Rj45Node
|
||||||
|
from core.nodes.wireless import WirelessNode
|
||||||
|
|
||||||
GROUP: str = "FRR"
|
GROUP: str = "FRR"
|
||||||
FRR_STATE_DIR: str = "/var/run/frr"
|
FRR_STATE_DIR: str = "/var/run/frr"
|
||||||
|
|
||||||
|
|
||||||
|
def is_wireless(node: NodeBase) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the node is a wireless type node.
|
||||||
|
|
||||||
|
:param node: node to check type for
|
||||||
|
:return: True if wireless type, False otherwise
|
||||||
|
"""
|
||||||
|
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
|
||||||
|
|
||||||
|
|
||||||
def has_mtu_mismatch(iface: CoreInterface) -> bool:
|
def has_mtu_mismatch(iface: CoreInterface) -> bool:
|
||||||
"""
|
"""
|
||||||
Helper to detect MTU mismatch and add the appropriate FRR
|
Helper to detect MTU mismatch and add the appropriate FRR
|
||||||
|
@ -53,32 +65,47 @@ def get_router_id(node: CoreNodeBase) -> str:
|
||||||
return "0.0.0.0"
|
return "0.0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def rj45_check(iface: CoreInterface) -> bool:
|
||||||
|
"""
|
||||||
|
Helper to detect whether interface is connected an external RJ45
|
||||||
|
link.
|
||||||
|
"""
|
||||||
|
if iface.net:
|
||||||
|
for peer_iface in iface.net.get_ifaces():
|
||||||
|
if peer_iface == iface:
|
||||||
|
continue
|
||||||
|
if isinstance(peer_iface.node, Rj45Node):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
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_config(
|
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_config(
|
frr_sbin_search = self.node.session.options.get(
|
||||||
"frr_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/frr"
|
"frr_sbin_search",
|
||||||
|
default="/usr/local/sbin /usr/sbin /usr/lib/frr /usr/libexec/frr",
|
||||||
).strip('"')
|
).strip('"')
|
||||||
|
|
||||||
services = []
|
services = []
|
||||||
|
@ -119,16 +146,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
|
||||||
|
|
||||||
|
@ -149,8 +176,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:
|
||||||
|
@ -158,7 +185,7 @@ class FRROspfv2(FrrService, ConfigService):
|
||||||
addresses = []
|
addresses = []
|
||||||
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:
|
||||||
addresses.append(str(ip4.ip))
|
addresses.append(str(ip4))
|
||||||
data = dict(router_id=router_id, addresses=addresses)
|
data = dict(router_id=router_id, addresses=addresses)
|
||||||
text = """
|
text = """
|
||||||
router ospf
|
router ospf
|
||||||
|
@ -166,15 +193,31 @@ class FRROspfv2(FrrService, ConfigService):
|
||||||
% for addr in addresses:
|
% for addr in addresses:
|
||||||
network ${addr} area 0
|
network ${addr} area 0
|
||||||
% endfor
|
% endfor
|
||||||
|
ospf opaque-lsa
|
||||||
!
|
!
|
||||||
"""
|
"""
|
||||||
return self.render_text(text, data)
|
return self.render_text(text, data)
|
||||||
|
|
||||||
def frr_iface_config(self, iface: CoreInterface) -> str:
|
def frr_iface_config(self, iface: CoreInterface) -> str:
|
||||||
if has_mtu_mismatch(iface):
|
has_mtu = has_mtu_mismatch(iface)
|
||||||
return "ip ospf mtu-ignore"
|
has_rj45 = rj45_check(iface)
|
||||||
else:
|
is_ptp = isinstance(iface.net, PtpNet)
|
||||||
return ""
|
data = dict(has_mtu=has_mtu, is_ptp=is_ptp, has_rj45=has_rj45)
|
||||||
|
text = """
|
||||||
|
% if has_mtu:
|
||||||
|
ip ospf mtu-ignore
|
||||||
|
% endif
|
||||||
|
% if has_rj45:
|
||||||
|
<% return STOP_RENDERING %>
|
||||||
|
% endif
|
||||||
|
% if is_ptp:
|
||||||
|
ip ospf network point-to-point
|
||||||
|
% endif
|
||||||
|
ip ospf hello-interval 2
|
||||||
|
ip ospf dead-interval 6
|
||||||
|
ip ospf retransmit-interval 5
|
||||||
|
"""
|
||||||
|
return self.render_text(text, data)
|
||||||
|
|
||||||
|
|
||||||
class FRROspfv3(FrrService, ConfigService):
|
class FRROspfv3(FrrService, ConfigService):
|
||||||
|
@ -185,8 +228,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
|
||||||
|
|
||||||
|
@ -222,8 +265,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
|
||||||
|
@ -252,8 +295,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:
|
||||||
|
@ -277,8 +320,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:
|
||||||
|
@ -303,8 +346,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:
|
||||||
|
@ -324,7 +367,7 @@ class FRRBabel(FrrService, ConfigService):
|
||||||
return self.render_text(text, data)
|
return self.render_text(text, data)
|
||||||
|
|
||||||
def frr_iface_config(self, iface: CoreInterface) -> str:
|
def frr_iface_config(self, iface: CoreInterface) -> str:
|
||||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
if is_wireless(iface.net):
|
||||||
text = """
|
text = """
|
||||||
babel wireless
|
babel wireless
|
||||||
no babel split-horizon
|
no babel split-horizon
|
||||||
|
@ -343,8 +386,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:
|
||||||
|
|
|
@ -48,6 +48,10 @@ bootdaemon()
|
||||||
flags="$flags -6"
|
flags="$flags -6"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "ospfd" ]; then
|
||||||
|
flags="$flags --apiserver"
|
||||||
|
fi
|
||||||
|
|
||||||
#force FRR to use CORE generated conf file
|
#force FRR to use CORE generated conf file
|
||||||
flags="$flags -d -f $FRR_CONF"
|
flags="$flags -d -f $FRR_CONF"
|
||||||
$FRR_SBIN_DIR/$1 $flags
|
$FRR_SBIN_DIR/$1 $flags
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
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,19 +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_arouted = "arouted" in self.node.config_services
|
|
||||||
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 = []
|
||||||
|
@ -78,29 +77,25 @@ class NrlSmf(ConfigService):
|
||||||
ip4_prefix = f"{ip4.ip}/{24}"
|
ip4_prefix = f"{ip4.ip}/{24}"
|
||||||
break
|
break
|
||||||
return dict(
|
return dict(
|
||||||
has_arouted=has_arouted,
|
has_nhdp=has_nhdp, has_olsr=has_olsr, ifnames=ifnames, ip4_prefix=ip4_prefix
|
||||||
has_nhdp=has_nhdp,
|
|
||||||
has_olsr=has_olsr,
|
|
||||||
ifnames=ifnames,
|
|
||||||
ip4_prefix=ip4_prefix,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -113,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):
|
||||||
|
@ -135,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):
|
||||||
|
@ -157,37 +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]] = {}
|
||||||
|
|
||||||
|
|
||||||
class Arouted(ConfigService):
|
|
||||||
name: str = "arouted"
|
|
||||||
group: str = GROUP
|
|
||||||
directories: List[str] = []
|
|
||||||
files: List[str] = ["startarouted.sh"]
|
|
||||||
executables: List[str] = ["arouted"]
|
|
||||||
dependencies: List[str] = []
|
|
||||||
startup: List[str] = ["bash startarouted.sh"]
|
|
||||||
validate: List[str] = ["pidof arouted"]
|
|
||||||
shutdown: List[str] = ["pkill arouted"]
|
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
|
||||||
default_configs: List[Configuration] = []
|
|
||||||
modes: Dict[str, Dict[str, str]] = {}
|
|
||||||
|
|
||||||
def data(self) -> Dict[str, Any]:
|
|
||||||
ip4_prefix = None
|
|
||||||
for iface in self.node.get_ifaces(control=False):
|
|
||||||
ip4 = iface.get_ip4()
|
|
||||||
if ip4:
|
|
||||||
ip4_prefix = f"{ip4.ip}/{24}"
|
|
||||||
break
|
|
||||||
return dict(ip4_prefix=ip4_prefix)
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
for f in "/tmp/${node.name}_smf"; do
|
|
||||||
count=1
|
|
||||||
until [ -e "$f" ]; do
|
|
||||||
if [ $count -eq 10 ]; then
|
|
||||||
echo "ERROR: nrlmsf pipe not found: $f" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sleep 0.1
|
|
||||||
count=$(($count + 1))
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
ip route add ${ip4_prefix} dev lo
|
|
||||||
arouted instance ${node.name}_smf tap ${node.name}_tap stability 10 2>&1 > /var/log/arouted.log &
|
|
|
@ -1,8 +1,5 @@
|
||||||
<%
|
<%
|
||||||
ifaces = ",".join(ifnames)
|
ifaces = ",".join(ifnames)
|
||||||
arouted = ""
|
|
||||||
if has_arouted:
|
|
||||||
arouted = "tap %s_tap unicast %s push lo,%s resequence on" % (node.name, ip4_prefix, ifnames[0])
|
|
||||||
if has_nhdp:
|
if has_nhdp:
|
||||||
flood = "ecds"
|
flood = "ecds"
|
||||||
elif has_olsr:
|
elif has_olsr:
|
||||||
|
@ -12,4 +9,4 @@
|
||||||
%>
|
%>
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# auto-generated by NrlSmf service
|
# auto-generated by NrlSmf service
|
||||||
nrlsmf instance ${node.name}_smf ${ifaces} ${arouted} ${flood} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 &
|
nrlsmf instance ${node.name}_smf ${flood} ${ifaces} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 &
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet
|
||||||
from core.nodes.base import CoreNodeBase
|
from core.nodes.base import CoreNodeBase, NodeBase
|
||||||
from core.nodes.interface import DEFAULT_MTU, CoreInterface
|
from core.nodes.interface import DEFAULT_MTU, CoreInterface
|
||||||
from core.nodes.network import PtpNet, WlanNode
|
from core.nodes.network import PtpNet, WlanNode
|
||||||
from core.nodes.physical import Rj45Node
|
from core.nodes.physical import Rj45Node
|
||||||
|
from core.nodes.wireless import WirelessNode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
GROUP: str = "Quagga"
|
GROUP: str = "Quagga"
|
||||||
QUAGGA_STATE_DIR: str = "/var/run/quagga"
|
QUAGGA_STATE_DIR: str = "/var/run/quagga"
|
||||||
|
|
||||||
|
|
||||||
|
def is_wireless(node: NodeBase) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the node is a wireless type node.
|
||||||
|
|
||||||
|
:param node: node to check type for
|
||||||
|
:return: True if wireless type, False otherwise
|
||||||
|
"""
|
||||||
|
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
|
||||||
|
|
||||||
|
|
||||||
def has_mtu_mismatch(iface: CoreInterface) -> bool:
|
def has_mtu_mismatch(iface: CoreInterface) -> bool:
|
||||||
"""
|
"""
|
||||||
Helper to detect MTU mismatch and add the appropriate OSPF
|
Helper to detect MTU mismatch and add the appropriate OSPF
|
||||||
|
@ -73,26 +84,26 @@ 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_config(
|
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('"')
|
||||||
quagga_sbin_search = self.node.session.options.get_config(
|
quagga_sbin_search = self.node.session.options.get(
|
||||||
"quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga"
|
"quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga"
|
||||||
).strip('"')
|
).strip('"')
|
||||||
quagga_state_dir = QUAGGA_STATE_DIR
|
quagga_state_dir = QUAGGA_STATE_DIR
|
||||||
|
@ -142,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
|
||||||
|
|
||||||
|
@ -172,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:
|
||||||
|
@ -223,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
|
||||||
|
|
||||||
|
@ -265,7 +276,7 @@ class Ospfv3mdr(Ospfv3):
|
||||||
|
|
||||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||||
config = super().quagga_iface_config(iface)
|
config = super().quagga_iface_config(iface)
|
||||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
if is_wireless(iface.net):
|
||||||
config = self.clean_text(
|
config = self.clean_text(
|
||||||
f"""
|
f"""
|
||||||
{config}
|
{config}
|
||||||
|
@ -289,15 +300,12 @@ 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
|
||||||
|
|
||||||
def quagga_config(self) -> str:
|
def quagga_config(self) -> str:
|
||||||
return ""
|
|
||||||
|
|
||||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
|
||||||
router_id = get_router_id(self.node)
|
router_id = get_router_id(self.node)
|
||||||
text = f"""
|
text = f"""
|
||||||
! BGP configuration
|
! BGP configuration
|
||||||
|
@ -311,6 +319,9 @@ class Bgp(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
return self.clean_text(text)
|
return self.clean_text(text)
|
||||||
|
|
||||||
|
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class Rip(QuaggaService, ConfigService):
|
class Rip(QuaggaService, ConfigService):
|
||||||
"""
|
"""
|
||||||
|
@ -318,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:
|
||||||
|
@ -343,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:
|
||||||
|
@ -369,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:
|
||||||
|
@ -390,7 +401,7 @@ class Babel(QuaggaService, ConfigService):
|
||||||
return self.render_text(text, data)
|
return self.render_text(text, data)
|
||||||
|
|
||||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
if is_wireless(iface.net):
|
||||||
text = """
|
text = """
|
||||||
babel wireless
|
babel wireless
|
||||||
no babel split-horizon
|
no babel split-horizon
|
||||||
|
@ -409,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, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
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, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
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()
|
return dict(ifnames=ifnames)
|
||||||
|
|
||||||
|
|
||||||
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 values:
|
% for ifname, prefixes in ifaces:
|
||||||
interface ${ifname}
|
interface ${ifname}
|
||||||
{
|
{
|
||||||
AdvSendAdvert on;
|
AdvSendAdvert on;
|
||||||
|
|
|
@ -13,4 +13,5 @@ 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 &
|
tcpdump -s 12288 -C 10 -n -w ${node.name}.${ifname}.pcap -i ${ifname} > /dev/null 2>&1 &
|
||||||
% endfor
|
% endfor
|
||||||
elif [ "x$1" = "xstop" ]; then
|
elif [ "x$1" = "xstop" ]; then
|
||||||
mkdir -p $SESSION_DIR/pcap
|
mkdir -p $SESSION_DIR/pcap
|
||||||
|
|
|
@ -6,18 +6,18 @@ import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.emane.emanemodel import EmaneModel
|
from core.emane.emanemodel import EmaneModel
|
||||||
from core.emane.linkmonitor import EmaneLinkMonitor
|
from core.emane.linkmonitor import EmaneLinkMonitor
|
||||||
from core.emane.modelmanager import EmaneModelManager
|
from core.emane.modelmanager import EmaneModelManager
|
||||||
from core.emane.nodes import EmaneNet
|
from core.emane.nodes import EmaneNet, TunTap
|
||||||
from core.emulator.data import LinkData
|
from core.emulator.data import LinkData
|
||||||
from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
|
from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase
|
from core.nodes.base import CoreNode, NodeBase
|
||||||
from core.nodes.interface import CoreInterface, TunTap
|
from core.nodes.interface import CoreInterface
|
||||||
from core.xml import emanexml
|
from core.xml import emanexml
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -45,8 +45,6 @@ except ImportError:
|
||||||
EventServiceException = None
|
EventServiceException = None
|
||||||
logger.debug("compatible emane python bindings not installed")
|
logger.debug("compatible emane python bindings not installed")
|
||||||
|
|
||||||
DEFAULT_EMANE_PREFIX = "/usr"
|
|
||||||
DEFAULT_DEV = "ctrl0"
|
|
||||||
DEFAULT_LOG_LEVEL: int = 3
|
DEFAULT_LOG_LEVEL: int = 3
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,32 +126,32 @@ 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_config_int(
|
self.platformport: int = self.session.options.get_int(
|
||||||
"emane_platform_port", 8100
|
"emane_platform_port", 8100
|
||||||
)
|
)
|
||||||
self.transformport: int = self.session.options.get_config_int(
|
self.transformport: int = self.session.options.get_int(
|
||||||
"emane_transform_port", 8200
|
"emane_transform_port", 8200
|
||||||
)
|
)
|
||||||
self.doeventloop: bool = False
|
self.doeventloop: bool = False
|
||||||
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_config_int("nem_id_start")
|
nem_id = self.session.options.get_int("nem_id_start")
|
||||||
while nem_id in self.nems_to_ifaces:
|
while nem_id in self.nems_to_ifaces:
|
||||||
nem_id += 1
|
nem_id += 1
|
||||||
self.nems_to_ifaces[nem_id] = iface
|
self.nems_to_ifaces[nem_id] = iface
|
||||||
|
@ -163,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.
|
||||||
|
|
||||||
|
@ -183,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.
|
||||||
|
@ -201,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.
|
||||||
|
|
||||||
|
@ -213,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
|
||||||
|
@ -223,12 +221,10 @@ class EmaneManager:
|
||||||
:param iface: interface running emane
|
:param iface: interface running emane
|
||||||
:return: net, node, or interface model configuration
|
:return: net, node, or interface model configuration
|
||||||
"""
|
"""
|
||||||
model_name = emane_net.model.name
|
model_name = emane_net.wireless_model.name
|
||||||
config = None
|
|
||||||
# try to retrieve interface specific configuration
|
# try to retrieve interface specific configuration
|
||||||
if iface.node_id is not None:
|
key = utils.iface_config_id(iface.node.id, iface.id)
|
||||||
key = utils.iface_config_id(iface.node.id, iface.node_id)
|
config = self.get_config(key, model_name, default=False)
|
||||||
config = self.get_config(key, model_name, default=False)
|
|
||||||
# attempt to retrieve node specific config, when iface config is not present
|
# attempt to retrieve node specific config, when iface config is not present
|
||||||
if not config:
|
if not config:
|
||||||
config = self.get_config(iface.node.id, model_name, default=False)
|
config = self.get_config(iface.node.id, model_name, default=False)
|
||||||
|
@ -239,7 +235,7 @@ class EmaneManager:
|
||||||
config = self.get_config(emane_net.id, model_name, default=False)
|
config = self.get_config(emane_net.id, model_name, default=False)
|
||||||
# return default config values, when a config is not present
|
# return default config values, when a config is not present
|
||||||
if not config:
|
if not config:
|
||||||
config = emane_net.model.default_values()
|
config = emane_net.wireless_model.default_values()
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def config_reset(self, node_id: int = None) -> None:
|
def config_reset(self, node_id: int = None) -> None:
|
||||||
|
@ -264,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.
|
||||||
|
@ -272,7 +268,8 @@ class EmaneManager:
|
||||||
nodes = set()
|
nodes = set()
|
||||||
for emane_net in self._emane_nets.values():
|
for emane_net in self._emane_nets.values():
|
||||||
for iface in emane_net.get_ifaces():
|
for iface in emane_net.get_ifaces():
|
||||||
nodes.add(iface.node)
|
if isinstance(iface.node, CoreNode):
|
||||||
|
nodes.add(iface.node)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def setup(self) -> EmaneState:
|
def setup(self) -> EmaneState:
|
||||||
|
@ -323,7 +320,7 @@ class EmaneManager:
|
||||||
for emane_net, iface in self.get_ifaces():
|
for emane_net, iface in self.get_ifaces():
|
||||||
self.start_iface(emane_net, iface)
|
self.start_iface(emane_net, iface)
|
||||||
|
|
||||||
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
|
def start_iface(self, emane_net: EmaneNet, iface: TunTap) -> None:
|
||||||
nem_id = self.next_nem_id(iface)
|
nem_id = self.next_nem_id(iface)
|
||||||
nem_port = self.get_nem_port(iface)
|
nem_port = self.get_nem_port(iface)
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -338,10 +335,10 @@ 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, CoreInterface]]:
|
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.model:
|
if not emane_net.wireless_model:
|
||||||
logger.error("emane net(%s) has no model", emane_net.name)
|
logger.error("emane net(%s) has no model", emane_net.name)
|
||||||
continue
|
continue
|
||||||
for iface in emane_net.get_ifaces():
|
for iface in emane_net.get_ifaces():
|
||||||
|
@ -352,11 +349,12 @@ class EmaneManager:
|
||||||
iface.name,
|
iface.name,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
ifaces.append((emane_net, iface))
|
if isinstance(iface, TunTap):
|
||||||
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id))
|
ifaces.append((emane_net, iface))
|
||||||
|
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
|
||||||
|
@ -384,6 +382,8 @@ class EmaneManager:
|
||||||
service = EmaneEventService(
|
service = EmaneEventService(
|
||||||
self, event_net.brname, eventgroup, int(eventport)
|
self, event_net.brname, eventgroup, int(eventport)
|
||||||
)
|
)
|
||||||
|
if self.doeventmonitor():
|
||||||
|
service.start()
|
||||||
self.services[event_net.brname] = service
|
self.services[event_net.brname] = service
|
||||||
self.nem_service[nem_id] = service
|
self.nem_service[nem_id] = service
|
||||||
except EventServiceException:
|
except EventServiceException:
|
||||||
|
@ -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,11 +480,11 @@ 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 IOError:
|
except OSError:
|
||||||
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:
|
||||||
return self.session.options.get_config_int("link_enabled") == 1
|
return self.session.options.get_int("link_enabled") == 1
|
||||||
|
|
||||||
def poststartup(self) -> None:
|
def poststartup(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -498,7 +498,7 @@ class EmaneManager:
|
||||||
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
|
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
|
||||||
)
|
)
|
||||||
for iface in emane_net.get_ifaces():
|
for iface in emane_net.get_ifaces():
|
||||||
emane_net.model.post_startup(iface)
|
emane_net.wireless_model.post_startup(iface)
|
||||||
if events_enabled:
|
if events_enabled:
|
||||||
iface.setposition()
|
iface.setposition()
|
||||||
|
|
||||||
|
@ -550,9 +550,11 @@ class EmaneManager:
|
||||||
emane_net = self._emane_nets[node_id]
|
emane_net = self._emane_nets[node_id]
|
||||||
logger.debug("checking emane model for node: %s", node_id)
|
logger.debug("checking emane model for node: %s", node_id)
|
||||||
# skip nodes that already have a model set
|
# skip nodes that already have a model set
|
||||||
if emane_net.model:
|
if emane_net.wireless_model:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"node(%s) already has model(%s)", emane_net.id, emane_net.model.name
|
"node(%s) already has model(%s)",
|
||||||
|
emane_net.id,
|
||||||
|
emane_net.wireless_model.name,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
# set model configured for node, due to legacy messaging configuration
|
# set model configured for node, due to legacy messaging configuration
|
||||||
|
@ -602,8 +604,8 @@ class EmaneManager:
|
||||||
"""
|
"""
|
||||||
node = iface.node
|
node = iface.node
|
||||||
loglevel = str(DEFAULT_LOG_LEVEL)
|
loglevel = str(DEFAULT_LOG_LEVEL)
|
||||||
cfgloglevel = self.session.options.get_config_int("emane_log_level")
|
cfgloglevel = self.session.options.get_int("emane_log_level", 2)
|
||||||
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
|
realtime = self.session.options.get_bool("emane_realtime", True)
|
||||||
if cfgloglevel:
|
if cfgloglevel:
|
||||||
logger.info("setting user-defined emane log level: %d", cfgloglevel)
|
logger.info("setting user-defined emane log level: %d", cfgloglevel)
|
||||||
loglevel = str(cfgloglevel)
|
loglevel = str(cfgloglevel)
|
||||||
|
@ -622,9 +624,9 @@ 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: CoreInterface, 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 isinstance(iface, TunTap) and external == "0":
|
if external == "0":
|
||||||
iface.set_ips()
|
iface.set_ips()
|
||||||
# at this point we register location handlers for generating
|
# at this point we register location handlers for generating
|
||||||
# EMANE location events
|
# EMANE location events
|
||||||
|
@ -636,20 +638,13 @@ class EmaneManager:
|
||||||
"""
|
"""
|
||||||
Returns boolean whether or not EMANE events will be monitored.
|
Returns boolean whether or not EMANE events will be monitored.
|
||||||
"""
|
"""
|
||||||
# this support must be explicitly turned on; by default, CORE will
|
return self.session.options.get_bool("emane_event_monitor", False)
|
||||||
# generate the EMANE events when nodes are moved
|
|
||||||
return self.session.options.get_config_bool("emane_event_monitor")
|
|
||||||
|
|
||||||
def genlocationevents(self) -> bool:
|
def genlocationevents(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns boolean whether or not EMANE events will be generated.
|
Returns boolean whether or not EMANE events will be generated.
|
||||||
"""
|
"""
|
||||||
# By default, CORE generates EMANE location events when nodes
|
return self.session.options.get_bool("emane_event_generate", True)
|
||||||
# are moved; this can be explicitly disabled in core.conf
|
|
||||||
tmp = self.session.options.get_config_bool("emane_event_generate")
|
|
||||||
if tmp is None:
|
|
||||||
tmp = not self.doeventmonitor()
|
|
||||||
return tmp
|
|
||||||
|
|
||||||
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -732,9 +727,6 @@ class EmaneManager:
|
||||||
self.session.broadcast_node(node)
|
self.session.broadcast_node(node)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
|
|
||||||
return isinstance(net, EmaneNet)
|
|
||||||
|
|
||||||
def emanerunning(self, node: CoreNode) -> bool:
|
def emanerunning(self, node: CoreNode) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if an EMANE process associated with the given node is running,
|
Return True if an EMANE process associated with the given node is running,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
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
|
||||||
|
@ -33,7 +32,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.
|
||||||
|
|
||||||
|
@ -51,7 +50,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.
|
||||||
|
|
||||||
|
@ -74,7 +73,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 Dict, List, Optional, Set
|
from typing import Optional
|
||||||
|
|
||||||
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, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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
|
||||||
|
@ -190,9 +190,9 @@ class EmaneLinkMonitor:
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
options = self.emane_manager.session.options
|
options = self.emane_manager.session.options
|
||||||
self.loss_threshold = options.get_config_int("loss_threshold")
|
self.loss_threshold = options.get_int("loss_threshold")
|
||||||
self.link_interval = options.get_config_int("link_interval")
|
self.link_interval = options.get_int("link_interval")
|
||||||
self.link_timeout = options.get_config_int("link_timeout")
|
self.link_timeout = options.get_int("link_timeout")
|
||||||
self.initialize()
|
self.initialize()
|
||||||
if not self.clients:
|
if not self.clients:
|
||||||
logger.info("no valid emane models to monitor links")
|
logger.info("no valid emane models to monitor links")
|
||||||
|
@ -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,7 +1,6 @@
|
||||||
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
|
||||||
|
@ -12,10 +11,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.
|
||||||
|
|
||||||
|
@ -38,7 +37,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.
|
||||||
|
|
||||||
|
@ -63,7 +62,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,7 +2,6 @@
|
||||||
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
|
||||||
|
@ -12,11 +11,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",
|
||||||
|
@ -26,7 +25,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,7 +4,6 @@ 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
|
||||||
|
|
||||||
|
@ -42,12 +41,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:
|
||||||
|
@ -56,11 +55,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),
|
||||||
|
@ -71,7 +70,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,7 +4,6 @@ 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
|
||||||
|
@ -28,7 +27,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:
|
||||||
|
|
|
@ -4,19 +4,15 @@ share the same MAC+PHY model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Type
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Callable, Optional, 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 (
|
from core.emulator.enumerations import MessageFlags, RegisterTlvs
|
||||||
EventTypes,
|
from core.errors import CoreCommandError, CoreError
|
||||||
LinkTypes,
|
from core.nodes.base import CoreNetworkBase, CoreNode, NodeOptions
|
||||||
MessageFlags,
|
|
||||||
NodeTypes,
|
|
||||||
RegisterTlvs,
|
|
||||||
)
|
|
||||||
from core.errors import CoreError
|
|
||||||
from core.nodes.base import CoreNetworkBase, CoreNode
|
|
||||||
from core.nodes.interface import CoreInterface
|
from core.nodes.interface import CoreInterface
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -24,10 +20,7 @@ logger = logging.getLogger(__name__)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.emane.emanemodel import EmaneModel
|
from core.emane.emanemodel import EmaneModel
|
||||||
from core.emulator.session import Session
|
from core.emulator.session import Session
|
||||||
from core.location.mobility import WirelessModel, WayPointMobility
|
from core.location.mobility import WayPointMobility
|
||||||
|
|
||||||
OptionalEmaneModel = Optional[EmaneModel]
|
|
||||||
WirelessModelType = Type[WirelessModel]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from emane.events import LocationEvent
|
from emane.events import LocationEvent
|
||||||
|
@ -39,6 +32,120 @@ except ImportError:
|
||||||
logger.debug("compatible emane python bindings not installed")
|
logger.debug("compatible emane python bindings not installed")
|
||||||
|
|
||||||
|
|
||||||
|
class TunTap(CoreInterface):
|
||||||
|
"""
|
||||||
|
TUN/TAP virtual device in TAP mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
_id: int,
|
||||||
|
name: str,
|
||||||
|
localname: str,
|
||||||
|
use_ovs: bool,
|
||||||
|
node: CoreNode = None,
|
||||||
|
server: "DistributedServer" = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(_id, name, localname, use_ovs, node=node, server=server)
|
||||||
|
self.node: CoreNode = node
|
||||||
|
|
||||||
|
def startup(self) -> None:
|
||||||
|
"""
|
||||||
|
Startup logic for a tunnel tap.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
self.up = True
|
||||||
|
|
||||||
|
def shutdown(self) -> None:
|
||||||
|
"""
|
||||||
|
Shutdown functionality for a tunnel tap.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
if not self.up:
|
||||||
|
return
|
||||||
|
self.up = False
|
||||||
|
|
||||||
|
def waitfor(
|
||||||
|
self, func: Callable[[], int], attempts: int = 10, maxretrydelay: float = 0.25
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Wait for func() to return zero with exponential backoff.
|
||||||
|
|
||||||
|
:param func: function to wait for a result of zero
|
||||||
|
:param attempts: number of attempts to wait for a zero result
|
||||||
|
:param maxretrydelay: maximum retry delay
|
||||||
|
:return: True if wait succeeded, False otherwise
|
||||||
|
"""
|
||||||
|
delay = 0.01
|
||||||
|
result = False
|
||||||
|
for i in range(1, attempts + 1):
|
||||||
|
r = func()
|
||||||
|
if r == 0:
|
||||||
|
result = True
|
||||||
|
break
|
||||||
|
msg = f"attempt {i} failed with nonzero exit status {r}"
|
||||||
|
if i < attempts + 1:
|
||||||
|
msg += ", retrying..."
|
||||||
|
logger.info(msg)
|
||||||
|
time.sleep(delay)
|
||||||
|
delay += delay
|
||||||
|
if delay > maxretrydelay:
|
||||||
|
delay = maxretrydelay
|
||||||
|
else:
|
||||||
|
msg += ", giving up"
|
||||||
|
logger.info(msg)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def nodedevexists(self) -> int:
|
||||||
|
"""
|
||||||
|
Checks if device exists.
|
||||||
|
|
||||||
|
:return: 0 if device exists, 1 otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.node.node_net_client.device_show(self.name)
|
||||||
|
return 0
|
||||||
|
except CoreCommandError:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def waitfordevicenode(self) -> None:
|
||||||
|
"""
|
||||||
|
Check for presence of a node device - tap device may not appear right away waits.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
logger.debug("waiting for device node: %s", self.name)
|
||||||
|
count = 0
|
||||||
|
while True:
|
||||||
|
result = self.waitfor(self.nodedevexists)
|
||||||
|
if result:
|
||||||
|
break
|
||||||
|
should_retry = count < 5
|
||||||
|
is_emane_running = self.node.session.emane.emanerunning(self.node)
|
||||||
|
if all([should_retry, is_emane_running]):
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
raise RuntimeError("node device failed to exist")
|
||||||
|
|
||||||
|
def set_ips(self) -> None:
|
||||||
|
"""
|
||||||
|
Set interface ip addresses.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
self.waitfordevicenode()
|
||||||
|
for ip in self.ips():
|
||||||
|
self.node.node_net_client.create_address(self.name, str(ip))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EmaneOptions(NodeOptions):
|
||||||
|
emane_model: str = None
|
||||||
|
"""name of emane model to associate an emane network to"""
|
||||||
|
|
||||||
|
|
||||||
class EmaneNet(CoreNetworkBase):
|
class EmaneNet(CoreNetworkBase):
|
||||||
"""
|
"""
|
||||||
EMANE node contains NEM configuration and causes connected nodes
|
EMANE node contains NEM configuration and causes connected nodes
|
||||||
|
@ -46,22 +153,26 @@ class EmaneNet(CoreNetworkBase):
|
||||||
Emane controller object that exists in a session.
|
Emane controller object that exists in a session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
apitype: NodeTypes = NodeTypes.EMANE
|
|
||||||
linktype: LinkTypes = LinkTypes.WIRED
|
|
||||||
type: str = "wlan"
|
|
||||||
has_custom_iface: bool = True
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session: "Session",
|
session: "Session",
|
||||||
_id: int = None,
|
_id: int = None,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
server: DistributedServer = None,
|
server: DistributedServer = None,
|
||||||
|
options: EmaneOptions = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(session, _id, name, server)
|
options = options or EmaneOptions()
|
||||||
|
super().__init__(session, _id, name, server, options)
|
||||||
self.conf: str = ""
|
self.conf: str = ""
|
||||||
self.model: "OptionalEmaneModel" = None
|
|
||||||
self.mobility: Optional[WayPointMobility] = None
|
self.mobility: Optional[WayPointMobility] = None
|
||||||
|
model_class = self.session.emane.get_model(options.emane_model)
|
||||||
|
self.wireless_model: Optional["EmaneModel"] = model_class(self.session, self.id)
|
||||||
|
if self.session.is_running():
|
||||||
|
self.session.emane.add_node(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_options(cls) -> EmaneOptions:
|
||||||
|
return EmaneOptions()
|
||||||
|
|
||||||
def linkconfig(
|
def linkconfig(
|
||||||
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
|
||||||
|
@ -69,18 +180,15 @@ class EmaneNet(CoreNetworkBase):
|
||||||
"""
|
"""
|
||||||
The CommEffect model supports link configuration.
|
The CommEffect model supports link configuration.
|
||||||
"""
|
"""
|
||||||
if not self.model:
|
if not self.wireless_model:
|
||||||
return
|
return
|
||||||
self.model.linkconfig(iface, options, iface2)
|
self.wireless_model.linkconfig(iface, options, iface2)
|
||||||
|
|
||||||
def config(self, conf: str) -> None:
|
|
||||||
self.conf = conf
|
|
||||||
|
|
||||||
def startup(self) -> None:
|
def startup(self) -> None:
|
||||||
pass
|
self.up = True
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
pass
|
self.up = False
|
||||||
|
|
||||||
def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||||
pass
|
pass
|
||||||
|
@ -88,30 +196,37 @@ class EmaneNet(CoreNetworkBase):
|
||||||
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
|
def updatemodel(self, config: dict[str, str]) -> None:
|
||||||
raise CoreError("emane networks cannot be linked to other networks")
|
"""
|
||||||
|
Update configuration for the current model.
|
||||||
|
|
||||||
def updatemodel(self, config: Dict[str, str]) -> None:
|
:param config: configuration to update model with
|
||||||
if not self.model:
|
:return: nothing
|
||||||
|
"""
|
||||||
|
if not self.wireless_model:
|
||||||
raise CoreError(f"no model set to update for node({self.name})")
|
raise CoreError(f"no model set to update for node({self.name})")
|
||||||
logger.info("node(%s) updating model(%s): %s", self.id, self.model.name, config)
|
logger.info(
|
||||||
self.model.update_config(config)
|
"node(%s) updating model(%s): %s", self.id, self.wireless_model.name, config
|
||||||
|
)
|
||||||
|
self.wireless_model.update_config(config)
|
||||||
|
|
||||||
def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None:
|
def setmodel(
|
||||||
|
self,
|
||||||
|
model: Union[type["EmaneModel"], type["WayPointMobility"]],
|
||||||
|
config: dict[str, str],
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
set the EmaneModel associated with this node
|
set the EmaneModel associated with this node
|
||||||
"""
|
"""
|
||||||
if model.config_type == RegisterTlvs.WIRELESS:
|
if model.config_type == RegisterTlvs.WIRELESS:
|
||||||
# EmaneModel really uses values from ConfigurableManager
|
self.wireless_model = model(session=self.session, _id=self.id)
|
||||||
# when buildnemxml() is called, not during init()
|
self.wireless_model.update_config(config)
|
||||||
self.model = model(session=self.session, _id=self.id)
|
|
||||||
self.model.update_config(config)
|
|
||||||
elif model.config_type == RegisterTlvs.MOBILITY:
|
elif model.config_type == RegisterTlvs.MOBILITY:
|
||||||
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 = super().links(flags)
|
links = []
|
||||||
emane_manager = self.session.emane
|
emane_manager = self.session.emane
|
||||||
# gather current emane links
|
# gather current emane links
|
||||||
nem_ids = set()
|
nem_ids = set()
|
||||||
|
@ -132,22 +247,44 @@ class EmaneNet(CoreNetworkBase):
|
||||||
# ignore incomplete links
|
# ignore incomplete links
|
||||||
if (nem2, nem1) not in emane_links:
|
if (nem2, nem1) not in emane_links:
|
||||||
continue
|
continue
|
||||||
link = emane_manager.get_nem_link(nem1, nem2)
|
link = emane_manager.get_nem_link(nem1, nem2, flags)
|
||||||
if link:
|
if link:
|
||||||
links.append(link)
|
links.append(link)
|
||||||
return links
|
return links
|
||||||
|
|
||||||
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
|
def create_tuntap(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
|
||||||
# TUN/TAP is not ready for addressing yet; the device may
|
"""
|
||||||
# take some time to appear, and installing it into a
|
Create a tuntap interface for the provided node.
|
||||||
# namespace after it has been bound removes addressing;
|
|
||||||
# save addresses with the interface now
|
:param node: node to create tuntap interface for
|
||||||
iface_id = node.newtuntap(iface_data.id, iface_data.name)
|
:param iface_data: interface data to create interface with
|
||||||
node.attachnet(iface_id, self)
|
:return: created tuntap interface
|
||||||
iface = node.get_iface(iface_id)
|
"""
|
||||||
iface.set_mac(iface_data.mac)
|
with node.lock:
|
||||||
for ip in iface_data.get_ips():
|
if iface_data.id is not None and iface_data.id in node.ifaces:
|
||||||
iface.add_ip(ip)
|
raise CoreError(
|
||||||
if self.session.state == EventTypes.RUNTIME_STATE:
|
f"node({self.id}) interface({iface_data.id}) already exists"
|
||||||
|
)
|
||||||
|
iface_id = (
|
||||||
|
iface_data.id if iface_data.id is not None else node.next_iface_id()
|
||||||
|
)
|
||||||
|
name = iface_data.name if iface_data.name is not None else f"eth{iface_id}"
|
||||||
|
session_id = self.session.short_session_id()
|
||||||
|
localname = f"tap{node.id}.{iface_id}.{session_id}"
|
||||||
|
iface = TunTap(iface_id, name, localname, self.session.use_ovs(), node=node)
|
||||||
|
if iface_data.mac:
|
||||||
|
iface.set_mac(iface_data.mac)
|
||||||
|
for ip in iface_data.get_ips():
|
||||||
|
iface.add_ip(ip)
|
||||||
|
node.ifaces[iface_id] = iface
|
||||||
|
self.attach(iface)
|
||||||
|
if self.up:
|
||||||
|
iface.startup()
|
||||||
|
if self.session.is_running():
|
||||||
self.session.emane.start_iface(self, iface)
|
self.session.emane.start_iface(self, iface)
|
||||||
return iface
|
return iface
|
||||||
|
|
||||||
|
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
|
||||||
|
raise CoreError(
|
||||||
|
f"emane network({self.name}) do not support adopting interfaces"
|
||||||
|
)
|
||||||
|
|
67
daemon/core/emulator/broadcast.py
Normal file
67
daemon/core/emulator/broadcast.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
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)
|
239
daemon/core/emulator/controlnets.py
Normal file
239
daemon/core/emulator/controlnets.py
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
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,10 +1,6 @@
|
||||||
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
|
||||||
|
@ -18,31 +14,12 @@ 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.
|
||||||
|
|
||||||
|
@ -53,13 +30,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()
|
||||||
|
|
||||||
|
@ -70,9 +47,6 @@ 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.
|
||||||
|
@ -140,13 +114,11 @@ class CoreEmu:
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.info("shutting down all sessions")
|
logger.info("shutting down all sessions")
|
||||||
sessions = self.sessions.copy()
|
while self.sessions:
|
||||||
self.sessions.clear()
|
_, session = self.sessions.popitem()
|
||||||
for _id in sessions:
|
|
||||||
session = sessions[_id]
|
|
||||||
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, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
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
|
||||||
|
@ -92,6 +92,10 @@ class NodeOptions:
|
||||||
image: str = None
|
image: str = None
|
||||||
emane: str = None
|
emane: str = None
|
||||||
legacy: bool = False
|
legacy: bool = False
|
||||||
|
# src, dst
|
||||||
|
binds: list[tuple[str, str]] = field(default_factory=list)
|
||||||
|
# src, dst, unique, delete
|
||||||
|
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:
|
||||||
"""
|
"""
|
||||||
|
@ -144,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,13 +8,14 @@ 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, Dict, Tuple
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from fabric import Connection
|
from fabric import Connection
|
||||||
from invoke import UnexpectedExit
|
from invoke import UnexpectedExit
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
from core.emulator.links import CoreLink
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.executables import get_requirements
|
from core.executables import get_requirements
|
||||||
from core.nodes.interface import GreTap
|
from core.nodes.interface import GreTap
|
||||||
|
@ -47,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.
|
||||||
|
@ -104,7 +105,7 @@ class DistributedServer:
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(data.encode("utf-8"))
|
temp.write(data.encode())
|
||||||
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)
|
||||||
|
@ -122,11 +123,9 @@ 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_config(
|
self.address: str = self.session.options.get("distributed_address")
|
||||||
"distributed_address", default=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_server(self, name: str, host: str) -> None:
|
def add_server(self, name: str, host: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -183,24 +182,38 @@ class DistributedController:
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""
|
"""
|
||||||
Start distributed network tunnels.
|
Start distributed network tunnels for control networks.
|
||||||
|
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
mtu = self.session.options.get_config_int("mtu")
|
mtu = self.session.options.get_int("mtu")
|
||||||
for node_id in self.session.nodes:
|
for node in self.session.nodes.values():
|
||||||
node = self.session.nodes[node_id]
|
if not isinstance(node, CtrlNet) or node.serverintf is not None:
|
||||||
if not isinstance(node, CoreNetwork):
|
|
||||||
continue
|
|
||||||
if isinstance(node, CtrlNet) and node.serverintf is not None:
|
|
||||||
continue
|
continue
|
||||||
for name in self.servers:
|
for name in self.servers:
|
||||||
server = self.servers[name]
|
server = self.servers[name]
|
||||||
self.create_gre_tunnel(node, server, mtu, True)
|
self.create_gre_tunnel(node, server, mtu, True)
|
||||||
|
|
||||||
|
def create_gre_tunnels(self, core_link: CoreLink) -> None:
|
||||||
|
"""
|
||||||
|
Creates gre tunnels for a core link with a ptp network connection.
|
||||||
|
|
||||||
|
:param core_link: core link to create gre tunnel for
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
if not self.servers:
|
||||||
|
return
|
||||||
|
if not core_link.ptp:
|
||||||
|
raise CoreError(
|
||||||
|
"attempted to create gre tunnel for core link without a ptp network"
|
||||||
|
)
|
||||||
|
mtu = self.session.options.get_int("mtu")
|
||||||
|
for server in self.servers.values():
|
||||||
|
self.create_gre_tunnel(core_link.ptp, server, mtu, True)
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,17 @@ class MessageFlags(Enum):
|
||||||
TTY = 0x40
|
TTY = 0x40
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlags(Enum):
|
||||||
|
"""
|
||||||
|
Configuration flags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
NONE = 0x00
|
||||||
|
REQUEST = 0x01
|
||||||
|
UPDATE = 0x02
|
||||||
|
RESET = 0x03
|
||||||
|
|
||||||
|
|
||||||
class NodeTypes(Enum):
|
class NodeTypes(Enum):
|
||||||
"""
|
"""
|
||||||
Node types.
|
Node types.
|
||||||
|
@ -38,6 +49,8 @@ class NodeTypes(Enum):
|
||||||
CONTROL_NET = 13
|
CONTROL_NET = 13
|
||||||
DOCKER = 15
|
DOCKER = 15
|
||||||
LXC = 16
|
LXC = 16
|
||||||
|
WIRELESS = 17
|
||||||
|
PODMAN = 18
|
||||||
|
|
||||||
|
|
||||||
class LinkTypes(Enum):
|
class LinkTypes(Enum):
|
||||||
|
|
145
daemon/core/emulator/hooks.py
Normal file
145
daemon/core/emulator/hooks.py
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
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}"
|
||||||
|
)
|
257
daemon/core/emulator/links.py
Normal file
257
daemon/core/emulator/links.py
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
"""
|
||||||
|
Provides functionality for maintaining information about known links
|
||||||
|
for a session.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from collections.abc import ValuesView
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from core.emulator.data import LinkData, LinkOptions
|
||||||
|
from core.emulator.enumerations import LinkTypes, MessageFlags
|
||||||
|
from core.errors import CoreError
|
||||||
|
from core.nodes.base import NodeBase
|
||||||
|
from core.nodes.interface import CoreInterface
|
||||||
|
from core.nodes.network import PtpNet
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
LinkKeyType = tuple[int, Optional[int], int, Optional[int]]
|
||||||
|
|
||||||
|
|
||||||
|
def create_key(
|
||||||
|
node1: NodeBase,
|
||||||
|
iface1: Optional[CoreInterface],
|
||||||
|
node2: NodeBase,
|
||||||
|
iface2: Optional[CoreInterface],
|
||||||
|
) -> LinkKeyType:
|
||||||
|
"""
|
||||||
|
Creates a unique key for tracking links.
|
||||||
|
|
||||||
|
:param node1: first node in link
|
||||||
|
:param iface1: node1 interface
|
||||||
|
:param node2: second node in link
|
||||||
|
:param iface2: node2 interface
|
||||||
|
:return: link key
|
||||||
|
"""
|
||||||
|
iface1_id = iface1.id if iface1 else None
|
||||||
|
iface2_id = iface2.id if iface2 else None
|
||||||
|
if node1.id < node2.id:
|
||||||
|
return node1.id, iface1_id, node2.id, iface2_id
|
||||||
|
else:
|
||||||
|
return node2.id, iface2_id, node1.id, iface1_id
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CoreLink:
|
||||||
|
"""
|
||||||
|
Provides a core link data structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
node1: NodeBase
|
||||||
|
iface1: Optional[CoreInterface]
|
||||||
|
node2: NodeBase
|
||||||
|
iface2: Optional[CoreInterface]
|
||||||
|
ptp: PtpNet = None
|
||||||
|
label: str = None
|
||||||
|
color: str = None
|
||||||
|
|
||||||
|
def key(self) -> LinkKeyType:
|
||||||
|
"""
|
||||||
|
Retrieve the key for this link.
|
||||||
|
|
||||||
|
:return: link key
|
||||||
|
"""
|
||||||
|
return create_key(self.node1, self.iface1, self.node2, self.iface2)
|
||||||
|
|
||||||
|
def is_unidirectional(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if this link is considered unidirectional, due to current
|
||||||
|
iface configurations.
|
||||||
|
|
||||||
|
:return: True if unidirectional, False otherwise
|
||||||
|
"""
|
||||||
|
unidirectional = False
|
||||||
|
if self.iface1 and self.iface2:
|
||||||
|
unidirectional = self.iface1.options != self.iface2.options
|
||||||
|
return unidirectional
|
||||||
|
|
||||||
|
def options(self) -> LinkOptions:
|
||||||
|
"""
|
||||||
|
Retrieve the options for this link.
|
||||||
|
|
||||||
|
:return: options for this link
|
||||||
|
"""
|
||||||
|
if self.is_unidirectional():
|
||||||
|
options = self.iface1.options
|
||||||
|
else:
|
||||||
|
if self.iface1:
|
||||||
|
options = self.iface1.options
|
||||||
|
else:
|
||||||
|
options = self.iface2.options
|
||||||
|
return options
|
||||||
|
|
||||||
|
def get_data(self, message_type: MessageFlags, source: str = None) -> LinkData:
|
||||||
|
"""
|
||||||
|
Create link data for this link.
|
||||||
|
|
||||||
|
:param message_type: link data message type
|
||||||
|
:param source: source for this data
|
||||||
|
:return: link data
|
||||||
|
"""
|
||||||
|
iface1_data = self.iface1.get_data() if self.iface1 else None
|
||||||
|
iface2_data = self.iface2.get_data() if self.iface2 else None
|
||||||
|
return LinkData(
|
||||||
|
message_type=message_type,
|
||||||
|
type=LinkTypes.WIRED,
|
||||||
|
node1_id=self.node1.id,
|
||||||
|
node2_id=self.node2.id,
|
||||||
|
iface1=iface1_data,
|
||||||
|
iface2=iface2_data,
|
||||||
|
options=self.options(),
|
||||||
|
label=self.label,
|
||||||
|
color=self.color,
|
||||||
|
source=source,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_data_unidirectional(self, source: str = None) -> LinkData:
|
||||||
|
"""
|
||||||
|
Create other unidirectional link data.
|
||||||
|
|
||||||
|
:param source: source for this data
|
||||||
|
:return: unidirectional link data
|
||||||
|
"""
|
||||||
|
iface1_data = self.iface1.get_data() if self.iface1 else None
|
||||||
|
iface2_data = self.iface2.get_data() if self.iface2 else None
|
||||||
|
return LinkData(
|
||||||
|
message_type=MessageFlags.NONE,
|
||||||
|
type=LinkTypes.WIRED,
|
||||||
|
node1_id=self.node2.id,
|
||||||
|
node2_id=self.node1.id,
|
||||||
|
iface1=iface2_data,
|
||||||
|
iface2=iface1_data,
|
||||||
|
options=self.iface2.options,
|
||||||
|
label=self.label,
|
||||||
|
color=self.color,
|
||||||
|
source=source,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LinkManager:
|
||||||
|
"""
|
||||||
|
Provides core link management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
Create a LinkManager instance.
|
||||||
|
"""
|
||||||
|
self._links: dict[LinkKeyType, CoreLink] = {}
|
||||||
|
self._node_links: dict[int, dict[LinkKeyType, CoreLink]] = {}
|
||||||
|
|
||||||
|
def add(self, core_link: CoreLink) -> None:
|
||||||
|
"""
|
||||||
|
Add a core link to be tracked.
|
||||||
|
|
||||||
|
:param core_link: link to track
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
node1, iface1 = core_link.node1, core_link.iface1
|
||||||
|
node2, iface2 = core_link.node2, core_link.iface2
|
||||||
|
if core_link.key() in self._links:
|
||||||
|
raise CoreError(
|
||||||
|
f"node1({node1.name}) iface1({iface1.id}) "
|
||||||
|
f"node2({node2.name}) iface2({iface2.id}) link already exists"
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"adding link from node(%s:%s) to node(%s:%s)",
|
||||||
|
node1.name,
|
||||||
|
iface1.name if iface1 else None,
|
||||||
|
node2.name,
|
||||||
|
iface2.name if iface2 else None,
|
||||||
|
)
|
||||||
|
self._links[core_link.key()] = core_link
|
||||||
|
node1_links = self._node_links.setdefault(node1.id, {})
|
||||||
|
node1_links[core_link.key()] = core_link
|
||||||
|
node2_links = self._node_links.setdefault(node2.id, {})
|
||||||
|
node2_links[core_link.key()] = core_link
|
||||||
|
|
||||||
|
def delete(
|
||||||
|
self,
|
||||||
|
node1: NodeBase,
|
||||||
|
iface1: Optional[CoreInterface],
|
||||||
|
node2: NodeBase,
|
||||||
|
iface2: Optional[CoreInterface],
|
||||||
|
) -> CoreLink:
|
||||||
|
"""
|
||||||
|
Remove a link from being tracked.
|
||||||
|
|
||||||
|
:param node1: first node in link
|
||||||
|
:param iface1: node1 interface
|
||||||
|
:param node2: second node in link
|
||||||
|
:param iface2: node2 interface
|
||||||
|
:return: removed core link
|
||||||
|
"""
|
||||||
|
key = create_key(node1, iface1, node2, iface2)
|
||||||
|
if key not in self._links:
|
||||||
|
raise CoreError(
|
||||||
|
f"node1({node1.name}) iface1({iface1.id}) "
|
||||||
|
f"node2({node2.name}) iface2({iface2.id}) is not linked"
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"deleting link from node(%s:%s) to node(%s:%s)",
|
||||||
|
node1.name,
|
||||||
|
iface1.name if iface1 else None,
|
||||||
|
node2.name,
|
||||||
|
iface2.name if iface2 else None,
|
||||||
|
)
|
||||||
|
node1_links = self._node_links[node1.id]
|
||||||
|
node1_links.pop(key)
|
||||||
|
node2_links = self._node_links[node2.id]
|
||||||
|
node2_links.pop(key)
|
||||||
|
return self._links.pop(key)
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""
|
||||||
|
Resets and clears all tracking information.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
self._links.clear()
|
||||||
|
self._node_links.clear()
|
||||||
|
|
||||||
|
def get_link(
|
||||||
|
self,
|
||||||
|
node1: NodeBase,
|
||||||
|
iface1: Optional[CoreInterface],
|
||||||
|
node2: NodeBase,
|
||||||
|
iface2: Optional[CoreInterface],
|
||||||
|
) -> Optional[CoreLink]:
|
||||||
|
"""
|
||||||
|
Retrieve a link for provided values.
|
||||||
|
|
||||||
|
:param node1: first node in link
|
||||||
|
:param iface1: interface for node1
|
||||||
|
:param node2: second node in link
|
||||||
|
:param iface2: interface for node2
|
||||||
|
:return: core link if present, None otherwise
|
||||||
|
"""
|
||||||
|
key = create_key(node1, iface1, node2, iface2)
|
||||||
|
return self._links.get(key)
|
||||||
|
|
||||||
|
def links(self) -> ValuesView[CoreLink]:
|
||||||
|
"""
|
||||||
|
Retrieve all known links
|
||||||
|
|
||||||
|
:return: iterator for all known links
|
||||||
|
"""
|
||||||
|
return self._links.values()
|
||||||
|
|
||||||
|
def node_links(self, node: NodeBase) -> ValuesView[CoreLink]:
|
||||||
|
"""
|
||||||
|
Retrieve all links for a given node.
|
||||||
|
|
||||||
|
:param node: node to get links for
|
||||||
|
:return: node links
|
||||||
|
"""
|
||||||
|
return self._node_links.get(node.id, {}).values()
|
File diff suppressed because it is too large
Load diff
|
@ -1,24 +1,16 @@
|
||||||
from typing import Any, List
|
from typing import Optional
|
||||||
|
|
||||||
from core.config import (
|
from core.config import ConfigBool, ConfigInt, ConfigString, Configuration
|
||||||
ConfigBool,
|
from core.errors import CoreError
|
||||||
ConfigInt,
|
|
||||||
ConfigString,
|
|
||||||
ConfigurableManager,
|
|
||||||
ConfigurableOptions,
|
|
||||||
Configuration,
|
|
||||||
)
|
|
||||||
from core.emulator.enumerations import RegisterTlvs
|
|
||||||
from core.plugins.sdt import Sdt
|
from core.plugins.sdt import Sdt
|
||||||
|
|
||||||
|
|
||||||
class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
class SessionConfig:
|
||||||
"""
|
"""
|
||||||
Provides session configuration.
|
Provides session configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "session"
|
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"),
|
||||||
|
@ -42,34 +34,54 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
||||||
ConfigInt(id="link_timeout", default="4", label="EMANE Link Timeout (sec)"),
|
ConfigInt(id="link_timeout", default="4", label="EMANE Link Timeout (sec)"),
|
||||||
ConfigInt(id="mtu", default="0", label="MTU for All Devices"),
|
ConfigInt(id="mtu", default="0", label="MTU for All Devices"),
|
||||||
]
|
]
|
||||||
config_type: RegisterTlvs = RegisterTlvs.UTILITY
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, config: dict[str, str] = None) -> None:
|
||||||
super().__init__()
|
|
||||||
self.set_configs(self.default_values())
|
|
||||||
|
|
||||||
def get_config(
|
|
||||||
self,
|
|
||||||
_id: str,
|
|
||||||
node_id: int = ConfigurableManager._default_node,
|
|
||||||
config_type: str = ConfigurableManager._default_type,
|
|
||||||
default: Any = None,
|
|
||||||
) -> str:
|
|
||||||
"""
|
"""
|
||||||
Retrieves a specific configuration for a node and configuration type.
|
Create a SessionConfig instance.
|
||||||
|
|
||||||
:param _id: specific configuration to retrieve
|
:param config: configuration to initialize with
|
||||||
:param node_id: node id to store configuration for
|
|
||||||
:param config_type: configuration type to store configuration for
|
|
||||||
:param default: default value to return when value is not found
|
|
||||||
:return: configuration value
|
|
||||||
"""
|
"""
|
||||||
value = super().get_config(_id, node_id, config_type, default)
|
self._config: dict[str, str] = {x.id: x.default for x in self.options}
|
||||||
if value == "":
|
self._config.update(config or {})
|
||||||
value = default
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get_config_bool(self, name: str, default: Any = None) -> bool:
|
def update(self, config: dict[str, str]) -> None:
|
||||||
|
"""
|
||||||
|
Update current configuration with provided values.
|
||||||
|
|
||||||
|
:param config: configuration to update with
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
self._config.update(config)
|
||||||
|
|
||||||
|
def set(self, name: str, value: str) -> None:
|
||||||
|
"""
|
||||||
|
Set a configuration value.
|
||||||
|
|
||||||
|
:param name: name of configuration to set
|
||||||
|
:param value: value to set
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
self._config[name] = value
|
||||||
|
|
||||||
|
def get(self, name: str, default: str = None) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Retrieve configuration value.
|
||||||
|
|
||||||
|
:param name: name of configuration to get
|
||||||
|
:param default: value to return as default
|
||||||
|
:return: return found configuration value or default
|
||||||
|
"""
|
||||||
|
return self._config.get(name, default)
|
||||||
|
|
||||||
|
def all(self) -> dict[str, str]:
|
||||||
|
"""
|
||||||
|
Retrieve all configuration options.
|
||||||
|
|
||||||
|
:return: configuration value dict
|
||||||
|
"""
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def get_bool(self, name: str, default: bool = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Get configuration value as a boolean.
|
Get configuration value as a boolean.
|
||||||
|
|
||||||
|
@ -77,12 +89,15 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
||||||
:param default: default value if not found
|
:param default: default value if not found
|
||||||
:return: boolean for configuration value
|
:return: boolean for configuration value
|
||||||
"""
|
"""
|
||||||
value = self.get_config(name)
|
value = self._config.get(name)
|
||||||
|
if value is None and default is None:
|
||||||
|
raise CoreError(f"missing session options for {name}")
|
||||||
if value is None:
|
if value is None:
|
||||||
return default
|
return default
|
||||||
return value.lower() == "true"
|
else:
|
||||||
|
return value.lower() == "true"
|
||||||
|
|
||||||
def get_config_int(self, name: str, default: Any = None) -> int:
|
def get_int(self, name: str, default: int = None) -> int:
|
||||||
"""
|
"""
|
||||||
Get configuration value as int.
|
Get configuration value as int.
|
||||||
|
|
||||||
|
@ -90,17 +105,10 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
|
||||||
:param default: default value if not found
|
:param default: default value if not found
|
||||||
:return: int for configuration value
|
:return: int for configuration value
|
||||||
"""
|
"""
|
||||||
value = self.get_config(name, default=default)
|
value = self._config.get(name)
|
||||||
if value is not None:
|
if value is None and default is None:
|
||||||
value = int(value)
|
raise CoreError(f"missing session options for {name}")
|
||||||
return value
|
if value is None:
|
||||||
|
return default
|
||||||
def config_reset(self, node_id: int = None) -> None:
|
else:
|
||||||
"""
|
return int(value)
|
||||||
Clear prior configuration files and reset to default values.
|
|
||||||
|
|
||||||
:param node_id: node id to store configuration for
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
super().config_reset(node_id)
|
|
||||||
self.set_configs(self.default_values())
|
|
||||||
|
|
|
@ -1,34 +1,33 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
BASH: str = "bash"
|
BASH: str = "bash"
|
||||||
VNODED: str = "vnoded"
|
|
||||||
VCMD: str = "vcmd"
|
|
||||||
SYSCTL: str = "sysctl"
|
|
||||||
IP: str = "ip"
|
|
||||||
ETHTOOL: str = "ethtool"
|
ETHTOOL: str = "ethtool"
|
||||||
TC: str = "tc"
|
IP: str = "ip"
|
||||||
MOUNT: str = "mount"
|
MOUNT: str = "mount"
|
||||||
UMOUNT: str = "umount"
|
|
||||||
OVS_VSCTL: str = "ovs-vsctl"
|
|
||||||
TEST: str = "test"
|
|
||||||
NFTABLES: str = "nft"
|
NFTABLES: str = "nft"
|
||||||
|
OVS_VSCTL: str = "ovs-vsctl"
|
||||||
|
SYSCTL: str = "sysctl"
|
||||||
|
TC: str = "tc"
|
||||||
|
TEST: str = "test"
|
||||||
|
UMOUNT: str = "umount"
|
||||||
|
VCMD: str = "vcmd"
|
||||||
|
VNODED: str = "vnoded"
|
||||||
|
|
||||||
COMMON_REQUIREMENTS: List[str] = [
|
COMMON_REQUIREMENTS: list[str] = [
|
||||||
BASH,
|
BASH,
|
||||||
NFTABLES,
|
|
||||||
ETHTOOL,
|
ETHTOOL,
|
||||||
IP,
|
IP,
|
||||||
MOUNT,
|
MOUNT,
|
||||||
|
NFTABLES,
|
||||||
SYSCTL,
|
SYSCTL,
|
||||||
TC,
|
TC,
|
||||||
UMOUNT,
|
|
||||||
TEST,
|
TEST,
|
||||||
|
UMOUNT,
|
||||||
|
VCMD,
|
||||||
|
VNODED,
|
||||||
]
|
]
|
||||||
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
|
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.
|
||||||
|
|
||||||
|
@ -38,6 +37,4 @@ def get_requirements(use_ovs: bool) -> List[str]:
|
||||||
requirements = COMMON_REQUIREMENTS
|
requirements = COMMON_REQUIREMENTS
|
||||||
if use_ovs:
|
if use_ovs:
|
||||||
requirements += OVS_REQUIREMENTS
|
requirements += OVS_REQUIREMENTS
|
||||||
else:
|
|
||||||
requirements += VCMD_REQUIREMENTS
|
|
||||||
return requirements
|
return requirements
|
||||||
|
|
|
@ -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, Dict, Optional, Type
|
from typing import Any, Optional
|
||||||
|
|
||||||
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 Dict, List, Optional, Type
|
from typing import Optional
|
||||||
|
|
||||||
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,9 +6,10 @@ 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, Dict, Iterable, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ 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,
|
||||||
|
@ -55,7 +57,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()}
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,27 +72,30 @@ 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
|
||||||
|
@ -242,9 +247,10 @@ 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:
|
||||||
self.handling_throughputs = self.client.throughputs(
|
if not self.handling_throughputs:
|
||||||
self.session.id, self.handle_throughputs
|
self.handling_throughputs = self.client.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:
|
||||||
|
@ -368,7 +374,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 = set(x.id for x in sessions)
|
session_ids = {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",
|
||||||
|
@ -397,23 +403,25 @@ 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 = []
|
||||||
for edge in self.links.values():
|
for edge in self.links.values():
|
||||||
link = edge.link
|
link = edge.link
|
||||||
if not definition:
|
if not definition:
|
||||||
if link.iface1 and not link.iface1.mac:
|
node1 = self.session.nodes[link.node1_id]
|
||||||
|
node2 = self.session.nodes[link.node2_id]
|
||||||
|
if nutils.is_container(node1) and link.iface1 and not link.iface1.mac:
|
||||||
link.iface1.mac = self.ifaces_manager.next_mac()
|
link.iface1.mac = self.ifaces_manager.next_mac()
|
||||||
if link.iface2 and not link.iface2.mac:
|
if nutils.is_container(node2) and link.iface2 and not link.iface2.mac:
|
||||||
link.iface2.mac = self.ifaces_manager.next_mac()
|
link.iface2.mac = self.ifaces_manager.next_mac()
|
||||||
links.append(link)
|
links.append(link)
|
||||||
if edge.asymmetric_link:
|
if edge.asymmetric_link:
|
||||||
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()
|
||||||
|
@ -429,13 +437,15 @@ 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:
|
||||||
if not session_id:
|
session_id = session_id or self.session.id
|
||||||
session_id = self.session.id
|
self.cancel_throughputs()
|
||||||
result = False
|
result = False
|
||||||
try:
|
try:
|
||||||
result = self.client.stop_session(session_id)
|
result = self.client.stop_session(session_id)
|
||||||
|
@ -453,7 +463,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)
|
||||||
|
@ -644,7 +654,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
|
||||||
|
@ -665,14 +675,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 nutils.is_container(src_node):
|
if edge.link.iface1:
|
||||||
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 nutils.is_container(dst_node):
|
if edge.link.iface2:
|
||||||
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:
|
||||||
|
@ -683,7 +693,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):
|
||||||
|
@ -694,7 +704,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():
|
||||||
|
@ -708,7 +718,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):
|
||||||
|
@ -728,7 +738,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):
|
||||||
|
@ -741,9 +751,17 @@ 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]:
|
||||||
|
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):
|
||||||
|
@ -765,7 +783,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",
|
||||||
|
@ -774,7 +792,10 @@ class CoreClient:
|
||||||
)
|
)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_mobility_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)
|
||||||
|
|
||||||
|
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",
|
||||||
|
@ -785,7 +806,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(
|
||||||
|
|
BIN
daemon/core/gui/data/icons/podman.png
Normal file
BIN
daemon/core/gui/data/icons/podman.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
daemon/core/gui/data/icons/wireless.png
Normal file
BIN
daemon/core/gui/data/icons/wireless.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -1,131 +1,77 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/home/developer/.core/configs/emane-demo-antenna.xml">
|
<scenario name="/tmp/tmpd4t2sxy2">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="5" name="wlan5" model="emane_rfpipe" type="EMANE">
|
<network id="5" name="wlan5" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="388" y="555" lat="47.57412169587584" lon="-122.12709380504643" alt="2.0"/>
|
<position x="388.0" y="555.0" lat="47.574121408201655" lon="-122.12709602379641" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" type="mdr" class="" image="">
|
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="258" y="147" lat="47.577830502987744" lon="-122.12884551985047" alt="2.0"/>
|
<position x="258.0" y="147.0" lat="47.57783021533393" lon="-122.12884773860046" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" type="mdr" class="" image="">
|
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="526" y="147" lat="47.577830502987744" lon="-122.12523429240828" alt="2.0"/>
|
<position x="526.0" y="147.0" lat="47.57783021533393" lon="-122.12523651115826" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="3" name="n3" type="mdr" class="" image="">
|
<device id="3" name="n3" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="241" y="387" lat="47.57564888355958" lon="-122.12907459024791" alt="2.0"/>
|
<position x="241.0" y="387.0" lat="47.575648595893774" lon="-122.1290768089979" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="4" name="n4" type="mdr" class="" image="">
|
<device id="4" name="n4" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="529" y="385" lat="47.57566706409707" lon="-122.1251938682205" alt="2.0"/>
|
<position x="529.0" y="385.0" lat="47.57566677643136" lon="-122.12519608697049" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="5" node2="1">
|
<link node1="1" node2="5">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="5" node2="2">
|
<link node1="2" node2="5">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="5" node2="3">
|
<link node1="3" node2="5">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="5" node2="4">
|
<link node1="4" node2="5">
|
||||||
<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"/>
|
<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"/>
|
||||||
</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="5" 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="none"/>
|
|
||||||
<configuration name="propagationmodel" value="2ray"/>
|
|
||||||
<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="1" model="emane_rfpipe">
|
<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>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -140,6 +86,17 @@
|
||||||
</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"/>
|
||||||
|
@ -156,7 +113,10 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="outofband"/>
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<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"/>
|
||||||
|
@ -169,6 +129,23 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
<emane_configuration node="2" model="emane_rfpipe">
|
<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>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -183,6 +160,17 @@
|
||||||
</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"/>
|
||||||
|
@ -199,7 +187,10 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="outofband"/>
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<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"/>
|
||||||
|
@ -212,6 +203,23 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
<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"/>
|
||||||
|
@ -226,6 +234,17 @@
|
||||||
</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"/>
|
||||||
|
@ -242,7 +261,10 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="outofband"/>
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<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"/>
|
||||||
|
@ -255,6 +277,23 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
<emane_configuration node="4" model="emane_rfpipe">
|
<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>
|
<mac>
|
||||||
<configuration name="datarate" value="1000000"/>
|
<configuration name="datarate" value="1000000"/>
|
||||||
<configuration name="delay" value="0.000000"/>
|
<configuration name="delay" value="0.000000"/>
|
||||||
|
@ -269,6 +308,17 @@
|
||||||
</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"/>
|
||||||
|
@ -285,7 +335,84 @@
|
||||||
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
<configuration name="noisemaxsegmentduration" value="1000000"/>
|
||||||
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
<configuration name="noisemaxsegmentoffset" value="300000"/>
|
||||||
<configuration name="noisemode" value="outofband"/>
|
<configuration name="noisemode" value="outofband"/>
|
||||||
|
<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="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">
|
||||||
|
<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="none"/>
|
||||||
|
<configuration name="processingpoolsize" value="0"/>
|
||||||
|
<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"/>
|
||||||
|
@ -298,7 +425,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.579166412353516" lon="-122.13232421875" 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=""/>
|
||||||
|
@ -311,10 +438,19 @@
|
||||||
<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="canvas c1" value="{name {Canvas1}}"/>
|
<configuration name="shapes" 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="edges" value="[]"/>
|
||||||
|
<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,66 +1,55 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/home/developer/.core/configs/emane-demo-eel.xml">
|
<scenario name="/tmp/tmp2mkcwn17">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" type="mdr" class="" image="">
|
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
|
<position x="153.0" y="172.0" lat="47.577602967549986" lon="-122.13026258517293" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" type="mdr" class="" image="">
|
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="3" node2="1">
|
<link node1="1" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="3" node2="2">
|
<link node1="2" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</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"/>
|
||||||
|
@ -75,6 +64,17 @@
|
||||||
</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,7 +91,10 @@
|
||||||
<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"/>
|
||||||
|
@ -104,7 +107,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.579166412353516" lon="-122.13232421875" 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=""/>
|
||||||
|
@ -117,10 +120,19 @@
|
||||||
<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="canvas c1" value="{name {Canvas1}}"/>
|
<configuration name="shapes" 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="edges" value="[]"/>
|
||||||
|
<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,66 +1,55 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/home/developer/.core/configs/emane-demo-files.xml">
|
<scenario name="/tmp/tmpsj4dhmce">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" type="mdr" class="" image="">
|
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
|
<position x="153.0" y="173.0" lat="47.57759387761812" lon="-122.13026258517293" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" type="mdr" class="" image="">
|
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="3" node2="1">
|
<link node1="1" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="3" node2="2">
|
<link node1="2" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</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"/>
|
||||||
|
@ -75,6 +64,17 @@
|
||||||
</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,7 +91,10 @@
|
||||||
<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"/>
|
||||||
|
@ -104,7 +107,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.579166412353516" lon="-122.13232421875" 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=""/>
|
||||||
|
@ -117,10 +120,19 @@
|
||||||
<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="canvas c1" value="{name {Canvas1}}"/>
|
<configuration name="shapes" 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="edges" value="[]"/>
|
||||||
|
<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,66 +1,55 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/home/developer/.core/configs/emane-demo-gpsd.xml">
|
<scenario name="/tmp/tmp081pn3j9">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" type="mdr" class="" image="">
|
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
|
<position x="153.0" y="173.0" lat="47.57759387761812" lon="-122.13026258517293" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" type="mdr" class="" image="">
|
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="3" node2="1">
|
<link node1="1" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="3" node2="2">
|
<link node1="2" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</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"/>
|
||||||
|
@ -75,6 +64,17 @@
|
||||||
</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,7 +91,10 @@
|
||||||
<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"/>
|
||||||
|
@ -104,7 +107,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.579166412353516" lon="-122.13232421875" 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=""/>
|
||||||
|
@ -117,10 +120,19 @@
|
||||||
<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="canvas c1" value="{name {Canvas1}}"/>
|
<configuration name="shapes" 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="edges" value="[]"/>
|
||||||
|
<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,66 +1,55 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<scenario name="/home/developer/.core/configs/emane-demo-precomputed.xml">
|
<scenario name="/tmp/tmpfokvqh5k">
|
||||||
<networks>
|
<networks>
|
||||||
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
|
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
|
||||||
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
|
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
|
||||||
</network>
|
</network>
|
||||||
</networks>
|
</networks>
|
||||||
<devices>
|
<devices>
|
||||||
<device id="1" name="n1" type="mdr" class="" image="">
|
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
|
<position x="153.0" y="172.0" lat="47.577602967549986" lon="-122.13026258517293" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="2" name="n2" type="mdr" class="" image="">
|
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
|
||||||
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
|
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
|
||||||
<services>
|
<services>
|
||||||
<service name="zebra"/>
|
<service name="zebra"/>
|
||||||
<service name="OSPFv3MDR"/>
|
|
||||||
<service name="IPForward"/>
|
<service name="IPForward"/>
|
||||||
|
<service name="OSPFv3MDR"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
</devices>
|
</devices>
|
||||||
<links>
|
<links>
|
||||||
<link node1="3" node2="1">
|
<link node1="1" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</link>
|
</link>
|
||||||
<link node1="3" node2="2">
|
<link node1="2" node2="3">
|
||||||
<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"/>
|
<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"/>
|
||||||
</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"/>
|
||||||
|
@ -75,6 +64,17 @@
|
||||||
</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,7 +91,10 @@
|
||||||
<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"/>
|
||||||
|
@ -104,7 +107,7 @@
|
||||||
</external>
|
</external>
|
||||||
</emane_configuration>
|
</emane_configuration>
|
||||||
</emane_configurations>
|
</emane_configurations>
|
||||||
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
|
<session_origin lat="47.579166412353516" lon="-122.13232421875" 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=""/>
|
||||||
|
@ -117,10 +120,19 @@
|
||||||
<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="canvas c1" value="{name {Canvas1}}"/>
|
<configuration name="shapes" 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="edges" value="[]"/>
|
||||||
|
<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,10 +95,9 @@
|
||||||
<service name="DefaultRoute"/>
|
<service name="DefaultRoute"/>
|
||||||
</services>
|
</services>
|
||||||
</device>
|
</device>
|
||||||
<device id="14" name="n14" icon="" canvas="0" type="host" class="" image="">
|
<device id="14" name="n14" icon="" canvas="0" type="PC" 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>
|
||||||
|
@ -260,9 +259,5 @@
|
||||||
<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, Dict, Optional
|
from typing import TYPE_CHECKING, 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, List, Optional
|
from typing import TYPE_CHECKING, 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, Tuple
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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,6 +13,36 @@ 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"
|
||||||
|
@ -27,7 +57,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 = self.get_rgb(initcolor)
|
red, green, blue = get_rgb_values(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)
|
||||||
|
@ -66,7 +96,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="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
|
frame, background=get_rgb(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)
|
||||||
|
|
||||||
|
@ -89,7 +119,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="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
|
frame, background=get_rgb(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)
|
||||||
|
|
||||||
|
@ -112,7 +142,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="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
|
frame, background=get_rgb(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)
|
||||||
|
|
||||||
|
@ -150,39 +180,27 @@ 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 = self.red_entry.get()
|
red = int(self.red_entry.get() or 0)
|
||||||
blue = self.blue_entry.get()
|
blue = int(self.blue_entry.get() or 0)
|
||||||
green = self.green_entry.get()
|
green = int(self.green_entry.get() or 0)
|
||||||
self.set_scale(red, green, blue)
|
self.set_scale(red, green, blue)
|
||||||
if red and blue and green:
|
hex_code = get_rgb(red, green, blue)
|
||||||
hex_code = "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
self.hex.set(hex_code)
|
||||||
self.hex.set(hex_code)
|
self.display.config(background=hex_code)
|
||||||
self.display.config(background=hex_code)
|
self.set_label(red, green, blue)
|
||||||
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 = self.get_rgb(hex_code)
|
red, green, blue = get_rgb_values(hex_code)
|
||||||
else:
|
self.set_entry(red, green, blue)
|
||||||
return
|
self.set_scale(red, green, blue)
|
||||||
self.set_entry(red, green, blue)
|
self.display.config(background=hex_code)
|
||||||
self.set_scale(red, green, blue)
|
self.set_label(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())
|
||||||
|
@ -199,21 +217,7 @@ class ColorPickerDialog(Dialog):
|
||||||
self.green.set(green)
|
self.green.set(green)
|
||||||
self.blue.set(blue)
|
self.blue.set(blue)
|
||||||
|
|
||||||
def set_label(self, red: str, green: str, blue: str) -> None:
|
def set_label(self, red: int, green: int, blue: int) -> None:
|
||||||
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
self.red_label.configure(background=get_rgb(red, 0, 0))
|
||||||
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
self.green_label.configure(background=get_rgb(0, green, 0))
|
||||||
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
self.blue_label.configure(background=get_rgb(0, 0, 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, Dict, List, Optional, Set
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -34,24 +34,23 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.core: "CoreClient" = app.core
|
self.core: "CoreClient" = app.core
|
||||||
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()
|
self.radiovar: tk.IntVar = tk.IntVar(value=2)
|
||||||
self.radiovar.set(2)
|
self.directories: list[str] = []
|
||||||
self.directories: List[str] = []
|
self.templates: list[str] = []
|
||||||
self.templates: List[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.StringVar = tk.StringVar()
|
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
|
||||||
|
@ -61,13 +60,14 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.validation_time_entry: Optional[ttk.Entry] = None
|
self.validation_time_entry: Optional[ttk.Entry] = None
|
||||||
self.validation_mode_entry: Optional[ttk.Entry] = None
|
self.validation_mode_entry: Optional[ttk.Entry] = None
|
||||||
self.template_text: Optional[CodeText] = None
|
self.template_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,14 +87,18 @@ 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)
|
||||||
self.mode_configs = defaults.modes
|
self.mode_configs = defaults.modes
|
||||||
self.config = ConfigOption.from_dict(defaults.config)
|
self.config = ConfigOption.from_dict(defaults.config)
|
||||||
self.default_config = {x.name: x.value for x in self.config.values()}
|
self.default_config = {x.name: x.value for x in self.config.values()}
|
||||||
|
self.rendered = self.core.get_config_service_rendered(
|
||||||
|
self.node.id, self.service_name
|
||||||
|
)
|
||||||
service_config = self.node.config_service_configs.get(self.service_name)
|
service_config = self.node.config_service_configs.get(self.service_name)
|
||||||
if service_config:
|
if service_config:
|
||||||
for key, value in service_config.config.items():
|
for key, value in service_config.config.items():
|
||||||
|
@ -110,7 +114,6 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
self.top.columnconfigure(0, weight=1)
|
self.top.columnconfigure(0, weight=1)
|
||||||
self.top.rowconfigure(0, weight=1)
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
# draw notebook
|
# draw notebook
|
||||||
self.notebook = ttk.Notebook(self.top)
|
self.notebook = ttk.Notebook(self.top)
|
||||||
self.notebook.grid(sticky=tk.NSEW, pady=PADY)
|
self.notebook.grid(sticky=tk.NSEW, pady=PADY)
|
||||||
|
@ -125,6 +128,7 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
||||||
tab.grid(sticky=tk.NSEW)
|
tab.grid(sticky=tk.NSEW)
|
||||||
tab.columnconfigure(0, weight=1)
|
tab.columnconfigure(0, weight=1)
|
||||||
|
tab.rowconfigure(2, weight=1)
|
||||||
self.notebook.add(tab, text="Directories/Files")
|
self.notebook.add(tab, text="Directories/Files")
|
||||||
|
|
||||||
label = ttk.Label(
|
label = ttk.Label(
|
||||||
|
@ -137,33 +141,54 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
label = ttk.Label(frame, text="Directories")
|
label = ttk.Label(frame, text="Directories")
|
||||||
label.grid(row=0, column=0, sticky=tk.W, padx=PADX)
|
label.grid(row=0, column=0, sticky=tk.W, padx=PADX)
|
||||||
directories_combobox = ttk.Combobox(
|
state = "readonly" if self.directories else tk.DISABLED
|
||||||
frame, values=self.directories, state="readonly"
|
directories_combobox = ttk.Combobox(frame, values=self.directories, state=state)
|
||||||
)
|
|
||||||
directories_combobox.grid(row=0, column=1, sticky=tk.EW, pady=PADY)
|
directories_combobox.grid(row=0, column=1, sticky=tk.EW, pady=PADY)
|
||||||
if self.directories:
|
if self.directories:
|
||||||
directories_combobox.current(0)
|
directories_combobox.current(0)
|
||||||
|
label = ttk.Label(frame, text="Files")
|
||||||
label = ttk.Label(frame, text="Templates")
|
|
||||||
label.grid(row=1, column=0, sticky=tk.W, padx=PADX)
|
label.grid(row=1, column=0, sticky=tk.W, padx=PADX)
|
||||||
|
state = "readonly" if self.templates else tk.DISABLED
|
||||||
self.templates_combobox = ttk.Combobox(
|
self.templates_combobox = ttk.Combobox(
|
||||||
frame, values=self.templates, state="readonly"
|
frame, values=self.templates, state=state
|
||||||
)
|
)
|
||||||
self.templates_combobox.bind(
|
self.templates_combobox.bind(
|
||||||
"<<ComboboxSelected>>", self.handle_template_changed
|
"<<ComboboxSelected>>", self.handle_template_changed
|
||||||
)
|
)
|
||||||
self.templates_combobox.grid(row=1, column=1, sticky=tk.EW, pady=PADY)
|
self.templates_combobox.grid(row=1, column=1, sticky=tk.EW, pady=PADY)
|
||||||
|
# draw file template tab
|
||||||
self.template_text = CodeText(tab)
|
notebook = ttk.Notebook(tab)
|
||||||
|
notebook.rowconfigure(0, weight=1)
|
||||||
|
notebook.columnconfigure(0, weight=1)
|
||||||
|
notebook.grid(sticky=tk.NSEW, pady=PADY)
|
||||||
|
# draw rendered file tab
|
||||||
|
rendered_tab = ttk.Frame(notebook, padding=FRAME_PAD)
|
||||||
|
rendered_tab.grid(sticky=tk.NSEW)
|
||||||
|
rendered_tab.rowconfigure(0, weight=1)
|
||||||
|
rendered_tab.columnconfigure(0, weight=1)
|
||||||
|
notebook.add(rendered_tab, text="Rendered")
|
||||||
|
self.rendered_text = CodeText(rendered_tab)
|
||||||
|
self.rendered_text.grid(sticky=tk.NSEW)
|
||||||
|
self.rendered_text.text.bind("<FocusOut>", self.update_template_file_data)
|
||||||
|
# draw template file tab
|
||||||
|
template_tab = ttk.Frame(notebook, padding=FRAME_PAD)
|
||||||
|
template_tab.grid(sticky=tk.NSEW)
|
||||||
|
template_tab.rowconfigure(0, weight=1)
|
||||||
|
template_tab.columnconfigure(0, weight=1)
|
||||||
|
notebook.add(template_tab, text="Template")
|
||||||
|
self.template_text = CodeText(template_tab)
|
||||||
self.template_text.grid(sticky=tk.NSEW)
|
self.template_text.grid(sticky=tk.NSEW)
|
||||||
tab.rowconfigure(self.template_text.grid_info()["row"], weight=1)
|
self.template_text.text.bind("<FocusOut>", self.update_template_file_data)
|
||||||
if self.templates:
|
if self.templates:
|
||||||
self.templates_combobox.current(0)
|
self.templates_combobox.current(0)
|
||||||
self.template_text.text.delete(1.0, "end")
|
template_name = self.templates[0]
|
||||||
self.template_text.text.insert(
|
temp_data = self.temp_service_files[template_name]
|
||||||
"end", self.temp_service_files[self.templates[0]]
|
self.template_text.set_text(temp_data)
|
||||||
)
|
rendered_data = self.rendered[template_name]
|
||||||
self.template_text.text.bind("<FocusOut>", self.update_template_file_data)
|
self.rendered_text.set_text(rendered_data)
|
||||||
|
else:
|
||||||
|
self.template_text.text.configure(state=tk.DISABLED)
|
||||||
|
self.rendered_text.text.configure(state=tk.DISABLED)
|
||||||
|
|
||||||
def draw_tab_config(self) -> None:
|
def draw_tab_config(self) -> None:
|
||||||
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
|
||||||
|
@ -243,7 +268,7 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
label = ttk.Label(frame, text="Validation Time")
|
label = ttk.Label(frame, text="Validation Time")
|
||||||
label.grid(row=0, column=0, sticky=tk.W, padx=PADX)
|
label.grid(row=0, column=0, sticky=tk.W, padx=PADX)
|
||||||
self.validation_time_entry = ttk.Entry(frame)
|
self.validation_time_entry = ttk.Entry(frame)
|
||||||
self.validation_time_entry.insert("end", self.validation_time)
|
self.validation_time_entry.insert("end", str(self.validation_time))
|
||||||
self.validation_time_entry.config(state=tk.DISABLED)
|
self.validation_time_entry.config(state=tk.DISABLED)
|
||||||
self.validation_time_entry.grid(row=0, column=1, sticky=tk.EW, pady=PADY)
|
self.validation_time_entry.grid(row=0, column=1, sticky=tk.EW, pady=PADY)
|
||||||
|
|
||||||
|
@ -323,9 +348,11 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def handle_template_changed(self, event: tk.Event) -> None:
|
def handle_template_changed(self, event: tk.Event) -> None:
|
||||||
template = self.templates_combobox.get()
|
template_name = self.templates_combobox.get()
|
||||||
self.template_text.text.delete(1.0, "end")
|
temp_data = self.temp_service_files[template_name]
|
||||||
self.template_text.text.insert("end", self.temp_service_files[template])
|
self.template_text.set_text(temp_data)
|
||||||
|
rendered = self.rendered[template_name]
|
||||||
|
self.rendered_text.set_text(rendered)
|
||||||
|
|
||||||
def handle_mode_changed(self, event: tk.Event) -> None:
|
def handle_mode_changed(self, event: tk.Event) -> None:
|
||||||
mode = self.modes_combobox.get()
|
mode = self.modes_combobox.get()
|
||||||
|
@ -333,10 +360,13 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
logger.info("mode config: %s", config)
|
logger.info("mode config: %s", config)
|
||||||
self.config_frame.set_values(config)
|
self.config_frame.set_values(config)
|
||||||
|
|
||||||
def update_template_file_data(self, event: tk.Event) -> None:
|
def update_template_file_data(self, _event: tk.Event) -> None:
|
||||||
scrolledtext = event.widget
|
|
||||||
template = self.templates_combobox.get()
|
template = self.templates_combobox.get()
|
||||||
self.temp_service_files[template] = scrolledtext.get(1.0, "end")
|
self.temp_service_files[template] = self.rendered_text.get_text()
|
||||||
|
if self.rendered[template] != self.temp_service_files[template]:
|
||||||
|
self.modified_files.add(template)
|
||||||
|
return
|
||||||
|
self.temp_service_files[template] = self.template_text.get_text()
|
||||||
if self.temp_service_files[template] != self.original_service_files[template]:
|
if self.temp_service_files[template] != self.original_service_files[template]:
|
||||||
self.modified_files.add(template)
|
self.modified_files.add(template)
|
||||||
else:
|
else:
|
||||||
|
@ -351,14 +381,24 @@ class ConfigServiceConfigDialog(Dialog):
|
||||||
return has_custom_templates or has_custom_config
|
return has_custom_templates or has_custom_config
|
||||||
|
|
||||||
def click_defaults(self) -> None:
|
def click_defaults(self) -> None:
|
||||||
|
# clear all saved state data
|
||||||
|
self.modified_files.clear()
|
||||||
self.node.config_service_configs.pop(self.service_name, None)
|
self.node.config_service_configs.pop(self.service_name, None)
|
||||||
|
self.temp_service_files = dict(self.original_service_files)
|
||||||
|
# reset session definition and retrieve default rendered templates
|
||||||
|
self.core.start_session(definition=True)
|
||||||
|
self.rendered = self.core.get_config_service_rendered(
|
||||||
|
self.node.id, self.service_name
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"cleared config service config: %s", self.node.config_service_configs
|
"cleared config service config: %s", self.node.config_service_configs
|
||||||
)
|
)
|
||||||
self.temp_service_files = dict(self.original_service_files)
|
# reset current selected file data and config data, if present
|
||||||
filename = self.templates_combobox.get()
|
template_name = self.templates_combobox.get()
|
||||||
self.template_text.text.delete(1.0, "end")
|
temp_data = self.temp_service_files[template_name]
|
||||||
self.template_text.text.insert("end", self.temp_service_files[filename])
|
self.template_text.set_text(temp_data)
|
||||||
|
rendered_data = self.rendered[template_name]
|
||||||
|
self.rendered_text.set_text(rendered_data)
|
||||||
if self.config_frame:
|
if self.config_frame:
|
||||||
logger.info("resetting defaults: %s", self.default_config)
|
logger.info("resetting defaults: %s", self.default_config)
|
||||||
self.config_frame.set_values(self.default_config)
|
self.config_frame.set_values(self.default_config)
|
||||||
|
@ -367,7 +407,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, Dict, Optional
|
from typing import TYPE_CHECKING, 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, Set
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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 Services", master=master)
|
super().__init__(app, "Node Config 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.services):
|
for group in sorted(self.app.core.config_services_groups):
|
||||||
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.services[group]):
|
for name in sorted(self.app.core.config_services_groups[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="Services", command=self.click_services)
|
button = ttk.Button(frame, text="Config 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, Dict, List, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -37,11 +37,13 @@ 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)
|
||||||
|
@ -80,7 +82,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 ttk
|
from tkinter import messagebox, 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,6 +91,13 @@ 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, List, Optional
|
from typing import TYPE_CHECKING, 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, Dict, Optional
|
from typing import TYPE_CHECKING, 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, Dict, Optional
|
from typing import TYPE_CHECKING, 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:
|
||||||
|
@ -230,13 +230,8 @@ class NodeConfigDialog(Dialog):
|
||||||
if nutils.is_model(self.node):
|
if nutils.is_model(self.node):
|
||||||
label = ttk.Label(frame, text="Type")
|
label = ttk.Label(frame, text="Type")
|
||||||
label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY)
|
label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY)
|
||||||
combobox = ttk.Combobox(
|
entry = ttk.Entry(frame, textvariable=self.type, state=tk.DISABLED)
|
||||||
frame,
|
entry.grid(row=row, column=1, sticky=tk.EW)
|
||||||
textvariable=self.type,
|
|
||||||
values=list(nutils.NODE_MODELS),
|
|
||||||
state=combo_state,
|
|
||||||
)
|
|
||||||
combobox.grid(row=row, column=1, sticky=tk.EW)
|
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
# container image field
|
# container image field
|
||||||
|
@ -275,7 +270,7 @@ class NodeConfigDialog(Dialog):
|
||||||
ifaces_scroll.listbox.bind("<<ListboxSelect>>", self.iface_select)
|
ifaces_scroll.listbox.bind("<<ListboxSelect>>", self.iface_select)
|
||||||
|
|
||||||
# interfaces
|
# interfaces
|
||||||
if self.canvas_node.ifaces:
|
if nutils.is_container(self.node):
|
||||||
self.draw_ifaces()
|
self.draw_ifaces()
|
||||||
|
|
||||||
self.draw_spacer()
|
self.draw_spacer()
|
||||||
|
|
|
@ -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, Set
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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, Set
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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, Dict, Optional
|
from typing import TYPE_CHECKING, 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, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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, List, Optional
|
from typing import TYPE_CHECKING, 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, List, Optional, Union
|
from typing import TYPE_CHECKING, 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
|
||||||
"""
|
"""
|
||||||
|
|
55
daemon/core/gui/dialogs/wirelessconfig.py
Normal file
55
daemon/core/gui/dialogs/wirelessconfig.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc.wrappers import ConfigOption, Node
|
||||||
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
from core.gui.themes import PADX, PADY
|
||||||
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
|
class WirelessConfigDialog(Dialog):
|
||||||
|
def __init__(self, app: "Application", canvas_node: "CanvasNode"):
|
||||||
|
super().__init__(app, f"Wireless Configuration - {canvas_node.core_node.name}")
|
||||||
|
self.node: Node = canvas_node.core_node
|
||||||
|
self.config_frame: Optional[ConfigFrame] = None
|
||||||
|
self.config: dict[str, ConfigOption] = {}
|
||||||
|
try:
|
||||||
|
config = self.node.wireless_config
|
||||||
|
if not config:
|
||||||
|
config = self.app.core.get_wireless_config(self.node.id)
|
||||||
|
self.config: dict[str, ConfigOption] = config
|
||||||
|
self.draw()
|
||||||
|
except grpc.RpcError as e:
|
||||||
|
self.app.show_grpc_exception("Wireless Config Error", e)
|
||||||
|
self.has_error: bool = True
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def draw(self) -> None:
|
||||||
|
self.top.columnconfigure(0, weight=1)
|
||||||
|
self.top.rowconfigure(0, weight=1)
|
||||||
|
self.config_frame = ConfigFrame(self.top, self.app, self.config)
|
||||||
|
self.config_frame.draw_config()
|
||||||
|
self.config_frame.grid(sticky=tk.NSEW, pady=PADY)
|
||||||
|
self.draw_buttons()
|
||||||
|
|
||||||
|
def draw_buttons(self) -> None:
|
||||||
|
frame = ttk.Frame(self.top)
|
||||||
|
frame.grid(sticky=tk.EW)
|
||||||
|
for i in range(2):
|
||||||
|
frame.columnconfigure(i, weight=1)
|
||||||
|
button = ttk.Button(frame, text="Apply", command=self.click_apply)
|
||||||
|
button.grid(row=0, column=0, padx=PADX, sticky=tk.EW)
|
||||||
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
|
button.grid(row=0, column=1, sticky=tk.EW)
|
||||||
|
|
||||||
|
def click_apply(self) -> None:
|
||||||
|
self.config_frame.parse_config()
|
||||||
|
self.node.wireless_config = self.config
|
||||||
|
self.destroy()
|
|
@ -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, Dict, Optional
|
from typing import TYPE_CHECKING, 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, Tuple, Union
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
|
||||||
from core.api.grpc.wrappers import Interface, Link
|
from core.api.grpc.wrappers import Interface, Link
|
||||||
from core.gui import themes
|
from core.gui import nodeutils, 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)
|
||||||
|
@ -416,6 +416,8 @@ class Edge:
|
||||||
self.src_label2 = None
|
self.src_label2 = None
|
||||||
self.dst_label = None
|
self.dst_label = None
|
||||||
self.dst_label2 = None
|
self.dst_label2 = None
|
||||||
|
if self.dst:
|
||||||
|
self.arc_common_edges()
|
||||||
|
|
||||||
def hide(self) -> None:
|
def hide(self) -> None:
|
||||||
self.hidden = True
|
self.hidden = True
|
||||||
|
@ -507,6 +509,7 @@ class CanvasWirelessEdge(Edge):
|
||||||
if self.src.hidden or self.dst.hidden:
|
if self.src.hidden or self.dst.hidden:
|
||||||
self.hide()
|
self.hide()
|
||||||
self.set_binding()
|
self.set_binding()
|
||||||
|
self.arc_common_edges()
|
||||||
|
|
||||||
def set_binding(self) -> None:
|
def set_binding(self) -> None:
|
||||||
self.src.canvas.tag_bind(self.id, "<Button-1>", self.show_info)
|
self.src.canvas.tag_bind(self.id, "<Button-1>", self.show_info)
|
||||||
|
@ -565,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)
|
||||||
|
@ -635,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:
|
if link.iface1 and not nodeutils.is_rj45(self.src.core_node):
|
||||||
iface1 = link.iface1
|
iface1 = link.iface1
|
||||||
self.src.ifaces[iface1.id] = iface1
|
self.src.ifaces[iface1.id] = iface1
|
||||||
if link.iface2:
|
if link.iface2 and not nodeutils.is_rj45(self.dst.core_node):
|
||||||
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)
|
||||||
|
@ -748,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:
|
if self.link.iface1 and not nodeutils.is_rj45(self.src.core_node):
|
||||||
del self.src.ifaces[self.link.iface1.id]
|
del self.src.ifaces[self.link.iface1.id]
|
||||||
if self.link.iface2:
|
if self.link.iface2 and not nodeutils.is_rj45(self.dst.core_node):
|
||||||
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()
|
||||||
|
@ -758,6 +761,4 @@ class CanvasEdge(Edge):
|
||||||
self.src.delete_antenna()
|
self.src.delete_antenna()
|
||||||
self.app.core.deleted_canvas_edges([self])
|
self.app.core.deleted_canvas_edges([self])
|
||||||
super().delete()
|
super().delete()
|
||||||
if self.dst:
|
|
||||||
self.arc_common_edges()
|
|
||||||
self.manager.edges.pop(self.token, None)
|
self.manager.edges.pop(self.token, None)
|
||||||
|
|
|
@ -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, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
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,9 +1,10 @@
|
||||||
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, Dict, Optional, Tuple, ValuesView
|
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||||
|
|
||||||
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
|
||||||
|
@ -34,7 +35,7 @@ class ShowVar(BooleanVar):
|
||||||
self.manager: "CanvasManager" = manager
|
self.manager: "CanvasManager" = manager
|
||||||
self.tag: str = tag
|
self.tag: str = tag
|
||||||
|
|
||||||
def state(self) -> str:
|
def state(self) -> Literal["normal", "hidden"]:
|
||||||
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:
|
||||||
|
@ -78,14 +79,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,
|
||||||
)
|
)
|
||||||
|
@ -111,8 +112,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()
|
||||||
|
@ -273,17 +274,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)
|
||||||
|
@ -303,7 +304,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:
|
||||||
|
@ -313,7 +314,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:
|
||||||
|
@ -330,7 +331,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, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
@ -16,6 +16,7 @@ from core.gui.dialogs.mobilityconfig import MobilityConfigDialog
|
||||||
from core.gui.dialogs.nodeconfig import NodeConfigDialog
|
from core.gui.dialogs.nodeconfig import NodeConfigDialog
|
||||||
from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
|
from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog
|
||||||
from core.gui.dialogs.nodeservice import NodeServiceDialog
|
from core.gui.dialogs.nodeservice import NodeServiceDialog
|
||||||
|
from core.gui.dialogs.wirelessconfig import WirelessConfigDialog
|
||||||
from core.gui.dialogs.wlanconfig import WlanConfigDialog
|
from core.gui.dialogs.wlanconfig import WlanConfigDialog
|
||||||
from core.gui.frames.node import NodeInfoFrame
|
from core.gui.frames.node import NodeInfoFrame
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
|
@ -61,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:
|
||||||
|
@ -219,6 +220,7 @@ class CanvasNode:
|
||||||
# clear existing menu
|
# clear existing menu
|
||||||
self.context.delete(0, tk.END)
|
self.context.delete(0, tk.END)
|
||||||
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
||||||
|
is_wireless = self.core_node.type == NodeType.WIRELESS
|
||||||
is_emane = self.core_node.type == NodeType.EMANE
|
is_emane = self.core_node.type == NodeType.EMANE
|
||||||
is_mobility = is_wlan or is_emane
|
is_mobility = is_wlan or is_emane
|
||||||
if self.app.core.is_runtime():
|
if self.app.core.is_runtime():
|
||||||
|
@ -231,6 +233,10 @@ class CanvasNode:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="WLAN Config", command=self.show_wlan_config
|
label="WLAN Config", command=self.show_wlan_config
|
||||||
)
|
)
|
||||||
|
if is_wireless:
|
||||||
|
self.context.add_command(
|
||||||
|
label="Wireless Config", command=self.show_wireless_config
|
||||||
|
)
|
||||||
if is_mobility and self.core_node.id in self.app.core.mobility_players:
|
if is_mobility and self.core_node.id in self.app.core.mobility_players:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="Mobility Player", command=self.show_mobility_player
|
label="Mobility Player", command=self.show_mobility_player
|
||||||
|
@ -268,6 +274,10 @@ class CanvasNode:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="WLAN Config", command=self.show_wlan_config
|
label="WLAN Config", command=self.show_wlan_config
|
||||||
)
|
)
|
||||||
|
if is_wireless:
|
||||||
|
self.context.add_command(
|
||||||
|
label="Wireless Config", command=self.show_wireless_config
|
||||||
|
)
|
||||||
if is_mobility:
|
if is_mobility:
|
||||||
self.context.add_command(
|
self.context.add_command(
|
||||||
label="Mobility Config", command=self.show_mobility_config
|
label="Mobility Config", command=self.show_mobility_config
|
||||||
|
@ -298,7 +308,10 @@ class CanvasNode:
|
||||||
other_iface = edge.other_iface(self)
|
other_iface = edge.other_iface(self)
|
||||||
label = other_node.core_node.name
|
label = other_node.core_node.name
|
||||||
if other_iface:
|
if other_iface:
|
||||||
label = f"{label}:{other_iface.name}"
|
iface_label = other_iface.id
|
||||||
|
if other_iface.name:
|
||||||
|
iface_label = other_iface.name
|
||||||
|
label = f"{label}:{iface_label}"
|
||||||
func_unlink = functools.partial(self.click_unlink, edge)
|
func_unlink = functools.partial(self.click_unlink, edge)
|
||||||
unlink_menu.add_command(label=label, command=func_unlink)
|
unlink_menu.add_command(label=label, command=func_unlink)
|
||||||
themes.style_menu(unlink_menu)
|
themes.style_menu(unlink_menu)
|
||||||
|
@ -343,6 +356,10 @@ class CanvasNode:
|
||||||
dialog = NodeConfigDialog(self.app, self)
|
dialog = NodeConfigDialog(self.app, self)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
|
def show_wireless_config(self) -> None:
|
||||||
|
dialog = WirelessConfigDialog(self.app, self)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
def show_wlan_config(self) -> None:
|
def show_wlan_config(self) -> None:
|
||||||
dialog = WlanConfigDialog(self.app, self)
|
dialog = WlanConfigDialog(self.app, self)
|
||||||
if not dialog.has_error:
|
if not dialog.has_error:
|
||||||
|
@ -526,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, Dict, List, Optional, Union
|
from typing import TYPE_CHECKING, Any, 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,5 +1,4 @@
|
||||||
import enum
|
import enum
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
|
|
||||||
class ShapeType(enum.Enum):
|
class ShapeType(enum.Enum):
|
||||||
|
@ -9,7 +8,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,5 +1,3 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
ANNOTATION: str = "annotation"
|
ANNOTATION: str = "annotation"
|
||||||
GRIDLINE: str = "gridline"
|
GRIDLINE: str = "gridline"
|
||||||
SHAPE: str = "shape"
|
SHAPE: str = "shape"
|
||||||
|
@ -15,7 +13,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,
|
||||||
|
@ -29,7 +27,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, Tuple
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
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("+%d+%d" % (x, y))
|
self.tw.wm_geometry(f"+{x:d}+{y:d}")
|
||||||
|
|
||||||
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 Dict, Optional, Tuple
|
from typing import Optional
|
||||||
|
|
||||||
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:
|
||||||
|
@ -53,6 +53,7 @@ class ImageEnum(Enum):
|
||||||
LINK = "link"
|
LINK = "link"
|
||||||
HUB = "hub"
|
HUB = "hub"
|
||||||
WLAN = "wlan"
|
WLAN = "wlan"
|
||||||
|
WIRELESS = "wireless"
|
||||||
EMANE = "emane"
|
EMANE = "emane"
|
||||||
RJ45 = "rj45"
|
RJ45 = "rj45"
|
||||||
TUNNEL = "tunnel"
|
TUNNEL = "tunnel"
|
||||||
|
@ -77,6 +78,7 @@ 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"
|
||||||
|
@ -86,20 +88,22 @@ 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,
|
||||||
(NodeType.DEFAULT, "mdr"): ImageEnum.MDR,
|
(NodeType.DEFAULT, "mdr"): ImageEnum.MDR,
|
||||||
(NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER,
|
(NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER,
|
||||||
(NodeType.HUB, ""): ImageEnum.HUB,
|
(NodeType.HUB, None): ImageEnum.HUB,
|
||||||
(NodeType.SWITCH, ""): ImageEnum.SWITCH,
|
(NodeType.SWITCH, None): ImageEnum.SWITCH,
|
||||||
(NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN,
|
(NodeType.WIRELESS_LAN, None): ImageEnum.WLAN,
|
||||||
(NodeType.EMANE, ""): ImageEnum.EMANE,
|
(NodeType.WIRELESS, None): ImageEnum.WIRELESS,
|
||||||
(NodeType.RJ45, ""): ImageEnum.RJ45,
|
(NodeType.EMANE, None): ImageEnum.EMANE,
|
||||||
(NodeType.TUNNEL, ""): ImageEnum.TUNNEL,
|
(NodeType.RJ45, None): ImageEnum.RJ45,
|
||||||
(NodeType.DOCKER, ""): ImageEnum.DOCKER,
|
(NodeType.TUNNEL, None): ImageEnum.TUNNEL,
|
||||||
(NodeType.LXC, ""): ImageEnum.LXC,
|
(NodeType.DOCKER, None): ImageEnum.DOCKER,
|
||||||
|
(NodeType.PODMAN, None): ImageEnum.PODMAN,
|
||||||
|
(NodeType.LXC, None): ImageEnum.LXC,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
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
|
||||||
|
@ -241,10 +241,10 @@ class InterfaceManager:
|
||||||
dst_node = edge.dst.core_node
|
dst_node = edge.dst.core_node
|
||||||
self.determine_subnets(edge.src, edge.dst)
|
self.determine_subnets(edge.src, edge.dst)
|
||||||
src_iface = None
|
src_iface = None
|
||||||
if nutils.is_container(src_node):
|
if nutils.is_iface_node(src_node):
|
||||||
src_iface = self.create_iface(edge.src, edge.linked_wireless)
|
src_iface = self.create_iface(edge.src, edge.linked_wireless)
|
||||||
dst_iface = None
|
dst_iface = None
|
||||||
if nutils.is_container(dst_node):
|
if nutils.is_iface_node(dst_node):
|
||||||
dst_iface = self.create_iface(edge.dst, edge.linked_wireless)
|
dst_iface = self.create_iface(edge.dst, edge.linked_wireless)
|
||||||
link = Link(
|
link = Link(
|
||||||
type=LinkType.WIRED,
|
type=LinkType.WIRED,
|
||||||
|
@ -258,22 +258,26 @@ class InterfaceManager:
|
||||||
|
|
||||||
def create_iface(self, canvas_node: CanvasNode, wireless_link: bool) -> Interface:
|
def create_iface(self, canvas_node: CanvasNode, wireless_link: bool) -> Interface:
|
||||||
node = canvas_node.core_node
|
node = canvas_node.core_node
|
||||||
ip4, ip6 = self.get_ips(node)
|
if nutils.is_bridge(node):
|
||||||
if wireless_link:
|
iface_id = canvas_node.next_iface_id()
|
||||||
ip4_mask = WIRELESS_IP4_MASK
|
iface = Interface(id=iface_id)
|
||||||
ip6_mask = WIRELESS_IP6_MASK
|
|
||||||
else:
|
else:
|
||||||
ip4_mask = IP4_MASK
|
ip4, ip6 = self.get_ips(node)
|
||||||
ip6_mask = IP6_MASK
|
if wireless_link:
|
||||||
iface_id = canvas_node.next_iface_id()
|
ip4_mask = WIRELESS_IP4_MASK
|
||||||
name = f"eth{iface_id}"
|
ip6_mask = WIRELESS_IP6_MASK
|
||||||
iface = Interface(
|
else:
|
||||||
id=iface_id,
|
ip4_mask = IP4_MASK
|
||||||
name=name,
|
ip6_mask = IP6_MASK
|
||||||
ip4=ip4,
|
iface_id = canvas_node.next_iface_id()
|
||||||
ip4_mask=ip4_mask,
|
name = f"eth{iface_id}"
|
||||||
ip6=ip6,
|
iface = Interface(
|
||||||
ip6_mask=ip6_mask,
|
id=iface_id,
|
||||||
)
|
name=name,
|
||||||
|
ip4=ip4,
|
||||||
|
ip4_mask=ip4_mask,
|
||||||
|
ip6=ip6,
|
||||||
|
ip6_mask=ip6_mask,
|
||||||
|
)
|
||||||
logger.info("create node(%s) interface(%s)", node.name, iface)
|
logger.info("create node(%s) interface(%s)", node.name, iface)
|
||||||
return iface
|
return iface
|
||||||
|
|
|
@ -235,7 +235,11 @@ 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(label="Enable Throughput?", command=self.click_throughput)
|
menu.add_checkbutton(
|
||||||
|
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:
|
||||||
|
@ -393,7 +397,7 @@ class Menubar(tk.Menu):
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def click_throughput(self) -> None:
|
def click_throughput(self) -> None:
|
||||||
if not self.core.handling_throughputs:
|
if self.core.show_throughputs.get():
|
||||||
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, List, Optional, Set
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from PIL.ImageTk import PhotoImage
|
from PIL.ImageTk import PhotoImage
|
||||||
|
|
||||||
|
@ -13,18 +13,27 @@ 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] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
|
CONTAINER_NODES: set[NodeType] = {
|
||||||
IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC}
|
NodeType.DEFAULT,
|
||||||
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
NodeType.DOCKER,
|
||||||
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
|
NodeType.LXC,
|
||||||
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
|
NodeType.PODMAN,
|
||||||
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
|
}
|
||||||
MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
IMAGE_NODES: set[NodeType] = {NodeType.DOCKER, NodeType.LXC, NodeType.PODMAN}
|
||||||
NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"}
|
WIRELESS_NODES: set[NodeType] = {
|
||||||
ROUTER_NODES: Set[str] = {"router", "mdr"}
|
NodeType.WIRELESS_LAN,
|
||||||
|
NodeType.EMANE,
|
||||||
|
NodeType.WIRELESS,
|
||||||
|
}
|
||||||
|
RJ45_NODES: set[NodeType] = {NodeType.RJ45}
|
||||||
|
BRIDGE_NODES: set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
|
||||||
|
IGNORE_NODES: set[NodeType] = {NodeType.CONTROL_NET}
|
||||||
|
MOBILITY_NODES: set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
|
||||||
|
NODE_MODELS: set[str] = {"router", "PC", "mdr", "prouter"}
|
||||||
|
ROUTER_NODES: set[str] = {"router", "mdr"}
|
||||||
ANTENNA_ICON: Optional[PhotoImage] = None
|
ANTENNA_ICON: Optional[PhotoImage] = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +46,7 @@ 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)
|
||||||
|
@ -46,6 +56,7 @@ def setup() -> None:
|
||||||
(ImageEnum.HUB, NodeType.HUB, "Hub"),
|
(ImageEnum.HUB, NodeType.HUB, "Hub"),
|
||||||
(ImageEnum.SWITCH, NodeType.SWITCH, "Switch"),
|
(ImageEnum.SWITCH, NodeType.SWITCH, "Switch"),
|
||||||
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"),
|
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"),
|
||||||
|
(ImageEnum.WIRELESS, NodeType.WIRELESS, "Wireless"),
|
||||||
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
|
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
|
||||||
(ImageEnum.RJ45, NodeType.RJ45, "RJ45"),
|
(ImageEnum.RJ45, NodeType.RJ45, "RJ45"),
|
||||||
(ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"),
|
(ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"),
|
||||||
|
@ -97,7 +108,11 @@ def is_custom(node: Node) -> bool:
|
||||||
return is_model(node) and node.model not in NODE_MODELS
|
return is_model(node) and node.model not in NODE_MODELS
|
||||||
|
|
||||||
|
|
||||||
def get_custom_services(gui_config: GuiConfig, name: str) -> List[str]:
|
def is_iface_node(node: Node) -> bool:
|
||||||
|
return is_container(node) or is_bridge(node)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -114,7 +129,7 @@ def _get_custom_file(config: GuiConfig, name: str) -> Optional[str]:
|
||||||
def get_icon(node: Node, app: "Application") -> PhotoImage:
|
def get_icon(node: Node, app: "Application") -> PhotoImage:
|
||||||
scale = app.app_scale
|
scale = app.app_scale
|
||||||
image = None
|
image = None
|
||||||
# node icon was overriden with a specific value
|
# node icon was overridden with a specific value
|
||||||
if node.icon:
|
if node.icon:
|
||||||
try:
|
try:
|
||||||
image = images.from_file(node.icon, width=images.NODE_SIZE, scale=scale)
|
image = images.from_file(node.icon, width=images.NODE_SIZE, scale=scale)
|
||||||
|
@ -145,7 +160,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, Dict
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
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, List, Optional
|
from typing import TYPE_CHECKING, 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:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue