From 0d2bcccf3e0077d31140bee7c0faa82444e4ee4f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 2 Jun 2020 14:48:57 -0700 Subject: [PATCH 001/210] added initial files to support transitioning to using poetry/invoke to provide an environment for core --- daemon/poetry.lock | 1061 +++++++++++++++++++++++++++++++++++++++++ daemon/pyproject.toml | 33 ++ tasks.py | 14 + 3 files changed, 1108 insertions(+) create mode 100644 daemon/poetry.lock create mode 100644 daemon/pyproject.toml create mode 100644 tasks.py diff --git a/daemon/poetry.lock b/daemon/poetry.lock new file mode 100644 index 000000000..c5e1ebb65 --- /dev/null +++ b/daemon/poetry.lock @@ -0,0 +1,1061 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.4" + +[[package]] +category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + +[[package]] +category = "main" +description = "Modern password hashing for your software and your servers" +name = "bcrypt" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.1.7" + +[package.dependencies] +cffi = ">=1.1" +six = ">=1.4.1" + +[package.extras] +tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)"] + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "19.3b0" + +[package.dependencies] +appdirs = "*" +attrs = ">=18.1.0" +click = ">=6.5" +toml = ">=0.9.4" + +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +name = "cffi" +optional = false +python-versions = "*" +version = "1.14.0" + +[package.dependencies] +pycparser = "*" + +[[package]] +category = "dev" +description = "Validate configuration and produce human readable error messages." +name = "cfgv" +optional = false +python-versions = ">=3.6" +version = "3.0.0" + +[[package]] +category = "dev" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "main" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +name = "cryptography" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "2.9.2" + +[package.dependencies] +cffi = ">=1.8,<1.11.3 || >1.11.3" +six = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +idna = ["idna (>=2.1)"] +pep8test = ["flake8", "flake8-import-order", "pep8-naming"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] + +[[package]] +category = "main" +description = "A backport of the dataclasses module for Python 3.6" +marker = "python_version == \"3.6\"" +name = "dataclasses" +optional = false +python-versions = ">=3.6, <3.7" +version = "0.7" + +[[package]] +category = "dev" +description = "Distribution utilities" +name = "distlib" +optional = false +python-versions = "*" +version = "0.3.0" + +[[package]] +category = "main" +description = "High level SSH command execution" +name = "fabric" +optional = false +python-versions = "*" +version = "2.5.0" + +[package.dependencies] +invoke = ">=1.3,<2.0" +paramiko = ">=2.4" + +[package.extras] +pytest = ["mock (>=2.0.0,<3.0)", "pytest (>=3.2.5,<4.0)"] +testing = ["mock (>=2.0.0,<3.0)"] + +[[package]] +category = "dev" +description = "A platform independent file lock." +name = "filelock" +optional = false +python-versions = "*" +version = "3.0.12" + +[[package]] +category = "dev" +description = "the modular source code checker: pep8 pyflakes and co" +name = "flake8" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "3.8.2" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + +[[package]] +category = "main" +description = "HTTP/2-based RPC framework" +name = "grpcio" +optional = false +python-versions = "*" +version = "1.29.0" + +[package.dependencies] +six = ">=1.5.2" + +[[package]] +category = "dev" +description = "Protobuf code generator for gRPC" +name = "grpcio-tools" +optional = false +python-versions = "*" +version = "1.29.0" + +[package.dependencies] +grpcio = ">=1.29.0" +protobuf = ">=3.5.0.post1" + +[[package]] +category = "dev" +description = "File identification library for Python" +name = "identify" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "1.4.18" + +[package.extras] +license = ["editdistance"] + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.6.0" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + +[[package]] +category = "dev" +description = "Read resources from Python packages" +marker = "python_version < \"3.7\"" +name = "importlib-resources" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.5.0" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + +[package.dependencies.zipp] +python = "<3.8" +version = ">=0.4" + +[package.extras] +docs = ["sphinx", "rst.linker", "jaraco.packaging"] + +[[package]] +category = "main" +description = "Pythonic task execution" +name = "invoke" +optional = false +python-versions = "*" +version = "1.4.1" + +[[package]] +category = "dev" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "4.3.21" + +[package.extras] +pipfile = ["pipreqs", "requirementslib"] +pyproject = ["toml"] +requirements = ["pipreqs", "pip-api"] +xdg_home = ["appdirs (>=1.4.0)"] + +[[package]] +category = "main" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +name = "lxml" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +version = "4.5.1" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["beautifulsoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +category = "main" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +name = "mako" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.3" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "McCabe checker, plugin for flake8" +name = "mccabe" +optional = false +python-versions = "*" +version = "0.6.1" + +[[package]] +category = "dev" +description = "Rolling backport of unittest.mock for all Pythons" +name = "mock" +optional = false +python-versions = ">=3.6" +version = "4.0.2" + +[package.extras] +build = ["twine", "wheel", "blurb"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +python-versions = ">=3.5" +version = "8.3.0" + +[[package]] +category = "main" +description = "A network address manipulation library for Python" +name = "netaddr" +optional = false +python-versions = "*" +version = "0.7.19" + +[[package]] +category = "dev" +description = "Node.js virtual environment builder" +name = "nodeenv" +optional = false +python-versions = "*" +version = "1.4.0" + +[[package]] +category = "dev" +description = "Core utilities for Python packages" +name = "packaging" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.4" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "main" +description = "SSH2 protocol library" +name = "paramiko" +optional = false +python-versions = "*" +version = "2.7.1" + +[package.dependencies] +bcrypt = ">=3.1.3" +cryptography = ">=2.5" +pynacl = ">=1.0.1" + +[package.extras] +all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] +gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=1.3)"] + +[[package]] +category = "main" +description = "Python Imaging Library (Fork)" +name = "pillow" +optional = false +python-versions = ">=3.5" +version = "7.1.2" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "dev" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +name = "pre-commit" +optional = false +python-versions = ">=3.6" +version = "2.1.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=15.2" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + +[package.dependencies.importlib-resources] +python = "<3.7" +version = "*" + +[[package]] +category = "main" +description = "Protocol Buffers" +name = "protobuf" +optional = false +python-versions = "*" +version = "3.12.2" + +[package.dependencies] +setuptools = "*" +six = ">=1.9" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.1" + +[[package]] +category = "dev" +description = "Python style guide checker" +name = "pycodestyle" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" + +[[package]] +category = "main" +description = "C parser in Python" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" + +[[package]] +category = "dev" +description = "passive checker of Python programs" +name = "pyflakes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.2.0" + +[[package]] +category = "main" +description = "Python binding to the Networking and Cryptography (NaCl) library" +name = "pynacl" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" + +[package.dependencies] +cffi = ">=1.4.1" +six = "*" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"] + +[[package]] +category = "dev" +description = "Python parsing module" +name = "pyparsing" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + +[[package]] +category = "main" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +name = "pyproj" +optional = false +python-versions = ">=3.5" +version = "2.6.1.post1" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +python-versions = ">=3.5" +version = "5.4.3" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +checkqa-mypy = ["mypy (v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "main" +description = "YAML parser and emitter for Python" +name = "pyyaml" +optional = false +python-versions = "*" +version = "5.3.1" + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.1" + +[[package]] +category = "dev" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.21" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.0,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12,<2" + +[package.dependencies.importlib-resources] +python = "<3.7" +version = ">=1.0,<2" + +[package.extras] +docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] +testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +category = "dev" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.2.3" + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=3.6" +version = "3.1.0" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["jaraco.itertools", "func-timeout"] + +[metadata] +content-hash = "ff2407f8ca447047101b8e0c8656027d07d2f15e51b3a950f2c2d789f929da6b" +python-versions = "^3.6" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +bcrypt = [ + {file = "bcrypt-3.1.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7"}, + {file = "bcrypt-3.1.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31"}, + {file = "bcrypt-3.1.7-cp27-cp27m-win32.whl", hash = "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161"}, + {file = "bcrypt-3.1.7-cp27-cp27m-win_amd64.whl", hash = "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e"}, + {file = "bcrypt-3.1.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0"}, + {file = "bcrypt-3.1.7-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052"}, + {file = "bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105"}, + {file = "bcrypt-3.1.7-cp34-cp34m-win32.whl", hash = "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de"}, + {file = "bcrypt-3.1.7-cp34-cp34m-win_amd64.whl", hash = "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133"}, + {file = "bcrypt-3.1.7-cp35-cp35m-win32.whl", hash = "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5"}, + {file = "bcrypt-3.1.7-cp35-cp35m-win_amd64.whl", hash = "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09"}, + {file = "bcrypt-3.1.7-cp36-cp36m-win32.whl", hash = "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c"}, + {file = "bcrypt-3.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89"}, + {file = "bcrypt-3.1.7-cp37-cp37m-win32.whl", hash = "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294"}, + {file = "bcrypt-3.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"}, + {file = "bcrypt-3.1.7-cp38-cp38-win32.whl", hash = "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1"}, + {file = "bcrypt-3.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752"}, + {file = "bcrypt-3.1.7.tar.gz", hash = "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42"}, +] +black = [ + {file = "black-19.3b0-py36-none-any.whl", hash = "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf"}, + {file = "black-19.3b0.tar.gz", hash = "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"}, +] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, +] +cfgv = [ + {file = "cfgv-3.0.0-py2.py3-none-any.whl", hash = "sha256:f22b426ed59cd2ab2b54ff96608d846c33dfb8766a67f0b4a6ce130ce244414f"}, + {file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +cryptography = [ + {file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, + {file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"}, + {file = "cryptography-2.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365"}, + {file = "cryptography-2.9.2-cp27-cp27m-win32.whl", hash = "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"}, + {file = "cryptography-2.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55"}, + {file = "cryptography-2.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270"}, + {file = "cryptography-2.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf"}, + {file = "cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d"}, + {file = "cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785"}, + {file = "cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b"}, + {file = "cryptography-2.9.2-cp35-cp35m-win32.whl", hash = "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae"}, + {file = "cryptography-2.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b"}, + {file = "cryptography-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6"}, + {file = "cryptography-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3"}, + {file = "cryptography-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b"}, + {file = "cryptography-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e"}, + {file = "cryptography-2.9.2-cp38-cp38-win32.whl", hash = "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0"}, + {file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, + {file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, +] +dataclasses = [ + {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, + {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, +] +distlib = [ + {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, +] +fabric = [ + {file = "fabric-2.5.0-py2.py3-none-any.whl", hash = "sha256:160331934ea60036604928e792fa8e9f813266b098ef5562aa82b88527740389"}, + {file = "fabric-2.5.0.tar.gz", hash = "sha256:24842d7d51556adcabd885ac3cf5e1df73fc622a1708bf3667bf5927576cdfa6"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"}, + {file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"}, +] +grpcio = [ + {file = "grpcio-1.29.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e90f3d11185c36593186e5ff1f581acc6ddfa4190f145b0366e579de1f52803b"}, + {file = "grpcio-1.29.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5024b26e17a1bfc9390fb3b8077bf886eee02970af780fd23072970ef08cefe8"}, + {file = "grpcio-1.29.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:23bc395a32c2465564cb242e48bdd2fdbe5a4aebf307649a800da1b971ee7f29"}, + {file = "grpcio-1.29.0-cp27-cp27m-win32.whl", hash = "sha256:886d48c32960b39e059494637eb0157a694956248d03b0de814447c188b74799"}, + {file = "grpcio-1.29.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da0ca9b1089d00e39a8b83deec799a4e5c37ec1b44d804495424acde50531868"}, + {file = "grpcio-1.29.0-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:ebf0ccb782027ef9e213e03b6d00bbd8dabd80959db7d468c0738e6d94b5204c"}, + {file = "grpcio-1.29.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2637ce96b7c954d2b71060f50eb4c72f81668f1b2faa6cbdc74677e405978901"}, + {file = "grpcio-1.29.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:75b2247307a7ecaf6abc9eb2bd04af8f88816c111b87bf0044d7924396e9549c"}, + {file = "grpcio-1.29.0-cp35-cp35m-linux_armv7l.whl", hash = "sha256:7bf3cb1e0f4a9c89f7b748583b994bdce183103d89d5ff486da48a7668a052c7"}, + {file = "grpcio-1.29.0-cp35-cp35m-macosx_10_7_intel.whl", hash = "sha256:a6dddb177b3cfa0cfe299fb9e07d6a3382cc79466bef48fe9c4326d5c5b1dcb8"}, + {file = "grpcio-1.29.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:b49f243936b0f6ae8eb6adf88a1e54e736f1c6724a1bff6b591d105d708263ad"}, + {file = "grpcio-1.29.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9cfb4b71cc3c8757f137d47000f9d90d4bd818733f9ab4f78bd447e052a4cb9a"}, + {file = "grpcio-1.29.0-cp35-cp35m-win32.whl", hash = "sha256:10cdc8946a7c2284bbc8e16d346eaa2beeaae86ea598f345df86d4ef7dfedb84"}, + {file = "grpcio-1.29.0-cp35-cp35m-win_amd64.whl", hash = "sha256:806c9759f5589b3761561187408e0313a35c5c53f075c7590effab8d27d67dfe"}, + {file = "grpcio-1.29.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:57c8cc2ae8cb94c3a89671af7e1380a4cdfcd6bab7ba303f4461ec32ded250ae"}, + {file = "grpcio-1.29.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:97b72bf2242a351a89184134adbb0ae3b422e6893c6c712bc7669e2eab21501b"}, + {file = "grpcio-1.29.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:517538a54afdd67162ea2af1ac3326c0752c5d13e6ddadbc4885f6a28e91ab28"}, + {file = "grpcio-1.29.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:eede3039c3998e2cc0f6713f4ac70f235bd32967c9b958a17bf937aceebc12c3"}, + {file = "grpcio-1.29.0-cp36-cp36m-win32.whl", hash = "sha256:54e4658c09084b09cd83a5ea3a8bce78e4031ff1010bb8908c399a22a76a6f08"}, + {file = "grpcio-1.29.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7e02a7c40304eecee203f809a982732bd37fad4e798acad98fe73c66e44ff2db"}, + {file = "grpcio-1.29.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ff7931241351521b8df01d7448800ce0d59364321d8d82c49b826d455678ff08"}, + {file = "grpcio-1.29.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:5fd9ffe938e9225c654c60eb21ff011108cc27302db85200413807e0eda99a4a"}, + {file = "grpcio-1.29.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:9ef0370bcf629ece4e7e37796e4604e2514b920669be2911fc3f9c163a73a57b"}, + {file = "grpcio-1.29.0-cp37-cp37m-win32.whl", hash = "sha256:3d8c510b6eabce5192ce126003d74d7751c7218d3e2ad39fcf02400d7ec43abe"}, + {file = "grpcio-1.29.0-cp37-cp37m-win_amd64.whl", hash = "sha256:81bbf78a399e0ee516c81ddad8601f12af3fc9b30f2e4b2fbd64efd327304a4d"}, + {file = "grpcio-1.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:80e9f9f6265149ca7c84e1c8c31c2cf3e2869c45776fbe8880a3133a11d6d290"}, + {file = "grpcio-1.29.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:524ae8d3da61b856cf08abb3d0947df05402919e4be1f88328e0c1004031f72e"}, + {file = "grpcio-1.29.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c3a0ef12ee86f6e72db50e01c3dba7735a76d8c30104b9b0f7fd9d65ceb9d93f"}, + {file = "grpcio-1.29.0-cp38-cp38-win32.whl", hash = "sha256:97fcbdf1f12e0079d26db73da11ee35a09adc870b1e72fbff0211f6a8003a4e8"}, + {file = "grpcio-1.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:b85f355fc24b68a6c52f2750e7141110d1fcd07dfdc9b282de0000550fe0511b"}, + {file = "grpcio-1.29.0.tar.gz", hash = "sha256:a97ea91e31863c9a3879684b5fb3c6ab4b17c5431787548fc9f52b9483ea9c25"}, +] +grpcio-tools = [ + {file = "grpcio-tools-1.29.0.tar.gz", hash = "sha256:0f681c1ebd5472b804baa391b16dc59d92b065903999566f4776bfbd010bcec9"}, + {file = "grpcio_tools-1.29.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b504e844e6f3610f279e0fba719052a73d5acc858a82d5a1151155b3c2304478"}, + {file = "grpcio_tools-1.29.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c52bcc2e5e9d93b805e6f292e543cbabeb9a751dc9d4d451c39d4c30ee311142"}, + {file = "grpcio_tools-1.29.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5beffd530b496866b8e8dc811e942815a6e637669350c1341b5972bb692465cc"}, + {file = "grpcio_tools-1.29.0-cp27-cp27m-win32.whl", hash = "sha256:49dcf4c11ba2766d065c90a61eb1cefc55d5d094f93c1f66a4d98bfcbc5f740c"}, + {file = "grpcio_tools-1.29.0-cp27-cp27m-win_amd64.whl", hash = "sha256:bab2a3d627f114091a758d8a7ae48af54bff717f84bb34538fed5114982e73a5"}, + {file = "grpcio_tools-1.29.0-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:2a1f27a21d09e864cdfcff22265af86d9a548ea9a775e5d6a27d7abb71c3b5aa"}, + {file = "grpcio_tools-1.29.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:56aade8ed52a6cca74a4703279aaae4aa2e2b87d0ccb5778f95d31267e74fc6b"}, + {file = "grpcio_tools-1.29.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78075ee7459001cf5c81b1f2e3f047b63d35ed018b9e15e3abeda59b70af0a4e"}, + {file = "grpcio_tools-1.29.0-cp35-cp35m-linux_armv7l.whl", hash = "sha256:1626cd01a484f29cc9b33c3902851490149d40a550b92a382978571ca7e712cf"}, + {file = "grpcio_tools-1.29.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2f1d80e3988d86477633fb39442a2310513d02fcc48881b359257a4be3cfd336"}, + {file = "grpcio_tools-1.29.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:8ffdcb1cbbc1bdfe249eb08c9fc6557b4f83b9f6145b5914bfd2973013d6dc1f"}, + {file = "grpcio_tools-1.29.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:7e52c8ed5e0157ff85493f93540e3c897c7d97be03afc73230d1022ba7b80528"}, + {file = "grpcio_tools-1.29.0-cp35-cp35m-win32.whl", hash = "sha256:f464d2efe04a46a17cf9493d67e6839aa535bb8a904cc6a2b588f1b156c9265d"}, + {file = "grpcio_tools-1.29.0-cp35-cp35m-win_amd64.whl", hash = "sha256:9de112c090ab67e90b8c36eee5876278c8d037bf7c55052848886c1e8a2dd1c2"}, + {file = "grpcio_tools-1.29.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:38ab9e8afdf34289eab85ce2343c451c36837bf2521b927b30d9a845304abf4c"}, + {file = "grpcio_tools-1.29.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1038b3d6cfd7206caf7c0a54ed06896e2aeb0a7d213a40d9000a70595e2fca21"}, + {file = "grpcio_tools-1.29.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:2a681ebfde0d83b70117cac745a97a3e5dc258fd817c1c1dd2bf99579b663a28"}, + {file = "grpcio_tools-1.29.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:47d13ddbbc2bd0e21a6109f74e731049b1d8738b5d0124580efca3721fe77fd2"}, + {file = "grpcio_tools-1.29.0-cp36-cp36m-win32.whl", hash = "sha256:fb9c46b8a0ee1a5990f29d891d6023cb92fdab9aed408194667df04f72e9caf6"}, + {file = "grpcio_tools-1.29.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f672a606a59145bacc58cf4c4bb407f107abe1289f607c09e9224c99e897ed1a"}, + {file = "grpcio_tools-1.29.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1a606f2f5b23822e2e5271bf0df98c140ceed154ea6bf5c04ea85a37a0317771"}, + {file = "grpcio_tools-1.29.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d89a43d14fb3043c1876e78d7ad5018c762b0ce51c199c588fa9142442546005"}, + {file = "grpcio_tools-1.29.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:faf845f71fcb6cb5088429c676ae644116d56e5de41c639be4d7399bf71b9637"}, + {file = "grpcio_tools-1.29.0-cp37-cp37m-win32.whl", hash = "sha256:05f214bc904c8e4ebf0240993a868895ff96184172243c0c61b323f6f029863d"}, + {file = "grpcio_tools-1.29.0-cp37-cp37m-win_amd64.whl", hash = "sha256:afcb030067ba1b6c371a7bfd1ffd77375534144000d47d245ca77ebbd195901d"}, + {file = "grpcio_tools-1.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b55346fa75df4b1581627022a2c79cfeb58cdaebf719cdbf63ff8ae6d7d7704b"}, + {file = "grpcio_tools-1.29.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:22d91ceb853f6846bcc23f15d8a936574eeb9fc7e8941bb8a1a5f8fcf4f566b2"}, + {file = "grpcio_tools-1.29.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6eddefcd10f261d2aef6c122fb0651a53fcaee86e47d407492c9acf57107c91a"}, + {file = "grpcio_tools-1.29.0-cp38-cp38-win32.whl", hash = "sha256:658e131e983f4c3bec2e096c3cc048e6420acad2b19fad82328c481088ce0d1a"}, + {file = "grpcio_tools-1.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c52f68e864f60ed51ea59a3fd18d0989720bbf2e32d47b4096eba7b0b7f7086"}, +] +identify = [ + {file = "identify-1.4.18-py2.py3-none-any.whl", hash = "sha256:9f53e80371f2ac7c969eefda8efaabd4f77c6300f5f8fc4b634744a0db8fe5cc"}, + {file = "identify-1.4.18.tar.gz", hash = "sha256:de4e1de6c23f52b71c8a54ff558219f3783ff011b432f29360d84a8a31ba561c"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, + {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, +] +importlib-resources = [ + {file = "importlib_resources-1.5.0-py2.py3-none-any.whl", hash = "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49"}, + {file = "importlib_resources-1.5.0.tar.gz", hash = "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca"}, +] +invoke = [ + {file = "invoke-1.4.1-py2-none-any.whl", hash = "sha256:93e12876d88130c8e0d7fd6618dd5387d6b36da55ad541481dfa5e001656f134"}, + {file = "invoke-1.4.1-py3-none-any.whl", hash = "sha256:87b3ef9d72a1667e104f89b159eaf8a514dbf2f3576885b2bbdefe74c3fb2132"}, + {file = "invoke-1.4.1.tar.gz", hash = "sha256:de3f23bfe669e3db1085789fd859eb8ca8e0c5d9c20811e2407fa042e8a5e15d"}, +] +isort = [ + {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, + {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, +] +lxml = [ + {file = "lxml-4.5.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ee2be8b8f72a2772e72ab926a3bccebf47bb727bda41ae070dc91d1fb759b726"}, + {file = "lxml-4.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:fadd2a63a2bfd7fb604508e553d1cf68eca250b2fbdbd81213b5f6f2fbf23529"}, + {file = "lxml-4.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4f282737d187ae723b2633856085c31ae5d4d432968b7f3f478a48a54835f5c4"}, + {file = "lxml-4.5.1-cp27-cp27m-win32.whl", hash = "sha256:7fd88cb91a470b383aafad554c3fe1ccf6dfb2456ff0e84b95335d582a799804"}, + {file = "lxml-4.5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:0790ddca3f825dd914978c94c2545dbea5f56f008b050e835403714babe62a5f"}, + {file = "lxml-4.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9144ce36ca0824b29ebc2e02ca186e54040ebb224292072250467190fb613b96"}, + {file = "lxml-4.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a636346c6c0e1092ffc202d97ec1843a75937d8c98aaf6771348ad6422e44bb0"}, + {file = "lxml-4.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f95d28193c3863132b1f55c1056036bf580b5a488d908f7d22a04ace8935a3a9"}, + {file = "lxml-4.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b26719890c79a1dae7d53acac5f089d66fd8cc68a81f4e4bd355e45470dc25e1"}, + {file = "lxml-4.5.1-cp35-cp35m-win32.whl", hash = "sha256:a9e3b8011388e7e373565daa5e92f6c9cb844790dc18e43073212bb3e76f7007"}, + {file = "lxml-4.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:2754d4406438c83144f9ffd3628bbe2dcc6d62b20dbc5c1ec4bc4385e5d44b42"}, + {file = "lxml-4.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:925baf6ff1ef2c45169f548cc85204433e061360bfa7d01e1be7ae38bef73194"}, + {file = "lxml-4.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a87dbee7ad9dce3aaefada2081843caf08a44a8f52e03e0a4cc5819f8398f2f4"}, + {file = "lxml-4.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:51bb4edeb36d24ec97eb3e6a6007be128b720114f9a875d6b370317d62ac80b9"}, + {file = "lxml-4.5.1-cp36-cp36m-win32.whl", hash = "sha256:c79e5debbe092e3c93ca4aee44c9a7631bdd407b2871cb541b979fd350bbbc29"}, + {file = "lxml-4.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b7462cdab6fffcda853338e1741ce99706cdf880d921b5a769202ea7b94e8528"}, + {file = "lxml-4.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06748c7192eab0f48e3d35a7adae609a329c6257495d5e53878003660dc0fec6"}, + {file = "lxml-4.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1aa7a6197c1cdd65d974f3e4953764eee3d9c7b67e3966616b41fab7f8f516b7"}, + {file = "lxml-4.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:afb53edf1046599991fb4a7d03e601ab5f5422a5435c47ee6ba91ec3b61416a6"}, + {file = "lxml-4.5.1-cp37-cp37m-win32.whl", hash = "sha256:2d1ddce96cf15f1254a68dba6935e6e0f1fe39247de631c115e84dd404a6f031"}, + {file = "lxml-4.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:22c6d34fdb0e65d5f782a4d1a1edb52e0a8365858dafb1c08cb1d16546cf0786"}, + {file = "lxml-4.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c47a8a5d00060122ca5908909478abce7bbf62d812e3fc35c6c802df8fb01fe7"}, + {file = "lxml-4.5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b77975465234ff49fdad871c08aa747aae06f5e5be62866595057c43f8d2f62c"}, + {file = "lxml-4.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2b02c106709466a93ed424454ce4c970791c486d5fcdf52b0d822a7e29789626"}, + {file = "lxml-4.5.1-cp38-cp38-win32.whl", hash = "sha256:7eee37c1b9815e6505847aa5e68f192e8a1b730c5c7ead39ff317fde9ce29448"}, + {file = "lxml-4.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:d8d40e0121ca1606aa9e78c28a3a7d88a05c06b3ca61630242cded87d8ce55fa"}, + {file = "lxml-4.5.1.tar.gz", hash = "sha256:27ee0faf8077c7c1a589573b1450743011117f1aa1a91d5ae776bbc5ca6070f2"}, +] +mako = [ + {file = "Mako-1.1.3-py2.py3-none-any.whl", hash = "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"}, + {file = "Mako-1.1.3.tar.gz", hash = "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mock = [ + {file = "mock-4.0.2-py3-none-any.whl", hash = "sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0"}, + {file = "mock-4.0.2.tar.gz", hash = "sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72"}, +] +more-itertools = [ + {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, + {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, +] +netaddr = [ + {file = "netaddr-0.7.19-py2.py3-none-any.whl", hash = "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca"}, + {file = "netaddr-0.7.19.tar.gz", hash = "sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd"}, +] +nodeenv = [ + {file = "nodeenv-1.4.0-py2.py3-none-any.whl", hash = "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"}, +] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] +paramiko = [ + {file = "paramiko-2.7.1-py2.py3-none-any.whl", hash = "sha256:9c980875fa4d2cb751604664e9a2d0f69096643f5be4db1b99599fe114a97b2f"}, + {file = "paramiko-2.7.1.tar.gz", hash = "sha256:920492895db8013f6cc0179293147f830b8c7b21fdfc839b6bad760c27459d9f"}, +] +pillow = [ + {file = "Pillow-7.1.2-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3"}, + {file = "Pillow-7.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:d23e2aa9b969cf9c26edfb4b56307792b8b374202810bd949effd1c6e11ebd6d"}, + {file = "Pillow-7.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b532bcc2f008e96fd9241177ec580829dee817b090532f43e54074ecffdcd97f"}, + {file = "Pillow-7.1.2-cp35-cp35m-win32.whl", hash = "sha256:12e4bad6bddd8546a2f9771485c7e3d2b546b458ae8ff79621214119ac244523"}, + {file = "Pillow-7.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9744350687459234867cbebfe9df8f35ef9e1538f3e729adbd8fde0761adb705"}, + {file = "Pillow-7.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:f54be399340aa602066adb63a86a6a5d4f395adfdd9da2b9a0162ea808c7b276"}, + {file = "Pillow-7.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1f694e28c169655c50bb89a3fa07f3b854d71eb47f50783621de813979ba87f3"}, + {file = "Pillow-7.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f784aad988f12c80aacfa5b381ec21fd3f38f851720f652b9f33facc5101cf4d"}, + {file = "Pillow-7.1.2-cp36-cp36m-win32.whl", hash = "sha256:b37bb3bd35edf53125b0ff257822afa6962649995cbdfde2791ddb62b239f891"}, + {file = "Pillow-7.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:b67a6c47ed963c709ed24566daa3f95a18f07d3831334da570c71da53d97d088"}, + {file = "Pillow-7.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:eaa83729eab9c60884f362ada982d3a06beaa6cc8b084cf9f76cae7739481dfa"}, + {file = "Pillow-7.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f46e0e024346e1474083c729d50de909974237c72daca05393ee32389dabe457"}, + {file = "Pillow-7.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0e2a3bceb0fd4e0cb17192ae506d5f082b309ffe5fc370a5667959c9b2f85fa3"}, + {file = "Pillow-7.1.2-cp37-cp37m-win32.whl", hash = "sha256:ccc9ad2460eb5bee5642eaf75a0438d7f8887d484490d5117b98edd7f33118b7"}, + {file = "Pillow-7.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b943e71c2065ade6fef223358e56c167fc6ce31c50bc7a02dd5c17ee4338e8ac"}, + {file = "Pillow-7.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:04766c4930c174b46fd72d450674612ab44cca977ebbcc2dde722c6933290107"}, + {file = "Pillow-7.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f455efb7a98557412dc6f8e463c1faf1f1911ec2432059fa3e582b6000fc90e2"}, + {file = "Pillow-7.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ee94fce8d003ac9fd206496f2707efe9eadcb278d94c271f129ab36aa7181344"}, + {file = "Pillow-7.1.2-cp38-cp38-win32.whl", hash = "sha256:4b02b9c27fad2054932e89f39703646d0c543f21d3cc5b8e05434215121c28cd"}, + {file = "Pillow-7.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:3d25dd8d688f7318dca6d8cd4f962a360ee40346c15893ae3b95c061cdbc4079"}, + {file = "Pillow-7.1.2-pp373-pypy36_pp73-win32.whl", hash = "sha256:0f01e63c34f0e1e2580cc0b24e86a5ccbbfa8830909a52ee17624c4193224cd9"}, + {file = "Pillow-7.1.2-py3.8-macosx-10.9-x86_64.egg", hash = "sha256:70e3e0d99a0dcda66283a185f80697a9b08806963c6149c8e6c5f452b2aa59c0"}, + {file = "Pillow-7.1.2.tar.gz", hash = "sha256:a0b49960110bc6ff5fead46013bcb8825d101026d466f3a4de3476defe0fb0dd"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +pre-commit = [ + {file = "pre_commit-2.1.1-py2.py3-none-any.whl", hash = "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6"}, + {file = "pre_commit-2.1.1.tar.gz", hash = "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb"}, +] +protobuf = [ + {file = "protobuf-3.12.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e1464a4a2cf12f58f662c8e6421772c07947266293fb701cb39cd9c1e183f63c"}, + {file = "protobuf-3.12.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6f349adabf1c004aba53f7b4633459f8ca8a09654bf7e69b509c95a454755776"}, + {file = "protobuf-3.12.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:be04fe14ceed7f8641e30f36077c1a654ff6f17d0c7a5283b699d057d150d82a"}, + {file = "protobuf-3.12.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f4b73736108a416c76c17a8a09bc73af3d91edaa26c682aaa460ef91a47168d3"}, + {file = "protobuf-3.12.2-cp35-cp35m-win32.whl", hash = "sha256:5524c7020eb1fb7319472cb75c4c3206ef18b34d6034d2ee420a60f99cddeb07"}, + {file = "protobuf-3.12.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bff02030bab8b969f4de597543e55bd05e968567acb25c0a87495a31eb09e925"}, + {file = "protobuf-3.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c9ca9f76805e5a637605f171f6c4772fc4a81eced4e2f708f79c75166a2c99ea"}, + {file = "protobuf-3.12.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:304e08440c4a41a0f3592d2a38934aad6919d692bb0edfb355548786728f9a5e"}, + {file = "protobuf-3.12.2-cp36-cp36m-win32.whl", hash = "sha256:b5a114ea9b7fc90c2cc4867a866512672a47f66b154c6d7ee7e48ddb68b68122"}, + {file = "protobuf-3.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:85b94d2653b0fdf6d879e39d51018bf5ccd86c81c04e18a98e9888694b98226f"}, + {file = "protobuf-3.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7ab28a8f1f043c58d157bceb64f80e4d2f7f1b934bc7ff5e7f7a55a337ea8b0"}, + {file = "protobuf-3.12.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eafe9fa19fcefef424ee089fb01ac7177ff3691af7cc2ae8791ae523eb6ca907"}, + {file = "protobuf-3.12.2-cp37-cp37m-win32.whl", hash = "sha256:612bc97e42b22af10ba25e4140963fbaa4c5181487d163f4eb55b0b15b3dfcd2"}, + {file = "protobuf-3.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e72736dd822748b0721f41f9aaaf6a5b6d5cfc78f6c8690263aef8bba4457f0e"}, + {file = "protobuf-3.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:87535dc2d2ef007b9d44e309d2b8ea27a03d2fa09556a72364d706fcb7090828"}, + {file = "protobuf-3.12.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:50b5fee674878b14baea73b4568dc478c46a31dd50157a5b5d2f71138243b1a9"}, + {file = "protobuf-3.12.2-py2.py3-none-any.whl", hash = "sha256:a96f8fc625e9ff568838e556f6f6ae8eca8b4837cdfb3f90efcb7c00e342a2eb"}, + {file = "protobuf-3.12.2.tar.gz", hash = "sha256:49ef8ab4c27812a89a76fa894fe7a08f42f2147078392c0dee51d4a444ef6df5"}, +] +py = [ + {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, + {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pynacl = [ + {file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"}, + {file = "PyNaCl-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7"}, + {file = "PyNaCl-1.4.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122"}, + {file = "PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d"}, + {file = "PyNaCl-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4"}, + {file = "PyNaCl-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25"}, + {file = "PyNaCl-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4"}, + {file = "PyNaCl-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6"}, + {file = "PyNaCl-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f"}, + {file = "PyNaCl-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f"}, + {file = "PyNaCl-1.4.0-cp38-cp38-win32.whl", hash = "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96"}, + {file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"}, + {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pyproj = [ + {file = "pyproj-2.6.1.post1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:457ad3856014ac26af1d86def6dc8cf69c1fa377b6e2fd6e97912d51cf66bdbe"}, + {file = "pyproj-2.6.1.post1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6f3f36440ea61f5f6da4e6beb365dddcbe159815450001d9fb753545affa45ff"}, + {file = "pyproj-2.6.1.post1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6a212d0e5c7efa33d039f0c8b0a489e2204fcd28b56206567852ad7f5f2a653e"}, + {file = "pyproj-2.6.1.post1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:451a3d1c563b672458029ebc04acbb3266cd8b3025268eb871a9176dc3638911"}, + {file = "pyproj-2.6.1.post1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e015f900b4b84e908f8035ab16ebf02d67389c1c216c17a2196fc2e515c00762"}, + {file = "pyproj-2.6.1.post1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a13e5731b3a360ee7fbd1e9199ec9203fafcece8ebd0b1351f16d0a90cad6828"}, + {file = "pyproj-2.6.1.post1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:33c1c2968a4f4f87d517c4275a18b557e5c13907cf2609371fadea8463c3ba05"}, + {file = "pyproj-2.6.1.post1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3fef83a01c1e86dd9fa99d8214f749837cfafc34d9d6230b4b0a998fa7a68a1a"}, + {file = "pyproj-2.6.1.post1-cp36-cp36m-win32.whl", hash = "sha256:a6ac4861979cd05a0f5400fefa41d26c0269a5fb8237618aef7c998907db39e1"}, + {file = "pyproj-2.6.1.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:cbf6ccf990860b06c5262ff97c4b78e1d07883981635cd53a6aa438a68d92945"}, + {file = "pyproj-2.6.1.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:adacb67a9f71fb54ca1b887a6ab20f32dd536fcdf2acec84a19e25ad768f7965"}, + {file = "pyproj-2.6.1.post1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e50d5d20b87758acf8f13f39a3b3eb21d5ef32339d2bc8cdeb8092416e0051df"}, + {file = "pyproj-2.6.1.post1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2518d1606e2229b82318e704b40290e02a2a52d77b40cdcb2978973d6fc27b20"}, + {file = "pyproj-2.6.1.post1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:33a5d1cfbb40a019422eb80709a0e270704390ecde7278fdc0b88f3647c56a39"}, + {file = "pyproj-2.6.1.post1-cp37-cp37m-win32.whl", hash = "sha256:daf2998e3f5bcdd579a18faf009f37f53538e9b7d0a252581a610297d31e8536"}, + {file = "pyproj-2.6.1.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:a8b7c8accdc61dac8e91acab7c1f7b4590d1e102f2ee9b1f1e6399fad225958e"}, + {file = "pyproj-2.6.1.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f097e8f341a162438918e908be86d105a28194ff6224633b2e9616c5031153f"}, + {file = "pyproj-2.6.1.post1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d90a5d1fdd066b0e9b22409b0f5e81933469918fa04c2cf7f9a76ce84cb29dad"}, + {file = "pyproj-2.6.1.post1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f5a8015c74ec8f6508aebf493b58ba20ccb4da8168bf05f0c2a37faccb518da9"}, + {file = "pyproj-2.6.1.post1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d87836be6b720fb4d9c112136aa47621b6ca09a554e645c1081561eb8e2fa1f4"}, + {file = "pyproj-2.6.1.post1-cp38-cp38-win32.whl", hash = "sha256:bc2f3a15d065e206d63edd2cc4739aa0a35c05338ee276ab1dc72f56f1944bda"}, + {file = "pyproj-2.6.1.post1-cp38-cp38-win_amd64.whl", hash = "sha256:93cbad7b699e8e80def7de80c350617f35e6a0b82862f8ce3c014657c25fdb3c"}, + {file = "pyproj-2.6.1.post1.tar.gz", hash = "sha256:4f5b02b4abbd41610397c635b275a8ee4a2b5bc72a75572b98ac6ae7befa471e"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +pyyaml = [ + {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, + {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, + {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, + {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, + {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +toml = [ + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, +] +virtualenv = [ + {file = "virtualenv-20.0.21-py2.py3-none-any.whl", hash = "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"}, + {file = "virtualenv-20.0.21.tar.gz", hash = "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf"}, +] +wcwidth = [ + {file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"}, + {file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"}, +] +zipp = [ + {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, + {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, +] diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml new file mode 100644 index 000000000..6df5f10e8 --- /dev/null +++ b/daemon/pyproject.toml @@ -0,0 +1,33 @@ +[tool.poetry] +name = "core" +version = "6.4.0" +description = "" +authors = [] + +[tool.poetry.dependencies] +python = "^3.6" +dataclasses = { version = "*", python = "3.6" } +fabric = "*" +grpcio = "*" +invoke = "*" +lxml = "*" +mako = "*" +netaddr = "*" +pillow = "*" +protobuf = "*" +pyproj = "*" +pyyaml = "*" + +[tool.poetry.dev-dependencies] +black = "==19.3b0" +flake8 = "*" +grpcio-tools = "*" +isort = "*" +mock = "*" +pre-commit = "*" +pytest = "*" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" + diff --git a/tasks.py b/tasks.py new file mode 100644 index 000000000..74e6af76c --- /dev/null +++ b/tasks.py @@ -0,0 +1,14 @@ +from invoke import task + + +@task +def core(c): + c.run( + "poetry run sudo python3 scripts/core-daemon " + "-f data/core.conf -l data/logging.conf" + ) + + +@task +def core_pygui(c): + c.run("poetry run python3 scripts/core-pygui") From ccf2646c00fd5641137d20f578ab448e97e99ee9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 13:59:29 -0700 Subject: [PATCH 002/210] daemon: refactored add_link,update_link,delete_link to have more specific logic, refactored CoreNodeBase to have newnetif and for it to return the interface created --- daemon/core/api/tlv/corehandlers.py | 2 +- daemon/core/emulator/session.py | 512 ++++++++++------------------ daemon/core/nodes/base.py | 37 +- daemon/core/nodes/network.py | 8 +- daemon/core/nodes/physical.py | 10 +- daemon/tests/test_nodes.py | 20 +- 6 files changed, 233 insertions(+), 356 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index f3e1fbaaf..5531e5af0 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -767,7 +767,7 @@ def handle_link_message(self, message): ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value), ) - link_type = None + link_type = LinkTypes.WIRED link_type_value = message.get_tlv(LinkTlvs.TYPE.value) if link_type_value is not None: link_type = LinkTypes(link_type_value) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 45c17743d..54486bfb5 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -12,7 +12,7 @@ import tempfile import threading import time -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar from core import constants, utils from core.configservice.manager import ConfigServiceManager @@ -193,76 +193,28 @@ def get_node_type(cls, _class: Type[NodeBase]) -> NodeTypes: raise CoreError(f"invalid node class: {_class}") return node_type - def _link_nodes( - self, node_one_id: int, node_two_id: int - ) -> Tuple[ - Optional[CoreNode], - Optional[CoreNode], - Optional[CoreNetworkBase], - Optional[CoreNetworkBase], - ]: - """ - Convenience method for retrieving nodes within link data. - - :param node_one_id: node one id - :param node_two_id: node two id - :return: nodes, network nodes if present, and tunnel if present - """ - logging.debug( - "link message between node1(%s) and node2(%s)", node_one_id, node_two_id - ) - - # values to fill - net_one = None - net_two = None - - # retrieve node one - node_one = self.get_node(node_one_id, NodeBase) - node_two = self.get_node(node_two_id, NodeBase) - - if isinstance(node_one, CoreNetworkBase): - if not net_one: - net_one = node_one - else: - net_two = node_one - node_one = None - - if isinstance(node_two, CoreNetworkBase): - if not net_one: - net_one = node_two - else: - net_two = node_two - node_two = None - - logging.debug( - "link node types n1(%s) n2(%s) net1(%s) net2(%s)", - node_one, - node_two, - net_one, - net_two, - ) - return node_one, node_two, net_one, net_two - - def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None: + def _link_wireless( + self, node_one: CoreNodeBase, node_two: CoreNodeBase, connect: bool + ) -> None: """ Objects to deal with when connecting/disconnecting wireless links. - :param objects: possible objects to deal with + :param node_one: node one for wireless link + :param node_two: node two for wireless link :param connect: link interfaces if True, unlink otherwise :return: nothing :raises core.CoreError: when objects to link is less than 2, or no common networks are found """ - objects = [x for x in objects if x] - if len(objects) < 2: - raise CoreError(f"wireless link failure: {objects}") - logging.debug( - "handling wireless linking objects(%s) connect(%s)", objects, connect + logging.info( + "handling wireless linking node1(%s) node2(%s): %s", + node_one.name, + node_two.name, + connect, ) - common_networks = objects[0].commonnets(objects[1]) + common_networks = node_one.commonnets(node_one) if not common_networks: raise CoreError("no common network found for wireless link/unlink") - for common_network, interface_one, interface_two in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): logging.info( @@ -270,13 +222,6 @@ def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None common_network, ) continue - - logging.info( - "wireless linking connect(%s): %s - %s", - connect, - interface_one, - interface_two, - ) if connect: common_network.link(interface_one, interface_two) else: @@ -305,105 +250,70 @@ def add_link( """ if not options: options = LinkOptions() - - # get node objects identified by link data - node_one, node_two, net_one, net_two = self._link_nodes( - node_one_id, node_two_id - ) - - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - node_one_interface = None - node_two_interface = None - - try: - # wireless link - if options.type == LinkTypes.WIRELESS: - objects = [node_one, node_two, net_one, net_two] - self._link_wireless(objects, connect=True) - # wired link + node1 = self.get_node(node_one_id, NodeBase) + node2 = self.get_node(node_two_id, NodeBase) + node1_interface = None + node2_interface = None + + # wireless link + if options.type == LinkTypes.WIRELESS: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + self._link_wireless(node1, node2, connect=True) else: - # 2 nodes being linked, ptp network - if all([node_one, node_two]) and not net_one: - logging.info( - "adding link for peer to peer nodes: %s - %s", - node_one.name, - node_two.name, - ) - start = self.state.should_start() - net_one = self.create_node(PtpNet, start=start) - - # node to network - if node_one and net_one: - logging.info( - "adding link from node to network: %s - %s", - node_one.name, - net_one.name, - ) - ifindex = node_one.newnetif(net_one, interface_one) - node_one_interface = node_one.netif(ifindex) - wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) - if not wireless_net: - net_one.linkconfig(node_one_interface, options) - - # network to node - if node_two and net_one: - logging.info( - "adding link from network to node: %s - %s", - node_two.name, - net_one.name, - ) - ifindex = node_two.newnetif(net_one, interface_two) - node_two_interface = node_two.netif(ifindex) - wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) - if not options.unidirectional and not wireless_net: - net_one.linkconfig(node_two_interface, options) - - # network to network - if net_one and net_two: - logging.info( - "adding link from network to network: %s - %s", - net_one.name, - net_two.name, - ) - interface = net_one.linknet(net_two) - node_one_interface = interface - net_one.linkconfig(interface, options) - if not options.unidirectional: - interface.swapparams("_params_up") - net_two.linkconfig(interface, options) - interface.swapparams("_params_up") - - # a tunnel node was found for the nodes - addresses = [] - if not node_one and all([net_one, interface_one]): - addresses.extend(interface_one.get_addresses()) - if not node_two and all([net_two, interface_two]): - addresses.extend(interface_two.get_addresses()) - - # tunnel node logic - key = options.key - if key and isinstance(net_one, TunnelNode): - logging.info("setting tunnel key for: %s", net_one.name) - net_one.setkey(key) - if addresses: - net_one.addrconfig(addresses) - if key and isinstance(net_two, TunnelNode): - logging.info("setting tunnel key for: %s", net_two.name) - net_two.setkey(key) - if addresses: - net_two.addrconfig(addresses) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() + raise CoreError( + f"cannot wireless link node1({type(node1)}) node2({type(node2)})" + ) + # wired link + else: + # peer to peer link + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + logging.info("linking ptp: %s - %s", node1.name, node2.name) + start = self.state.should_start() + ptp = self.create_node(PtpNet, start=start) + node1_interface = node1.newnetif(ptp, interface_one) + node2_interface = node2.newnetif(ptp, interface_two) + ptp.linkconfig(node1_interface, options) + if not options.unidirectional: + ptp.linkconfig(node2_interface, options) + # link node to net + elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): + node1_interface = node1.newnetif(node2, interface_one) + if not isinstance(node2, (EmaneNet, WlanNode)): + node2.linkconfig(node1_interface, options) + # link net to node + elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): + node2_interface = node2.newnetif(node1, interface_two) + wireless_net = isinstance(node1, (EmaneNet, WlanNode)) + if not options.unidirectional and not wireless_net: + node1.linkconfig(node2_interface, options) + # network to network + elif isinstance(node1, CoreNetworkBase) and isinstance( + node2, CoreNetworkBase + ): + logging.info( + "linking network to network: %s - %s", node1.name, node2.name + ) + node1_interface = node1.linknet(node2) + node1.linkconfig(node1_interface, options) + if not options.unidirectional: + node1_interface.swapparams("_params_up") + node2.linkconfig(node1_interface, options) + node1_interface.swapparams("_params_up") + else: + raise CoreError( + f"cannot link node1({type(node1)}) node2({type(node2)})" + ) + # configure tunnel nodes + key = options.key + if isinstance(node1, TunnelNode): + logging.info("setting tunnel key for: %s", node1.name) + node1.setkey(key, interface_one) + if isinstance(node2, TunnelNode): + logging.info("setting tunnel key for: %s", node2.name) + node2.setkey(key, interface_two) self.sdt.add_link(node_one_id, node_two_id) - return node_one_interface, node_two_interface + return node1_interface, node2_interface def delete_link( self, @@ -424,93 +334,52 @@ def delete_link( :return: nothing :raises core.CoreError: when no common network is found for link being deleted """ - # get node objects identified by link data - node_one, node_two, net_one, net_two = self._link_nodes( - node_one_id, node_two_id + node1 = self.get_node(node_one_id, NodeBase) + node2 = self.get_node(node_two_id, NodeBase) + logging.info( + "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", + link_type.name, + node1.name, + interface_one_id, + node2.name, + interface_two_id, ) - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if link_type == LinkTypes.WIRELESS: - objects = [node_one, node_two, net_one, net_two] - self._link_wireless(objects, connect=False) - # wired link + # wireless link + if link_type == LinkTypes.WIRELESS: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + self._link_wireless(node1, node2, connect=False) else: - if all([node_one, node_two]): - # TODO: fix this for the case where ifindex[1,2] are not specified - # a wired unlink event, delete the connecting bridge - interface_one = node_one.netif(interface_one_id) - interface_two = node_two.netif(interface_two_id) - - # get interfaces from common network, if no network node - # otherwise get interfaces between a node and network - if not interface_one and not interface_two: - common_networks = node_one.commonnets(node_two) - for ( - network, - common_interface_one, - common_interface_two, - ) in common_networks: - if (net_one and network == net_one) or not net_one: - interface_one = common_interface_one - interface_two = common_interface_two - break - - if all([interface_one, interface_two]) and any( - [interface_one.net, interface_two.net] - ): - if interface_one.net != interface_two.net and all( - [interface_one.up, interface_two.up] - ): - raise CoreError("no common network found") - - logging.info( - "deleting link node(%s):interface(%s) node(%s):interface(%s)", - node_one.name, - interface_one.name, - node_two.name, - interface_two.name, - ) - net_one = interface_one.net - interface_one.detachnet() - interface_two.detachnet() - if net_one.numnetif() == 0: - self.delete_node(net_one.id) - node_one.delnetif(interface_one.netindex) - node_two.delnetif(interface_two.netindex) - elif node_one and net_one: - interface = node_one.netif(interface_one_id) - if interface: - logging.info( - "deleting link node(%s):interface(%s) node(%s)", - node_one.name, - interface.name, - net_one.name, - ) - interface.detachnet() - node_one.delnetif(interface.netindex) - elif node_two and net_one: - interface = node_two.netif(interface_two_id) - if interface: - logging.info( - "deleting link node(%s):interface(%s) node(%s)", - node_two.name, - interface.name, - net_one.name, - ) - interface.detachnet() - node_two.delnetif(interface.netindex) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() - + raise CoreError( + "cannot delete wireless link " + f"node1({type(node1)}) node2({type(node2)})" + ) + # wired link + else: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + interface1 = node1.netif(interface_one_id) + interface2 = node2.netif(interface_two_id) + if not interface1: + raise CoreError( + f"node({node1.name}) missing interface({interface_one_id})" + ) + if not interface2: + raise CoreError( + f"node({node2.name}) missing interface({interface_two_id})" + ) + if interface1.net != interface2.net: + raise CoreError( + f"node1({node1.name}) node2({node2.name}) " + "not connected to same net" + ) + ptp = interface1.net + node1.delnetif(interface_one_id) + node2.delnetif(interface_two_id) + self.delete_node(ptp.id) + elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): + node1.delnetif(interface_one_id) + elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): + node2.delnetif(interface_two_id) self.sdt.delete_link(node_one_id, node_two_id) def update_link( @@ -530,84 +399,79 @@ def update_link( :param interface_two_id: interface id for node two :param options: data to update link with :return: nothing - :raises core.CoreError: when updating a wireless type link, when there is a unknown - link between networks + :raises core.CoreError: when updating a wireless type link, when there is a + unknown link between networks """ if not options: options = LinkOptions() - - # get node objects identified by link data - node_one, node_two, net_one, net_two = self._link_nodes( - node_one_id, node_two_id + node1 = self.get_node(node_one_id, NodeBase) + node2 = self.get_node(node_two_id, NodeBase) + logging.info( + "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", + options.type.name, + node1.name, + interface_one_id, + node2.name, + interface_two_id, ) - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if options.type == LinkTypes.WIRELESS: - raise CoreError("cannot update wireless link") - else: - if not node_one and not node_two: - if net_one and net_two: - # modify link between nets - interface = net_one.getlinknetif(net_two) - upstream = False - - if not interface: - upstream = True - interface = net_two.getlinknetif(net_one) - - if not interface: - raise CoreError("modify unknown link between nets") - - if upstream: - interface.swapparams("_params_up") - net_one.linkconfig(interface, options) - interface.swapparams("_params_up") - else: - net_one.linkconfig(interface, options) - - if not options.unidirectional: - if upstream: - net_two.linkconfig(interface, options) - else: - interface.swapparams("_params_up") - net_two.linkconfig(interface, options) - interface.swapparams("_params_up") - else: - raise CoreError("modify link for unknown nodes") - elif not node_one: - # node1 = layer 2node, node2 = layer3 node - interface = node_two.netif(interface_two_id) - net_one.linkconfig(interface, options) - elif not node_two: - # node2 = layer 2node, node1 = layer3 node - interface = node_one.netif(interface_one_id) - net_one.linkconfig(interface, options) + # wireless link + if options.type == LinkTypes.WIRELESS: + raise CoreError("cannot update wireless link") + else: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + interface1 = node1.netif(interface_one_id) + interface2 = node2.netif(interface_two_id) + if not interface1: + raise CoreError( + f"node({node1.name}) missing interface({interface_one_id})" + ) + if not interface2: + raise CoreError( + f"node({node2.name}) missing interface({interface_two_id})" + ) + if interface1.net != interface2.net: + raise CoreError( + f"node1({node1.name}) node2({node2.name}) " + "not connected to same net" + ) + ptp = interface1.net + ptp.linkconfig(interface1, options, interface2) + if not options.unidirectional: + ptp.linkconfig(interface2, options, interface1) + elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): + interface = node1.netif(interface_one_id) + node2.linkconfig(interface, options) + elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): + interface = node2.netif(interface_two_id) + node1.linkconfig(interface, options) + elif isinstance(node1, CoreNetworkBase) and isinstance( + node2, CoreNetworkBase + ): + interface = node1.getlinknetif(node2) + upstream = False + if not interface: + upstream = True + interface = node2.getlinknetif(node1) + if not interface: + raise CoreError("modify unknown link between nets") + if upstream: + interface.swapparams("_params_up") + node1.linkconfig(interface, options) + interface.swapparams("_params_up") else: - common_networks = node_one.commonnets(node_two) - if not common_networks: - raise CoreError("no common network found") - - for net_one, interface_one, interface_two in common_networks: - if ( - interface_one_id is not None - and interface_one_id != node_one.getifindex(interface_one) - ): - continue - - net_one.linkconfig(interface_one, options, interface_two) - if not options.unidirectional: - net_one.linkconfig(interface_two, options, interface_one) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() + node1.linkconfig(interface, options) + if not options.unidirectional: + if upstream: + node2.linkconfig(interface, options) + else: + interface.swapparams("_params_up") + node2.linkconfig(interface, options) + interface.swapparams("_params_up") + else: + raise CoreError( + f"cannot update link node1({type(node1)}) node2({type(node2)})" + ) def _next_node_id(self) -> int: """ @@ -1345,17 +1209,15 @@ def delete_node(self, _id: int) -> bool: :return: True if node deleted, False otherwise """ # delete node and check for session shutdown if a node was removed - logging.info("deleting node(%s)", _id) node = None with self._nodes_lock: if _id in self.nodes: node = self.nodes.pop(_id) - + logging.info("deleted node(%s)", node.name) if node: node.shutdown() self.sdt.delete_node(_id) self.check_shutdown() - return node is not None def delete_nodes(self) -> None: @@ -1767,15 +1629,15 @@ def add_remove_control_interface( try: ip4 = control_net.prefix[node.id] ip4_mask = control_net.prefix.prefixlen - interface = InterfaceData( + interface_data = InterfaceData( id=control_net.CTRLIF_IDX_BASE + net_index, name=f"ctrl{net_index}", mac=utils.random_mac(), ip4=ip4, ip4_mask=ip4_mask, ) - ifindex = node.newnetif(control_net, interface) - node.netif(ifindex).control = True + interface = node.newnetif(control_net, interface_data) + interface.control = True except ValueError: msg = f"Control interface not added to node {node.id}. " msg += f"Invalid control network prefix ({control_net.prefix}). " diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 0c76d6a23..498a9beb0 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -355,10 +355,11 @@ def delnetif(self, ifindex: int) -> None: :return: nothing """ if ifindex not in self._netif: - raise ValueError(f"ifindex {ifindex} does not exist") + raise CoreError(f"node({self.name}) ifindex({ifindex}) does not exist") netif = self._netif.pop(ifindex) + logging.info("node(%s) removing interface(%s)", self.name, netif.name) + netif.detachnet() netif.shutdown() - del netif def netif(self, ifindex: int) -> Optional[CoreInterface]: """ @@ -473,6 +474,18 @@ def termcmdstring(self, sh: str) -> str: """ raise NotImplementedError + def newnetif( + self, net: "CoreNetworkBase", interface: InterfaceData + ) -> CoreInterface: + """ + Create a new network interface. + + :param net: network to associate with + :param interface: interface data for new interface + :return: interface index + """ + raise NotImplementedError + class CoreNode(CoreNodeBase): """ @@ -846,7 +859,9 @@ def ifup(self, ifindex: int) -> None: interface_name = self.ifname(ifindex) self.node_net_client.device_up(interface_name) - def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int: + def newnetif( + self, net: "CoreNetworkBase", interface: InterfaceData + ) -> CoreInterface: """ Create a new network interface. @@ -868,16 +883,16 @@ def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int: netif.sethwaddr(interface.mac) for address in addresses: netif.addaddr(address) - return ifindex else: ifindex = self.newveth(interface.id, interface.name) - self.attachnet(ifindex, net) - if interface.mac: - self.sethwaddr(ifindex, interface.mac) - for address in addresses: - self.addaddr(ifindex, address) - self.ifup(ifindex) - return ifindex + self.attachnet(ifindex, net) + if interface.mac: + self.sethwaddr(ifindex, interface.mac) + for address in addresses: + self.addaddr(ifindex, address) + self.ifup(ifindex) + netif = self.netif(ifindex) + return netif def addfile(self, srcname: str, filename: str) -> None: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 095fbe9b7..6d6ad5892 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -12,7 +12,7 @@ from core import utils from core.constants import EBTABLES_BIN, TC_BIN from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import LinkOptions +from core.emulator.emudata import InterfaceData, LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -697,15 +697,19 @@ def addrconfig(self, addrlist: List[str]) -> None: ) self.attach(self.gretap) - def setkey(self, key: int) -> None: + def setkey(self, key: int, interface_data: InterfaceData) -> None: """ Set the GRE key used for the GreTap device. This needs to be set prior to instantiating the GreTap device (before addrconfig). :param key: gre key + :param interface_data: interface data for setting up tunnel key :return: nothing """ self.grekey = key + addresses = interface_data.get_addresses() + if addresses: + self.addrconfig(addresses) class CtrlNet(CoreNetwork): diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index ee00c7053..6faa78245 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -157,7 +157,7 @@ def newifindex(self) -> int: self.ifindex += 1 return ifindex - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: + def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: logging.info("creating interface") addresses = interface.get_addresses() ifindex = interface.id @@ -171,12 +171,12 @@ def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) self.adoptnetif(remote_tap, ifindex, interface.mac, addresses) - return ifindex + return remote_tap else: # this is reached when configuring services (self.up=False) netif = GreTap(node=self, name=name, session=self.session, start=False) self.adoptnetif(netif, ifindex, interface.mac, addresses) - return ifindex + return netif def privatedir(self, path: str) -> None: if path[0] != "/": @@ -297,7 +297,7 @@ def shutdown(self) -> None: self.up = False self.restorestate() - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: + def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: """ This is called when linking with another node. Since this node represents an interface, we do not create another object here, @@ -320,7 +320,7 @@ def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: self.interface.attachnet(net) for addr in interface.get_addresses(): self.addaddr(addr) - return ifindex + return self.interface def delnetif(self, ifindex: int) -> None: """ diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 26e78367b..0cbdb8aeb 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -54,12 +54,11 @@ def test_node_sethwaddr(self, session: Session): node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - interface = node.netif(index) + interface = node.newnetif(switch, interface_data) mac = "aa:aa:aa:ff:ff:ff" # when - node.sethwaddr(index, mac) + node.sethwaddr(interface.netindex, mac) # then assert interface.hwaddr == mac @@ -69,25 +68,23 @@ def test_node_sethwaddr_exception(self, session: Session): node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - node.netif(index) + interface = node.newnetif(switch, interface_data) mac = "aa:aa:aa:ff:ff:fff" # when with pytest.raises(CoreError): - node.sethwaddr(index, mac) + node.sethwaddr(interface.netindex, mac) def test_node_addaddr(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - interface = node.netif(index) + interface = node.newnetif(switch, interface_data) addr = "192.168.0.1/24" # when - node.addaddr(index, addr) + node.addaddr(interface.netindex, addr) # then assert interface.addrlist[0] == addr @@ -97,13 +94,12 @@ def test_node_addaddr_exception(self, session): node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - node.netif(index) + interface = node.newnetif(switch, interface_data) addr = "256.168.0.1/24" # when with pytest.raises(CoreError): - node.addaddr(index, addr) + node.addaddr(interface.netindex, addr) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): From c64094ac1c219c5dc9b0d56f07279c702b3115cf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:01:38 -0700 Subject: [PATCH 003/210] daemon: updated session.delete_link to have the interface ids default to none, since only one may need to be provided, updated link tests to account for more cases --- daemon/core/api/tlv/corehandlers.py | 1 - daemon/core/emulator/session.py | 4 +- daemon/tests/test_links.py | 136 +++++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 5531e5af0..3adaed63f 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -788,7 +788,6 @@ def handle_link_message(self, message): link_options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) link_options.key = message.get_tlv(LinkTlvs.KEY.value) link_options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) - if message.flags & MessageFlags.ADD.value: self.session.add_link( node_one_id, node_two_id, interface_one, interface_two, link_options diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 54486bfb5..854d5cc80 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -319,8 +319,8 @@ def delete_link( self, node_one_id: int, node_two_id: int, - interface_one_id: int, - interface_two_id: int, + interface_one_id: int = None, + interface_two_id: int = None, link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 9736537ea..71942e4b1 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -25,7 +25,7 @@ def create_ptp_network( class TestLinks: - def test_ptp(self, session: Session, ip_prefixes: IpPrefixes): + def test_add_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(CoreNode) node_two = session.add_node(CoreNode) @@ -39,20 +39,20 @@ def test_ptp(self, session: Session, ip_prefixes: IpPrefixes): assert node_one.netif(interface_one.id) assert node_two.netif(interface_two.id) - def test_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): + def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(CoreNode) node_two = session.add_node(SwitchNode) interface_one = ip_prefixes.create_interface(node_one) # when - session.add_link(node_one.id, node_two.id, interface_one) + session.add_link(node_one.id, node_two.id, interface_one=interface_one) # then assert node_two.all_link_data() assert node_one.netif(interface_one.id) - def test_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): + def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(SwitchNode) node_two = session.add_node(CoreNode) @@ -76,7 +76,7 @@ def test_net_to_net(self, session): # then assert node_one.all_link_data() - def test_link_update(self, session: Session, ip_prefixes: IpPrefixes): + def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given delay = 50 bandwidth = 5000000 @@ -95,12 +95,9 @@ def test_link_update(self, session: Session, ip_prefixes: IpPrefixes): assert interface_one.getparam("jitter") != jitter # when - link_options = LinkOptions() - link_options.delay = delay - link_options.bandwidth = bandwidth - link_options.per = per - link_options.dup = dup - link_options.jitter = jitter + link_options = LinkOptions( + delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + ) session.update_link( node_one.id, node_two.id, @@ -115,7 +112,94 @@ def test_link_update(self, session: Session, ip_prefixes: IpPrefixes): assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("jitter") == jitter - def test_link_delete(self, session: Session, ip_prefixes: IpPrefixes): + def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): + # given + delay = 50 + bandwidth = 5000000 + per = 25 + dup = 25 + jitter = 10 + node_one = session.add_node(SwitchNode) + node_two = session.add_node(CoreNode) + interface_two_data = ip_prefixes.create_interface(node_two) + session.add_link(node_one.id, node_two.id, interface_two=interface_two_data) + interface_two = node_two.netif(interface_two_data.id) + assert interface_two.getparam("delay") != delay + assert interface_two.getparam("bw") != bandwidth + assert interface_two.getparam("loss") != per + assert interface_two.getparam("duplicate") != dup + assert interface_two.getparam("jitter") != jitter + + # when + link_options = LinkOptions( + delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + ) + session.update_link( + node_one.id, + node_two.id, + interface_two_id=interface_two_data.id, + options=link_options, + ) + + # then + assert interface_two.getparam("delay") == delay + assert interface_two.getparam("bw") == bandwidth + assert interface_two.getparam("loss") == per + assert interface_two.getparam("duplicate") == dup + assert interface_two.getparam("jitter") == jitter + + def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): + # given + delay = 50 + bandwidth = 5000000 + per = 25 + dup = 25 + jitter = 10 + node_one = session.add_node(CoreNode) + node_two = session.add_node(CoreNode) + interface_one_data = ip_prefixes.create_interface(node_one) + interface_two_data = ip_prefixes.create_interface(node_two) + session.add_link( + node_one.id, node_two.id, interface_one_data, interface_two_data + ) + interface_one = node_one.netif(interface_one_data.id) + interface_two = node_two.netif(interface_two_data.id) + assert interface_one.getparam("delay") != delay + assert interface_one.getparam("bw") != bandwidth + assert interface_one.getparam("loss") != per + assert interface_one.getparam("duplicate") != dup + assert interface_one.getparam("jitter") != jitter + assert interface_two.getparam("delay") != delay + assert interface_two.getparam("bw") != bandwidth + assert interface_two.getparam("loss") != per + assert interface_two.getparam("duplicate") != dup + assert interface_two.getparam("jitter") != jitter + + # when + link_options = LinkOptions( + delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + ) + session.update_link( + node_one.id, + node_two.id, + interface_one_data.id, + interface_two_data.id, + link_options, + ) + + # then + assert interface_one.getparam("delay") == delay + assert interface_one.getparam("bw") == bandwidth + assert interface_one.getparam("loss") == per + assert interface_one.getparam("duplicate") == dup + assert interface_one.getparam("jitter") == jitter + assert interface_two.getparam("delay") == delay + assert interface_two.getparam("bw") == bandwidth + assert interface_two.getparam("loss") == per + assert interface_two.getparam("duplicate") == dup + assert interface_two.getparam("jitter") == jitter + + def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(CoreNode) node_two = session.add_node(CoreNode) @@ -133,3 +217,31 @@ def test_link_delete(self, session: Session, ip_prefixes: IpPrefixes): # then assert not node_one.netif(interface_one.id) assert not node_two.netif(interface_two.id) + + def test_delete_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): + # given + node_one = session.add_node(CoreNode) + node_two = session.add_node(SwitchNode) + interface_one = ip_prefixes.create_interface(node_one) + session.add_link(node_one.id, node_two.id, interface_one) + assert node_one.netif(interface_one.id) + + # when + session.delete_link(node_one.id, node_two.id, interface_one_id=interface_one.id) + + # then + assert not node_one.netif(interface_one.id) + + def test_delete_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): + # given + node_one = session.add_node(SwitchNode) + node_two = session.add_node(CoreNode) + interface_two = ip_prefixes.create_interface(node_two) + session.add_link(node_one.id, node_two.id, interface_two=interface_two) + assert node_two.netif(interface_two.id) + + # when + session.delete_link(node_one.id, node_two.id, interface_two_id=interface_two.id) + + # then + assert not node_two.netif(interface_two.id) From 00cda5c55067db0f995817bcd0d4e04f1fe2eae8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:08:50 -0700 Subject: [PATCH 004/210] fixed test_link name --- daemon/tests/test_links.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 71942e4b1..9f693da1e 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -65,7 +65,7 @@ def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): assert node_one.all_link_data() assert node_two.netif(interface_two.id) - def test_net_to_net(self, session): + def test_add_net_to_net(self, session): # given node_one = session.add_node(SwitchNode) node_two = session.add_node(SwitchNode) From e72e332babe9aa8502b903bac2f6a8bd0662f2c9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:12:51 -0700 Subject: [PATCH 005/210] daemon: removed need to use getaddr for CoreInterface.othernet as it now has a default of None --- daemon/core/nodes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 498a9beb0..4b8d513ba 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1040,7 +1040,7 @@ def getlinknetif(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: :return: interface the provided network is linked to """ for netif in self.netifs(): - if getattr(netif, "othernet", None) == net: + if netif.othernet == net: return netif return None From cfaa9397ada5df0a5cde683376e0bf3de4bfe807 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 08:34:02 -0700 Subject: [PATCH 006/210] daemon: added class variable type hinting to core.api.grpc --- daemon/core/api/grpc/client.py | 12 ++++++------ daemon/core/api/grpc/events.py | 6 +++--- daemon/core/api/grpc/grpcutils.py | 7 ++++--- daemon/core/api/grpc/server.py | 8 ++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 0361a69b7..1bc880699 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -5,7 +5,7 @@ import logging import threading from contextlib import contextmanager -from typing import Any, Callable, Dict, Generator, Iterable, List +from typing import Any, Callable, Dict, Generator, Iterable, List, Optional import grpc @@ -108,7 +108,7 @@ def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: :param ip6_prefix: ip6 prefix to use for generation :raises ValueError: when both ip4 and ip6 prefixes have not been provided """ - self.prefixes = IpPrefixes(ip4_prefix, ip6_prefix) + self.prefixes: IpPrefixes = IpPrefixes(ip4_prefix, ip6_prefix) def create_interface( self, node_id: int, interface_id: int, name: str = None, mac: str = None @@ -177,10 +177,10 @@ def __init__(self, address: str = "localhost:50051", proxy: bool = False) -> Non :param address: grpc server address to connect to """ - self.address = address - self.stub = None - self.channel = None - self.proxy = proxy + self.address: str = address + self.stub: Optional[core_pb2_grpc.CoreApiStub] = None + self.channel: Optional[grpc.Channel] = None + self.proxy: bool = proxy def start_session( self, diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 837860e3a..82cf1eac0 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -140,9 +140,9 @@ def __init__( :param session: session to process events for :param event_types: types of events to process """ - self.session = session - self.event_types = event_types - self.queue = Queue() + self.session: Session = session + self.event_types: Iterable[core_pb2.EventType] = event_types + self.queue: Queue = Queue() self.add_handlers() def add_handlers(self) -> None: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 5c6f3a80a..73d19a2a3 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -1,6 +1,6 @@ import logging import time -from typing import Any, Dict, List, Tuple, Type +from typing import Any, Dict, List, Tuple, Type, Union import grpc import netaddr @@ -190,7 +190,8 @@ def convert_value(value: Any) -> str: def get_config_options( - config: Dict[str, str], configurable_options: Type[ConfigurableOptions] + config: Dict[str, str], + configurable_options: Union[ConfigurableOptions, Type[ConfigurableOptions]], ) -> Dict[str, common_pb2.ConfigOption]: """ Retrieve configuration options in a form that is used by the grpc server. @@ -418,7 +419,7 @@ def service_configuration(session: Session, config: ServiceConfig) -> None: service.shutdown = tuple(config.shutdown) -def get_service_configuration(service: Type[CoreService]) -> NodeServiceData: +def get_service_configuration(service: CoreService) -> NodeServiceData: """ Convenience for converting a service to service data proto. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 7d7f7c806..adddff145 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -6,7 +6,7 @@ import threading import time from concurrent import futures -from typing import Iterable, Type +from typing import Iterable, Optional, Type import grpc from grpc import ServicerContext @@ -131,9 +131,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def __init__(self, coreemu: CoreEmu) -> None: super().__init__() - self.coreemu = coreemu - self.running = True - self.server = None + self.coreemu: CoreEmu = coreemu + self.running: bool = True + self.server: Optional[grpc.Server] = None atexit.register(self._exit_handler) def _exit_handler(self) -> None: From ef3cf5697d0080b45a5bf5894c5e87f2b105c0ea Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 08:54:06 -0700 Subject: [PATCH 007/210] daemon: added class variable type hinting for core.xml --- daemon/core/config.py | 7 ++++--- daemon/core/xml/corexml.py | 28 +++++++++++----------------- daemon/core/xml/corexmldeployment.py | 6 +++--- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index 1f5bc3c0d..d4ba61647 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,7 +4,7 @@ import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Dict, List, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes @@ -136,7 +136,8 @@ def config_reset(self, node_id: int = None) -> None: """ Clears all configurations or configuration for a specific node. - :param node_id: node id to clear configurations for, default is None and clears all configurations + :param node_id: node id to clear configurations for, default is None and clears + all configurations :return: nothing """ if not node_id: @@ -222,7 +223,7 @@ def get_configs( result = node_configs.get(config_type) return result - def get_all_configs(self, node_id: int = _default_node) -> List[Dict[str, str]]: + def get_all_configs(self, node_id: int = _default_node) -> Dict[str, Any]: """ Retrieve all current configuration types for a node. diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index cb25e7176..973eb77fa 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -124,9 +124,9 @@ def add_configuration(parent: etree.Element, name: str, value: str) -> None: class NodeElement: def __init__(self, session: "Session", node: NodeBase, element_name: str) -> None: - self.session = session - self.node = node - self.element = etree.Element(element_name) + self.session: "Session" = session + self.node: NodeBase = node + self.element: etree.Element = etree.Element(element_name) add_attribute(self.element, "id", node.id) add_attribute(self.element, "name", node.name) add_attribute(self.element, "icon", node.icon) @@ -151,8 +151,8 @@ def add_position(self) -> None: class ServiceElement: def __init__(self, service: Type[CoreService]) -> None: - self.service = service - self.element = etree.Element("service") + self.service: Type[CoreService] = service + self.element: etree.Element = etree.Element("service") add_attribute(self.element, "name", service.name) self.add_directories() self.add_startup() @@ -268,10 +268,10 @@ def add_type(self) -> None: class CoreXmlWriter: def __init__(self, session: "Session") -> None: - self.session = session - self.scenario = etree.Element("scenario") - self.networks = None - self.devices = None + self.session: "Session" = session + self.scenario: etree.Element = etree.Element("scenario") + self.networks: etree.SubElement = etree.SubElement(self.scenario, "networks") + self.devices: etree.SubElement = etree.SubElement(self.scenario, "devices") self.write_session() def write_session(self) -> None: @@ -362,13 +362,11 @@ def write_session_metadata(self) -> None: def write_emane_configs(self) -> None: emane_global_configuration = create_emane_config(self.session) self.scenario.append(emane_global_configuration) - emane_configurations = etree.Element("emane_configurations") for node_id in self.session.emane.nodes(): all_configs = self.session.emane.get_all_configs(node_id) if not all_configs: continue - for model_name in all_configs: config = all_configs[model_name] logging.debug( @@ -453,9 +451,6 @@ def write_default_services(self) -> None: self.scenario.append(node_types) def write_nodes(self) -> List[LinkData]: - self.networks = etree.SubElement(self.scenario, "networks") - self.devices = etree.SubElement(self.scenario, "devices") - links = [] for node_id in self.session.nodes: node = self.session.nodes[node_id] @@ -472,7 +467,6 @@ def write_nodes(self) -> List[LinkData]: # add known links links.extend(node.all_link_data()) - return links def write_network(self, node: NodeBase) -> None: @@ -597,8 +591,8 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: class CoreXmlReader: def __init__(self, session: "Session") -> None: - self.session = session - self.scenario = None + self.session: "Session" = session + self.scenario: Optional[etree.ElementTree] = None def read(self, file_name: str) -> None: xml_tree = etree.parse(file_name) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 5f340b694..04915bf13 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -101,9 +101,9 @@ def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]: class CoreXmlDeployment: def __init__(self, session: "Session", scenario: etree.Element) -> None: - self.session = session - self.scenario = scenario - self.root = etree.SubElement( + self.session: "Session" = session + self.scenario: etree.Element = scenario + self.root: etree.SubElement = etree.SubElement( scenario, "container", id="TestBed", name="TestBed" ) self.add_deployment() From 6201875b782c266335f23518a4cfd8cfc30f5b42 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 09:52:01 -0700 Subject: [PATCH 008/210] daemon: added class variable type hinting to core.emane --- daemon/core/emane/bypass.py | 13 ++++--- daemon/core/emane/commeffect.py | 16 ++++---- daemon/core/emane/emanemanager.py | 44 +++++++++++---------- daemon/core/emane/emanemanifest.py | 1 + daemon/core/emane/emanemodel.py | 26 +++++++------ daemon/core/emane/ieee80211abg.py | 6 +-- daemon/core/emane/linkmonitor.py | 61 ++++++++++++++++-------------- daemon/core/emane/nodes.py | 26 +++++++------ daemon/core/emane/rfpipe.py | 6 +-- daemon/core/emane/tdma.py | 13 ++++--- daemon/core/nodes/network.py | 4 +- 11 files changed, 116 insertions(+), 100 deletions(-) diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 83f3b6e8b..8aabc3f98 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -1,6 +1,7 @@ """ EMANE Bypass model for CORE """ +from typing import List, Set from core.config import Configuration from core.emane import emanemodel @@ -8,14 +9,14 @@ class EmaneBypassModel(emanemodel.EmaneModel): - name = "emane_bypass" + name: str = "emane_bypass" # values to ignore, when writing xml files - config_ignore = {"none"} + config_ignore: Set[str] = {"none"} # mac definitions - mac_library = "bypassmaclayer" - mac_config = [ + mac_library: str = "bypassmaclayer" + mac_config: List[Configuration] = [ Configuration( _id="none", _type=ConfigDataTypes.BOOL, @@ -25,8 +26,8 @@ class EmaneBypassModel(emanemodel.EmaneModel): ] # phy definitions - phy_library = "bypassphylayer" - phy_config = [] + phy_library: str = "bypassphylayer" + phy_config: List[Configuration] = [] @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index b7060e964..71acb1999 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -22,6 +22,7 @@ try: from emanesh.events.commeffectevent import CommEffectEvent except ImportError: + CommEffectEvent = None logging.debug("compatible emane python bindings not installed") @@ -38,16 +39,15 @@ def convert_none(x: float) -> int: class EmaneCommEffectModel(emanemodel.EmaneModel): - name = "emane_commeffect" - - shim_library = "commeffectshim" - shim_xml = "commeffectshim.xml" - shim_defaults = {} - config_shim = [] + name: str = "emane_commeffect" + shim_library: str = "commeffectshim" + shim_xml: str = "commeffectshim.xml" + shim_defaults: Dict[str, str] = {} + config_shim: List[Configuration] = [] # comm effect does not need the default phy and external configurations - phy_config = [] - external_config = [] + phy_config: List[Configuration] = [] + external_config: List[Configuration] = [] @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 12b477f0c..146d186f9 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -70,11 +70,13 @@ class EmaneManager(ModelManager): controlling the EMANE daemons. """ - name = "emane" - config_type = RegisterTlvs.EMULATION_SERVER - SUCCESS, NOT_NEEDED, NOT_READY = (0, 1, 2) - EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG" - DEFAULT_LOG_LEVEL = 3 + name: str = "emane" + config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER + SUCCESS: int = 0 + NOT_NEEDED: int = 1 + NOT_READY: int = 2 + EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG" + DEFAULT_LOG_LEVEL: int = 3 def __init__(self, session: "Session") -> None: """ @@ -84,29 +86,29 @@ def __init__(self, session: "Session") -> None: :return: nothing """ super().__init__() - self.session = session - self._emane_nets = {} - self._emane_node_lock = threading.Lock() + self.session: "Session" = session + self._emane_nets: Dict[int, EmaneNet] = {} + self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters - self.platformport = self.session.options.get_config_int( + self.platformport: int = self.session.options.get_config_int( "emane_platform_port", 8100 ) - self.transformport = self.session.options.get_config_int( + self.transformport: int = self.session.options.get_config_int( "emane_transform_port", 8200 ) - self.doeventloop = False - self.eventmonthread = None + self.doeventloop: bool = False + self.eventmonthread: Optional[threading.Thread] = None # model for global EMANE configuration options - self.emane_config = EmaneGlobalModel(session) + self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session) self.set_configs(self.emane_config.default_values()) # link monitor - self.link_monitor = EmaneLinkMonitor(self) + self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self) - self.service = None - self.eventchannel = None - self.event_device = None + self.service: Optional[EventService] = None + self.eventchannel: Optional[Tuple[str, int, str]] = None + self.event_device: Optional[str] = None self.emane_check() def getifcconfig( @@ -890,12 +892,12 @@ class EmaneGlobalModel: Global EMANE configuration options. """ - name = "emane" - bitmap = None + name: str = "emane" + bitmap: Optional[str] = None def __init__(self, session: "Session") -> None: - self.session = session - self.core_config = [ + self.session: "Session" = session + self.core_config: List[Configuration] = [ Configuration( _id="platform_id_start", _type=ConfigDataTypes.INT32, diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index 914b4f830..41dc7beb5 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -11,6 +11,7 @@ try: from emanesh import manifest except ImportError: + manifest = None logging.debug("compatible emane python bindings not installed") diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 7b5ff417f..78d5ec5eb 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -3,7 +3,7 @@ """ import logging import os -from typing import Dict, List +from typing import Dict, List, Optional, Set from core.config import ConfigGroup, Configuration from core.emane import emanemanifest @@ -25,19 +25,23 @@ class EmaneModel(WirelessModel): """ # default mac configuration settings - mac_library = None - mac_xml = None - mac_defaults = {} - mac_config = [] + mac_library: Optional[str] = None + mac_xml: Optional[str] = None + mac_defaults: Dict[str, str] = {} + mac_config: List[Configuration] = [] # default phy configuration settings, using the universal model - phy_library = None - phy_xml = "emanephy.xml" - phy_defaults = {"subid": "1", "propagationmodel": "2ray", "noisemode": "none"} - phy_config = [] + phy_library: Optional[str] = None + phy_xml: str = "emanephy.xml" + phy_defaults: Dict[str, str] = { + "subid": "1", + "propagationmodel": "2ray", + "noisemode": "none", + } + phy_config: List[Configuration] = [] # support for external configurations - external_config = [ + external_config: List[Configuration] = [ Configuration("external", ConfigDataTypes.BOOL, default="0"), Configuration( "platformendpoint", ConfigDataTypes.STRING, default="127.0.0.1:40001" @@ -47,7 +51,7 @@ class EmaneModel(WirelessModel): ), ] - config_ignore = set() + config_ignore: Set[str] = set() @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/ieee80211abg.py b/daemon/core/emane/ieee80211abg.py index ecfd36941..0d58ec9e5 100644 --- a/daemon/core/emane/ieee80211abg.py +++ b/daemon/core/emane/ieee80211abg.py @@ -8,11 +8,11 @@ class EmaneIeee80211abgModel(emanemodel.EmaneModel): # model name - name = "emane_ieee80211abg" + name: str = "emane_ieee80211abg" # mac configuration - mac_library = "ieee80211abgmaclayer" - mac_xml = "ieee80211abgmaclayer.xml" + mac_library: str = "ieee80211abgmaclayer" + mac_xml: str = "ieee80211abgmaclayer.xml" @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 861c108c3..b9fd9a2a8 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -2,7 +2,7 @@ import sched import threading import time -from typing import TYPE_CHECKING, Dict, List, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import netaddr from lxml import etree @@ -17,28 +17,29 @@ try: from emanesh import shell except ImportError: + shell = None logging.debug("compatible emane python bindings not installed") if TYPE_CHECKING: from core.emane.emanemanager import EmaneManager -DEFAULT_PORT = 47_000 -MAC_COMPONENT_INDEX = 1 -EMANE_RFPIPE = "rfpipemaclayer" -EMANE_80211 = "ieee80211abgmaclayer" -EMANE_TDMA = "tdmaeventschedulerradiomodel" -SINR_TABLE = "NeighborStatusTable" -NEM_SELF = 65535 +DEFAULT_PORT: int = 47_000 +MAC_COMPONENT_INDEX: int = 1 +EMANE_RFPIPE: str = "rfpipemaclayer" +EMANE_80211: str = "ieee80211abgmaclayer" +EMANE_TDMA: str = "tdmaeventschedulerradiomodel" +SINR_TABLE: str = "NeighborStatusTable" +NEM_SELF: int = 65535 class LossTable: def __init__(self, losses: Dict[float, float]) -> None: - self.losses = losses - self.sinrs = sorted(self.losses.keys()) - self.loss_lookup = {} + self.losses: Dict[float, float] = losses + self.sinrs: List[float] = sorted(self.losses.keys()) + self.loss_lookup: Dict[int, float] = {} for index, value in enumerate(self.sinrs): self.loss_lookup[index] = self.losses[value] - self.mac_id = None + self.mac_id: Optional[str] = None def get_loss(self, sinr: float) -> float: index = self._get_index(sinr) @@ -54,11 +55,11 @@ def _get_index(self, current_sinr: float) -> int: class EmaneLink: def __init__(self, from_nem: int, to_nem: int, sinr: float) -> None: - self.from_nem = from_nem - self.to_nem = to_nem - self.sinr = sinr - self.last_seen = None - self.updated = False + self.from_nem: int = from_nem + self.to_nem: int = to_nem + self.sinr: float = sinr + self.last_seen: Optional[float] = None + self.updated: bool = False self.touch() def update(self, sinr: float) -> None: @@ -78,9 +79,11 @@ def __repr__(self) -> str: class EmaneClient: def __init__(self, address: str) -> None: - self.address = address - self.client = shell.ControlPortClient(self.address, DEFAULT_PORT) - self.nems = {} + self.address: str = address + self.client: shell.ControlPortClient = shell.ControlPortClient( + self.address, DEFAULT_PORT + ) + self.nems: Dict[int, LossTable] = {} self.setup() def setup(self) -> None: @@ -174,15 +177,15 @@ def stop(self) -> None: class EmaneLinkMonitor: def __init__(self, emane_manager: "EmaneManager") -> None: - self.emane_manager = emane_manager - self.clients = [] - self.links = {} - self.complete_links = set() - self.loss_threshold = None - self.link_interval = None - self.link_timeout = None - self.scheduler = None - self.running = False + self.emane_manager: "EmaneManager" = emane_manager + self.clients: List[EmaneClient] = [] + self.links: Dict[Tuple[int, int], EmaneLink] = {} + self.complete_links: Set[Tuple[int, int]] = set() + self.loss_threshold: Optional[int] = None + self.link_interval: Optional[int] = None + self.link_timeout: Optional[int] = None + self.scheduler: Optional[sched.scheduler] = None + self.running: bool = False def start(self) -> None: self.loss_threshold = int(self.emane_manager.get_config("loss_threshold")) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index f4de8f47f..e88cb194d 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -16,13 +16,16 @@ RegisterTlvs, TransportType, ) +from core.errors import CoreError from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface if TYPE_CHECKING: + from core.emane.emanemodel import EmaneModel from core.emulator.session import Session - from core.location.mobility import WirelessModel + from core.location.mobility import WirelessModel, WayPointMobility + OptionalEmaneModel = Optional[EmaneModel] WirelessModelType = Type[WirelessModel] try: @@ -31,6 +34,7 @@ try: from emanesh.events import LocationEvent except ImportError: + LocationEvent = None logging.debug("compatible emane python bindings not installed") @@ -41,10 +45,10 @@ class EmaneNet(CoreNetworkBase): Emane controller object that exists in a session. """ - apitype = NodeTypes.EMANE - linktype = LinkTypes.WIRED - type = "wlan" - is_emane = True + apitype: NodeTypes = NodeTypes.EMANE + linktype: LinkTypes = LinkTypes.WIRED + type: str = "wlan" + is_emane: bool = True def __init__( self, @@ -55,10 +59,10 @@ def __init__( server: DistributedServer = None, ) -> None: super().__init__(session, _id, name, start, server) - self.conf = "" - self.nemidmap = {} - self.model = None - self.mobility = None + self.conf: str = "" + self.nemidmap: Dict[CoreInterface, int] = {} + self.model: "OptionalEmaneModel" = None + self.mobility: Optional[WayPointMobility] = None def linkconfig( self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None @@ -84,11 +88,11 @@ def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: - raise ValueError("no model set to update for node(%s)", self.id) + raise CoreError(f"no model set to update for node({self.name})") logging.info( "node(%s) updating model(%s): %s", self.id, self.model.name, config ) - self.model.set_configs(config, node_id=self.id) + self.model.update_config(config) def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None: """ diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/rfpipe.py index 23790b3cf..068ef8002 100644 --- a/daemon/core/emane/rfpipe.py +++ b/daemon/core/emane/rfpipe.py @@ -8,11 +8,11 @@ class EmaneRfPipeModel(emanemodel.EmaneModel): # model name - name = "emane_rfpipe" + name: str = "emane_rfpipe" # mac configuration - mac_library = "rfpipemaclayer" - mac_xml = "rfpipemaclayer.xml" + mac_library: str = "rfpipemaclayer" + mac_xml: str = "rfpipemaclayer.xml" @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 17f5328fa..ee80f3d72 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -4,6 +4,7 @@ import logging import os +from typing import Set from core import constants, utils from core.config import Configuration @@ -13,18 +14,18 @@ class EmaneTdmaModel(emanemodel.EmaneModel): # model name - name = "emane_tdma" + name: str = "emane_tdma" # mac configuration - mac_library = "tdmaeventschedulerradiomodel" - mac_xml = "tdmaeventschedulerradiomodel.xml" + mac_library: str = "tdmaeventschedulerradiomodel" + mac_xml: str = "tdmaeventschedulerradiomodel.xml" # add custom schedule options and ignore it when writing emane xml - schedule_name = "schedule" - default_schedule = os.path.join( + schedule_name: str = "schedule" + default_schedule: str = os.path.join( constants.CORE_DATA_DIR, "examples", "tdma", "schedule.xml" ) - config_ignore = {schedule_name} + config_ignore: Set[str] = {schedule_name} @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 6d6ad5892..235b43f2d 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1090,12 +1090,12 @@ def setmodel(self, model: "WirelessModelType", config: Dict[str, str]): def update_mobility(self, config: Dict[str, str]) -> None: if not self.mobility: - raise ValueError(f"no mobility set to update for node({self.id})") + raise CoreError(f"no mobility set to update for node({self.name})") self.mobility.update_config(config) def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: - raise ValueError(f"no model set to update for node({self.id})") + raise CoreError(f"no model set to update for node({self.name})") logging.debug( "node(%s) updating model(%s): %s", self.id, self.model.name, config ) From b28ef76d65a483c09bc19f28bec8e1c6635568d8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 10:05:49 -0700 Subject: [PATCH 009/210] daemon: added class variable type hinting to core.config --- daemon/core/config.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index d4ba61647..618e1273d 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,7 +4,7 @@ import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes @@ -29,9 +29,9 @@ def __init__(self, name: str, start: int, stop: int) -> None: :param start: configurations start index for this group :param stop: configurations stop index for this group """ - self.name = name - self.start = start - self.stop = stop + self.name: str = name + self.start: int = start + self.stop: int = stop class Configuration: @@ -56,18 +56,21 @@ def __init__( :param default: default value for configuration :param options: list options if this is a configuration with a combobox """ - self.id = _id - self.type = _type - self.default = default + self.id: str = _id + self.type: ConfigDataTypes = _type + self.default: str = default if not options: options = [] - self.options = options + self.options: List[str] = options if not label: label = _id - self.label = label + self.label: str = label def __str__(self): - return f"{self.__class__.__name__}(id={self.id}, type={self.type}, default={self.default}, options={self.options})" + return ( + f"{self.__class__.__name__}(id={self.id}, type={self.type}, " + f"default={self.default}, options={self.options})" + ) class ConfigurableOptions: @@ -75,9 +78,9 @@ class ConfigurableOptions: Provides a base for defining configuration options within CORE. """ - name = None - bitmap = None - options = [] + name: Optional[str] = None + bitmap: Optional[str] = None + options: List[Configuration] = [] @classmethod def configurations(cls) -> List[Configuration]: @@ -115,8 +118,8 @@ class ConfigurableManager: nodes. """ - _default_node = -1 - _default_type = _default_node + _default_node: int = -1 + _default_type: int = _default_node def __init__(self) -> None: """ @@ -243,8 +246,8 @@ def __init__(self) -> None: Creates a ModelManager object. """ super().__init__() - self.models = {} - self.node_models = {} + self.models: Dict[str, Any] = {} + self.node_models: Dict[int, str] = {} def set_model_config( self, node_id: int, model_name: str, config: Dict[str, str] = None From 76305f72577b273050818148c3303c9c9102232c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:49:53 -0700 Subject: [PATCH 010/210] converted usages of per to loss --- daemon/core/api/grpc/grpcutils.py | 4 ++-- daemon/core/api/grpc/server.py | 27 +++++++++++++------------- daemon/core/api/tlv/coreapi.py | 2 +- daemon/core/api/tlv/corehandlers.py | 10 +++++----- daemon/core/api/tlv/enumerations.py | 2 +- daemon/core/emane/commeffect.py | 2 +- daemon/core/emulator/data.py | 2 +- daemon/core/emulator/emudata.py | 2 +- daemon/core/gui/dialogs/linkconfig.py | 8 ++++---- daemon/core/location/mobility.py | 2 +- daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/network.py | 6 +++--- daemon/core/xml/corexml.py | 4 ++-- daemon/proto/core/api/grpc/core.proto | 2 +- daemon/tests/test_links.py | 28 +++++++++++++-------------- daemon/tests/test_xml.py | 16 +++++++-------- 16 files changed, 61 insertions(+), 60 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 73d19a2a3..4acecad9f 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -94,7 +94,7 @@ def add_link_data( if options_data: options.delay = options_data.delay options.bandwidth = options_data.bandwidth - options.per = options_data.per + options.loss = options_data.loss options.dup = options_data.dup options.jitter = options_data.jitter options.mer = options_data.mer @@ -343,7 +343,7 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: key=link_data.key, mburst=link_data.mburst, mer=link_data.mer, - per=link_data.per, + loss=link_data.loss, bandwidth=link_data.bandwidth, burst=link_data.burst, delay=link_data.delay, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index adddff145..9ea4e5555 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -885,20 +885,21 @@ def EditLink( interface_one_id = request.interface_one_id interface_two_id = request.interface_two_id options_data = request.options - link_options = LinkOptions() - link_options.delay = options_data.delay - link_options.bandwidth = options_data.bandwidth - link_options.per = options_data.per - link_options.dup = options_data.dup - link_options.jitter = options_data.jitter - link_options.mer = options_data.mer - link_options.burst = options_data.burst - link_options.mburst = options_data.mburst - link_options.unidirectional = options_data.unidirectional - link_options.key = options_data.key - link_options.opaque = options_data.opaque + options = LinkOptions( + delay=options_data.delay, + bandwidth=options_data.bandwidth, + loss=options_data.loss, + dup=options_data.dup, + jitter=options_data.jitter, + mer=options_data.mer, + burst=options_data.burst, + mburst=options_data.mburst, + unidirectional=options_data.unidirectional, + key=options_data.key, + opaque=options_data.opaque, + ) session.update_link( - node_one_id, node_two_id, interface_one_id, interface_two_id, link_options + node_one_id, node_two_id, interface_one_id, interface_two_id, options ) return core_pb2.EditLinkResponse(result=True) diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index df60e3749..088a7631f 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -495,7 +495,7 @@ class CoreLinkTlv(CoreTlv): LinkTlvs.N2_NUMBER.value: CoreTlvDataUint32, LinkTlvs.DELAY.value: CoreTlvDataUint64, LinkTlvs.BANDWIDTH.value: CoreTlvDataUint64, - LinkTlvs.PER.value: CoreTlvDataString, + LinkTlvs.LOSS.value: CoreTlvDataString, LinkTlvs.DUP.value: CoreTlvDataString, LinkTlvs.JITTER.value: CoreTlvDataUint64, LinkTlvs.MER.value: CoreTlvDataUint16, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3adaed63f..d02c274dc 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -334,9 +334,9 @@ def handle_broadcast_link(self, link_data): :return: nothing """ logging.debug("handling broadcast link: %s", link_data) - per = "" - if link_data.per is not None: - per = str(link_data.per) + loss = "" + if link_data.loss is not None: + loss = str(link_data.loss) dup = "" if link_data.dup is not None: dup = str(link_data.dup) @@ -348,7 +348,7 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.N2_NUMBER, link_data.node2_id), (LinkTlvs.DELAY, link_data.delay), (LinkTlvs.BANDWIDTH, link_data.bandwidth), - (LinkTlvs.PER, per), + (LinkTlvs.LOSS, loss), (LinkTlvs.DUP, dup), (LinkTlvs.JITTER, link_data.jitter), (LinkTlvs.MER, link_data.mer), @@ -776,7 +776,7 @@ def handle_link_message(self, message): link_options.delay = message.get_tlv(LinkTlvs.DELAY.value) link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) link_options.session = message.get_tlv(LinkTlvs.SESSION.value) - link_options.per = message.get_tlv(LinkTlvs.PER.value) + link_options.loss = message.get_tlv(LinkTlvs.LOSS.value) link_options.dup = message.get_tlv(LinkTlvs.DUP.value) link_options.jitter = message.get_tlv(LinkTlvs.JITTER.value) link_options.mer = message.get_tlv(LinkTlvs.MER.value) diff --git a/daemon/core/api/tlv/enumerations.py b/daemon/core/api/tlv/enumerations.py index ed06bbe75..0efb7c997 100644 --- a/daemon/core/api/tlv/enumerations.py +++ b/daemon/core/api/tlv/enumerations.py @@ -59,7 +59,7 @@ class LinkTlvs(Enum): N2_NUMBER = 0x02 DELAY = 0x03 BANDWIDTH = 0x04 - PER = 0x05 + LOSS = 0x05 DUP = 0x06 JITTER = 0x07 MER = 0x08 diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 71acb1999..21252b6f9 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -141,7 +141,7 @@ def linkconfig( nemid, latency=convert_none(options.delay), jitter=convert_none(options.jitter), - loss=convert_none(options.per), + loss=convert_none(options.loss), duplicate=convert_none(options.dup), unicast=int(convert_none(options.bandwidth)), broadcast=int(convert_none(options.bandwidth)), diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index d3283974d..819716e37 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -101,7 +101,7 @@ class LinkData: node2_id: int = None delay: float = None bandwidth: float = None - per: float = None + loss: float = None dup: float = None jitter: float = None mer: float = None diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index b6dbd57cd..992b9cd2d 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -68,7 +68,7 @@ class LinkOptions: session: int = None delay: int = None bandwidth: int = None - per: float = None + loss: float = None dup: int = None jitter: int = None mer: int = None diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 92361ed4a..c553bb940 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -223,7 +223,7 @@ def click_apply(self): duplicate = get_int(self.duplicate) loss = get_float(self.loss) options = core_pb2.LinkOptions( - bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, per=loss + bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss ) link.options.CopyFrom(options) @@ -252,7 +252,7 @@ def click_apply(self): jitter=down_jitter, delay=down_delay, dup=down_duplicate, - per=down_loss, + loss=down_loss, unidirectional=True, ) self.edge.asymmetric_link = core_pb2.Link( @@ -317,12 +317,12 @@ def load_link_config(self): self.bandwidth.set(str(link.options.bandwidth)) self.jitter.set(str(link.options.jitter)) self.duplicate.set(str(link.options.dup)) - self.loss.set(str(link.options.per)) + self.loss.set(str(link.options.loss)) self.delay.set(str(link.options.delay)) if not self.is_symmetric: asym_link = self.edge.asymmetric_link self.down_bandwidth.set(str(asym_link.options.bandwidth)) self.down_jitter.set(str(asym_link.options.jitter)) self.down_duplicate.set(str(asym_link.options.dup)) - self.down_loss.set(str(asym_link.options.per)) + self.down_loss.set(str(asym_link.options.loss)) self.down_delay.set(str(asym_link.options.delay)) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index e9efa16bb..43996ba33 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -338,7 +338,7 @@ def setlinkparams(self) -> None: options = LinkOptions( bandwidth=self.bw, delay=self.delay, - per=self.loss, + loss=self.loss, jitter=self.jitter, ) self.wlan.linkconfig(netif, options) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 4b8d513ba..2b3c7751a 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1134,7 +1134,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat bandwidth=netif.getparam("bw"), dup=netif.getparam("duplicate"), jitter=netif.getparam("jitter"), - per=netif.getparam("loss"), + loss=netif.getparam("loss"), ) all_links.append(link_data) @@ -1153,7 +1153,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat bandwidth=netif.getparam("bw"), dup=netif.getparam("duplicate"), jitter=netif.getparam("jitter"), - per=netif.getparam("loss"), + loss=netif.getparam("loss"), ) netif.swapparams("_params_up") diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 235b43f2d..8ac1939ee 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -482,7 +482,7 @@ def linkconfig( netem = "netem" delay = options.delay changed = max(changed, netif.setparam("delay", delay)) - loss = options.per + loss = options.loss if loss is not None: loss = float(loss) changed = max(changed, netif.setparam("loss", loss)) @@ -939,7 +939,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat unidirectional=unidirectional, delay=if1.getparam("delay"), bandwidth=if1.getparam("bw"), - per=if1.getparam("loss"), + loss=if1.getparam("loss"), dup=if1.getparam("duplicate"), jitter=if1.getparam("jitter"), interface1_id=if1.node.getifindex(if1), @@ -970,7 +970,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat node2_id=if1.node.id, delay=if2.getparam("delay"), bandwidth=if2.getparam("bw"), - per=if2.getparam("loss"), + loss=if2.getparam("loss"), dup=if2.getparam("duplicate"), jitter=if2.getparam("jitter"), unidirectional=1, diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 973eb77fa..820f1cea3 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -569,7 +569,7 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: options = etree.Element("options") add_attribute(options, "delay", link_data.delay) add_attribute(options, "bandwidth", link_data.bandwidth) - add_attribute(options, "per", link_data.per) + add_attribute(options, "per", link_data.loss) add_attribute(options, "dup", link_data.dup) add_attribute(options, "jitter", link_data.jitter) add_attribute(options, "mer", link_data.mer) @@ -957,7 +957,7 @@ def read_links(self) -> None: link_options.mburst = get_int(options_element, "mburst") link_options.jitter = get_int(options_element, "jitter") link_options.key = get_int(options_element, "key") - link_options.per = get_float(options_element, "per") + link_options.loss = get_float(options_element, "per") link_options.unidirectional = get_int(options_element, "unidirectional") link_options.session = options_element.get("session") link_options.emulation_id = get_int(options_element, "emulation_id") diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index d602f9d32..c9c2d94ba 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -719,7 +719,7 @@ message LinkOptions { int32 key = 3; int32 mburst = 4; int32 mer = 5; - float per = 6; + float loss = 6; int64 bandwidth = 7; int32 burst = 8; int64 delay = 9; diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 9f693da1e..9c4fd4f27 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -80,7 +80,7 @@ def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given delay = 50 bandwidth = 5000000 - per = 25 + loss = 25 dup = 25 jitter = 10 node_one = session.add_node(CoreNode) @@ -90,13 +90,13 @@ def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): interface_one = node_one.netif(interface_one_data.id) assert interface_one.getparam("delay") != delay assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per + assert interface_one.getparam("loss") != loss assert interface_one.getparam("duplicate") != dup assert interface_one.getparam("jitter") != jitter # when link_options = LinkOptions( - delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( node_one.id, @@ -108,7 +108,7 @@ def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # then assert interface_one.getparam("delay") == delay assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per + assert interface_one.getparam("loss") == loss assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("jitter") == jitter @@ -116,7 +116,7 @@ def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given delay = 50 bandwidth = 5000000 - per = 25 + loss = 25 dup = 25 jitter = 10 node_one = session.add_node(SwitchNode) @@ -126,13 +126,13 @@ def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): interface_two = node_two.netif(interface_two_data.id) assert interface_two.getparam("delay") != delay assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per + assert interface_two.getparam("loss") != loss assert interface_two.getparam("duplicate") != dup assert interface_two.getparam("jitter") != jitter # when link_options = LinkOptions( - delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( node_one.id, @@ -144,7 +144,7 @@ def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # then assert interface_two.getparam("delay") == delay assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per + assert interface_two.getparam("loss") == loss assert interface_two.getparam("duplicate") == dup assert interface_two.getparam("jitter") == jitter @@ -152,7 +152,7 @@ def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given delay = 50 bandwidth = 5000000 - per = 25 + loss = 25 dup = 25 jitter = 10 node_one = session.add_node(CoreNode) @@ -166,18 +166,18 @@ def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): interface_two = node_two.netif(interface_two_data.id) assert interface_one.getparam("delay") != delay assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per + assert interface_one.getparam("loss") != loss assert interface_one.getparam("duplicate") != dup assert interface_one.getparam("jitter") != jitter assert interface_two.getparam("delay") != delay assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per + assert interface_two.getparam("loss") != loss assert interface_two.getparam("duplicate") != dup assert interface_two.getparam("jitter") != jitter # when link_options = LinkOptions( - delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( node_one.id, @@ -190,12 +190,12 @@ def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): # then assert interface_one.getparam("delay") == delay assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per + assert interface_one.getparam("loss") == loss assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("jitter") == jitter assert interface_two.getparam("delay") == delay assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per + assert interface_two.getparam("loss") == loss assert interface_two.getparam("duplicate") == dup assert interface_two.getparam("jitter") == jitter diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index c40a9ef3e..0345daed1 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -304,7 +304,7 @@ def test_link_options( # create link link_options = LinkOptions() - link_options.per = 10.5 + link_options.loss = 10.5 link_options.bandwidth = 50000 link_options.jitter = 10 link_options.delay = 30 @@ -347,7 +347,7 @@ def test_link_options( node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per + assert link_options.loss == link.loss assert link_options.bandwidth == link.bandwidth assert link_options.jitter == link.jitter assert link_options.delay == link.delay @@ -371,7 +371,7 @@ def test_link_options_ptp( # create link link_options = LinkOptions() - link_options.per = 10.5 + link_options.loss = 10.5 link_options.bandwidth = 50000 link_options.jitter = 10 link_options.delay = 30 @@ -416,7 +416,7 @@ def test_link_options_ptp( node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per + assert link_options.loss == link.loss assert link_options.bandwidth == link.bandwidth assert link_options.jitter == link.jitter assert link_options.delay == link.delay @@ -443,7 +443,7 @@ def test_link_options_bidirectional( link_options_one.unidirectional = 1 link_options_one.bandwidth = 5000 link_options_one.delay = 10 - link_options_one.per = 10.5 + link_options_one.loss = 10.5 link_options_one.dup = 5 link_options_one.jitter = 5 session.add_link( @@ -453,7 +453,7 @@ def test_link_options_bidirectional( link_options_two.unidirectional = 1 link_options_two.bandwidth = 10000 link_options_two.delay = 20 - link_options_two.per = 10 + link_options_two.loss = 10 link_options_two.dup = 10 link_options_two.jitter = 10 session.update_link( @@ -504,11 +504,11 @@ def test_link_options_bidirectional( link_two = links[1] assert link_options_one.bandwidth == link_one.bandwidth assert link_options_one.delay == link_one.delay - assert link_options_one.per == link_one.per + assert link_options_one.loss == link_one.loss assert link_options_one.dup == link_one.dup assert link_options_one.jitter == link_one.jitter assert link_options_two.bandwidth == link_two.bandwidth assert link_options_two.delay == link_two.delay - assert link_options_two.per == link_two.per + assert link_options_two.loss == link_two.loss assert link_options_two.dup == link_two.dup assert link_options_two.jitter == link_two.jitter From 876699e8efeb03daee471b0662d7d3eba8183917 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 16:52:41 -0700 Subject: [PATCH 011/210] variable/grpc cleanup to rename everything using spelt out numbers instead of actual numbers --- daemon/core/api/grpc/client.py | 94 +++---- daemon/core/api/grpc/grpcutils.py | 38 +-- daemon/core/api/grpc/server.py | 120 +++++---- daemon/core/api/tlv/corehandlers.py | 54 ++-- daemon/core/emane/linkmonitor.py | 20 +- daemon/core/emulator/distributed.py | 12 +- daemon/core/emulator/session.py | 154 +++++------ daemon/core/gui/coreclient.py | 58 ++--- daemon/core/gui/dialogs/linkconfig.py | 48 ++-- daemon/core/gui/graph/edges.py | 14 +- daemon/core/gui/graph/graph.py | 78 +++--- daemon/core/gui/interface.py | 24 +- daemon/core/plugins/sdt.py | 56 ++-- daemon/core/xml/corexml.py | 56 ++-- daemon/examples/configservices/testing.py | 14 +- daemon/examples/docker/docker2core.py | 10 +- daemon/examples/docker/docker2docker.py | 10 +- daemon/examples/docker/switch.py | 12 +- daemon/examples/grpc/distributed_switch.py | 12 +- daemon/examples/grpc/emane80211.py | 8 +- daemon/examples/grpc/switch.py | 8 +- daemon/examples/grpc/wlan.py | 8 +- daemon/examples/lxd/lxd2core.py | 10 +- daemon/examples/lxd/lxd2lxd.py | 10 +- daemon/examples/lxd/switch.py | 18 +- daemon/examples/python/distributed_emane.py | 12 +- daemon/examples/python/distributed_lxd.py | 10 +- daemon/examples/python/distributed_ptp.py | 10 +- daemon/examples/python/distributed_switch.py | 12 +- daemon/examples/python/emane80211.py | 2 +- daemon/examples/python/switch.py | 2 +- daemon/examples/python/switch_inject.py | 2 +- daemon/examples/python/wlan.py | 2 +- daemon/proto/core/api/grpc/core.proto | 28 +- daemon/proto/core/api/grpc/emane.proto | 16 +- daemon/proto/core/api/grpc/wlan.proto | 4 +- daemon/tests/emane/test_emane.py | 37 +-- daemon/tests/test_conf.py | 16 +- daemon/tests/test_core.py | 64 ++--- daemon/tests/test_grpc.py | 54 ++-- daemon/tests/test_gui.py | 159 ++++++------ daemon/tests/test_links.py | 246 +++++++++--------- daemon/tests/test_services.py | 20 +- daemon/tests/test_xml.py | 258 +++++++++---------- docs/scripting.md | 2 +- 45 files changed, 934 insertions(+), 968 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 1bc880699..3a16d4fd0 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -609,30 +609,30 @@ def get_node_links( def add_link( self, session_id: int, - node_one_id: int, - node_two_id: int, - interface_one: core_pb2.Interface = None, - interface_two: core_pb2.Interface = None, + node1_id: int, + node2_id: int, + interface1: core_pb2.Interface = None, + interface2: core_pb2.Interface = None, options: core_pb2.LinkOptions = None, ) -> core_pb2.AddLinkResponse: """ Add a link between nodes. :param session_id: session id - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one: node one interface data - :param interface_two: node two interface data + :param node1_id: node one id + :param node2_id: node two id + :param interface1: node one interface data + :param interface2: node two interface data :param options: options for link (jitter, bandwidth, etc) :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ link = core_pb2.Link( - node_one_id=node_one_id, - node_two_id=node_two_id, + node1_id=node1_id, + node2_id=node2_id, type=core_pb2.LinkType.WIRED, - interface_one=interface_one, - interface_two=interface_two, + interface1=interface1, + interface2=interface2, options=options, ) request = core_pb2.AddLinkRequest(session_id=session_id, link=link) @@ -641,59 +641,59 @@ def add_link( def edit_link( self, session_id: int, - node_one_id: int, - node_two_id: int, + node1_id: int, + node2_id: int, options: core_pb2.LinkOptions, - interface_one_id: int = None, - interface_two_id: int = None, + interface1_id: int = None, + interface2_id: int = None, ) -> core_pb2.EditLinkResponse: """ Edit a link between nodes. :param session_id: session id - :param node_one_id: node one id - :param node_two_id: node two id + :param node1_id: node one id + :param node2_id: node two id :param options: options for link (jitter, bandwidth, etc) - :param interface_one_id: node one interface id - :param interface_two_id: node two interface id + :param interface1_id: node one interface id + :param interface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ request = core_pb2.EditLinkRequest( session_id=session_id, - node_one_id=node_one_id, - node_two_id=node_two_id, + node1_id=node1_id, + node2_id=node2_id, options=options, - interface_one_id=interface_one_id, - interface_two_id=interface_two_id, + interface1_id=interface1_id, + interface2_id=interface2_id, ) return self.stub.EditLink(request) def delete_link( self, session_id: int, - node_one_id: int, - node_two_id: int, - interface_one_id: int = None, - interface_two_id: int = None, + node1_id: int, + node2_id: int, + interface1_id: int = None, + interface2_id: int = None, ) -> core_pb2.DeleteLinkResponse: """ Delete a link between nodes. :param session_id: session id - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one_id: node one interface id - :param interface_two_id: node two interface id + :param node1_id: node one id + :param node2_id: node two id + :param interface1_id: node one interface id + :param interface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.DeleteLinkRequest( session_id=session_id, - node_one_id=node_one_id, - node_two_id=node_two_id, - interface_one_id=interface_one_id, - interface_two_id=interface_two_id, + node1_id=node1_id, + node2_id=node2_id, + interface1_id=interface1_id, + interface2_id=interface2_id, ) return self.stub.DeleteLink(request) @@ -1111,20 +1111,20 @@ def open_xml(self, file_path: str, start: bool = False) -> core_pb2.OpenXmlRespo return self.stub.OpenXml(request) def emane_link( - self, session_id: int, nem_one: int, nem_two: int, linked: bool + self, session_id: int, nem1: int, nem2: int, linked: bool ) -> EmaneLinkResponse: """ Helps broadcast wireless link/unlink between EMANE nodes. :param session_id: session to emane link - :param nem_one: first nem for emane link - :param nem_two: second nem for emane link + :param nem1: first nem for emane link + :param nem2: second nem for emane link :param linked: True to link, False to unlink :return: get emane link response :raises grpc.RpcError: when session or nodes related to nems do not exist """ request = EmaneLinkRequest( - session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked + session_id=session_id, nem1=nem1, nem2=nem2, linked=linked ) return self.stub.EmaneLink(request) @@ -1243,24 +1243,24 @@ def execute_script(self, script: str) -> ExecuteScriptResponse: return self.stub.ExecuteScript(request) def wlan_link( - self, session_id: int, wlan: int, node_one: int, node_two: int, linked: bool + self, session_id: int, wlan_id: int, node1_id: int, node2_id: int, linked: bool ) -> WlanLinkResponse: """ Links/unlinks nodes on the same WLAN. :param session_id: session id containing wlan and nodes - :param wlan: wlan nodes must belong to - :param node_one: first node of pair to link/unlink - :param node_two: second node of pair to link/unlin + :param wlan_id: wlan nodes must belong to + :param node1_id: first node of pair to link/unlink + :param node2_id: second node of pair to link/unlin :param linked: True to link, False to unlink :return: wlan link response :raises grpc.RpcError: when session or one of the nodes do not exist """ request = WlanLinkRequest( session_id=session_id, - wlan=wlan, - node_one=node_one, - node_two=node_two, + wlan=wlan_id, + node1_id=node1_id, + node2_id=node2_id, linked=linked, ) return self.stub.WlanLink(request) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 73d19a2a3..539face1e 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -86,8 +86,8 @@ def add_link_data( :param link_proto: link proto :return: link interfaces and options """ - interface_one = link_interface(link_proto.interface_one) - interface_two = link_interface(link_proto.interface_two) + interface1_data = link_interface(link_proto.interface1) + interface2_data = link_interface(link_proto.interface2) link_type = LinkTypes(link_proto.type) options = LinkOptions(type=link_type) options_data = link_proto.options @@ -103,7 +103,7 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return interface_one, interface_two, options + return interface1_data, interface2_data, options def create_nodes( @@ -141,10 +141,10 @@ def create_links( """ funcs = [] for link_proto in link_protos: - node_one_id = link_proto.node_one_id - node_two_id = link_proto.node_two_id - interface_one, interface_two, options = add_link_data(link_proto) - args = (node_one_id, node_two_id, interface_one, interface_two, options) + node1_id = link_proto.node1_id + node2_id = link_proto.node2_id + interface1, interface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, interface1, interface2, options) funcs.append((session.add_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -165,10 +165,10 @@ def edit_links( """ funcs = [] for link_proto in link_protos: - node_one_id = link_proto.node_one_id - node_two_id = link_proto.node_two_id - interface_one, interface_two, options = add_link_data(link_proto) - args = (node_one_id, node_two_id, interface_one.id, interface_two.id, options) + node1_id = link_proto.node1_id + node2_id = link_proto.node2_id + interface1, interface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, interface1.id, interface2.id, options) funcs.append((session.update_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -315,9 +315,9 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: :param link_data: link to convert :return: core protobuf Link """ - interface_one = None + interface1 = None if link_data.interface1_id is not None: - interface_one = core_pb2.Interface( + interface1 = core_pb2.Interface( id=link_data.interface1_id, name=link_data.interface1_name, mac=convert_value(link_data.interface1_mac), @@ -326,9 +326,9 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: ip6=convert_value(link_data.interface1_ip6), ip6mask=link_data.interface1_ip6_mask, ) - interface_two = None + interface2 = None if link_data.interface2_id is not None: - interface_two = core_pb2.Interface( + interface2 = core_pb2.Interface( id=link_data.interface2_id, name=link_data.interface2_name, mac=convert_value(link_data.interface2_mac), @@ -352,10 +352,10 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: ) return core_pb2.Link( type=link_data.link_type.value, - node_one_id=link_data.node1_id, - node_two_id=link_data.node2_id, - interface_one=interface_one, - interface_two=interface_two, + node1_id=link_data.node1_id, + node2_id=link_data.node2_id, + interface1=interface1, + interface2=interface2, options=options, network_id=link_data.network_id, label=link_data.label, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index adddff145..a0ddf806e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -845,27 +845,23 @@ def AddLink( :return: add-link response """ logging.debug("add link: %s", request) - # validate session and nodes session = self.get_session(request.session_id, context) - self.get_node(session, request.link.node_one_id, context, NodeBase) - self.get_node(session, request.link.node_two_id, context, NodeBase) - - node_one_id = request.link.node_one_id - node_two_id = request.link.node_two_id - interface_one, interface_two, options = grpcutils.add_link_data(request.link) - node_one_interface, node_two_interface = session.add_link( - node_one_id, node_two_id, interface_one, interface_two, options=options + node1_id = request.link.node1_id + node2_id = request.link.node2_id + self.get_node(session, node1_id, context, NodeBase) + self.get_node(session, node2_id, context, NodeBase) + interface1, interface2, options = grpcutils.add_link_data(request.link) + node1_interface, node2_interface = session.add_link( + node1_id, node2_id, interface1, interface2, options=options ) - interface_one_proto = None - interface_two_proto = None - if node_one_interface: - interface_one_proto = grpcutils.interface_to_proto(node_one_interface) - if node_two_interface: - interface_two_proto = grpcutils.interface_to_proto(node_two_interface) + interface1_proto = None + interface2_proto = None + if node1_interface: + interface1_proto = grpcutils.interface_to_proto(node1_interface) + if node2_interface: + interface2_proto = grpcutils.interface_to_proto(node2_interface) return core_pb2.AddLinkResponse( - result=True, - interface_one=interface_one_proto, - interface_two=interface_two_proto, + result=True, interface1=interface1_proto, interface2=interface2_proto ) def EditLink( @@ -880,10 +876,10 @@ def EditLink( """ logging.debug("edit link: %s", request) session = self.get_session(request.session_id, context) - node_one_id = request.node_one_id - node_two_id = request.node_two_id - interface_one_id = request.interface_one_id - interface_two_id = request.interface_two_id + node1_id = request.node1_id + node2_id = request.node2_id + interface1_id = request.interface1_id + interface2_id = request.interface2_id options_data = request.options link_options = LinkOptions() link_options.delay = options_data.delay @@ -898,7 +894,7 @@ def EditLink( link_options.key = options_data.key link_options.opaque = options_data.opaque session.update_link( - node_one_id, node_two_id, interface_one_id, interface_two_id, link_options + node1_id, node2_id, interface1_id, interface2_id, link_options ) return core_pb2.EditLinkResponse(result=True) @@ -914,13 +910,11 @@ def DeleteLink( """ logging.debug("delete link: %s", request) session = self.get_session(request.session_id, context) - node_one_id = request.node_one_id - node_two_id = request.node_two_id - interface_one_id = request.interface_one_id - interface_two_id = request.interface_two_id - session.delete_link( - node_one_id, node_two_id, interface_one_id, interface_two_id - ) + node1_id = request.node1_id + node2_id = request.node2_id + interface1_id = request.interface1_id + interface2_id = request.interface2_id + session.delete_link(node1_id, node2_id, interface1_id, interface2_id) return core_pb2.DeleteLinkResponse(result=True) def GetHooks( @@ -1519,30 +1513,30 @@ def EmaneLink( """ logging.debug("emane link: %s", request) session = self.get_session(request.session_id, context) - nem_one = request.nem_one - emane_one, netif = session.emane.nemlookup(nem_one) - if not emane_one or not netif: - context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem_one} not found") - node_one = netif.node - - nem_two = request.nem_two - emane_two, netif = session.emane.nemlookup(nem_two) - if not emane_two or not netif: - context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem_two} not found") - node_two = netif.node - - if emane_one.id == emane_two.id: + nem1 = request.nem1 + emane1, netif = session.emane.nemlookup(nem1) + if not emane1 or not netif: + context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found") + node1 = netif.node + + nem2 = request.nem2 + emane2, netif = session.emane.nemlookup(nem2) + if not emane2 or not netif: + context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found") + node2 = netif.node + + if emane1.id == emane2.id: if request.linked: flag = MessageFlags.ADD else: flag = MessageFlags.DELETE - color = session.get_link_color(emane_one.id) + color = session.get_link_color(emane1.id) link = LinkData( message_type=flag, link_type=LinkTypes.WIRELESS, - node1_id=node_one.id, - node2_id=node_two.id, - network_id=emane_one.id, + node1_id=node1.id, + node2_id=node2.id, + network_id=emane1.id, color=color, ) session.broadcast_link(link) @@ -1739,21 +1733,23 @@ def WlanLink( grpc.StatusCode.NOT_FOUND, f"wlan node {request.wlan} does not using BasicRangeModel", ) - n1 = self.get_node(session, request.node_one, context, CoreNode) - n2 = self.get_node(session, request.node_two, context, CoreNode) - n1_netif, n2_netif = None, None - for net, netif1, netif2 in n1.commonnets(n2): + node1 = self.get_node(session, request.node1_id, context, CoreNode) + node2 = self.get_node(session, request.node2_id, context, CoreNode) + node1_interface, node2_interface = None, None + for net, interface1, interface2 in node1.commonnets(node2): if net == wlan: - n1_netif = netif1 - n2_netif = netif2 + node1_interface = interface1 + node2_interface = interface2 break result = False - if n1_netif and n2_netif: + if node1_interface and node2_interface: if request.linked: - wlan.link(n1_netif, n2_netif) + wlan.link(node1_interface, node2_interface) else: - wlan.unlink(n1_netif, n2_netif) - wlan.model.sendlinkmsg(n1_netif, n2_netif, unlink=not request.linked) + wlan.unlink(node1_interface, node2_interface) + wlan.model.sendlinkmsg( + node1_interface, node2_interface, unlink=not request.linked + ) result = True return WlanLinkResponse(result=result) @@ -1764,9 +1760,9 @@ def EmanePathlosses( ) -> EmanePathlossesResponse: for request in request_iterator: session = self.get_session(request.session_id, context) - n1 = self.get_node(session, request.node_one, context, CoreNode) - nem1 = grpcutils.get_nem_id(n1, request.interface_one_id, context) - n2 = self.get_node(session, request.node_two, context, CoreNode) - nem2 = grpcutils.get_nem_id(n2, request.interface_two_id, context) - session.emane.publish_pathloss(nem1, nem2, request.rx_one, request.rx_two) + node1 = self.get_node(session, request.node1_id, context, CoreNode) + nem1 = grpcutils.get_nem_id(node1, request.interface1_id, context) + node2 = self.get_node(session, request.node2_id, context, CoreNode) + nem2 = grpcutils.get_nem_id(node2, request.interface2_id, context) + session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2) return EmanePathlossesResponse() diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3adaed63f..e7a67b3e8 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -745,10 +745,9 @@ def handle_link_message(self, message): :param core.api.tlv.coreapi.CoreLinkMessage message: link message to handle :return: link message replies """ - node_one_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) - node_two_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) - - interface_one = InterfaceData( + node1_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) + node2_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) + interface1_data = InterfaceData( id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value), mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value), @@ -757,7 +756,7 @@ def handle_link_message(self, message): ip6=message.get_tlv(LinkTlvs.INTERFACE1_IP6.value), ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value), ) - interface_two = InterfaceData( + interface2_data = InterfaceData( id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value), mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value), @@ -766,45 +765,38 @@ def handle_link_message(self, message): ip6=message.get_tlv(LinkTlvs.INTERFACE2_IP6.value), ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value), ) - link_type = LinkTypes.WIRED link_type_value = message.get_tlv(LinkTlvs.TYPE.value) if link_type_value is not None: link_type = LinkTypes(link_type_value) - - link_options = LinkOptions(type=link_type) - link_options.delay = message.get_tlv(LinkTlvs.DELAY.value) - link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) - link_options.session = message.get_tlv(LinkTlvs.SESSION.value) - link_options.per = message.get_tlv(LinkTlvs.PER.value) - link_options.dup = message.get_tlv(LinkTlvs.DUP.value) - link_options.jitter = message.get_tlv(LinkTlvs.JITTER.value) - link_options.mer = message.get_tlv(LinkTlvs.MER.value) - link_options.burst = message.get_tlv(LinkTlvs.BURST.value) - link_options.mburst = message.get_tlv(LinkTlvs.MBURST.value) - link_options.gui_attributes = message.get_tlv(LinkTlvs.GUI_ATTRIBUTES.value) - link_options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value) - link_options.emulation_id = message.get_tlv(LinkTlvs.EMULATION_ID.value) - link_options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) - link_options.key = message.get_tlv(LinkTlvs.KEY.value) - link_options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) + options = LinkOptions(type=link_type) + options.delay = message.get_tlv(LinkTlvs.DELAY.value) + options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) + options.session = message.get_tlv(LinkTlvs.SESSION.value) + options.per = message.get_tlv(LinkTlvs.PER.value) + options.dup = message.get_tlv(LinkTlvs.DUP.value) + options.jitter = message.get_tlv(LinkTlvs.JITTER.value) + options.mer = message.get_tlv(LinkTlvs.MER.value) + options.burst = message.get_tlv(LinkTlvs.BURST.value) + options.mburst = message.get_tlv(LinkTlvs.MBURST.value) + options.gui_attributes = message.get_tlv(LinkTlvs.GUI_ATTRIBUTES.value) + options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value) + options.emulation_id = message.get_tlv(LinkTlvs.EMULATION_ID.value) + options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) + options.key = message.get_tlv(LinkTlvs.KEY.value) + options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: self.session.add_link( - node_one_id, node_two_id, interface_one, interface_two, link_options + node1_id, node2_id, interface1_data, interface2_data, options ) elif message.flags & MessageFlags.DELETE.value: self.session.delete_link( - node_one_id, node_two_id, interface_one.id, interface_two.id + node1_id, node2_id, interface1_data.id, interface2_data.id ) else: self.session.update_link( - node_one_id, - node_two_id, - interface_one.id, - interface_two.id, - link_options, + node1_id, node2_id, interface1_data.id, interface2_data.id, options ) - return () def handle_execute_message(self, message): diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index b9fd9a2a8..ca9f44933 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -269,11 +269,11 @@ def check_links(self) -> None: self.scheduler.enter(self.link_interval, 0, self.check_links) def get_complete_id(self, link_id: Tuple[int, int]) -> Tuple[int, int]: - value_one, value_two = link_id - if value_one < value_two: - return value_one, value_two + value1, value2 = link_id + if value1 < value2: + return value1, value2 else: - return value_two, value_one + return value2, value1 def is_complete_link(self, link_id: Tuple[int, int]) -> bool: reverse_id = link_id[1], link_id[0] @@ -287,8 +287,8 @@ def get_link_label(self, link_id: Tuple[int, int]) -> str: return f"{source_link.sinr:.1f} / {dest_link.sinr:.1f}" def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None: - nem_one, nem_two = link_id - link = self.emane_manager.get_nem_link(nem_one, nem_two, message_type) + nem1, nem2 = link_id + link = self.emane_manager.get_nem_link(nem1, nem2, message_type) if link: label = self.get_link_label(link_id) link.label = label @@ -298,16 +298,16 @@ def send_message( self, message_type: MessageFlags, label: str, - node_one: int, - node_two: int, + node1: int, + node2: int, emane_id: int, ) -> None: color = self.emane_manager.session.get_link_color(emane_id) link_data = LinkData( message_type=message_type, label=label, - node1_id=node_one, - node2_id=node_two, + node1_id=node1, + node2_id=node2, network_id=emane_id, link_type=LinkTypes.WIRELESS, color=color, diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 3753e1c23..75081447f 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -224,18 +224,20 @@ def create_gre_tunnel( self.tunnels[key] = tunnel return tunnel - def tunnel_key(self, n1_id: int, n2_id: int) -> int: + def tunnel_key(self, node1_id: int, node2_id: int) -> int: """ Compute a 32-bit key used to uniquely identify a GRE tunnel. The hash(n1num), hash(n2num) values are used, so node numbers may be None or string values (used for e.g. "ctrlnet"). - :param n1_id: node one id - :param n2_id: node two id + :param node1_id: node one id + :param node2_id: node two id :return: tunnel key for the node pair """ - logging.debug("creating tunnel key for: %s, %s", n1_id, n2_id) + logging.debug("creating tunnel key for: %s, %s", node1_id, node2_id) key = ( - (self.session.id << 16) ^ utils.hashkey(n1_id) ^ (utils.hashkey(n2_id) << 8) + (self.session.id << 16) + ^ utils.hashkey(node1_id) + ^ (utils.hashkey(node2_id) << 8) ) return key & 0xFFFFFFFF diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 854d5cc80..0a90b9431 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -194,13 +194,13 @@ def get_node_type(cls, _class: Type[NodeBase]) -> NodeTypes: return node_type def _link_wireless( - self, node_one: CoreNodeBase, node_two: CoreNodeBase, connect: bool + self, node1: CoreNodeBase, node2: CoreNodeBase, connect: bool ) -> None: """ Objects to deal with when connecting/disconnecting wireless links. - :param node_one: node one for wireless link - :param node_two: node two for wireless link + :param node1: node one for wireless link + :param node2: node two for wireless link :param connect: link interfaces if True, unlink otherwise :return: nothing :raises core.CoreError: when objects to link is less than 2, or no common @@ -208,14 +208,14 @@ def _link_wireless( """ logging.info( "handling wireless linking node1(%s) node2(%s): %s", - node_one.name, - node_two.name, + node1.name, + node2.name, connect, ) - common_networks = node_one.commonnets(node_one) + common_networks = node1.commonnets(node1) if not common_networks: raise CoreError("no common network found for wireless link/unlink") - for common_network, interface_one, interface_two in common_networks: + for common_network, interface1, interface2 in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): logging.info( "skipping common network that is not wireless/emane: %s", @@ -223,26 +223,26 @@ def _link_wireless( ) continue if connect: - common_network.link(interface_one, interface_two) + common_network.link(interface1, interface2) else: - common_network.unlink(interface_one, interface_two) + common_network.unlink(interface1, interface2) def add_link( self, - node_one_id: int, - node_two_id: int, - interface_one: InterfaceData = None, - interface_two: InterfaceData = None, + node1_id: int, + node2_id: int, + interface1_data: InterfaceData = None, + interface2_data: InterfaceData = None, options: LinkOptions = None, ) -> Tuple[CoreInterface, CoreInterface]: """ Add a link between nodes. - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one: node one interface + :param node1_id: node one id + :param node2_id: node two id + :param interface1_data: node one interface data, defaults to none - :param interface_two: node two interface + :param interface2_data: node two interface data, defaults to none :param options: data for creating link, defaults to no options @@ -250,10 +250,10 @@ def add_link( """ if not options: options = LinkOptions() - node1 = self.get_node(node_one_id, NodeBase) - node2 = self.get_node(node_two_id, NodeBase) - node1_interface = None - node2_interface = None + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) + interface1 = None + interface2 = None # wireless link if options.type == LinkTypes.WIRELESS: @@ -270,22 +270,22 @@ def add_link( logging.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() ptp = self.create_node(PtpNet, start=start) - node1_interface = node1.newnetif(ptp, interface_one) - node2_interface = node2.newnetif(ptp, interface_two) - ptp.linkconfig(node1_interface, options) + interface1 = node1.newnetif(ptp, interface1_data) + interface2 = node2.newnetif(ptp, interface2_data) + ptp.linkconfig(interface1, options) if not options.unidirectional: - ptp.linkconfig(node2_interface, options) + ptp.linkconfig(interface2, options) # link node to net elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - node1_interface = node1.newnetif(node2, interface_one) + interface1 = node1.newnetif(node2, interface1_data) if not isinstance(node2, (EmaneNet, WlanNode)): - node2.linkconfig(node1_interface, options) + node2.linkconfig(interface1, options) # link net to node elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - node2_interface = node2.newnetif(node1, interface_two) + interface2 = node2.newnetif(node1, interface2_data) wireless_net = isinstance(node1, (EmaneNet, WlanNode)) if not options.unidirectional and not wireless_net: - node1.linkconfig(node2_interface, options) + node1.linkconfig(interface2, options) # network to network elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase @@ -293,12 +293,12 @@ def add_link( logging.info( "linking network to network: %s - %s", node1.name, node2.name ) - node1_interface = node1.linknet(node2) - node1.linkconfig(node1_interface, options) + interface1 = node1.linknet(node2) + node1.linkconfig(interface1, options) if not options.unidirectional: - node1_interface.swapparams("_params_up") - node2.linkconfig(node1_interface, options) - node1_interface.swapparams("_params_up") + interface1.swapparams("_params_up") + node2.linkconfig(interface1, options) + interface1.swapparams("_params_up") else: raise CoreError( f"cannot link node1({type(node1)}) node2({type(node2)})" @@ -308,41 +308,41 @@ def add_link( key = options.key if isinstance(node1, TunnelNode): logging.info("setting tunnel key for: %s", node1.name) - node1.setkey(key, interface_one) + node1.setkey(key, interface1_data) if isinstance(node2, TunnelNode): logging.info("setting tunnel key for: %s", node2.name) - node2.setkey(key, interface_two) - self.sdt.add_link(node_one_id, node_two_id) - return node1_interface, node2_interface + node2.setkey(key, interface2_data) + self.sdt.add_link(node1_id, node2_id) + return interface1, interface2 def delete_link( self, - node_one_id: int, - node_two_id: int, - interface_one_id: int = None, - interface_two_id: int = None, + node1_id: int, + node2_id: int, + interface1_id: int = None, + interface2_id: int = None, link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ Delete a link between nodes. - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one_id: interface id for node one - :param interface_two_id: interface id for node two + :param node1_id: node one id + :param node2_id: node two id + :param interface1_id: interface id for node one + :param interface2_id: interface id for node two :param link_type: link type to delete :return: nothing :raises core.CoreError: when no common network is found for link being deleted """ - node1 = self.get_node(node_one_id, NodeBase) - node2 = self.get_node(node_two_id, NodeBase) + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) logging.info( "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", link_type.name, node1.name, - interface_one_id, + interface1_id, node2.name, - interface_two_id, + interface2_id, ) # wireless link @@ -357,15 +357,15 @@ def delete_link( # wired link else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface_one_id) - interface2 = node2.netif(interface_two_id) + interface1 = node1.netif(interface1_id) + interface2 = node2.netif(interface2_id) if not interface1: raise CoreError( - f"node({node1.name}) missing interface({interface_one_id})" + f"node({node1.name}) missing interface({interface1_id})" ) if not interface2: raise CoreError( - f"node({node2.name}) missing interface({interface_two_id})" + f"node({node2.name}) missing interface({interface2_id})" ) if interface1.net != interface2.net: raise CoreError( @@ -373,30 +373,30 @@ def delete_link( "not connected to same net" ) ptp = interface1.net - node1.delnetif(interface_one_id) - node2.delnetif(interface_two_id) + node1.delnetif(interface1_id) + node2.delnetif(interface2_id) self.delete_node(ptp.id) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - node1.delnetif(interface_one_id) + node1.delnetif(interface1_id) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - node2.delnetif(interface_two_id) - self.sdt.delete_link(node_one_id, node_two_id) + node2.delnetif(interface2_id) + self.sdt.delete_link(node1_id, node2_id) def update_link( self, - node_one_id: int, - node_two_id: int, - interface_one_id: int = None, - interface_two_id: int = None, + node1_id: int, + node2_id: int, + interface1_id: int = None, + interface2_id: int = None, options: LinkOptions = None, ) -> None: """ Update link information between nodes. - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one_id: interface id for node one - :param interface_two_id: interface id for node two + :param node1_id: node one id + :param node2_id: node two id + :param interface1_id: interface id for node one + :param interface2_id: interface id for node two :param options: data to update link with :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a @@ -404,15 +404,15 @@ def update_link( """ if not options: options = LinkOptions() - node1 = self.get_node(node_one_id, NodeBase) - node2 = self.get_node(node_two_id, NodeBase) + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) logging.info( "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", options.type.name, node1.name, - interface_one_id, + interface1_id, node2.name, - interface_two_id, + interface2_id, ) # wireless link @@ -420,15 +420,15 @@ def update_link( raise CoreError("cannot update wireless link") else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface_one_id) - interface2 = node2.netif(interface_two_id) + interface1 = node1.netif(interface1_id) + interface2 = node2.netif(interface2_id) if not interface1: raise CoreError( - f"node({node1.name}) missing interface({interface_one_id})" + f"node({node1.name}) missing interface({interface1_id})" ) if not interface2: raise CoreError( - f"node({node2.name}) missing interface({interface_two_id})" + f"node({node2.name}) missing interface({interface2_id})" ) if interface1.net != interface2.net: raise CoreError( @@ -440,10 +440,10 @@ def update_link( if not options.unidirectional: ptp.linkconfig(interface2, options, interface1) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - interface = node1.netif(interface_one_id) + interface = node1.netif(interface1_id) node2.linkconfig(interface, options) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - interface = node2.netif(interface_two_id) + interface = node2.netif(interface2_id) node1.linkconfig(interface, options) elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 2b565e7f6..5c1c52a0b 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -164,25 +164,19 @@ def handle_events(self, event: core_pb2.Event): def handle_link_event(self, event: core_pb2.LinkEvent): logging.debug("Link event: %s", event) - node_one_id = event.link.node_one_id - node_two_id = event.link.node_two_id - if node_one_id == node_two_id: + node1_id = event.link.node1_id + node2_id = event.link.node2_id + if node1_id == node2_id: logging.warning("ignoring links with loops: %s", event) return - canvas_node_one = self.canvas_nodes[node_one_id] - canvas_node_two = self.canvas_nodes[node_two_id] + canvas_node1 = self.canvas_nodes[node1_id] + canvas_node2 = self.canvas_nodes[node2_id] if event.message_type == core_pb2.MessageType.ADD: - self.app.canvas.add_wireless_edge( - canvas_node_one, canvas_node_two, event.link - ) + self.app.canvas.add_wireless_edge(canvas_node1, canvas_node2, event.link) elif event.message_type == core_pb2.MessageType.DELETE: - self.app.canvas.delete_wireless_edge( - canvas_node_one, canvas_node_two, event.link - ) + self.app.canvas.delete_wireless_edge(canvas_node1, canvas_node2, event.link) elif event.message_type == core_pb2.MessageType.NONE: - self.app.canvas.update_wireless_edge( - canvas_node_one, canvas_node_two, event.link - ) + self.app.canvas.update_wireless_edge(canvas_node1, canvas_node2, event.link) else: logging.warning("unknown link event: %s", event) @@ -472,10 +466,10 @@ def start_session(self) -> core_pb2.StartSessionResponse: for edge in self.links.values(): link = core_pb2.Link() link.CopyFrom(edge.link) - if link.HasField("interface_one") and not link.interface_one.mac: - link.interface_one.mac = self.interfaces_manager.next_mac() - if link.HasField("interface_two") and not link.interface_two.mac: - link.interface_two.mac = self.interfaces_manager.next_mac() + if link.HasField("interface1") and not link.interface1.mac: + link.interface1.mac = self.interfaces_manager.next_mac() + if link.HasField("interface2") and not link.interface2.mac: + link.interface2.mac = self.interfaces_manager.next_mac() links.append(link) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() @@ -693,10 +687,10 @@ def create_nodes_and_links(self): for link_proto in link_protos: response = self.client.add_link( self.session_id, - link_proto.node_one_id, - link_proto.node_two_id, - link_proto.interface_one, - link_proto.interface_two, + link_proto.node1_id, + link_proto.node2_id, + link_proto.interface1, + link_proto.interface2, link_proto.options, ) logging.debug("create link: %s", response) @@ -881,20 +875,20 @@ def create_link( link = core_pb2.Link( type=core_pb2.LinkType.WIRED, - node_one_id=src_node.id, - node_two_id=dst_node.id, - interface_one=src_interface, - interface_two=dst_interface, + node1_id=src_node.id, + node2_id=dst_node.id, + interface1=src_interface, + interface2=dst_interface, ) # assign after creating link proto, since interfaces are copied if src_interface: - interface_one = link.interface_one - edge.src_interface = interface_one - canvas_src_node.interfaces[interface_one.id] = interface_one + interface1 = link.interface1 + edge.src_interface = interface1 + canvas_src_node.interfaces[interface1.id] = interface1 if dst_interface: - interface_two = link.interface_two - edge.dst_interface = interface_two - canvas_dst_node.interfaces[interface_two.id] = interface_two + interface2 = link.interface2 + edge.dst_interface = interface2 + canvas_dst_node.interfaces[interface2.id] = interface2 edge.set_link(link) self.links[edge.token] = edge logging.info("Add link between %s and %s", src_node.name, dst_node.name) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 92361ed4a..1c20e2e10 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -227,21 +227,21 @@ def click_apply(self): ) link.options.CopyFrom(options) - interface_one = None - if link.HasField("interface_one"): - interface_one = link.interface_one.id - interface_two = None - if link.HasField("interface_two"): - interface_two = link.interface_two.id + interface1_id = None + if link.HasField("interface1"): + interface1_id = link.interface1.id + interface2_id = None + if link.HasField("interface2"): + interface2_id = link.interface2.id if not self.is_symmetric: link.options.unidirectional = True - asym_interface_one = None - if interface_one: - asym_interface_one = core_pb2.Interface(id=interface_one) - asym_interface_two = None - if interface_two: - asym_interface_two = core_pb2.Interface(id=interface_two) + asym_interface1 = None + if interface1_id: + asym_interface1 = core_pb2.Interface(id=interface1_id) + asym_interface2 = None + if interface2_id: + asym_interface2 = core_pb2.Interface(id=interface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) down_delay = get_int(self.down_delay) @@ -256,10 +256,10 @@ def click_apply(self): unidirectional=True, ) self.edge.asymmetric_link = core_pb2.Link( - node_one_id=link.node_two_id, - node_two_id=link.node_one_id, - interface_one=asym_interface_one, - interface_two=asym_interface_two, + node1_id=link.node2_id, + node2_id=link.node1_id, + interface1=asym_interface1, + interface2=asym_interface2, options=options, ) else: @@ -270,20 +270,20 @@ def click_apply(self): session_id = self.app.core.session_id self.app.core.client.edit_link( session_id, - link.node_one_id, - link.node_two_id, + link.node1_id, + link.node2_id, link.options, - interface_one, - interface_two, + interface1_id, + interface2_id, ) if self.edge.asymmetric_link: self.app.core.client.edit_link( session_id, - link.node_two_id, - link.node_one_id, + link.node2_id, + link.node1_id, self.edge.asymmetric_link.options, - interface_one, - interface_two, + interface1_id, + interface2_id, ) self.destroy() diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 00268c885..1d2264ebc 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -296,13 +296,13 @@ def interface_label(self, interface: core_pb2.Interface) -> str: return label def create_node_labels(self) -> Tuple[str, str]: - label_one = None - if self.link.HasField("interface_one"): - label_one = self.interface_label(self.link.interface_one) - label_two = None - if self.link.HasField("interface_two"): - label_two = self.interface_label(self.link.interface_two) - return label_one, label_two + label1 = None + if self.link.HasField("interface1"): + label1 = self.interface_label(self.link.interface1) + label2 = None + if self.link.HasField("interface2"): + label2 = self.interface_label(self.link.interface2) + return label1, label2 def draw_labels(self) -> None: src_text, dst_text = self.create_node_labels() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 3d6fd369a..90dcd9f6b 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -300,41 +300,39 @@ def draw_session(self, session: core_pb2.Session): # draw existing links for link in session.links: logging.debug("drawing link: %s", link) - canvas_node_one = self.core.canvas_nodes[link.node_one_id] - node_one = canvas_node_one.core_node - canvas_node_two = self.core.canvas_nodes[link.node_two_id] - node_two = canvas_node_two.core_node - token = create_edge_token(canvas_node_one.id, canvas_node_two.id) + canvas_node1 = self.core.canvas_nodes[link.node1_id] + node1 = canvas_node1.core_node + canvas_node2 = self.core.canvas_nodes[link.node2_id] + node2 = canvas_node2.core_node + token = create_edge_token(canvas_node1.id, canvas_node2.id) if link.type == core_pb2.LinkType.WIRELESS: - self.add_wireless_edge(canvas_node_one, canvas_node_two, link) + self.add_wireless_edge(canvas_node1, canvas_node2, link) else: if token not in self.edges: - src_pos = (node_one.position.x, node_one.position.y) - dst_pos = (node_two.position.x, node_two.position.y) - edge = CanvasEdge(self, canvas_node_one.id, src_pos, dst_pos) + src_pos = (node1.position.x, node1.position.y) + dst_pos = (node2.position.x, node2.position.y) + edge = CanvasEdge(self, canvas_node1.id, src_pos, dst_pos) edge.token = token - edge.dst = canvas_node_two.id + edge.dst = canvas_node2.id edge.set_link(link) edge.check_wireless() - canvas_node_one.edges.add(edge) - canvas_node_two.edges.add(edge) + canvas_node1.edges.add(edge) + canvas_node2.edges.add(edge) self.edges[edge.token] = edge self.core.links[edge.token] = edge - if link.HasField("interface_one"): - interface_one = link.interface_one + if link.HasField("interface1"): + interface1 = link.interface1 + self.core.interface_to_edge[(node1.id, interface1.id)] = token + canvas_node1.interfaces[interface1.id] = interface1 + edge.src_interface = interface1 + if link.HasField("interface2"): + interface2 = link.interface2 self.core.interface_to_edge[ - (node_one.id, interface_one.id) - ] = token - canvas_node_one.interfaces[interface_one.id] = interface_one - edge.src_interface = interface_one - if link.HasField("interface_two"): - interface_two = link.interface_two - self.core.interface_to_edge[ - (node_two.id, interface_two.id) + (node2.id, interface2.id) ] = edge.token - canvas_node_two.interfaces[interface_two.id] = interface_two - edge.dst_interface = interface_two + canvas_node2.interfaces[interface2.id] = interface2 + edge.dst_interface = interface2 elif link.options.unidirectional: edge = self.edges[token] edge.asymmetric_link = link @@ -965,26 +963,26 @@ def paste(self): copy_link = copy_edge.link options = edge.link.options copy_link.options.CopyFrom(options) - interface_one = None - if copy_link.HasField("interface_one"): - interface_one = copy_link.interface_one.id - interface_two = None - if copy_link.HasField("interface_two"): - interface_two = copy_link.interface_two.id + interface1_id = None + if copy_link.HasField("interface1"): + interface1_id = copy_link.interface1.id + interface2_id = None + if copy_link.HasField("interface2"): + interface2_id = copy_link.interface2.id if not options.unidirectional: copy_edge.asymmetric_link = None else: - asym_interface_one = None - if interface_one: - asym_interface_one = core_pb2.Interface(id=interface_one) - asym_interface_two = None - if interface_two: - asym_interface_two = core_pb2.Interface(id=interface_two) + asym_interface1 = None + if interface1_id: + asym_interface1 = core_pb2.Interface(id=interface1_id) + asym_interface2 = None + if interface2_id: + asym_interface2 = core_pb2.Interface(id=interface2_id) copy_edge.asymmetric_link = core_pb2.Link( - node_one_id=copy_link.node_two_id, - node_two_id=copy_link.node_one_id, - interface_one=asym_interface_one, - interface_two=asym_interface_two, + node1_id=copy_link.node2_id, + node2_id=copy_link.node1_id, + interface1=asym_interface1, + interface2=asym_interface2, options=edge.asymmetric_link.options, ) self.itemconfig( diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 1973fe995..34270f56b 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -89,21 +89,21 @@ def removed(self, links: List["core_pb2.Link"]) -> None: remaining_subnets = set() for edge in self.app.core.links.values(): link = edge.link - if link.HasField("interface_one"): - subnets = self.get_subnets(link.interface_one) + if link.HasField("interface1"): + subnets = self.get_subnets(link.interface1) remaining_subnets.add(subnets) - if link.HasField("interface_two"): - subnets = self.get_subnets(link.interface_two) + if link.HasField("interface2"): + subnets = self.get_subnets(link.interface2) remaining_subnets.add(subnets) # remove all subnets from used subnets when no longer present # or remove used indexes from subnet interfaces = [] for link in links: - if link.HasField("interface_one"): - interfaces.append(link.interface_one) - if link.HasField("interface_two"): - interfaces.append(link.interface_two) + if link.HasField("interface1"): + interfaces.append(link.interface1) + if link.HasField("interface2"): + interfaces.append(link.interface2) for interface in interfaces: subnets = self.get_subnets(interface) if subnets not in remaining_subnets: @@ -117,10 +117,10 @@ def removed(self, links: List["core_pb2.Link"]) -> None: def joined(self, links: List["core_pb2.Link"]) -> None: interfaces = [] for link in links: - if link.HasField("interface_one"): - interfaces.append(link.interface_one) - if link.HasField("interface_two"): - interfaces.append(link.interface_two) + if link.HasField("interface1"): + interfaces.append(link.interface1) + if link.HasField("interface2"): + interfaces.append(link.interface2) # add to used subnets and mark used indexes for interface in interfaces: diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 8b4ec39fe..062217cb5 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -21,8 +21,8 @@ from core.emulator.session import Session -def get_link_id(node_one: int, node_two: int, network_id: int) -> str: - link_id = f"{node_one}-{node_two}" +def get_link_id(node1_id: int, node2_id: int, network_id: int) -> str: + link_id = f"{node1_id}-{node2_id}" if network_id is not None: link_id = f"{link_id}-{network_id}" return link_id @@ -351,27 +351,27 @@ def wireless_net_check(self, node_id: int) -> bool: return result def add_link( - self, node_one: int, node_two: int, network_id: int = None, label: str = None + self, node1_id: int, node2_id: int, network_id: int = None, label: str = None ) -> None: """ Handle adding a link in SDT. - :param node_one: node one id - :param node_two: node two id + :param node1_id: node one id + :param node2_id: node two id :param network_id: network link is associated with, None otherwise :param label: label for link :return: nothing """ - logging.debug("sdt add link: %s, %s, %s", node_one, node_two, network_id) + logging.debug("sdt add link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return - if self.wireless_net_check(node_one) or self.wireless_net_check(node_two): + if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): return color = DEFAULT_LINK_COLOR if network_id: color = self.session.get_link_color(network_id) line = f"{color},2" - link_id = get_link_id(node_one, node_two, network_id) + link_id = get_link_id(node1_id, node2_id, network_id) layer = LINK_LAYER if network_id: node = self.session.nodes.get(network_id) @@ -383,47 +383,47 @@ def add_link( if label: link_label = f'linklabel on,"{label}"' self.cmd( - f"link {node_one},{node_two},{link_id} linkLayer {layer} line {line} " + f"link {node1_id},{node2_id},{link_id} linkLayer {layer} line {line} " f"{link_label}" ) - def delete_link(self, node_one: int, node_two: int, network_id: int = None) -> None: + def delete_link(self, node1_id: int, node2_id: int, network_id: int = None) -> None: """ Handle deleting a link in SDT. - :param node_one: node one id - :param node_two: node two id + :param node1_id: node one id + :param node2_id: node two id :param network_id: network link is associated with, None otherwise :return: nothing """ - logging.debug("sdt delete link: %s, %s, %s", node_one, node_two, network_id) + logging.debug("sdt delete link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return - if self.wireless_net_check(node_one) or self.wireless_net_check(node_two): + if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): return - link_id = get_link_id(node_one, node_two, network_id) - self.cmd(f"delete link,{node_one},{node_two},{link_id}") + link_id = get_link_id(node1_id, node2_id, network_id) + self.cmd(f"delete link,{node1_id},{node2_id},{link_id}") def edit_link( - self, node_one: int, node_two: int, network_id: int, label: str + self, node1_id: int, node2_id: int, network_id: int, label: str ) -> None: """ Handle editing a link in SDT. - :param node_one: node one id - :param node_two: node two id + :param node1_id: node one id + :param node2_id: node two id :param network_id: network link is associated with, None otherwise :param label: label to update :return: nothing """ - logging.debug("sdt edit link: %s, %s, %s", node_one, node_two, network_id) + logging.debug("sdt edit link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return - if self.wireless_net_check(node_one) or self.wireless_net_check(node_two): + if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): return - link_id = get_link_id(node_one, node_two, network_id) + link_id = get_link_id(node1_id, node2_id, network_id) link_label = f'linklabel on,"{label}"' - self.cmd(f"link {node_one},{node_two},{link_id} {link_label}") + self.cmd(f"link {node1_id},{node2_id},{link_id} {link_label}") def handle_link_update(self, link_data: LinkData) -> None: """ @@ -432,13 +432,13 @@ def handle_link_update(self, link_data: LinkData) -> None: :param link_data: link data to handle :return: nothing """ - node_one = link_data.node1_id - node_two = link_data.node2_id + node1_id = link_data.node1_id + node2_id = link_data.node2_id network_id = link_data.network_id label = link_data.label if link_data.message_type == MessageFlags.ADD: - self.add_link(node_one, node_two, network_id, label) + self.add_link(node1_id, node2_id, network_id, label) elif link_data.message_type == MessageFlags.DELETE: - self.delete_link(node_one, node_two, network_id) + self.delete_link(node1_id, node2_id, network_id) elif link_data.message_type == MessageFlags.NONE and label: - self.edit_link(node_one, node_two, network_id, label) + self.edit_link(node1_id, node2_id, network_id, label) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 973eb77fa..afc1d8263 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -534,7 +534,7 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: # check for interface one if link_data.interface1_id is not None: - interface_one = self.create_interface_element( + interface1 = self.create_interface_element( "interface_one", link_data.node1_id, link_data.interface1_id, @@ -544,11 +544,11 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: link_data.interface1_ip6, link_data.interface1_ip6_mask, ) - link_element.append(interface_one) + link_element.append(interface1) # check for interface two if link_data.interface2_id is not None: - interface_two = self.create_interface_element( + interface2 = self.create_interface_element( "interface_two", link_data.node2_id, link_data.interface2_id, @@ -558,14 +558,14 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: link_data.interface2_ip6, link_data.interface2_ip6_mask, ) - link_element.append(interface_two) + link_element.append(interface2) # check for options, don't write for emane/wlan links - node_one = self.session.get_node(link_data.node1_id, NodeBase) - node_two = self.session.get_node(link_data.node2_id, NodeBase) - is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet)) - is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet)) - if not any([is_node_one_wireless, is_node_two_wireless]): + node1 = self.session.get_node(link_data.node1_id, NodeBase) + node2 = self.session.get_node(link_data.node2_id, NodeBase) + is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet)) + is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet)) + if not any([is_node1_wireless, is_node2_wireless]): options = etree.Element("options") add_attribute(options, "delay", link_data.delay) add_attribute(options, "bandwidth", link_data.bandwidth) @@ -932,19 +932,19 @@ def read_links(self) -> None: node_sets = set() for link_element in link_elements.iterchildren(): - node_one = get_int(link_element, "node_one") - node_two = get_int(link_element, "node_two") - node_set = frozenset((node_one, node_two)) + node1_id = get_int(link_element, "node_one") + node2_id = get_int(link_element, "node_two") + node_set = frozenset((node1_id, node2_id)) - interface_one_element = link_element.find("interface_one") - interface_one = None - if interface_one_element is not None: - interface_one = create_interface_data(interface_one_element) + interface1_element = link_element.find("interface_one") + interface1_data = None + if interface1_element is not None: + interface1_data = create_interface_data(interface1_element) - interface_two_element = link_element.find("interface_two") - interface_two = None - if interface_two_element is not None: - interface_two = create_interface_data(interface_two_element) + interface2_element = link_element.find("interface_two") + interface2_data = None + if interface2_element is not None: + interface2_data = create_interface_data(interface2_element) options_element = link_element.find("options") link_options = LinkOptions() @@ -966,18 +966,18 @@ def read_links(self) -> None: link_options.gui_attributes = options_element.get("gui_attributes") if link_options.unidirectional == 1 and node_set in node_sets: - logging.info( - "updating link node_one(%s) node_two(%s)", node_one, node_two - ) + logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) self.session.update_link( - node_one, node_two, interface_one.id, interface_two.id, link_options + node1_id, + node2_id, + interface1_data.id, + interface2_data.id, + link_options, ) else: - logging.info( - "adding link node_one(%s) node_two(%s)", node_one, node_two - ) + logging.info("adding link node1(%s) node2(%s)", node1_id, node2_id) self.session.add_link( - node_one, node_two, interface_one, interface_two, link_options + node1_id, node2_id, interface1_data, interface2_data, link_options ) node_sets.add(node_set) diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index bc67ff462..948ec739b 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -11,7 +11,7 @@ # setup basic network prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(model="nothing") + options = NodeOptions(model=None) coreemu = CoreEmu() session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) @@ -19,14 +19,14 @@ # node one options.config_services = ["DefaultRoute", "IPForward"] - node_one = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node_one) - session.add_link(node_one.id, switch.id, interface_one=interface) + node1 = session.add_node(CoreNode, options=options) + interface = prefixes.create_interface(node1) + session.add_link(node1.id, switch.id, interface1_data=interface) # node two - node_two = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node_two) - session.add_link(node_two.id, switch.id, interface_one=interface) + node2 = session.add_node(CoreNode, options=options) + interface = prefixes.create_interface(node2) + session.add_link(node2.id, switch.id, interface1_data=interface) # start session and run services session.instantiate() diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index 1211a16f9..8151a5903 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -17,15 +17,15 @@ options = NodeOptions(model=None, image="ubuntu") # create node one - node_one = session.add_node(DockerNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(DockerNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(CoreNode) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(CoreNode) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index 9e1ae11ff..a7a70534a 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -18,15 +18,15 @@ options = NodeOptions(model=None, image="ubuntu") # create node one - node_one = session.add_node(DockerNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(DockerNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(DockerNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(DockerNode, options=options) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index 74d58fe02..ef0579454 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -22,20 +22,20 @@ switch = session.add_node(SwitchNode) # node one - node_one = session.add_node(DockerNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(DockerNode, options=options) + interface1_data = prefixes.create_interface(node1) # node two - node_two = session.add_node(DockerNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(DockerNode, options=options) + interface2_data = prefixes.create_interface(node2) # node three node_three = session.add_node(CoreNode) interface_three = prefixes.create_interface(node_three) # add links - session.add_link(node_one.id, switch.id, interface_one) - session.add_link(node_two.id, switch.id, interface_two) + session.add_link(node1.id, switch.id, interface1_data) + session.add_link(node2.id, switch.id, interface2_data) session.add_link(node_three.id, switch.id, interface_three) # instantiate diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index 0477efdd0..e847016fb 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -44,11 +44,11 @@ def main(args): node = Node(position=position) response = core.add_node(session_id, node) logging.info("created node one: %s", response) - node_one_id = response.node_id + node1_id = response.node_id # create link - interface_one = interface_helper.create_interface(node_one_id, 0) - response = core.add_link(session_id, node_one_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link from node one to switch: %s", response) # create node two @@ -56,11 +56,11 @@ def main(args): node = Node(position=position, server=server_name) response = core.add_node(session_id, node) logging.info("created node two: %s", response) - node_two_id = response.node_id + node2_id = response.node_id # create link - interface_one = interface_helper.create_interface(node_two_id, 0) - response = core.add_link(session_id, node_two_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link from node two to switch: %s", response) # change session state diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index 5656268c2..245322668 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -57,11 +57,11 @@ def main(): node2_id = response.node_id # links nodes to switch - interface_one = interface_helper.create_interface(node1_id, 0) - response = core.add_link(session_id, node1_id, emane_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, emane_id, interface1) logging.info("created link: %s", response) - interface_one = interface_helper.create_interface(node2_id, 0) - response = core.add_link(session_id, node2_id, emane_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, emane_id, interface1) logging.info("created link: %s", response) # change session state diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 3ab0e0ba4..74e315c6c 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -53,11 +53,11 @@ def main(): node2_id = response.node_id # links nodes to switch - interface_one = interface_helper.create_interface(node1_id, 0) - response = core.add_link(session_id, node1_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link: %s", response) - interface_one = interface_helper.create_interface(node2_id, 0) - response = core.add_link(session_id, node2_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link: %s", response) # change session state diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index 6118ae4ce..d60ca1be3 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -65,11 +65,11 @@ def main(): node2_id = response.node_id # links nodes to switch - interface_one = interface_helper.create_interface(node1_id, 0) - response = core.add_link(session_id, node1_id, wlan_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, wlan_id, interface1) logging.info("created link: %s", response) - interface_one = interface_helper.create_interface(node2_id, 0) - response = core.add_link(session_id, node2_id, wlan_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, wlan_id, interface1) logging.info("created link: %s", response) # change session state diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index 1365bd4c8..49b689433 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -17,15 +17,15 @@ options = NodeOptions(image="ubuntu") # create node one - node_one = session.add_node(LxcNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(LxcNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(CoreNode) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(CoreNode) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index 53a360e8f..18af8037d 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -18,15 +18,15 @@ options = NodeOptions(image="ubuntu:18.04") # create node one - node_one = session.add_node(LxcNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(LxcNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(LxcNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(LxcNode, options=options) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 3b6226e44..31a798877 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -22,21 +22,21 @@ switch = session.add_node(SwitchNode) # node one - node_one = session.add_node(LxcNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(LxcNode, options=options) + interface1_data = prefixes.create_interface(node1) # node two - node_two = session.add_node(LxcNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(LxcNode, options=options) + interface2_data = prefixes.create_interface(node2) # node three - node_three = session.add_node(CoreNode) - interface_three = prefixes.create_interface(node_three) + node3 = session.add_node(CoreNode) + interface3_data = prefixes.create_interface(node3) # add links - session.add_link(node_one.id, switch.id, interface_one) - session.add_link(node_two.id, switch.id, interface_two) - session.add_link(node_three.id, switch.id, interface_three) + session.add_link(node1.id, switch.id, interface1_data) + session.add_link(node2.id, switch.id, interface2_data) + session.add_link(node3.id, switch.id, interface3_data) # instantiate session.instantiate() diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index 3248a8e3e..d9b41ea42 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -52,17 +52,17 @@ def main(args): # create local node, switch, and remote nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) emane_net = session.add_node(EmaneNet) session.emane.set_model(emane_net, EmaneIeee80211abgModel) options.server = server_name - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, emane_net.id, interface_one=interface_one) - session.add_link(node_two.id, emane_net.id, interface_one=interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, emane_net.id, interface1_data=interface1_data) + session.add_link(node2.id, emane_net.id, interface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index de919012a..affb16a8a 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -43,14 +43,14 @@ def main(args): # create local node, switch, and remote nodes options = NodeOptions(image="ubuntu:18.04") - node_one = session.add_node(LxcNode, options=options) + node1 = session.add_node(LxcNode, options=options) options.server = server_name - node_two = session.add_node(LxcNode, options=options) + node2 = session.add_node(LxcNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index 26531399d..6bf33474d 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -43,14 +43,14 @@ def main(args): # create local node, switch, and remote nodes options = NodeOptions() - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) options.server = server_name - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index c52c1cc1a..8991161e9 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -45,17 +45,17 @@ def main(args): session.set_state(EventTypes.CONFIGURATION_STATE) # create local node, switch, and remote nodes - node_one = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) switch = session.add_node(SwitchNode) options = NodeOptions() options.server = server_name - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, switch.id, interface_one=interface_one) - session.add_link(node_two.id, switch.id, interface_one=interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, switch.id, interface1_data=interface1_data) + session.add_link(node2.id, switch.id, interface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index da93026bc..d3f6652af 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -43,7 +43,7 @@ def main(): node = session.add_node(CoreNode, options=options) node.setposition(x=150 * (i + 1), y=150) interface = prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface_one=interface) + session.add_link(node.id, emane_network.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index 9475fc471..1b939cd74 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -32,7 +32,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode) interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface_one=interface) + session.add_link(node.id, switch.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch_inject.py b/daemon/examples/python/switch_inject.py index 8c929e917..59816b192 100644 --- a/daemon/examples/python/switch_inject.py +++ b/daemon/examples/python/switch_inject.py @@ -34,7 +34,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode) interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface_one=interface) + session.add_link(node.id, switch.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index b09ae5ce0..0302bbd35 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -36,7 +36,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode, options=options) interface = prefixes.create_interface(node) - session.add_link(node.id, wlan.id, interface_one=interface) + session.add_link(node.id, wlan.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index d602f9d32..8062a731e 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -492,16 +492,16 @@ message AddLinkRequest { message AddLinkResponse { bool result = 1; - Interface interface_one = 2; - Interface interface_two = 3; + Interface interface1 = 2; + Interface interface2 = 3; } message EditLinkRequest { int32 session_id = 1; - int32 node_one_id = 2; - int32 node_two_id = 3; - int32 interface_one_id = 4; - int32 interface_two_id = 5; + int32 node1_id = 2; + int32 node2_id = 3; + int32 interface1_id = 4; + int32 interface2_id = 5; LinkOptions options = 6; } @@ -511,10 +511,10 @@ message EditLinkResponse { message DeleteLinkRequest { int32 session_id = 1; - int32 node_one_id = 2; - int32 node_two_id = 3; - int32 interface_one_id = 4; - int32 interface_two_id = 5; + int32 node1_id = 2; + int32 node2_id = 3; + int32 interface1_id = 4; + int32 interface2_id = 5; } message DeleteLinkResponse { @@ -702,11 +702,11 @@ message Node { } message Link { - int32 node_one_id = 1; - int32 node_two_id = 2; + int32 node1_id = 1; + int32 node2_id = 2; LinkType.Enum type = 3; - Interface interface_one = 4; - Interface interface_two = 5; + Interface interface1 = 4; + Interface interface2 = 5; LinkOptions options = 6; int32 network_id = 7; string label = 8; diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 8c3ee4cae..e41897002 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -75,8 +75,8 @@ message GetEmaneEventChannelResponse { message EmaneLinkRequest { int32 session_id = 1; - int32 nem_one = 2; - int32 nem_two = 3; + int32 nem1 = 2; + int32 nem2 = 3; bool linked = 4; } @@ -93,12 +93,12 @@ message EmaneModelConfig { message EmanePathlossesRequest { int32 session_id = 1; - int32 node_one = 2; - float rx_one = 3; - int32 interface_one_id = 4; - int32 node_two = 5; - float rx_two = 6; - int32 interface_two_id = 7; + int32 node1_id = 2; + float rx1 = 3; + int32 interface1_id = 4; + int32 node2_id = 5; + float rx2 = 6; + int32 interface2_id = 7; } message EmanePathlossesResponse { diff --git a/daemon/proto/core/api/grpc/wlan.proto b/daemon/proto/core/api/grpc/wlan.proto index bbb9757f2..9605d633d 100644 --- a/daemon/proto/core/api/grpc/wlan.proto +++ b/daemon/proto/core/api/grpc/wlan.proto @@ -38,8 +38,8 @@ message SetWlanConfigResponse { message WlanLinkRequest { int32 session_id = 1; int32 wlan = 2; - int32 node_one = 3; - int32 node_two = 4; + int32 node1_id = 3; + int32 node2_id = 4; bool linked = 5; } diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 2d90ebccb..15e3d869c 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -3,6 +3,7 @@ """ import os from tempfile import TemporaryFile +from typing import Type from xml.etree import ElementTree import pytest @@ -43,7 +44,9 @@ def ping( class TestEmane: @pytest.mark.parametrize("model", _EMANE_MODELS) - def test_models(self, session: Session, model: EmaneModel, ip_prefixes: IpPrefixes): + def test_models( + self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes + ): """ Test emane models within a basic network. @@ -70,20 +73,20 @@ def test_models(self, session: Session, model: EmaneModel, ip_prefixes: IpPrefix # create nodes options = NodeOptions(model="mdr") options.set_position(150, 150) - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) options.set_position(300, 150) - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) - for i, node in enumerate([node_one, node_two]): + for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface_one=interface) + session.add_link(node.id, emane_network.id, interface1_data=interface) # instantiate session session.instantiate() - # ping n2 from n1 and assert success - status = ping(node_one, node_two, ip_prefixes, count=5) + # ping node2 from node1 and assert success + status = ping(node1, node2, ip_prefixes, count=5) assert not status def test_xml_emane( @@ -110,22 +113,22 @@ def test_xml_emane( # create nodes options = NodeOptions(model="mdr") options.set_position(150, 150) - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) options.set_position(300, 150) - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) - for i, node in enumerate([node_one, node_two]): + for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface_one=interface) + session.add_link(node.id, emane_network.id, interface1_data=interface) # instantiate session session.instantiate() # get ids for nodes emane_id = emane_network.id - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -141,9 +144,9 @@ def test_xml_emane( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) @@ -154,7 +157,7 @@ def test_xml_emane( ) # verify nodes and configuration were restored - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) assert session.get_node(emane_id, EmaneNet) assert value == config_value diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index 1973dceea..e90acfbd6 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -14,11 +14,11 @@ class TestConfigurableOptions(ConfigurableOptions): - name_one = "value1" - name_two = "value2" + name1 = "value1" + name2 = "value2" options = [ - Configuration(_id=name_one, _type=ConfigDataTypes.STRING, label=name_one), - Configuration(_id=name_two, _type=ConfigDataTypes.STRING, label=name_two), + Configuration(_id=name1, _type=ConfigDataTypes.STRING, label=name1), + Configuration(_id=name2, _type=ConfigDataTypes.STRING, label=name2), ] @@ -33,11 +33,11 @@ def test_configurable_options_default(self): # then assert len(default_values) == 2 - assert TestConfigurableOptions.name_one in default_values - assert TestConfigurableOptions.name_two in default_values + assert TestConfigurableOptions.name1 in default_values + assert TestConfigurableOptions.name2 in default_values assert len(instance_default_values) == 2 - assert TestConfigurableOptions.name_one in instance_default_values - assert TestConfigurableOptions.name_two in instance_default_values + assert TestConfigurableOptions.name1 in instance_default_values + assert TestConfigurableOptions.name2 in instance_default_values def test_nodes(self): # given diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 68515a41c..626f84a7b 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -48,19 +48,19 @@ def test_wired_ping( net_node = session.add_node(net_type) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to net node - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, net_node.id, interface_one=interface) + session.add_link(node.id, net_node.id, interface1_data=interface) # instantiate session session.instantiate() - # ping n2 from n1 and assert success - status = ping(node_one, node_two, ip_prefixes) + # ping node2 from node1 and assert success + status = ping(node1, node2, ip_prefixes) assert not status def test_vnode_client(self, request, session: Session, ip_prefixes: IpPrefixes): @@ -75,16 +75,16 @@ def test_vnode_client(self, request, session: Session, ip_prefixes: IpPrefixes): ptp_node = session.add_node(PtpNet) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # get node client for testing - client = node_one.client + client = node1.client # instantiate session session.instantiate() @@ -108,13 +108,13 @@ def test_netif(self, session: Session, ip_prefixes: IpPrefixes): ptp_node = session.add_node(PtpNet) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # instantiate session session.instantiate() @@ -123,22 +123,22 @@ def test_netif(self, session: Session, ip_prefixes: IpPrefixes): assert ptp_node.all_link_data(MessageFlags.ADD) # check common nets exist between linked nodes - assert node_one.commonnets(node_two) - assert node_two.commonnets(node_one) + assert node1.commonnets(node2) + assert node2.commonnets(node1) # check we can retrieve netif index - assert node_one.ifname(0) - assert node_two.ifname(0) + assert node1.ifname(0) + assert node2.ifname(0) # check interface parameters - interface = node_one.netif(0) + interface = node1.netif(0) interface.setparam("test", 1) assert interface.getparam("test") == 1 assert interface.getparams() # delete netif and test that if no longer exists - node_one.delnetif(0) - assert not node_one.netif(0) + node1.delnetif(0) + assert not node1.netif(0) def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes): """ @@ -155,19 +155,19 @@ def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes): # create nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # link nodes - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface1_data=interface) # instantiate session session.instantiate() - # ping n2 from n1 and assert success - status = ping(node_one, node_two, ip_prefixes) + # ping node2 from node1 and assert success + status = ping(node1, node2, ip_prefixes) assert not status def test_mobility(self, session: Session, ip_prefixes: IpPrefixes): @@ -185,13 +185,13 @@ def test_mobility(self, session: Session, ip_prefixes: IpPrefixes): # create nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # link nodes - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface1_data=interface) # configure mobility script for session config = { diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index c0686d71f..131af93d4 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -34,23 +34,23 @@ def test_start_session(self, grpc_server: CoreGrpcServer): client = CoreGrpcClient() session = grpc_server.coreemu.create_session() position = core_pb2.Position(x=50, y=100) - node_one = core_pb2.Node(id=1, position=position, model="PC") + node1 = core_pb2.Node(id=1, position=position, model="PC") position = core_pb2.Position(x=100, y=100) - node_two = core_pb2.Node(id=2, position=position, model="PC") + node2 = core_pb2.Node(id=2, position=position, model="PC") position = core_pb2.Position(x=200, y=200) wlan_node = core_pb2.Node( id=3, type=NodeTypes.WIRELESS_LAN.value, position=position ) - nodes = [node_one, node_two, wlan_node] + nodes = [node1, node2, wlan_node] interface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") - interface_one = interface_helper.create_interface(node_one.id, 0) - interface_two = interface_helper.create_interface(node_two.id, 0) + interface1 = interface_helper.create_interface(node1.id, 0) + interface2 = interface_helper.create_interface(node2.id, 0) link = core_pb2.Link( type=core_pb2.LinkType.WIRED, - node_one_id=node_one.id, - node_two_id=node_two.id, - interface_one=interface_one, - interface_two=interface_two, + node1_id=node1.id, + node2_id=node2.id, + interface1=interface1, + interface2=interface2, ) links = [link] hook = core_pb2.Hook( @@ -99,11 +99,11 @@ def test_start_session(self, grpc_server: CoreGrpcServer): ) mobility_configs = [mobility_config] service_config = ServiceConfig( - node_id=node_one.id, service="DefaultRoute", validate=["echo hello"] + node_id=node1.id, service="DefaultRoute", validate=["echo hello"] ) service_configs = [service_config] service_file_config = ServiceFileConfig( - node_id=node_one.id, + node_id=node1.id, service="DefaultRoute", file="defaultroute.sh", data="echo hello", @@ -128,11 +128,11 @@ def test_start_session(self, grpc_server: CoreGrpcServer): ) # then - assert node_one.id in session.nodes - assert node_two.id in session.nodes + assert node1.id in session.nodes + assert node2.id in session.nodes assert wlan_node.id in session.nodes - assert session.nodes[node_one.id].netif(0) is not None - assert session.nodes[node_two.id].netif(0) is not None + assert session.nodes[node1.id].netif(0) is not None + assert session.nodes[node2.id].netif(0) is not None hook_file, hook_data = session._hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data @@ -153,11 +153,11 @@ def test_start_session(self, grpc_server: CoreGrpcServer): ) assert set_model_config[model_config_key] == model_config_value service = session.services.get_service( - node_one.id, service_config.service, default_service=True + node1.id, service_config.service, default_service=True ) assert service.validate == tuple(service_config.validate) service_file = session.services.get_service_file( - node_one, service_file_config.service, service_file_config.file + node1, service_file_config.service, service_file_config.file ) assert service_file.data == service_file_config.data @@ -596,7 +596,7 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # then with client.context_connect(): response = client.edit_link( - session.id, node.id, switch.id, options, interface_one_id=interface.id + session.id, node.id, switch.id, options, interface1_id=interface.id ) # then @@ -608,28 +608,28 @@ def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + node1 = session.add_node(CoreNode) + interface1 = ip_prefixes.create_interface(node1) + node2 = session.add_node(CoreNode) + interface2 = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1, interface2) link_node = None for node_id in session.nodes: node = session.nodes[node_id] - if node.id not in {node_one.id, node_two.id}: + if node.id not in {node1.id, node2.id}: link_node = node break - assert len(link_node.all_link_data(0)) == 1 + assert len(link_node.all_link_data()) == 1 # then with client.context_connect(): response = client.delete_link( - session.id, node_one.id, node_two.id, interface_one.id, interface_two.id + session.id, node1.id, node2.id, interface1.id, interface2.id ) # then assert response.result is True - assert len(link_node.all_link_data(0)) == 0 + assert len(link_node.all_link_data()) == 0 def test_get_wlan_config(self, grpc_server: CoreGrpcServer): # given diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 800a8e620..1187b4d7f 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -50,12 +50,13 @@ def test_node_add( self, coretlv: CoreHandler, node_type: NodeTypes, model: Optional[str] ): node_id = 1 + name = "node1" message = coreapi.CoreNodeMessage.create( MessageFlags.ADD.value, [ (NodeTlvs.NUMBER, node_id), (NodeTlvs.TYPE, node_type.value), - (NodeTlvs.NAME, "n1"), + (NodeTlvs.NAME, name), (NodeTlvs.X_POSITION, 0), (NodeTlvs.Y_POSITION, 0), (NodeTlvs.MODEL, model), @@ -63,7 +64,9 @@ def test_node_add( ) coretlv.handle_message(message) - assert coretlv.session.get_node(node_id, NodeBase) is not None + node = coretlv.session.get_node(node_id, NodeBase) + assert node + assert node.name == name def test_node_update(self, coretlv: CoreHandler): node_id = 1 @@ -99,71 +102,71 @@ def test_node_delete(self, coretlv: CoreHandler): coretlv.session.get_node(node_id, NodeBase) def test_link_add_node_to_net(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 def test_link_add_net_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface2_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, switch), - (LinkTlvs.N2_NUMBER, node_one), + (LinkTlvs.N1_NUMBER, switch_id), + (LinkTlvs.N2_NUMBER, node1_id), (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface_one), + (LinkTlvs.INTERFACE2_IP4, interface2_ip4), (LinkTlvs.INTERFACE2_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 def test_link_add_node_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - node_two = 2 - coretlv.session.add_node(CoreNode, _id=node_two) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + node2_id = 2 + coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) - interface_two = str(ip_prefix[node_two]) + interface1_ip4 = str(ip_prefix[node1_id]) + interface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, node_two), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, node2_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface_two), + (LinkTlvs.INTERFACE2_IP4, interface2_ip4), (LinkTlvs.INTERFACE2_IP4_MASK, 24), ], ) @@ -177,24 +180,24 @@ def test_link_add_node_to_node(self, coretlv: CoreHandler): assert len(all_links) == 1 def test_link_update(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] @@ -204,37 +207,37 @@ def test_link_update(self, coretlv: CoreHandler): message = coreapi.CoreLinkMessage.create( 0, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), (LinkTlvs.BANDWIDTH, bandwidth), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] assert link.bandwidth == bandwidth def test_link_delete_node_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - node_two = 2 - coretlv.session.add_node(CoreNode, _id=node_two) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + node2_id = 2 + coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) - interface_two = str(ip_prefix[node_two]) + interface1_ip4 = str(ip_prefix[node1_id]) + interface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, node_two), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, node2_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), - (LinkTlvs.INTERFACE2_IP4, interface_two), + (LinkTlvs.INTERFACE2_IP4, interface2_ip4), (LinkTlvs.INTERFACE2_IP4_MASK, 24), ], ) @@ -248,8 +251,8 @@ def test_link_delete_node_to_node(self, coretlv: CoreHandler): message = coreapi.CoreLinkMessage.create( MessageFlags.DELETE.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, node_two), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, node2_id), (LinkTlvs.INTERFACE1_NUMBER, 0), (LinkTlvs.INTERFACE2_NUMBER, 0), ], @@ -263,74 +266,74 @@ def test_link_delete_node_to_node(self, coretlv: CoreHandler): assert len(all_links) == 0 def test_link_delete_node_to_net(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( MessageFlags.DELETE.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 0 def test_link_delete_net_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( MessageFlags.DELETE.value, [ - (LinkTlvs.N1_NUMBER, switch), - (LinkTlvs.N2_NUMBER, node_one), + (LinkTlvs.N1_NUMBER, switch_id), + (LinkTlvs.N2_NUMBER, node1_id), (LinkTlvs.INTERFACE2_NUMBER, 0), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 0 diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 9f693da1e..61f9d13d8 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -10,71 +10,71 @@ def create_ptp_network( session: Session, ip_prefixes: IpPrefixes ) -> Tuple[CoreNode, CoreNode]: # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to net node - interface_one = ip_prefixes.create_interface(node_one) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session session.instantiate() - return node_one, node_two + return node1, node2 class TestLinks: def test_add_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) # when - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # then - assert node_one.netif(interface_one.id) - assert node_two.netif(interface_two.id) + assert node1.netif(interface1_data.id) + assert node2.netif(interface2_data.id) def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(SwitchNode) - interface_one = ip_prefixes.create_interface(node_one) + node1 = session.add_node(CoreNode) + node2 = session.add_node(SwitchNode) + interface1_data = ip_prefixes.create_interface(node1) # when - session.add_link(node_one.id, node_two.id, interface_one=interface_one) + session.add_link(node1.id, node2.id, interface1_data=interface1_data) # then - assert node_two.all_link_data() - assert node_one.netif(interface_one.id) + assert node2.all_link_data() + assert node1.netif(interface1_data.id) def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(SwitchNode) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(SwitchNode) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) # when - session.add_link(node_one.id, node_two.id, interface_two=interface_two) + session.add_link(node1.id, node2.id, interface2_data=interface2_data) # then - assert node_one.all_link_data() - assert node_two.netif(interface_two.id) + assert node1.all_link_data() + assert node2.netif(interface2_data.id) def test_add_net_to_net(self, session): # given - node_one = session.add_node(SwitchNode) - node_two = session.add_node(SwitchNode) + node1 = session.add_node(SwitchNode) + node2 = session.add_node(SwitchNode) # when - session.add_link(node_one.id, node_two.id) + session.add_link(node1.id, node2.id) # then - assert node_one.all_link_data() + assert node1.all_link_data() def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -83,34 +83,31 @@ def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): per = 25 dup = 25 jitter = 10 - node_one = session.add_node(CoreNode) - node_two = session.add_node(SwitchNode) - interface_one_data = ip_prefixes.create_interface(node_one) - session.add_link(node_one.id, node_two.id, interface_one_data) - interface_one = node_one.netif(interface_one_data.id) - assert interface_one.getparam("delay") != delay - assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per - assert interface_one.getparam("duplicate") != dup - assert interface_one.getparam("jitter") != jitter + node1 = session.add_node(CoreNode) + node2 = session.add_node(SwitchNode) + interface1_data = ip_prefixes.create_interface(node1) + session.add_link(node1.id, node2.id, interface1_data) + interface1 = node1.netif(interface1_data.id) + assert interface1.getparam("delay") != delay + assert interface1.getparam("bw") != bandwidth + assert interface1.getparam("loss") != per + assert interface1.getparam("duplicate") != dup + assert interface1.getparam("jitter") != jitter # when - link_options = LinkOptions( + options = LinkOptions( delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter ) session.update_link( - node_one.id, - node_two.id, - interface_one_id=interface_one_data.id, - options=link_options, + node1.id, node2.id, interface1_id=interface1_data.id, options=options ) # then - assert interface_one.getparam("delay") == delay - assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per - assert interface_one.getparam("duplicate") == dup - assert interface_one.getparam("jitter") == jitter + assert interface1.getparam("delay") == delay + assert interface1.getparam("bw") == bandwidth + assert interface1.getparam("loss") == per + assert interface1.getparam("duplicate") == dup + assert interface1.getparam("jitter") == jitter def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -119,34 +116,31 @@ def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): per = 25 dup = 25 jitter = 10 - node_one = session.add_node(SwitchNode) - node_two = session.add_node(CoreNode) - interface_two_data = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_two=interface_two_data) - interface_two = node_two.netif(interface_two_data.id) - assert interface_two.getparam("delay") != delay - assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per - assert interface_two.getparam("duplicate") != dup - assert interface_two.getparam("jitter") != jitter + node1 = session.add_node(SwitchNode) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface2_data=interface2_data) + interface2 = node2.netif(interface2_data.id) + assert interface2.getparam("delay") != delay + assert interface2.getparam("bw") != bandwidth + assert interface2.getparam("loss") != per + assert interface2.getparam("duplicate") != dup + assert interface2.getparam("jitter") != jitter # when - link_options = LinkOptions( + options = LinkOptions( delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter ) session.update_link( - node_one.id, - node_two.id, - interface_two_id=interface_two_data.id, - options=link_options, + node1.id, node2.id, interface2_id=interface2_data.id, options=options ) # then - assert interface_two.getparam("delay") == delay - assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per - assert interface_two.getparam("duplicate") == dup - assert interface_two.getparam("jitter") == jitter + assert interface2.getparam("delay") == delay + assert interface2.getparam("bw") == bandwidth + assert interface2.getparam("loss") == per + assert interface2.getparam("duplicate") == dup + assert interface2.getparam("jitter") == jitter def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -155,93 +149,85 @@ def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): per = 25 dup = 25 jitter = 10 - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) - interface_one_data = ip_prefixes.create_interface(node_one) - interface_two_data = ip_prefixes.create_interface(node_two) - session.add_link( - node_one.id, node_two.id, interface_one_data, interface_two_data - ) - interface_one = node_one.netif(interface_one_data.id) - interface_two = node_two.netif(interface_two_data.id) - assert interface_one.getparam("delay") != delay - assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per - assert interface_one.getparam("duplicate") != dup - assert interface_one.getparam("jitter") != jitter - assert interface_two.getparam("delay") != delay - assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per - assert interface_two.getparam("duplicate") != dup - assert interface_two.getparam("jitter") != jitter + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) + interface1 = node1.netif(interface1_data.id) + interface2 = node2.netif(interface2_data.id) + assert interface1.getparam("delay") != delay + assert interface1.getparam("bw") != bandwidth + assert interface1.getparam("loss") != per + assert interface1.getparam("duplicate") != dup + assert interface1.getparam("jitter") != jitter + assert interface2.getparam("delay") != delay + assert interface2.getparam("bw") != bandwidth + assert interface2.getparam("loss") != per + assert interface2.getparam("duplicate") != dup + assert interface2.getparam("jitter") != jitter # when - link_options = LinkOptions( + options = LinkOptions( delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter ) session.update_link( - node_one.id, - node_two.id, - interface_one_data.id, - interface_two_data.id, - link_options, + node1.id, node2.id, interface1_data.id, interface2_data.id, options ) # then - assert interface_one.getparam("delay") == delay - assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per - assert interface_one.getparam("duplicate") == dup - assert interface_one.getparam("jitter") == jitter - assert interface_two.getparam("delay") == delay - assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per - assert interface_two.getparam("duplicate") == dup - assert interface_two.getparam("jitter") == jitter + assert interface1.getparam("delay") == delay + assert interface1.getparam("bw") == bandwidth + assert interface1.getparam("loss") == per + assert interface1.getparam("duplicate") == dup + assert interface1.getparam("jitter") == jitter + assert interface2.getparam("delay") == delay + assert interface2.getparam("bw") == bandwidth + assert interface2.getparam("loss") == per + assert interface2.getparam("duplicate") == dup + assert interface2.getparam("jitter") == jitter def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) - assert node_one.netif(interface_one.id) - assert node_two.netif(interface_two.id) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) + assert node1.netif(interface1_data.id) + assert node2.netif(interface2_data.id) # when - session.delete_link( - node_one.id, node_two.id, interface_one.id, interface_two.id - ) + session.delete_link(node1.id, node2.id, interface1_data.id, interface2_data.id) # then - assert not node_one.netif(interface_one.id) - assert not node_two.netif(interface_two.id) + assert not node1.netif(interface1_data.id) + assert not node2.netif(interface2_data.id) def test_delete_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(SwitchNode) - interface_one = ip_prefixes.create_interface(node_one) - session.add_link(node_one.id, node_two.id, interface_one) - assert node_one.netif(interface_one.id) + node1 = session.add_node(CoreNode) + node2 = session.add_node(SwitchNode) + interface1_data = ip_prefixes.create_interface(node1) + session.add_link(node1.id, node2.id, interface1_data) + assert node1.netif(interface1_data.id) # when - session.delete_link(node_one.id, node_two.id, interface_one_id=interface_one.id) + session.delete_link(node1.id, node2.id, interface1_id=interface1_data.id) # then - assert not node_one.netif(interface_one.id) + assert not node1.netif(interface1_data.id) def test_delete_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(SwitchNode) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_two=interface_two) - assert node_two.netif(interface_two.id) + node1 = session.add_node(SwitchNode) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface2_data=interface2_data) + assert node2.netif(interface2_data.id) # when - session.delete_link(node_one.id, node_two.id, interface_two_id=interface_two.id) + session.delete_link(node1.id, node2.id, interface2_id=interface2_data.id) # then - assert not node_two.netif(interface_two.id) + assert not node2.netif(interface2_data.id) diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py index e304a2750..264a65663 100644 --- a/daemon/tests/test_services.py +++ b/daemon/tests/test_services.py @@ -206,23 +206,23 @@ def test_service_set_file(self, session: Session): # given ServiceManager.add_services(_SERVICES_PATH) my_service = ServiceManager.get(SERVICE_ONE) - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) file_name = my_service.configs[0] - file_data_one = "# custom file one" - file_data_two = "# custom file two" + file_data1 = "# custom file one" + file_data2 = "# custom file two" session.services.set_service_file( - node_one.id, my_service.name, file_name, file_data_one + node1.id, my_service.name, file_name, file_data1 ) session.services.set_service_file( - node_two.id, my_service.name, file_name, file_data_two + node2.id, my_service.name, file_name, file_data2 ) # when - custom_service_one = session.services.get_service(node_one.id, my_service.name) - session.services.create_service_files(node_one, custom_service_one) - custom_service_two = session.services.get_service(node_two.id, my_service.name) - session.services.create_service_files(node_two, custom_service_two) + custom_service1 = session.services.get_service(node1.id, my_service.name) + session.services.create_service_files(node1, custom_service1) + custom_service2 = session.services.get_service(node2.id, my_service.name) + session.services.create_service_files(node2, custom_service2) def test_service_import(self): """ diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index c40a9ef3e..35e03f7da 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -68,20 +68,20 @@ def test_xml_ptp( ptp_node = session.add_node(PtpNet) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -97,16 +97,16 @@ def test_xml_ptp( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) def test_xml_ptp_services( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -123,28 +123,28 @@ def test_xml_ptp_services( # create nodes options = NodeOptions(model="host") - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # set custom values for node service - session.services.set_service(node_one.id, SshService.name) + session.services.set_service(node1.id, SshService.name) service_file = SshService.configs[0] file_data = "# test" session.services.set_service_file( - node_one.id, SshService.name, service_file, file_data + node1.id, SshService.name, service_file, file_data ) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -160,19 +160,19 @@ def test_xml_ptp_services( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # retrieve custom service - service = session.services.get_service(node_one.id, SshService.name) + service = session.services.get_service(node1.id, SshService.name) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) assert service.config_data.get(service_file) == file_data def test_xml_mobility( @@ -192,21 +192,21 @@ def test_xml_mobility( # create nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # link nodes - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface1_data=interface) # instantiate session session.instantiate() # get ids for nodes wlan_id = wlan_node.id - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -222,9 +222,9 @@ def test_xml_mobility( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) @@ -233,8 +233,8 @@ def test_xml_mobility( value = str(session.mobility.get_config("test", wlan_id, BasicRangeModel.name)) # verify nodes and configuration were restored - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) assert session.get_node(wlan_id, WlanNode) assert value == "1" @@ -246,18 +246,18 @@ def test_network_to_network(self, session: Session, tmpdir: TemporaryFile): :param tmpdir: tmpdir to create data in """ # create nodes - switch_one = session.add_node(SwitchNode) - switch_two = session.add_node(SwitchNode) + switch1 = session.add_node(SwitchNode) + switch2 = session.add_node(SwitchNode) # link nodes - session.add_link(switch_one.id, switch_two.id) + session.add_link(switch1.id, switch2.id) # instantiate session session.instantiate() # get ids for nodes - n1_id = switch_one.id - n2_id = switch_two.id + node1_id = switch1.id + node2_id = switch2.id # save xml xml_file = tmpdir.join("session.xml") @@ -273,19 +273,19 @@ def test_network_to_network(self, session: Session, tmpdir: TemporaryFile): # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, SwitchNode) + assert not session.get_node(node1_id, SwitchNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, SwitchNode) + assert not session.get_node(node2_id, SwitchNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - switch_one = session.get_node(n1_id, SwitchNode) - switch_two = session.get_node(n2_id, SwitchNode) - assert switch_one - assert switch_two - assert len(switch_one.all_link_data() + switch_two.all_link_data()) == 1 + switch1 = session.get_node(node1_id, SwitchNode) + switch2 = session.get_node(node2_id, SwitchNode) + assert switch1 + assert switch2 + assert len(switch1.all_link_data() + switch2.all_link_data()) == 1 def test_link_options( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -298,25 +298,25 @@ def test_link_options( :param ip_prefixes: generates ip addresses for nodes """ # create nodes - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) + node1 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) switch = session.add_node(SwitchNode) # create link - link_options = LinkOptions() - link_options.per = 10.5 - link_options.bandwidth = 50000 - link_options.jitter = 10 - link_options.delay = 30 - link_options.dup = 5 - session.add_link(node_one.id, switch.id, interface_one, options=link_options) + options = LinkOptions() + options.per = 10.5 + options.bandwidth = 50000 + options.jitter = 10 + options.delay = 30 + options.dup = 5 + session.add_link(node1.id, switch.id, interface1_data, options=options) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = switch.id + node1_id = node1.id + node2_id = switch.id # save xml xml_file = tmpdir.join("session.xml") @@ -332,26 +332,26 @@ def test_link_options( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, SwitchNode) + assert not session.get_node(node2_id, SwitchNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, SwitchNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, SwitchNode) links = [] for node_id in session.nodes: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per - assert link_options.bandwidth == link.bandwidth - assert link_options.jitter == link.jitter - assert link_options.delay == link.delay - assert link_options.dup == link.dup + assert options.per == link.per + assert options.bandwidth == link.bandwidth + assert options.jitter == link.jitter + assert options.delay == link.delay + assert options.dup == link.dup def test_link_options_ptp( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -364,28 +364,26 @@ def test_link_options_ptp( :param ip_prefixes: generates ip addresses for nodes """ # create nodes - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) # create link - link_options = LinkOptions() - link_options.per = 10.5 - link_options.bandwidth = 50000 - link_options.jitter = 10 - link_options.delay = 30 - link_options.dup = 5 - session.add_link( - node_one.id, node_two.id, interface_one, interface_two, link_options - ) + options = LinkOptions() + options.per = 10.5 + options.bandwidth = 50000 + options.jitter = 10 + options.delay = 30 + options.dup = 5 + session.add_link(node1.id, node2.id, interface1_data, interface2_data, options) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -401,26 +399,26 @@ def test_link_options_ptp( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) links = [] for node_id in session.nodes: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per - assert link_options.bandwidth == link.bandwidth - assert link_options.jitter == link.jitter - assert link_options.delay == link.delay - assert link_options.dup == link.dup + assert options.per == link.per + assert options.bandwidth == link.bandwidth + assert options.jitter == link.jitter + assert options.delay == link.delay + assert options.dup == link.dup def test_link_options_bidirectional( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -433,43 +431,37 @@ def test_link_options_bidirectional( :param ip_prefixes: generates ip addresses for nodes """ # create nodes - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) # create link - link_options_one = LinkOptions() - link_options_one.unidirectional = 1 - link_options_one.bandwidth = 5000 - link_options_one.delay = 10 - link_options_one.per = 10.5 - link_options_one.dup = 5 - link_options_one.jitter = 5 - session.add_link( - node_one.id, node_two.id, interface_one, interface_two, link_options_one - ) - link_options_two = LinkOptions() - link_options_two.unidirectional = 1 - link_options_two.bandwidth = 10000 - link_options_two.delay = 20 - link_options_two.per = 10 - link_options_two.dup = 10 - link_options_two.jitter = 10 + options1 = LinkOptions() + options1.unidirectional = 1 + options1.bandwidth = 5000 + options1.delay = 10 + options1.per = 10.5 + options1.dup = 5 + options1.jitter = 5 + session.add_link(node1.id, node2.id, interface1_data, interface2_data, options1) + options2 = LinkOptions() + options2.unidirectional = 1 + options2.bandwidth = 10000 + options2.delay = 20 + options2.per = 10 + options2.dup = 10 + options2.jitter = 10 session.update_link( - node_two.id, - node_one.id, - interface_two.id, - interface_one.id, - link_options_two, + node2.id, node1.id, interface2_data.id, interface1_data.id, options2 ) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -485,30 +477,30 @@ def test_link_options_bidirectional( # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) links = [] for node_id in session.nodes: node = session.nodes[node_id] links += node.all_link_data() assert len(links) == 2 - link_one = links[0] - link_two = links[1] - assert link_options_one.bandwidth == link_one.bandwidth - assert link_options_one.delay == link_one.delay - assert link_options_one.per == link_one.per - assert link_options_one.dup == link_one.dup - assert link_options_one.jitter == link_one.jitter - assert link_options_two.bandwidth == link_two.bandwidth - assert link_options_two.delay == link_two.delay - assert link_options_two.per == link_two.per - assert link_options_two.dup == link_two.dup - assert link_options_two.jitter == link_two.jitter + link1 = links[0] + link2 = links[1] + assert options1.bandwidth == link1.bandwidth + assert options1.delay == link1.delay + assert options1.per == link1.per + assert options1.dup == link1.dup + assert options1.jitter == link1.jitter + assert options2.bandwidth == link2.bandwidth + assert options2.delay == link2.delay + assert options2.per == link2.per + assert options2.dup == link2.dup + assert options2.jitter == link2.jitter diff --git a/docs/scripting.md b/docs/scripting.md index 8c1a705c0..59bc02ae4 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -62,7 +62,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode) interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface_one=interface) + session.add_link(node.id, switch.id, interface1_data=interface) # instantiate session session.instantiate() From 178d12b32761a1a5708b70cfe8948a09115aaf4a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 17:32:55 -0700 Subject: [PATCH 012/210] daemon: updated variables for InterfaceData to be denote data to make it more clear --- daemon/core/api/grpc/grpcutils.py | 6 +++--- daemon/core/emulator/emudata.py | 6 +++--- daemon/core/nodes/base.py | 20 ++++++++++---------- daemon/core/nodes/physical.py | 24 ++++++++++++++---------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 539face1e..9a944bbef 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -59,13 +59,13 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: :param interface_proto: interface proto :return: interface data """ - interface = None + interface_data = None if interface_proto: name = interface_proto.name if interface_proto.name else None mac = interface_proto.mac if interface_proto.mac else None ip4 = interface_proto.ip4 if interface_proto.ip4 else None ip6 = interface_proto.ip6 if interface_proto.ip6 else None - interface = InterfaceData( + interface_data = InterfaceData( id=interface_proto.id, name=name, mac=mac, @@ -74,7 +74,7 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: ip6=ip6, ip6_mask=interface_proto.ip6mask, ) - return interface + return interface_data def add_link_data( diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index b6dbd57cd..24b9495af 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -201,6 +201,6 @@ def create_interface( generation :return: new interface data for the provided node """ - interface = self.gen_interface(node.id, name, mac) - interface.id = node.newifindex() - return interface + interface_data = self.gen_interface(node.id, name, mac) + interface_data.id = node.newifindex() + return interface_data diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 4b8d513ba..37a41b812 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -475,13 +475,13 @@ def termcmdstring(self, sh: str) -> str: raise NotImplementedError def newnetif( - self, net: "CoreNetworkBase", interface: InterfaceData + self, net: "CoreNetworkBase", interface_data: InterfaceData ) -> CoreInterface: """ Create a new network interface. :param net: network to associate with - :param interface: interface data for new interface + :param interface_data: interface data for new interface :return: interface index """ raise NotImplementedError @@ -860,34 +860,34 @@ def ifup(self, ifindex: int) -> None: self.node_net_client.device_up(interface_name) def newnetif( - self, net: "CoreNetworkBase", interface: InterfaceData + self, net: "CoreNetworkBase", interface_data: InterfaceData ) -> CoreInterface: """ Create a new network interface. :param net: network to associate with - :param interface: interface data for new interface + :param interface_data: interface data for new interface :return: interface index """ - addresses = interface.get_addresses() + addresses = interface_data.get_addresses() with self.lock: # TODO: emane specific code if net.is_emane is True: - ifindex = self.newtuntap(interface.id, interface.name) + ifindex = self.newtuntap(interface_data.id, interface_data.name) # TUN/TAP is not ready for addressing yet; the device may # take some time to appear, and installing it into a # namespace after it has been bound removes addressing; # save addresses with the interface now self.attachnet(ifindex, net) netif = self.netif(ifindex) - netif.sethwaddr(interface.mac) + netif.sethwaddr(interface_data.mac) for address in addresses: netif.addaddr(address) else: - ifindex = self.newveth(interface.id, interface.name) + ifindex = self.newveth(interface_data.id, interface_data.name) self.attachnet(ifindex, net) - if interface.mac: - self.sethwaddr(ifindex, interface.mac) + if interface_data.mac: + self.sethwaddr(ifindex, interface_data.mac) for address in addresses: self.addaddr(ifindex, address) self.ifup(ifindex) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 6faa78245..a72ff128f 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -157,25 +157,27 @@ def newifindex(self) -> int: self.ifindex += 1 return ifindex - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: + def newnetif( + self, net: CoreNetworkBase, interface_data: InterfaceData + ) -> CoreInterface: logging.info("creating interface") - addresses = interface.get_addresses() - ifindex = interface.id + addresses = interface_data.get_addresses() + ifindex = interface_data.id if ifindex is None: ifindex = self.newifindex() - name = interface.name + name = interface_data.name if name is None: name = f"gt{ifindex}" if self.up: # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adoptnetif(remote_tap, ifindex, interface.mac, addresses) + self.adoptnetif(remote_tap, ifindex, interface_data.mac, addresses) return remote_tap else: # this is reached when configuring services (self.up=False) netif = GreTap(node=self, name=name, session=self.session, start=False) - self.adoptnetif(netif, ifindex, interface.mac, addresses) + self.adoptnetif(netif, ifindex, interface_data.mac, addresses) return netif def privatedir(self, path: str) -> None: @@ -297,19 +299,21 @@ def shutdown(self) -> None: self.up = False self.restorestate() - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: + def newnetif( + self, net: CoreNetworkBase, interface_data: InterfaceData + ) -> CoreInterface: """ This is called when linking with another node. Since this node represents an interface, we do not create another object here, but attach ourselves to the given network. :param net: new network instance - :param interface: interface data for new interface + :param interface_data: interface data for new interface :return: interface index :raises ValueError: when an interface has already been created, one max """ with self.lock: - ifindex = interface.id + ifindex = interface_data.id if ifindex is None: ifindex = 0 if self.interface.net is not None: @@ -318,7 +322,7 @@ def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterf self.ifindex = ifindex if net is not None: self.interface.attachnet(net) - for addr in interface.get_addresses(): + for addr in interface_data.get_addresses(): self.addaddr(addr) return self.interface From 23d957679e5bf121607fff392c3213ce8b0637b3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 20:22:51 -0700 Subject: [PATCH 013/210] daemon: Session cleanup, removed unused functions, used context managers for writing files, made variables used externally no longer private --- daemon/core/api/grpc/server.py | 4 +- daemon/core/api/tlv/corehandlers.py | 12 +- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emulator/session.py | 277 +++++++-------------------- daemon/core/nodes/base.py | 2 +- daemon/core/plugins/sdt.py | 2 +- daemon/core/services/coreservices.py | 8 +- daemon/core/xml/corexml.py | 4 +- daemon/tests/test_grpc.py | 2 +- daemon/tests/test_gui.py | 4 +- daemon/tests/test_xml.py | 2 +- 11 files changed, 99 insertions(+), 220 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index a0ddf806e..ca9e0133b 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -930,8 +930,8 @@ def GetHooks( logging.debug("get hooks: %s", request) session = self.get_session(request.session_id, context) hooks = [] - for state in session._hooks: - state_hooks = session._hooks[state] + for state in session.hooks: + state_hooks = session.hooks[state] for file_name, file_data in state_hooks: hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) hooks.append(hook) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index e7a67b3e8..33222cf3b 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -12,6 +12,7 @@ import time from itertools import repeat from queue import Empty, Queue +from typing import Optional from core import utils from core.api.tlv import coreapi, dataconversion, structutils @@ -39,6 +40,7 @@ NodeTypes, RegisterTlvs, ) +from core.emulator.session import Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel from core.nodes.base import CoreNode, CoreNodeBase, NodeBase @@ -83,7 +85,7 @@ def __init__(self, request, client_address, server): thread.start() self.handler_threads.append(thread) - self.session = None + self.session: Optional[Session] = None self.coreemu = server.coreemu utils.close_onexec(request.fileno()) socketserver.BaseRequestHandler.__init__(self, request, client_address, server) @@ -176,7 +178,7 @@ def session_message(self, flags=0): node_count_list.append(str(session.get_node_count())) - date_list.append(time.ctime(session._state_time)) + date_list.append(time.ctime(session.state_time)) thumb = session.thumbnail if not thumb: @@ -1819,7 +1821,7 @@ def send_objects(self): """ # find all nodes and links links_data = [] - with self.session._nodes_lock: + with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] self.session.broadcast_node(node, MessageFlags.ADD) @@ -1897,8 +1899,8 @@ def send_objects(self): # TODO: send location info # send hook scripts - for state in sorted(self.session._hooks.keys()): - for file_name, config_data in self.session._hooks[state]: + for state in sorted(self.session.hooks.keys()): + for file_name, config_data in self.session.hooks[state]: file_data = FileData( message_type=MessageFlags.ADD, name=str(file_name), diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 146d186f9..cb978cb9d 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -279,7 +279,7 @@ def setup(self) -> int: logging.debug("emane setup") # TODO: drive this from the session object - with self.session._nodes_lock: + with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] if isinstance(node, EmaneNet): diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 0a90b9431..f35060485 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -6,7 +6,6 @@ import logging import os import pwd -import random import shutil import subprocess import tempfile @@ -113,15 +112,13 @@ def __init__( # dict of nodes: all nodes and nets self.nodes: Dict[int, NodeBase] = {} - self._nodes_lock = threading.Lock() + self.nodes_lock = threading.Lock() + # states and hooks handlers self.state: EventTypes = EventTypes.DEFINITION_STATE - self._state_time: float = time.monotonic() - self._state_file: str = os.path.join(self.session_dir, "state") - - # hooks handlers - self._hooks: Dict[EventTypes, Tuple[str, str]] = {} - self._state_hooks: Dict[EventTypes, Callable[[int], None]] = {} + self.state_time: float = time.monotonic() + self.hooks: Dict[EventTypes, Tuple[str, str]] = {} + self.state_hooks: Dict[EventTypes, List[Callable[[EventTypes], None]]] = {} self.add_state_hook( state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook ) @@ -154,15 +151,6 @@ def __init__( self.emane: EmaneManager = EmaneManager(self) self.sdt: Sdt = Sdt(self) - # initialize default node services - self.services.default_services = { - "mdr": ("zebra", "OSPFv3MDR", "IPForward"), - "PC": ("DefaultRoute",), - "prouter": (), - "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), - "host": ("DefaultRoute", "SSH"), - } - # config services self.service_manager: Optional[ConfigServiceManager] = None @@ -473,7 +461,7 @@ def update_link( f"cannot update link node1({type(node1)}) node2({type(node2)})" ) - def _next_node_id(self) -> int: + def next_node_id(self) -> int: """ Find the next valid node id, starting from 1. @@ -506,7 +494,7 @@ def add_node( # determine node id if not _id: - _id = self._next_node_id() + _id = self.next_node_id() # generate name if not provided if not options: @@ -692,7 +680,7 @@ def add_hook( "setting state hook: %s - %s source(%s)", state, file_name, source_name ) hook = file_name, data - state_hooks = self._hooks.setdefault(state, []) + state_hooks = self.hooks.setdefault(state, []) state_hooks.append(hook) # immediately run a hook if it is in the current state @@ -727,7 +715,7 @@ def clear(self) -> None: self.emane.shutdown() self.delete_nodes() self.distributed.shutdown() - self.del_hooks() + self.hooks.clear() self.emane.reset() self.emane.config_reset() self.location.reset() @@ -795,7 +783,6 @@ def broadcast_event(self, event_data: EventData) -> None: :param event_data: event data to send out :return: nothing """ - for handler in self.event_handlers: handler(event_data) @@ -806,7 +793,6 @@ def broadcast_exception(self, exception_data: ExceptionData) -> None: :param exception_data: exception data to send out :return: nothing """ - for handler in self.exception_handlers: handler(exception_data) @@ -837,7 +823,6 @@ def broadcast_file(self, file_data: FileData) -> None: :param file_data: file data to send out :return: nothing """ - for handler in self.file_handlers: handler(file_data) @@ -848,7 +833,6 @@ def broadcast_config(self, config_data: ConfigData) -> None: :param config_data: config data to send out :return: nothing """ - for handler in self.config_handlers: handler(config_data) @@ -859,7 +843,6 @@ def broadcast_link(self, link_data: LinkData) -> None: :param link_data: link data to send out :return: nothing """ - for handler in self.link_handlers: handler(link_data) @@ -871,22 +854,14 @@ def set_state(self, state: EventTypes, send_event: bool = False) -> None: :param send_event: if true, generate core API event messages :return: nothing """ - state_name = state.name if self.state == state: - logging.info( - "session(%s) is already in state: %s, skipping change", - self.id, - state_name, - ) return - self.state = state - self._state_time = time.monotonic() - logging.info("changing session(%s) to state %s", self.id, state_name) + self.state_time = time.monotonic() + logging.info("changing session(%s) to state %s", self.id, state.name) self.write_state(state) self.run_hooks(state) self.run_state_hooks(state) - if send_event: event_data = EventData(event_type=state, time=str(time.monotonic())) self.broadcast_event(event_data) @@ -898,10 +873,10 @@ def write_state(self, state: EventTypes) -> None: :param state: state to write to file :return: nothing """ + state_file = os.path.join(self.session_dir, "state") try: - state_file = open(self._state_file, "w") - state_file.write(f"{state.value} {state.name}\n") - state_file.close() + with open(state_file, "w") as f: + f.write(f"{state.value} {state.name}\n") except IOError: logging.exception("error writing state file: %s", state.name) @@ -913,61 +888,10 @@ def run_hooks(self, state: EventTypes) -> None: :param state: state to run hooks for :return: nothing """ - - # check that state change hooks exist - if state not in self._hooks: - return - - # retrieve all state hooks - hooks = self._hooks.get(state, []) - - # execute all state hooks - if hooks: - for hook in hooks: - self.run_hook(hook) - else: - logging.info("no state hooks for %s", state) - - def set_hook( - self, hook_type: str, file_name: str, source_name: str, data: str - ) -> None: - """ - Store a hook from a received file message. - - :param hook_type: hook type - :param file_name: file name for hook - :param source_name: source name - :param data: hook data - :return: nothing - """ - logging.info( - "setting state hook: %s - %s from %s", hook_type, file_name, source_name - ) - - _hook_id, state = hook_type.split(":")[:2] - if not state.isdigit(): - logging.error("error setting hook having state '%s'", state) - return - - state = int(state) - hook = file_name, data - - # append hook to current state hooks - state_hooks = self._hooks.setdefault(state, []) - state_hooks.append(hook) - - # immediately run a hook if it is in the current state - # (this allows hooks in the definition and configuration states) - if self.state == state: - logging.info("immediately running new state hook") + hooks = self.hooks.get(state, []) + for hook in hooks: self.run_hook(hook) - def del_hooks(self) -> None: - """ - Clear the hook scripts dict. - """ - self._hooks.clear() - def run_hook(self, hook: Tuple[str, str]) -> None: """ Run a hook. @@ -977,37 +901,23 @@ def run_hook(self, hook: Tuple[str, str]) -> None: """ file_name, data = hook logging.info("running hook %s", file_name) - - # write data to hook file + file_path = os.path.join(self.session_dir, file_name) + log_path = os.path.join(self.session_dir, f"{file_name}.log") try: - hook_file = open(os.path.join(self.session_dir, file_name), "w") - hook_file.write(data) - hook_file.close() - except IOError: - logging.exception("error writing hook '%s'", file_name) - - # setup hook stdout and stderr - try: - stdout = open(os.path.join(self.session_dir, file_name + ".log"), "w") - stderr = subprocess.STDOUT - except IOError: - logging.exception("error setting up hook stderr and stdout") - stdout = None - stderr = None - - # execute hook file - try: - args = ["/bin/sh", file_name] - subprocess.check_call( - args, - stdout=stdout, - stderr=stderr, - close_fds=True, - cwd=self.session_dir, - env=self.get_environment(), - ) - except (OSError, subprocess.CalledProcessError): - logging.exception("error running hook: %s", file_name) + with open(file_path, "w") as f: + f.write(data) + with open(log_path, "w") as f: + args = ["/bin/sh", file_name] + subprocess.check_call( + args, + stdout=f, + stderr=subprocess.STDOUT, + close_fds=True, + cwd=self.session_dir, + env=self.get_environment(), + ) + except (IOError, subprocess.CalledProcessError): + logging.exception("error running hook: %s", file_path) def run_state_hooks(self, state: EventTypes) -> None: """ @@ -1016,17 +926,16 @@ def run_state_hooks(self, state: EventTypes) -> None: :param state: state to run hooks for :return: nothing """ - for hook in self._state_hooks.get(state, []): - try: - hook(state) - except Exception: - message = ( - f"exception occured when running {state.name} state hook: {hook}" - ) - logging.exception(message) - self.exception( - ExceptionLevels.ERROR, "Session.run_state_hooks", message - ) + for hook in self.state_hooks.get(state, []): + self.run_state_hook(state, hook) + + def run_state_hook(self, state: EventTypes, hook: Callable[[EventTypes], None]): + try: + hook(state) + except Exception: + message = f"exception occurred when running {state.name} state hook: {hook}" + logging.exception(message) + self.exception(ExceptionLevels.ERROR, "Session.run_state_hooks", message) def add_state_hook( self, state: EventTypes, hook: Callable[[EventTypes], None] @@ -1038,15 +947,16 @@ def add_state_hook( :param hook: hook callback for the state :return: nothing """ - hooks = self._state_hooks.setdefault(state, []) + hooks = self.state_hooks.setdefault(state, []) if hook in hooks: raise CoreError("attempting to add duplicate state hook") hooks.append(hook) - if self.state == state: - hook(state) + self.run_state_hook(state, hook) - def del_state_hook(self, state: int, hook: Callable[[int], None]) -> None: + def del_state_hook( + self, state: EventTypes, hook: Callable[[EventTypes], None] + ) -> None: """ Delete a state hook. @@ -1054,24 +964,23 @@ def del_state_hook(self, state: int, hook: Callable[[int], None]) -> None: :param hook: hook to delete :return: nothing """ - hooks = self._state_hooks.setdefault(state, []) - hooks.remove(hook) + hooks = self.state_hooks.get(state, []) + if hook in hooks: + hooks.remove(hook) - def runtime_state_hook(self, state: EventTypes) -> None: + def runtime_state_hook(self, _state: EventTypes) -> None: """ Runtime state hook check. - :param state: state to check + :param _state: state to check :return: nothing """ - if state == EventTypes.RUNTIME_STATE: - self.emane.poststartup() - - # create session deployed xml - xml_file_name = os.path.join(self.session_dir, "session-deployed.xml") - xml_writer = corexml.CoreXmlWriter(self) - corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario) - xml_writer.write(xml_file_name) + self.emane.poststartup() + # create session deployed xml + xml_file_name = os.path.join(self.session_dir, "session-deployed.xml") + xml_writer = corexml.CoreXmlWriter(self) + corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario) + xml_writer.write(xml_file_name) def get_environment(self, state: bool = True) -> Dict[str, str]: """ @@ -1090,10 +999,8 @@ def get_environment(self, state: bool = True) -> Dict[str, str]: env["SESSION_FILENAME"] = str(self.file_name) env["SESSION_USER"] = str(self.user) env["SESSION_NODE_COUNT"] = str(self.get_node_count()) - if state: env["SESSION_STATE"] = str(self.state) - # attempt to read and add environment config file environment_config_file = os.path.join(constants.CORE_CONF_DIR, "environment") try: @@ -1104,7 +1011,6 @@ def get_environment(self, state: bool = True) -> Dict[str, str]: "environment configuration file does not exist: %s", environment_config_file, ) - # attempt to read and add user environment file if self.user: environment_user_file = os.path.join( @@ -1117,7 +1023,6 @@ def get_environment(self, state: bool = True) -> Dict[str, str]: "user core environment settings file not present: %s", environment_user_file, ) - return env def set_thumbnail(self, thumb_file: str) -> None: @@ -1131,7 +1036,6 @@ def set_thumbnail(self, thumb_file: str) -> None: logging.error("thumbnail file to set does not exist: %s", thumb_file) self.thumbnail = None return - destination_file = os.path.join(self.session_dir, os.path.basename(thumb_file)) shutil.copy(thumb_file, destination_file) self.thumbnail = destination_file @@ -1151,20 +1055,8 @@ def set_user(self, user: str) -> None: os.chown(self.session_dir, uid, gid) except IOError: logging.exception("failed to set permission on %s", self.session_dir) - self.user = user - def get_node_id(self) -> int: - """ - Return a unique, new node id. - """ - with self._nodes_lock: - while True: - node_id = random.randint(1, 0xFFFF) - if node_id not in self.nodes: - break - return node_id - def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: """ Create an emulation node. @@ -1176,7 +1068,7 @@ def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: :raises core.CoreError: when id of the node to create already exists """ node = _class(self, *args, **kwargs) - with self._nodes_lock: + with self.nodes_lock: if node.id in self.nodes: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") @@ -1192,9 +1084,9 @@ def get_node(self, _id: int, _class: Type[NT]) -> NT: :return: node for the given id :raises core.CoreError: when node does not exist """ - if _id not in self.nodes: + node = self.nodes.get(_id) + if node is None: raise CoreError(f"unknown node id {_id}") - node = self.nodes[_id] if not isinstance(node, _class): actual = node.__class__.__name__ expected = _class.__name__ @@ -1210,7 +1102,7 @@ def delete_node(self, _id: int) -> bool: """ # delete node and check for session shutdown if a node was removed node = None - with self._nodes_lock: + with self.nodes_lock: if _id in self.nodes: node = self.nodes.pop(_id) logging.info("deleted node(%s)", node.name) @@ -1224,7 +1116,7 @@ def delete_nodes(self) -> None: """ Clear the nodes dictionary, and call shutdown for each node. """ - with self._nodes_lock: + with self.nodes_lock: funcs = [] while self.nodes: _, node = self.nodes.popitem() @@ -1237,29 +1129,15 @@ def write_nodes(self) -> None: Write nodes to a 'nodes' file in the session dir. The 'nodes' file lists: number, name, api-type, class-type """ + file_path = os.path.join(self.session_dir, "nodes") try: - with self._nodes_lock: - file_path = os.path.join(self.session_dir, "nodes") + with self.nodes_lock: with open(file_path, "w") as f: - for _id in self.nodes.keys(): - node = self.nodes[_id] + for _id, node in self.nodes.items(): f.write(f"{_id} {node.name} {node.apitype} {type(node)}\n") except IOError: logging.exception("error writing nodes file") - def dump_session(self) -> None: - """ - Log information about the session in its current state. - """ - logging.info("session id=%s name=%s state=%s", self.id, self.name, self.state) - logging.info( - "file=%s thumbnail=%s node_count=%s/%s", - self.file_name, - self.thumbnail, - self.get_node_count(), - len(self.nodes), - ) - def exception( self, level: ExceptionLevels, source: str, text: str, node_id: int = None ) -> None: @@ -1327,17 +1205,15 @@ def get_node_count(self) -> int: :return: created node count """ - with self._nodes_lock: + with self.nodes_lock: count = 0 - for node_id in self.nodes: - node = self.nodes[node_id] + for node in self.nodes.values(): is_p2p_ctrlnet = isinstance(node, (PtpNet, CtrlNet)) is_tap = isinstance(node, GreTapBridge) and not isinstance( node, TunnelNode ) if is_p2p_ctrlnet or is_tap: continue - count += 1 return count @@ -1359,7 +1235,6 @@ def check_runtime(self) -> None: if self.state == EventTypes.RUNTIME_STATE: logging.info("valid runtime state found, returning") return - # start event loop and set to runtime self.event_loop.run() self.set_state(EventTypes.RUNTIME_STATE, send_event=True) @@ -1375,7 +1250,7 @@ def data_collect(self) -> None: self.event_loop.stop() # stop node services - with self._nodes_lock: + with self.nodes_lock: funcs = [] for node_id in self.nodes: node = self.nodes[node_id] @@ -1447,7 +1322,7 @@ def boot_nodes(self) -> List[Exception]: :return: service boot exceptions """ - with self._nodes_lock: + with self.nodes_lock: funcs = [] start = time.monotonic() for _id in self.nodes: @@ -1545,7 +1420,6 @@ def add_remove_control_net( else: prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index] logging.debug("prefix spec: %s", prefix_spec) - server_interface = self.get_control_net_server_interfaces()[net_index] # return any existing controlnet bridge @@ -1685,7 +1559,7 @@ def runtime(self) -> float: if not in runtime. """ if self.state == EventTypes.RUNTIME_STATE: - return time.monotonic() - self._state_time + return time.monotonic() - self.state_time else: return 0.0 @@ -1708,7 +1582,6 @@ def add_event( """ event_time = float(event_time) current_time = self.runtime() - if current_time > 0: if event_time <= current_time: logging.warning( @@ -1718,11 +1591,9 @@ def add_event( ) return event_time = event_time - current_time - self.event_loop.add_event( event_time, self.run_event, node=node, name=name, data=data ) - if not name: name = "" logging.info( @@ -1732,8 +1603,6 @@ def add_event( data, ) - # TODO: if data is None, this blows up, but this ties into how event functions - # are ran, need to clean that up def run_event( self, node_id: int = None, name: str = None, data: str = None ) -> None: @@ -1745,10 +1614,12 @@ def run_event( :param data: event data :return: nothing """ + if data is None: + logging.warning("no data for event node(%s) name(%s)", node_id, name) + return now = self.runtime() if not name: name = "" - logging.info("running event %s at time %s cmd=%s", name, now, data) if not node_id: utils.mute_detach(data) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 37a41b812..66ea41a09 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -63,7 +63,7 @@ def __init__( self.session: "Session" = session if _id is None: - _id = session.get_node_id() + _id = session.next_node_id() self.id: int = _id if name is None: name = f"o{self.id}" diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 062217cb5..04fff3e43 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -215,7 +215,7 @@ def sendobjs(self) -> None: for layer in CORE_LAYERS: self.cmd(f"layer {layer}") - with self.session._nodes_lock: + with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] if isinstance(node, CoreNetworkBase): diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 491113ffd..391b53d13 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -325,7 +325,13 @@ def __init__(self, session: "Session") -> None: """ self.session = session # dict of default services tuples, key is node type - self.default_services = {} + self.default_services = { + "mdr": ("zebra", "OSPFv3MDR", "IPForward"), + "PC": ("DefaultRoute",), + "prouter": (), + "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), + "host": ("DefaultRoute", "SSH"), + } # dict of node ids to dict of custom services by name self.custom_services = {} diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index afc1d8263..45e8d9c56 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -320,8 +320,8 @@ def write_session_origin(self) -> None: def write_session_hooks(self) -> None: # hook scripts hooks = etree.Element("session_hooks") - for state in sorted(self.session._hooks, key=lambda x: x.value): - for file_name, data in self.session._hooks[state]: + for state in sorted(self.session.hooks, key=lambda x: x.value): + for file_name, data in self.session.hooks[state]: hook = etree.SubElement(hooks, "hook") add_attribute(hook, "name", file_name) add_attribute(hook, "state", state.value) diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 131af93d4..8beb4b9a1 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -133,7 +133,7 @@ def test_start_session(self, grpc_server: CoreGrpcServer): assert wlan_node.id in session.nodes assert session.nodes[node1.id].netif(0) is not None assert session.nodes[node2.id].netif(0) is not None - hook_file, hook_data = session._hooks[EventTypes.RUNTIME_STATE][0] + hook_file, hook_data = session.hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data assert session.location.refxyz == (location_x, location_y, location_z) diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 1187b4d7f..d3b9362d4 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -382,7 +382,7 @@ def test_session_delete(self, coretlv: CoreHandler): def test_file_hook_add(self, coretlv: CoreHandler): state = EventTypes.DATACOLLECT_STATE - assert coretlv.session._hooks.get(state) is None + assert coretlv.session.hooks.get(state) is None file_name = "test.sh" file_data = "echo hello" message = coreapi.CoreFileMessage.create( @@ -396,7 +396,7 @@ def test_file_hook_add(self, coretlv: CoreHandler): coretlv.handle_message(message) - hooks = coretlv.session._hooks.get(state) + hooks = coretlv.session.hooks.get(state) assert len(hooks) == 1 name, data = hooks[0] assert file_name == name diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 35e03f7da..b28e09861 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -48,7 +48,7 @@ def test_xml_hooks(self, session: Session, tmpdir: TemporaryFile): session.open_xml(file_path, start=True) # verify nodes have been recreated - runtime_hooks = session._hooks.get(state) + runtime_hooks = session.hooks.get(state) assert runtime_hooks runtime_hook = runtime_hooks[0] assert file_name == runtime_hook[0] From e18ffaafce70b5c467d03ad11c6777e17197097c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 17:41:13 -0700 Subject: [PATCH 014/210] daemon: xml files will now write and read loss, but fallback to looking for per for compatibility --- daemon/core/xml/corexml.py | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 820f1cea3..2b15aa5a5 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -569,7 +569,7 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: options = etree.Element("options") add_attribute(options, "delay", link_data.delay) add_attribute(options, "bandwidth", link_data.bandwidth) - add_attribute(options, "per", link_data.loss) + add_attribute(options, "loss", link_data.loss) add_attribute(options, "dup", link_data.dup) add_attribute(options, "jitter", link_data.jitter) add_attribute(options, "mer", link_data.mer) @@ -947,37 +947,39 @@ def read_links(self) -> None: interface_two = create_interface_data(interface_two_element) options_element = link_element.find("options") - link_options = LinkOptions() + options = LinkOptions() if options_element is not None: - link_options.bandwidth = get_int(options_element, "bandwidth") - link_options.burst = get_int(options_element, "burst") - link_options.delay = get_int(options_element, "delay") - link_options.dup = get_int(options_element, "dup") - link_options.mer = get_int(options_element, "mer") - link_options.mburst = get_int(options_element, "mburst") - link_options.jitter = get_int(options_element, "jitter") - link_options.key = get_int(options_element, "key") - link_options.loss = get_float(options_element, "per") - link_options.unidirectional = get_int(options_element, "unidirectional") - link_options.session = options_element.get("session") - link_options.emulation_id = get_int(options_element, "emulation_id") - link_options.network_id = get_int(options_element, "network_id") - link_options.opaque = options_element.get("opaque") - link_options.gui_attributes = options_element.get("gui_attributes") - - if link_options.unidirectional == 1 and node_set in node_sets: + options.bandwidth = get_int(options_element, "bandwidth") + options.burst = get_int(options_element, "burst") + options.delay = get_int(options_element, "delay") + options.dup = get_int(options_element, "dup") + options.mer = get_int(options_element, "mer") + options.mburst = get_int(options_element, "mburst") + options.jitter = get_int(options_element, "jitter") + options.key = get_int(options_element, "key") + options.loss = get_float(options_element, "loss") + if options.loss is None: + options.loss = get_float(options_element, "per") + options.unidirectional = get_int(options_element, "unidirectional") + options.session = options_element.get("session") + options.emulation_id = get_int(options_element, "emulation_id") + options.network_id = get_int(options_element, "network_id") + options.opaque = options_element.get("opaque") + options.gui_attributes = options_element.get("gui_attributes") + + if options.unidirectional == 1 and node_set in node_sets: logging.info( "updating link node_one(%s) node_two(%s)", node_one, node_two ) self.session.update_link( - node_one, node_two, interface_one.id, interface_two.id, link_options + node_one, node_two, interface_one.id, interface_two.id, options ) else: logging.info( "adding link node_one(%s) node_two(%s)", node_one, node_two ) self.session.add_link( - node_one, node_two, interface_one, interface_two, link_options + node_one, node_two, interface_one, interface_two, options ) node_sets.add(node_set) From 5df2e36083acaec721d3ef6b78fe2110c6d411e2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 21:48:51 -0700 Subject: [PATCH 015/210] daemon: fixed session.add_event parameter to be specific to node_id --- daemon/core/api/tlv/corehandlers.py | 32 ++++++++++++++--------------- daemon/core/emulator/session.py | 11 +++------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 562f8c894..2cd7bface 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -809,38 +809,38 @@ def handle_execute_message(self, message): :param core.api.tlv.coreapi.CoreExecMessage message: execute message to handle :return: reply messages """ - node_num = message.get_tlv(ExecuteTlvs.NODE.value) + node_id = message.get_tlv(ExecuteTlvs.NODE.value) execute_num = message.get_tlv(ExecuteTlvs.NUMBER.value) execute_time = message.get_tlv(ExecuteTlvs.TIME.value) command = message.get_tlv(ExecuteTlvs.COMMAND.value) # local flag indicates command executed locally, not on a node - if node_num is None and not message.flags & MessageFlags.LOCAL.value: + if node_id is None and not message.flags & MessageFlags.LOCAL.value: raise ValueError("Execute Message is missing node number.") if execute_num is None: raise ValueError("Execute Message is missing execution number.") if execute_time is not None: - self.session.add_event(execute_time, node=node_num, name=None, data=command) + self.session.add_event( + float(execute_time), node_id=node_id, name=None, data=command + ) return () try: - node = self.session.get_node(node_num, CoreNodeBase) + node = self.session.get_node(node_id, CoreNodeBase) # build common TLV items for reply tlv_data = b"" - if node_num is not None: - tlv_data += coreapi.CoreExecuteTlv.pack( - ExecuteTlvs.NODE.value, node_num - ) + if node_id is not None: + tlv_data += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node_id) tlv_data += coreapi.CoreExecuteTlv.pack( ExecuteTlvs.NUMBER.value, execute_num ) tlv_data += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, command) if message.flags & MessageFlags.TTY.value: - if node_num is None: + if node_id is None: raise NotImplementedError # echo back exec message with cmd for spawning interactive terminal if command == "bash": @@ -850,7 +850,6 @@ def handle_execute_message(self, message): reply = coreapi.CoreExecMessage.pack(MessageFlags.TTY.value, tlv_data) return (reply,) else: - logging.info("execute message with cmd=%s", command) # execute command and send a response if ( message.flags & MessageFlags.STRING.value @@ -870,7 +869,6 @@ def handle_execute_message(self, message): except CoreCommandError as e: res = e.stderr status = e.returncode - logging.info("done exec cmd=%s with status=%d", command, status) if message.flags & MessageFlags.TEXT.value: tlv_data += coreapi.CoreExecuteTlv.pack( ExecuteTlvs.RESULT.value, res @@ -888,7 +886,7 @@ def handle_execute_message(self, message): else: node.cmd(command, wait=False) except CoreError: - logging.exception("error getting object: %s", node_num) + logging.exception("error getting object: %s", node_id) # XXX wait and queue this message to try again later # XXX maybe this should be done differently if not message.flags & MessageFlags.LOCAL.value: @@ -1549,11 +1547,11 @@ def handle_event_message(self, message): if event_type == EventTypes.INSTANTIATION_STATE and isinstance( node, WlanNode ): - self.session.start_mobility(node_ids=(node.id,)) + self.session.start_mobility(node_ids=[node.id]) return () logging.warning( - "dropping unhandled event message for node: %s", node_id + "dropping unhandled event message for node: %s", node.name ) return () self.session.set_state(event_type) @@ -1611,14 +1609,16 @@ def handle_event_message(self, message): self.session.save_xml(filename) elif event_type == EventTypes.SCHEDULED: etime = event_data.time - node = event_data.node + node_id = event_data.node name = event_data.name data = event_data.data if etime is None: logging.warning("Event message scheduled event missing start time") return () if message.flags & MessageFlags.ADD.value: - self.session.add_event(float(etime), node=node, name=name, data=data) + self.session.add_event( + float(etime), node_id=node_id, name=name, data=data + ) else: raise NotImplementedError diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index f35060485..2225bb6f8 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1564,23 +1564,18 @@ def runtime(self) -> float: return 0.0 def add_event( - self, - event_time: float, - node: CoreNode = None, - name: str = None, - data: str = None, + self, event_time: float, node_id: int = None, name: str = None, data: str = None ) -> None: """ Add an event to the event queue, with a start time relative to the start of the runtime state. :param event_time: event time - :param node: node to add event for + :param node_id: node to add event for :param name: name of event :param data: data for event :return: nothing """ - event_time = float(event_time) current_time = self.runtime() if current_time > 0: if event_time <= current_time: @@ -1592,7 +1587,7 @@ def add_event( return event_time = event_time - current_time self.event_loop.add_event( - event_time, self.run_event, node=node, name=name, data=data + event_time, self.run_event, node_id=node_id, name=name, data=data ) if not name: name = "" From 8d48393525094387f7598d192e21a68cbdabfae0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 21:53:09 -0700 Subject: [PATCH 016/210] daemon: updated usage of if1/2 to be consistent with interface1/2 for now --- daemon/core/nodes/network.py | 54 +++++++++++++++++------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 8ac1939ee..9b46dfd50 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -898,16 +898,16 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat if len(self._netif) != 2: return all_links - if1, if2 = self._netif.values() + interface1, interface2 = self._netif.values() unidirectional = 0 - if if1.getparams() != if2.getparams(): + if interface1.getparams() != interface2.getparams(): unidirectional = 1 interface1_ip4 = None interface1_ip4_mask = None interface1_ip6 = None interface1_ip6_mask = None - for address in if1.addrlist: + for address in interface1.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): @@ -921,7 +921,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat interface2_ip4_mask = None interface2_ip6 = None interface2_ip6_mask = None - for address in if2.addrlist: + for address in interface2.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): @@ -933,31 +933,30 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat link_data = LinkData( message_type=flags, - node1_id=if1.node.id, - node2_id=if2.node.id, + node1_id=interface1.node.id, + node2_id=interface2.node.id, link_type=self.linktype, unidirectional=unidirectional, - delay=if1.getparam("delay"), - bandwidth=if1.getparam("bw"), - loss=if1.getparam("loss"), - dup=if1.getparam("duplicate"), - jitter=if1.getparam("jitter"), - interface1_id=if1.node.getifindex(if1), - interface1_name=if1.name, - interface1_mac=if1.hwaddr, + delay=interface1.getparam("delay"), + bandwidth=interface1.getparam("bw"), + loss=interface1.getparam("loss"), + dup=interface1.getparam("duplicate"), + jitter=interface1.getparam("jitter"), + interface1_id=interface1.node.getifindex(interface1), + interface1_name=interface1.name, + interface1_mac=interface1.hwaddr, interface1_ip4=interface1_ip4, interface1_ip4_mask=interface1_ip4_mask, interface1_ip6=interface1_ip6, interface1_ip6_mask=interface1_ip6_mask, - interface2_id=if2.node.getifindex(if2), - interface2_name=if2.name, - interface2_mac=if2.hwaddr, + interface2_id=interface2.node.getifindex(interface2), + interface2_name=interface2.name, + interface2_mac=interface2.hwaddr, interface2_ip4=interface2_ip4, interface2_ip4_mask=interface2_ip4_mask, interface2_ip6=interface2_ip6, interface2_ip6_mask=interface2_ip6_mask, ) - all_links.append(link_data) # build a 2nd link message for the upstream link parameters @@ -966,19 +965,18 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, - node1_id=if2.node.id, - node2_id=if1.node.id, - delay=if2.getparam("delay"), - bandwidth=if2.getparam("bw"), - loss=if2.getparam("loss"), - dup=if2.getparam("duplicate"), - jitter=if2.getparam("jitter"), + node1_id=interface2.node.id, + node2_id=interface1.node.id, + delay=interface2.getparam("delay"), + bandwidth=interface2.getparam("bw"), + loss=interface2.getparam("loss"), + dup=interface2.getparam("duplicate"), + jitter=interface2.getparam("jitter"), unidirectional=1, - interface1_id=if2.node.getifindex(if2), - interface2_id=if1.node.getifindex(if1), + interface1_id=interface2.node.getifindex(interface2), + interface2_id=interface1.node.getifindex(interface1), ) all_links.append(link_data) - return all_links From 91f1f7f004dba90735b735753a492b8e193424a8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 22:01:07 -0700 Subject: [PATCH 017/210] daemon: added global type hinting to core.emulator.session and core.api.grpc.server --- daemon/core/api/grpc/server.py | 6 +++--- daemon/core/emulator/session.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 3374df2e7..8b349b67c 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -6,7 +6,7 @@ import threading import time from concurrent import futures -from typing import Iterable, Optional, Type +from typing import Iterable, Optional, Pattern, Type import grpc from grpc import ServicerContext @@ -118,8 +118,8 @@ from core.nodes.network import WlanNode from core.services.coreservices import ServiceManager -_ONE_DAY_IN_SECONDS = 60 * 60 * 24 -_INTERFACE_REGEX = re.compile(r"veth(?P[0-9a-fA-F]+)") +_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24 +_INTERFACE_REGEX: Pattern = re.compile(r"veth(?P[0-9a-fA-F]+)") class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 2225bb6f8..53f5156d0 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -11,7 +11,7 @@ import tempfile import threading import time -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar from core import constants, utils from core.configservice.manager import ConfigServiceManager @@ -59,7 +59,7 @@ from core.xml.corexml import CoreXmlReader, CoreXmlWriter # maps for converting from API call node type values to classes and vice versa -NODES = { +NODES: Dict[NodeTypes, Type[NodeBase]] = { NodeTypes.DEFAULT: CoreNode, NodeTypes.PHYSICAL: PhysicalNode, NodeTypes.SWITCH: SwitchNode, @@ -74,11 +74,11 @@ NodeTypes.DOCKER: DockerNode, NodeTypes.LXC: LxcNode, } -NODES_TYPE = {NODES[x]: x for x in NODES} -CONTAINER_NODES = {DockerNode, LxcNode} -CTRL_NET_ID = 9001 -LINK_COLORS = ["green", "blue", "orange", "purple", "turquoise"] -NT = TypeVar("NT", bound=NodeBase) +NODES_TYPE: Dict[Type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES} +CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode} +CTRL_NET_ID: int = 9001 +LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"] +NT: TypeVar = TypeVar("NT", bound=NodeBase) class Session: From d94bae6b42aabddd92d42a99a1fa7b4a9f4cab6b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 22:25:38 -0700 Subject: [PATCH 018/210] daemon: added class variable type hinting to core.services.coreservices --- daemon/core/services/coreservices.py | 83 ++++++++++++++-------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 391b53d13..d22bc7a59 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -10,7 +10,7 @@ import enum import logging import time -from typing import TYPE_CHECKING, Iterable, List, Tuple, Type +from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple, Type from core import utils from core.constants import which @@ -36,14 +36,15 @@ class ServiceMode(enum.Enum): class ServiceDependencies: """ Can generate boot paths for services, based on their dependencies. Will validate - that all services will be booted and that all dependencies exist within the services provided. + that all services will be booted and that all dependencies exist within the services + provided. """ - def __init__(self, services: List[Type["CoreService"]]) -> None: + def __init__(self, services: List["CoreService"]) -> None: # helpers to check validity - self.dependents = {} - self.booted = set() - self.node_services = {} + self.dependents: Dict[str, Set[str]] = {} + self.booted: Set[str] = set() + self.node_services: Dict[str, "CoreService"] = {} for service in services: self.node_services[service.name] = service for dependency in service.dependencies: @@ -51,9 +52,9 @@ def __init__(self, services: List[Type["CoreService"]]) -> None: dependents.add(service.name) # used to find paths - self.path = [] - self.visited = set() - self.visiting = set() + self.path: List["CoreService"] = [] + self.visited: Set[str] = set() + self.visiting: Set[str] = set() def boot_paths(self) -> List[List["CoreService"]]: """ @@ -131,7 +132,7 @@ def _visit(self, current_service: "CoreService") -> List["CoreService"]: class ServiceShim: - keys = [ + keys: List[str] = [ "dirs", "files", "startidx", @@ -241,10 +242,10 @@ class ServiceManager: Manages services available for CORE nodes to use. """ - services = {} + services: Dict[str, Type["CoreService"]] = {} @classmethod - def add(cls, service: "CoreService") -> None: + def add(cls, service: Type["CoreService"]) -> None: """ Add a service to manager. @@ -314,8 +315,8 @@ class CoreServices: custom service configuration. A CoreService is not a Configurable. """ - name = "services" - config_type = RegisterTlvs.UTILITY + name: str = "services" + config_type: RegisterTlvs = RegisterTlvs.UTILITY def __init__(self, session: "Session") -> None: """ @@ -323,17 +324,17 @@ def __init__(self, session: "Session") -> None: :param session: session this manager is tied to """ - self.session = session + self.session: "Session" = session # dict of default services tuples, key is node type - self.default_services = { - "mdr": ("zebra", "OSPFv3MDR", "IPForward"), - "PC": ("DefaultRoute",), - "prouter": (), - "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), - "host": ("DefaultRoute", "SSH"), + self.default_services: Dict[str, List[str]] = { + "mdr": ["zebra", "OSPFv3MDR", "IPForward"], + "PC": ["DefaultRoute"], + "prouter": [], + "router": ["zebra", "OSPFv2", "OSPFv3", "IPForward"], + "host": ["DefaultRoute", "SSH"], } # dict of node ids to dict of custom services by name - self.custom_services = {} + self.custom_services: Dict[int, Dict[str, "CoreService"]] = {} def reset(self) -> None: """ @@ -425,7 +426,7 @@ def add_services( continue node.services.append(service) - def all_configs(self) -> List[Tuple[int, Type["CoreService"]]]: + def all_configs(self) -> List[Tuple[int, "CoreService"]]: """ Return (node_id, service) tuples for all stored configs. Used when reconnecting to a session or opening XML. @@ -808,50 +809,50 @@ class CoreService: """ # service name should not include spaces - name = None + name: Optional[str] = None # executables that must exist for service to run - executables = () + executables: Tuple[str, ...] = () # sets service requirements that must be started prior to this service starting - dependencies = () + dependencies: Tuple[str, ...] = () # group string allows grouping services together - group = None + group: Optional[str] = None # private, per-node directories required by this service - dirs = () + dirs: Tuple[str, ...] = () # config files written by this service - configs = () + configs: Tuple[str, ...] = () # config file data - config_data = {} + config_data: Dict[str, str] = {} # list of startup commands - startup = () + startup: Tuple[str, ...] = () # list of shutdown commands - shutdown = () + shutdown: Tuple[str, ...] = () # list of validate commands - validate = () + validate: Tuple[str, ...] = () # validation mode, used to determine startup success - validation_mode = ServiceMode.NON_BLOCKING + validation_mode: ServiceMode = ServiceMode.NON_BLOCKING # time to wait in seconds for determining if service started successfully - validation_timer = 5 + validation_timer: int = 5 # validation period in seconds, how frequent validation is attempted - validation_period = 0.5 + validation_period: float = 0.5 # metadata associated with this service - meta = None + meta: Optional[str] = None # custom configuration text - custom = False - custom_needed = False + custom: bool = False + custom_needed: bool = False def __init__(self) -> None: """ @@ -859,8 +860,8 @@ def __init__(self) -> None: against their config. Services are instantiated when a custom configuration is used to override their default parameters. """ - self.custom = True - self.config_data = self.__class__.config_data.copy() + self.custom: bool = True + self.config_data: Dict[str, str] = self.__class__.config_data.copy() @classmethod def on_load(cls) -> None: From 8587da062161130c9ab81b072548c79a7e860b5d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 23:50:08 -0700 Subject: [PATCH 019/210] daemon: moved node instantiation into lock to guarantee id uniqueness, removed node count from environment as it also attmpts to use lock and wouldnt be accurate either --- daemon/core/emulator/session.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 53f5156d0..826e3f0a0 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -998,7 +998,6 @@ def get_environment(self, state: bool = True) -> Dict[str, str]: env["SESSION_NAME"] = str(self.name) env["SESSION_FILENAME"] = str(self.file_name) env["SESSION_USER"] = str(self.user) - env["SESSION_NODE_COUNT"] = str(self.get_node_count()) if state: env["SESSION_STATE"] = str(self.state) # attempt to read and add environment config file @@ -1067,8 +1066,8 @@ def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: :return: the created node instance :raises core.CoreError: when id of the node to create already exists """ - node = _class(self, *args, **kwargs) with self.nodes_lock: + node = _class(self, *args, **kwargs) if node.id in self.nodes: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") From 3243a69afa891a23d75fcd149b2cc0d1b98261fb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 00:46:11 -0700 Subject: [PATCH 020/210] daemon: updated xml files to use node1 and interface1 instead of node_one and interface_one, will still fallback to parse old names --- daemon/core/xml/corexml.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 831ffae6d..759de680e 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -45,11 +45,11 @@ def get_type(element: etree.Element, name: str, _type: Generic[T]) -> Optional[T return value -def get_float(element: etree.Element, name: str) -> float: +def get_float(element: etree.Element, name: str) -> Optional[float]: return get_type(element, name, float) -def get_int(element: etree.Element, name: str) -> int: +def get_int(element: etree.Element, name: str) -> Optional[int]: return get_type(element, name, int) @@ -529,13 +529,13 @@ def create_interface_element( def create_link_element(self, link_data: LinkData) -> etree.Element: link_element = etree.Element("link") - add_attribute(link_element, "node_one", link_data.node1_id) - add_attribute(link_element, "node_two", link_data.node2_id) + add_attribute(link_element, "node1", link_data.node1_id) + add_attribute(link_element, "node2", link_data.node2_id) # check for interface one if link_data.interface1_id is not None: interface1 = self.create_interface_element( - "interface_one", + "interface1", link_data.node1_id, link_data.interface1_id, link_data.interface1_mac, @@ -549,7 +549,7 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: # check for interface two if link_data.interface2_id is not None: interface2 = self.create_interface_element( - "interface_two", + "interface2", link_data.node2_id, link_data.interface2_id, link_data.interface2_mac, @@ -932,16 +932,24 @@ def read_links(self) -> None: node_sets = set() for link_element in link_elements.iterchildren(): - node1_id = get_int(link_element, "node_one") - node2_id = get_int(link_element, "node_two") + node1_id = get_int(link_element, "node1") + if node1_id is None: + node1_id = get_int(link_element, "node_one") + node2_id = get_int(link_element, "node2") + if node2_id is None: + node2_id = get_int(link_element, "node_two") node_set = frozenset((node1_id, node2_id)) - interface1_element = link_element.find("interface_one") + interface1_element = link_element.find("interface1") + if interface1_element is None: + interface1_element = link_element.find("interface_one") interface1_data = None if interface1_element is not None: interface1_data = create_interface_data(interface1_element) - interface2_element = link_element.find("interface_two") + interface2_element = link_element.find("interface2") + if interface2_element is None: + interface2_element = link_element.find("interface_two") interface2_data = None if interface2_element is not None: interface2_data = create_interface_data(interface2_element) From c4c667bb741fbd2027eedd61c61ddb35c38ca414 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 09:37:58 -0700 Subject: [PATCH 021/210] daemon: removed node.startup from inside constructor, session is now responsible, providing more control and avoiding issues when using super calls where you dont want to start just yet --- daemon/core/emane/nodes.py | 3 +-- daemon/core/emulator/session.py | 14 ++++++++++---- daemon/core/nodes/base.py | 26 ++++++++------------------ daemon/core/nodes/docker.py | 4 +--- daemon/core/nodes/lxd.py | 4 +--- daemon/core/nodes/network.py | 20 +++++--------------- daemon/core/nodes/physical.py | 15 ++++----------- 7 files changed, 30 insertions(+), 56 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index e88cb194d..68c1bc054 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -55,10 +55,9 @@ def __init__( session: "Session", _id: int = None, name: str = None, - start: bool = True, server: DistributedServer = None, ) -> None: - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.conf: str = "" self.nemidmap: Dict[CoreInterface, int] = {} self.model: "OptionalEmaneModel" = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 826e3f0a0..e63c30c73 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -257,7 +257,7 @@ def add_link( if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): logging.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() - ptp = self.create_node(PtpNet, start=start) + ptp = self.create_node(PtpNet, start) interface1 = node1.newnetif(ptp, interface1_data) interface2 = node2.newnetif(ptp, interface2_data) ptp.linkconfig(interface1, options) @@ -517,10 +517,10 @@ def add_node( name, start, ) - kwargs = dict(_id=_id, name=name, start=start, server=server) + kwargs = dict(_id=_id, name=name, server=server) if _class in CONTAINER_NODES: kwargs["image"] = options.image - node = self.create_node(_class, **kwargs) + node = self.create_node(_class, start, **kwargs) # set node attributes node.icon = options.icon @@ -1056,11 +1056,14 @@ def set_user(self, user: str) -> None: logging.exception("failed to set permission on %s", self.session_dir) self.user = user - def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: + def create_node( + self, _class: Type[NT], start: bool, *args: Any, **kwargs: Any + ) -> NT: """ Create an emulation node. :param _class: node class to create + :param start: True to start node, False otherwise :param args: list of arguments for the class to create :param kwargs: dictionary of arguments for the class to create :return: the created node instance @@ -1072,6 +1075,8 @@ def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") self.nodes[node.id] = node + if start: + node.startup() return node def get_node(self, _id: int, _class: Type[NT]) -> NT: @@ -1464,6 +1469,7 @@ def add_remove_control_net( ) control_net = self.create_node( CtrlNet, + True, prefix, _id=_id, updown_script=updown_script, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index cd77f857b..49fe76207 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type import netaddr @@ -47,7 +47,6 @@ def __init__( session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -56,7 +55,6 @@ def __init__( :param session: CORE session object :param _id: id :param name: object name - :param start: start value :param server: remote server node will run on, default is None for localhost """ @@ -254,7 +252,6 @@ def __init__( session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -263,11 +260,10 @@ def __init__( :param session: CORE session object :param _id: object id :param name: object name - :param start: boolean for starting :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.config_services: Dict[str, "ConfigService"] = {} self.nodedir: Optional[str] = None self.tmpnodedir: bool = False @@ -492,8 +488,8 @@ class CoreNode(CoreNodeBase): Provides standard core node logic. """ - apitype = NodeTypes.DEFAULT - valid_address_types = {"inet", "inet6", "inet6link"} + apitype: NodeTypes = NodeTypes.DEFAULT + valid_address_types: Set[str] = {"inet", "inet6", "inet6link"} def __init__( self, @@ -501,7 +497,6 @@ def __init__( _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -511,11 +506,10 @@ def __init__( :param _id: object id :param name: object name :param nodedir: node directory - :param start: start flag :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.nodedir: Optional[str] = nodedir self.ctrlchnlname: str = os.path.abspath( os.path.join(self.session.session_dir, self.name) @@ -526,8 +520,6 @@ def __init__( self._mounts: List[Tuple[str, str]] = [] use_ovs = session.options.get_config("ovs") == "True" self.node_net_client: LinuxNetClient = self.create_node_net_client(use_ovs) - if start: - self.startup() def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ @@ -981,15 +973,14 @@ class CoreNetworkBase(NodeBase): Base class for networks """ - linktype = LinkTypes.WIRED - is_emane = False + linktype: LinkTypes = LinkTypes.WIRED + is_emane: bool = False def __init__( self, session: "Session", _id: int, name: str, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -998,11 +989,10 @@ def __init__( :param session: CORE session object :param _id: object id :param name: object name - :param start: should object start :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.brname = None self._linked = {} self._linked_lock = threading.Lock() diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index fa4b8f8bc..e911db74c 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -77,7 +77,6 @@ def __init__( _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: DistributedServer = None, image: str = None ) -> None: @@ -88,7 +87,6 @@ def __init__( :param _id: object id :param name: object name :param nodedir: node directory - :param start: start flag :param server: remote server node will run on, default is None for localhost :param image: image to start container with @@ -96,7 +94,7 @@ def __init__( if image is None: image = "ubuntu" self.image: str = image - super().__init__(session, _id, name, nodedir, start, server) + super().__init__(session, _id, name, nodedir, server) def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index af906f012..a66791ced 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -74,7 +74,6 @@ def __init__( _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: DistributedServer = None, image: str = None, ) -> None: @@ -85,7 +84,6 @@ def __init__( :param _id: object id :param name: object name :param nodedir: node directory - :param start: start flag :param server: remote server node will run on, default is None for localhost :param image: image to start container with @@ -93,7 +91,7 @@ def __init__( if image is None: image = "ubuntu" self.image: str = image - super().__init__(session, _id, name, nodedir, start, server) + super().__init__(session, _id, name, nodedir, server) def alive(self) -> bool: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 9b46dfd50..b85c2eee1 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -264,7 +264,6 @@ def __init__( session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, policy: NetworkPolicy = None, ) -> None: @@ -274,12 +273,11 @@ def __init__( :param session: core session instance :param _id: object id :param name: object name - :param start: start flag :param server: remote server node will run on, default is None for localhost :param policy: network policy """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) if name is None: name = str(self.id) if policy is not None: @@ -288,9 +286,6 @@ def __init__( sessionid = self.session.short_session_id() self.brname: str = f"b.{self.id}.{sessionid}" self.has_ebtables_chain: bool = False - if start: - self.startup() - ebq.startupdateloop(self) def host_cmd( self, @@ -327,6 +322,7 @@ def startup(self) -> None: self.net_client.create_bridge(self.brname) self.has_ebtables_chain = False self.up = True + ebq.startupdateloop(self) def shutdown(self) -> None: """ @@ -610,7 +606,6 @@ def __init__( localip: str = None, ttl: int = 255, key: int = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -628,7 +623,7 @@ def __init__( :param server: remote server node will run on, default is None for localhost """ - CoreNetwork.__init__(self, session, _id, name, False, server, policy) + CoreNetwork.__init__(self, session, _id, name, server, policy) if key is None: key = self.session.id ^ self.id self.grekey: int = key @@ -647,8 +642,6 @@ def __init__( ttl=ttl, key=self.grekey, ) - if start: - self.startup() def startup(self) -> None: """ @@ -734,7 +727,6 @@ def __init__( _id: int = None, name: str = None, hostid: int = None, - start: bool = True, server: "DistributedServer" = None, assign_address: bool = True, updown_script: str = None, @@ -748,7 +740,6 @@ def __init__( :param name: node namee :param prefix: control network ipv4 prefix :param hostid: host id - :param start: start flag :param server: remote server node will run on, default is None for localhost :param assign_address: assigned address @@ -761,7 +752,7 @@ def __init__( self.assign_address: bool = assign_address self.updown_script: Optional[str] = updown_script self.serverintf: Optional[str] = serverintf - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) def add_addresses(self, index: int) -> None: """ @@ -1025,7 +1016,6 @@ def __init__( session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, policy: NetworkPolicy = None, ) -> None: @@ -1040,7 +1030,7 @@ def __init__( will run on, default is None for localhost :param policy: wlan policy """ - super().__init__(session, _id, name, start, server, policy) + super().__init__(session, _id, name, server, policy) # wireless and mobility models (BasicRangeModel, Ns2WaypointMobility) self.model: Optional[WirelessModel] = None self.mobility: Optional[WayPointMobility] = None diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index a72ff128f..a2e39d494 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -28,22 +28,19 @@ def __init__( _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: DistributedServer = None, ) -> None: - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) if not self.server: raise CoreError("physical nodes must be assigned to a remote server") self.nodedir: Optional[str] = nodedir - self.up: bool = start self.lock: threading.RLock = threading.RLock() self._mounts: List[Tuple[str, str]] = [] - if start: - self.startup() def startup(self) -> None: with self.lock: self.makenodedir() + self.up = True def shutdown(self) -> None: if not self.up: @@ -144,7 +141,7 @@ def linkconfig( """ Apply tc queing disciplines using linkconfig. """ - linux_bridge = CoreNetwork(session=self.session, start=False) + linux_bridge = CoreNetwork(self.session) linux_bridge.up = True linux_bridge.linkconfig(netif, options, netif2) del linux_bridge @@ -244,7 +241,6 @@ def __init__( _id: int = None, name: str = None, mtu: int = 1500, - start: bool = True, server: DistributedServer = None, ) -> None: """ @@ -254,19 +250,16 @@ def __init__( :param _id: node id :param name: node name :param mtu: rj45 mtu - :param start: start flag :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.interface = CoreInterface(session, self, name, name, mtu, server) self.interface.transport_type = TransportType.RAW self.lock: threading.RLock = threading.RLock() self.ifindex: Optional[int] = None self.old_up: bool = False self.old_addrs: List[Tuple[str, Optional[str]]] = [] - if start: - self.startup() def startup(self) -> None: """ From cf4194889410c1faab5d04ca307a85f4f9b4f656 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 12:36:07 -0700 Subject: [PATCH 022/210] daemon: fixed error with EmaneNet startup throwing an error, updated Rj45Node and PhysicalNode to implement all abstract methods --- daemon/core/emane/nodes.py | 3 +++ daemon/core/nodes/physical.py | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 68c1bc054..8383cdd17 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -76,6 +76,9 @@ def linkconfig( def config(self, conf: str) -> None: self.conf = conf + def startup(self) -> None: + pass + def shutdown(self) -> None: pass diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index a2e39d494..132140938 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -217,14 +217,17 @@ def opennodefile(self, filename: str, mode: str = "w") -> IO: return open(hostfilename, mode) def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - with self.opennodefile(filename, "w") as node_file: - node_file.write(contents) - os.chmod(node_file.name, mode) - logging.info("created nodefile: '%s'; mode: 0%o", node_file.name, mode) + with self.opennodefile(filename, "w") as f: + f.write(contents) + os.chmod(f.name, mode) + logging.info("created nodefile: '%s'; mode: 0%o", f.name, mode) def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: return self.host_cmd(args, wait=wait) + def addfile(self, srcname: str, filename: str) -> None: + raise NotImplementedError + class Rj45Node(CoreNodeBase): """ @@ -446,10 +449,13 @@ def setposition(self, x: float = None, y: float = None, z: float = None) -> None self.interface.setposition() def termcmdstring(self, sh: str) -> str: - """ - Create a terminal command string. + raise NotImplementedError - :param sh: shell to execute command in - :return: str - """ + def addfile(self, srcname: str, filename: str) -> None: + raise NotImplementedError + + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + raise NotImplementedError + + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: raise NotImplementedError From f5916fab5b041c3dcb20ba8589abd4f7e6e698df Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 12:44:51 -0700 Subject: [PATCH 023/210] daemon: added not implemented methods to CoreNodeBase --- daemon/core/nodes/base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 49fe76207..3e9dfe7af 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -41,7 +41,6 @@ class NodeBase: apitype: Optional[NodeTypes] = None - # TODO: appears start has no usage, verify and remove def __init__( self, session: "Session", @@ -268,6 +267,12 @@ def __init__( self.nodedir: Optional[str] = None self.tmpnodedir: bool = False + def startup(self) -> None: + raise NotImplementedError + + def shutdown(self) -> None: + raise NotImplementedError + def add_config_service(self, service_class: "ConfigServiceType") -> None: """ Adds a configuration service to the node. From 0462c1b0841bebdc0516d885344b3c52a3a03096 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 13:35:06 -0700 Subject: [PATCH 024/210] daemon: added usage of ABC to NodeBase, CoreNodeBase, and CoreNetworkBase to help enforce accounting for abstract functions --- daemon/core/emane/nodes.py | 3 + daemon/core/nodes/base.py | 155 ++++++++++++++++++---------------- daemon/core/nodes/physical.py | 10 +-- 3 files changed, 92 insertions(+), 76 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 8383cdd17..c4c3428ba 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -88,6 +88,9 @@ def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: pass + def linknet(self, net: "CoreNetworkBase") -> CoreInterface: + raise CoreError("emane networks cannot be linked to other networks") + def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: raise CoreError(f"no model set to update for node({self.name})") diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3e9dfe7af..6c7ebcf09 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1,7 +1,7 @@ """ Defines the base logic for nodes used within core. """ - +import abc import logging import os import shutil @@ -34,7 +34,7 @@ _DEFAULT_MTU = 1500 -class NodeBase: +class NodeBase(abc.ABC): """ Base class for CORE nodes (nodes and networks) """ @@ -78,6 +78,7 @@ def __init__( use_ovs = session.options.get_config("ovs") == "True" self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd) + @abc.abstractmethod def startup(self) -> None: """ Each object implements its own startup method. @@ -86,6 +87,7 @@ def startup(self) -> None: """ raise NotImplementedError + @abc.abstractmethod def shutdown(self) -> None: """ Each object implements its own shutdown method. @@ -267,12 +269,74 @@ def __init__( self.nodedir: Optional[str] = None self.tmpnodedir: bool = False + @abc.abstractmethod def startup(self) -> None: raise NotImplementedError + @abc.abstractmethod def shutdown(self) -> None: raise NotImplementedError + @abc.abstractmethod + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + """ + Create a node file with a given mode. + + :param filename: name of file to create + :param contents: contents of file + :param mode: mode for file + :return: nothing + """ + raise NotImplementedError + + @abc.abstractmethod + def addfile(self, srcname: str, filename: str) -> None: + """ + Add a file. + + :param srcname: source file name + :param filename: file name to add + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + raise NotImplementedError + + @abc.abstractmethod + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: + """ + Runs a command within a node container. + + :param args: command to run + :param wait: True to wait for status, False otherwise + :param shell: True to use shell, False otherwise + :return: combined stdout and stderr + :raises CoreCommandError: when a non-zero exit status occurs + """ + raise NotImplementedError + + @abc.abstractmethod + def termcmdstring(self, sh: str) -> str: + """ + Create a terminal command string. + + :param sh: shell to execute command in + :return: str + """ + raise NotImplementedError + + @abc.abstractmethod + def newnetif( + self, net: "CoreNetworkBase", interface_data: InterfaceData + ) -> CoreInterface: + """ + Create a new network interface. + + :param net: network to associate with + :param interface_data: interface data for new interface + :return: interface index + """ + raise NotImplementedError + def add_config_service(self, service_class: "ConfigServiceType") -> None: """ Adds a configuration service to the node. @@ -432,61 +496,6 @@ def commonnets( common.append((netif1.net, netif1, netif2)) return common - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - """ - Create a node file with a given mode. - - :param filename: name of file to create - :param contents: contents of file - :param mode: mode for file - :return: nothing - """ - raise NotImplementedError - - def addfile(self, srcname: str, filename: str) -> None: - """ - Add a file. - - :param srcname: source file name - :param filename: file name to add - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - raise NotImplementedError - - def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: - """ - Runs a command within a node container. - - :param args: command to run - :param wait: True to wait for status, False otherwise - :param shell: True to use shell, False otherwise - :return: combined stdout and stderr - :raises CoreCommandError: when a non-zero exit status occurs - """ - raise NotImplementedError - - def termcmdstring(self, sh: str) -> str: - """ - Create a terminal command string. - - :param sh: shell to execute command in - :return: str - """ - raise NotImplementedError - - def newnetif( - self, net: "CoreNetworkBase", interface_data: InterfaceData - ) -> CoreInterface: - """ - Create a new network interface. - - :param net: network to associate with - :param interface_data: interface data for new interface - :return: interface index - """ - raise NotImplementedError - class CoreNode(CoreNodeBase): """ @@ -1002,6 +1011,7 @@ def __init__( self._linked = {} self._linked_lock = threading.Lock() + @abc.abstractmethod def startup(self) -> None: """ Each object implements its own startup method. @@ -1010,6 +1020,7 @@ def startup(self) -> None: """ raise NotImplementedError + @abc.abstractmethod def shutdown(self) -> None: """ Each object implements its own shutdown method. @@ -1018,6 +1029,7 @@ def shutdown(self) -> None: """ raise NotImplementedError + @abc.abstractmethod def linknet(self, net: "CoreNetworkBase") -> CoreInterface: """ Link network to another. @@ -1025,7 +1037,21 @@ def linknet(self, net: "CoreNetworkBase") -> CoreInterface: :param net: network to link with :return: created interface """ - pass + raise NotImplementedError + + @abc.abstractmethod + def linkconfig( + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + ) -> None: + """ + Configure link parameters by applying tc queuing disciplines on the interface. + + :param netif: interface one + :param options: options for configuring link + :param netif2: interface two + :return: nothing + """ + raise NotImplementedError def getlinknetif(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: """ @@ -1156,19 +1182,6 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat return all_links - def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None - ) -> None: - """ - Configure link parameters by applying tc queuing disciplines on the interface. - - :param netif: interface one - :param options: options for configuring link - :param netif2: interface two - :return: nothing - """ - raise NotImplementedError - class Position: """ diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 132140938..741fe7d59 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -226,7 +226,7 @@ def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: return self.host_cmd(args, wait=wait) def addfile(self, srcname: str, filename: str) -> None: - raise NotImplementedError + raise CoreError("physical node does not support addfile") class Rj45Node(CoreNodeBase): @@ -449,13 +449,13 @@ def setposition(self, x: float = None, y: float = None, z: float = None) -> None self.interface.setposition() def termcmdstring(self, sh: str) -> str: - raise NotImplementedError + raise CoreError("rj45 does not support terminal commands") def addfile(self, srcname: str, filename: str) -> None: - raise NotImplementedError + raise CoreError("rj45 does not support addfile") def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - raise NotImplementedError + raise CoreError("rj45 does not support nodefile") def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: - raise NotImplementedError + raise CoreError("rj45 does not support cmds") From 0725199d6d2f9f5420628201861e0e78b64bd4b7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 09:30:16 -0700 Subject: [PATCH 025/210] initial sweeping changes to call all usages of various interface related variables and functions (netif, interface, if, ifc, etc) to use a consistent name iface --- daemon/core/api/grpc/client.py | 72 ++-- daemon/core/api/grpc/events.py | 2 +- daemon/core/api/grpc/grpcutils.py | 134 +++--- daemon/core/api/grpc/server.py | 117 +++--- daemon/core/api/tlv/coreapi.py | 26 +- daemon/core/api/tlv/corehandlers.py | 88 ++-- daemon/core/api/tlv/dataconversion.py | 2 +- daemon/core/api/tlv/enumerations.py | 26 +- .../configservices/frrservices/services.py | 88 ++-- .../frrservices/templates/frr.conf | 6 +- .../frrservices/templates/frrboot.sh | 6 +- .../configservices/nrlservices/services.py | 42 +- .../nrlservices/templates/nrlnhdp.sh | 4 +- .../nrlservices/templates/nrlolsrv2.sh | 4 +- .../nrlservices/templates/olsrd.sh | 4 +- .../nrlservices/templates/startsmf.sh | 4 +- .../configservices/quaggaservices/services.py | 98 ++--- .../quaggaservices/templates/Quagga.conf | 6 +- .../sercurityservices/services.py | 12 +- .../configservices/utilservices/services.py | 64 ++- .../utilservices/templates/index.html | 4 +- daemon/core/emane/commeffect.py | 18 +- daemon/core/emane/emanemanager.py | 84 ++-- daemon/core/emane/emanemodel.py | 28 +- daemon/core/emane/linkmonitor.py | 6 +- daemon/core/emane/nodes.py | 80 ++-- daemon/core/emulator/data.py | 30 +- daemon/core/emulator/distributed.py | 4 +- daemon/core/emulator/emudata.py | 10 +- daemon/core/emulator/session.py | 184 ++++----- daemon/core/gui/coreclient.py | 117 +++--- daemon/core/gui/dialogs/emaneconfig.py | 10 +- daemon/core/gui/dialogs/ipdialog.py | 2 +- daemon/core/gui/dialogs/linkconfig.py | 36 +- daemon/core/gui/dialogs/macdialog.py | 2 +- daemon/core/gui/dialogs/nodeconfig.py | 76 ++-- daemon/core/gui/graph/edges.py | 26 +- daemon/core/gui/graph/graph.py | 84 ++-- daemon/core/gui/graph/node.py | 14 +- daemon/core/gui/interface.py | 64 +-- daemon/core/gui/menubar.py | 2 +- daemon/core/location/mobility.py | 127 +++--- daemon/core/nodes/base.py | 388 ++++++++---------- daemon/core/nodes/docker.py | 2 +- daemon/core/nodes/interface.py | 13 +- daemon/core/nodes/lxd.py | 6 +- daemon/core/nodes/netclient.py | 34 +- daemon/core/nodes/network.py | 284 ++++++------- daemon/core/nodes/physical.py | 195 ++++----- daemon/core/services/bird.py | 34 +- daemon/core/services/emaneservices.py | 6 +- daemon/core/services/frr.py | 144 +++---- daemon/core/services/nrl.py | 58 ++- daemon/core/services/quagga.py | 138 +++---- daemon/core/services/sdn.py | 24 +- daemon/core/services/security.py | 18 +- daemon/core/services/utility.py | 62 ++- daemon/core/services/xorp.py | 100 ++--- daemon/core/xml/corexml.py | 120 +++--- daemon/core/xml/corexmldeployment.py | 36 +- daemon/core/xml/emanexml.py | 76 ++-- daemon/examples/configservices/testing.py | 8 +- daemon/examples/docker/docker2core.py | 4 +- daemon/examples/docker/docker2docker.py | 4 +- daemon/examples/docker/switch.py | 6 +- daemon/examples/grpc/distributed_switch.py | 4 +- daemon/examples/grpc/emane80211.py | 4 +- daemon/examples/grpc/switch.py | 4 +- daemon/examples/grpc/wlan.py | 4 +- daemon/examples/lxd/lxd2core.py | 4 +- daemon/examples/lxd/lxd2lxd.py | 4 +- daemon/examples/lxd/switch.py | 6 +- daemon/examples/myservices/sample.py | 4 +- daemon/examples/python/distributed_emane.py | 8 +- daemon/examples/python/distributed_lxd.py | 4 +- daemon/examples/python/distributed_ptp.py | 4 +- daemon/examples/python/distributed_switch.py | 8 +- daemon/examples/python/emane80211.py | 4 +- daemon/examples/python/switch.py | 4 +- daemon/examples/python/switch_inject.py | 4 +- daemon/examples/python/wlan.py | 4 +- daemon/proto/core/api/grpc/core.proto | 26 +- daemon/proto/core/api/grpc/emane.proto | 10 +- daemon/scripts/core-route-monitor | 4 +- daemon/tests/conftest.py | 2 +- daemon/tests/emane/test_emane.py | 8 +- daemon/tests/test_core.py | 44 +- daemon/tests/test_grpc.py | 60 +-- daemon/tests/test_gui.py | 80 ++-- daemon/tests/test_links.py | 174 ++++---- daemon/tests/test_nodes.py | 28 +- daemon/tests/test_xml.py | 30 +- docs/scripting.md | 4 +- 93 files changed, 1958 insertions(+), 2159 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 3a16d4fd0..47aaef63f 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -110,27 +110,27 @@ def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: """ self.prefixes: IpPrefixes = IpPrefixes(ip4_prefix, ip6_prefix) - def create_interface( - self, node_id: int, interface_id: int, name: str = None, mac: str = None + def create_iface( + self, node_id: int, iface_id: int, name: str = None, mac: str = None ) -> core_pb2.Interface: """ Create an interface protobuf object. :param node_id: node id to create interface for - :param interface_id: interface id + :param iface_id: interface id :param name: name of interface :param mac: mac address for interface :return: interface protobuf """ - interface_data = self.prefixes.gen_interface(node_id, name, mac) + iface_data = self.prefixes.gen_iface(node_id, name, mac) return core_pb2.Interface( - id=interface_id, - name=interface_data.name, - ip4=interface_data.ip4, - ip4mask=interface_data.ip4_mask, - ip6=interface_data.ip6, - ip6mask=interface_data.ip6_mask, - mac=interface_data.mac, + id=iface_id, + name=iface_data.name, + ip4=iface_data.ip4, + ip4mask=iface_data.ip4_mask, + ip6=iface_data.ip6, + ip6mask=iface_data.ip6_mask, + mac=iface_data.mac, ) @@ -611,8 +611,8 @@ def add_link( session_id: int, node1_id: int, node2_id: int, - interface1: core_pb2.Interface = None, - interface2: core_pb2.Interface = None, + iface1: core_pb2.Interface = None, + iface2: core_pb2.Interface = None, options: core_pb2.LinkOptions = None, ) -> core_pb2.AddLinkResponse: """ @@ -621,8 +621,8 @@ def add_link( :param session_id: session id :param node1_id: node one id :param node2_id: node two id - :param interface1: node one interface data - :param interface2: node two interface data + :param iface1: node one interface data + :param iface2: node two interface data :param options: options for link (jitter, bandwidth, etc) :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist @@ -631,8 +631,8 @@ def add_link( node1_id=node1_id, node2_id=node2_id, type=core_pb2.LinkType.WIRED, - interface1=interface1, - interface2=interface2, + iface1=iface1, + iface2=iface2, options=options, ) request = core_pb2.AddLinkRequest(session_id=session_id, link=link) @@ -644,8 +644,8 @@ def edit_link( node1_id: int, node2_id: int, options: core_pb2.LinkOptions, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, ) -> core_pb2.EditLinkResponse: """ Edit a link between nodes. @@ -654,8 +654,8 @@ def edit_link( :param node1_id: node one id :param node2_id: node two id :param options: options for link (jitter, bandwidth, etc) - :param interface1_id: node one interface id - :param interface2_id: node two interface id + :param iface1_id: node one interface id + :param iface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -664,8 +664,8 @@ def edit_link( node1_id=node1_id, node2_id=node2_id, options=options, - interface1_id=interface1_id, - interface2_id=interface2_id, + iface1_id=iface1_id, + iface2_id=iface2_id, ) return self.stub.EditLink(request) @@ -674,8 +674,8 @@ def delete_link( session_id: int, node1_id: int, node2_id: int, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, ) -> core_pb2.DeleteLinkResponse: """ Delete a link between nodes. @@ -683,8 +683,8 @@ def delete_link( :param session_id: session id :param node1_id: node one id :param node2_id: node two id - :param interface1_id: node one interface id - :param interface2_id: node two interface id + :param iface1_id: node one interface id + :param iface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ @@ -692,8 +692,8 @@ def delete_link( session_id=session_id, node1_id=node1_id, node2_id=node2_id, - interface1_id=interface1_id, - interface2_id=interface2_id, + iface1_id=iface1_id, + iface2_id=iface2_id, ) return self.stub.DeleteLink(request) @@ -1028,7 +1028,7 @@ def get_emane_models(self, session_id: int) -> GetEmaneModelsResponse: return self.stub.GetEmaneModels(request) def get_emane_model_config( - self, session_id: int, node_id: int, model: str, interface_id: int = -1 + self, session_id: int, node_id: int, model: str, iface_id: int = -1 ) -> GetEmaneModelConfigResponse: """ Get emane model configuration for a node or a node's interface. @@ -1036,12 +1036,12 @@ def get_emane_model_config( :param session_id: session id :param node_id: node id :param model: emane model name - :param interface_id: node interface id + :param iface_id: node interface id :return: response with a list of configuration groups :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneModelConfigRequest( - session_id=session_id, node_id=node_id, model=model, interface=interface_id + session_id=session_id, node_id=node_id, model=model, iface_id=iface_id ) return self.stub.GetEmaneModelConfig(request) @@ -1051,7 +1051,7 @@ def set_emane_model_config( node_id: int, model: str, config: Dict[str, str] = None, - interface_id: int = -1, + iface_id: int = -1, ) -> SetEmaneModelConfigResponse: """ Set emane model configuration for a node or a node's interface. @@ -1060,12 +1060,12 @@ def set_emane_model_config( :param node_id: node id :param model: emane model name :param config: emane model configuration - :param interface_id: node interface id + :param iface_id: node interface id :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ model_config = EmaneModelConfig( - node_id=node_id, model=model, config=config, interface_id=interface_id + node_id=node_id, model=model, config=config, iface_id=iface_id ) request = SetEmaneModelConfigRequest( session_id=session_id, emane_model_config=model_config @@ -1128,7 +1128,7 @@ def emane_link( ) return self.stub.EmaneLink(request) - def get_interfaces(self) -> core_pb2.GetInterfacesResponse: + def get_ifaces(self) -> core_pb2.GetInterfacesResponse: """ Retrieves a list of interfaces available on the host machine that are not a part of a CORE session. diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 82cf1eac0..ff65142d3 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -82,7 +82,7 @@ def handle_config_event(event: ConfigData) -> core_pb2.ConfigEvent: data_values=event.data_values, possible_values=event.possible_values, groups=event.groups, - interface=event.interface_number, + iface_id=event.iface_id, network_id=event.network_id, opaque=event.opaque, data_types=event.data_types, diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index c9b76b736..f2f857985 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -52,29 +52,29 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption return _type, _id, options -def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: +def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: """ Create interface data from interface proto. - :param interface_proto: interface proto + :param iface_proto: interface proto :return: interface data """ - interface_data = None - if interface_proto: - name = interface_proto.name if interface_proto.name else None - mac = interface_proto.mac if interface_proto.mac else None - ip4 = interface_proto.ip4 if interface_proto.ip4 else None - ip6 = interface_proto.ip6 if interface_proto.ip6 else None - interface_data = InterfaceData( - id=interface_proto.id, + iface_data = None + if iface_proto: + name = iface_proto.name if iface_proto.name else None + mac = iface_proto.mac if iface_proto.mac else None + ip4 = iface_proto.ip4 if iface_proto.ip4 else None + ip6 = iface_proto.ip6 if iface_proto.ip6 else None + iface_data = InterfaceData( + id=iface_proto.id, name=name, mac=mac, ip4=ip4, - ip4_mask=interface_proto.ip4mask, + ip4_mask=iface_proto.ip4mask, ip6=ip6, - ip6_mask=interface_proto.ip6mask, + ip6_mask=iface_proto.ip6mask, ) - return interface_data + return iface_data def add_link_data( @@ -86,8 +86,8 @@ def add_link_data( :param link_proto: link proto :return: link interfaces and options """ - interface1_data = link_interface(link_proto.interface1) - interface2_data = link_interface(link_proto.interface2) + iface1_data = link_iface(link_proto.iface1) + iface2_data = link_iface(link_proto.iface2) link_type = LinkTypes(link_proto.type) options = LinkOptions(type=link_type) options_data = link_proto.options @@ -103,7 +103,7 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return interface1_data, interface2_data, options + return iface1_data, iface2_data, options def create_nodes( @@ -143,8 +143,8 @@ def create_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - interface1, interface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, interface1, interface2, options) + iface1, iface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, iface1, iface2, options) funcs.append((session.add_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -167,8 +167,8 @@ def edit_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - interface1, interface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, interface1.id, interface2.id, options) + iface1, iface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, iface1.id, iface2.id, options) funcs.append((session.update_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -279,16 +279,16 @@ def get_links(node: NodeBase): return links -def get_emane_model_id(node_id: int, interface_id: int) -> int: +def get_emane_model_id(node_id: int, iface_id: int) -> int: """ Get EMANE model id :param node_id: node id - :param interface_id: interface id + :param iface_id: interface id :return: EMANE model id """ - if interface_id >= 0: - return node_id * 1000 + interface_id + if iface_id >= 0: + return node_id * 1000 + iface_id else: return node_id @@ -300,12 +300,12 @@ def parse_emane_model_id(_id: int) -> Tuple[int, int]: :param _id: id to parse :return: node id and interface id """ - interface = -1 + iface_id = -1 node_id = _id if _id >= 1000: - interface = _id % 1000 + iface_id = _id % 1000 node_id = int(_id / 1000) - return node_id, interface + return node_id, iface_id def convert_link(link_data: LinkData) -> core_pb2.Link: @@ -315,27 +315,27 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: :param link_data: link to convert :return: core protobuf Link """ - interface1 = None - if link_data.interface1_id is not None: - interface1 = core_pb2.Interface( - id=link_data.interface1_id, - name=link_data.interface1_name, - mac=convert_value(link_data.interface1_mac), - ip4=convert_value(link_data.interface1_ip4), - ip4mask=link_data.interface1_ip4_mask, - ip6=convert_value(link_data.interface1_ip6), - ip6mask=link_data.interface1_ip6_mask, + iface1 = None + if link_data.iface1_id is not None: + iface1 = core_pb2.Interface( + id=link_data.iface1_id, + name=link_data.iface1_name, + mac=convert_value(link_data.iface1_mac), + ip4=convert_value(link_data.iface1_ip4), + ip4mask=link_data.iface1_ip4_mask, + ip6=convert_value(link_data.iface1_ip6), + ip6mask=link_data.iface1_ip6_mask, ) - interface2 = None - if link_data.interface2_id is not None: - interface2 = core_pb2.Interface( - id=link_data.interface2_id, - name=link_data.interface2_name, - mac=convert_value(link_data.interface2_mac), - ip4=convert_value(link_data.interface2_ip4), - ip4mask=link_data.interface2_ip4_mask, - ip6=convert_value(link_data.interface2_ip6), - ip6mask=link_data.interface2_ip6_mask, + iface2 = None + if link_data.iface2_id is not None: + iface2 = core_pb2.Interface( + id=link_data.iface2_id, + name=link_data.iface2_name, + mac=convert_value(link_data.iface2_mac), + ip4=convert_value(link_data.iface2_ip4), + ip4mask=link_data.iface2_ip4_mask, + ip6=convert_value(link_data.iface2_ip6), + ip6mask=link_data.iface2_ip6_mask, ) options = core_pb2.LinkOptions( opaque=link_data.opaque, @@ -354,8 +354,8 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: type=link_data.link_type.value, node1_id=link_data.node1_id, node2_id=link_data.node2_id, - interface1=interface1, - interface2=interface2, + iface1=iface1, + iface2=iface2, options=options, network_id=link_data.network_id, label=link_data.label, @@ -440,20 +440,20 @@ def get_service_configuration(service: CoreService) -> NodeServiceData: ) -def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: +def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: """ Convenience for converting a core interface to the protobuf representation. - :param interface: interface to convert + :param iface: interface to convert :return: interface proto """ net_id = None - if interface.net: - net_id = interface.net.id + if iface.net: + net_id = iface.net.id ip4 = None ip4mask = None ip6 = None ip6mask = None - for addr in interface.addrlist: + for addr in iface.addrlist: network = netaddr.IPNetwork(addr) mask = network.prefixlen ip = str(network.ip) @@ -464,12 +464,12 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: ip6 = ip ip6mask = mask return core_pb2.Interface( - id=interface.netindex, + id=iface.node_id, netid=net_id, - name=interface.name, - mac=str(interface.hwaddr), - mtu=interface.mtu, - flowid=interface.flow_id, + name=iface.name, + mac=str(iface.hwaddr), + mtu=iface.mtu, + flowid=iface.flow_id, ip4=ip4, ip4mask=ip4mask, ip6=ip6, @@ -477,21 +477,21 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: ) -def get_nem_id(node: CoreNode, netif_id: int, context: ServicerContext) -> int: +def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: """ Get nem id for a given node and interface id. :param node: node to get nem id for - :param netif_id: id of interface on node to get nem id for + :param iface_id: id of interface on node to get nem id for :param context: request context :return: nem id """ - netif = node.netif(netif_id) - if not netif: - message = f"{node.name} missing interface {netif_id}" + iface = node.ifaces.get(iface_id) + if not iface: + message = f"{node.name} missing interface {iface_id}" context.abort(grpc.StatusCode.NOT_FOUND, message) - net = netif.net + net = iface.net if not isinstance(net, EmaneNet): - message = f"{node.name} interface {netif_id} is not an EMANE network" + message = f"{node.name} interface {iface_id} is not an EMANE network" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) - return net.getnemid(netif) + return net.getnemid(iface) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8b349b67c..87b69a774 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -246,7 +246,7 @@ def StartSession( config = session.emane.get_configs() config.update(request.emane_config) for config in request.emane_model_configs: - _id = get_emane_model_id(config.node_id, config.interface_id) + _id = get_emane_model_id(config.node_id, config.iface_id) session.emane.set_model_config(_id, config.model, config.config) # wlan configs @@ -625,16 +625,14 @@ def Throughputs( key = key.split(".") node_id = _INTERFACE_REGEX.search(key[0]).group("node") node_id = int(node_id, base=16) - interface_id = int(key[1], base=16) + iface_id = int(key[1], base=16) session_id = int(key[2], base=16) if session.id != session_id: continue - interface_throughput = ( - throughputs_event.interface_throughputs.add() - ) - interface_throughput.node_id = node_id - interface_throughput.interface_id = interface_id - interface_throughput.throughput = throughput + iface_throughput = throughputs_event.iface_throughputs.add() + iface_throughput.node_id = node_id + iface_throughput.iface_id = iface_id + iface_throughput.throughput = throughput elif key.startswith("b."): try: key = key.split(".") @@ -686,13 +684,13 @@ def GetNode( logging.debug("get node: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, NodeBase) - interfaces = [] - for interface_id in node._netif: - interface = node._netif[interface_id] - interface_proto = grpcutils.interface_to_proto(interface) - interfaces.append(interface_proto) + ifaces = [] + for iface_id in node.ifaces: + iface = node.ifaces[iface_id] + iface_proto = grpcutils.iface_to_proto(iface) + ifaces.append(iface_proto) node_proto = grpcutils.get_node_proto(session, node) - return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces) + return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces) def MoveNodes( self, @@ -850,18 +848,18 @@ def AddLink( node2_id = request.link.node2_id self.get_node(session, node1_id, context, NodeBase) self.get_node(session, node2_id, context, NodeBase) - interface1, interface2, options = grpcutils.add_link_data(request.link) - node1_interface, node2_interface = session.add_link( - node1_id, node2_id, interface1, interface2, options=options + iface1_data, iface2_data, options = grpcutils.add_link_data(request.link) + node1_iface, node2_iface = session.add_link( + node1_id, node2_id, iface1_data, iface2_data, options=options ) - interface1_proto = None - interface2_proto = None - if node1_interface: - interface1_proto = grpcutils.interface_to_proto(node1_interface) - if node2_interface: - interface2_proto = grpcutils.interface_to_proto(node2_interface) + iface1_proto = None + iface2_proto = None + if node1_iface: + iface1_proto = grpcutils.iface_to_proto(node1_iface) + if node2_iface: + iface2_proto = grpcutils.iface_to_proto(node2_iface) return core_pb2.AddLinkResponse( - result=True, interface1=interface1_proto, interface2=interface2_proto + result=True, iface1=iface1_proto, iface2=iface2_proto ) def EditLink( @@ -878,8 +876,8 @@ def EditLink( session = self.get_session(request.session_id, context) node1_id = request.node1_id node2_id = request.node2_id - interface1_id = request.interface1_id - interface2_id = request.interface2_id + iface1_id = request.iface1_id + iface2_id = request.iface2_id options_data = request.options options = LinkOptions( delay=options_data.delay, @@ -894,7 +892,7 @@ def EditLink( key=options_data.key, opaque=options_data.opaque, ) - session.update_link(node1_id, node2_id, interface1_id, interface2_id, options) + session.update_link(node1_id, node2_id, iface1_id, iface2_id, options) return core_pb2.EditLinkResponse(result=True) def DeleteLink( @@ -911,9 +909,9 @@ def DeleteLink( session = self.get_session(request.session_id, context) node1_id = request.node1_id node2_id = request.node2_id - interface1_id = request.interface1_id - interface2_id = request.interface2_id - session.delete_link(node1_id, node2_id, interface1_id, interface2_id) + iface1_id = request.iface1_id + iface2_id = request.iface2_id + session.delete_link(node1_id, node2_id, iface1_id, iface2_id) return core_pb2.DeleteLinkResponse(result=True) def GetHooks( @@ -1371,7 +1369,7 @@ def GetEmaneModelConfig( logging.debug("get emane model config: %s", request) session = self.get_session(request.session_id, context) model = session.emane.models[request.model] - _id = get_emane_model_id(request.node_id, request.interface) + _id = get_emane_model_id(request.node_id, request.iface_id) current_config = session.emane.get_model_config(_id, request.model) config = get_config_options(current_config, model) return GetEmaneModelConfigResponse(config=config) @@ -1390,7 +1388,7 @@ def SetEmaneModelConfig( logging.debug("set emane model config: %s", request) session = self.get_session(request.session_id, context) model_config = request.emane_model_config - _id = get_emane_model_id(model_config.node_id, model_config.interface_id) + _id = get_emane_model_id(model_config.node_id, model_config.iface_id) session.emane.set_model_config(_id, model_config.model, model_config.config) return SetEmaneModelConfigResponse(result=True) @@ -1419,12 +1417,9 @@ def GetEmaneModelConfigs( model = session.emane.models[model_name] current_config = session.emane.get_model_config(_id, model_name) config = get_config_options(current_config, model) - node_id, interface = grpcutils.parse_emane_model_id(_id) + node_id, iface_id = grpcutils.parse_emane_model_id(_id) model_config = GetEmaneModelConfigsResponse.ModelConfig( - node_id=node_id, - model=model_name, - interface=interface, - config=config, + node_id=node_id, model=model_name, iface_id=iface_id, config=config ) configs.append(model_config) return GetEmaneModelConfigsResponse(configs=configs) @@ -1489,16 +1484,12 @@ def GetInterfaces( :param context: context object :return: get-interfaces response that has all the system's interfaces """ - interfaces = [] - for interface in os.listdir("/sys/class/net"): - if ( - interface.startswith("b.") - or interface.startswith("veth") - or interface == "lo" - ): + ifaces = [] + for iface in os.listdir("/sys/class/net"): + if iface.startswith("b.") or iface.startswith("veth") or iface == "lo": continue - interfaces.append(interface) - return core_pb2.GetInterfacesResponse(interfaces=interfaces) + ifaces.append(iface) + return core_pb2.GetInterfacesResponse(ifaces=ifaces) def EmaneLink( self, request: EmaneLinkRequest, context: ServicerContext @@ -1513,16 +1504,16 @@ def EmaneLink( logging.debug("emane link: %s", request) session = self.get_session(request.session_id, context) nem1 = request.nem1 - emane1, netif = session.emane.nemlookup(nem1) - if not emane1 or not netif: + emane1, iface = session.emane.nemlookup(nem1) + if not emane1 or not iface: context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found") - node1 = netif.node + node1 = iface.node nem2 = request.nem2 - emane2, netif = session.emane.nemlookup(nem2) - if not emane2 or not netif: + emane2, iface = session.emane.nemlookup(nem2) + if not emane2 or not iface: context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found") - node2 = netif.node + node2 = iface.node if emane1.id == emane2.id: if request.linked: @@ -1734,21 +1725,19 @@ def WlanLink( ) node1 = self.get_node(session, request.node1_id, context, CoreNode) node2 = self.get_node(session, request.node2_id, context, CoreNode) - node1_interface, node2_interface = None, None - for net, interface1, interface2 in node1.commonnets(node2): + node1_iface, node2_iface = None, None + for net, iface1, iface2 in node1.commonnets(node2): if net == wlan: - node1_interface = interface1 - node2_interface = interface2 + node1_iface = iface1 + node2_iface = iface2 break result = False - if node1_interface and node2_interface: + if node1_iface and node2_iface: if request.linked: - wlan.link(node1_interface, node2_interface) + wlan.link(node1_iface, node2_iface) else: - wlan.unlink(node1_interface, node2_interface) - wlan.model.sendlinkmsg( - node1_interface, node2_interface, unlink=not request.linked - ) + wlan.unlink(node1_iface, node2_iface) + wlan.model.sendlinkmsg(node1_iface, node2_iface, unlink=not request.linked) result = True return WlanLinkResponse(result=result) @@ -1760,8 +1749,8 @@ def EmanePathlosses( for request in request_iterator: session = self.get_session(request.session_id, context) node1 = self.get_node(session, request.node1_id, context, CoreNode) - nem1 = grpcutils.get_nem_id(node1, request.interface1_id, context) + nem1 = grpcutils.get_nem_id(node1, request.iface1_id, context) node2 = self.get_node(session, request.node2_id, context, CoreNode) - nem2 = grpcutils.get_nem_id(node2, request.interface2_id, context) + nem2 = grpcutils.get_nem_id(node2, request.iface2_id, context) session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2) return EmanePathlossesResponse() diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 088a7631f..5d0b08e78 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -508,18 +508,18 @@ class CoreLinkTlv(CoreTlv): LinkTlvs.EMULATION_ID.value: CoreTlvDataUint32, LinkTlvs.NETWORK_ID.value: CoreTlvDataUint32, LinkTlvs.KEY.value: CoreTlvDataUint32, - LinkTlvs.INTERFACE1_NUMBER.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE1_IP4.value: CoreTlvDataIpv4Addr, - LinkTlvs.INTERFACE1_IP4_MASK.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE1_MAC.value: CoreTlvDataMacAddr, - LinkTlvs.INTERFACE1_IP6.value: CoreTlvDataIPv6Addr, - LinkTlvs.INTERFACE1_IP6_MASK.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE2_NUMBER.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE2_IP4.value: CoreTlvDataIpv4Addr, - LinkTlvs.INTERFACE2_IP4_MASK.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE2_MAC.value: CoreTlvDataMacAddr, - LinkTlvs.INTERFACE2_IP6.value: CoreTlvDataIPv6Addr, - LinkTlvs.INTERFACE2_IP6_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE1_NUMBER.value: CoreTlvDataUint16, + LinkTlvs.IFACE1_IP4.value: CoreTlvDataIpv4Addr, + LinkTlvs.IFACE1_IP4_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE1_MAC.value: CoreTlvDataMacAddr, + LinkTlvs.IFACE1_IP6.value: CoreTlvDataIPv6Addr, + LinkTlvs.IFACE1_IP6_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE2_NUMBER.value: CoreTlvDataUint16, + LinkTlvs.IFACE2_IP4.value: CoreTlvDataIpv4Addr, + LinkTlvs.IFACE2_IP4_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE2_MAC.value: CoreTlvDataMacAddr, + LinkTlvs.IFACE2_IP6.value: CoreTlvDataIPv6Addr, + LinkTlvs.IFACE2_IP6_MASK.value: CoreTlvDataUint16, LinkTlvs.INTERFACE1_NAME.value: CoreTlvDataString, LinkTlvs.INTERFACE2_NAME.value: CoreTlvDataString, LinkTlvs.OPAQUE.value: CoreTlvDataString, @@ -577,7 +577,7 @@ class CoreConfigTlv(CoreTlv): ConfigTlvs.POSSIBLE_VALUES.value: CoreTlvDataString, ConfigTlvs.GROUPS.value: CoreTlvDataString, ConfigTlvs.SESSION.value: CoreTlvDataString, - ConfigTlvs.INTERFACE_NUMBER.value: CoreTlvDataUint16, + ConfigTlvs.IFACE_ID.value: CoreTlvDataUint16, ConfigTlvs.NETWORK_ID.value: CoreTlvDataUint32, ConfigTlvs.OPAQUE.value: CoreTlvDataString, } diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 2cd7bface..b09a37fef 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -71,7 +71,7 @@ def __init__(self, request, client_address, server): MessageTypes.REGISTER.value: self.handle_register_message, MessageTypes.CONFIG.value: self.handle_config_message, MessageTypes.FILE.value: self.handle_file_message, - MessageTypes.INTERFACE.value: self.handle_interface_message, + MessageTypes.INTERFACE.value: self.handle_iface_message, MessageTypes.EVENT.value: self.handle_event_message, MessageTypes.SESSION.value: self.handle_session_message, } @@ -363,18 +363,18 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.EMULATION_ID, link_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), (LinkTlvs.KEY, link_data.key), - (LinkTlvs.INTERFACE1_NUMBER, link_data.interface1_id), - (LinkTlvs.INTERFACE1_IP4, link_data.interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, link_data.interface1_ip4_mask), - (LinkTlvs.INTERFACE1_MAC, link_data.interface1_mac), - (LinkTlvs.INTERFACE1_IP6, link_data.interface1_ip6), - (LinkTlvs.INTERFACE1_IP6_MASK, link_data.interface1_ip6_mask), - (LinkTlvs.INTERFACE2_NUMBER, link_data.interface2_id), - (LinkTlvs.INTERFACE2_IP4, link_data.interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, link_data.interface2_ip4_mask), - (LinkTlvs.INTERFACE2_MAC, link_data.interface2_mac), - (LinkTlvs.INTERFACE2_IP6, link_data.interface2_ip6), - (LinkTlvs.INTERFACE2_IP6_MASK, link_data.interface2_ip6_mask), + (LinkTlvs.IFACE1_NUMBER, link_data.iface1_id), + (LinkTlvs.IFACE1_IP4, link_data.iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, link_data.iface1_ip4_mask), + (LinkTlvs.IFACE1_MAC, link_data.iface1_mac), + (LinkTlvs.IFACE1_IP6, link_data.iface1_ip6), + (LinkTlvs.IFACE1_IP6_MASK, link_data.iface1_ip6_mask), + (LinkTlvs.IFACE2_NUMBER, link_data.iface2_id), + (LinkTlvs.IFACE2_IP4, link_data.iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, link_data.iface2_ip4_mask), + (LinkTlvs.IFACE2_MAC, link_data.iface2_mac), + (LinkTlvs.IFACE2_IP6, link_data.iface2_ip6), + (LinkTlvs.IFACE2_IP6_MASK, link_data.iface2_ip6_mask), (LinkTlvs.OPAQUE, link_data.opaque), ], ) @@ -749,23 +749,23 @@ def handle_link_message(self, message): """ node1_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) node2_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) - interface1_data = InterfaceData( - id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value), + iface1_data = InterfaceData( + id=message.get_tlv(LinkTlvs.IFACE1_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value), - mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value), - ip4=message.get_tlv(LinkTlvs.INTERFACE1_IP4.value), - ip4_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP4_MASK.value), - ip6=message.get_tlv(LinkTlvs.INTERFACE1_IP6.value), - ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value), + mac=message.get_tlv(LinkTlvs.IFACE1_MAC.value), + ip4=message.get_tlv(LinkTlvs.IFACE1_IP4.value), + ip4_mask=message.get_tlv(LinkTlvs.IFACE1_IP4_MASK.value), + ip6=message.get_tlv(LinkTlvs.IFACE1_IP6.value), + ip6_mask=message.get_tlv(LinkTlvs.IFACE1_IP6_MASK.value), ) - interface2_data = InterfaceData( - id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value), + iface2_data = InterfaceData( + id=message.get_tlv(LinkTlvs.IFACE2_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value), - mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value), - ip4=message.get_tlv(LinkTlvs.INTERFACE2_IP4.value), - ip4_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP4_MASK.value), - ip6=message.get_tlv(LinkTlvs.INTERFACE2_IP6.value), - ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value), + mac=message.get_tlv(LinkTlvs.IFACE2_MAC.value), + ip4=message.get_tlv(LinkTlvs.IFACE2_IP4.value), + ip4_mask=message.get_tlv(LinkTlvs.IFACE2_IP4_MASK.value), + ip6=message.get_tlv(LinkTlvs.IFACE2_IP6.value), + ip6_mask=message.get_tlv(LinkTlvs.IFACE2_IP6_MASK.value), ) link_type = LinkTypes.WIRED link_type_value = message.get_tlv(LinkTlvs.TYPE.value) @@ -789,16 +789,12 @@ def handle_link_message(self, message): options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: - self.session.add_link( - node1_id, node2_id, interface1_data, interface2_data, options - ) + self.session.add_link(node1_id, node2_id, iface1_data, iface2_data, options) elif message.flags & MessageFlags.DELETE.value: - self.session.delete_link( - node1_id, node2_id, interface1_data.id, interface2_data.id - ) + self.session.delete_link(node1_id, node2_id, iface1_data.id, iface2_data.id) else: self.session.update_link( - node1_id, node2_id, interface1_data.id, interface2_data.id, options + node1_id, node2_id, iface1_data.id, iface2_data.id, options ) return () @@ -1008,7 +1004,7 @@ def handle_config_message(self, message): possible_values=message.get_tlv(ConfigTlvs.POSSIBLE_VALUES.value), groups=message.get_tlv(ConfigTlvs.GROUPS.value), session=message.get_tlv(ConfigTlvs.SESSION.value), - interface_number=message.get_tlv(ConfigTlvs.INTERFACE_NUMBER.value), + iface_id=message.get_tlv(ConfigTlvs.IFACE_ID.value), network_id=message.get_tlv(ConfigTlvs.NETWORK_ID.value), opaque=message.get_tlv(ConfigTlvs.OPAQUE.value), ) @@ -1325,11 +1321,11 @@ def handle_config_mobility_models(self, message_type, config_data): replies = [] node_id = config_data.node object_name = config_data.object - interface_id = config_data.interface_number + iface_id = config_data.iface_id values_str = config_data.data_values - if interface_id is not None: - node_id = node_id * 1000 + interface_id + if iface_id is not None: + node_id = node_id * 1000 + iface_id logging.debug( "received configure message for %s nodenum: %s", object_name, node_id @@ -1375,11 +1371,11 @@ def handle_config_emane(self, message_type, config_data): replies = [] node_id = config_data.node object_name = config_data.object - interface_id = config_data.interface_number + iface_id = config_data.iface_id values_str = config_data.data_values - if interface_id is not None: - node_id = node_id * 1000 + interface_id + if iface_id is not None: + node_id = node_id * 1000 + iface_id logging.debug( "received configure message for %s nodenum: %s", object_name, node_id @@ -1407,11 +1403,11 @@ def handle_config_emane_models(self, message_type, config_data): replies = [] node_id = config_data.node object_name = config_data.object - interface_id = config_data.interface_number + iface_id = config_data.iface_id values_str = config_data.data_values - if interface_id is not None: - node_id = node_id * 1000 + interface_id + if iface_id is not None: + node_id = node_id * 1000 + iface_id logging.debug( "received configure message for %s nodenum: %s", object_name, node_id @@ -1505,7 +1501,7 @@ def handle_file_message(self, message): return () - def handle_interface_message(self, message): + def handle_iface_message(self, message): """ Interface Message handler. @@ -1950,7 +1946,7 @@ def __init__(self, request, client_address, server): MessageTypes.REGISTER.value: self.handle_register_message, MessageTypes.CONFIG.value: self.handle_config_message, MessageTypes.FILE.value: self.handle_file_message, - MessageTypes.INTERFACE.value: self.handle_interface_message, + MessageTypes.INTERFACE.value: self.handle_iface_message, MessageTypes.EVENT.value: self.handle_event_message, MessageTypes.SESSION.value: self.handle_session_message, } diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index 876e72a52..cd10ef046 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -75,7 +75,7 @@ def convert_config(config_data): (ConfigTlvs.POSSIBLE_VALUES, config_data.possible_values), (ConfigTlvs.GROUPS, config_data.groups), (ConfigTlvs.SESSION, session), - (ConfigTlvs.INTERFACE_NUMBER, config_data.interface_number), + (ConfigTlvs.IFACE_ID, config_data.iface_id), (ConfigTlvs.NETWORK_ID, config_data.network_id), (ConfigTlvs.OPAQUE, config_data.opaque), ], diff --git a/daemon/core/api/tlv/enumerations.py b/daemon/core/api/tlv/enumerations.py index 0efb7c997..b4ec254a0 100644 --- a/daemon/core/api/tlv/enumerations.py +++ b/daemon/core/api/tlv/enumerations.py @@ -72,18 +72,18 @@ class LinkTlvs(Enum): EMULATION_ID = 0x23 NETWORK_ID = 0x24 KEY = 0x25 - INTERFACE1_NUMBER = 0x30 - INTERFACE1_IP4 = 0x31 - INTERFACE1_IP4_MASK = 0x32 - INTERFACE1_MAC = 0x33 - INTERFACE1_IP6 = 0x34 - INTERFACE1_IP6_MASK = 0x35 - INTERFACE2_NUMBER = 0x36 - INTERFACE2_IP4 = 0x37 - INTERFACE2_IP4_MASK = 0x38 - INTERFACE2_MAC = 0x39 - INTERFACE2_IP6 = 0x40 - INTERFACE2_IP6_MASK = 0x41 + 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 INTERFACE1_NAME = 0x42 INTERFACE2_NAME = 0x43 OPAQUE = 0x50 @@ -118,7 +118,7 @@ class ConfigTlvs(Enum): POSSIBLE_VALUES = 0x08 GROUPS = 0x09 SESSION = 0x0A - INTERFACE_NUMBER = 0x0B + IFACE_ID = 0x0B NETWORK_ID = 0x24 OPAQUE = 0x50 diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index c4502f86d..8764e32ce 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -13,33 +13,33 @@ GROUP = "FRR" -def has_mtu_mismatch(ifc: CoreInterface) -> bool: +def has_mtu_mismatch(iface: CoreInterface) -> bool: """ Helper to detect MTU mismatch and add the appropriate FRR mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: return True - if not ifc.net: + if not iface.net: return False - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return True return False -def get_min_mtu(ifc): +def get_min_mtu(iface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @@ -47,10 +47,8 @@ def get_router_id(node: CoreNodeBase) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -97,25 +95,25 @@ def data(self) -> Dict[str, Any]: want_ip6 = True services.append(service) - interfaces = [] - for ifc in self.node.netifs(): + ifaces = [] + for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in ifc.addrlist: + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): ip4s.append(x) else: ip6s.append(x) - is_control = getattr(ifc, "control", False) - interfaces.append((ifc, ip4s, ip6s, is_control)) + is_control = getattr(iface, "control", False) + ifaces.append((iface, ip4s, ip6s, is_control)) return dict( frr_conf=frr_conf, frr_sbin_search=frr_sbin_search, frr_bin_search=frr_bin_search, frr_state_dir=constants.FRR_STATE_DIR, - interfaces=interfaces, + ifaces=ifaces, want_ip4=want_ip4, want_ip6=want_ip6, services=services, @@ -138,7 +136,7 @@ class FrrService(abc.ABC): ipv6_routing = False @abc.abstractmethod - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: raise NotImplementedError @abc.abstractmethod @@ -162,10 +160,8 @@ class FRROspfv2(FrrService, ConfigService): def frr_config(self) -> str: router_id = get_router_id(self.node) addresses = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if netaddr.valid_ipv4(addr): addresses.append(a) @@ -180,8 +176,8 @@ def frr_config(self) -> str: """ return self.render_text(text, data) - def frr_interface_config(self, ifc: CoreInterface) -> str: - if has_mtu_mismatch(ifc): + def frr_iface_config(self, iface: CoreInterface) -> str: + if has_mtu_mismatch(iface): return "ip ospf mtu-ignore" else: return "" @@ -203,10 +199,8 @@ class FRROspfv3(FrrService, ConfigService): def frr_config(self) -> str: router_id = get_router_id(self.node) ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) data = dict(router_id=router_id, ifnames=ifnames) text = """ router ospf6 @@ -218,9 +212,9 @@ def frr_config(self) -> str: """ return self.render_text(text, data) - def frr_interface_config(self, ifc: CoreInterface) -> str: - mtu = get_min_mtu(ifc) - if mtu < ifc.mtu: + def frr_iface_config(self, iface: CoreInterface) -> str: + mtu = get_min_mtu(iface) + if mtu < iface.mtu: return f"ipv6 ospf6 ifmtu {mtu}" else: return "" @@ -254,7 +248,7 @@ def frr_config(self) -> str: """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: return "" @@ -279,7 +273,7 @@ def frr_config(self) -> str: """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: return "" @@ -304,7 +298,7 @@ def frr_config(self) -> str: """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: return "" @@ -321,10 +315,8 @@ class FRRBabel(FrrService, ConfigService): def frr_config(self) -> str: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) text = """ router babel % for ifname in ifnames: @@ -337,8 +329,8 @@ def frr_config(self) -> str: data = dict(ifnames=ifnames) return self.render_text(text, data) - def frr_interface_config(self, ifc: CoreInterface) -> str: - if isinstance(ifc.net, (WlanNode, EmaneNet)): + def frr_iface_config(self, iface: CoreInterface) -> str: + if isinstance(iface.net, (WlanNode, EmaneNet)): text = """ babel wireless no babel split-horizon @@ -363,9 +355,9 @@ class FRRpimd(FrrService, ConfigService): def frr_config(self) -> str: ifname = "eth0" - for ifc in self.node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in self.node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break text = f""" @@ -382,7 +374,7 @@ def frr_config(self) -> str: """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: text = """ ip mfea ip igmp diff --git a/daemon/core/configservices/frrservices/templates/frr.conf b/daemon/core/configservices/frrservices/templates/frr.conf index 748c86923..8e0361368 100644 --- a/daemon/core/configservices/frrservices/templates/frr.conf +++ b/daemon/core/configservices/frrservices/templates/frr.conf @@ -1,5 +1,5 @@ -% for ifc, ip4s, ip6s, is_control in interfaces: -interface ${ifc.name} +% for iface, ip4s, ip6s, is_control in ifaces: +interface ${iface.name} % if want_ip4: % for addr in ip4s: ip address ${addr} @@ -12,7 +12,7 @@ interface ${ifc.name} % endif % if not is_control: % for service in services: - % for line in service.frr_interface_config(ifc).split("\n"): + % for line in service.frr_iface_config(iface).split("\n"): ${line} % endfor % endfor diff --git a/daemon/core/configservices/frrservices/templates/frrboot.sh b/daemon/core/configservices/frrservices/templates/frrboot.sh index 3c14cd1a5..db47b6d1a 100644 --- a/daemon/core/configservices/frrservices/templates/frrboot.sh +++ b/daemon/core/configservices/frrservices/templates/frrboot.sh @@ -98,8 +98,8 @@ confcheck bootfrr # reset interfaces -% for ifc, _, _ , _ in interfaces: -ip link set dev ${ifc.name} down +% for iface, _, _ , _ in ifaces: +ip link set dev ${iface.name} down sleep 1 -ip link set dev ${ifc.name} up +ip link set dev ${iface.name} up % endfor diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index 3dddf1ba4..ca95b8f63 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -24,8 +24,8 @@ class MgenSinkService(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - name = utils.sysctl_devname(ifc.name) + for iface in self.node.get_ifaces(): + name = utils.sysctl_devname(iface.name) ifnames.append(name) return dict(ifnames=ifnames) @@ -47,10 +47,8 @@ class NrlNhdp(ConfigService): def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(has_smf=has_smf, ifnames=ifnames) @@ -74,13 +72,11 @@ def data(self) -> Dict[str, Any]: has_olsr = "OLSR" in self.node.config_services ifnames = [] ip4_prefix = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) if ip4_prefix: continue - for a in ifc.addrlist: + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): ip4_prefix = f"{a}/{24}" @@ -112,10 +108,8 @@ def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services has_zebra = "zebra" in self.node.config_services ifname = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifname = ifc.name + for iface in self.node.get_ifaces(control=False): + ifname = iface.name break return dict(has_smf=has_smf, has_zebra=has_zebra, ifname=ifname) @@ -137,10 +131,8 @@ class NrlOlsrv2(ConfigService): def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(has_smf=has_smf, ifnames=ifnames) @@ -161,10 +153,8 @@ class OlsrOrg(ConfigService): def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(has_smf=has_smf, ifnames=ifnames) @@ -199,12 +189,10 @@ class Arouted(ConfigService): def data(self) -> Dict[str, Any]: ip4_prefix = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue + for iface in self.node.get_ifaces(control=False): if ip4_prefix: continue - for a in ifc.addrlist: + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): ip4_prefix = f"{a}/{24}" diff --git a/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh b/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh index 00b7e11d1..4513dfe9c 100644 --- a/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh +++ b/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh @@ -1,7 +1,7 @@ <% - interfaces = "-i " + " -i ".join(ifnames) + ifaces = "-i " + " -i ".join(ifnames) smf = "" if has_smf: smf = "-flooding ecds -smfClient %s_smf" % node.name %> -nrlnhdp -l /var/log/nrlnhdp.log -rpipe ${node.name}_nhdp ${smf} ${interfaces} +nrlnhdp -l /var/log/nrlnhdp.log -rpipe ${node.name}_nhdp ${smf} ${ifaces} diff --git a/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh b/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh index d7a8d3b6d..81196e26e 100644 --- a/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh +++ b/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh @@ -1,7 +1,7 @@ <% - interfaces = "-i " + " -i ".join(ifnames) + ifaces = "-i " + " -i ".join(ifnames) smf = "" if has_smf: smf = "-flooding ecds -smfClient %s_smf" % node.name %> -nrlolsrv2 -l /var/log/nrlolsrv2.log -rpipe ${node.name}_olsrv2 -p olsr ${smf} ${interfaces} +nrlolsrv2 -l /var/log/nrlolsrv2.log -rpipe ${node.name}_olsrv2 -p olsr ${smf} ${ifaces} diff --git a/daemon/core/configservices/nrlservices/templates/olsrd.sh b/daemon/core/configservices/nrlservices/templates/olsrd.sh index 076f049be..3040ca6b0 100644 --- a/daemon/core/configservices/nrlservices/templates/olsrd.sh +++ b/daemon/core/configservices/nrlservices/templates/olsrd.sh @@ -1,4 +1,4 @@ <% - interfaces = "-i " + " -i ".join(ifnames) + ifaces = "-i " + " -i ".join(ifnames) %> -olsrd ${interfaces} +olsrd ${ifaces} diff --git a/daemon/core/configservices/nrlservices/templates/startsmf.sh b/daemon/core/configservices/nrlservices/templates/startsmf.sh index 67fc0fe67..921568def 100644 --- a/daemon/core/configservices/nrlservices/templates/startsmf.sh +++ b/daemon/core/configservices/nrlservices/templates/startsmf.sh @@ -1,5 +1,5 @@ <% - interfaces = ",".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]) @@ -12,4 +12,4 @@ %> #!/bin/sh # auto-generated by NrlSmf service -nrlsmf instance ${node.name}_smf ${interfaces} ${arouted} ${flood} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 & +nrlsmf instance ${node.name}_smf ${ifaces} ${arouted} ${flood} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 & diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 32ce99be2..19e21476a 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -14,33 +14,33 @@ GROUP = "Quagga" -def has_mtu_mismatch(ifc: CoreInterface) -> bool: +def has_mtu_mismatch(iface: CoreInterface) -> bool: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: return True - if not ifc.net: + if not iface.net: return False - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return True return False -def get_min_mtu(ifc): +def get_min_mtu(iface: CoreInterface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @@ -48,10 +48,8 @@ def get_router_id(node: CoreNodeBase) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -98,25 +96,25 @@ def data(self) -> Dict[str, Any]: want_ip6 = True services.append(service) - interfaces = [] - for ifc in self.node.netifs(): + ifaces = [] + for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in ifc.addrlist: + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): ip4s.append(x) else: ip6s.append(x) - is_control = getattr(ifc, "control", False) - interfaces.append((ifc, ip4s, ip6s, is_control)) + is_control = getattr(iface, "control", False) + ifaces.append((iface, ip4s, ip6s, is_control)) return dict( quagga_bin_search=quagga_bin_search, quagga_sbin_search=quagga_sbin_search, quagga_state_dir=quagga_state_dir, quagga_conf=quagga_conf, - interfaces=interfaces, + ifaces=ifaces, want_ip4=want_ip4, want_ip6=want_ip6, services=services, @@ -139,7 +137,7 @@ class QuaggaService(abc.ABC): ipv6_routing = False @abc.abstractmethod - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: raise NotImplementedError @abc.abstractmethod @@ -159,8 +157,8 @@ class Ospfv2(QuaggaService, ConfigService): shutdown = ["killall ospfd"] ipv4_routing = True - def quagga_interface_config(self, ifc: CoreInterface) -> str: - if has_mtu_mismatch(ifc): + def quagga_iface_config(self, iface: CoreInterface) -> str: + if has_mtu_mismatch(iface): return "ip ospf mtu-ignore" else: return "" @@ -168,10 +166,8 @@ def quagga_interface_config(self, ifc: CoreInterface) -> str: def quagga_config(self) -> str: router_id = get_router_id(self.node) addresses = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if netaddr.valid_ipv4(addr): addresses.append(a) @@ -200,9 +196,9 @@ class Ospfv3(QuaggaService, ConfigService): ipv4_routing = True ipv6_routing = True - def quagga_interface_config(self, ifc: CoreInterface) -> str: - mtu = get_min_mtu(ifc) - if mtu < ifc.mtu: + def quagga_iface_config(self, iface: CoreInterface) -> str: + mtu = get_min_mtu(iface) + if mtu < iface.mtu: return f"ipv6 ospf6 ifmtu {mtu}" else: return "" @@ -210,10 +206,8 @@ def quagga_interface_config(self, ifc: CoreInterface) -> str: def quagga_config(self) -> str: router_id = get_router_id(self.node) ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) data = dict(router_id=router_id, ifnames=ifnames) text = """ router ospf6 @@ -238,14 +232,14 @@ class Ospfv3mdr(Ospfv3): name = "OSPFv3MDR" def data(self) -> Dict[str, Any]: - for ifc in self.node.netifs(): - is_wireless = isinstance(ifc.net, (WlanNode, EmaneNet)) + for iface in self.node.get_ifaces(): + is_wireless = isinstance(iface.net, (WlanNode, EmaneNet)) logging.info("MDR wireless: %s", is_wireless) return dict() - def quagga_interface_config(self, ifc: CoreInterface) -> str: - config = super().quagga_interface_config(ifc) - if isinstance(ifc.net, (WlanNode, EmaneNet)): + def quagga_iface_config(self, iface: CoreInterface) -> str: + config = super().quagga_iface_config(iface) + if isinstance(iface.net, (WlanNode, EmaneNet)): config = self.clean_text( f""" {config} @@ -277,7 +271,7 @@ class Bgp(QuaggaService, ConfigService): def quagga_config(self) -> str: return "" - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: router_id = get_router_id(self.node) text = f""" ! BGP configuration @@ -313,7 +307,7 @@ def quagga_config(self) -> str: """ return self.clean_text(text) - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: return "" @@ -338,7 +332,7 @@ def quagga_config(self) -> str: """ return self.clean_text(text) - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: return "" @@ -355,10 +349,8 @@ class Babel(QuaggaService, ConfigService): def quagga_config(self) -> str: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) text = """ router babel % for ifname in ifnames: @@ -371,8 +363,8 @@ def quagga_config(self) -> str: data = dict(ifnames=ifnames) return self.render_text(text, data) - def quagga_interface_config(self, ifc: CoreInterface) -> str: - if isinstance(ifc.net, (WlanNode, EmaneNet)): + def quagga_iface_config(self, iface: CoreInterface) -> str: + if isinstance(iface.net, (WlanNode, EmaneNet)): text = """ babel wireless no babel split-horizon @@ -397,9 +389,9 @@ class Xpimd(QuaggaService, ConfigService): def quagga_config(self) -> str: ifname = "eth0" - for ifc in self.node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in self.node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break text = f""" @@ -416,7 +408,7 @@ def quagga_config(self) -> str: """ return self.clean_text(text) - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: text = """ ip mfea ip pim diff --git a/daemon/core/configservices/quaggaservices/templates/Quagga.conf b/daemon/core/configservices/quaggaservices/templates/Quagga.conf index 853b17073..1d69838f0 100644 --- a/daemon/core/configservices/quaggaservices/templates/Quagga.conf +++ b/daemon/core/configservices/quaggaservices/templates/Quagga.conf @@ -1,5 +1,5 @@ -% for ifc, ip4s, ip6s, is_control in interfaces: -interface ${ifc.name} +% for iface, ip4s, ip6s, is_control in ifaces: +interface ${iface.name} % if want_ip4: % for addr in ip4s: ip address ${addr} @@ -12,7 +12,7 @@ interface ${ifc.name} % endif % if not is_control: % for service in services: - % for line in service.quagga_interface_config(ifc).split("\n"): + % for line in service.quagga_iface_config(iface).split("\n"): ${line} % endfor % endfor diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 17f081cdd..6e92bf623 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -78,10 +78,8 @@ class VpnServer(ConfigService): def data(self) -> Dict[str, Any]: address = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for x in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): address = addr @@ -134,8 +132,6 @@ class Nat(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(ifnames=ifnames) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 8ddf1cc7b..5aa3bb543 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -25,10 +25,10 @@ class DefaultRouteService(ConfigService): def data(self) -> Dict[str, Any]: # only add default routes for linked routing nodes routes = [] - netifs = self.node.netifs(sort=True) - if netifs: - netif = netifs[0] - for x in netif.addrlist: + ifaces = self.node.get_ifaces() + if ifaces: + iface = ifaces[0] + for x in iface.addrlist: net = netaddr.IPNetwork(x).cidr if net.size > 1: router = net[1] @@ -52,10 +52,8 @@ class DefaultMulticastRouteService(ConfigService): def data(self) -> Dict[str, Any]: ifname = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifname = ifc.name + for iface in self.node.get_ifaces(control=False): + ifname = iface.name break return dict(ifname=ifname) @@ -76,10 +74,8 @@ class StaticRouteService(ConfigService): def data(self) -> Dict[str, Any]: routes = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for x in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): dst = "3ffe:4::/64" @@ -107,8 +103,8 @@ class IpForwardService(ConfigService): def data(self) -> Dict[str, Any]: devnames = [] - for ifc in self.node.netifs(): - devname = utils.sysctl_devname(ifc.name) + for iface in self.node.get_ifaces(): + devname = utils.sysctl_devname(iface.name) devnames.append(devname) return dict(devnames=devnames) @@ -151,10 +147,8 @@ class DhcpService(ConfigService): def data(self) -> Dict[str, Any]: subnets = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for x in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): net = netaddr.IPNetwork(x) @@ -182,10 +176,8 @@ class DhcpClientService(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(ifnames=ifnames) @@ -220,10 +212,8 @@ class PcapService(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict() @@ -242,19 +232,17 @@ class RadvdService(ConfigService): modes = {} def data(self) -> Dict[str, Any]: - interfaces = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue + ifaces = [] + for iface in self.node.get_ifaces(control=False): prefixes = [] - for x in ifc.addrlist: + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): prefixes.append(x) if not prefixes: continue - interfaces.append((ifc.name, prefixes)) - return dict(interfaces=interfaces) + ifaces.append((iface.name, prefixes)) + return dict(ifaces=ifaces) class AtdService(ConfigService): @@ -294,9 +282,7 @@ class HttpService(ConfigService): modes = {} def data(self) -> Dict[str, Any]: - interfaces = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - interfaces.append(ifc) - return dict(interfaces=interfaces) + ifaces = [] + for iface in self.node.get_ifaces(control=False): + ifaces.append(iface) + return dict(ifaces=ifaces) diff --git a/daemon/core/configservices/utilservices/templates/index.html b/daemon/core/configservices/utilservices/templates/index.html index aaf9d9fa3..bed270aea 100644 --- a/daemon/core/configservices/utilservices/templates/index.html +++ b/daemon/core/configservices/utilservices/templates/index.html @@ -5,8 +5,8 @@

${node.name} web server

This is the default web page for this server.

The web server software is running but no content has been added, yet.

diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 21252b6f9..0f441d76b 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -63,7 +63,7 @@ def config_groups(cls) -> List[ConfigGroup]: return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))] def build_xml_files( - self, config: Dict[str, str], interface: CoreInterface = None + self, config: Dict[str, str], iface: CoreInterface = None ) -> None: """ Build the necessary nem and commeffect XMLs in the given path. @@ -72,17 +72,17 @@ def build_xml_files( nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used. :param config: emane model configuration for the node and interface - :param interface: interface for the emane node + :param iface: interface for the emane node :return: nothing """ # retrieve xml names - nem_name = emanexml.nem_file_name(self, interface) - shim_name = emanexml.shim_file_name(self, interface) + nem_name = emanexml.nem_file_name(self, iface) + shim_name = emanexml.shim_file_name(self, iface) # create and write nem document nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") transport_type = TransportType.VIRTUAL - if interface and interface.transport_type == TransportType.RAW: + if iface and iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW transport_file = emanexml.transport_file_name(self.id, transport_type) etree.SubElement(nem_element, "transport", definition=transport_file) @@ -115,7 +115,7 @@ def build_xml_files( emanexml.create_file(shim_element, "shim", shim_file) def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Generate CommEffect events when a Link Message is received having @@ -126,7 +126,7 @@ def linkconfig( logging.warning("%s: EMANE event service unavailable", self.name) return - if netif is None or netif2 is None: + if iface is None or iface2 is None: logging.warning("%s: missing NEM information", self.name) return @@ -134,8 +134,8 @@ def linkconfig( # TODO: may want to split out seconds portion of delay and jitter event = CommEffectEvent() emane_node = self.session.get_node(self.id, EmaneNet) - nemid = emane_node.getnemid(netif) - nemid2 = emane_node.getnemid(netif2) + nemid = emane_node.getnemid(iface) + nemid2 = emane_node.getnemid(iface2) logging.info("sending comm effect event") event.append( nemid, diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index cb978cb9d..58b850801 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -111,41 +111,39 @@ def __init__(self, session: "Session") -> None: self.event_device: Optional[str] = None self.emane_check() - def getifcconfig( - self, node_id: int, interface: CoreInterface, model_name: str + def get_iface_config( + self, node_id: int, iface: CoreInterface, model_name: str ) -> Dict[str, str]: """ Retrieve interface configuration or node configuration if not provided. :param node_id: node id - :param interface: node interface + :param iface: node interface :param model_name: model to get configuration for :return: node/interface model configuration """ # use the network-wide config values or interface(NEM)-specific values? - if interface is None: + if iface is None: return self.get_configs(node_id=node_id, config_type=model_name) else: # don"t use default values when interface config is the same as net - # note here that using ifc.node.id as key allows for only one type + # note here that using iface.node.id as key allows for only one type # of each model per node; # TODO: use both node and interface as key - # Adamson change: first check for iface config keyed by "node:ifc.name" + # Adamson change: first check for iface config keyed by "node:iface.name" # (so that nodes w/ multiple interfaces of same conftype can have # different configs for each separate interface) - key = 1000 * interface.node.id - if interface.netindex is not None: - key += interface.netindex + key = 1000 * iface.node.id + if iface.node_id is not None: + key += iface.node_id # try retrieve interface specific configuration, avoid getting defaults config = self.get_configs(node_id=key, config_type=model_name) # otherwise retrieve the interfaces node configuration, avoid using defaults if not config: - config = self.get_configs( - node_id=interface.node.id, config_type=model_name - ) + config = self.get_configs(node_id=iface.node.id, config_type=model_name) # get non interface config, when none found if not config: @@ -265,8 +263,8 @@ def getnodes(self) -> Set[CoreNode]: # assumes self._objslock already held nodes = set() for emane_net in self._emane_nets.values(): - for netif in emane_net.netifs(): - nodes.add(netif.node) + for iface in emane_net.get_ifaces(): + nodes.add(iface.node) return nodes def setup(self) -> int: @@ -352,13 +350,13 @@ def startup(self) -> int: if self.numnems() > 0: self.startdaemons() - self.installnetifs() + self.install_ifaces() for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] - for netif in emane_node.netifs(): + for iface in emane_node.get_ifaces(): nems.append( - (netif.node.name, netif.name, emane_node.getnemid(netif)) + (iface.node.name, iface.name, emane_node.getnemid(iface)) ) if nems: @@ -392,8 +390,8 @@ def poststartup(self) -> None: emane_node.name, ) emane_node.model.post_startup() - for netif in emane_node.netifs(): - netif.setposition() + for iface in emane_node.get_ifaces(): + iface.setposition() def reset(self) -> None: """ @@ -420,7 +418,7 @@ def shutdown(self) -> None: logging.info("stopping EMANE daemons") if self.links_enabled(): self.link_monitor.stop() - self.deinstallnetifs() + self.deinstall_ifaces() self.stopdaemons() self.stopeventmonitor() @@ -474,31 +472,31 @@ def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]] EMANE network and NEM interface. """ emane_node = None - netif = None + iface = None for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] - netif = emane_node.getnemnetif(nemid) - if netif is not None: + iface = emane_node.get_nem_iface(nemid) + if iface is not None: break else: emane_node = None - return emane_node, netif + return emane_node, iface def get_nem_link( self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE ) -> Optional[LinkData]: - emane1, netif = self.nemlookup(nem1) - if not emane1 or not netif: + emane1, iface = self.nemlookup(nem1) + if not emane1 or not iface: logging.error("invalid nem: %s", nem1) return None - node1 = netif.node - emane2, netif = self.nemlookup(nem2) - if not emane2 or not netif: + node1 = iface.node + emane2, iface = self.nemlookup(nem2) + if not emane2 or not iface: logging.error("invalid nem: %s", nem2) return None - node2 = netif.node + node2 = iface.node color = self.session.get_link_color(emane1.id) return LinkData( message_type=flags, @@ -516,7 +514,7 @@ def numnems(self) -> int: count = 0 for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] - count += len(emane_node.netifs()) + count += len(emane_node.ifaces) return count def buildplatformxml(self, ctrlnet: CtrlNet) -> None: @@ -607,19 +605,19 @@ def startdaemons(self) -> None: n = node.id # control network not yet started here - self.session.add_remove_control_interface( + self.session.add_remove_control_iface( node, 0, remove=False, conf_required=False ) if otanetidx > 0: logging.info("adding ota device ctrl%d", otanetidx) - self.session.add_remove_control_interface( + self.session.add_remove_control_iface( node, otanetidx, remove=False, conf_required=False ) if eventservicenetidx >= 0: logging.info("adding event service device ctrl%d", eventservicenetidx) - self.session.add_remove_control_interface( + self.session.add_remove_control_iface( node, eventservicenetidx, remove=False, conf_required=False ) @@ -676,23 +674,23 @@ def stopdaemons(self) -> None: except CoreCommandError: logging.exception("error shutting down emane daemons") - def installnetifs(self) -> None: + def install_ifaces(self) -> None: """ Install TUN/TAP virtual interfaces into their proper namespaces now that the EMANE daemons are running. """ for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] - logging.info("emane install netifs for node: %d", key) - emane_node.installnetifs() + node = self._emane_nets[key] + logging.info("emane install interface for node(%s): %d", node.name, key) + node.install_ifaces() - def deinstallnetifs(self) -> None: + def deinstall_ifaces(self) -> None: """ Uninstall TUN/TAP virtual interfaces. """ for key in sorted(self._emane_nets.keys()): emane_node = self._emane_nets[key] - emane_node.deinstallnetifs() + emane_node.deinstall_ifaces() def doeventmonitor(self) -> bool: """ @@ -808,12 +806,12 @@ def handlelocationeventtoxyz( Returns True if successfully parsed and a Node Message was sent. """ # convert nemid to node number - _emanenode, netif = self.nemlookup(nemid) - if netif is None: + _emanenode, iface = self.nemlookup(nemid) + if iface is None: logging.info("location event for unknown NEM %s", nemid) return False - n = netif.node.id + n = iface.node.id # convert from lat/long/alt to x,y,z coordinates x, y, z = self.session.location.getxyz(lat, lon, alt) x = int(x) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 78d5ec5eb..1a14011a4 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -97,28 +97,28 @@ def config_groups(cls) -> List[ConfigGroup]: ] def build_xml_files( - self, config: Dict[str, str], interface: CoreInterface = None + self, config: Dict[str, str], iface: CoreInterface = None ) -> None: """ Builds xml files for this emane model. Creates a nem.xml file that points to both mac.xml and phy.xml definitions. :param config: emane model configuration for the node and interface - :param interface: interface for the emane node + :param iface: interface for the emane node :return: nothing """ - nem_name = emanexml.nem_file_name(self, interface) - mac_name = emanexml.mac_file_name(self, interface) - phy_name = emanexml.phy_file_name(self, interface) + nem_name = emanexml.nem_file_name(self, iface) + mac_name = emanexml.mac_file_name(self, iface) + phy_name = emanexml.phy_file_name(self, iface) # remote server for file server = None - if interface is not None: - server = interface.node.server + if iface is not None: + server = iface.node.server # check if this is external transport_type = TransportType.VIRTUAL - if interface and interface.transport_type == TransportType.RAW: + if iface and iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW transport_name = emanexml.transport_file_name(self.id, transport_type) @@ -144,31 +144,31 @@ def post_startup(self) -> None: """ logging.debug("emane model(%s) has no post setup tasks", self.name) - def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: """ Invoked from MobilityModel when nodes are moved; this causes emane location events to be generated for the nodes in the moved list, making EmaneModels compatible with Ns2ScriptedMobility. :param moved: moved nodes - :param moved_netifs: interfaces that were moved + :param moved_ifaces: interfaces that were moved :return: nothing """ try: wlan = self.session.get_node(self.id, EmaneNet) - wlan.setnempositions(moved_netifs) + wlan.setnempositions(moved_ifaces) except CoreError: logging.exception("error during update") def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Invoked when a Link Message is received. Default is unimplemented. - :param netif: interface one + :param iface: interface one :param options: options for configuring link - :param netif2: interface two + :param iface2: interface two :return: nothing """ logging.warning("emane model(%s) does not support link config", self.name) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index ca9f44933..097080c33 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -212,10 +212,10 @@ def get_addresses(self) -> List[str]: addresses = [] nodes = self.emane_manager.getnodes() for node in nodes: - for netif in node.netifs(): - if isinstance(netif.net, CtrlNet): + for iface in node.get_ifaces(): + if isinstance(iface.net, CtrlNet): ip4 = None - for x in netif.addrlist: + for x in iface.addrlist: address, prefix = x.split("/") if netaddr.valid_ipv4(address): ip4 = address diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index c4c3428ba..eed51ff28 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -64,14 +64,14 @@ def __init__( self.mobility: Optional[WayPointMobility] = None def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ The CommEffect model supports link configuration. """ if not self.model: return - self.model.linkconfig(netif, options, netif2) + self.model.linkconfig(iface, options, iface2) def config(self, conf: str) -> None: self.conf = conf @@ -82,10 +82,10 @@ def startup(self) -> None: def shutdown(self) -> None: pass - def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None: pass - def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: pass def linknet(self, net: "CoreNetworkBase") -> CoreInterface: @@ -113,39 +113,33 @@ def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None: self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) - def setnemid(self, netif: CoreInterface, nemid: int) -> None: + def setnemid(self, iface: CoreInterface, nemid: int) -> None: """ Record an interface to numerical ID mapping. The Emane controller object manages and assigns these IDs for all NEMs. """ - self.nemidmap[netif] = nemid + self.nemidmap[iface] = nemid - def getnemid(self, netif: CoreInterface) -> Optional[int]: + def getnemid(self, iface: CoreInterface) -> Optional[int]: """ Given an interface, return its numerical ID. """ - if netif not in self.nemidmap: + if iface not in self.nemidmap: return None else: - return self.nemidmap[netif] + return self.nemidmap[iface] - def getnemnetif(self, nemid: int) -> Optional[CoreInterface]: + def get_nem_iface(self, nemid: int) -> Optional[CoreInterface]: """ Given a numerical NEM ID, return its interface. This returns the first interface that matches the given NEM ID. """ - for netif in self.nemidmap: - if self.nemidmap[netif] == nemid: - return netif + for iface in self.nemidmap: + if self.nemidmap[iface] == nemid: + return iface return None - def netifs(self, sort: bool = True) -> List[CoreInterface]: - """ - Retrieve list of linked interfaces sorted by node number. - """ - return sorted(self._netif.values(), key=lambda ifc: ifc.node.id) - - def installnetifs(self) -> None: + def install_ifaces(self) -> None: """ Install TAP devices into their namespaces. This is done after EMANE daemons have been started, because that is their only chance @@ -159,48 +153,48 @@ def installnetifs(self) -> None: warntxt += "Python bindings failed to load" logging.error(warntxt) - for netif in self.netifs(): + for iface in self.get_ifaces(): external = self.session.emane.get_config( "external", self.id, self.model.name ) if external == "0": - netif.setaddrs() + iface.setaddrs() if not self.session.emane.genlocationevents(): - netif.poshook = None + iface.poshook = None continue # at this point we register location handlers for generating # EMANE location events - netif.poshook = self.setnemposition - netif.setposition() + iface.poshook = self.setnemposition + iface.setposition() - def deinstallnetifs(self) -> None: + def deinstall_ifaces(self) -> None: """ Uninstall TAP devices. This invokes their shutdown method for any required cleanup; the device may be actually removed when emanetransportd terminates. """ - for netif in self.netifs(): - if netif.transport_type == TransportType.VIRTUAL: - netif.shutdown() - netif.poshook = None + for iface in self.get_ifaces(): + if iface.transport_type == TransportType.VIRTUAL: + iface.shutdown() + iface.poshook = None def _nem_position( - self, netif: CoreInterface + self, iface: CoreInterface ) -> Optional[Tuple[int, float, float, float]]: """ Creates nem position for emane event for a given interface. - :param netif: interface to get nem emane position for + :param iface: interface to get nem emane position for :return: nem position tuple, None otherwise """ - nemid = self.getnemid(netif) - ifname = netif.localname + nemid = self.getnemid(iface) + ifname = iface.localname if nemid is None: logging.info("nemid for %s is unknown", ifname) return - node = netif.node + node = iface.node x, y, z = node.getposition() lat, lon, alt = self.session.location.getgeo(x, y, z) if node.position.alt is not None: @@ -210,30 +204,30 @@ def _nem_position( alt = int(round(alt)) return nemid, lon, lat, alt - def setnemposition(self, netif: CoreInterface) -> None: + def setnemposition(self, iface: CoreInterface) -> None: """ Publish a NEM location change event using the EMANE event service. - :param netif: interface to set nem position for + :param iface: interface to set nem position for """ if self.session.emane.service is None: logging.info("position service not available") return - position = self._nem_position(netif) + position = self._nem_position(iface) if position: nemid, lon, lat, alt = position event = LocationEvent() event.append(nemid, latitude=lat, longitude=lon, altitude=alt) self.session.emane.service.publish(0, event) - def setnempositions(self, moved_netifs: List[CoreInterface]) -> None: + def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None: """ Several NEMs have moved, from e.g. a WaypointMobilityModel calculation. Generate an EMANE Location Event having several - entries for each netif that has moved. + entries for each interface that has moved. """ - if len(moved_netifs) == 0: + if len(moved_ifaces) == 0: return if self.session.emane.service is None: @@ -241,8 +235,8 @@ def setnempositions(self, moved_netifs: List[CoreInterface]) -> None: return event = LocationEvent() - for netif in moved_netifs: - position = self._nem_position(netif) + for iface in moved_ifaces: + position = self._nem_position(iface) if position: nemid, lon, lat, alt = position event.append(nemid, latitude=lat, longitude=lon, altitude=alt) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 819716e37..47f458203 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -27,7 +27,7 @@ class ConfigData: possible_values: str = None groups: str = None session: int = None - interface_number: int = None + iface_id: int = None network_id: int = None opaque: str = None @@ -114,19 +114,19 @@ class LinkData: emulation_id: int = None network_id: int = None key: int = None - interface1_id: int = None - interface1_name: str = None - interface1_ip4: str = None - interface1_ip4_mask: int = None - interface1_mac: str = None - interface1_ip6: str = None - interface1_ip6_mask: int = None - interface2_id: int = None - interface2_name: str = None - interface2_ip4: str = None - interface2_ip4_mask: int = None - interface2_mac: str = None - interface2_ip6: str = None - interface2_ip6_mask: int = None + iface1_id: int = None + iface1_name: str = None + iface1_ip4: str = None + iface1_ip4_mask: int = None + iface1_mac: str = None + iface1_ip6: str = None + iface1_ip6_mask: int = None + iface2_id: int = None + iface2_name: str = None + iface2_ip4: str = None + iface2_ip4_mask: int = None + iface2_mac: str = None + iface2_ip6: str = None + iface2_ip6_mask: int = None opaque: str = None color: str = None diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 75081447f..381eb019d 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -208,7 +208,7 @@ def create_gre_tunnel( "local tunnel node(%s) to remote(%s) key(%s)", node.name, host, key ) local_tap = GreTap(session=self.session, remoteip=host, key=key) - local_tap.net_client.set_interface_master(node.brname, local_tap.localname) + local_tap.net_client.set_iface_master(node.brname, local_tap.localname) # server to local logging.info( @@ -217,7 +217,7 @@ def create_gre_tunnel( remote_tap = GreTap( session=self.session, remoteip=self.address, key=key, server=server ) - remote_tap.net_client.set_interface_master(node.brname, remote_tap.localname) + remote_tap.net_client.set_iface_master(node.brname, remote_tap.localname) # save tunnels for shutdown tunnel = (local_tap, remote_tap) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 2aecdacee..25ce71ac1 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -155,7 +155,7 @@ def ip6_address(self, node_id: int) -> str: raise ValueError("ip6 prefixes have not been set") return str(self.ip6[node_id]) - def gen_interface(self, node_id: int, name: str = None, mac: str = None): + def gen_iface(self, node_id: int, name: str = None, mac: str = None): """ Creates interface data for linking nodes, using the nodes unique id for generation, along with a random mac address, unless provided. @@ -188,7 +188,7 @@ def gen_interface(self, node_id: int, name: str = None, mac: str = None): name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac ) - def create_interface( + def create_iface( self, node: "CoreNode", name: str = None, mac: str = None ) -> InterfaceData: """ @@ -201,6 +201,6 @@ def create_interface( generation :return: new interface data for the provided node """ - interface_data = self.gen_interface(node.id, name, mac) - interface_data.id = node.newifindex() - return interface_data + iface_data = self.gen_iface(node.id, name, mac) + iface_data.id = node.next_iface_id() + return iface_data diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index e63c30c73..2dc5ad12d 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -203,7 +203,7 @@ def _link_wireless( common_networks = node1.commonnets(node1) if not common_networks: raise CoreError("no common network found for wireless link/unlink") - for common_network, interface1, interface2 in common_networks: + for common_network, iface1, iface2 in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): logging.info( "skipping common network that is not wireless/emane: %s", @@ -211,16 +211,16 @@ def _link_wireless( ) continue if connect: - common_network.link(interface1, interface2) + common_network.link(iface1, iface2) else: - common_network.unlink(interface1, interface2) + common_network.unlink(iface1, iface2) def add_link( self, node1_id: int, node2_id: int, - interface1_data: InterfaceData = None, - interface2_data: InterfaceData = None, + iface1_data: InterfaceData = None, + iface2_data: InterfaceData = None, options: LinkOptions = None, ) -> Tuple[CoreInterface, CoreInterface]: """ @@ -228,9 +228,9 @@ def add_link( :param node1_id: node one id :param node2_id: node two id - :param interface1_data: node one interface + :param iface1_data: node one interface data, defaults to none - :param interface2_data: node two interface + :param iface2_data: node two interface data, defaults to none :param options: data for creating link, defaults to no options @@ -240,8 +240,8 @@ def add_link( options = LinkOptions() node1 = self.get_node(node1_id, NodeBase) node2 = self.get_node(node2_id, NodeBase) - interface1 = None - interface2 = None + iface1 = None + iface2 = None # wireless link if options.type == LinkTypes.WIRELESS: @@ -258,22 +258,22 @@ def add_link( logging.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() ptp = self.create_node(PtpNet, start) - interface1 = node1.newnetif(ptp, interface1_data) - interface2 = node2.newnetif(ptp, interface2_data) - ptp.linkconfig(interface1, options) + iface1 = node1.new_iface(ptp, iface1_data) + iface2 = node2.new_iface(ptp, iface2_data) + ptp.linkconfig(iface1, options) if not options.unidirectional: - ptp.linkconfig(interface2, options) + ptp.linkconfig(iface2, options) # link node to net elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - interface1 = node1.newnetif(node2, interface1_data) + iface1 = node1.new_iface(node2, iface1_data) if not isinstance(node2, (EmaneNet, WlanNode)): - node2.linkconfig(interface1, options) + node2.linkconfig(iface1, options) # link net to node elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - interface2 = node2.newnetif(node1, interface2_data) + iface2 = node2.new_iface(node1, iface2_data) wireless_net = isinstance(node1, (EmaneNet, WlanNode)) if not options.unidirectional and not wireless_net: - node1.linkconfig(interface2, options) + node1.linkconfig(iface2, options) # network to network elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase @@ -281,12 +281,12 @@ def add_link( logging.info( "linking network to network: %s - %s", node1.name, node2.name ) - interface1 = node1.linknet(node2) - node1.linkconfig(interface1, options) + iface1 = node1.linknet(node2) + node1.linkconfig(iface1, options) if not options.unidirectional: - interface1.swapparams("_params_up") - node2.linkconfig(interface1, options) - interface1.swapparams("_params_up") + iface1.swapparams("_params_up") + node2.linkconfig(iface1, options) + iface1.swapparams("_params_up") else: raise CoreError( f"cannot link node1({type(node1)}) node2({type(node2)})" @@ -296,19 +296,19 @@ def add_link( key = options.key if isinstance(node1, TunnelNode): logging.info("setting tunnel key for: %s", node1.name) - node1.setkey(key, interface1_data) + node1.setkey(key, iface1_data) if isinstance(node2, TunnelNode): logging.info("setting tunnel key for: %s", node2.name) - node2.setkey(key, interface2_data) + node2.setkey(key, iface2_data) self.sdt.add_link(node1_id, node2_id) - return interface1, interface2 + return iface1, iface2 def delete_link( self, node1_id: int, node2_id: int, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ @@ -316,8 +316,8 @@ def delete_link( :param node1_id: node one id :param node2_id: node two id - :param interface1_id: interface id for node one - :param interface2_id: interface id for node two + :param iface1_id: interface id for node one + :param iface2_id: interface id for node two :param link_type: link type to delete :return: nothing :raises core.CoreError: when no common network is found for link being deleted @@ -328,9 +328,9 @@ def delete_link( "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", link_type.name, node1.name, - interface1_id, + iface1_id, node2.name, - interface2_id, + iface2_id, ) # wireless link @@ -345,37 +345,29 @@ def delete_link( # wired link else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface1_id) - interface2 = node2.netif(interface2_id) - if not interface1: - raise CoreError( - f"node({node1.name}) missing interface({interface1_id})" - ) - if not interface2: - raise CoreError( - f"node({node2.name}) missing interface({interface2_id})" - ) - if interface1.net != interface2.net: + iface1 = node1.get_iface(iface1_id) + iface2 = node2.get_iface(iface2_id) + if iface1.net != iface2.net: raise CoreError( f"node1({node1.name}) node2({node2.name}) " "not connected to same net" ) - ptp = interface1.net - node1.delnetif(interface1_id) - node2.delnetif(interface2_id) + ptp = iface1.net + node1.delete_iface(iface1_id) + node2.delete_iface(iface2_id) self.delete_node(ptp.id) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - node1.delnetif(interface1_id) + node1.delete_iface(iface1_id) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - node2.delnetif(interface2_id) + node2.delete_iface(iface2_id) self.sdt.delete_link(node1_id, node2_id) def update_link( self, node1_id: int, node2_id: int, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, options: LinkOptions = None, ) -> None: """ @@ -383,8 +375,8 @@ def update_link( :param node1_id: node one id :param node2_id: node two id - :param interface1_id: interface id for node one - :param interface2_id: interface id for node two + :param iface1_id: interface id for node one + :param iface2_id: interface id for node two :param options: data to update link with :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a @@ -398,9 +390,9 @@ def update_link( "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", options.type.name, node1.name, - interface1_id, + iface1_id, node2.name, - interface2_id, + iface2_id, ) # wireless link @@ -408,54 +400,54 @@ def update_link( raise CoreError("cannot update wireless link") else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface1_id) - interface2 = node2.netif(interface2_id) - if not interface1: + iface1 = node1.ifaces.get(iface1_id) + iface2 = node2.ifaces.get(iface2_id) + if not iface1: raise CoreError( - f"node({node1.name}) missing interface({interface1_id})" + f"node({node1.name}) missing interface({iface1_id})" ) - if not interface2: + if not iface2: raise CoreError( - f"node({node2.name}) missing interface({interface2_id})" + f"node({node2.name}) missing interface({iface2_id})" ) - if interface1.net != interface2.net: + if iface1.net != iface2.net: raise CoreError( f"node1({node1.name}) node2({node2.name}) " "not connected to same net" ) - ptp = interface1.net - ptp.linkconfig(interface1, options, interface2) + ptp = iface1.net + ptp.linkconfig(iface1, options, iface2) if not options.unidirectional: - ptp.linkconfig(interface2, options, interface1) + ptp.linkconfig(iface2, options, iface1) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - interface = node1.netif(interface1_id) - node2.linkconfig(interface, options) + iface = node1.get_iface(iface1_id) + node2.linkconfig(iface, options) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - interface = node2.netif(interface2_id) - node1.linkconfig(interface, options) + iface = node2.get_iface(iface2_id) + node1.linkconfig(iface, options) elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase ): - interface = node1.getlinknetif(node2) + iface = node1.get_linked_iface(node2) upstream = False - if not interface: + if not iface: upstream = True - interface = node2.getlinknetif(node1) - if not interface: + iface = node2.get_linked_iface(node1) + if not iface: raise CoreError("modify unknown link between nets") if upstream: - interface.swapparams("_params_up") - node1.linkconfig(interface, options) - interface.swapparams("_params_up") + iface.swapparams("_params_up") + node1.linkconfig(iface, options) + iface.swapparams("_params_up") else: - node1.linkconfig(interface, options) + node1.linkconfig(iface, options) if not options.unidirectional: if upstream: - node2.linkconfig(interface, options) + node2.linkconfig(iface, options) else: - interface.swapparams("_params_up") - node2.linkconfig(interface, options) - interface.swapparams("_params_up") + iface.swapparams("_params_up") + node2.linkconfig(iface, options) + iface.swapparams("_params_up") else: raise CoreError( f"cannot update link node1({type(node1)}) node2({type(node2)})" @@ -553,7 +545,7 @@ def add_node( is_boot_node = isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node) if self.state == EventTypes.RUNTIME_STATE and is_boot_node: self.write_nodes() - self.add_remove_control_interface(node=node, remove=False) + self.add_remove_control_iface(node=node, remove=False) self.services.boot_services(node) self.sdt.add_node(node) @@ -1268,7 +1260,7 @@ def data_collect(self) -> None: self.emane.shutdown() # update control interface hosts - self.update_control_interface_hosts(remove=True) + self.update_control_iface_hosts(remove=True) # remove all four possible control networks self.add_remove_control_net(0, remove=True) @@ -1314,7 +1306,7 @@ def boot_node(self, node: CoreNode) -> None: :return: nothing """ logging.info("booting node(%s): %s", node.name, [x.name for x in node.services]) - self.add_remove_control_interface(node=node, remove=False) + self.add_remove_control_iface(node=node, remove=False) self.services.boot_services(node) node.start_config_services() @@ -1338,7 +1330,7 @@ def boot_nodes(self) -> List[Exception]: total = time.monotonic() - start logging.debug("boot run time: %s", total) if not exceptions: - self.update_control_interface_hosts() + self.update_control_iface_hosts() return exceptions def get_control_net_prefixes(self) -> List[str]: @@ -1356,7 +1348,7 @@ def get_control_net_prefixes(self) -> List[str]: p0 = p return [p0, p1, p2, p3] - def get_control_net_server_interfaces(self) -> List[str]: + def get_control_net_server_ifaces(self) -> List[str]: """ Retrieve control net server interfaces. @@ -1424,7 +1416,7 @@ def add_remove_control_net( else: prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index] logging.debug("prefix spec: %s", prefix_spec) - server_interface = self.get_control_net_server_interfaces()[net_index] + server_iface = self.get_control_net_server_ifaces()[net_index] # return any existing controlnet bridge try: @@ -1465,7 +1457,7 @@ def add_remove_control_net( _id, prefix, updown_script, - server_interface, + server_iface, ) control_net = self.create_node( CtrlNet, @@ -1473,11 +1465,11 @@ def add_remove_control_net( prefix, _id=_id, updown_script=updown_script, - serverintf=server_interface, + serverintf=server_iface, ) return control_net - def add_remove_control_interface( + def add_remove_control_iface( self, node: CoreNode, net_index: int = 0, @@ -1503,27 +1495,27 @@ def add_remove_control_interface( if not node: return # ctrl# already exists - if node.netif(control_net.CTRLIF_IDX_BASE + net_index): + if node.ifaces.get(control_net.CTRLIF_IDX_BASE + net_index): return try: ip4 = control_net.prefix[node.id] ip4_mask = control_net.prefix.prefixlen - interface_data = InterfaceData( + iface_data = InterfaceData( id=control_net.CTRLIF_IDX_BASE + net_index, name=f"ctrl{net_index}", mac=utils.random_mac(), ip4=ip4, ip4_mask=ip4_mask, ) - interface = node.newnetif(control_net, interface_data) - interface.control = True + iface = node.new_iface(control_net, iface_data) + iface.control = True except ValueError: msg = f"Control interface not added to node {node.id}. " msg += f"Invalid control network prefix ({control_net.prefix}). " msg += "A longer prefix length may be required for this many nodes." logging.exception(msg) - def update_control_interface_hosts( + def update_control_iface_hosts( self, net_index: int = 0, remove: bool = False ) -> None: """ @@ -1549,9 +1541,9 @@ def update_control_interface_hosts( return entries = [] - for interface in control_net.netifs(): - name = interface.node.name - for address in interface.addrlist: + for iface in control_net.get_ifaces(): + name = iface.node.name + for address in iface.addrlist: address = address.split("/")[0] entries.append(f"{address} {name}") diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 5c1c52a0b..3be58e17d 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -57,8 +57,8 @@ def __init__(self, app: "Application", proxy: bool): self.read_config() # helpers - self.interface_to_edge = {} - self.interfaces_manager = InterfaceManager(self.app) + self.iface_to_edge = {} + self.ifaces_manager = InterfaceManager(self.app) # session data self.state = None @@ -91,8 +91,8 @@ def client(self): def reset(self): # helpers - self.interfaces_manager.reset() - self.interface_to_edge.clear() + self.ifaces_manager.reset() + self.iface_to_edge.clear() # session data self.canvas_nodes.clear() self.links.clear() @@ -263,7 +263,7 @@ def join_session(self, session_id: int, query_location: bool = True): self.emane_config = response.config # update interface manager - self.interfaces_manager.joined(session.links) + self.ifaces_manager.joined(session.links) # draw session self.app.canvas.reset_and_redraw(session) @@ -278,11 +278,11 @@ def join_session(self, session_id: int, query_location: bool = True): # get emane model config response = self.client.get_emane_model_configs(self.session_id) for config in response.configs: - interface = None - if config.interface != -1: - interface = config.interface + iface_id = None + if config.iface_id != -1: + iface_id = config.iface_id canvas_node = self.canvas_nodes[config.node_id] - canvas_node.emane_model_configs[(config.model, interface)] = dict( + canvas_node.emane_model_configs[(config.model, iface_id)] = dict( config.config ) @@ -460,16 +460,16 @@ def edit_node(self, core_node: core_pb2.Node): self.app.show_grpc_exception("Edit Node Error", e) def start_session(self) -> core_pb2.StartSessionResponse: - self.interfaces_manager.reset_mac() + self.ifaces_manager.reset_mac() nodes = [x.core_node for x in self.canvas_nodes.values()] links = [] for edge in self.links.values(): link = core_pb2.Link() link.CopyFrom(edge.link) - if link.HasField("interface1") and not link.interface1.mac: - link.interface1.mac = self.interfaces_manager.next_mac() - if link.HasField("interface2") and not link.interface2.mac: - link.interface2.mac = self.interfaces_manager.next_mac() + if link.HasField("iface1") and not link.iface1.mac: + link.iface1.mac = self.ifaces_manager.next_mac() + if link.HasField("iface2") and not link.iface2.mac: + link.iface2.mac = self.ifaces_manager.next_mac() links.append(link) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() @@ -689,8 +689,8 @@ def create_nodes_and_links(self): self.session_id, link_proto.node1_id, link_proto.node2_id, - link_proto.interface1, - link_proto.interface2, + link_proto.iface1, + link_proto.iface2, link_proto.options, ) logging.debug("create link: %s", response) @@ -733,7 +733,7 @@ def send_data(self): config_proto.node_id, config_proto.model, config_proto.config, - config_proto.interface_id, + config_proto.iface_id, ) if self.emane_config: config = {x: self.emane_config[x].value for x in self.emane_config} @@ -824,31 +824,26 @@ def deleted_graph_edges(self, edges: Iterable[CanvasEdge]) -> None: for edge in edges: del self.links[edge.token] links.append(edge.link) - self.interfaces_manager.removed(links) + self.ifaces_manager.removed(links) - def create_interface(self, canvas_node: CanvasNode) -> core_pb2.Interface: + def create_iface(self, canvas_node: CanvasNode) -> core_pb2.Interface: node = canvas_node.core_node - ip4, ip6 = self.interfaces_manager.get_ips(node) - ip4_mask = self.interfaces_manager.ip4_mask - ip6_mask = self.interfaces_manager.ip6_mask - interface_id = canvas_node.next_interface_id() - name = f"eth{interface_id}" - interface = core_pb2.Interface( - id=interface_id, - name=name, - ip4=ip4, - ip4mask=ip4_mask, - ip6=ip6, - ip6mask=ip6_mask, + ip4, ip6 = self.ifaces_manager.get_ips(node) + ip4_mask = self.ifaces_manager.ip4_mask + ip6_mask = self.ifaces_manager.ip6_mask + iface_id = canvas_node.next_iface_id() + name = f"eth{iface_id}" + iface = core_pb2.Interface( + id=iface_id, name=name, ip4=ip4, ip4mask=ip4_mask, ip6=ip6, ip6mask=ip6_mask ) logging.info( "create node(%s) interface(%s) IPv4(%s) IPv6(%s)", node.name, - interface.name, - interface.ip4, - interface.ip6, + iface.name, + iface.ip4, + iface.ip6, ) - return interface + return iface def create_link( self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode @@ -861,34 +856,34 @@ def create_link( dst_node = canvas_dst_node.core_node # determine subnet - self.interfaces_manager.determine_subnets(canvas_src_node, canvas_dst_node) + self.ifaces_manager.determine_subnets(canvas_src_node, canvas_dst_node) - src_interface = None + src_iface = None if NodeUtils.is_container_node(src_node.type): - src_interface = self.create_interface(canvas_src_node) - self.interface_to_edge[(src_node.id, src_interface.id)] = edge.token + src_iface = self.create_iface(canvas_src_node) + self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token - dst_interface = None + dst_iface = None if NodeUtils.is_container_node(dst_node.type): - dst_interface = self.create_interface(canvas_dst_node) - self.interface_to_edge[(dst_node.id, dst_interface.id)] = edge.token + dst_iface = self.create_iface(canvas_dst_node) + self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token link = core_pb2.Link( type=core_pb2.LinkType.WIRED, node1_id=src_node.id, node2_id=dst_node.id, - interface1=src_interface, - interface2=dst_interface, + iface1=src_iface, + iface2=dst_iface, ) # assign after creating link proto, since interfaces are copied - if src_interface: - interface1 = link.interface1 - edge.src_interface = interface1 - canvas_src_node.interfaces[interface1.id] = interface1 - if dst_interface: - interface2 = link.interface2 - edge.dst_interface = interface2 - canvas_dst_node.interfaces[interface2.id] = interface2 + if src_iface: + iface1 = link.iface1 + edge.src_iface = iface1 + canvas_src_node.ifaces[iface1.id] = iface1 + if dst_iface: + iface2 = link.iface2 + edge.dst_iface = iface2 + canvas_dst_node.ifaces[iface2.id] = iface2 edge.set_link(link) self.links[edge.token] = edge logging.info("Add link between %s and %s", src_node.name, dst_node.name) @@ -928,12 +923,12 @@ def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]: continue node_id = canvas_node.core_node.id for key, config in canvas_node.emane_model_configs.items(): - model, interface = key + model, iface_id = key config = {x: config[x].value for x in config} - if interface is None: - interface = -1 + if iface_id is None: + iface_id = -1 config_proto = EmaneModelConfig( - node_id=node_id, interface_id=interface, model=model, config=config + node_id=node_id, iface_id=iface_id, model=model, config=config ) configs.append(config_proto) return configs @@ -1021,19 +1016,19 @@ def get_mobility_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption return dict(config) def get_emane_model_config( - self, node_id: int, model: str, interface: int = None + self, node_id: int, model: str, iface_id: int = None ) -> Dict[str, common_pb2.ConfigOption]: - if interface is None: - interface = -1 + if iface_id is None: + iface_id = -1 response = self.client.get_emane_model_config( - self.session_id, node_id, model, interface + self.session_id, node_id, model, iface_id ) config = response.config logging.debug( "get emane model config: node id: %s, EMANE model: %s, interface: %s, config: %s", node_id, model, - interface, + iface_id, config, ) return dict(config) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 000ebb05c..8f7ca089f 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -56,7 +56,7 @@ def __init__( app: "Application", canvas_node: "CanvasNode", model: str, - interface: int = None, + iface_id: int = None, ): super().__init__( app, f"{canvas_node.core_node.name} {model} Configuration", master=master @@ -64,16 +64,16 @@ def __init__( self.canvas_node = canvas_node self.node = canvas_node.core_node self.model = f"emane_{model}" - self.interface = interface + self.iface_id = iface_id self.config_frame = None self.has_error = False try: self.config = self.canvas_node.emane_model_configs.get( - (self.model, self.interface) + (self.model, self.iface_id) ) if not self.config: self.config = self.app.core.get_emane_model_config( - self.node.id, self.model, self.interface + self.node.id, self.model, self.iface_id ) self.draw() except grpc.RpcError as e: @@ -103,7 +103,7 @@ def draw_buttons(self): def click_apply(self): self.config_frame.parse_config() - key = (self.model, self.interface) + key = (self.model, self.iface_id) self.canvas_node.emane_model_configs[key] = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/ipdialog.py b/daemon/core/gui/dialogs/ipdialog.py index 62f5d0ba7..d31dcdff5 100644 --- a/daemon/core/gui/dialogs/ipdialog.py +++ b/daemon/core/gui/dialogs/ipdialog.py @@ -146,6 +146,6 @@ def click_save(self) -> None: ip_config.ip6 = self.ip6 ip_config.ip4s = ip4s ip_config.ip6s = ip6s - self.app.core.interfaces_manager.update_ips(self.ip4, self.ip6) + self.app.core.ifaces_manager.update_ips(self.ip4, self.ip6) self.app.save_config() self.destroy() diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 9c3fc9871..adf8156f7 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -227,21 +227,21 @@ def click_apply(self): ) link.options.CopyFrom(options) - interface1_id = None - if link.HasField("interface1"): - interface1_id = link.interface1.id - interface2_id = None - if link.HasField("interface2"): - interface2_id = link.interface2.id + iface1_id = None + if link.HasField("iface1"): + iface1_id = link.iface1.id + iface2_id = None + if link.HasField("iface2"): + iface2_id = link.iface2.id if not self.is_symmetric: link.options.unidirectional = True - asym_interface1 = None - if interface1_id: - asym_interface1 = core_pb2.Interface(id=interface1_id) - asym_interface2 = None - if interface2_id: - asym_interface2 = core_pb2.Interface(id=interface2_id) + asym_iface1 = None + if iface1_id: + asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface2 = None + if iface2_id: + asym_iface2 = core_pb2.Interface(id=iface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) down_delay = get_int(self.down_delay) @@ -258,8 +258,8 @@ def click_apply(self): self.edge.asymmetric_link = core_pb2.Link( node1_id=link.node2_id, node2_id=link.node1_id, - interface1=asym_interface1, - interface2=asym_interface2, + iface1=asym_iface1, + iface2=asym_iface2, options=options, ) else: @@ -273,8 +273,8 @@ def click_apply(self): link.node1_id, link.node2_id, link.options, - interface1_id, - interface2_id, + iface1_id, + iface2_id, ) if self.edge.asymmetric_link: self.app.core.client.edit_link( @@ -282,8 +282,8 @@ def click_apply(self): link.node2_id, link.node1_id, self.edge.asymmetric_link.options, - interface1_id, - interface2_id, + iface1_id, + iface2_id, ) self.destroy() diff --git a/daemon/core/gui/dialogs/macdialog.py b/daemon/core/gui/dialogs/macdialog.py index caca9fd08..46414cf98 100644 --- a/daemon/core/gui/dialogs/macdialog.py +++ b/daemon/core/gui/dialogs/macdialog.py @@ -55,7 +55,7 @@ def click_save(self) -> None: if not netaddr.valid_mac(mac): messagebox.showerror("MAC Error", f"{mac} is an invalid mac") else: - self.app.core.interfaces_manager.mac = netaddr.EUI(mac) + self.app.core.ifaces_manager.mac = netaddr.EUI(mac) self.app.guiconfig.mac = mac self.app.save_config() self.destroy() diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 0d46ae063..29ce20101 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -111,7 +111,7 @@ def __init__(self, app: "Application", canvas_node: "CanvasNode"): if self.node.server: server = self.node.server self.server = tk.StringVar(value=server) - self.interfaces = {} + self.ifaces = {} self.draw() def draw(self): @@ -183,53 +183,53 @@ def draw(self): row += 1 if NodeUtils.is_rj45_node(self.node.type): - response = self.app.core.client.get_interfaces() + response = self.app.core.client.get_ifaces() logging.debug("host machine available interfaces: %s", response) - interfaces = ListboxScroll(frame) - interfaces.listbox.config(state=state) - interfaces.grid( + ifaces = ListboxScroll(frame) + ifaces.listbox.config(state=state) + ifaces.grid( row=row, column=0, columnspan=2, sticky="ew", padx=PADX, pady=PADY ) - for inf in sorted(response.interfaces[:]): - interfaces.listbox.insert(tk.END, inf) + for inf in sorted(response.ifaces[:]): + ifaces.listbox.insert(tk.END, inf) row += 1 - interfaces.listbox.bind("<>", self.interface_select) + ifaces.listbox.bind("<>", self.iface_select) # interfaces - if self.canvas_node.interfaces: - self.draw_interfaces() + if self.canvas_node.ifaces: + self.draw_ifaces() self.draw_spacer() self.draw_buttons() - def draw_interfaces(self): + def draw_ifaces(self): notebook = ttk.Notebook(self.top) notebook.grid(sticky="nsew", pady=PADY) self.top.rowconfigure(notebook.grid_info()["row"], weight=1) state = tk.DISABLED if self.app.core.is_runtime() else tk.NORMAL - for interface_id in sorted(self.canvas_node.interfaces): - interface = self.canvas_node.interfaces[interface_id] + for iface_id in sorted(self.canvas_node.ifaces): + iface = self.canvas_node.ifaces[iface_id] tab = ttk.Frame(notebook, padding=FRAME_PAD) tab.grid(sticky="nsew", pady=PADY) tab.columnconfigure(1, weight=1) tab.columnconfigure(2, weight=1) - notebook.add(tab, text=interface.name) + notebook.add(tab, text=iface.name) row = 0 - emane_node = self.canvas_node.has_emane_link(interface.id) + emane_node = self.canvas_node.has_emane_link(iface.id) if emane_node: emane_model = emane_node.emane.split("_")[1] button = ttk.Button( tab, text=f"Configure EMANE {emane_model}", - command=lambda: self.click_emane_config(emane_model, interface.id), + command=lambda: self.click_emane_config(emane_model, iface.id), ) button.grid(row=row, sticky="ew", columnspan=3, pady=PADY) row += 1 label = ttk.Label(tab, text="MAC") label.grid(row=row, column=0, padx=PADX, pady=PADY) - auto_set = not interface.mac + auto_set = not iface.mac mac_state = tk.DISABLED if auto_set else tk.NORMAL is_auto = tk.BooleanVar(value=auto_set) checkbutton = ttk.Checkbutton( @@ -237,7 +237,7 @@ def draw_interfaces(self): ) checkbutton.var = is_auto checkbutton.grid(row=row, column=1, padx=PADX) - mac = tk.StringVar(value=interface.mac) + mac = tk.StringVar(value=iface.mac) entry = ttk.Entry(tab, textvariable=mac, state=mac_state) entry.grid(row=row, column=2, sticky="ew") func = partial(mac_auto, is_auto, entry, mac) @@ -247,8 +247,8 @@ def draw_interfaces(self): label = ttk.Label(tab, text="IPv4") label.grid(row=row, column=0, padx=PADX, pady=PADY) ip4_net = "" - if interface.ip4: - ip4_net = f"{interface.ip4}/{interface.ip4mask}" + if iface.ip4: + ip4_net = f"{iface.ip4}/{iface.ip4mask}" ip4 = tk.StringVar(value=ip4_net) entry = ttk.Entry(tab, textvariable=ip4, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") @@ -257,13 +257,13 @@ def draw_interfaces(self): label = ttk.Label(tab, text="IPv6") label.grid(row=row, column=0, padx=PADX, pady=PADY) ip6_net = "" - if interface.ip6: - ip6_net = f"{interface.ip6}/{interface.ip6mask}" + if iface.ip6: + ip6_net = f"{iface.ip6}/{iface.ip6mask}" ip6 = tk.StringVar(value=ip6_net) entry = ttk.Entry(tab, textvariable=ip6, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") - self.interfaces[interface.id] = InterfaceData(is_auto, mac, ip4, ip6) + self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6) def draw_buttons(self): frame = ttk.Frame(self.top) @@ -277,9 +277,9 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_emane_config(self, emane_model: str, interface_id: int): + def click_emane_config(self, emane_model: str, iface_id: int): dialog = EmaneModelDialog( - self, self.app, self.canvas_node, emane_model, interface_id + self, self.app, self.canvas_node, emane_model, iface_id ) dialog.show() @@ -309,12 +309,12 @@ def click_apply(self): self.canvas_node.image = self.image # update node interface data - for interface in self.canvas_node.interfaces.values(): - data = self.interfaces[interface.id] + for iface in self.canvas_node.ifaces.values(): + data = self.ifaces[iface.id] # validate ip4 ip4_net = data.ip4.get() - if not check_ip4(self, interface.name, ip4_net): + if not check_ip4(self, iface.name, ip4_net): error = True break if ip4_net: @@ -322,12 +322,12 @@ def click_apply(self): ip4mask = int(ip4mask) else: ip4, ip4mask = "", 0 - interface.ip4 = ip4 - interface.ip4mask = ip4mask + iface.ip4 = ip4 + iface.ip4mask = ip4mask # validate ip6 ip6_net = data.ip6.get() - if not check_ip6(self, interface.name, ip6_net): + if not check_ip6(self, iface.name, ip6_net): error = True break if ip6_net: @@ -335,28 +335,28 @@ def click_apply(self): ip6mask = int(ip6mask) else: ip6, ip6mask = "", 0 - interface.ip6 = ip6 - interface.ip6mask = ip6mask + iface.ip6 = ip6 + iface.ip6mask = ip6mask mac = data.mac.get() auto_mac = data.is_auto.get() if not auto_mac and not netaddr.valid_mac(mac): - title = f"MAC Error for {interface.name}" + title = f"MAC Error for {iface.name}" messagebox.showerror(title, "Invalid MAC Address") error = True break elif not auto_mac: mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) - interface.mac = str(mac) + iface.mac = str(mac) # redraw if not error: self.canvas_node.redraw() self.destroy() - def interface_select(self, event: tk.Event): + def iface_select(self, event: tk.Event): listbox = event.widget cur = listbox.curselection() if cur: - interface = listbox.get(cur[0]) - self.name.set(interface) + iface = listbox.get(cur[0]) + self.name.set(iface) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 1d2264ebc..152e1a2f9 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -259,8 +259,8 @@ def __init__( Create an instance of canvas edge object """ super().__init__(canvas, src) - self.src_interface = None - self.dst_interface = None + self.src_iface = None + self.dst_iface = None self.text_src = None self.text_dst = None self.link = None @@ -283,25 +283,25 @@ def set_link(self, link) -> None: self.link = link self.draw_labels() - def interface_label(self, interface: core_pb2.Interface) -> str: + def iface_label(self, iface: core_pb2.Interface) -> str: label = "" - if interface.name and self.canvas.show_interface_names.get(): - label = f"{interface.name}" - if interface.ip4 and self.canvas.show_ip4s.get(): + if iface.name and self.canvas.show_iface_names.get(): + label = f"{iface.name}" + if iface.ip4 and self.canvas.show_ip4s.get(): label = f"{label}\n" if label else "" - label += f"{interface.ip4}/{interface.ip4mask}" - if interface.ip6 and self.canvas.show_ip6s.get(): + label += f"{iface.ip4}/{iface.ip4mask}" + if iface.ip6 and self.canvas.show_ip6s.get(): label = f"{label}\n" if label else "" - label += f"{interface.ip6}/{interface.ip6mask}" + label += f"{iface.ip6}/{iface.ip6mask}" return label def create_node_labels(self) -> Tuple[str, str]: label1 = None - if self.link.HasField("interface1"): - label1 = self.interface_label(self.link.interface1) + if self.link.HasField("iface1"): + label1 = self.iface_label(self.link.iface1) label2 = None - if self.link.HasField("interface2"): - label2 = self.interface_label(self.link.interface2) + if self.link.HasField("iface2"): + label2 = self.iface_label(self.link.iface2) return label1, label2 def draw_labels(self) -> None: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 90dcd9f6b..269e3973f 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -97,7 +97,7 @@ def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"): self.show_link_labels = ShowVar(self, tags.LINK_LABEL, value=True) self.show_grid = ShowVar(self, tags.GRIDLINE, value=True) self.show_annotations = ShowVar(self, tags.ANNOTATION, value=True) - self.show_interface_names = BooleanVar(value=False) + self.show_iface_names = BooleanVar(value=False) self.show_ip4s = BooleanVar(value=True) self.show_ip6s = BooleanVar(value=True) @@ -136,7 +136,7 @@ def reset_and_redraw(self, session: core_pb2.Session): self.show_link_labels.set(True) self.show_grid.set(True) self.show_annotations.set(True) - self.show_interface_names.set(False) + self.show_iface_names.set(False) self.show_ip4s.set(True) self.show_ip6s.set(True) @@ -195,19 +195,19 @@ def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [bool, bool]: return valid_topleft and valid_bottomright def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent): - for interface_throughput in throughputs_event.interface_throughputs: - node_id = interface_throughput.node_id - interface_id = interface_throughput.interface_id - throughput = interface_throughput.throughput - interface_to_edge_id = (node_id, interface_id) - token = self.core.interface_to_edge.get(interface_to_edge_id) + for iface_throughput in throughputs_event.iface_throughputs: + node_id = iface_throughput.node_id + iface_id = iface_throughput.iface_id + throughput = iface_throughput.throughput + iface_to_edge_id = (node_id, iface_id) + token = self.core.iface_to_edge.get(iface_to_edge_id) if not token: continue edge = self.edges.get(token) if edge: edge.set_throughput(throughput) else: - del self.core.interface_to_edge[interface_to_edge_id] + del self.core.iface_to_edge[iface_to_edge_id] def draw_grid(self): """ @@ -321,18 +321,16 @@ def draw_session(self, session: core_pb2.Session): canvas_node2.edges.add(edge) self.edges[edge.token] = edge self.core.links[edge.token] = edge - if link.HasField("interface1"): - interface1 = link.interface1 - self.core.interface_to_edge[(node1.id, interface1.id)] = token - canvas_node1.interfaces[interface1.id] = interface1 - edge.src_interface = interface1 - if link.HasField("interface2"): - interface2 = link.interface2 - self.core.interface_to_edge[ - (node2.id, interface2.id) - ] = edge.token - canvas_node2.interfaces[interface2.id] = interface2 - edge.dst_interface = interface2 + if link.HasField("iface1"): + iface1 = link.iface1 + self.core.iface_to_edge[(node1.id, iface1.id)] = token + canvas_node1.ifaces[iface1.id] = iface1 + edge.src_iface = iface1 + if link.HasField("iface2"): + iface2 = link.iface2 + self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token + canvas_node2.ifaces[iface2.id] = iface2 + edge.dst_iface = iface2 elif link.options.unidirectional: edge = self.edges[token] edge.asymmetric_link = link @@ -513,14 +511,14 @@ def delete_selected_objects(self) -> None: edge.delete() # update node connected to edge being deleted other_id = edge.src - other_interface = edge.src_interface + other_iface = edge.src_iface if edge.src == object_id: other_id = edge.dst - other_interface = edge.dst_interface + other_iface = edge.dst_iface other_node = self.nodes[other_id] other_node.edges.remove(edge) - if other_interface: - del other_node.interfaces[other_interface.id] + if other_iface: + del other_node.ifaces[other_iface.id] if is_wireless: other_node.delete_antenna() @@ -538,12 +536,12 @@ def delete_edge(self, edge: CanvasEdge): del self.edges[edge.token] src_node = self.nodes[edge.src] src_node.edges.discard(edge) - if edge.src_interface: - del src_node.interfaces[edge.src_interface.id] + if edge.src_iface: + del src_node.ifaces[edge.src_iface.id] dst_node = self.nodes[edge.dst] dst_node.edges.discard(edge) - if edge.dst_interface: - del dst_node.interfaces[edge.dst_interface.id] + if edge.dst_iface: + del dst_node.ifaces[edge.dst_iface.id] src_wireless = NodeUtils.is_wireless_node(src_node.core_node.type) if src_wireless: dst_node.delete_antenna() @@ -963,26 +961,26 @@ def paste(self): copy_link = copy_edge.link options = edge.link.options copy_link.options.CopyFrom(options) - interface1_id = None - if copy_link.HasField("interface1"): - interface1_id = copy_link.interface1.id - interface2_id = None - if copy_link.HasField("interface2"): - interface2_id = copy_link.interface2.id + iface1_id = None + if copy_link.HasField("iface1"): + iface1_id = copy_link.iface1.id + iface2_id = None + if copy_link.HasField("iface2"): + iface2_id = copy_link.iface2.id if not options.unidirectional: copy_edge.asymmetric_link = None else: - asym_interface1 = None - if interface1_id: - asym_interface1 = core_pb2.Interface(id=interface1_id) - asym_interface2 = None - if interface2_id: - asym_interface2 = core_pb2.Interface(id=interface2_id) + asym_iface1 = None + if iface1_id: + asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface2 = None + if iface2_id: + asym_iface2 = core_pb2.Interface(id=iface2_id) copy_edge.asymmetric_link = core_pb2.Link( node1_id=copy_link.node2_id, node2_id=copy_link.node1_id, - interface1=asym_interface1, - interface2=asym_interface2, + iface1=asym_iface1, + iface2=asym_iface2, options=edge.asymmetric_link.options, ) self.itemconfig( diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 8ad3f02ab..3ba4b3f7c 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -55,7 +55,7 @@ def __init__( ) self.tooltip = CanvasTooltip(self.canvas) self.edges = set() - self.interfaces = {} + self.ifaces = {} self.wireless_edges = set() self.antennas = [] self.antenna_images = {} @@ -70,9 +70,9 @@ def __init__( self.context = tk.Menu(self.canvas) themes.style_menu(self.context) - def next_interface_id(self) -> int: + def next_iface_id(self) -> int: i = 0 - while i in self.interfaces: + while i in self.ifaces: i += 1 return i @@ -300,16 +300,16 @@ def show_config_services(self): dialog = NodeConfigServiceDialog(self.app, self) dialog.show() - def has_emane_link(self, interface_id: int) -> core_pb2.Node: + def has_emane_link(self, iface_id: int) -> core_pb2.Node: result = None for edge in self.edges: if self.id == edge.src: other_id = edge.dst - edge_interface_id = edge.src_interface.id + edge_iface_id = edge.src_iface.id else: other_id = edge.src - edge_interface_id = edge.dst_interface.id - if edge_interface_id != interface_id: + edge_iface_id = edge.dst_iface.id + if edge_iface_id != iface_id: continue other_node = self.canvas.nodes[other_id] if other_node.core_node.type == NodeType.EMANE: diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 34270f56b..14cba0242 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -12,10 +12,10 @@ from core.gui.graph.node import CanvasNode -def get_index(interface: "core_pb2.Interface") -> Optional[int]: - if not interface.ip4: +def get_index(iface: "core_pb2.Interface") -> Optional[int]: + if not iface.ip4: return None - net = netaddr.IPNetwork(f"{interface.ip4}/{interface.ip4mask}") + net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4mask}") ip_value = net.value cidr_value = net.cidr.value return ip_value - cidr_value @@ -89,43 +89,43 @@ def removed(self, links: List["core_pb2.Link"]) -> None: remaining_subnets = set() for edge in self.app.core.links.values(): link = edge.link - if link.HasField("interface1"): - subnets = self.get_subnets(link.interface1) + if link.HasField("iface1"): + subnets = self.get_subnets(link.iface1) remaining_subnets.add(subnets) - if link.HasField("interface2"): - subnets = self.get_subnets(link.interface2) + if link.HasField("iface2"): + subnets = self.get_subnets(link.iface2) remaining_subnets.add(subnets) # remove all subnets from used subnets when no longer present # or remove used indexes from subnet - interfaces = [] + ifaces = [] for link in links: - if link.HasField("interface1"): - interfaces.append(link.interface1) - if link.HasField("interface2"): - interfaces.append(link.interface2) - for interface in interfaces: - subnets = self.get_subnets(interface) + if link.HasField("iface1"): + ifaces.append(link.iface1) + if link.HasField("iface2"): + ifaces.append(link.iface2) + for iface in ifaces: + subnets = self.get_subnets(iface) if subnets not in remaining_subnets: self.used_subnets.pop(subnets.key(), None) else: - index = get_index(interface) + index = get_index(iface) if index is not None: subnets.used_indexes.discard(index) self.current_subnets = None def joined(self, links: List["core_pb2.Link"]) -> None: - interfaces = [] + ifaces = [] for link in links: - if link.HasField("interface1"): - interfaces.append(link.interface1) - if link.HasField("interface2"): - interfaces.append(link.interface2) + if link.HasField("iface1"): + ifaces.append(link.iface1) + if link.HasField("iface2"): + ifaces.append(link.iface2) # add to used subnets and mark used indexes - for interface in interfaces: - subnets = self.get_subnets(interface) - index = get_index(interface) + for iface in ifaces: + subnets = self.get_subnets(iface) + index = get_index(iface) if index is None: continue subnets.used_indexes.add(index) @@ -150,13 +150,13 @@ def get_ips(self, node: "core_pb2.Node") -> [str, str]: ip6 = self.current_subnets.ip6[index] return str(ip4), str(ip6) - def get_subnets(self, interface: "core_pb2.Interface") -> Subnets: + def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: ip4_subnet = self.ip4_subnets - if interface.ip4: - ip4_subnet = IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr + if iface.ip4: + ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4mask}").cidr ip6_subnet = self.ip6_subnets - if interface.ip6: - ip6_subnet = IPNetwork(f"{interface.ip6}/{interface.ip6mask}").cidr + if iface.ip6: + ip6_subnet = IPNetwork(f"{iface.ip6}/{iface.ip6mask}").cidr subnets = Subnets(ip4_subnet, ip6_subnet) return self.used_subnets.get(subnets.key(), subnets) @@ -196,16 +196,16 @@ def find_subnets( for edge in canvas_node.edges: src_node = canvas.nodes[edge.src] dst_node = canvas.nodes[edge.dst] - interface = edge.src_interface + iface = edge.src_iface check_node = src_node if src_node == canvas_node: - interface = edge.dst_interface + iface = edge.dst_iface check_node = dst_node if check_node.core_node.id in visited: continue visited.add(check_node.core_node.id) - if interface: - subnets = self.get_subnets(interface) + if iface: + subnets = self.get_subnets(iface) else: subnets = self.find_subnets(check_node, visited) if subnets: diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 62a9ceaef..cf4216d8d 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -139,7 +139,7 @@ def draw_view_menu(self) -> None: menu.add_checkbutton( label="Interface Names", command=self.click_edge_label_change, - variable=self.canvas.show_interface_names, + variable=self.canvas.show_iface_names, ) menu.add_checkbutton( label="IPv4 Addresses", diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 43996ba33..d56c40aa6 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -178,7 +178,7 @@ def sendevent(self, model: "WayPointMobility") -> None: self.session.broadcast_event(event_data) def updatewlans( - self, moved: List[CoreNode], moved_netifs: List[CoreInterface] + self, moved: List[CoreNode], moved_ifaces: List[CoreInterface] ) -> None: """ A mobility script has caused nodes in the 'moved' list to move. @@ -186,7 +186,7 @@ def updatewlans( were to recalculate for each individual node movement. :param moved: moved nodes - :param moved_netifs: moved network interfaces + :param moved_ifaces: moved network interfaces :return: nothing """ for node_id in self.nodes(): @@ -195,7 +195,7 @@ def updatewlans( except CoreError: continue if node.model: - node.model.update(moved, moved_netifs) + node.model.update(moved, moved_ifaces) class WirelessModel(ConfigurableOptions): @@ -228,12 +228,12 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat """ return [] - def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: """ Update this wireless model. :param moved: moved nodes - :param moved_netifs: moved network interfaces + :param moved_ifaces: moved network interfaces :return: nothing """ raise NotImplementedError @@ -301,8 +301,8 @@ def __init__(self, session: "Session", _id: int) -> None: super().__init__(session, _id) self.session: "Session" = session self.wlan: WlanNode = session.get_node(_id, WlanNode) - self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {} - self._netifslock: threading.Lock = threading.Lock() + self.iface_to_pos: Dict[CoreInterface, Tuple[float, float, float]] = {} + self.iface_lock: threading.Lock = threading.Lock() self.range: int = 0 self.bw: Optional[int] = None self.delay: Optional[int] = None @@ -333,48 +333,48 @@ def setlinkparams(self) -> None: Apply link parameters to all interfaces. This is invoked from WlanNode.setmodel() after the position callback has been set. """ - with self._netifslock: - for netif in self._netifs: + with self.iface_lock: + for iface in self.iface_to_pos: options = LinkOptions( bandwidth=self.bw, delay=self.delay, loss=self.loss, jitter=self.jitter, ) - self.wlan.linkconfig(netif, options) + self.wlan.linkconfig(iface, options) - def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]: + def get_position(self, iface: CoreInterface) -> Tuple[float, float, float]: """ Retrieve network interface position. - :param netif: network interface position to retrieve + :param iface: network interface position to retrieve :return: network interface position """ - with self._netifslock: - return self._netifs[netif] + with self.iface_lock: + return self.iface_to_pos[iface] - def set_position(self, netif: CoreInterface) -> None: + def set_position(self, iface: CoreInterface) -> None: """ A node has moved; given an interface, a new (x,y,z) position has been set; calculate the new distance between other nodes and link or unlink node pairs based on the configured range. - :param netif: network interface to set position for + :param iface: network interface to set position for :return: nothing """ - x, y, z = netif.node.position.get() - self._netifslock.acquire() - self._netifs[netif] = (x, y, z) + x, y, z = iface.node.position.get() + self.iface_lock.acquire() + self.iface_to_pos[iface] = (x, y, z) if x is None or y is None: - self._netifslock.release() + self.iface_lock.release() return - for netif2 in self._netifs: - self.calclink(netif, netif2) - self._netifslock.release() + for iface2 in self.iface_to_pos: + self.calclink(iface, iface2) + self.iface_lock.release() position_callback = set_position - def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: """ Node positions have changed without recalc. Update positions from node.position, then re-calculate links for those that have moved. @@ -382,37 +382,37 @@ def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> No one of the nodes has moved. :param moved: moved nodes - :param moved_netifs: moved network interfaces + :param moved_ifaces: moved network interfaces :return: nothing """ - with self._netifslock: - while len(moved_netifs): - netif = moved_netifs.pop() - nx, ny, nz = netif.node.getposition() - if netif in self._netifs: - self._netifs[netif] = (nx, ny, nz) - for netif2 in self._netifs: - if netif2 in moved_netifs: + with self.iface_lock: + while len(moved_ifaces): + iface = moved_ifaces.pop() + nx, ny, nz = iface.node.getposition() + if iface in self.iface_to_pos: + self.iface_to_pos[iface] = (nx, ny, nz) + for iface2 in self.iface_to_pos: + if iface2 in moved_ifaces: continue - self.calclink(netif, netif2) + self.calclink(iface, iface2) - def calclink(self, netif: CoreInterface, netif2: CoreInterface) -> None: + def calclink(self, iface: CoreInterface, iface2: CoreInterface) -> None: """ Helper used by set_position() and update() to calculate distance between two interfaces and perform linking/unlinking. Sends link/unlink messages and updates the WlanNode's linked dict. - :param netif: interface one - :param netif2: interface two + :param iface: interface one + :param iface2: interface two :return: nothing """ - if netif == netif2: + if iface == iface2: return try: - x, y, z = self._netifs[netif] - x2, y2, z2 = self._netifs[netif2] + x, y, z = self.iface_to_pos[iface] + x2, y2, z2 = self.iface_to_pos[iface2] if x2 is None or y2 is None: return @@ -420,8 +420,8 @@ def calclink(self, netif: CoreInterface, netif2: CoreInterface) -> None: d = self.calcdistance((x, y, z), (x2, y2, z2)) # ordering is important, to keep the wlan._linked dict organized - a = min(netif, netif2) - b = max(netif, netif2) + a = min(iface, iface2) + b = max(iface, iface2) with self.wlan._linked_lock: linked = self.wlan.linked(a, b) @@ -475,42 +475,39 @@ def update_config(self, config: Dict[str, str]) -> None: self.setlinkparams() def create_link_data( - self, - interface1: CoreInterface, - interface2: CoreInterface, - message_type: MessageFlags, + self, iface1: CoreInterface, iface2: CoreInterface, message_type: MessageFlags ) -> LinkData: """ Create a wireless link/unlink data message. - :param interface1: interface one - :param interface2: interface two + :param iface1: interface one + :param iface2: interface two :param message_type: link message type :return: link data """ color = self.session.get_link_color(self.wlan.id) return LinkData( message_type=message_type, - node1_id=interface1.node.id, - node2_id=interface2.node.id, + node1_id=iface1.node.id, + node2_id=iface2.node.id, network_id=self.wlan.id, link_type=LinkTypes.WIRELESS, color=color, ) def sendlinkmsg( - self, netif: CoreInterface, netif2: CoreInterface, unlink: bool = False + self, iface: CoreInterface, iface2: CoreInterface, unlink: bool = False ) -> None: """ Send a wireless link/unlink API message to the GUI. - :param netif: interface one - :param netif2: interface two + :param iface: interface one + :param iface2: interface two :param unlink: unlink or not :return: nothing """ message_type = MessageFlags.DELETE if unlink else MessageFlags.ADD - link_data = self.create_link_data(netif, netif2, message_type) + link_data = self.create_link_data(iface, iface2, message_type) self.session.broadcast_link(link_data) def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: @@ -643,17 +640,17 @@ def runround(self) -> None: return return self.run() - # only move netifs attached to self.wlan, or all nodenum in script? + # only move interfaces attached to self.wlan, or all nodenum in script? moved = [] - moved_netifs = [] - for netif in self.wlan.netifs(): - node = netif.node + moved_ifaces = [] + for iface in self.wlan.get_ifaces(): + node = iface.node if self.movenode(node, dt): moved.append(node) - moved_netifs.append(netif) + moved_ifaces.append(iface) # calculate all ranges after moving nodes; this saves calculations - self.session.mobility.updatewlans(moved, moved_netifs) + self.session.mobility.updatewlans(moved, moved_ifaces) # TODO: check session state self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround) @@ -725,16 +722,16 @@ def movenodesinitial(self) -> None: :return: nothing """ moved = [] - moved_netifs = [] - for netif in self.wlan.netifs(): - node = netif.node + moved_ifaces = [] + for iface in self.wlan.get_ifaces(): + node = iface.node if node.id not in self.initial: continue x, y, z = self.initial[node.id].coords self.setnodeposition(node, x, y, z) moved.append(node) - moved_netifs.append(netif) - self.session.mobility.updatewlans(moved, moved_netifs) + moved_ifaces.append(iface) + self.session.mobility.updatewlans(moved, moved_ifaces) def addwaypoint( self, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 6c7ebcf09..40aae6a80 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -68,8 +68,8 @@ def __init__( self.server: "DistributedServer" = server self.type: Optional[str] = None self.services: CoreServices = [] - self._netif: Dict[int, CoreInterface] = {} - self.ifindex: int = 0 + self.ifaces: Dict[int, CoreInterface] = {} + self.iface_id: int = 0 self.canvas: Optional[int] = None self.icon: Optional[str] = None self.opaque: Optional[str] = None @@ -139,58 +139,50 @@ def getposition(self) -> Tuple[float, float, float]: """ return self.position.get() - def ifname(self, ifindex: int) -> str: - """ - Retrieve interface name for index. - - :param ifindex: interface index - :return: interface name - """ - return self._netif[ifindex].name + def get_iface(self, iface_id: int) -> CoreInterface: + if iface_id not in self.ifaces: + raise CoreError(f"node({self.name}) does not have interface({iface_id})") + return self.ifaces[iface_id] - def netifs(self, sort: bool = False) -> List[CoreInterface]: + def get_ifaces(self, control: bool = True) -> List[CoreInterface]: """ - Retrieve network interfaces, sorted if desired. + Retrieve sorted list of interfaces, optionally do not include control + interfaces. - :param sort: boolean used to determine if interfaces should be sorted - :return: network interfaces + :param control: False to exclude control interfaces, included otherwise + :return: list of interfaces """ - if sort: - return [self._netif[x] for x in sorted(self._netif)] - else: - return list(self._netif.values()) - - def numnetif(self) -> int: - """ - Return the attached interface count. - - :return: number of network interfaces - """ - return len(self._netif) + ifaces = [] + for iface_id in sorted(self.ifaces): + iface = self.ifaces[iface_id] + if not control and getattr(iface, "control", False): + continue + ifaces.append(iface) + return ifaces - def getifindex(self, netif: CoreInterface) -> int: + def get_iface_id(self, iface: CoreInterface) -> int: """ - Retrieve index for an interface. + Retrieve id for an interface. - :param netif: interface to get index for + :param iface: interface to get id for :return: interface index if found, -1 otherwise """ - for ifindex in self._netif: - if self._netif[ifindex] is netif: - return ifindex - return -1 + for iface_id, local_iface in self.ifaces.items(): + if local_iface is iface: + return iface_id + raise CoreError(f"node({self.name}) does not have interface({iface.name})") - def newifindex(self) -> int: + def next_iface_id(self) -> int: """ Create a new interface index. :return: interface index """ - while self.ifindex in self._netif: - self.ifindex += 1 - ifindex = self.ifindex - self.ifindex += 1 - return ifindex + while self.iface_id in self.ifaces: + self.iface_id += 1 + iface_id = self.iface_id + self.iface_id += 1 + return iface_id def data( self, message_type: MessageFlags = MessageFlags.NONE, source: str = None @@ -325,14 +317,14 @@ def termcmdstring(self, sh: str) -> str: raise NotImplementedError @abc.abstractmethod - def newnetif( - self, net: "CoreNetworkBase", interface_data: InterfaceData + def new_iface( + self, net: "CoreNetworkBase", iface_data: InterfaceData ) -> CoreInterface: """ - Create a new network interface. + Create a new interface. :param net: network to associate with - :param interface_data: interface data for new interface + :param iface_data: interface data for new interface :return: interface index """ raise NotImplementedError @@ -399,67 +391,53 @@ def rmnodedir(self) -> None: if self.tmpnodedir: self.host_cmd(f"rm -rf {self.nodedir}") - def addnetif(self, netif: CoreInterface, ifindex: int) -> None: + def add_iface(self, iface: CoreInterface, iface_id: int) -> None: """ Add network interface to node and set the network interface index if successful. - :param netif: network interface to add - :param ifindex: interface index + :param iface: network interface to add + :param iface_id: interface id :return: nothing """ - if ifindex in self._netif: - raise ValueError(f"ifindex {ifindex} already exists") - self._netif[ifindex] = netif - netif.netindex = ifindex + if iface_id in self.ifaces: + raise CoreError(f"interface({iface_id}) already exists") + self.ifaces[iface_id] = iface + iface.node_id = iface_id - def delnetif(self, ifindex: int) -> None: + def delete_iface(self, iface_id: int) -> None: """ Delete a network interface - :param ifindex: interface index to delete + :param iface_id: interface index to delete :return: nothing """ - if ifindex not in self._netif: - raise CoreError(f"node({self.name}) ifindex({ifindex}) does not exist") - netif = self._netif.pop(ifindex) - logging.info("node(%s) removing interface(%s)", self.name, netif.name) - netif.detachnet() - netif.shutdown() - - def netif(self, ifindex: int) -> Optional[CoreInterface]: - """ - Retrieve network interface. + if iface_id not in self.ifaces: + raise CoreError(f"node({self.name}) interface({iface_id}) does not exist") + iface = self.ifaces.pop(iface_id) + logging.info("node(%s) removing interface(%s)", self.name, iface.name) + iface.detachnet() + iface.shutdown() - :param ifindex: index of interface to retrieve - :return: network interface, or None if not found - """ - if ifindex in self._netif: - return self._netif[ifindex] - else: - return None - - def attachnet(self, ifindex: int, net: "CoreNetworkBase") -> None: + def attachnet(self, iface_id: int, net: "CoreNetworkBase") -> None: """ Attach a network. - :param ifindex: interface of index to attach + :param iface_id: interface of index to attach :param net: network to attach :return: nothing """ - if ifindex not in self._netif: - raise ValueError(f"ifindex {ifindex} does not exist") - self._netif[ifindex].attachnet(net) + iface = self.get_iface(iface_id) + iface.attachnet(net) - def detachnet(self, ifindex: int) -> None: + def detachnet(self, iface_id: int) -> None: """ Detach network interface. - :param ifindex: interface index to detach + :param iface_id: interface id to detach :return: nothing """ - if ifindex not in self._netif: - raise ValueError(f"ifindex {ifindex} does not exist") - self._netif[ifindex].detachnet() + iface = self.get_iface(iface_id) + iface.detachnet() def setposition(self, x: float = None, y: float = None, z: float = None) -> None: """ @@ -472,8 +450,8 @@ def setposition(self, x: float = None, y: float = None, z: float = None) -> None """ changed = super().setposition(x, y, z) if changed: - for netif in self.netifs(sort=True): - netif.setposition() + for iface in self.get_ifaces(): + iface.setposition() def commonnets( self, node: "CoreNodeBase", want_ctrl: bool = False @@ -488,12 +466,10 @@ def commonnets( :return: tuples of common networks """ common = [] - for netif1 in self.netifs(): - if not want_ctrl and hasattr(netif1, "control"): - continue - for netif2 in node.netifs(): - if netif1.net == netif2.net: - common.append((netif1.net, netif1, netif2)) + for iface1 in self.get_ifaces(control=want_ctrl): + for iface2 in node.get_ifaces(): + if iface1.net == iface2.net: + common.append((iface1.net, iface1, iface2)) return common @@ -620,8 +596,8 @@ def shutdown(self) -> None: self._mounts = [] # shutdown all interfaces - for netif in self.netifs(): - netif.shutdown() + for iface in self.get_ifaces(): + iface.shutdown() # kill node process if present try: @@ -636,7 +612,7 @@ def shutdown(self) -> None: logging.exception("error removing node directory") # clear interface data, close client, and mark self and not up - self._netif.clear() + self.ifaces.clear() self.client.close() self.up = False except OSError: @@ -704,36 +680,36 @@ def mount(self, source: str, target: str) -> None: self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}") self._mounts.append((source, target)) - def newifindex(self) -> int: + def next_iface_id(self) -> int: """ Retrieve a new interface index. :return: new interface index """ with self.lock: - return super().newifindex() + return super().next_iface_id() - def newveth(self, ifindex: int = None, ifname: str = None) -> int: + def newveth(self, iface_id: int = None, ifname: str = None) -> int: """ Create a new interface. - :param ifindex: index for the new interface + :param iface_id: id for the new interface :param ifname: name for the new interface :return: nothing """ with self.lock: - if ifindex is None: - ifindex = self.newifindex() + if iface_id is None: + iface_id = self.next_iface_id() if ifname is None: - ifname = f"eth{ifindex}" + ifname = f"eth{iface_id}" sessionid = self.session.short_session_id() try: - suffix = f"{self.id:x}.{ifindex}.{sessionid}" + suffix = f"{self.id:x}.{iface_id}.{sessionid}" except TypeError: - suffix = f"{self.id}.{ifindex}.{sessionid}" + suffix = f"{self.id}.{iface_id}.{sessionid}" localname = f"veth{suffix}" if len(localname) >= 16: @@ -765,140 +741,138 @@ def newveth(self, ifindex: int = None, ifname: str = None) -> int: try: # add network interface to the node. If unsuccessful, destroy the # network interface and raise exception. - self.addnetif(veth, ifindex) + self.add_iface(veth, iface_id) except ValueError as e: veth.shutdown() del veth raise e - return ifindex + return iface_id - def newtuntap(self, ifindex: int = None, ifname: str = None) -> int: + def newtuntap(self, iface_id: int = None, ifname: str = None) -> int: """ Create a new tunnel tap. - :param ifindex: interface index + :param iface_id: interface id :param ifname: interface name :return: interface index """ with self.lock: - if ifindex is None: - ifindex = self.newifindex() + if iface_id is None: + iface_id = self.next_iface_id() if ifname is None: - ifname = f"eth{ifindex}" + ifname = f"eth{iface_id}" sessionid = self.session.short_session_id() - localname = f"tap{self.id}.{ifindex}.{sessionid}" + localname = f"tap{self.id}.{iface_id}.{sessionid}" name = ifname tuntap = TunTap(self.session, self, name, localname, start=self.up) try: - self.addnetif(tuntap, ifindex) + self.add_iface(tuntap, iface_id) except ValueError as e: tuntap.shutdown() del tuntap raise e - return ifindex + return iface_id - def sethwaddr(self, ifindex: int, addr: str) -> None: + def sethwaddr(self, iface_id: int, addr: str) -> None: """ - Set hardware addres for an interface. + Set hardware address for an interface. - :param ifindex: index of interface to set hardware address for + :param iface_id: id of interface to set hardware address for :param addr: hardware address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ addr = utils.validate_mac(addr) - interface = self._netif[ifindex] - interface.sethwaddr(addr) + iface = self.get_iface(iface_id) + iface.sethwaddr(addr) if self.up: - self.node_net_client.device_mac(interface.name, addr) + self.node_net_client.device_mac(iface.name, addr) - def addaddr(self, ifindex: int, addr: str) -> None: + def addaddr(self, iface_id: int, addr: str) -> None: """ Add interface address. - :param ifindex: index of interface to add address to + :param iface_id: id of interface to add address to :param addr: address to add to interface :return: nothing """ addr = utils.validate_ip(addr) - interface = self._netif[ifindex] - interface.addaddr(addr) + iface = self.get_iface(iface_id) + iface.addaddr(addr) if self.up: # ipv4 check broadcast = None if netaddr.valid_ipv4(addr): broadcast = "+" - self.node_net_client.create_address(interface.name, addr, broadcast) + self.node_net_client.create_address(iface.name, addr, broadcast) - def deladdr(self, ifindex: int, addr: str) -> None: + def deladdr(self, iface_id: int, addr: str) -> None: """ Delete address from an interface. - :param ifindex: index of interface to delete address from + :param iface_id: id of interface to delete address from :param addr: address to delete from interface :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - interface = self._netif[ifindex] - + iface = self.get_iface(iface_id) try: - interface.deladdr(addr) + iface.deladdr(addr) except ValueError: logging.exception("trying to delete unknown address: %s", addr) - if self.up: - self.node_net_client.delete_address(interface.name, addr) + self.node_net_client.delete_address(iface.name, addr) - def ifup(self, ifindex: int) -> None: + def ifup(self, iface_id: int) -> None: """ Bring an interface up. - :param ifindex: index of interface to bring up + :param iface_id: index of interface to bring up :return: nothing """ if self.up: - interface_name = self.ifname(ifindex) - self.node_net_client.device_up(interface_name) + iface = self.get_iface(iface_id) + self.node_net_client.device_up(iface.name) - def newnetif( - self, net: "CoreNetworkBase", interface_data: InterfaceData + def new_iface( + self, net: "CoreNetworkBase", iface_data: InterfaceData ) -> CoreInterface: """ Create a new network interface. :param net: network to associate with - :param interface_data: interface data for new interface + :param iface_data: interface data for new interface :return: interface index """ - addresses = interface_data.get_addresses() + addresses = iface_data.get_addresses() with self.lock: # TODO: emane specific code if net.is_emane is True: - ifindex = self.newtuntap(interface_data.id, interface_data.name) + iface_id = self.newtuntap(iface_data.id, iface_data.name) # TUN/TAP is not ready for addressing yet; the device may # take some time to appear, and installing it into a # namespace after it has been bound removes addressing; # save addresses with the interface now - self.attachnet(ifindex, net) - netif = self.netif(ifindex) - netif.sethwaddr(interface_data.mac) + self.attachnet(iface_id, net) + iface = self.get_iface(iface_id) + iface.sethwaddr(iface_data.mac) for address in addresses: - netif.addaddr(address) + iface.addaddr(address) else: - ifindex = self.newveth(interface_data.id, interface_data.name) - self.attachnet(ifindex, net) - if interface_data.mac: - self.sethwaddr(ifindex, interface_data.mac) + iface_id = self.newveth(iface_data.id, iface_data.name) + self.attachnet(iface_id, net) + if iface_data.mac: + self.sethwaddr(iface_id, iface_data.mac) for address in addresses: - self.addaddr(ifindex, address) - self.ifup(ifindex) - netif = self.netif(ifindex) - return netif + self.addaddr(iface_id, address) + self.ifup(iface_id) + iface = self.get_iface(iface_id) + return iface def addfile(self, srcname: str, filename: str) -> None: """ @@ -1041,54 +1015,54 @@ def linknet(self, net: "CoreNetworkBase") -> CoreInterface: @abc.abstractmethod def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. - :param netif: interface one + :param iface: interface one :param options: options for configuring link - :param netif2: interface two + :param iface2: interface two :return: nothing """ raise NotImplementedError - def getlinknetif(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: + def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: """ - Return the interface of that links this net with another net. + Return the interface that links this net with another net. :param net: interface to get link for :return: interface the provided network is linked to """ - for netif in self.netifs(): - if netif.othernet == net: - return netif + for iface in self.get_ifaces(): + if iface.othernet == net: + return iface return None - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach network interface. - :param netif: network interface to attach + :param iface: network interface to attach :return: nothing """ - i = self.newifindex() - self._netif[i] = netif - netif.netifi = i + i = self.next_iface_id() + self.ifaces[i] = iface + iface.net_id = i with self._linked_lock: - self._linked[netif] = {} + self._linked[iface] = {} - def detach(self, netif: CoreInterface) -> None: + def detach(self, iface: CoreInterface) -> None: """ Detach network interface. - :param netif: network interface to detach + :param iface: network interface to detach :return: nothing """ - del self._netif[netif.netifi] - netif.netifi = None + del self.ifaces[iface.net_id] + iface.net_id = None with self._linked_lock: - del self._linked[netif] + del self._linked[iface] def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ @@ -1102,41 +1076,39 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat # build a link message from this network node to each node having a # connected interface - for netif in self.netifs(sort=True): - if not hasattr(netif, "node"): - continue + for iface in self.get_ifaces(): uni = False - linked_node = netif.node + linked_node = iface.node if linked_node is None: # two layer-2 switches/hubs linked together via linknet() - if not netif.othernet: + if not iface.othernet: continue - linked_node = netif.othernet + linked_node = iface.othernet if linked_node.id == self.id: continue - netif.swapparams("_params_up") - upstream_params = netif.getparams() - netif.swapparams("_params_up") - if netif.getparams() != upstream_params: + iface.swapparams("_params_up") + upstream_params = iface.getparams() + iface.swapparams("_params_up") + if iface.getparams() != upstream_params: uni = True unidirectional = 0 if uni: unidirectional = 1 - interface2_ip4 = None - interface2_ip4_mask = None - interface2_ip6 = None - interface2_ip6_mask = None - for address in netif.addrlist: + iface2_ip4 = None + iface2_ip4_mask = None + iface2_ip6 = None + iface2_ip6_mask = None + for address in iface.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - interface2_ip4 = ip - interface2_ip4_mask = mask + iface2_ip4 = ip + iface2_ip4_mask = mask else: - interface2_ip6 = ip - interface2_ip6_mask = mask + iface2_ip6 = ip + iface2_ip6_mask = mask link_data = LinkData( message_type=flags, @@ -1144,42 +1116,38 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat node2_id=linked_node.id, link_type=self.linktype, unidirectional=unidirectional, - interface2_id=linked_node.getifindex(netif), - interface2_name=netif.name, - interface2_mac=netif.hwaddr, - interface2_ip4=interface2_ip4, - interface2_ip4_mask=interface2_ip4_mask, - interface2_ip6=interface2_ip6, - interface2_ip6_mask=interface2_ip6_mask, - delay=netif.getparam("delay"), - bandwidth=netif.getparam("bw"), - dup=netif.getparam("duplicate"), - jitter=netif.getparam("jitter"), - loss=netif.getparam("loss"), + iface2_id=linked_node.get_iface_id(iface), + iface2_name=iface.name, + iface2_mac=iface.hwaddr, + iface2_ip4=iface2_ip4, + iface2_ip4_mask=iface2_ip4_mask, + iface2_ip6=iface2_ip6, + iface2_ip6_mask=iface2_ip6_mask, + delay=iface.getparam("delay"), + bandwidth=iface.getparam("bw"), + dup=iface.getparam("duplicate"), + jitter=iface.getparam("jitter"), + loss=iface.getparam("loss"), ) - all_links.append(link_data) if not uni: continue - - netif.swapparams("_params_up") + iface.swapparams("_params_up") link_data = LinkData( message_type=MessageFlags.NONE, node1_id=linked_node.id, node2_id=self.id, link_type=self.linktype, unidirectional=1, - delay=netif.getparam("delay"), - bandwidth=netif.getparam("bw"), - dup=netif.getparam("duplicate"), - jitter=netif.getparam("jitter"), - loss=netif.getparam("loss"), + delay=iface.getparam("delay"), + bandwidth=iface.getparam("bw"), + dup=iface.getparam("duplicate"), + jitter=iface.getparam("jitter"), + loss=iface.getparam("loss"), ) - netif.swapparams("_params_up") - + iface.swapparams("_params_up") all_links.append(link_data) - return all_links diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index e911db74c..1ef814ee3 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -141,7 +141,7 @@ def shutdown(self) -> None: return with self.lock: - self._netif.clear() + self.ifaces.clear() self.client.stop_container() self.up = False diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index e73e29896..dc16517f5 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -57,11 +57,11 @@ def __init__( self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE self.transport_type: Optional[TransportType] = None - # node interface index - self.netindex: Optional[int] = None - # net interface index - self.netifi: Optional[int] = None - # index used to find flow data + # id of interface for node + self.node_id: Optional[int] = None + # id of interface for network + self.net_id: Optional[int] = None + # id used to find flow data self.flow_id: Optional[int] = None self.server: Optional["DistributedServer"] = server use_ovs = session.options.get_config("ovs") == "True" @@ -284,19 +284,16 @@ def shutdown(self) -> None: """ if not self.up: return - if self.node: try: self.node.node_net_client.device_flush(self.name) except CoreCommandError: logging.exception("error shutting down interface") - if self.localname: try: self.net_client.delete_device(self.localname) except CoreCommandError: logging.info("link already removed: %s", self.localname) - self.up = False diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index a66791ced..9773cb959 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -126,7 +126,7 @@ def shutdown(self) -> None: return with self.lock: - self._netif.clear() + self.ifaces.clear() self.client.stop_container() self.up = False @@ -215,7 +215,7 @@ def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> Non self.client.copy_file(source, filename) self.cmd(f"chmod {mode:o} {filename}") - def addnetif(self, netif: CoreInterface, ifindex: int) -> None: - super().addnetif(netif, ifindex) + def add_iface(self, iface: CoreInterface, iface_id: int) -> None: + super().add_iface(iface, iface_id) # adding small delay to allow time for adding addresses to work correctly time.sleep(0.5) diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 25a10b99b..b6c164b5c 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -155,14 +155,14 @@ def delete_tc(self, device: str) -> None: """ self.run(f"{TC_BIN} qdisc delete dev {device} root") - def checksums_off(self, interface_name: str) -> None: + def checksums_off(self, iface_name: str) -> None: """ Turns interface checksums off. - :param interface_name: interface to update + :param iface_name: interface to update :return: nothing """ - self.run(f"{ETHTOOL_BIN} -K {interface_name} rx off tx off") + self.run(f"{ETHTOOL_BIN} -K {iface_name} rx off tx off") def create_address(self, device: str, address: str, broadcast: str = None) -> None: """ @@ -250,26 +250,26 @@ def delete_bridge(self, name: str) -> None: self.device_down(name) self.run(f"{IP_BIN} link delete {name} type bridge") - def set_interface_master(self, bridge_name: str, interface_name: str) -> None: + def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ Assign interface master to a Linux bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {interface_name} master {bridge_name}") - self.device_up(interface_name) + self.run(f"{IP_BIN} link set dev {iface_name} master {bridge_name}") + self.device_up(iface_name) - def delete_interface(self, bridge_name: str, interface_name: str) -> None: + def delete_iface(self, bridge_name: str, iface_name: str) -> None: """ Delete an interface associated with a Linux bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {interface_name} nomaster") + self.run(f"{IP_BIN} link set dev {iface_name} nomaster") def existing_bridges(self, _id: int) -> bool: """ @@ -330,26 +330,26 @@ def delete_bridge(self, name: str) -> None: self.device_down(name) self.run(f"{OVS_BIN} del-br {name}") - def set_interface_master(self, bridge_name: str, interface_name: str) -> None: + def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ Create an interface associated with a network bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} add-port {bridge_name} {interface_name}") - self.device_up(interface_name) + self.run(f"{OVS_BIN} add-port {bridge_name} {iface_name}") + self.device_up(iface_name) - def delete_interface(self, bridge_name: str, interface_name: str) -> None: + def delete_iface(self, bridge_name: str, iface_name: str) -> None: """ Delete an interface associated with a OVS bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} del-port {bridge_name} {interface_name}") + self.run(f"{OVS_BIN} del-port {bridge_name} {iface_name}") def existing_bridges(self, _id: int) -> bool: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b85c2eee1..85e3e4881 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -216,20 +216,20 @@ def buildcmds(self, wlan: "CoreNetwork") -> None: ] ) # rebuild the chain - for netif1, v in wlan._linked.items(): - for netif2, linked in v.items(): + for iface1, v in wlan._linked.items(): + for oface2, linked in v.items(): if wlan.policy == NetworkPolicy.DROP and linked: self.cmds.extend( [ - f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j ACCEPT", - f"-A {wlan.brname} -o {netif1.localname} -i {netif2.localname} -j ACCEPT", + f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j ACCEPT", + f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j ACCEPT", ] ) elif wlan.policy == NetworkPolicy.ACCEPT and not linked: self.cmds.extend( [ - f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j DROP", - f"-A {wlan.brname} -o {netif1.localname} -i {netif2.localname} -j DROP", + f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j DROP", + f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j DROP", ] ) @@ -347,53 +347,53 @@ def shutdown(self) -> None: logging.exception("error during shutdown") # removes veth pairs used for bridge-to-bridge connections - for netif in self.netifs(): - netif.shutdown() + for iface in self.get_ifaces(): + iface.shutdown() - self._netif.clear() + self.ifaces.clear() self._linked.clear() del self.session self.up = False - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach a network interface. - :param netif: network interface to attach + :param iface: network interface to attach :return: nothing """ if self.up: - netif.net_client.set_interface_master(self.brname, netif.localname) - super().attach(netif) + iface.net_client.set_iface_master(self.brname, iface.localname) + super().attach(iface) - def detach(self, netif: CoreInterface) -> None: + def detach(self, iface: CoreInterface) -> None: """ Detach a network interface. - :param netif: network interface to detach + :param iface: network interface to detach :return: nothing """ if self.up: - netif.net_client.delete_interface(self.brname, netif.localname) - super().detach(netif) + iface.net_client.delete_iface(self.brname, iface.localname) + super().detach(iface) - def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool: + def linked(self, iface1: CoreInterface, iface2: CoreInterface) -> bool: """ Determine if the provided network interfaces are linked. - :param netif1: interface one - :param netif2: interface two + :param iface1: interface one + :param iface2: interface two :return: True if interfaces are linked, False otherwise """ # check if the network interfaces are attached to this network - if self._netif[netif1.netifi] != netif1: - raise ValueError(f"inconsistency for netif {netif1.name}") + if self.ifaces[iface1.net_id] != iface1: + raise ValueError(f"inconsistency for interface {iface1.name}") - if self._netif[netif2.netifi] != netif2: - raise ValueError(f"inconsistency for netif {netif2.name}") + if self.ifaces[iface2.net_id] != iface2: + raise ValueError(f"inconsistency for interface {iface2.name}") try: - linked = self._linked[netif1][netif2] + linked = self._linked[iface1][iface2] except KeyError: if self.policy == NetworkPolicy.ACCEPT: linked = True @@ -401,93 +401,93 @@ def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool: linked = False else: raise Exception(f"unknown policy: {self.policy.value}") - self._linked[netif1][netif2] = linked + self._linked[iface1][iface2] = linked return linked - def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: """ Unlink two interfaces, resulting in adding or removing ebtables filtering rules. - :param netif1: interface one - :param netif2: interface two + :param iface1: interface one + :param iface2: interface two :return: nothing """ with self._linked_lock: - if not self.linked(netif1, netif2): + if not self.linked(iface1, iface2): return - self._linked[netif1][netif2] = False + self._linked[iface1][iface2] = False ebq.ebchange(self) - def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None: """ Link two interfaces together, resulting in adding or removing ebtables filtering rules. - :param netif1: interface one - :param netif2: interface two + :param iface1: interface one + :param iface2: interface two :return: nothing """ with self._linked_lock: - if self.linked(netif1, netif2): + if self.linked(iface1, iface2): return - self._linked[netif1][netif2] = True + self._linked[iface1][iface2] = True ebq.ebchange(self) def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. - :param netif: interface one + :param iface: interface one :param options: options for configuring link - :param netif2: interface two + :param iface2: interface two :return: nothing """ - devname = netif.localname + devname = iface.localname tc = f"{TC_BIN} qdisc replace dev {devname}" parent = "root" changed = False bw = options.bandwidth - if netif.setparam("bw", bw): + if iface.setparam("bw", bw): # from tc-tbf(8): minimum value for burst is rate / kernel_hz - burst = max(2 * netif.mtu, int(bw / 1000)) + burst = max(2 * iface.mtu, int(bw / 1000)) # max IP payload limit = 0xFFFF tbf = f"tbf rate {bw} burst {burst} limit {limit}" if bw > 0: if self.up: cmd = f"{tc} {parent} handle 1: {tbf}" - netif.host_cmd(cmd) - netif.setparam("has_tbf", True) + iface.host_cmd(cmd) + iface.setparam("has_tbf", True) changed = True - elif netif.getparam("has_tbf") and bw <= 0: + elif iface.getparam("has_tbf") and bw <= 0: if self.up: cmd = f"{TC_BIN} qdisc delete dev {devname} {parent}" - netif.host_cmd(cmd) - netif.setparam("has_tbf", False) + iface.host_cmd(cmd) + iface.setparam("has_tbf", False) # removing the parent removes the child - netif.setparam("has_netem", False) + iface.setparam("has_netem", False) changed = True - if netif.getparam("has_tbf"): + if iface.getparam("has_tbf"): parent = "parent 1:1" netem = "netem" delay = options.delay - changed = max(changed, netif.setparam("delay", delay)) + changed = max(changed, iface.setparam("delay", delay)) loss = options.loss if loss is not None: loss = float(loss) - changed = max(changed, netif.setparam("loss", loss)) + changed = max(changed, iface.setparam("loss", loss)) duplicate = options.dup if duplicate is not None: duplicate = int(duplicate) - changed = max(changed, netif.setparam("duplicate", duplicate)) + changed = max(changed, iface.setparam("duplicate", duplicate)) jitter = options.jitter - changed = max(changed, netif.setparam("jitter", jitter)) + changed = max(changed, iface.setparam("jitter", jitter)) if not changed: return # jitter and delay use the same delay statement @@ -510,19 +510,19 @@ def linkconfig( duplicate_check = duplicate is None or duplicate <= 0 if all([delay_check, jitter_check, loss_check, duplicate_check]): # possibly remove netem if it exists and parent queue wasn't removed - if not netif.getparam("has_netem"): + if not iface.getparam("has_netem"): return if self.up: cmd = f"{TC_BIN} qdisc delete dev {devname} {parent} handle 10:" - netif.host_cmd(cmd) - netif.setparam("has_netem", False) + iface.host_cmd(cmd) + iface.setparam("has_netem", False) elif len(netem) > 1: if self.up: cmd = ( f"{TC_BIN} qdisc replace dev {devname} {parent} handle 10: {netem}" ) - netif.host_cmd(cmd) - netif.setparam("has_netem", True) + iface.host_cmd(cmd) + iface.setparam("has_netem", True) def linknet(self, net: CoreNetworkBase) -> CoreInterface: """ @@ -551,19 +551,19 @@ def linknet(self, net: CoreNetworkBase) -> CoreInterface: if len(name) >= 16: raise ValueError(f"interface name {name} too long") - netif = Veth(self.session, None, name, localname, start=self.up) - self.attach(netif) + iface = Veth(self.session, None, name, localname, start=self.up) + self.attach(iface) if net.up and net.brname: - netif.net_client.set_interface_master(net.brname, netif.name) - i = net.newifindex() - net._netif[i] = netif + iface.net_client.set_iface_master(net.brname, iface.name) + i = net.next_iface_id() + net.ifaces[i] = iface with net._linked_lock: - net._linked[netif] = {} - netif.net = self - netif.othernet = net - return netif + net._linked[iface] = {} + iface.net = self + iface.othernet = net + return iface - def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]: + def get_linked_iface(self, net: CoreNetworkBase) -> Optional[CoreInterface]: """ Return the interface of that links this net with another net (that were linked using linknet()). @@ -571,9 +571,9 @@ def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]: :param net: interface to get link for :return: interface the provided network is linked to """ - for netif in self.netifs(): - if netif.othernet == net: - return netif + for iface in self.get_ifaces(): + if iface.othernet == net: + return iface return None def addrconfig(self, addrlist: List[str]) -> None: @@ -690,17 +690,17 @@ def addrconfig(self, addrlist: List[str]) -> None: ) self.attach(self.gretap) - def setkey(self, key: int, interface_data: InterfaceData) -> None: + def setkey(self, key: int, iface_data: InterfaceData) -> None: """ Set the GRE key used for the GreTap device. This needs to be set prior to instantiating the GreTap device (before addrconfig). :param key: gre key - :param interface_data: interface data for setting up tunnel key + :param iface_data: interface data for setting up tunnel key :return: nothing """ self.grekey = key - addresses = interface_data.get_addresses() + addresses = iface_data.get_addresses() if addresses: self.addrconfig(addresses) @@ -802,7 +802,7 @@ def startup(self) -> None: self.host_cmd(f"{self.updown_script} {self.brname} startup") if self.serverintf: - self.net_client.set_interface_master(self.brname, self.serverintf) + self.net_client.set_iface_master(self.brname, self.serverintf) def shutdown(self) -> None: """ @@ -812,7 +812,7 @@ def shutdown(self) -> None: """ if self.serverintf is not None: try: - self.net_client.delete_interface(self.brname, self.serverintf) + self.net_client.delete_iface(self.brname, self.serverintf) except CoreCommandError: logging.exception( "error deleting server interface %s from bridge %s", @@ -850,18 +850,18 @@ class PtpNet(CoreNetwork): policy: NetworkPolicy = NetworkPolicy.ACCEPT - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach a network interface, but limit attachment to two interfaces. - :param netif: network interface + :param iface: network interface :return: nothing """ - if len(self._netif) >= 2: + if len(self.ifaces) >= 2: raise ValueError( "Point-to-point links support at most 2 network interfaces" ) - super().attach(netif) + super().attach(iface) def data( self, message_type: MessageFlags = MessageFlags.NONE, source: str = None @@ -886,67 +886,67 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat """ all_links = [] - if len(self._netif) != 2: + if len(self.ifaces) != 2: return all_links - interface1, interface2 = self._netif.values() + iface1, iface2 = self.get_ifaces() unidirectional = 0 - if interface1.getparams() != interface2.getparams(): + if iface1.getparams() != iface2.getparams(): unidirectional = 1 - interface1_ip4 = None - interface1_ip4_mask = None - interface1_ip6 = None - interface1_ip6_mask = None - for address in interface1.addrlist: + iface1_ip4 = None + iface1_ip4_mask = None + iface1_ip6 = None + iface1_ip6_mask = None + for address in iface1.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - interface1_ip4 = ip - interface1_ip4_mask = mask + iface1_ip4 = ip + iface1_ip4_mask = mask else: - interface1_ip6 = ip - interface1_ip6_mask = mask - - interface2_ip4 = None - interface2_ip4_mask = None - interface2_ip6 = None - interface2_ip6_mask = None - for address in interface2.addrlist: + iface1_ip6 = ip + iface1_ip6_mask = mask + + iface2_ip4 = None + iface2_ip4_mask = None + iface2_ip6 = None + iface2_ip6_mask = None + for address in iface2.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - interface2_ip4 = ip - interface2_ip4_mask = mask + iface2_ip4 = ip + iface2_ip4_mask = mask else: - interface2_ip6 = ip - interface2_ip6_mask = mask + iface2_ip6 = ip + iface2_ip6_mask = mask link_data = LinkData( message_type=flags, - node1_id=interface1.node.id, - node2_id=interface2.node.id, + node1_id=iface1.node.id, + node2_id=iface2.node.id, link_type=self.linktype, unidirectional=unidirectional, - delay=interface1.getparam("delay"), - bandwidth=interface1.getparam("bw"), - loss=interface1.getparam("loss"), - dup=interface1.getparam("duplicate"), - jitter=interface1.getparam("jitter"), - interface1_id=interface1.node.getifindex(interface1), - interface1_name=interface1.name, - interface1_mac=interface1.hwaddr, - interface1_ip4=interface1_ip4, - interface1_ip4_mask=interface1_ip4_mask, - interface1_ip6=interface1_ip6, - interface1_ip6_mask=interface1_ip6_mask, - interface2_id=interface2.node.getifindex(interface2), - interface2_name=interface2.name, - interface2_mac=interface2.hwaddr, - interface2_ip4=interface2_ip4, - interface2_ip4_mask=interface2_ip4_mask, - interface2_ip6=interface2_ip6, - interface2_ip6_mask=interface2_ip6_mask, + delay=iface1.getparam("delay"), + bandwidth=iface1.getparam("bw"), + loss=iface1.getparam("loss"), + dup=iface1.getparam("duplicate"), + jitter=iface1.getparam("jitter"), + iface1_id=iface1.node.get_iface_id(iface1), + iface1_name=iface1.name, + iface1_mac=iface1.hwaddr, + iface1_ip4=iface1_ip4, + iface1_ip4_mask=iface1_ip4_mask, + iface1_ip6=iface1_ip6, + iface1_ip6_mask=iface1_ip6_mask, + iface2_id=iface2.node.get_iface_id(iface2), + iface2_name=iface2.name, + iface2_mac=iface2.hwaddr, + iface2_ip4=iface2_ip4, + iface2_ip4_mask=iface2_ip4_mask, + iface2_ip6=iface2_ip6, + iface2_ip6_mask=iface2_ip6_mask, ) all_links.append(link_data) @@ -956,16 +956,16 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, - node1_id=interface2.node.id, - node2_id=interface1.node.id, - delay=interface2.getparam("delay"), - bandwidth=interface2.getparam("bw"), - loss=interface2.getparam("loss"), - dup=interface2.getparam("duplicate"), - jitter=interface2.getparam("jitter"), + node1_id=iface2.node.id, + node2_id=iface1.node.id, + delay=iface2.getparam("delay"), + bandwidth=iface2.getparam("bw"), + loss=iface2.getparam("loss"), + dup=iface2.getparam("duplicate"), + jitter=iface2.getparam("jitter"), unidirectional=1, - interface1_id=interface2.node.getifindex(interface2), - interface2_id=interface1.node.getifindex(interface1), + iface1_id=iface2.node.get_iface_id(iface2), + iface2_id=iface1.node.get_iface_id(iface1), ) all_links.append(link_data) return all_links @@ -1045,17 +1045,17 @@ def startup(self) -> None: self.net_client.disable_mac_learning(self.brname) ebq.ebchange(self) - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach a network interface. - :param netif: network interface + :param iface: network interface :return: nothing """ - super().attach(netif) + super().attach(iface) if self.model: - netif.poshook = self.model.position_callback - netif.setposition() + iface.poshook = self.model.position_callback + iface.setposition() def setmodel(self, model: "WirelessModelType", config: Dict[str, str]): """ @@ -1068,9 +1068,9 @@ def setmodel(self, model: "WirelessModelType", config: Dict[str, str]): logging.debug("node(%s) setting model: %s", self.name, model.name) if model.config_type == RegisterTlvs.WIRELESS: self.model = model(session=self.session, _id=self.id) - for netif in self.netifs(): - netif.poshook = self.model.position_callback - netif.setposition() + for iface in self.get_ifaces(): + iface.poshook = self.model.position_callback + iface.setposition() self.updatemodel(config) elif model.config_type == RegisterTlvs.MOBILITY: self.mobility = model(session=self.session, _id=self.id) @@ -1088,8 +1088,8 @@ def updatemodel(self, config: Dict[str, str]) -> None: "node(%s) updating model(%s): %s", self.id, self.model.name, config ) self.model.update_config(config) - for netif in self.netifs(): - netif.setposition() + for iface in self.get_ifaces(): + iface.setposition() def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 741fe7d59..555e0ec9d 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -51,8 +51,8 @@ def shutdown(self) -> None: _source, target = self._mounts.pop(-1) self.umount(target) - for netif in self.netifs(): - netif.shutdown() + for iface in self.get_ifaces(): + iface.shutdown() self.rmnodedir() @@ -65,117 +65,115 @@ def termcmdstring(self, sh: str = "/bin/sh") -> str: """ return sh - def sethwaddr(self, ifindex: int, addr: str) -> None: + def sethwaddr(self, iface_id: int, addr: str) -> None: """ Set hardware address for an interface. - :param ifindex: index of interface to set hardware address for + :param iface_id: index of interface to set hardware address for :param addr: hardware address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ addr = utils.validate_mac(addr) - interface = self._netif[ifindex] - interface.sethwaddr(addr) + iface = self.ifaces[iface_id] + iface.sethwaddr(addr) if self.up: - self.net_client.device_mac(interface.name, addr) + self.net_client.device_mac(iface.name, addr) - def addaddr(self, ifindex: int, addr: str) -> None: + def addaddr(self, iface_id: int, addr: str) -> None: """ Add an address to an interface. - :param ifindex: index of interface to add address to + :param iface_id: index of interface to add address to :param addr: address to add :return: nothing """ addr = utils.validate_ip(addr) - interface = self._netif[ifindex] + iface = self.get_iface(iface_id) if self.up: - self.net_client.create_address(interface.name, addr) - interface.addaddr(addr) + self.net_client.create_address(iface.name, addr) + iface.addaddr(addr) - def deladdr(self, ifindex: int, addr: str) -> None: + def deladdr(self, iface_id: int, addr: str) -> None: """ Delete an address from an interface. - :param ifindex: index of interface to delete + :param iface_id: index of interface to delete :param addr: address to delete :return: nothing """ - interface = self._netif[ifindex] - + iface = self.ifaces[iface_id] try: - interface.deladdr(addr) + iface.deladdr(addr) except ValueError: logging.exception("trying to delete unknown address: %s", addr) - if self.up: - self.net_client.delete_address(interface.name, addr) + self.net_client.delete_address(iface.name, addr) - def adoptnetif( - self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str] + def adopt_iface( + self, iface: CoreInterface, iface_id: int, hwaddr: str, addrlist: List[str] ) -> None: """ When a link message is received linking this node to another part of the emulation, no new interface is created; instead, adopt the - GreTap netif as the node interface. + GreTap interface as the node interface. """ - netif.name = f"gt{ifindex}" - netif.node = self - self.addnetif(netif, ifindex) + iface.name = f"gt{iface_id}" + iface.node = self + self.add_iface(iface, iface_id) # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" if self.up: - self.net_client.device_down(netif.localname) - self.net_client.device_name(netif.localname, netif.name) - netif.localname = netif.name + self.net_client.device_down(iface.localname) + self.net_client.device_name(iface.localname, iface.name) + iface.localname = iface.name if hwaddr: - self.sethwaddr(ifindex, hwaddr) + self.sethwaddr(iface_id, hwaddr) for addr in addrlist: - self.addaddr(ifindex, addr) + self.addaddr(iface_id, addr) if self.up: - self.net_client.device_up(netif.localname) + self.net_client.device_up(iface.localname) def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Apply tc queing disciplines using linkconfig. """ linux_bridge = CoreNetwork(self.session) linux_bridge.up = True - linux_bridge.linkconfig(netif, options, netif2) + linux_bridge.linkconfig(iface, options, iface2) del linux_bridge - def newifindex(self) -> int: + def next_iface_id(self) -> int: with self.lock: - while self.ifindex in self._netif: - self.ifindex += 1 - ifindex = self.ifindex - self.ifindex += 1 - return ifindex - - def newnetif( - self, net: CoreNetworkBase, interface_data: InterfaceData + while self.iface_id in self.ifaces: + self.iface_id += 1 + iface_id = self.iface_id + self.iface_id += 1 + return iface_id + + def new_iface( + self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: logging.info("creating interface") - addresses = interface_data.get_addresses() - ifindex = interface_data.id - if ifindex is None: - ifindex = self.newifindex() - name = interface_data.name + addresses = iface_data.get_addresses() + iface_id = iface_data.id + if iface_id is None: + iface_id = self.next_iface_id() + name = iface_data.name if name is None: - name = f"gt{ifindex}" + name = f"gt{iface_id}" if self.up: # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adoptnetif(remote_tap, ifindex, interface_data.mac, addresses) + self.adopt_iface(remote_tap, iface_id, iface_data.mac, addresses) return remote_tap else: # this is reached when configuring services (self.up=False) - netif = GreTap(node=self, name=name, session=self.session, start=False) - self.adoptnetif(netif, ifindex, interface_data.mac, addresses) - return netif + iface = GreTap(node=self, name=name, session=self.session, start=False) + self.adopt_iface(iface, iface_id, iface_data.mac, addresses) + return iface def privatedir(self, path: str) -> None: if path[0] != "/": @@ -257,10 +255,10 @@ def __init__( will run on, default is None for localhost """ super().__init__(session, _id, name, server) - self.interface = CoreInterface(session, self, name, name, mtu, server) - self.interface.transport_type = TransportType.RAW + self.iface = CoreInterface(session, self, name, name, mtu, server) + self.iface.transport_type = TransportType.RAW self.lock: threading.RLock = threading.RLock() - self.ifindex: Optional[int] = None + self.iface_id: Optional[int] = None self.old_up: bool = False self.old_addrs: List[Tuple[str, Optional[str]]] = [] @@ -273,7 +271,7 @@ def startup(self) -> None: """ # interface will also be marked up during net.attach() self.savestate() - self.net_client.device_up(self.interface.localname) + self.net_client.device_up(self.iface.localname) self.up = True def shutdown(self) -> None: @@ -285,7 +283,7 @@ def shutdown(self) -> None: """ if not self.up: return - localname = self.interface.localname + localname = self.iface.localname self.net_client.device_down(localname) self.net_client.device_flush(localname) try: @@ -295,8 +293,8 @@ def shutdown(self) -> None: self.up = False self.restorestate() - def newnetif( - self, net: CoreNetworkBase, interface_data: InterfaceData + def new_iface( + self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: """ This is called when linking with another node. Since this node @@ -304,70 +302,51 @@ def newnetif( but attach ourselves to the given network. :param net: new network instance - :param interface_data: interface data for new interface + :param iface_data: interface data for new interface :return: interface index :raises ValueError: when an interface has already been created, one max """ with self.lock: - ifindex = interface_data.id - if ifindex is None: - ifindex = 0 - if self.interface.net is not None: - raise ValueError("RJ45 nodes support at most 1 network interface") - self._netif[ifindex] = self.interface - self.ifindex = ifindex + iface_id = iface_data.id + if iface_id is None: + iface_id = 0 + if self.iface.net is not None: + raise CoreError("RJ45 nodes support at most 1 network interface") + self.ifaces[iface_id] = self.iface + self.iface_id = iface_id if net is not None: - self.interface.attachnet(net) - for addr in interface_data.get_addresses(): + self.iface.attachnet(net) + for addr in iface_data.get_addresses(): self.addaddr(addr) - return self.interface + return self.iface - def delnetif(self, ifindex: int) -> None: + def delete_iface(self, iface_id: int) -> None: """ Delete a network interface. - :param ifindex: interface index to delete + :param iface_id: interface index to delete :return: nothing """ - if ifindex is None: - ifindex = 0 - self._netif.pop(ifindex) - if ifindex == self.ifindex: - self.shutdown() - else: - raise ValueError(f"ifindex {ifindex} does not exist") - - def netif( - self, ifindex: int, net: CoreNetworkBase = None - ) -> Optional[CoreInterface]: - """ - This object is considered the network interface, so we only - return self here. This keeps the RJ45Node compatible with - real nodes. + self.get_iface(iface_id) + self.ifaces.pop(iface_id) + self.shutdown() - :param ifindex: interface index to retrieve - :param net: network to retrieve - :return: a network interface - """ - if net is not None and net == self.interface.net: - return self.interface - if ifindex is None: - ifindex = 0 - if ifindex == self.ifindex: - return self.interface - return None + def get_iface(self, iface_id: int) -> CoreInterface: + if iface_id != self.iface_id or iface_id not in self.ifaces: + raise CoreError(f"node({self.name}) interface({iface_id}) does not exist") + return self.iface - def getifindex(self, netif: CoreInterface) -> Optional[int]: + def get_iface_id(self, iface: CoreInterface) -> Optional[int]: """ Retrieve network interface index. - :param netif: network interface to retrieve + :param iface: network interface to retrieve index for :return: interface index, None otherwise """ - if netif != self.interface: - return None - return self.ifindex + if iface is not self.iface: + raise CoreError(f"node({self.name}) does not have interface({iface.name})") + return self.iface_id def addaddr(self, addr: str) -> None: """ @@ -380,7 +359,7 @@ def addaddr(self, addr: str) -> None: addr = utils.validate_ip(addr) if self.up: self.net_client.create_address(self.name, addr) - self.interface.addaddr(addr) + self.iface.addaddr(addr) def deladdr(self, addr: str) -> None: """ @@ -392,7 +371,7 @@ def deladdr(self, addr: str) -> None: """ if self.up: self.net_client.delete_address(self.name, addr) - self.interface.deladdr(addr) + self.iface.deladdr(addr) def savestate(self) -> None: """ @@ -404,7 +383,7 @@ def savestate(self) -> None: """ self.old_up = False self.old_addrs: List[Tuple[str, Optional[str]]] = [] - localname = self.interface.localname + localname = self.iface.localname output = self.net_client.address_show(localname) for line in output.split("\n"): items = line.split() @@ -429,7 +408,7 @@ def restorestate(self) -> None: :return: nothing :raises CoreCommandError: when there is a command exception """ - localname = self.interface.localname + localname = self.iface.localname logging.info("restoring rj45 state: %s", localname) for addr in self.old_addrs: self.net_client.create_address(localname, addr[0], addr[1]) @@ -446,7 +425,7 @@ def setposition(self, x: float = None, y: float = None, z: float = None) -> None :return: True if position changed, False otherwise """ super().setposition(x, y, z) - self.interface.setposition() + self.iface.setposition() def termcmdstring(self, sh: str) -> str: raise CoreError("rj45 does not support terminal commands") diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index 4901ea56e..16f0bb84b 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -35,10 +35,8 @@ def routerid(node): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -84,7 +82,7 @@ def generateBirdConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue - cfg += s.generatebirdconfig(node) + cfg += s.generate_bird_config(node) return cfg @@ -106,11 +104,11 @@ class BirdService(CoreService): meta = "The config file for this service can be found in the bird service." @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): return "" @classmethod - def generatebirdifcconfig(cls, node): + def generate_bird_iface_config(cls, node): """ Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same @@ -118,10 +116,8 @@ def generatebirdifcconfig(cls, node): """ cfg = "" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += ' interface "%s";\n' % ifc.name + for iface in node.get_ifaces(control=False): + cfg += ' interface "%s";\n' % iface.name return cfg @@ -135,7 +131,7 @@ class BirdBgp(BirdService): custom_needed = True @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): return """ /* This is a sample config that should be customized with appropriate AS numbers * and peers; add one section like this for each neighbor */ @@ -165,7 +161,7 @@ class BirdOspf(BirdService): name = "BIRD_OSPFv2" @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "protocol ospf {\n" cfg += " export filter {\n" cfg += " if source = RTS_BGP then {\n" @@ -175,7 +171,7 @@ def generatebirdconfig(cls, node): cfg += " accept;\n" cfg += " };\n" cfg += " area 0.0.0.0 {\n" - cfg += cls.generatebirdifcconfig(node) + cfg += cls.generate_bird_iface_config(node) cfg += " };\n" cfg += "}\n\n" @@ -190,12 +186,12 @@ class BirdRadv(BirdService): name = "BIRD_RADV" @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol radv {\n" cfg += " # auto configuration on all interfaces\n" - cfg += cls.generatebirdifcconfig(node) + cfg += cls.generate_bird_iface_config(node) cfg += " # Advertise DNS\n" cfg += " rdnss {\n" cfg += "# lifetime mult 10;\n" @@ -218,11 +214,11 @@ class BirdRip(BirdService): name = "BIRD_RIP" @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "protocol rip {\n" cfg += " period 10;\n" cfg += " garbage time 60;\n" - cfg += cls.generatebirdifcconfig(node) + cfg += cls.generate_bird_iface_config(node) cfg += " honor neighbor;\n" cfg += " authentication none;\n" cfg += " import all;\n" @@ -241,7 +237,7 @@ class BirdStatic(BirdService): custom_needed = True @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol static {\n" cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n" diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index 9d09516e5..da438bab0 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -20,14 +20,14 @@ class EmaneTransportService(CoreService): def generate_config(cls, node, filename): if filename == cls.configs[0]: transport_commands = [] - for interface in node.netifs(sort=True): + for iface in node.get_ifaces(): try: - network_node = node.session.get_node(interface.net.id, EmaneNet) + network_node = node.session.get_node(iface.net.id, EmaneNet) config = node.session.emane.get_configs( network_node.id, network_node.model.name ) if config and emanexml.is_external(config): - nem_id = network_node.getnemid(interface) + nem_id = network_node.getnemid(iface) command = ( "emanetransportd -r -l 0 -d ../transportdaemon%s.xml" % nem_id diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 9a3443394..97a8b3341 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -59,12 +59,12 @@ def generateFrrConf(cls, node): """ # we could verify here that filename == frr.conf cfg = "" - for ifc in node.netifs(): - cfg += "interface %s\n" % ifc.name + for iface in node.get_ifaces(): + cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(ifc, "control") and ifc.control is True: + if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, ifc.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" continue cfgv4 = "" @@ -74,18 +74,18 @@ def generateFrrConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue - ifccfg = s.generatefrrifcconfig(node, ifc) + iface_config = s.generate_frr_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True if s.ipv6_routing: want_ipv6 = True - cfgv6 += ifccfg + cfgv6 += iface_config else: - cfgv4 += ifccfg + cfgv4 += iface_config if want_ipv4: ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv4list)) @@ -93,7 +93,7 @@ def generateFrrConf(cls, node): cfg += cfgv4 if want_ipv6: ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv6list)) @@ -104,7 +104,7 @@ def generateFrrConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue - cfg += s.generatefrrconfig(node) + cfg += s.generate_frr_config(node) return cfg @staticmethod @@ -237,10 +237,10 @@ def generateFrrBoot(cls, node): frr_bin_search, constants.FRR_STATE_DIR, ) - for ifc in node.netifs(): - cfg += f"ip link set dev {ifc.name} down\n" + for iface in node.get_ifaces(): + cfg += f"ip link set dev {iface.name} down\n" cfg += "sleep 1\n" - cfg += f"ip link set dev {ifc.name} up\n" + cfg += f"ip link set dev {iface.name} up\n" return cfg @classmethod @@ -334,10 +334,8 @@ def routerid(node): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -345,16 +343,16 @@ def routerid(node): return "0.0.0.0" @staticmethod - def rj45check(ifc): + def rj45check(iface): """ Helper to detect whether interface is connected an external RJ45 link. """ - if ifc.net: - for peerifc in ifc.net.netifs(): - if peerifc == ifc: + if iface.net: + for peer_iface in iface.net.get_ifaces(): + if peer_iface == iface: continue - if isinstance(peerifc.node, Rj45Node): + if isinstance(peer_iface.node, Rj45Node): return True return False @@ -363,11 +361,11 @@ def generate_config(cls, node, filename): return "" @classmethod - def generatefrrifcconfig(cls, node, ifc): + def generate_frr_iface_config(cls, node, iface): return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): return "" @@ -385,43 +383,41 @@ class FRROspfv2(FrrService): ipv4_routing = True @staticmethod - def mtucheck(ifc): + def mtucheck(iface): """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: # a workaround for PhysicalNode GreTap, which has no knowledge of # the other nodes/nets return " ip ospf mtu-ignore\n" - if not ifc.net: + if not iface.net: return "" - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return " ip ospf mtu-ignore\n" return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ip ospf network point-to-point\n" return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router ospf\n" rtrid = cls.routerid(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue @@ -430,8 +426,8 @@ def generatefrrconfig(cls, node): return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) + def generate_frr_iface_config(cls, node, iface): + return cls.mtucheck(iface) class FRROspfv3(FrrService): @@ -449,57 +445,55 @@ class FRROspfv3(FrrService): ipv6_routing = True @staticmethod - def minmtu(ifc): + def minmtu(iface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @classmethod - def mtucheck(cls, ifc): + def mtucheck(cls, iface): """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(ifc) - if minmtu < ifc.mtu: + minmtu = cls.minmtu(iface) + if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ipv6 ospf6 network point-to-point\n" return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router ospf6\n" rtrid = cls.routerid(node) cfg += " router-id %s\n" % rtrid - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " interface %s area 0.0.0.0\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " interface %s area 0.0.0.0\n" % iface.name cfg += "!\n" return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) + def generate_frr_iface_config(cls, node, iface): + return cls.mtucheck(iface) # cfg = cls.mtucheck(ifc) # external RJ45 connections will use default OSPF timers # if cls.rj45check(ifc): @@ -531,7 +525,7 @@ class FRRBgp(FrrService): ipv6_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" @@ -555,7 +549,7 @@ class FRRRip(FrrService): ipv4_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = """\ router rip redistribute static @@ -579,7 +573,7 @@ class FRRRipng(FrrService): ipv6_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = """\ router ripng redistribute static @@ -604,18 +598,16 @@ class FRRBabel(FrrService): ipv6_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router babel\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " network %s\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " network %s\n" % iface.name cfg += " redistribute static\n redistribute ipv4 connected\n" return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): - if ifc.net and isinstance(ifc.net, (EmaneNet, WlanNode)): + def generate_frr_iface_config(cls, node, iface): + if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)): return " babel wireless\n no babel split-horizon\n" else: return " babel wired\n babel split-horizon\n" @@ -633,11 +625,11 @@ class FRRpimd(FrrService): ipv4_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): ifname = "eth0" - for ifc in node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break cfg = "router mfea\n!\n" cfg += "router igmp\n!\n" @@ -649,7 +641,7 @@ def generatefrrconfig(cls, node): return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): + def generate_frr_iface_config(cls, node, iface): return " ip mfea\n ip igmp\n ip pim\n" @@ -668,17 +660,17 @@ class FRRIsis(FrrService): ipv6_routing = True @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " isis network point-to-point\n" return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router isis DEFAULT\n" cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id cfg += " metric-style wide\n" @@ -687,9 +679,9 @@ def generatefrrconfig(cls, node): return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): + def generate_frr_iface_config(cls, node, iface): cfg = " ip router isis DEFAULT\n" cfg += " ipv6 router isis DEFAULT\n" cfg += " isis circuit-type level-2-only\n" - cfg += cls.ptpcheck(ifc) + cfg += cls.ptpcheck(iface) return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 3c9f262d2..38b90d488 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -32,10 +32,8 @@ def firstipv4prefix(node, prefixlen=24): prefix of a node, using the supplied prefix length. This ignores the interface's prefix length, so e.g. '/32' can turn into '/24'. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return f"{a}/{prefixlen}" @@ -54,8 +52,8 @@ class MgenSinkService(NrlService): @classmethod def generate_config(cls, node, filename): cfg = "0.0 LISTEN UDP 5000\n" - for ifc in node.netifs(): - name = utils.sysctl_devname(ifc.name) + for iface in node.get_ifaces(): + name = utils.sysctl_devname(iface.name) cfg += "0.0 Join 224.225.1.2 INTERFACE %s\n" % name return cfg @@ -91,11 +89,11 @@ def get_startup(cls, node): cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) > 0: - interfacenames = map(lambda x: x.name, netifs) + ifaces = node.get_ifaces(control=False) + if len(ifaces) > 0: + iface_names = map(lambda x: x.name, ifaces) cmd += " -i " - cmd += " -i ".join(interfacenames) + cmd += " -i ".join(iface_names) return (cmd,) @@ -125,16 +123,16 @@ def generate_config(cls, node, filename): cmd = "nrlsmf instance %s_smf" % node.name servicenames = map(lambda x: x.name, node.services) - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) == 0: + ifaces = node.get_ifaces(control=False) + if len(ifaces) == 0: return "" if "arouted" in servicenames: comments += "# arouted service is enabled\n" cmd += " tap %s_tap" % (node.name,) cmd += " unicast %s" % cls.firstipv4prefix(node, 24) - cmd += " push lo,%s resequence on" % netifs[0].name - if len(netifs) > 0: + cmd += " push lo,%s resequence on" % ifaces[0].name + if len(ifaces) > 0: if "NHDP" in servicenames: comments += "# NHDP service is enabled\n" cmd += " ecds " @@ -143,8 +141,8 @@ def generate_config(cls, node, filename): cmd += " smpr " else: cmd += " cf " - interfacenames = map(lambda x: x.name, netifs) - cmd += ",".join(interfacenames) + iface_names = map(lambda x: x.name, ifaces) + cmd += ",".join(iface_names) cmd += " hash MD5" cmd += " log /var/log/nrlsmf.log" @@ -171,10 +169,10 @@ def get_startup(cls, node): """ cmd = cls.startup[0] # are multiple interfaces supported? No. - netifs = list(node.netifs()) - if len(netifs) > 0: - ifc = netifs[0] - cmd += " -i %s" % ifc.name + ifaces = node.get_ifaces() + if len(ifaces) > 0: + iface = ifaces[0] + cmd += " -i %s" % iface.name cmd += " -l /var/log/nrlolsrd.log" cmd += " -rpipe %s_olsr" % node.name @@ -215,11 +213,11 @@ def get_startup(cls, node): cmd += " -p olsr" - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) > 0: - interfacenames = map(lambda x: x.name, netifs) + ifaces = node.get_ifaces(control=False) + if len(ifaces) > 0: + iface_names = map(lambda x: x.name, ifaces) cmd += " -i " - cmd += " -i ".join(interfacenames) + cmd += " -i ".join(iface_names) return (cmd,) @@ -243,11 +241,11 @@ def get_startup(cls, node): Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) > 0: - interfacenames = map(lambda x: x.name, netifs) + ifaces = node.get_ifaces(control=False) + if len(ifaces) > 0: + iface_names = map(lambda x: x.name, ifaces) cmd += " -i " - cmd += " -i ".join(interfacenames) + cmd += " -i ".join(iface_names) return (cmd,) @@ -607,8 +605,8 @@ def generate_config(cls, node, filename): comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name - netifs = [x for x in node.netifs() if not getattr(x, "control", False)] - if len(netifs) == 0: + ifaces = node.get_ifaces(control=False) + if len(ifaces) == 0: return "" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index a62cbc5ca..41cfa3d88 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -56,12 +56,12 @@ def generateQuaggaConf(cls, node): """ # we could verify here that filename == Quagga.conf cfg = "" - for ifc in node.netifs(): - cfg += "interface %s\n" % ifc.name + for iface in node.get_ifaces(): + cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(ifc, "control") and ifc.control is True: + if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, ifc.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" continue cfgv4 = "" @@ -71,18 +71,18 @@ def generateQuaggaConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue - ifccfg = s.generatequaggaifcconfig(node, ifc) + iface_config = s.generate_quagga_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True if s.ipv6_routing: want_ipv6 = True - cfgv6 += ifccfg + cfgv6 += iface_config else: - cfgv4 += ifccfg + cfgv4 += iface_config if want_ipv4: ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv4list)) @@ -90,7 +90,7 @@ def generateQuaggaConf(cls, node): cfg += cfgv4 if want_ipv6: ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv6list)) @@ -101,7 +101,7 @@ def generateQuaggaConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue - cfg += s.generatequaggaconfig(node) + cfg += s.generate_quagga_config(node) return cfg @staticmethod @@ -252,10 +252,8 @@ def routerid(node): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -263,16 +261,16 @@ def routerid(node): return "0.0.0.%d" % node.id @staticmethod - def rj45check(ifc): + def rj45check(iface): """ Helper to detect whether interface is connected an external RJ45 link. """ - if ifc.net: - for peerifc in ifc.net.netifs(): - if peerifc == ifc: + if iface.net: + for peer_iface in iface.net.get_ifaces(): + if peer_iface == iface: continue - if isinstance(peerifc.node, Rj45Node): + if isinstance(peer_iface.node, Rj45Node): return True return False @@ -281,11 +279,11 @@ def generate_config(cls, node, filename): return "" @classmethod - def generatequaggaifcconfig(cls, node, ifc): + def generate_quagga_iface_config(cls, node, iface): return "" @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): return "" @@ -303,43 +301,41 @@ class Ospfv2(QuaggaService): ipv4_routing = True @staticmethod - def mtucheck(ifc): + def mtucheck(iface): """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: # a workaround for PhysicalNode GreTap, which has no knowledge of # the other nodes/nets return " ip ospf mtu-ignore\n" - if not ifc.net: + if not iface.net: return "" - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return " ip ospf mtu-ignore\n" return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ip ospf network point-to-point\n" return "" @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "router ospf\n" rtrid = cls.routerid(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if netaddr.valid_ipv4(addr): cfg += " network %s area 0\n" % a @@ -347,12 +343,12 @@ def generatequaggaconfig(cls, node): return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - cfg = cls.mtucheck(ifc) + def generate_quagga_iface_config(cls, node, iface): + cfg = cls.mtucheck(iface) # external RJ45 connections will use default OSPF timers - if cls.rj45check(ifc): + if cls.rj45check(iface): return cfg - cfg += cls.ptpcheck(ifc) + cfg += cls.ptpcheck(iface) return ( cfg + """\ @@ -378,58 +374,56 @@ class Ospfv3(QuaggaService): ipv6_routing = True @staticmethod - def minmtu(ifc): + def minmtu(iface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @classmethod - def mtucheck(cls, ifc): + def mtucheck(cls, iface): """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(ifc) - if minmtu < ifc.mtu: + minmtu = cls.minmtu(iface) + if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ipv6 ospf6 network point-to-point\n" return "" @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "router ospf6\n" rtrid = cls.routerid(node) cfg += " instance-id 65\n" cfg += " router-id %s\n" % rtrid - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " interface %s area 0.0.0.0\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " interface %s area 0.0.0.0\n" % iface.name cfg += "!\n" return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) + def generate_quagga_iface_config(cls, node, iface): + return cls.mtucheck(iface) class Ospfv3mdr(Ospfv3): @@ -444,9 +438,9 @@ class Ospfv3mdr(Ospfv3): ipv4_routing = True @classmethod - def generatequaggaifcconfig(cls, node, ifc): - cfg = cls.mtucheck(ifc) - if ifc.net is not None and isinstance(ifc.net, (WlanNode, EmaneNet)): + def generate_quagga_iface_config(cls, node, iface): + cfg = cls.mtucheck(iface) + if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): return ( cfg + """\ @@ -479,7 +473,7 @@ class Bgp(QuaggaService): ipv6_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" @@ -503,7 +497,7 @@ class Rip(QuaggaService): ipv4_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = """\ router rip redistribute static @@ -527,7 +521,7 @@ class Ripng(QuaggaService): ipv6_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = """\ router ripng redistribute static @@ -552,18 +546,16 @@ class Babel(QuaggaService): ipv6_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "router babel\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " network %s\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " network %s\n" % iface.name cfg += " redistribute static\n redistribute connected\n" return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS: + def generate_quagga_iface_config(cls, node, iface): + if iface.net and iface.net.linktype == LinkTypes.WIRELESS: return " babel wireless\n no babel split-horizon\n" else: return " babel wired\n babel split-horizon\n" @@ -581,11 +573,11 @@ class Xpimd(QuaggaService): ipv4_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): ifname = "eth0" - for ifc in node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break cfg = "router mfea\n!\n" cfg += "router igmp\n!\n" @@ -597,5 +589,5 @@ def generatequaggaconfig(cls, node): return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): + def generate_quagga_iface_config(cls, node, iface): return " ip mfea\n ip igmp\n ip pim\n" diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index ab46f551d..71ab815ff 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -49,10 +49,8 @@ def generate_config(cls, node, filename): cfg += "\n## Now add all our interfaces as ports to the switch\n" portnum = 1 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - ifnumstr = re.findall(r"\d+", ifc.name) + for iface in node.get_ifaces(control=False): + ifnumstr = re.findall(r"\d+", iface.name) ifnum = ifnumstr[0] # create virtual interfaces @@ -61,18 +59,18 @@ def generate_config(cls, node, filename): # remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces # or assign them manually to rtr interfaces if zebra is not running - for ifcaddr in ifc.addrlist: - addr = ifcaddr.split("/")[0] + for addr in iface.addrlist: + addr = addr.split("/")[0] if netaddr.valid_ipv4(addr): - cfg += "ip addr del %s dev %s\n" % (ifcaddr, ifc.name) + cfg += "ip addr del %s dev %s\n" % (addr, iface.name) if has_zebra == 0: - cfg += "ip addr add %s dev rtr%s\n" % (ifcaddr, ifnum) + cfg += "ip addr add %s dev rtr%s\n" % (addr, ifnum) elif netaddr.valid_ipv6(addr): - cfg += "ip -6 addr del %s dev %s\n" % (ifcaddr, ifc.name) + cfg += "ip -6 addr del %s dev %s\n" % (addr, iface.name) if has_zebra == 0: - cfg += "ip -6 addr add %s dev rtr%s\n" % (ifcaddr, ifnum) + cfg += "ip -6 addr add %s dev rtr%s\n" % (addr, ifnum) else: - raise ValueError("invalid address: %s" % ifcaddr) + raise ValueError("invalid address: %s" % addr) # add interfaces to bridge # Make port numbers explicit so they're easier to follow in reading the script @@ -102,9 +100,7 @@ def generate_config(cls, node, filename): cfg += "## if the above controller will be present then you probably want to delete them\n" # Setup default flows portnum = 1 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue + for iface in node.get_ifaces(control=False): cfg += "## Take the data from the CORE interface and put it on the veth and vice versa\n" cfg += ( "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index eb6545b28..91c942f1c 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -131,18 +131,18 @@ class Nat(CoreService): custom_needed = False @classmethod - def generateifcnatrule(cls, ifc, line_prefix=""): + def generate_iface_nat_rule(cls, iface, line_prefix=""): """ Generate a NAT line for one interface. """ cfg = line_prefix + "iptables -t nat -A POSTROUTING -o " - cfg += ifc.name + " -j MASQUERADE\n" + cfg += iface.name + " -j MASQUERADE\n" - cfg += line_prefix + "iptables -A FORWARD -i " + ifc.name + cfg += line_prefix + "iptables -A FORWARD -i " + iface.name cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n" cfg += line_prefix + "iptables -A FORWARD -i " - cfg += ifc.name + " -j DROP\n" + cfg += iface.name + " -j DROP\n" return cfg @classmethod @@ -154,14 +154,12 @@ def generate_config(cls, node, filename): cfg += "# generated by security.py\n" cfg += "# NAT out the first interface by default\n" have_nat = False - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue + for iface in node.get_ifaces(control=False): if have_nat: - cfg += cls.generateifcnatrule(ifc, line_prefix="#") + cfg += cls.generate_iface_nat_rule(iface, line_prefix="#") else: have_nat = True - cfg += "# NAT out the " + ifc.name + " interface\n" - cfg += cls.generateifcnatrule(ifc) + cfg += "# NAT out the " + iface.name + " interface\n" + cfg += cls.generate_iface_nat_rule(iface) cfg += "\n" return cfg diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 8a6e828b8..273318e15 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -55,8 +55,8 @@ def generateconfiglinux(cls, node, filename): """ % { "sysctl": constants.SYSCTL_BIN } - for ifc in node.netifs(): - name = utils.sysctl_devname(ifc.name) + for iface in node.get_ifaces(): + name = utils.sysctl_devname(iface.name) cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % ( constants.SYSCTL_BIN, name, @@ -77,10 +77,10 @@ class DefaultRouteService(UtilService): @classmethod def generate_config(cls, node, filename): routes = [] - netifs = node.netifs(sort=True) - if netifs: - netif = netifs[0] - for x in netif.addrlist: + ifaces = node.get_ifaces() + if ifaces: + iface = ifaces[0] + for x in iface.addrlist: net = netaddr.IPNetwork(x).cidr if net.size > 1: router = net[1] @@ -104,14 +104,12 @@ def generate_config(cls, node, filename): cfg += "# the first interface is chosen below; please change it " cfg += "as needed\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue + for iface in node.get_ifaces(control=False): if os.uname()[0] == "Linux": rtcmd = "ip route add 224.0.0.0/4 dev" else: raise Exception("unknown platform") - cfg += "%s %s\n" % (rtcmd, ifc.name) + cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "\n" break return cfg @@ -129,10 +127,8 @@ def generate_config(cls, node, filename): cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\n".join(map(cls.routestr, ifc.addrlist)) + for iface in node.get_ifaces(control=False): + cfg += "\n".join(map(cls.routestr, iface.addrlist)) cfg += "\n" return cfg @@ -259,10 +255,8 @@ def generate_config(cls, node, filename): ddns-update-style none; """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\n".join(map(cls.subnetentry, ifc.addrlist)) + for iface in node.get_ifaces(control=False): + cfg += "\n".join(map(cls.subnetentry, iface.addrlist)) cfg += "\n" return cfg @@ -320,13 +314,11 @@ def generate_config(cls, node, filename): cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += " /var/run/resolvconf/resolv.conf\n" - cfg += "/sbin/dhclient -nw -pf /var/run/dhclient-%s.pid" % ifc.name - cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (ifc.name, ifc.name) + cfg += "/sbin/dhclient -nw -pf /var/run/dhclient-%s.pid" % iface.name + cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (iface.name, iface.name) return cfg @@ -585,10 +577,8 @@ def generatehtml(cls, node, filename): """ % node.name ) - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - body += "
  • %s - %s
  • \n" % (ifc.name, ifc.addrlist) + for iface in node.get_ifaces(control=False): + body += "
  • %s - %s
  • \n" % (iface.name, iface.addrlist) return "%s" % body @@ -619,14 +609,14 @@ def generate_config(cls, node, filename): if [ "x$1" = "xstart" ]; then """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: + for iface in node.get_ifaces(): + if hasattr(iface, "control") and iface.control is True: cfg += "# " redir = "< /dev/null" cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % ( node.name, - ifc.name, - ifc.name, + iface.name, + iface.name, redir, ) cfg += """ @@ -654,10 +644,8 @@ def generate_config(cls, node, filename): using the network address of each interface. """ cfg = "# auto-generated by RADVD service (utility.py)\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - prefixes = list(map(cls.subnetentry, ifc.addrlist)) + for iface in node.get_ifaces(control=False): + prefixes = list(map(cls.subnetentry, iface.addrlist)) if len(prefixes) < 1: continue cfg += ( @@ -670,7 +658,7 @@ def generate_config(cls, node, filename): AdvDefaultPreference low; AdvHomeAgentFlag off; """ - % ifc.name + % iface.name ) for prefix in prefixes: if prefix == "": diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 2312e6d43..3dfef56ab 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -35,11 +35,11 @@ def generate_config(cls, node, filename): invoked here. Filename currently ignored. """ cfg = "interfaces {\n" - for ifc in node.netifs(): - cfg += " interface %s {\n" % ifc.name - cfg += "\tvif %s {\n" % ifc.name - cfg += "".join(map(cls.addrstr, ifc.addrlist)) - cfg += cls.lladdrstr(ifc) + for iface in node.get_ifaces(): + cfg += " interface %s {\n" % iface.name + cfg += "\tvif %s {\n" % iface.name + cfg += "".join(map(cls.addrstr, iface.addrlist)) + cfg += cls.lladdrstr(iface) cfg += "\t}\n" cfg += " }\n" cfg += "}\n\n" @@ -65,11 +65,11 @@ def addrstr(x): return cfg @staticmethod - def lladdrstr(ifc): + def lladdrstr(iface): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % ifc.hwaddr.tolinklocal() + cfg = "\t address %s {\n" % iface.hwaddr.tolinklocal() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -104,15 +104,15 @@ def fea(forwarding): return cfg @staticmethod - def mfea(forwarding, ifcs): + def mfea(forwarding, ifaces): """ Helper to add a multicast forwarding engine entry to the config file. """ names = [] - for ifc in ifcs: - if hasattr(ifc, "control") and ifc.control is True: + for iface in ifaces: + if hasattr(iface, "control") and iface.control is True: continue - names.append(ifc.name) + names.append(iface.name) names.append("register_vif") cfg = "plumbing {\n" @@ -148,10 +148,8 @@ def routerid(node): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -184,12 +182,10 @@ def generatexorpconfig(cls, node): cfg += " ospf4 {\n" cfg += "\trouter-id: %s\n" % rtrid cfg += "\tarea 0.0.0.0 {\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\t interface %s {\n" % ifc.name - cfg += "\t\tvif %s {\n" % ifc.name - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + cfg += "\t interface %s {\n" % iface.name + cfg += "\t\tvif %s {\n" % iface.name + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue @@ -220,11 +216,9 @@ def generatexorpconfig(cls, node): cfg += " ospf6 0 { /* Instance ID 0 */\n" cfg += "\trouter-id: %s\n" % rtrid cfg += "\tarea 0.0.0.0 {\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\t interface %s {\n" % ifc.name - cfg += "\t\tvif %s {\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += "\t interface %s {\n" % iface.name + cfg += "\t\tvif %s {\n" % iface.name cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" @@ -277,12 +271,10 @@ def generatexorpconfig(cls, node): cfg += "\nprotocols {\n" cfg += " rip {\n" cfg += '\texport: "export-connected"\n' - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue @@ -310,12 +302,10 @@ def generatexorpconfig(cls, node): cfg += "\nprotocols {\n" cfg += " ripng {\n" cfg += '\texport: "export-connected"\n' - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name - cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal() + for iface in node.get_ifaces(control=False): + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name + cfg += "\t\taddress %s {\n" % iface.hwaddr.tolinklocal() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" @@ -334,17 +324,15 @@ class XorpPimSm4(XorpService): @classmethod def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea4", node.netifs()) + cfg = cls.mfea("mfea4", node.get_ifaces()) cfg += "\nprotocols {\n" cfg += " igmp {\n" names = [] - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - names.append(ifc.name) - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name + for iface in node.get_ifaces(control=False): + names.append(iface.name) + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name cfg += "\t\tdisable: false\n" cfg += "\t }\n" cfg += "\t}\n" @@ -394,17 +382,15 @@ class XorpPimSm6(XorpService): @classmethod def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea6", node.netifs()) + cfg = cls.mfea("mfea6", node.get_ifaces()) cfg += "\nprotocols {\n" cfg += " mld {\n" names = [] - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - names.append(ifc.name) - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name + for iface in node.get_ifaces(control=False): + names.append(iface.name) + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name cfg += "\t\tdisable: false\n" cfg += "\t }\n" cfg += "\t}\n" @@ -459,12 +445,10 @@ def generatexorpconfig(cls, node): cfg += "\nprotocols {\n" cfg += " olsr4 {\n" cfg += "\tmain-address: %s\n" % rtrid - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 759de680e..fe596d7a9 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -58,16 +58,16 @@ def add_attribute(element: etree.Element, name: str, value: Any) -> None: element.set(name, str(value)) -def create_interface_data(interface_element: etree.Element) -> InterfaceData: - interface_id = int(interface_element.get("id")) - name = interface_element.get("name") - mac = interface_element.get("mac") - ip4 = interface_element.get("ip4") - ip4_mask = get_int(interface_element, "ip4_mask") - ip6 = interface_element.get("ip6") - ip6_mask = get_int(interface_element, "ip6_mask") +def create_iface_data(iface_element: etree.Element) -> InterfaceData: + iface_id = int(iface_element.get("id")) + name = iface_element.get("name") + mac = iface_element.get("mac") + ip4 = iface_element.get("ip4") + ip4_mask = get_int(iface_element, "ip4_mask") + ip6 = iface_element.get("ip6") + ip6_mask = get_int(iface_element, "ip6_mask") return InterfaceData( - id=interface_id, + id=iface_id, name=name, mac=mac, ip4=ip4, @@ -482,7 +482,7 @@ def write_links(self, links: List[LinkData]) -> None: # add link data for link_data in links: # skip basic range links - if link_data.interface1_id is None and link_data.interface2_id is None: + if link_data.iface1_id is None and link_data.iface2_id is None: continue link_element = self.create_link_element(link_data) @@ -495,37 +495,37 @@ def write_device(self, node: NodeBase) -> None: device = DeviceElement(self.session, node) self.devices.append(device.element) - def create_interface_element( + def create_iface_element( self, element_name: str, node_id: int, - interface_id: int, + iface_id: int, mac: str, ip4: str, ip4_mask: int, ip6: str, ip6_mask: int, ) -> etree.Element: - interface = etree.Element(element_name) + iface = etree.Element(element_name) node = self.session.get_node(node_id, NodeBase) - interface_name = None + iface_name = None if isinstance(node, CoreNodeBase): - node_interface = node.netif(interface_id) - interface_name = node_interface.name + node_iface = node.get_iface(iface_id) + iface_name = node_iface.name # check if emane interface - if isinstance(node_interface.net, EmaneNet): - nem = node_interface.net.getnemid(node_interface) - add_attribute(interface, "nem", nem) - - add_attribute(interface, "id", interface_id) - add_attribute(interface, "name", interface_name) - add_attribute(interface, "mac", mac) - add_attribute(interface, "ip4", ip4) - add_attribute(interface, "ip4_mask", ip4_mask) - add_attribute(interface, "ip6", ip6) - add_attribute(interface, "ip6_mask", ip6_mask) - return interface + if isinstance(node_iface.net, EmaneNet): + nem = node_iface.net.getnemid(node_iface) + add_attribute(iface, "nem", nem) + + add_attribute(iface, "id", iface_id) + add_attribute(iface, "name", iface_name) + add_attribute(iface, "mac", mac) + add_attribute(iface, "ip4", ip4) + add_attribute(iface, "ip4_mask", ip4_mask) + add_attribute(iface, "ip6", ip6) + add_attribute(iface, "ip6_mask", ip6_mask) + return iface def create_link_element(self, link_data: LinkData) -> etree.Element: link_element = etree.Element("link") @@ -533,32 +533,32 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: add_attribute(link_element, "node2", link_data.node2_id) # check for interface one - if link_data.interface1_id is not None: - interface1 = self.create_interface_element( + if link_data.iface1_id is not None: + iface1 = self.create_iface_element( "interface1", link_data.node1_id, - link_data.interface1_id, - link_data.interface1_mac, - link_data.interface1_ip4, - link_data.interface1_ip4_mask, - link_data.interface1_ip6, - link_data.interface1_ip6_mask, + link_data.iface1_id, + link_data.iface1_mac, + link_data.iface1_ip4, + link_data.iface1_ip4_mask, + link_data.iface1_ip6, + link_data.iface1_ip6_mask, ) - link_element.append(interface1) + link_element.append(iface1) # check for interface two - if link_data.interface2_id is not None: - interface2 = self.create_interface_element( + if link_data.iface2_id is not None: + iface2 = self.create_iface_element( "interface2", link_data.node2_id, - link_data.interface2_id, - link_data.interface2_mac, - link_data.interface2_ip4, - link_data.interface2_ip4_mask, - link_data.interface2_ip6, - link_data.interface2_ip6_mask, + link_data.iface2_id, + link_data.iface2_mac, + link_data.iface2_ip4, + link_data.iface2_ip4_mask, + link_data.iface2_ip6, + link_data.iface2_ip6_mask, ) - link_element.append(interface2) + link_element.append(iface2) # check for options, don't write for emane/wlan links node1 = self.session.get_node(link_data.node1_id, NodeBase) @@ -940,19 +940,19 @@ def read_links(self) -> None: node2_id = get_int(link_element, "node_two") node_set = frozenset((node1_id, node2_id)) - interface1_element = link_element.find("interface1") - if interface1_element is None: - interface1_element = link_element.find("interface_one") - interface1_data = None - if interface1_element is not None: - interface1_data = create_interface_data(interface1_element) + iface1_element = link_element.find("interface1") + if iface1_element is None: + iface1_element = link_element.find("interface_one") + iface1_data = None + if iface1_element is not None: + iface1_data = create_iface_data(iface1_element) - interface2_element = link_element.find("interface2") - if interface2_element is None: - interface2_element = link_element.find("interface_two") - interface2_data = None - if interface2_element is not None: - interface2_data = create_interface_data(interface2_element) + iface2_element = link_element.find("interface2") + if iface2_element is None: + iface2_element = link_element.find("interface_two") + iface2_data = None + if iface2_element is not None: + iface2_data = create_iface_data(iface2_element) options_element = link_element.find("options") options = LinkOptions() @@ -978,12 +978,12 @@ def read_links(self) -> None: if options.unidirectional == 1 and node_set in node_sets: logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) self.session.update_link( - node1_id, node2_id, interface1_data.id, interface2_data.id, options + node1_id, node2_id, iface1_data.id, iface2_data.id, options ) else: logging.info("adding link node1(%s) node2(%s)", node1_id, node2_id) self.session.add_link( - node1_id, node2_id, interface1_data, interface2_data, options + node1_id, node2_id, iface1_data, iface2_data, options ) node_sets.add(node_set) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 04915bf13..7954b71a7 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -24,25 +24,25 @@ def add_address( parent_element: etree.Element, address_type: str, address: str, - interface_name: str = None, + iface_name: str = None, ) -> None: address_element = etree.SubElement(parent_element, "address", type=address_type) address_element.text = address - if interface_name is not None: - address_element.set("iface", interface_name) + if iface_name is not None: + address_element.set("iface", iface_name) def add_mapping(parent_element: etree.Element, maptype: str, mapref: str) -> None: etree.SubElement(parent_element, "mapping", type=maptype, ref=mapref) -def add_emane_interface( +def add_emane_iface( host_element: etree.Element, - netif: CoreInterface, + iface: CoreInterface, platform_name: str = "p1", transport_name: str = "t1", ) -> etree.Element: - nem_id = netif.net.nemidmap[netif] + nem_id = iface.net.nemidmap[iface] host_id = host_element.get("id") # platform data @@ -89,10 +89,10 @@ def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]: split = line.split() if not split: continue - interface_name = split[1] + iface_name = split[1] address = split[3] if not address.startswith("127."): - addresses.append((interface_name, address)) + addresses.append((iface_name, address)) return addresses else: # TODO: handle other hosts @@ -112,11 +112,11 @@ def find_device(self, name: str) -> etree.Element: device = self.scenario.find(f"devices/device[@name='{name}']") return device - def find_interface(self, device: NodeBase, name: str) -> etree.Element: - interface = self.scenario.find( + def find_iface(self, device: NodeBase, name: str) -> etree.Element: + iface = self.scenario.find( f"devices/device[@name='{device.name}']/interfaces/interface[@name='{name}']" ) - return interface + return iface def add_deployment(self) -> None: physical_host = self.add_physical_host(socket.gethostname()) @@ -136,8 +136,8 @@ def add_physical_host(self, name: str) -> etree.Element: add_type(host_element, "physical") # add ipv4 addresses - for interface_name, address in get_ipv4_addresses("localhost"): - add_address(host_element, "IPv4", address, interface_name) + for iface_name, address in get_ipv4_addresses("localhost"): + add_address(host_element, "IPv4", address, iface_name) return host_element @@ -155,15 +155,15 @@ def add_virtual_host(self, physical_host: etree.Element, node: NodeBase) -> None # add host type add_type(host_element, "virtual") - for netif in node.netifs(): + for iface in node.get_ifaces(): emane_element = None - if isinstance(netif.net, EmaneNet): - emane_element = add_emane_interface(host_element, netif) + if isinstance(iface.net, EmaneNet): + emane_element = add_emane_iface(host_element, iface) parent_element = host_element if emane_element is not None: parent_element = emane_element - for address in netif.addrlist: + for address in iface.addrlist: address_type = get_address_type(address) - add_address(parent_element, address_type, address, netif.name) + add_address(parent_element, address_type, address, iface.name) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 2589edd9a..4f5114762 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -158,19 +158,19 @@ def build_node_platform_xml( logging.warning("warning: EMANE network %s has no associated model", node.name) return nem_id - for netif in node.netifs(): + for iface in node.get_ifaces(): logging.debug( - "building platform xml for interface(%s) nem_id(%s)", netif.name, nem_id + "building platform xml for interface(%s) nem_id(%s)", iface.name, nem_id ) # build nem xml - nem_definition = nem_file_name(node.model, netif) + nem_definition = nem_file_name(node.model, iface) nem_element = etree.Element( - "nem", id=str(nem_id), name=netif.localname, definition=nem_definition + "nem", id=str(nem_id), name=iface.localname, definition=nem_definition ) # check if this is an external transport, get default config if an interface # specific one does not exist - config = emane_manager.getifcconfig(node.model.id, netif, node.model.name) + config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) if is_external(config): nem_element.set("transport", "external") @@ -180,9 +180,9 @@ def build_node_platform_xml( add_param(nem_element, transport_endpoint, config[transport_endpoint]) else: # build transport xml - transport_type = netif.transport_type + transport_type = iface.transport_type if not transport_type: - logging.info("warning: %s interface type unsupported!", netif.name) + logging.info("warning: %s interface type unsupported!", iface.name) transport_type = TransportType.RAW transport_file = transport_file_name(node.id, transport_type) transport_element = etree.SubElement( @@ -190,14 +190,14 @@ def build_node_platform_xml( ) # add transport parameter - add_param(transport_element, "device", netif.name) + add_param(transport_element, "device", iface.name) # add nem entry - nem_entries[netif] = nem_element + nem_entries[iface] = nem_element # merging code - key = netif.node.id - if netif.transport_type == TransportType.RAW: + key = iface.node.id + if iface.transport_type == TransportType.RAW: key = "host" otadev = control_net.brname eventdev = control_net.brname @@ -229,10 +229,10 @@ def build_node_platform_xml( platform_element.append(nem_element) - node.setnemid(netif, nem_id) + node.setnemid(iface, nem_id) macstr = _hwaddr_prefix + ":00:00:" macstr += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - netif.sethwaddr(macstr) + iface.sethwaddr(macstr) # increment nem id nem_id += 1 @@ -280,19 +280,19 @@ def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None: vtype = TransportType.VIRTUAL rtype = TransportType.RAW - for netif in node.netifs(): + for iface in node.get_ifaces(): # check for interface specific emane configuration and write xml files - config = emane_manager.getifcconfig(node.model.id, netif, node.model.name) + config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) if config: - node.model.build_xml_files(config, netif) + node.model.build_xml_files(config, iface) # check transport type needed for interface - if netif.transport_type == TransportType.VIRTUAL: + if iface.transport_type == TransportType.VIRTUAL: need_virtual = True - vtype = netif.transport_type + vtype = iface.transport_type else: need_raw = True - rtype = netif.transport_type + rtype = iface.transport_type if need_virtual: build_transport_xml(emane_manager, node, vtype) @@ -494,70 +494,70 @@ def transport_file_name(node_id: int, transport_type: TransportType) -> str: return f"n{node_id}trans{transport_type.value}.xml" -def _basename(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def _basename(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Create name that is leveraged for configuration file creation. :param emane_model: emane model to create name for - :param interface: interface for this model + :param iface: interface for this model :return: basename used for file creation """ name = f"n{emane_model.id}" - if interface: - node_id = interface.node.id - if emane_model.session.emane.getifcconfig(node_id, interface, emane_model.name): - name = interface.localname.replace(".", "_") + if iface: + node_id = iface.node.id + if emane_model.session.emane.get_iface_config(node_id, iface, emane_model.name): + name = iface.localname.replace(".", "_") return f"{name}{emane_model.name}" -def nem_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def nem_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: nem xml filename """ - basename = _basename(emane_model, interface) + basename = _basename(emane_model, iface) append = "" - if interface and interface.transport_type == TransportType.RAW: + if iface and iface.transport_type == TransportType.RAW: append = "_raw" return f"{basename}nem{append}.xml" -def shim_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def shim_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the SHIM XML file, e.g. "commeffectshim.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: shim xml filename """ - name = _basename(emane_model, interface) + name = _basename(emane_model, iface) return f"{name}shim.xml" -def mac_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def mac_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the MAC XML file, e.g. "n3rfpipemac.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: mac xml filename """ - name = _basename(emane_model, interface) + name = _basename(emane_model, iface) return f"{name}mac.xml" -def phy_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def phy_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: phy xml filename """ - name = _basename(emane_model, interface) + name = _basename(emane_model, iface) return f"{name}phy.xml" diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index 948ec739b..767d0f455 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -20,13 +20,13 @@ # node one options.config_services = ["DefaultRoute", "IPForward"] node1 = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node1) - session.add_link(node1.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node1) + session.add_link(node1.id, switch.id, iface1_data=interface) # node two node2 = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node2) - session.add_link(node2.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node2) + session.add_link(node2.id, switch.id, iface1_data=interface) # start session and run services session.instantiate() diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index 8151a5903..c38f96af8 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -18,11 +18,11 @@ # create node one node1 = session.add_node(DockerNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(CoreNode) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index a7a70534a..5b62d4331 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -19,11 +19,11 @@ # create node one node1 = session.add_node(DockerNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(DockerNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index ef0579454..161cd823a 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -23,15 +23,15 @@ # node one node1 = session.add_node(DockerNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # node two node2 = session.add_node(DockerNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # node three node_three = session.add_node(CoreNode) - interface_three = prefixes.create_interface(node_three) + interface_three = prefixes.create_iface(node_three) # add links session.add_link(node1.id, switch.id, interface1_data) diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index e847016fb..0d781c19f 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -47,7 +47,7 @@ def main(args): node1_id = response.node_id # create link - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link from node one to switch: %s", response) @@ -59,7 +59,7 @@ def main(args): node2_id = response.node_id # create link - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link from node two to switch: %s", response) diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index 245322668..b8036db07 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -57,10 +57,10 @@ def main(): node2_id = response.node_id # links nodes to switch - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, emane_id, interface1) logging.info("created link: %s", response) - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, emane_id, interface1) logging.info("created link: %s", response) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 74e315c6c..1ed7c684b 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -53,10 +53,10 @@ def main(): node2_id = response.node_id # links nodes to switch - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link: %s", response) - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link: %s", response) diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index d60ca1be3..715d47066 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -65,10 +65,10 @@ def main(): node2_id = response.node_id # links nodes to switch - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, wlan_id, interface1) logging.info("created link: %s", response) - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, wlan_id, interface1) logging.info("created link: %s", response) diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index 49b689433..3d8eef6ab 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -18,11 +18,11 @@ # create node one node1 = session.add_node(LxcNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(CoreNode) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index 18af8037d..a7209b5cf 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -19,11 +19,11 @@ # create node one node1 = session.add_node(LxcNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(LxcNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 31a798877..9b6801f57 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -23,15 +23,15 @@ # node one node1 = session.add_node(LxcNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # node two node2 = session.add_node(LxcNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # node three node3 = session.add_node(CoreNode) - interface3_data = prefixes.create_interface(node3) + interface3_data = prefixes.create_iface(node3) # add links session.add_link(node1.id, switch.id, interface1_data) diff --git a/daemon/examples/myservices/sample.py b/daemon/examples/myservices/sample.py index 8c6dbe061..e0c9a232f 100644 --- a/daemon/examples/myservices/sample.py +++ b/daemon/examples/myservices/sample.py @@ -80,8 +80,8 @@ def generate_config(cls, node, filename): if filename == cls.configs[0]: cfg += "# auto-generated by MyService (sample.py)\n" - for ifc in node.netifs(): - cfg += f'echo "Node {node.name} has interface {ifc.name}"\n' + for iface in node.get_ifaces(): + cfg += f'echo "Node {node.name} has interface {iface.name}"\n' elif filename == cls.configs[1]: cfg += "echo hello" diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index d9b41ea42..3ee561081 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -59,10 +59,10 @@ def main(args): node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) - session.add_link(node1.id, emane_net.id, interface1_data=interface1_data) - session.add_link(node2.id, emane_net.id, interface1_data=interface2_data) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) + session.add_link(node1.id, emane_net.id, iface1_data=interface1_data) + session.add_link(node2.id, emane_net.id, iface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index affb16a8a..1573836aa 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -48,8 +48,8 @@ def main(args): node2 = session.add_node(LxcNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index 6bf33474d..1486c237b 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -48,8 +48,8 @@ def main(args): node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index 8991161e9..e9eb1e81d 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -52,10 +52,10 @@ def main(args): node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) - session.add_link(node1.id, switch.id, interface1_data=interface1_data) - session.add_link(node2.id, switch.id, interface1_data=interface2_data) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) + session.add_link(node1.id, switch.id, iface1_data=interface1_data) + session.add_link(node2.id, switch.id, iface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index d3f6652af..322e569f7 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -42,8 +42,8 @@ def main(): for i in range(NODES): node = session.add_node(CoreNode, options=options) node.setposition(x=150 * (i + 1), y=150) - interface = prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, emane_network.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index 1b939cd74..902e79e07 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -31,8 +31,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch_inject.py b/daemon/examples/python/switch_inject.py index 59816b192..89f70e050 100644 --- a/daemon/examples/python/switch_inject.py +++ b/daemon/examples/python/switch_inject.py @@ -33,8 +33,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index 0302bbd35..547a58605 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -35,8 +35,8 @@ def main(): options.set_position(0, 0) for _ in range(NODES): node = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node) - session.add_link(node.id, wlan.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, wlan.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 828b41fb8..f691621ac 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -319,12 +319,12 @@ message ThroughputsRequest { message ThroughputsEvent { int32 session_id = 1; repeated BridgeThroughput bridge_throughputs = 2; - repeated InterfaceThroughput interface_throughputs = 3; + repeated InterfaceThroughput iface_throughputs = 3; } message InterfaceThroughput { int32 node_id = 1; - int32 interface_id = 2; + int32 iface_id = 2; double throughput = 3; } @@ -374,7 +374,7 @@ message ConfigEvent { string bitmap = 8; string possible_values = 9; string groups = 10; - int32 interface = 11; + int32 iface_id = 11; int32 network_id = 12; string opaque = 13; } @@ -416,7 +416,7 @@ message GetNodeRequest { message GetNodeResponse { Node node = 1; - repeated Interface interfaces = 2; + repeated Interface ifaces = 2; } message EditNodeRequest { @@ -492,16 +492,16 @@ message AddLinkRequest { message AddLinkResponse { bool result = 1; - Interface interface1 = 2; - Interface interface2 = 3; + Interface iface1 = 2; + Interface iface2 = 3; } message EditLinkRequest { int32 session_id = 1; int32 node1_id = 2; int32 node2_id = 3; - int32 interface1_id = 4; - int32 interface2_id = 5; + int32 iface1_id = 4; + int32 iface2_id = 5; LinkOptions options = 6; } @@ -513,8 +513,8 @@ message DeleteLinkRequest { int32 session_id = 1; int32 node1_id = 2; int32 node2_id = 3; - int32 interface1_id = 4; - int32 interface2_id = 5; + int32 iface1_id = 4; + int32 iface2_id = 5; } message DeleteLinkResponse { @@ -561,7 +561,7 @@ message GetInterfacesRequest { } message GetInterfacesResponse { - repeated string interfaces = 1; + repeated string ifaces = 1; } message ExecuteScriptRequest { @@ -705,8 +705,8 @@ message Link { int32 node1_id = 1; int32 node2_id = 2; LinkType.Enum type = 3; - Interface interface1 = 4; - Interface interface2 = 5; + Interface iface1 = 4; + Interface iface2 = 5; LinkOptions options = 6; int32 network_id = 7; string label = 8; diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index e41897002..ac5456fdc 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -32,7 +32,7 @@ message GetEmaneModelsResponse { message GetEmaneModelConfigRequest { int32 session_id = 1; int32 node_id = 2; - int32 interface = 3; + int32 iface_id = 3; string model = 4; } @@ -57,7 +57,7 @@ message GetEmaneModelConfigsResponse { message ModelConfig { int32 node_id = 1; string model = 2; - int32 interface = 3; + int32 iface_id = 3; map config = 4; } repeated ModelConfig configs = 1; @@ -86,7 +86,7 @@ message EmaneLinkResponse { message EmaneModelConfig { int32 node_id = 1; - int32 interface_id = 2; + int32 iface_id = 2; string model = 3; map config = 4; } @@ -95,10 +95,10 @@ message EmanePathlossesRequest { int32 session_id = 1; int32 node1_id = 2; float rx1 = 3; - int32 interface1_id = 4; + int32 iface1_id = 4; int32 node2_id = 5; float rx2 = 6; - int32 interface2_id = 7; + int32 iface2_id = 7; } message EmanePathlossesResponse { diff --git a/daemon/scripts/core-route-monitor b/daemon/scripts/core-route-monitor index b12e6205b..d644ae1be 100755 --- a/daemon/scripts/core-route-monitor +++ b/daemon/scripts/core-route-monitor @@ -101,8 +101,8 @@ class RouterMonitor: node_map[node.id] = node.channel if self.src_id is None: response = self.core.get_node(self.session, node.id) - for netif in response.interfaces: - if self.src == netif.ip4: + for iface in response.ifaces: + if self.src == iface.ip4: self.src_id = node.id break except grpc.RpcError: diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 9d54d9c20..c3315e7c3 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -89,7 +89,7 @@ def ip_prefixes(): @pytest.fixture(scope="session") -def interface_helper(): +def iface_helper(): return InterfaceHelper(ip4_prefix="10.83.0.0/16") diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 15e3d869c..e1c7938b9 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -79,8 +79,8 @@ def test_models( for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, emane_network.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -119,8 +119,8 @@ def test_xml_emane( for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, emane_network.id, iface1_data=iface_data) # instantiate session session.instantiate() diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 626f84a7b..5771f7ad8 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -53,8 +53,8 @@ def test_wired_ping( # link nodes to net node for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, net_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, net_node.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -80,8 +80,8 @@ def test_vnode_client(self, request, session: Session, ip_prefixes: IpPrefixes): # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface_data) # get node client for testing client = node1.client @@ -96,9 +96,9 @@ def test_vnode_client(self, request, session: Session, ip_prefixes: IpPrefixes): if not request.config.getoption("mock"): assert client.check_cmd("echo hello") == "hello" - def test_netif(self, session: Session, ip_prefixes: IpPrefixes): + def test_iface(self, session: Session, ip_prefixes: IpPrefixes): """ - Test netif methods. + Test interface methods. :param session: session for test :param ip_prefixes: generates ip addresses for nodes @@ -113,8 +113,8 @@ def test_netif(self, session: Session, ip_prefixes: IpPrefixes): # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface) # instantiate session session.instantiate() @@ -126,19 +126,19 @@ def test_netif(self, session: Session, ip_prefixes: IpPrefixes): assert node1.commonnets(node2) assert node2.commonnets(node1) - # check we can retrieve netif index - assert node1.ifname(0) - assert node2.ifname(0) + # check we can retrieve interface id + assert 0 in node1.ifaces + assert 0 in node2.ifaces # check interface parameters - interface = node1.netif(0) - interface.setparam("test", 1) - assert interface.getparam("test") == 1 - assert interface.getparams() + iface = node1.get_iface(0) + iface.setparam("test", 1) + assert iface.getparam("test") == 1 + assert iface.getparams() - # delete netif and test that if no longer exists - node1.delnetif(0) - assert not node1.netif(0) + # delete interface and test that if no longer exists + node1.delete_iface(0) + assert 0 not in node1.ifaces def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes): """ @@ -160,8 +160,8 @@ def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes): # link nodes for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface1_data=interface) + iface_id = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan_node.id, iface1_data=iface_id) # instantiate session session.instantiate() @@ -190,8 +190,8 @@ def test_mobility(self, session: Session, ip_prefixes: IpPrefixes): # link nodes for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface1_data=interface) + iface_id = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan_node.id, iface1_data=iface_id) # configure mobility script for session config = { diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 8beb4b9a1..23ff03018 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -42,15 +42,17 @@ def test_start_session(self, grpc_server: CoreGrpcServer): id=3, type=NodeTypes.WIRELESS_LAN.value, position=position ) nodes = [node1, node2, wlan_node] - interface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") - interface1 = interface_helper.create_interface(node1.id, 0) - interface2 = interface_helper.create_interface(node2.id, 0) + iface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") + iface1_id = 0 + iface1 = iface_helper.create_iface(node1.id, iface1_id) + iface2_id = 0 + iface2 = iface_helper.create_iface(node2.id, iface2_id) link = core_pb2.Link( type=core_pb2.LinkType.WIRED, node1_id=node1.id, node2_id=node2.id, - interface1=interface1, - interface2=interface2, + iface1=iface1, + iface2=iface2, ) links = [link] hook = core_pb2.Hook( @@ -81,7 +83,7 @@ def test_start_session(self, grpc_server: CoreGrpcServer): model_config_value = "500000" model_config = EmaneModelConfig( node_id=model_node_id, - interface_id=-1, + iface_id=-1, model=EmaneIeee80211abgModel.name, config={model_config_key: model_config_value}, ) @@ -131,8 +133,8 @@ def test_start_session(self, grpc_server: CoreGrpcServer): assert node1.id in session.nodes assert node2.id in session.nodes assert wlan_node.id in session.nodes - assert session.nodes[node1.id].netif(0) is not None - assert session.nodes[node2.id].netif(0) is not None + assert iface1_id in session.nodes[node1.id].ifaces + assert iface2_id in session.nodes[node2.id].ifaces hook_file, hook_data = session.hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data @@ -522,8 +524,8 @@ def test_get_node_links(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefix session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface_data) # then with client.context_connect(): @@ -540,17 +542,15 @@ def test_get_node_links_exception( session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface_data) # then with pytest.raises(grpc.RpcError): with client.context_connect(): client.get_node_links(session.id, 3) - def test_add_link( - self, grpc_server: CoreGrpcServer, interface_helper: InterfaceHelper - ): + def test_add_link(self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() @@ -559,16 +559,16 @@ def test_add_link( assert len(switch.all_link_data()) == 0 # then - interface = interface_helper.create_interface(node.id, 0) + iface = iface_helper.create_iface(node.id, 0) with client.context_connect(): - response = client.add_link(session.id, node.id, switch.id, interface) + response = client.add_link(session.id, node.id, switch.id, iface) # then assert response.result is True assert len(switch.all_link_data()) == 1 def test_add_link_exception( - self, grpc_server: CoreGrpcServer, interface_helper: InterfaceHelper + self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper ): # given client = CoreGrpcClient() @@ -576,10 +576,10 @@ def test_add_link_exception( node = session.add_node(CoreNode) # then - interface = interface_helper.create_interface(node.id, 0) + iface = iface_helper.create_iface(node.id, 0) with pytest.raises(grpc.RpcError): with client.context_connect(): - client.add_link(session.id, 1, 3, interface) + client.add_link(session.id, 1, 3, iface) def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given @@ -587,8 +587,8 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface) options = core_pb2.LinkOptions(bandwidth=30000) link = switch.all_link_data()[0] assert options.bandwidth != link.bandwidth @@ -596,7 +596,7 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # then with client.context_connect(): response = client.edit_link( - session.id, node.id, switch.id, options, interface1_id=interface.id + session.id, node.id, switch.id, options, iface1_id=iface.id ) # then @@ -609,10 +609,10 @@ def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node1 = session.add_node(CoreNode) - interface1 = ip_prefixes.create_interface(node1) + iface1 = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) - interface2 = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1, interface2) + iface2 = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1, iface2) link_node = None for node_id in session.nodes: node = session.nodes[node_id] @@ -624,7 +624,7 @@ def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) # then with client.context_connect(): response = client.delete_link( - session.id, node1.id, node2.id, interface1.id, interface2.id + session.id, node1.id, node2.id, iface1.id, iface2.id ) # then @@ -729,7 +729,7 @@ def test_get_emane_model_configs(self, grpc_server: CoreGrpcServer): assert emane_network.id == model_config.node_id assert model_config.model == EmaneIeee80211abgModel.name assert len(model_config.config) > 0 - assert model_config.interface == -1 + assert model_config.iface_id == -1 def test_set_emane_model_config(self, grpc_server: CoreGrpcServer): # given @@ -1028,8 +1028,8 @@ def test_link_events(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan.id, interface) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan.id, iface) link_data = wlan.all_link_data()[0] queue = Queue() diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index d3b9362d4..c413295a5 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -107,15 +107,15 @@ def test_link_add_node_to_net(self, coretlv: CoreHandler): switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) @@ -131,15 +131,15 @@ def test_link_add_net_to_node(self, coretlv: CoreHandler): switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface2_ip4 = str(ip_prefix[node1_id]) + iface2_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, switch_id), (LinkTlvs.N2_NUMBER, node1_id), - (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, 24), + (LinkTlvs.IFACE2_NUMBER, 0), + (LinkTlvs.IFACE2_IP4, iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, 24), ], ) @@ -155,19 +155,19 @@ def test_link_add_node_to_node(self, coretlv: CoreHandler): node2_id = 2 coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) - interface2_ip4 = str(ip_prefix[node2_id]) + iface1_ip4 = str(ip_prefix[node1_id]) + iface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, node2_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), - (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE2_NUMBER, 0), + (LinkTlvs.IFACE2_IP4, iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, 24), ], ) @@ -185,15 +185,15 @@ def test_link_update(self, coretlv: CoreHandler): switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -209,7 +209,7 @@ def test_link_update(self, coretlv: CoreHandler): [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_NUMBER, 0), (LinkTlvs.BANDWIDTH, bandwidth), ], ) @@ -227,18 +227,18 @@ def test_link_delete_node_to_node(self, coretlv: CoreHandler): node2_id = 2 coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) - interface2_ip4 = str(ip_prefix[node2_id]) + iface1_ip4 = str(ip_prefix[node1_id]) + iface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, node2_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), - (LinkTlvs.INTERFACE2_IP4, interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE2_IP4, iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -253,8 +253,8 @@ def test_link_delete_node_to_node(self, coretlv: CoreHandler): [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, node2_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE2_NUMBER, 0), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE2_NUMBER, 0), ], ) coretlv.handle_message(message) @@ -271,15 +271,15 @@ def test_link_delete_node_to_net(self, coretlv: CoreHandler): switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -292,7 +292,7 @@ def test_link_delete_node_to_net(self, coretlv: CoreHandler): [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_NUMBER, 0), ], ) coretlv.handle_message(message) @@ -307,15 +307,15 @@ def test_link_delete_net_to_node(self, coretlv: CoreHandler): switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -328,7 +328,7 @@ def test_link_delete_net_to_node(self, coretlv: CoreHandler): [ (LinkTlvs.N1_NUMBER, switch_id), (LinkTlvs.N2_NUMBER, node1_id), - (LinkTlvs.INTERFACE2_NUMBER, 0), + (LinkTlvs.IFACE2_NUMBER, 0), ], ) coretlv.handle_message(message) diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 819e2be8a..fea4f4f85 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -14,9 +14,9 @@ def create_ptp_network( node2 = session.add_node(CoreNode) # link nodes to net node - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1_data, interface2_data) + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) # instantiate session session.instantiate() @@ -29,41 +29,41 @@ def test_add_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) # when - session.add_link(node1.id, node2.id, interface1_data, interface2_data) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) # then - assert node1.netif(interface1_data.id) - assert node2.netif(interface2_data.id) + assert node1.get_iface(iface1_data.id) + assert node2.get_iface(iface2_data.id) def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(SwitchNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) # when - session.add_link(node1.id, node2.id, interface1_data=interface1_data) + session.add_link(node1.id, node2.id, iface1_data=iface1_data) # then assert node2.all_link_data() - assert node1.netif(interface1_data.id) + assert node1.get_iface(iface1_data.id) def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(SwitchNode) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) + iface2_data = ip_prefixes.create_iface(node2) # when - session.add_link(node1.id, node2.id, interface2_data=interface2_data) + session.add_link(node1.id, node2.id, iface2_data=iface2_data) # then assert node1.all_link_data() - assert node2.netif(interface2_data.id) + assert node2.get_iface(iface2_data.id) def test_add_net_to_net(self, session): # given @@ -85,29 +85,29 @@ def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): jitter = 10 node1 = session.add_node(CoreNode) node2 = session.add_node(SwitchNode) - interface1_data = ip_prefixes.create_interface(node1) - session.add_link(node1.id, node2.id, interface1_data) - interface1 = node1.netif(interface1_data.id) - assert interface1.getparam("delay") != delay - assert interface1.getparam("bw") != bandwidth - assert interface1.getparam("loss") != loss - assert interface1.getparam("duplicate") != dup - assert interface1.getparam("jitter") != jitter + iface1_data = ip_prefixes.create_iface(node1) + session.add_link(node1.id, node2.id, iface1_data) + iface1 = node1.get_iface(iface1_data.id) + assert iface1.getparam("delay") != delay + assert iface1.getparam("bw") != bandwidth + assert iface1.getparam("loss") != loss + assert iface1.getparam("duplicate") != dup + assert iface1.getparam("jitter") != jitter # when options = LinkOptions( delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( - node1.id, node2.id, interface1_id=interface1_data.id, options=options + node1.id, node2.id, iface1_id=iface1_data.id, options=options ) # then - assert interface1.getparam("delay") == delay - assert interface1.getparam("bw") == bandwidth - assert interface1.getparam("loss") == loss - assert interface1.getparam("duplicate") == dup - assert interface1.getparam("jitter") == jitter + assert iface1.getparam("delay") == delay + assert iface1.getparam("bw") == bandwidth + assert iface1.getparam("loss") == loss + assert iface1.getparam("duplicate") == dup + assert iface1.getparam("jitter") == jitter def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -118,29 +118,29 @@ def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): jitter = 10 node1 = session.add_node(SwitchNode) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface2_data=interface2_data) - interface2 = node2.netif(interface2_data.id) - assert interface2.getparam("delay") != delay - assert interface2.getparam("bw") != bandwidth - assert interface2.getparam("loss") != loss - assert interface2.getparam("duplicate") != dup - assert interface2.getparam("jitter") != jitter + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface2_data=iface2_data) + iface2 = node2.get_iface(iface2_data.id) + assert iface2.getparam("delay") != delay + assert iface2.getparam("bw") != bandwidth + assert iface2.getparam("loss") != loss + assert iface2.getparam("duplicate") != dup + assert iface2.getparam("jitter") != jitter # when options = LinkOptions( delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( - node1.id, node2.id, interface2_id=interface2_data.id, options=options + node1.id, node2.id, iface2_id=iface2_data.id, options=options ) # then - assert interface2.getparam("delay") == delay - assert interface2.getparam("bw") == bandwidth - assert interface2.getparam("loss") == loss - assert interface2.getparam("duplicate") == dup - assert interface2.getparam("jitter") == jitter + assert iface2.getparam("delay") == delay + assert iface2.getparam("bw") == bandwidth + assert iface2.getparam("loss") == loss + assert iface2.getparam("duplicate") == dup + assert iface2.getparam("jitter") == jitter def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -151,83 +151,81 @@ def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): jitter = 10 node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1_data, interface2_data) - interface1 = node1.netif(interface1_data.id) - interface2 = node2.netif(interface2_data.id) - assert interface1.getparam("delay") != delay - assert interface1.getparam("bw") != bandwidth - assert interface1.getparam("loss") != loss - assert interface1.getparam("duplicate") != dup - assert interface1.getparam("jitter") != jitter - assert interface2.getparam("delay") != delay - assert interface2.getparam("bw") != bandwidth - assert interface2.getparam("loss") != loss - assert interface2.getparam("duplicate") != dup - assert interface2.getparam("jitter") != jitter + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) + iface1 = node1.get_iface(iface1_data.id) + iface2 = node2.get_iface(iface2_data.id) + assert iface1.getparam("delay") != delay + assert iface1.getparam("bw") != bandwidth + assert iface1.getparam("loss") != loss + assert iface1.getparam("duplicate") != dup + assert iface1.getparam("jitter") != jitter + assert iface2.getparam("delay") != delay + assert iface2.getparam("bw") != bandwidth + assert iface2.getparam("loss") != loss + assert iface2.getparam("duplicate") != dup + assert iface2.getparam("jitter") != jitter # when options = LinkOptions( delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) - session.update_link( - node1.id, node2.id, interface1_data.id, interface2_data.id, options - ) + session.update_link(node1.id, node2.id, iface1_data.id, iface2_data.id, options) # then - assert interface1.getparam("delay") == delay - assert interface1.getparam("bw") == bandwidth - assert interface1.getparam("loss") == loss - assert interface1.getparam("duplicate") == dup - assert interface1.getparam("jitter") == jitter - assert interface2.getparam("delay") == delay - assert interface2.getparam("bw") == bandwidth - assert interface2.getparam("loss") == loss - assert interface2.getparam("duplicate") == dup - assert interface2.getparam("jitter") == jitter + assert iface1.getparam("delay") == delay + assert iface1.getparam("bw") == bandwidth + assert iface1.getparam("loss") == loss + assert iface1.getparam("duplicate") == dup + assert iface1.getparam("jitter") == jitter + assert iface2.getparam("delay") == delay + assert iface2.getparam("bw") == bandwidth + assert iface2.getparam("loss") == loss + assert iface2.getparam("duplicate") == dup + assert iface2.getparam("jitter") == jitter def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1_data, interface2_data) - assert node1.netif(interface1_data.id) - assert node2.netif(interface2_data.id) + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) + assert node1.get_iface(iface1_data.id) + assert node2.get_iface(iface2_data.id) # when - session.delete_link(node1.id, node2.id, interface1_data.id, interface2_data.id) + session.delete_link(node1.id, node2.id, iface1_data.id, iface2_data.id) # then - assert not node1.netif(interface1_data.id) - assert not node2.netif(interface2_data.id) + assert iface1_data.id not in node1.ifaces + assert iface2_data.id not in node2.ifaces def test_delete_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(SwitchNode) - interface1_data = ip_prefixes.create_interface(node1) - session.add_link(node1.id, node2.id, interface1_data) - assert node1.netif(interface1_data.id) + iface1_data = ip_prefixes.create_iface(node1) + session.add_link(node1.id, node2.id, iface1_data) + assert node1.get_iface(iface1_data.id) # when - session.delete_link(node1.id, node2.id, interface1_id=interface1_data.id) + session.delete_link(node1.id, node2.id, iface1_id=iface1_data.id) # then - assert not node1.netif(interface1_data.id) + assert iface1_data.id not in node1.ifaces def test_delete_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(SwitchNode) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface2_data=interface2_data) - assert node2.netif(interface2_data.id) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface2_data=iface2_data) + assert node2.get_iface(iface2_data.id) # when - session.delete_link(node1.id, node2.id, interface2_id=interface2_data.id) + session.delete_link(node1.id, node2.id, iface2_id=iface2_data.id) # then - assert not node2.netif(interface2_data.id) + assert iface2_data.id not in node2.ifaces diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 0cbdb8aeb..d7e435abd 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -53,53 +53,53 @@ def test_node_sethwaddr(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) mac = "aa:aa:aa:ff:ff:ff" # when - node.sethwaddr(interface.netindex, mac) + node.sethwaddr(iface.node_id, mac) # then - assert interface.hwaddr == mac + assert iface.hwaddr == mac def test_node_sethwaddr_exception(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) mac = "aa:aa:aa:ff:ff:fff" # when with pytest.raises(CoreError): - node.sethwaddr(interface.netindex, mac) + node.sethwaddr(iface.node_id, mac) def test_node_addaddr(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) addr = "192.168.0.1/24" # when - node.addaddr(interface.netindex, addr) + node.addaddr(iface.node_id, addr) # then - assert interface.addrlist[0] == addr + assert iface.addrlist[0] == addr def test_node_addaddr_exception(self, session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) addr = "256.168.0.1/24" # when with pytest.raises(CoreError): - node.addaddr(interface.netindex, addr) + node.addaddr(iface.node_id, addr) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 0b44a3540..55f5a2abe 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -73,8 +73,8 @@ def test_xml_ptp( # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -128,8 +128,8 @@ def test_xml_ptp_services( # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface_data) # set custom values for node service session.services.set_service(node1.id, SshService.name) @@ -197,8 +197,8 @@ def test_xml_mobility( # link nodes for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan_node.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -299,7 +299,7 @@ def test_link_options( """ # create nodes node1 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) switch = session.add_node(SwitchNode) # create link @@ -309,7 +309,7 @@ def test_link_options( options.jitter = 10 options.delay = 30 options.dup = 5 - session.add_link(node1.id, switch.id, interface1_data, options=options) + session.add_link(node1.id, switch.id, iface1_data, options=options) # instantiate session session.instantiate() @@ -365,9 +365,9 @@ def test_link_options_ptp( """ # create nodes node1 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) + iface2_data = ip_prefixes.create_iface(node2) # create link options = LinkOptions() @@ -376,7 +376,7 @@ def test_link_options_ptp( options.jitter = 10 options.delay = 30 options.dup = 5 - session.add_link(node1.id, node2.id, interface1_data, interface2_data, options) + session.add_link(node1.id, node2.id, iface1_data, iface2_data, options) # instantiate session session.instantiate() @@ -432,9 +432,9 @@ def test_link_options_bidirectional( """ # create nodes node1 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) + iface2_data = ip_prefixes.create_iface(node2) # create link options1 = LinkOptions() @@ -444,7 +444,7 @@ def test_link_options_bidirectional( options1.loss = 10.5 options1.dup = 5 options1.jitter = 5 - session.add_link(node1.id, node2.id, interface1_data, interface2_data, options1) + session.add_link(node1.id, node2.id, iface1_data, iface2_data, options1) options2 = LinkOptions() options2.unidirectional = 1 options2.bandwidth = 10000 @@ -453,7 +453,7 @@ def test_link_options_bidirectional( options2.dup = 10 options2.jitter = 10 session.update_link( - node2.id, node1.id, interface2_data.id, interface1_data.id, options2 + node2.id, node1.id, iface2_data.id, iface1_data.id, options2 ) # instantiate session diff --git a/docs/scripting.md b/docs/scripting.md index 59bc02ae4..18666a9ad 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -61,8 +61,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=interface) # instantiate session session.instantiate() From eeca33e72240aa83e355f584753c8f1e2038f91a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 12:50:24 -0700 Subject: [PATCH 026/210] combined core.emulator.data and core.emulator.emudata, updated LinkData to leverage InterfaceData, instead of repeated interface fields, removed session from LinkData and LinkOptions --- daemon/core/api/grpc/client.py | 2 +- daemon/core/api/grpc/grpcutils.py | 39 ++-- daemon/core/api/grpc/server.py | 3 +- daemon/core/api/tlv/corehandlers.py | 43 ++-- daemon/core/emane/commeffect.py | 2 +- daemon/core/emane/emanemodel.py | 2 +- daemon/core/emane/nodes.py | 3 +- daemon/core/emulator/data.py | 221 +++++++++++++++++-- daemon/core/emulator/emudata.py | 206 ----------------- daemon/core/emulator/session.py | 4 +- daemon/core/location/mobility.py | 3 +- daemon/core/nodes/base.py | 26 +-- daemon/core/nodes/network.py | 55 ++--- daemon/core/nodes/physical.py | 2 +- daemon/core/xml/corexml.py | 71 ++---- daemon/examples/configservices/testing.py | 2 +- daemon/examples/docker/docker2core.py | 2 +- daemon/examples/docker/docker2docker.py | 2 +- daemon/examples/docker/switch.py | 2 +- daemon/examples/lxd/lxd2core.py | 2 +- daemon/examples/lxd/lxd2lxd.py | 2 +- daemon/examples/lxd/switch.py | 2 +- daemon/examples/python/distributed_emane.py | 2 +- daemon/examples/python/distributed_lxd.py | 2 +- daemon/examples/python/distributed_ptp.py | 2 +- daemon/examples/python/distributed_switch.py | 2 +- daemon/examples/python/emane80211.py | 2 +- daemon/examples/python/switch.py | 2 +- daemon/examples/python/switch_inject.py | 2 +- daemon/examples/python/wlan.py | 2 +- daemon/tests/conftest.py | 2 +- daemon/tests/emane/test_emane.py | 2 +- daemon/tests/test_core.py | 2 +- daemon/tests/test_distributed.py | 2 +- daemon/tests/test_grpc.py | 3 +- daemon/tests/test_links.py | 2 +- daemon/tests/test_nodes.py | 2 +- daemon/tests/test_xml.py | 2 +- docs/scripting.md | 2 +- 39 files changed, 332 insertions(+), 399 deletions(-) delete mode 100644 daemon/core/emulator/emudata.py diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 47aaef63f..68bfc5022 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -92,7 +92,7 @@ WlanLinkRequest, WlanLinkResponse, ) -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes class InterfaceHelper: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index f2f857985..095c4d0cc 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -11,8 +11,7 @@ from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig from core.config import ConfigurableOptions from core.emane.nodes import EmaneNet -from core.emulator.data import LinkData -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.session import Session from core.nodes.base import CoreNode, NodeBase @@ -308,6 +307,18 @@ def parse_emane_model_id(_id: int) -> Tuple[int, int]: return node_id, iface_id +def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: + return core_pb2.Interface( + id=iface_data.id, + name=iface_data.name, + mac=iface_data.mac, + ip4=iface_data.ip4, + ip4mask=iface_data.ip4_mask, + ip6=iface_data.ip6, + ip6mask=iface_data.ip6_mask, + ) + + def convert_link(link_data: LinkData) -> core_pb2.Link: """ Convert link_data into core protobuf link. @@ -316,27 +327,11 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: :return: core protobuf Link """ iface1 = None - if link_data.iface1_id is not None: - iface1 = core_pb2.Interface( - id=link_data.iface1_id, - name=link_data.iface1_name, - mac=convert_value(link_data.iface1_mac), - ip4=convert_value(link_data.iface1_ip4), - ip4mask=link_data.iface1_ip4_mask, - ip6=convert_value(link_data.iface1_ip6), - ip6mask=link_data.iface1_ip6_mask, - ) + if link_data.iface1 is not None: + iface1 = convert_iface(link_data.iface1) iface2 = None - if link_data.iface2_id is not None: - iface2 = core_pb2.Interface( - id=link_data.iface2_id, - name=link_data.iface2_name, - mac=convert_value(link_data.iface2_mac), - ip4=convert_value(link_data.iface2_ip4), - ip4mask=link_data.iface2_ip4_mask, - ip6=convert_value(link_data.iface2_ip6), - ip6mask=link_data.iface2_ip6_mask, - ) + if link_data.iface2 is not None: + iface2 = convert_iface(link_data.iface2) options = core_pb2.LinkOptions( opaque=link_data.opaque, jitter=link_data.jitter, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 87b69a774..1be601167 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -108,8 +108,7 @@ WlanLinkResponse, ) from core.emulator.coreemu import CoreEmu -from core.emulator.data import LinkData -from core.emulator.emudata import LinkOptions, NodeOptions +from core.emulator.data import LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index b09a37fef..88906e0cc 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -29,8 +29,15 @@ NodeTlvs, SessionTlvs, ) -from core.emulator.data import ConfigData, EventData, ExceptionData, FileData -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions +from core.emulator.data import ( + ConfigData, + EventData, + ExceptionData, + FileData, + InterfaceData, + LinkOptions, + NodeOptions, +) from core.emulator.enumerations import ( ConfigDataTypes, EventTypes, @@ -342,6 +349,12 @@ def handle_broadcast_link(self, link_data): dup = "" if link_data.dup is not None: dup = str(link_data.dup) + iface1 = link_data.iface1 + if iface1 is None: + iface1 = InterfaceData() + iface2 = link_data.iface2 + if iface2 is None: + iface2 = InterfaceData() tlv_data = structutils.pack_values( coreapi.CoreLinkTlv, @@ -355,7 +368,6 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.JITTER, link_data.jitter), (LinkTlvs.MER, link_data.mer), (LinkTlvs.BURST, link_data.burst), - (LinkTlvs.SESSION, link_data.session), (LinkTlvs.MBURST, link_data.mburst), (LinkTlvs.TYPE, link_data.link_type.value), (LinkTlvs.GUI_ATTRIBUTES, link_data.gui_attributes), @@ -363,18 +375,18 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.EMULATION_ID, link_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), (LinkTlvs.KEY, link_data.key), - (LinkTlvs.IFACE1_NUMBER, link_data.iface1_id), - (LinkTlvs.IFACE1_IP4, link_data.iface1_ip4), - (LinkTlvs.IFACE1_IP4_MASK, link_data.iface1_ip4_mask), - (LinkTlvs.IFACE1_MAC, link_data.iface1_mac), - (LinkTlvs.IFACE1_IP6, link_data.iface1_ip6), - (LinkTlvs.IFACE1_IP6_MASK, link_data.iface1_ip6_mask), - (LinkTlvs.IFACE2_NUMBER, link_data.iface2_id), - (LinkTlvs.IFACE2_IP4, link_data.iface2_ip4), - (LinkTlvs.IFACE2_IP4_MASK, link_data.iface2_ip4_mask), - (LinkTlvs.IFACE2_MAC, link_data.iface2_mac), - (LinkTlvs.IFACE2_IP6, link_data.iface2_ip6), - (LinkTlvs.IFACE2_IP6_MASK, link_data.iface2_ip6_mask), + (LinkTlvs.IFACE1_NUMBER, iface1.id), + (LinkTlvs.IFACE1_IP4, iface1.ip4), + (LinkTlvs.IFACE1_IP4_MASK, iface1.ip4_mask), + (LinkTlvs.IFACE1_MAC, iface1.mac), + (LinkTlvs.IFACE1_IP6, iface1.ip6), + (LinkTlvs.IFACE1_IP6_MASK, iface1.ip6_mask), + (LinkTlvs.IFACE2_NUMBER, iface2.id), + (LinkTlvs.IFACE2_IP4, iface2.ip4), + (LinkTlvs.IFACE2_IP4_MASK, iface2.ip4_mask), + (LinkTlvs.IFACE2_MAC, iface2.mac), + (LinkTlvs.IFACE2_IP6, iface2.ip6), + (LinkTlvs.IFACE2_IP6_MASK, iface2.ip6_mask), (LinkTlvs.OPAQUE, link_data.opaque), ], ) @@ -774,7 +786,6 @@ def handle_link_message(self, message): options = LinkOptions(type=link_type) options.delay = message.get_tlv(LinkTlvs.DELAY.value) options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) - options.session = message.get_tlv(LinkTlvs.SESSION.value) options.loss = message.get_tlv(LinkTlvs.LOSS.value) options.dup = message.get_tlv(LinkTlvs.DUP.value) options.jitter = message.get_tlv(LinkTlvs.JITTER.value) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 0f441d76b..610099f1a 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -11,7 +11,7 @@ from core.config import ConfigGroup, Configuration from core.emane import emanemanifest, emanemodel from core.emane.nodes import EmaneNet -from core.emulator.emudata import LinkOptions +from core.emulator.data import LinkOptions from core.emulator.enumerations import TransportType from core.nodes.interface import CoreInterface from core.xml import emanexml diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 1a14011a4..43fbc0fba 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -8,7 +8,7 @@ from core.config import ConfigGroup, Configuration from core.emane import emanemanifest from core.emane.nodes import EmaneNet -from core.emulator.emudata import LinkOptions +from core.emulator.data import LinkOptions from core.emulator.enumerations import ConfigDataTypes, TransportType from core.errors import CoreError from core.location.mobility import WirelessModel diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index eed51ff28..c28f1382e 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -6,9 +6,8 @@ import logging from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type -from core.emulator.data import LinkData +from core.emulator.data import LinkData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 47f458203..c08a70f0d 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -1,10 +1,12 @@ """ CORE data objects. """ +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, List, Optional, Tuple -from dataclasses import dataclass -from typing import List, Tuple +import netaddr +from core import utils from core.emulator.enumerations import ( EventTypes, ExceptionLevels, @@ -13,6 +15,9 @@ NodeTypes, ) +if TYPE_CHECKING: + from core.nodes.base import CoreNode + @dataclass class ConfigData: @@ -93,6 +98,57 @@ class NodeData: source: str = None +@dataclass +class InterfaceData: + """ + Convenience class for storing interface data. + """ + + id: int = None + name: str = None + mac: str = None + ip4: str = None + ip4_mask: int = None + ip6: str = None + ip6_mask: int = None + + def get_addresses(self) -> List[str]: + """ + Returns a list of ip4 and ip6 addresses when present. + + :return: list of addresses + """ + addresses = [] + if self.ip4 and self.ip4_mask: + addresses.append(f"{self.ip4}/{self.ip4_mask}") + if self.ip6 and self.ip6_mask: + addresses.append(f"{self.ip6}/{self.ip6_mask}") + return addresses + + +@dataclass +class LinkOptions: + """ + Options for creating and updating links within core. + """ + + type: LinkTypes = LinkTypes.WIRED + delay: int = None + bandwidth: int = None + loss: float = None + dup: int = None + jitter: int = None + mer: int = None + burst: int = None + mburst: int = None + gui_attributes: str = None + unidirectional: bool = None + emulation_id: int = None + network_id: int = None + key: int = None + opaque: str = None + + @dataclass class LinkData: message_type: MessageFlags = None @@ -106,7 +162,6 @@ class LinkData: jitter: float = None mer: float = None burst: float = None - session: int = None mburst: float = None link_type: LinkTypes = None gui_attributes: str = None @@ -114,19 +169,151 @@ class LinkData: emulation_id: int = None network_id: int = None key: int = None - iface1_id: int = None - iface1_name: str = None - iface1_ip4: str = None - iface1_ip4_mask: int = None - iface1_mac: str = None - iface1_ip6: str = None - iface1_ip6_mask: int = None - iface2_id: int = None - iface2_name: str = None - iface2_ip4: str = None - iface2_ip4_mask: int = None - iface2_mac: str = None - iface2_ip6: str = None - iface2_ip6_mask: int = None + iface1: InterfaceData = None + iface2: InterfaceData = None opaque: str = None color: str = None + + +class IpPrefixes: + """ + Convenience class to help generate IP4 and IP6 addresses for nodes within CORE. + """ + + def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: + """ + Creates an IpPrefixes object. + + :param ip4_prefix: ip4 prefix to use for generation + :param ip6_prefix: ip6 prefix to use for generation + :raises ValueError: when both ip4 and ip6 prefixes have not been provided + """ + if not ip4_prefix and not ip6_prefix: + raise ValueError("ip4 or ip6 must be provided") + + self.ip4 = None + if ip4_prefix: + self.ip4 = netaddr.IPNetwork(ip4_prefix) + self.ip6 = None + if ip6_prefix: + self.ip6 = netaddr.IPNetwork(ip6_prefix) + + def ip4_address(self, node_id: int) -> str: + """ + Convenience method to return the IP4 address for a node. + + :param node_id: node id to get IP4 address for + :return: IP4 address or None + """ + if not self.ip4: + raise ValueError("ip4 prefixes have not been set") + return str(self.ip4[node_id]) + + def ip6_address(self, node_id: int) -> str: + """ + Convenience method to return the IP6 address for a node. + + :param node_id: node id to get IP6 address for + :return: IP4 address or None + """ + if not self.ip6: + raise ValueError("ip6 prefixes have not been set") + return str(self.ip6[node_id]) + + def gen_iface(self, node_id: int, name: str = None, mac: str = None): + """ + Creates interface data for linking nodes, using the nodes unique id for + generation, along with a random mac address, unless provided. + + :param node_id: node id to create an interface for + :param name: name to set for interface, default is eth{id} + :param mac: mac address to use for this interface, default is random + generation + :return: new interface data for the provided node + """ + # generate ip4 data + ip4 = None + ip4_mask = None + if self.ip4: + ip4 = self.ip4_address(node_id) + ip4_mask = self.ip4.prefixlen + + # generate ip6 data + ip6 = None + ip6_mask = None + if self.ip6: + ip6 = self.ip6_address(node_id) + ip6_mask = self.ip6.prefixlen + + # random mac + if not mac: + mac = utils.random_mac() + + return InterfaceData( + name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac + ) + + def create_iface( + self, node: "CoreNode", name: str = None, mac: str = None + ) -> InterfaceData: + """ + Creates interface data for linking nodes, using the nodes unique id for + generation, along with a random mac address, unless provided. + + :param node: node to create interface for + :param name: name to set for interface, default is eth{id} + :param mac: mac address to use for this interface, default is random + generation + :return: new interface data for the provided node + """ + iface_data = self.gen_iface(node.id, name, mac) + iface_data.id = node.next_iface_id() + return iface_data + + +@dataclass +class NodeOptions: + """ + Options for creating and updating nodes within core. + """ + + name: str = None + model: Optional[str] = "PC" + canvas: int = None + icon: str = None + opaque: str = None + services: List[str] = field(default_factory=list) + config_services: List[str] = field(default_factory=list) + x: float = None + y: float = None + lat: float = None + lon: float = None + alt: float = None + emulation_id: int = None + server: str = None + image: str = None + emane: str = None + + def set_position(self, x: float, y: float) -> None: + """ + Convenience method for setting position. + + :param x: x position + :param y: y position + :return: nothing + """ + self.x = x + self.y = y + + def set_location(self, lat: float, lon: float, alt: float) -> None: + """ + Convenience method for setting location. + + :param lat: latitude + :param lon: longitude + :param alt: altitude + :return: nothing + """ + self.lat = lat + self.lon = lon + self.alt = alt diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py deleted file mode 100644 index 25ce71ac1..000000000 --- a/daemon/core/emulator/emudata.py +++ /dev/null @@ -1,206 +0,0 @@ -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, List, Optional - -import netaddr - -from core import utils -from core.emulator.enumerations import LinkTypes - -if TYPE_CHECKING: - from core.nodes.base import CoreNode - - -@dataclass -class NodeOptions: - """ - Options for creating and updating nodes within core. - """ - - name: str = None - model: Optional[str] = "PC" - canvas: int = None - icon: str = None - opaque: str = None - services: List[str] = field(default_factory=list) - config_services: List[str] = field(default_factory=list) - x: float = None - y: float = None - lat: float = None - lon: float = None - alt: float = None - emulation_id: int = None - server: str = None - image: str = None - emane: str = None - - def set_position(self, x: float, y: float) -> None: - """ - Convenience method for setting position. - - :param x: x position - :param y: y position - :return: nothing - """ - self.x = x - self.y = y - - def set_location(self, lat: float, lon: float, alt: float) -> None: - """ - Convenience method for setting location. - - :param lat: latitude - :param lon: longitude - :param alt: altitude - :return: nothing - """ - self.lat = lat - self.lon = lon - self.alt = alt - - -@dataclass -class LinkOptions: - """ - Options for creating and updating links within core. - """ - - type: LinkTypes = LinkTypes.WIRED - session: int = None - delay: int = None - bandwidth: int = None - loss: float = None - dup: int = None - jitter: int = None - mer: int = None - burst: int = None - mburst: int = None - gui_attributes: str = None - unidirectional: bool = None - emulation_id: int = None - network_id: int = None - key: int = None - opaque: str = None - - -@dataclass -class InterfaceData: - """ - Convenience class for storing interface data. - """ - - id: int = None - name: str = None - mac: str = None - ip4: str = None - ip4_mask: int = None - ip6: str = None - ip6_mask: int = None - - def get_addresses(self) -> List[str]: - """ - Returns a list of ip4 and ip6 addresses when present. - - :return: list of addresses - """ - addresses = [] - if self.ip4 and self.ip4_mask: - addresses.append(f"{self.ip4}/{self.ip4_mask}") - if self.ip6 and self.ip6_mask: - addresses.append(f"{self.ip6}/{self.ip6_mask}") - return addresses - - -class IpPrefixes: - """ - Convenience class to help generate IP4 and IP6 addresses for nodes within CORE. - """ - - def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: - """ - Creates an IpPrefixes object. - - :param ip4_prefix: ip4 prefix to use for generation - :param ip6_prefix: ip6 prefix to use for generation - :raises ValueError: when both ip4 and ip6 prefixes have not been provided - """ - if not ip4_prefix and not ip6_prefix: - raise ValueError("ip4 or ip6 must be provided") - - self.ip4 = None - if ip4_prefix: - self.ip4 = netaddr.IPNetwork(ip4_prefix) - self.ip6 = None - if ip6_prefix: - self.ip6 = netaddr.IPNetwork(ip6_prefix) - - def ip4_address(self, node_id: int) -> str: - """ - Convenience method to return the IP4 address for a node. - - :param node_id: node id to get IP4 address for - :return: IP4 address or None - """ - if not self.ip4: - raise ValueError("ip4 prefixes have not been set") - return str(self.ip4[node_id]) - - def ip6_address(self, node_id: int) -> str: - """ - Convenience method to return the IP6 address for a node. - - :param node_id: node id to get IP6 address for - :return: IP4 address or None - """ - if not self.ip6: - raise ValueError("ip6 prefixes have not been set") - return str(self.ip6[node_id]) - - def gen_iface(self, node_id: int, name: str = None, mac: str = None): - """ - Creates interface data for linking nodes, using the nodes unique id for - generation, along with a random mac address, unless provided. - - :param node_id: node id to create an interface for - :param name: name to set for interface, default is eth{id} - :param mac: mac address to use for this interface, default is random - generation - :return: new interface data for the provided node - """ - # generate ip4 data - ip4 = None - ip4_mask = None - if self.ip4: - ip4 = self.ip4_address(node_id) - ip4_mask = self.ip4.prefixlen - - # generate ip6 data - ip6 = None - ip6_mask = None - if self.ip6: - ip6 = self.ip6_address(node_id) - ip6_mask = self.ip6.prefixlen - - # random mac - if not mac: - mac = utils.random_mac() - - return InterfaceData( - name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac - ) - - def create_iface( - self, node: "CoreNode", name: str = None, mac: str = None - ) -> InterfaceData: - """ - Creates interface data for linking nodes, using the nodes unique id for - generation, along with a random mac address, unless provided. - - :param node: node to create interface for - :param name: name to set for interface, default is eth{id} - :param mac: mac address to use for this interface, default is random - generation - :return: new interface data for the provided node - """ - iface_data = self.gen_iface(node.id, name, mac) - iface_data.id = node.next_iface_id() - return iface_data diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 2dc5ad12d..f2514e67b 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -22,11 +22,13 @@ EventData, ExceptionData, FileData, + InterfaceData, LinkData, + LinkOptions, NodeData, + NodeOptions, ) from core.emulator.distributed import DistributedController -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.enumerations import ( EventTypes, ExceptionLevels, diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index d56c40aa6..91a8baaeb 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -13,8 +13,7 @@ from core import utils from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager -from core.emulator.data import EventData, LinkData -from core.emulator.emudata import LinkOptions +from core.emulator.data import EventData, LinkData, LinkOptions from core.emulator.enumerations import ( ConfigDataTypes, EventTypes, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 40aae6a80..3c754aa2c 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -14,8 +14,7 @@ from core import utils from core.configservice.dependencies import ConfigServiceDependencies from core.constants import MOUNT_BIN, VNODED_BIN -from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import InterfaceData, LinkOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError from core.nodes.client import VnodeClient @@ -1096,19 +1095,18 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat if uni: unidirectional = 1 - iface2_ip4 = None - iface2_ip4_mask = None - iface2_ip6 = None - iface2_ip6_mask = None + iface2 = InterfaceData( + id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.hwaddr + ) for address in iface.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - iface2_ip4 = ip - iface2_ip4_mask = mask + iface2.ip4 = ip + iface2.ip4_mask = mask else: - iface2_ip6 = ip - iface2_ip6_mask = mask + iface2.ip6 = ip + iface2.ip6_mask = mask link_data = LinkData( message_type=flags, @@ -1116,13 +1114,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat node2_id=linked_node.id, link_type=self.linktype, unidirectional=unidirectional, - iface2_id=linked_node.get_iface_id(iface), - iface2_name=iface.name, - iface2_mac=iface.hwaddr, - iface2_ip4=iface2_ip4, - iface2_ip4_mask=iface2_ip4_mask, - iface2_ip6=iface2_ip6, - iface2_ip6_mask=iface2_ip6_mask, + iface2=iface2, delay=iface.getparam("delay"), bandwidth=iface.getparam("bw"), dup=iface.getparam("duplicate"), diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 85e3e4881..b2f6bbf39 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -11,8 +11,7 @@ from core import utils from core.constants import EBTABLES_BIN, TC_BIN -from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import InterfaceData, LinkOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -894,33 +893,31 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat if iface1.getparams() != iface2.getparams(): unidirectional = 1 - iface1_ip4 = None - iface1_ip4_mask = None - iface1_ip6 = None - iface1_ip6_mask = None + iface1_data = InterfaceData( + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.hwaddr + ) for address in iface1.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - iface1_ip4 = ip - iface1_ip4_mask = mask + iface1.ip4 = ip + iface1.ip4_mask = mask else: - iface1_ip6 = ip - iface1_ip6_mask = mask + iface1.ip6 = ip + iface1.ip6_mask = mask - iface2_ip4 = None - iface2_ip4_mask = None - iface2_ip6 = None - iface2_ip6_mask = None + iface2_data = InterfaceData( + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.hwaddr + ) for address in iface2.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - iface2_ip4 = ip - iface2_ip4_mask = mask + iface2.ip4 = ip + iface2.ip4_mask = mask else: - iface2_ip6 = ip - iface2_ip6_mask = mask + iface2.ip6 = ip + iface2.ip6_mask = mask link_data = LinkData( message_type=flags, @@ -933,26 +930,16 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat loss=iface1.getparam("loss"), dup=iface1.getparam("duplicate"), jitter=iface1.getparam("jitter"), - iface1_id=iface1.node.get_iface_id(iface1), - iface1_name=iface1.name, - iface1_mac=iface1.hwaddr, - iface1_ip4=iface1_ip4, - iface1_ip4_mask=iface1_ip4_mask, - iface1_ip6=iface1_ip6, - iface1_ip6_mask=iface1_ip6_mask, - iface2_id=iface2.node.get_iface_id(iface2), - iface2_name=iface2.name, - iface2_mac=iface2.hwaddr, - iface2_ip4=iface2_ip4, - iface2_ip4_mask=iface2_ip4_mask, - iface2_ip6=iface2_ip6, - iface2_ip6_mask=iface2_ip6_mask, + iface1=iface1_data, + iface2=iface2_data, ) all_links.append(link_data) # build a 2nd link message for the upstream link parameters # (swap if1 and if2) if unidirectional: + iface1_data = InterfaceData(id=iface2.node.get_iface_id(iface2)) + iface2_data = InterfaceData(id=iface1.node.get_iface_id(iface1)) link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, @@ -964,8 +951,8 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat dup=iface2.getparam("duplicate"), jitter=iface2.getparam("jitter"), unidirectional=1, - iface1_id=iface2.node.get_iface_id(iface2), - iface2_id=iface1.node.get_iface_id(iface1), + iface1=iface1_data, + iface2=iface2_data, ) all_links.append(link_data) return all_links diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 555e0ec9d..36bcb2679 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -9,8 +9,8 @@ from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN +from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.emudata import InterfaceData, LinkOptions from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNetworkBase, CoreNodeBase diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index fe596d7a9..1f92502c6 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -6,8 +6,7 @@ import core.nodes.base import core.nodes.physical from core.emane.nodes import EmaneNet -from core.emulator.data import LinkData -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes, NodeTypes from core.errors import CoreXmlError from core.nodes.base import CoreNodeBase, NodeBase @@ -482,12 +481,10 @@ def write_links(self, links: List[LinkData]) -> None: # add link data for link_data in links: # skip basic range links - if link_data.iface1_id is None and link_data.iface2_id is None: + if link_data.iface1 is None and link_data.iface2 is None: continue - link_element = self.create_link_element(link_data) link_elements.append(link_element) - if link_elements.getchildren(): self.scenario.append(link_elements) @@ -496,36 +493,24 @@ def write_device(self, node: NodeBase) -> None: self.devices.append(device.element) def create_iface_element( - self, - element_name: str, - node_id: int, - iface_id: int, - mac: str, - ip4: str, - ip4_mask: int, - ip6: str, - ip6_mask: int, + self, element_name: str, node_id: int, iface_data: InterfaceData ) -> etree.Element: - iface = etree.Element(element_name) + iface_element = etree.Element(element_name) node = self.session.get_node(node_id, NodeBase) - iface_name = None if isinstance(node, CoreNodeBase): - node_iface = node.get_iface(iface_id) - iface_name = node_iface.name - + iface = node.get_iface(iface_data.id) # check if emane interface - if isinstance(node_iface.net, EmaneNet): - nem = node_iface.net.getnemid(node_iface) - add_attribute(iface, "nem", nem) - - add_attribute(iface, "id", iface_id) - add_attribute(iface, "name", iface_name) - add_attribute(iface, "mac", mac) - add_attribute(iface, "ip4", ip4) - add_attribute(iface, "ip4_mask", ip4_mask) - add_attribute(iface, "ip6", ip6) - add_attribute(iface, "ip6_mask", ip6_mask) - return iface + if isinstance(iface.net, EmaneNet): + nem = iface.net.getnemid(iface) + add_attribute(iface_element, "nem", nem) + add_attribute(iface_element, "id", iface_data.id) + add_attribute(iface_element, "name", iface_data.name) + add_attribute(iface_element, "mac", iface_data.mac) + add_attribute(iface_element, "ip4", iface_data.ip4) + add_attribute(iface_element, "ip4_mask", iface_data.ip4_mask) + add_attribute(iface_element, "ip6", iface_data.ip6) + add_attribute(iface_element, "ip6_mask", iface_data.ip6_mask) + return iface_element def create_link_element(self, link_data: LinkData) -> etree.Element: link_element = etree.Element("link") @@ -533,30 +518,16 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: add_attribute(link_element, "node2", link_data.node2_id) # check for interface one - if link_data.iface1_id is not None: + if link_data.iface1 is not None: iface1 = self.create_iface_element( - "interface1", - link_data.node1_id, - link_data.iface1_id, - link_data.iface1_mac, - link_data.iface1_ip4, - link_data.iface1_ip4_mask, - link_data.iface1_ip6, - link_data.iface1_ip6_mask, + "interface1", link_data.node1_id, link_data.iface1 ) link_element.append(iface1) # check for interface two - if link_data.iface2_id is not None: + if link_data.iface2 is not None: iface2 = self.create_iface_element( - "interface2", - link_data.node2_id, - link_data.iface2_id, - link_data.iface2_mac, - link_data.iface2_ip4, - link_data.iface2_ip4_mask, - link_data.iface2_ip6, - link_data.iface2_ip6_mask, + "interface2", link_data.node2_id, link_data.iface2 ) link_element.append(iface2) @@ -582,7 +553,6 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: add_attribute(options, "network_id", link_data.network_id) add_attribute(options, "key", link_data.key) add_attribute(options, "opaque", link_data.opaque) - add_attribute(options, "session", link_data.session) if options.items(): link_element.append(options) @@ -969,7 +939,6 @@ def read_links(self) -> None: if options.loss is None: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") - options.session = options_element.get("session") options.emulation_id = get_int(options_element, "emulation_id") options.network_id = get_int(options_element, "network_id") options.opaque = options_element.get("opaque") diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index 767d0f455..9706f2c93 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index c38f96af8..ae7dae797 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.docker import DockerNode diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index 5b62d4331..308fd00f3 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.docker import DockerNode diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index 161cd823a..fa9e4e40e 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.docker import DockerNode diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index 3d8eef6ab..b41520d89 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.lxd import LxcNode diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index a7209b5cf..3a55e2e1d 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.lxd import LxcNode diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 9b6801f57..12767e71d 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.lxd import LxcNode diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index 3ee561081..4421283fd 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -9,7 +9,7 @@ from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index 1573836aa..26f7caa6c 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -7,7 +7,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.lxd import LxcNode diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index 1486c237b..fe714e1d3 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -7,7 +7,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index e9eb1e81d..35de1cada 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -7,7 +7,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index 322e569f7..9d6def4a7 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -10,7 +10,7 @@ from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index 902e79e07..f05176a30 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -6,7 +6,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/python/switch_inject.py b/daemon/examples/python/switch_inject.py index 89f70e050..18a75a497 100644 --- a/daemon/examples/python/switch_inject.py +++ b/daemon/examples/python/switch_inject.py @@ -8,7 +8,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index 547a58605..de26ab975 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -6,7 +6,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.location.mobility import BasicRangeModel from core.nodes.base import CoreNode diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index c3315e7c3..be62fc035 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -14,8 +14,8 @@ from core.api.tlv.corehandlers import CoreHandler from core.emane.emanemanager import EmaneManager from core.emulator.coreemu import CoreEmu +from core.emulator.data import IpPrefixes from core.emulator.distributed import DistributedServer -from core.emulator.emudata import IpPrefixes from core.emulator.enumerations import EventTypes from core.emulator.session import Session from core.nodes.base import CoreNode diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index e1c7938b9..f51e30b91 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -15,7 +15,7 @@ from core.emane.nodes import EmaneNet from core.emane.rfpipe import EmaneRfPipeModel from core.emane.tdma import EmaneTdmaModel -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.session import Session from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 5771f7ad8..2623b0df6 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -8,7 +8,7 @@ import pytest -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import MessageFlags from core.emulator.session import Session from core.errors import CoreCommandError diff --git a/daemon/tests/test_distributed.py b/daemon/tests/test_distributed.py index 0f4b1731d..01362cae9 100644 --- a/daemon/tests/test_distributed.py +++ b/daemon/tests/test_distributed.py @@ -1,4 +1,4 @@ -from core.emulator.emudata import NodeOptions +from core.emulator.data import NodeOptions from core.emulator.session import Session from core.nodes.base import CoreNode from core.nodes.network import HubNode diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 23ff03018..b2a1c3124 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -18,8 +18,7 @@ from core.api.tlv.enumerations import ConfigFlags from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet -from core.emulator.data import EventData, NodeData -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import EventData, IpPrefixes, NodeData, NodeOptions from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes from core.errors import CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index fea4f4f85..4078d8bc6 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -1,6 +1,6 @@ from typing import Tuple -from core.emulator.emudata import IpPrefixes, LinkOptions +from core.emulator.data import IpPrefixes, LinkOptions from core.emulator.session import Session from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index d7e435abd..327137d23 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -1,6 +1,6 @@ import pytest -from core.emulator.emudata import InterfaceData, NodeOptions +from core.emulator.data import InterfaceData, NodeOptions from core.emulator.session import Session from core.errors import CoreError from core.nodes.base import CoreNode diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 55f5a2abe..d81fe4711 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -3,7 +3,7 @@ import pytest -from core.emulator.emudata import IpPrefixes, LinkOptions, NodeOptions +from core.emulator.data import IpPrefixes, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes from core.emulator.session import Session from core.errors import CoreError diff --git a/docs/scripting.md b/docs/scripting.md index 18666a9ad..f65d66a31 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -36,7 +36,7 @@ interact with the GUI. import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode From a29a7a558277e9438e288dbeaa1bbd6839bc7dcf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:18:19 -0700 Subject: [PATCH 027/210] refactored LinkOptions to be used within LinkData, instead of duplicating data, removed session from LinkOptions and LinkData --- daemon/core/api/grpc/grpcutils.py | 30 ++++++++++++---------- daemon/core/api/tlv/corehandlers.py | 31 +++++++++++----------- daemon/core/emulator/data.py | 25 ++++++------------ daemon/core/nodes/base.py | 20 ++++----------- daemon/core/nodes/interface.py | 29 +++++++++++++++++++++ daemon/core/nodes/network.py | 21 +++++---------- daemon/core/xml/corexml.py | 29 ++++++++++----------- daemon/tests/test_grpc.py | 4 +-- daemon/tests/test_gui.py | 4 +-- daemon/tests/test_xml.py | 40 ++++++++++++++--------------- 10 files changed, 120 insertions(+), 113 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 095c4d0cc..5213a8350 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -319,6 +319,22 @@ def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: ) +def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions: + return core_pb2.LinkOptions( + opaque=options_data.opaque, + 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, + unidirectional=options_data.unidirectional, + ) + + def convert_link(link_data: LinkData) -> core_pb2.Link: """ Convert link_data into core protobuf link. @@ -332,19 +348,7 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: iface2 = None if link_data.iface2 is not None: iface2 = convert_iface(link_data.iface2) - options = core_pb2.LinkOptions( - opaque=link_data.opaque, - jitter=link_data.jitter, - key=link_data.key, - mburst=link_data.mburst, - mer=link_data.mer, - loss=link_data.loss, - bandwidth=link_data.bandwidth, - burst=link_data.burst, - delay=link_data.delay, - dup=link_data.dup, - unidirectional=link_data.unidirectional, - ) + options = convert_link_options(link_data.options) return core_pb2.Link( type=link_data.link_type.value, node1_id=link_data.node1_id, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 88906e0cc..3a4351f17 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -343,12 +343,13 @@ def handle_broadcast_link(self, link_data): :return: nothing """ logging.debug("handling broadcast link: %s", link_data) + options_data = link_data.options loss = "" - if link_data.loss is not None: - loss = str(link_data.loss) + if options_data.loss is not None: + loss = str(options_data.loss) dup = "" - if link_data.dup is not None: - dup = str(link_data.dup) + if options_data.dup is not None: + dup = str(options_data.dup) iface1 = link_data.iface1 if iface1 is None: iface1 = InterfaceData() @@ -361,20 +362,20 @@ def handle_broadcast_link(self, link_data): [ (LinkTlvs.N1_NUMBER, link_data.node1_id), (LinkTlvs.N2_NUMBER, link_data.node2_id), - (LinkTlvs.DELAY, link_data.delay), - (LinkTlvs.BANDWIDTH, link_data.bandwidth), + (LinkTlvs.DELAY, options_data.delay), + (LinkTlvs.BANDWIDTH, options_data.bandwidth), (LinkTlvs.LOSS, loss), (LinkTlvs.DUP, dup), - (LinkTlvs.JITTER, link_data.jitter), - (LinkTlvs.MER, link_data.mer), - (LinkTlvs.BURST, link_data.burst), - (LinkTlvs.MBURST, link_data.mburst), + (LinkTlvs.JITTER, options_data.jitter), + (LinkTlvs.MER, options_data.mer), + (LinkTlvs.BURST, options_data.burst), + (LinkTlvs.MBURST, options_data.mburst), (LinkTlvs.TYPE, link_data.link_type.value), - (LinkTlvs.GUI_ATTRIBUTES, link_data.gui_attributes), - (LinkTlvs.UNIDIRECTIONAL, link_data.unidirectional), - (LinkTlvs.EMULATION_ID, link_data.emulation_id), + (LinkTlvs.GUI_ATTRIBUTES, options_data.gui_attributes), + (LinkTlvs.UNIDIRECTIONAL, options_data.unidirectional), + (LinkTlvs.EMULATION_ID, options_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), - (LinkTlvs.KEY, link_data.key), + (LinkTlvs.KEY, options_data.key), (LinkTlvs.IFACE1_NUMBER, iface1.id), (LinkTlvs.IFACE1_IP4, iface1.ip4), (LinkTlvs.IFACE1_IP4_MASK, iface1.ip4_mask), @@ -387,7 +388,7 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.IFACE2_MAC, iface2.mac), (LinkTlvs.IFACE2_IP6, iface2.ip6), (LinkTlvs.IFACE2_IP6_MASK, iface2.ip6_mask), - (LinkTlvs.OPAQUE, link_data.opaque), + (LinkTlvs.OPAQUE, options_data.opaque), ], ) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index c08a70f0d..0c263135c 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -142,36 +142,27 @@ class LinkOptions: burst: int = None mburst: int = None gui_attributes: str = None - unidirectional: bool = None + unidirectional: int = None emulation_id: int = None - network_id: int = None key: int = None opaque: str = None @dataclass class LinkData: + """ + Represents all data associated with a link. + """ + message_type: MessageFlags = None + link_type: LinkTypes = None label: str = None node1_id: int = None node2_id: int = None - delay: float = None - bandwidth: float = None - loss: float = None - dup: float = None - jitter: float = None - mer: float = None - burst: float = None - mburst: float = None - link_type: LinkTypes = None - gui_attributes: str = None - unidirectional: int = None - emulation_id: int = None - network_id: int = None - key: int = None iface1: InterfaceData = None iface2: InterfaceData = None - opaque: str = None + options: LinkOptions = LinkOptions() + network_id: int = None color: str = None diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3c754aa2c..a6e4f147a 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -224,9 +224,7 @@ def data( def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ - Build CORE Link data for this object. There is no default - method for PyCoreObjs as PyCoreNodes do not implement this but - PyCoreNets do. + Build link data for this node. :param flags: message flags :return: list of link data @@ -1108,35 +1106,27 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat iface2.ip6 = ip iface2.ip6_mask = mask + options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=flags, node1_id=self.id, node2_id=linked_node.id, link_type=self.linktype, - unidirectional=unidirectional, iface2=iface2, - delay=iface.getparam("delay"), - bandwidth=iface.getparam("bw"), - dup=iface.getparam("duplicate"), - jitter=iface.getparam("jitter"), - loss=iface.getparam("loss"), + options=options_data, ) all_links.append(link_data) if not uni: continue iface.swapparams("_params_up") + options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, node1_id=linked_node.id, node2_id=self.id, link_type=self.linktype, - unidirectional=1, - delay=iface.getparam("delay"), - bandwidth=iface.getparam("bw"), - dup=iface.getparam("duplicate"), - jitter=iface.getparam("jitter"), - loss=iface.getparam("loss"), + options=options_data, ) iface.swapparams("_params_up") all_links.append(link_data) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index dc16517f5..1fb8b894d 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils +from core.emulator.data import LinkOptions from core.emulator.enumerations import MessageFlags, TransportType from core.errors import CoreCommandError from core.nodes.netclient import LinuxNetClient, get_net_client @@ -169,6 +170,34 @@ def getparam(self, key: str) -> float: """ return self._params.get(key) + def get_link_options(self, unidirectional: int) -> LinkOptions: + """ + Get currently set params as link options. + + :param unidirectional: unidirectional setting + :return: link options + """ + delay = self.getparam("delay") + if delay is not None: + delay = int(delay) + bandwidth = self.getparam("bw") + if bandwidth is not None: + bandwidth = int(bandwidth) + dup = self.getparam("duplicate") + if dup is not None: + dup = int(dup) + jitter = self.getparam("jitter") + if jitter is not None: + jitter = int(jitter) + return LinkOptions( + delay=delay, + bandwidth=bandwidth, + dup=dup, + jitter=jitter, + loss=self.getparam("loss"), + unidirectional=unidirectional, + ) + def getparams(self) -> List[Tuple[str, float]]: """ Return (key, value) pairs for parameters. diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b2f6bbf39..972d54f97 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -884,11 +884,12 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat :return: list of link data """ all_links = [] - if len(self.ifaces) != 2: return all_links - iface1, iface2 = self.get_ifaces() + ifaces = self.get_ifaces() + iface1 = ifaces[0] + iface2 = ifaces[1] unidirectional = 0 if iface1.getparams() != iface2.getparams(): unidirectional = 1 @@ -919,19 +920,15 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat iface2.ip6 = ip iface2.ip6_mask = mask + options_data = iface1.get_link_options(unidirectional) link_data = LinkData( message_type=flags, node1_id=iface1.node.id, node2_id=iface2.node.id, link_type=self.linktype, - unidirectional=unidirectional, - delay=iface1.getparam("delay"), - bandwidth=iface1.getparam("bw"), - loss=iface1.getparam("loss"), - dup=iface1.getparam("duplicate"), - jitter=iface1.getparam("jitter"), iface1=iface1_data, iface2=iface2_data, + options=options_data, ) all_links.append(link_data) @@ -940,19 +937,15 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat if unidirectional: iface1_data = InterfaceData(id=iface2.node.get_iface_id(iface2)) iface2_data = InterfaceData(id=iface1.node.get_iface_id(iface1)) + options_data = iface2.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, node1_id=iface2.node.id, node2_id=iface1.node.id, - delay=iface2.getparam("delay"), - bandwidth=iface2.getparam("bw"), - loss=iface2.getparam("loss"), - dup=iface2.getparam("duplicate"), - jitter=iface2.getparam("jitter"), - unidirectional=1, iface1=iface1_data, iface2=iface2_data, + options=options_data, ) all_links.append(link_data) return all_links diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 1f92502c6..4febe71f6 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -537,22 +537,22 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet)) is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet)) if not any([is_node1_wireless, is_node2_wireless]): + options_data = link_data.options options = etree.Element("options") - add_attribute(options, "delay", link_data.delay) - add_attribute(options, "bandwidth", link_data.bandwidth) - add_attribute(options, "loss", link_data.loss) - add_attribute(options, "dup", link_data.dup) - add_attribute(options, "jitter", link_data.jitter) - add_attribute(options, "mer", link_data.mer) - add_attribute(options, "burst", link_data.burst) - add_attribute(options, "mburst", link_data.mburst) - add_attribute(options, "type", link_data.link_type) - add_attribute(options, "gui_attributes", link_data.gui_attributes) - add_attribute(options, "unidirectional", link_data.unidirectional) - add_attribute(options, "emulation_id", link_data.emulation_id) + add_attribute(options, "delay", options_data.delay) + add_attribute(options, "bandwidth", options_data.bandwidth) + add_attribute(options, "loss", options_data.loss) + add_attribute(options, "dup", options_data.dup) + add_attribute(options, "jitter", options_data.jitter) + add_attribute(options, "mer", options_data.mer) + add_attribute(options, "burst", options_data.burst) + add_attribute(options, "mburst", options_data.mburst) + add_attribute(options, "gui_attributes", options_data.gui_attributes) + add_attribute(options, "unidirectional", options_data.unidirectional) + add_attribute(options, "emulation_id", options_data.emulation_id) add_attribute(options, "network_id", link_data.network_id) - add_attribute(options, "key", link_data.key) - add_attribute(options, "opaque", link_data.opaque) + add_attribute(options, "key", options_data.key) + add_attribute(options, "opaque", options_data.opaque) if options.items(): link_element.append(options) @@ -940,7 +940,6 @@ def read_links(self) -> None: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") options.emulation_id = get_int(options_element, "emulation_id") - options.network_id = get_int(options_element, "network_id") options.opaque = options_element.get("opaque") options.gui_attributes = options_element.get("gui_attributes") diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index b2a1c3124..cff7cd85c 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -590,7 +590,7 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): session.add_link(node.id, switch.id, iface) options = core_pb2.LinkOptions(bandwidth=30000) link = switch.all_link_data()[0] - assert options.bandwidth != link.bandwidth + assert options.bandwidth != link.options.bandwidth # then with client.context_connect(): @@ -601,7 +601,7 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # then assert response.result is True link = switch.all_link_data()[0] - assert options.bandwidth == link.bandwidth + assert options.bandwidth == link.options.bandwidth def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index c413295a5..8f01a2bfa 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -201,7 +201,7 @@ def test_link_update(self, coretlv: CoreHandler): all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] - assert link.bandwidth is None + assert link.options.bandwidth is None bandwidth = 50000 message = coreapi.CoreLinkMessage.create( @@ -219,7 +219,7 @@ def test_link_update(self, coretlv: CoreHandler): all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] - assert link.bandwidth == bandwidth + assert link.options.bandwidth == bandwidth def test_link_delete_node_to_node(self, coretlv: CoreHandler): node1_id = 1 diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index d81fe4711..91b598f34 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -347,11 +347,11 @@ def test_link_options( node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert options.loss == link.loss - assert options.bandwidth == link.bandwidth - assert options.jitter == link.jitter - assert options.delay == link.delay - assert options.dup == link.dup + assert options.loss == link.options.loss + assert options.bandwidth == link.options.bandwidth + assert options.jitter == link.options.jitter + assert options.delay == link.options.delay + assert options.dup == link.options.dup def test_link_options_ptp( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -414,11 +414,11 @@ def test_link_options_ptp( node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert options.loss == link.loss - assert options.bandwidth == link.bandwidth - assert options.jitter == link.jitter - assert options.delay == link.delay - assert options.dup == link.dup + assert options.loss == link.options.loss + assert options.bandwidth == link.options.bandwidth + assert options.jitter == link.options.jitter + assert options.delay == link.options.delay + assert options.dup == link.options.dup def test_link_options_bidirectional( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -494,13 +494,13 @@ def test_link_options_bidirectional( assert len(links) == 2 link1 = links[0] link2 = links[1] - assert options1.bandwidth == link1.bandwidth - assert options1.delay == link1.delay - assert options1.loss == link1.loss - assert options1.dup == link1.dup - assert options1.jitter == link1.jitter - assert options2.bandwidth == link2.bandwidth - assert options2.delay == link2.delay - assert options2.loss == link2.loss - assert options2.dup == link2.dup - assert options2.jitter == link2.jitter + assert options1.bandwidth == link1.options.bandwidth + assert options1.delay == link1.options.delay + assert options1.loss == link1.options.loss + assert options1.dup == link1.options.dup + assert options1.jitter == link1.options.jitter + assert options2.bandwidth == link2.options.bandwidth + assert options2.delay == link2.options.delay + assert options2.loss == link2.options.loss + assert options2.dup == link2.options.dup + assert options2.jitter == link2.options.jitter From 351b99aae003b3852240dd9effb825677252574d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 21:53:12 -0700 Subject: [PATCH 028/210] daemon: renamed LinkData.link_type to LinkData.type and removed LinkOptions.type to remove redundant information, link_type param added to session.add_link, delete_link, and update_link functions --- daemon/core/api/grpc/grpcutils.py | 16 ++++++++-------- daemon/core/api/grpc/server.py | 8 +++++--- daemon/core/api/tlv/corehandlers.py | 14 +++++++++----- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emane/linkmonitor.py | 2 +- daemon/core/emulator/data.py | 3 +-- daemon/core/emulator/session.py | 10 +++++++--- daemon/core/location/mobility.py | 2 +- daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/network.py | 4 ++-- 10 files changed, 37 insertions(+), 28 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 5213a8350..a8e0a7922 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -78,7 +78,7 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: def add_link_data( link_proto: core_pb2.Link -) -> Tuple[InterfaceData, InterfaceData, LinkOptions]: +) -> Tuple[InterfaceData, InterfaceData, LinkOptions, LinkTypes]: """ Convert link proto to link interfaces and options data. @@ -88,7 +88,7 @@ def add_link_data( iface1_data = link_iface(link_proto.iface1) iface2_data = link_iface(link_proto.iface2) link_type = LinkTypes(link_proto.type) - options = LinkOptions(type=link_type) + options = LinkOptions() options_data = link_proto.options if options_data: options.delay = options_data.delay @@ -102,7 +102,7 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return iface1_data, iface2_data, options + return iface1_data, iface2_data, options, link_type def create_nodes( @@ -142,8 +142,8 @@ def create_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - iface1, iface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, iface1, iface2, options) + iface1, iface2, options, link_type = add_link_data(link_proto) + args = (node1_id, node2_id, iface1, iface2, options, link_type) funcs.append((session.add_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -166,8 +166,8 @@ def edit_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - iface1, iface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, iface1.id, iface2.id, options) + iface1, iface2, options, link_type = add_link_data(link_proto) + args = (node1_id, node2_id, iface1.id, iface2.id, options, link_type) funcs.append((session.update_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -350,7 +350,7 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: iface2 = convert_iface(link_data.iface2) options = convert_link_options(link_data.options) return core_pb2.Link( - type=link_data.link_type.value, + type=link_data.type.value, node1_id=link_data.node1_id, node2_id=link_data.node2_id, iface1=iface1, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 1be601167..b9e0e0aae 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -847,9 +847,11 @@ def AddLink( node2_id = request.link.node2_id self.get_node(session, node1_id, context, NodeBase) self.get_node(session, node2_id, context, NodeBase) - iface1_data, iface2_data, options = grpcutils.add_link_data(request.link) + iface1_data, iface2_data, options, link_type = grpcutils.add_link_data( + request.link + ) node1_iface, node2_iface = session.add_link( - node1_id, node2_id, iface1_data, iface2_data, options=options + node1_id, node2_id, iface1_data, iface2_data, options, link_type ) iface1_proto = None iface2_proto = None @@ -1522,7 +1524,7 @@ def EmaneLink( color = session.get_link_color(emane1.id) link = LinkData( message_type=flag, - link_type=LinkTypes.WIRELESS, + type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, network_id=emane1.id, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3a4351f17..631cd5387 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -370,7 +370,7 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.MER, options_data.mer), (LinkTlvs.BURST, options_data.burst), (LinkTlvs.MBURST, options_data.mburst), - (LinkTlvs.TYPE, link_data.link_type.value), + (LinkTlvs.TYPE, link_data.type.value), (LinkTlvs.GUI_ATTRIBUTES, options_data.gui_attributes), (LinkTlvs.UNIDIRECTIONAL, options_data.unidirectional), (LinkTlvs.EMULATION_ID, options_data.emulation_id), @@ -784,7 +784,7 @@ def handle_link_message(self, message): link_type_value = message.get_tlv(LinkTlvs.TYPE.value) if link_type_value is not None: link_type = LinkTypes(link_type_value) - options = LinkOptions(type=link_type) + options = LinkOptions() options.delay = message.get_tlv(LinkTlvs.DELAY.value) options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) options.loss = message.get_tlv(LinkTlvs.LOSS.value) @@ -801,12 +801,16 @@ def handle_link_message(self, message): options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: - self.session.add_link(node1_id, node2_id, iface1_data, iface2_data, options) + self.session.add_link( + node1_id, node2_id, iface1_data, iface2_data, options, link_type + ) elif message.flags & MessageFlags.DELETE.value: - self.session.delete_link(node1_id, node2_id, iface1_data.id, iface2_data.id) + self.session.delete_link( + node1_id, node2_id, iface1_data.id, iface2_data.id, link_type + ) else: self.session.update_link( - node1_id, node2_id, iface1_data.id, iface2_data.id, options + node1_id, node2_id, iface1_data.id, iface2_data.id, options, link_type ) return () diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 58b850801..fc561b5f4 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -500,10 +500,10 @@ def get_nem_link( color = self.session.get_link_color(emane1.id) return LinkData( message_type=flags, + type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, network_id=emane1.id, - link_type=LinkTypes.WIRELESS, color=color, ) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 097080c33..1a9ac41a8 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -305,11 +305,11 @@ def send_message( color = self.emane_manager.session.get_link_color(emane_id) link_data = LinkData( message_type=message_type, + type=LinkTypes.WIRELESS, label=label, node1_id=node1, node2_id=node2, network_id=emane_id, - link_type=LinkTypes.WIRELESS, color=color, ) self.emane_manager.session.broadcast_link(link_data) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 0c263135c..899d32ae0 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -132,7 +132,6 @@ class LinkOptions: Options for creating and updating links within core. """ - type: LinkTypes = LinkTypes.WIRED delay: int = None bandwidth: int = None loss: float = None @@ -155,7 +154,7 @@ class LinkData: """ message_type: MessageFlags = None - link_type: LinkTypes = None + type: LinkTypes = LinkTypes.WIRED label: str = None node1_id: int = None node2_id: int = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index f2514e67b..814c89d9e 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -224,6 +224,7 @@ def add_link( iface1_data: InterfaceData = None, iface2_data: InterfaceData = None, options: LinkOptions = None, + link_type: LinkTypes = LinkTypes.WIRED, ) -> Tuple[CoreInterface, CoreInterface]: """ Add a link between nodes. @@ -236,6 +237,7 @@ def add_link( data, defaults to none :param options: data for creating link, defaults to no options + :param link_type: type of link to add :return: tuple of created core interfaces, depending on link """ if not options: @@ -246,7 +248,7 @@ def add_link( iface2 = None # wireless link - if options.type == LinkTypes.WIRELESS: + if link_type == LinkTypes.WIRELESS: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): self._link_wireless(node1, node2, connect=True) else: @@ -371,6 +373,7 @@ def update_link( iface1_id: int = None, iface2_id: int = None, options: LinkOptions = None, + link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ Update link information between nodes. @@ -380,6 +383,7 @@ def update_link( :param iface1_id: interface id for node one :param iface2_id: interface id for node two :param options: data to update link with + :param link_type: type of link to update :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a unknown link between networks @@ -390,7 +394,7 @@ def update_link( node2 = self.get_node(node2_id, NodeBase) logging.info( "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", - options.type.name, + link_type.name, node1.name, iface1_id, node2.name, @@ -398,7 +402,7 @@ def update_link( ) # wireless link - if options.type == LinkTypes.WIRELESS: + if link_type == LinkTypes.WIRELESS: raise CoreError("cannot update wireless link") else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 91a8baaeb..9bb2966e5 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -487,10 +487,10 @@ def create_link_data( color = self.session.get_link_color(self.wlan.id) return LinkData( message_type=message_type, + type=LinkTypes.WIRELESS, node1_id=iface1.node.id, node2_id=iface2.node.id, network_id=self.wlan.id, - link_type=LinkTypes.WIRELESS, color=color, ) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index a6e4f147a..97164cb6b 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1109,9 +1109,9 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=flags, + type=self.linktype, node1_id=self.id, node2_id=linked_node.id, - link_type=self.linktype, iface2=iface2, options=options_data, ) @@ -1123,9 +1123,9 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, + type=self.linktype, node1_id=linked_node.id, node2_id=self.id, - link_type=self.linktype, options=options_data, ) iface.swapparams("_params_up") diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 972d54f97..04d4e8f83 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -923,9 +923,9 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat options_data = iface1.get_link_options(unidirectional) link_data = LinkData( message_type=flags, + type=self.linktype, node1_id=iface1.node.id, node2_id=iface2.node.id, - link_type=self.linktype, iface1=iface1_data, iface2=iface2_data, options=options_data, @@ -940,7 +940,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat options_data = iface2.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, - link_type=self.linktype, + type=self.linktype, node1_id=iface2.node.id, node2_id=iface1.node.id, iface1=iface1_data, From a1734c3bc0cb8d4eaa17c414db72a3835d464ab7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 22:05:36 -0700 Subject: [PATCH 029/210] grpc: updated Interface proto fields to be more consistent with code, ip4mask to ip4_mask, ip6mask to ip6_mask, netid to net_id, flowid to flow_id --- daemon/core/api/grpc/client.py | 4 ++-- daemon/core/api/grpc/grpcutils.py | 26 +++++++++++++------------- daemon/core/gui/coreclient.py | 7 ++++++- daemon/core/gui/dialogs/nodeconfig.py | 20 ++++++++++---------- daemon/core/gui/graph/edges.py | 4 ++-- daemon/core/gui/interface.py | 6 +++--- daemon/proto/core/api/grpc/core.proto | 8 ++++---- 7 files changed, 40 insertions(+), 35 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 68bfc5022..db908e050 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -127,9 +127,9 @@ def create_iface( id=iface_id, name=iface_data.name, ip4=iface_data.ip4, - ip4mask=iface_data.ip4_mask, + ip4_mask=iface_data.ip4_mask, ip6=iface_data.ip6, - ip6mask=iface_data.ip6_mask, + ip6_mask=iface_data.ip6_mask, mac=iface_data.mac, ) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index a8e0a7922..9d26e4cfb 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -69,9 +69,9 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: name=name, mac=mac, ip4=ip4, - ip4_mask=iface_proto.ip4mask, + ip4_mask=iface_proto.ip4_mask, ip6=ip6, - ip6_mask=iface_proto.ip6mask, + ip6_mask=iface_proto.ip6_mask, ) return iface_data @@ -313,9 +313,9 @@ def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: name=iface_data.name, mac=iface_data.mac, ip4=iface_data.ip4, - ip4mask=iface_data.ip4_mask, + ip4_mask=iface_data.ip4_mask, ip6=iface_data.ip6, - ip6mask=iface_data.ip6_mask, + ip6_mask=iface_data.ip6_mask, ) @@ -449,30 +449,30 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: if iface.net: net_id = iface.net.id ip4 = None - ip4mask = None + ip4_mask = None ip6 = None - ip6mask = None + ip6_mask = None for addr in iface.addrlist: network = netaddr.IPNetwork(addr) mask = network.prefixlen ip = str(network.ip) if netaddr.valid_ipv4(ip) and not ip4: ip4 = ip - ip4mask = mask + ip4_mask = mask elif netaddr.valid_ipv6(ip) and not ip6: ip6 = ip - ip6mask = mask + ip6_mask = mask return core_pb2.Interface( id=iface.node_id, - netid=net_id, + net_id=net_id, name=iface.name, - mac=str(iface.hwaddr), + mac=iface.hwaddr, mtu=iface.mtu, - flowid=iface.flow_id, + flow_id=iface.flow_id, ip4=ip4, - ip4mask=ip4mask, + ip4_mask=ip4_mask, ip6=ip6, - ip6mask=ip6mask, + ip6_mask=ip6_mask, ) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 3be58e17d..8b0c423c2 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -834,7 +834,12 @@ def create_iface(self, canvas_node: CanvasNode) -> core_pb2.Interface: iface_id = canvas_node.next_iface_id() name = f"eth{iface_id}" iface = core_pb2.Interface( - id=iface_id, name=name, ip4=ip4, ip4mask=ip4_mask, ip6=ip6, ip6mask=ip6_mask + id=iface_id, + name=name, + ip4=ip4, + ip4_mask=ip4_mask, + ip6=ip6, + ip6_mask=ip6_mask, ) logging.info( "create node(%s) interface(%s) IPv4(%s) IPv6(%s)", diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 29ce20101..cec9e9f92 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -248,7 +248,7 @@ def draw_ifaces(self): label.grid(row=row, column=0, padx=PADX, pady=PADY) ip4_net = "" if iface.ip4: - ip4_net = f"{iface.ip4}/{iface.ip4mask}" + ip4_net = f"{iface.ip4}/{iface.ip4_mask}" ip4 = tk.StringVar(value=ip4_net) entry = ttk.Entry(tab, textvariable=ip4, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") @@ -258,7 +258,7 @@ def draw_ifaces(self): label.grid(row=row, column=0, padx=PADX, pady=PADY) ip6_net = "" if iface.ip6: - ip6_net = f"{iface.ip6}/{iface.ip6mask}" + ip6_net = f"{iface.ip6}/{iface.ip6_mask}" ip6 = tk.StringVar(value=ip6_net) entry = ttk.Entry(tab, textvariable=ip6, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") @@ -318,12 +318,12 @@ def click_apply(self): error = True break if ip4_net: - ip4, ip4mask = ip4_net.split("/") - ip4mask = int(ip4mask) + ip4, ip4_mask = ip4_net.split("/") + ip4_mask = int(ip4_mask) else: - ip4, ip4mask = "", 0 + ip4, ip4_mask = "", 0 iface.ip4 = ip4 - iface.ip4mask = ip4mask + iface.ip4_mask = ip4_mask # validate ip6 ip6_net = data.ip6.get() @@ -331,12 +331,12 @@ def click_apply(self): error = True break if ip6_net: - ip6, ip6mask = ip6_net.split("/") - ip6mask = int(ip6mask) + ip6, ip6_mask = ip6_net.split("/") + ip6_mask = int(ip6_mask) else: - ip6, ip6mask = "", 0 + ip6, ip6_mask = "", 0 iface.ip6 = ip6 - iface.ip6mask = ip6mask + iface.ip6_mask = ip6_mask mac = data.mac.get() auto_mac = data.is_auto.get() diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 152e1a2f9..ac637b281 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -289,10 +289,10 @@ def iface_label(self, iface: core_pb2.Interface) -> str: label = f"{iface.name}" if iface.ip4 and self.canvas.show_ip4s.get(): label = f"{label}\n" if label else "" - label += f"{iface.ip4}/{iface.ip4mask}" + label += f"{iface.ip4}/{iface.ip4_mask}" if iface.ip6 and self.canvas.show_ip6s.get(): label = f"{label}\n" if label else "" - label += f"{iface.ip6}/{iface.ip6mask}" + label += f"{iface.ip6}/{iface.ip6_mask}" return label def create_node_labels(self) -> Tuple[str, str]: diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 14cba0242..6c82ca51f 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -15,7 +15,7 @@ def get_index(iface: "core_pb2.Interface") -> Optional[int]: if not iface.ip4: return None - net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4mask}") + net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4_mask}") ip_value = net.value cidr_value = net.cidr.value return ip_value - cidr_value @@ -153,10 +153,10 @@ def get_ips(self, node: "core_pb2.Node") -> [str, str]: def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: ip4_subnet = self.ip4_subnets if iface.ip4: - ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4mask}").cidr + ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4_mask}").cidr ip6_subnet = self.ip6_subnets if iface.ip6: - ip6_subnet = IPNetwork(f"{iface.ip6}/{iface.ip6mask}").cidr + ip6_subnet = IPNetwork(f"{iface.ip6}/{iface.ip6_mask}").cidr subnets = Subnets(ip4_subnet, ip6_subnet) return self.used_subnets.get(subnets.key(), subnets) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index f691621ac..2819c5eae 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -732,11 +732,11 @@ message Interface { string name = 2; string mac = 3; string ip4 = 4; - int32 ip4mask = 5; + int32 ip4_mask = 5; string ip6 = 6; - int32 ip6mask = 7; - int32 netid = 8; - int32 flowid = 9; + int32 ip6_mask = 7; + int32 net_id = 8; + int32 flow_id = 9; int32 mtu = 10; } From f4671ab2b894c82693484e5f2bcb52a9c707529e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:25:26 -0700 Subject: [PATCH 030/210] daemon: refactored usages of hwaddr to mac and be consistent everywhere --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/nodes/base.py | 22 +++++++++++----------- daemon/core/nodes/interface.py | 14 +++++++------- daemon/core/nodes/network.py | 4 ++-- daemon/core/nodes/physical.py | 18 +++++++++--------- daemon/core/services/xorp.py | 4 ++-- daemon/core/xml/emanexml.py | 6 +++--- daemon/tests/test_nodes.py | 10 +++++----- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 9d26e4cfb..6f2911a42 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -466,7 +466,7 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: id=iface.node_id, net_id=net_id, name=iface.name, - mac=iface.hwaddr, + mac=iface.mac, mtu=iface.mtu, flow_id=iface.flow_id, ip4=ip4, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 97164cb6b..97da63a43 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -731,9 +731,9 @@ def newveth(self, iface_id: int = None, ifname: str = None) -> int: flow_id = self.node_net_client.get_ifindex(veth.name) veth.flow_id = int(flow_id) logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) - hwaddr = self.node_net_client.get_mac(veth.name) - logging.debug("interface mac: %s - %s", veth.name, hwaddr) - veth.sethwaddr(hwaddr) + mac = self.node_net_client.get_mac(veth.name) + logging.debug("interface mac: %s - %s", veth.name, mac) + veth.set_mac(mac) try: # add network interface to the node. If unsuccessful, destroy the @@ -775,20 +775,20 @@ def newtuntap(self, iface_id: int = None, ifname: str = None) -> int: return iface_id - def sethwaddr(self, iface_id: int, addr: str) -> None: + def set_mac(self, iface_id: int, mac: str) -> None: """ Set hardware address for an interface. :param iface_id: id of interface to set hardware address for - :param addr: hardware address to set + :param mac: mac address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_mac(addr) + mac = utils.validate_mac(mac) iface = self.get_iface(iface_id) - iface.sethwaddr(addr) + iface.set_mac(mac) if self.up: - self.node_net_client.device_mac(iface.name, addr) + self.node_net_client.device_mac(iface.name, mac) def addaddr(self, iface_id: int, addr: str) -> None: """ @@ -857,14 +857,14 @@ def new_iface( # save addresses with the interface now self.attachnet(iface_id, net) iface = self.get_iface(iface_id) - iface.sethwaddr(iface_data.mac) + iface.set_mac(iface_data.mac) for address in addresses: iface.addaddr(address) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: - self.sethwaddr(iface_id, iface_data.mac) + self.set_mac(iface_id, iface_data.mac) for address in addresses: self.addaddr(iface_id, address) self.ifup(iface_id) @@ -1094,7 +1094,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat unidirectional = 1 iface2 = InterfaceData( - id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.hwaddr + id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.mac ) for address in iface.addrlist: ip, _sep, mask = address.partition("/") diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 1fb8b894d..287723a76 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -53,7 +53,7 @@ def __init__( self.othernet: Optional[CoreNetworkBase] = None self._params: Dict[str, float] = {} self.addrlist: List[str] = [] - self.hwaddr: Optional[str] = None + self.mac: Optional[str] = None # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE @@ -150,16 +150,16 @@ def deladdr(self, addr: str) -> None: """ self.addrlist.remove(addr) - def sethwaddr(self, addr: str) -> None: + def set_mac(self, mac: str) -> None: """ - Set hardware address. + Set mac address. - :param addr: hardware address to set to. + :param mac: mac address to set :return: nothing """ - if addr is not None: - addr = utils.validate_mac(addr) - self.hwaddr = addr + if mac is not None: + mac = utils.validate_mac(mac) + self.mac = mac def getparam(self, key: str) -> float: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 04d4e8f83..ef9456db4 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -895,7 +895,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat unidirectional = 1 iface1_data = InterfaceData( - id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.hwaddr + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.mac ) for address in iface1.addrlist: ip, _sep, mask = address.partition("/") @@ -908,7 +908,7 @@ def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkDat iface1.ip6_mask = mask iface2_data = InterfaceData( - id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.hwaddr + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.mac ) for address in iface2.addrlist: ip, _sep, mask = address.partition("/") diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 36bcb2679..0ce8946ae 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -65,20 +65,20 @@ def termcmdstring(self, sh: str = "/bin/sh") -> str: """ return sh - def sethwaddr(self, iface_id: int, addr: str) -> None: + def set_mac(self, iface_id: int, mac: str) -> None: """ - Set hardware address for an interface. + Set mac address for an interface. :param iface_id: index of interface to set hardware address for - :param addr: hardware address to set + :param mac: mac address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_mac(addr) + mac = utils.validate_mac(mac) iface = self.ifaces[iface_id] - iface.sethwaddr(addr) + iface.set_mac(mac) if self.up: - self.net_client.device_mac(iface.name, addr) + self.net_client.device_mac(iface.name, mac) def addaddr(self, iface_id: int, addr: str) -> None: """ @@ -111,7 +111,7 @@ def deladdr(self, iface_id: int, addr: str) -> None: self.net_client.delete_address(iface.name, addr) def adopt_iface( - self, iface: CoreInterface, iface_id: int, hwaddr: str, addrlist: List[str] + self, iface: CoreInterface, iface_id: int, mac: str, addrlist: List[str] ) -> None: """ When a link message is received linking this node to another part of @@ -126,8 +126,8 @@ def adopt_iface( self.net_client.device_down(iface.localname) self.net_client.device_name(iface.localname, iface.name) iface.localname = iface.name - if hwaddr: - self.sethwaddr(iface_id, hwaddr) + if mac: + self.set_mac(iface_id, mac) for addr in addrlist: self.addaddr(iface_id, addr) if self.up: diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 3dfef56ab..10b4fd9f0 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -69,7 +69,7 @@ def lladdrstr(iface): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % iface.hwaddr.tolinklocal() + cfg = "\t address %s {\n" % iface.mac.tolinklocal() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -305,7 +305,7 @@ def generatexorpconfig(cls, node): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - cfg += "\t\taddress %s {\n" % iface.hwaddr.tolinklocal() + cfg += "\t\taddress %s {\n" % iface.mac.tolinklocal() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 4f5114762..d716777bc 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -18,7 +18,7 @@ from core.emane.emanemanager import EmaneManager from core.emane.emanemodel import EmaneModel -_hwaddr_prefix = "02:02" +_MAC_PREFIX = "02:02" def is_external(config: Dict[str, str]) -> bool: @@ -230,9 +230,9 @@ def build_node_platform_xml( platform_element.append(nem_element) node.setnemid(iface, nem_id) - macstr = _hwaddr_prefix + ":00:00:" + macstr = _MAC_PREFIX + ":00:00:" macstr += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.sethwaddr(macstr) + iface.set_mac(macstr) # increment nem id nem_id += 1 diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 327137d23..8af2e895c 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -49,7 +49,7 @@ def test_node_delete(self, session: Session): with pytest.raises(CoreError): session.get_node(node.id, CoreNode) - def test_node_sethwaddr(self, session: Session): + def test_node_set_mac(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) @@ -58,12 +58,12 @@ def test_node_sethwaddr(self, session: Session): mac = "aa:aa:aa:ff:ff:ff" # when - node.sethwaddr(iface.node_id, mac) + node.set_mac(iface.node_id, mac) # then - assert iface.hwaddr == mac + assert iface.mac == mac - def test_node_sethwaddr_exception(self, session: Session): + def test_node_set_mac_exception(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) @@ -73,7 +73,7 @@ def test_node_sethwaddr_exception(self, session: Session): # when with pytest.raises(CoreError): - node.sethwaddr(iface.node_id, mac) + node.set_mac(iface.node_id, mac) def test_node_addaddr(self, session: Session): # given From a64047e221cb83d3f27d9c3c75f4d81afbea4036 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:27:17 -0700 Subject: [PATCH 031/210] fixed issue with xorp service depending on old MacAddress class --- daemon/core/services/xorp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 10b4fd9f0..776b1d166 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -69,7 +69,7 @@ def lladdrstr(iface): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % iface.mac.tolinklocal() + cfg = "\t address %s {\n" % netaddr.EUI(iface.mac).eui64() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -305,7 +305,7 @@ def generatexorpconfig(cls, node): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - cfg += "\t\taddress %s {\n" % iface.mac.tolinklocal() + cfg += "\t\taddress %s {\n" % netaddr.EUI(iface.mac).eui64() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" From b92ff0586a6e04d1dae8a36f159e9f6449df7489 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:43:13 -0700 Subject: [PATCH 032/210] daemon: renamed NodeData.node_type to type, removed NodeData/NodeOptions fields that were not being used for clarity --- daemon/core/api/grpc/grpcutils.py | 1 - daemon/core/api/tlv/corehandlers.py | 1 - daemon/core/api/tlv/dataconversion.py | 12 +-- daemon/core/emulator/data.py | 115 ++++++++++++-------------- daemon/core/emulator/session.py | 1 - daemon/core/nodes/base.py | 11 +-- daemon/proto/core/api/grpc/core.proto | 13 ++- 7 files changed, 66 insertions(+), 88 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 6f2911a42..7c517caf4 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -34,7 +34,6 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption name=node_proto.name, model=node_proto.model, icon=node_proto.icon, - opaque=node_proto.opaque, image=node_proto.image, services=node_proto.services, config_services=node_proto.config_services, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 631cd5387..981bdb154 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -722,7 +722,6 @@ def handle_node_message(self, message): options.icon = message.get_tlv(NodeTlvs.ICON.value) options.canvas = message.get_tlv(NodeTlvs.CANVAS.value) - options.opaque = message.get_tlv(NodeTlvs.OPAQUE.value) options.server = message.get_tlv(NodeTlvs.EMULATION_SERVER.value) services = message.get_tlv(NodeTlvs.SERVICES.value) diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index cd10ef046..62b51d397 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -18,9 +18,6 @@ def convert_node(node_data): :param core.emulator.data.NodeData node_data: node data to convert :return: packed node message """ - session = None - if node_data.session is not None: - session = str(node_data.session) services = None if node_data.services is not None: services = "|".join([x for x in node_data.services]) @@ -28,25 +25,18 @@ def convert_node(node_data): coreapi.CoreNodeTlv, [ (NodeTlvs.NUMBER, node_data.id), - (NodeTlvs.TYPE, node_data.node_type.value), + (NodeTlvs.TYPE, node_data.type.value), (NodeTlvs.NAME, node_data.name), - (NodeTlvs.IP_ADDRESS, node_data.ip_address), - (NodeTlvs.MAC_ADDRESS, node_data.mac_address), - (NodeTlvs.IP6_ADDRESS, node_data.ip6_address), (NodeTlvs.MODEL, node_data.model), - (NodeTlvs.EMULATION_ID, node_data.emulation_id), (NodeTlvs.EMULATION_SERVER, node_data.server), - (NodeTlvs.SESSION, session), (NodeTlvs.X_POSITION, int(node_data.x_position)), (NodeTlvs.Y_POSITION, int(node_data.y_position)), (NodeTlvs.CANVAS, node_data.canvas), - (NodeTlvs.NETWORK_ID, node_data.network_id), (NodeTlvs.SERVICES, services), (NodeTlvs.LATITUDE, str(node_data.latitude)), (NodeTlvs.LONGITUDE, str(node_data.longitude)), (NodeTlvs.ALTITUDE, str(node_data.altitude)), (NodeTlvs.ICON, node_data.icon), - (NodeTlvs.OPAQUE, node_data.opaque), ], ) return coreapi.CoreNodeMessage.pack(node_data.message_type.value, tlv_data) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 899d32ae0..1a7a60961 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -72,29 +72,72 @@ class FileData: compressed_data: str = None +@dataclass +class NodeOptions: + """ + Options for creating and updating nodes within core. + """ + + name: str = None + model: Optional[str] = "PC" + canvas: int = None + icon: str = None + services: List[str] = field(default_factory=list) + config_services: List[str] = field(default_factory=list) + x: float = None + y: float = None + lat: float = None + lon: float = None + alt: float = None + server: str = None + image: str = None + emane: str = None + + def set_position(self, x: float, y: float) -> None: + """ + Convenience method for setting position. + + :param x: x position + :param y: y position + :return: nothing + """ + self.x = x + self.y = y + + def set_location(self, lat: float, lon: float, alt: float) -> None: + """ + Convenience method for setting location. + + :param lat: latitude + :param lon: longitude + :param alt: altitude + :return: nothing + """ + self.lat = lat + self.lon = lon + self.alt = alt + + @dataclass class NodeData: + """ + Used to represent nodes being broadcasted. + """ + message_type: MessageFlags = None + type: NodeTypes = None id: int = None - node_type: NodeTypes = None name: str = None - ip_address: str = None - mac_address: str = None - ip6_address: str = None model: str = None - emulation_id: int = None server: str = None - session: int = None - x_position: float = None - y_position: float = None + icon: str = None canvas: int = None - network_id: int = None services: List[str] = None + x_position: float = None + y_position: float = None latitude: float = None longitude: float = None altitude: float = None - icon: str = None - opaque: str = None source: str = None @@ -158,10 +201,10 @@ class LinkData: label: str = None node1_id: int = None node2_id: int = None + network_id: int = None iface1: InterfaceData = None iface2: InterfaceData = None options: LinkOptions = LinkOptions() - network_id: int = None color: str = None @@ -259,51 +302,3 @@ def create_iface( iface_data = self.gen_iface(node.id, name, mac) iface_data.id = node.next_iface_id() return iface_data - - -@dataclass -class NodeOptions: - """ - Options for creating and updating nodes within core. - """ - - name: str = None - model: Optional[str] = "PC" - canvas: int = None - icon: str = None - opaque: str = None - services: List[str] = field(default_factory=list) - config_services: List[str] = field(default_factory=list) - x: float = None - y: float = None - lat: float = None - lon: float = None - alt: float = None - emulation_id: int = None - server: str = None - image: str = None - emane: str = None - - def set_position(self, x: float, y: float) -> None: - """ - Convenience method for setting position. - - :param x: x position - :param y: y position - :return: nothing - """ - self.x = x - self.y = y - - def set_location(self, lat: float, lon: float, alt: float) -> None: - """ - Convenience method for setting location. - - :param lat: latitude - :param lon: longitude - :param alt: altitude - :return: nothing - """ - self.lat = lat - self.lon = lon - self.alt = alt diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 814c89d9e..ccabeddb6 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -523,7 +523,6 @@ def add_node( # set node attributes node.icon = options.icon node.canvas = options.canvas - node.opaque = options.opaque # set node position and broadcast it self.set_node_position(node, options) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 97da63a43..0ecc90853 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -195,29 +195,26 @@ def data( """ if self.apitype is None: return None - x, y, _ = self.getposition() model = self.type server = None if self.server is not None: server = self.server.name - services = [service.name for service in self.services] + services = [x.name for x in self.services] return NodeData( message_type=message_type, + type=self.apitype, id=self.id, - node_type=self.apitype, name=self.name, - emulation_id=self.id, + model=model, + server=server, canvas=self.canvas, icon=self.icon, - opaque=self.opaque, x_position=x, y_position=y, latitude=self.position.lat, longitude=self.position.lon, altitude=self.position.alt, - model=model, - server=server, services=services, source=source, ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 2819c5eae..46e1da914 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -692,13 +692,12 @@ message Node { repeated string services = 6; string emane = 7; string icon = 8; - string opaque = 9; - string image = 10; - string server = 11; - repeated string config_services = 12; - Geo geo = 13; - string dir = 14; - string channel = 15; + string image = 9; + string server = 10; + repeated string config_services = 11; + Geo geo = 12; + string dir = 13; + string channel = 14; } message Link { From 5d34a2b752631580e4d5ca01daaf0714fc707e44 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:59:50 -0700 Subject: [PATCH 033/210] daemon: removed opaque from NodeBase, since it is not used --- daemon/core/nodes/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 0ecc90853..378549ab8 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -71,7 +71,6 @@ def __init__( self.iface_id: int = 0 self.canvas: Optional[int] = None self.icon: Optional[str] = None - self.opaque: Optional[str] = None self.position: Position = Position() self.up: bool = False use_ovs = session.options.get_config("ovs") == "True" From 3d7d775bfbf5c673837baeecd25faa078e3906a6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 00:15:44 -0700 Subject: [PATCH 034/210] daemon: removed unused variables from LinkOptions --- daemon/core/api/grpc/grpcutils.py | 26 ++++++++++++-------------- daemon/core/api/grpc/server.py | 23 +++++++++++------------ daemon/core/api/tlv/corehandlers.py | 7 ------- daemon/core/emulator/data.py | 3 --- daemon/core/xml/corexml.py | 6 ------ 5 files changed, 23 insertions(+), 42 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 7c517caf4..d95b75559 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -88,19 +88,18 @@ def add_link_data( iface2_data = link_iface(link_proto.iface2) link_type = LinkTypes(link_proto.type) options = LinkOptions() - options_data = link_proto.options - if options_data: - options.delay = options_data.delay - options.bandwidth = options_data.bandwidth - options.loss = options_data.loss - options.dup = options_data.dup - options.jitter = options_data.jitter - options.mer = options_data.mer - options.burst = options_data.burst - options.mburst = options_data.mburst - options.unidirectional = options_data.unidirectional - options.key = options_data.key - options.opaque = options_data.opaque + options_proto = link_proto.options + if options_proto: + options.delay = options_proto.delay + options.bandwidth = options_proto.bandwidth + options.loss = options_proto.loss + options.dup = options_proto.dup + options.jitter = options_proto.jitter + options.mer = options_proto.mer + options.burst = options_proto.burst + options.mburst = options_proto.mburst + options.unidirectional = options_proto.unidirectional + options.key = options_proto.key return iface1_data, iface2_data, options, link_type @@ -320,7 +319,6 @@ def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions: return core_pb2.LinkOptions( - opaque=options_data.opaque, jitter=options_data.jitter, key=options_data.key, mburst=options_data.mburst, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index b9e0e0aae..1964b6e81 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -879,19 +879,18 @@ def EditLink( node2_id = request.node2_id iface1_id = request.iface1_id iface2_id = request.iface2_id - options_data = request.options + options_proto = request.options options = LinkOptions( - delay=options_data.delay, - bandwidth=options_data.bandwidth, - loss=options_data.loss, - dup=options_data.dup, - jitter=options_data.jitter, - mer=options_data.mer, - burst=options_data.burst, - mburst=options_data.mburst, - unidirectional=options_data.unidirectional, - key=options_data.key, - opaque=options_data.opaque, + delay=options_proto.delay, + bandwidth=options_proto.bandwidth, + loss=options_proto.loss, + dup=options_proto.dup, + jitter=options_proto.jitter, + mer=options_proto.mer, + burst=options_proto.burst, + mburst=options_proto.mburst, + unidirectional=options_proto.unidirectional, + key=options_proto.key, ) session.update_link(node1_id, node2_id, iface1_id, iface2_id, options) return core_pb2.EditLinkResponse(result=True) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 981bdb154..379b739e9 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -371,9 +371,7 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.BURST, options_data.burst), (LinkTlvs.MBURST, options_data.mburst), (LinkTlvs.TYPE, link_data.type.value), - (LinkTlvs.GUI_ATTRIBUTES, options_data.gui_attributes), (LinkTlvs.UNIDIRECTIONAL, options_data.unidirectional), - (LinkTlvs.EMULATION_ID, options_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), (LinkTlvs.KEY, options_data.key), (LinkTlvs.IFACE1_NUMBER, iface1.id), @@ -388,7 +386,6 @@ def handle_broadcast_link(self, link_data): (LinkTlvs.IFACE2_MAC, iface2.mac), (LinkTlvs.IFACE2_IP6, iface2.ip6), (LinkTlvs.IFACE2_IP6_MASK, iface2.ip6_mask), - (LinkTlvs.OPAQUE, options_data.opaque), ], ) @@ -792,12 +789,8 @@ def handle_link_message(self, message): options.mer = message.get_tlv(LinkTlvs.MER.value) options.burst = message.get_tlv(LinkTlvs.BURST.value) options.mburst = message.get_tlv(LinkTlvs.MBURST.value) - options.gui_attributes = message.get_tlv(LinkTlvs.GUI_ATTRIBUTES.value) options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value) - options.emulation_id = message.get_tlv(LinkTlvs.EMULATION_ID.value) - options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) options.key = message.get_tlv(LinkTlvs.KEY.value) - options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: self.session.add_link( diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 1a7a60961..c57f8b24f 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -183,11 +183,8 @@ class LinkOptions: mer: int = None burst: int = None mburst: int = None - gui_attributes: str = None unidirectional: int = None - emulation_id: int = None key: int = None - opaque: str = None @dataclass diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 4febe71f6..190cf8f77 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -547,12 +547,9 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: add_attribute(options, "mer", options_data.mer) add_attribute(options, "burst", options_data.burst) add_attribute(options, "mburst", options_data.mburst) - add_attribute(options, "gui_attributes", options_data.gui_attributes) add_attribute(options, "unidirectional", options_data.unidirectional) - add_attribute(options, "emulation_id", options_data.emulation_id) add_attribute(options, "network_id", link_data.network_id) add_attribute(options, "key", options_data.key) - add_attribute(options, "opaque", options_data.opaque) if options.items(): link_element.append(options) @@ -939,9 +936,6 @@ def read_links(self) -> None: if options.loss is None: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") - options.emulation_id = get_int(options_element, "emulation_id") - options.opaque = options_element.get("opaque") - options.gui_attributes = options_element.get("gui_attributes") if options.unidirectional == 1 and node_set in node_sets: logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) From 1702fe256f2ae153e65baabcb963c0d6638f1397 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 00:30:39 -0700 Subject: [PATCH 035/210] doc: updated refactored example in documentation --- docs/services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/services.md b/docs/services.md index 9f47ae482..2ce52e997 100644 --- a/docs/services.md +++ b/docs/services.md @@ -263,7 +263,7 @@ class MyService(CoreService): if filename == cls.configs[0]: cfg += "# auto-generated by MyService (sample.py)\n" - for ifc in node.netifs(): + for ifc in node.get_ifaces(): cfg += f'echo "Node {node.name} has interface {ifc.name}"\n' elif filename == cls.configs[1]: cfg += "echo hello" From ecc3eb1c891b8458e944158b9a14b0f43f9348d8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 09:06:31 -0700 Subject: [PATCH 036/210] daemon: refactored NodeData to reference a node instead of replicating fields as an intermediate passthrough, removed data() functions from nodes due to this change --- daemon/core/api/grpc/events.py | 22 +++++++++------- daemon/core/api/tlv/corehandlers.py | 1 - daemon/core/api/tlv/dataconversion.py | 36 ++++++++++++++----------- daemon/core/emulator/data.py | 19 +++----------- daemon/core/emulator/session.py | 4 +-- daemon/core/nodes/base.py | 38 +-------------------------- daemon/core/nodes/interface.py | 21 +-------------- daemon/core/nodes/network.py | 15 +---------- daemon/core/plugins/sdt.py | 18 +++++-------- daemon/tests/test_grpc.py | 7 ++--- 10 files changed, 52 insertions(+), 129 deletions(-) diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index ff65142d3..75f9eb2ee 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -15,24 +15,28 @@ from core.emulator.session import Session -def handle_node_event(event: NodeData) -> core_pb2.NodeEvent: +def handle_node_event(node_data: NodeData) -> core_pb2.NodeEvent: """ Handle node event when there is a node event - :param event: node data + :param node_data: node data :return: node event that contains node id, name, model, position, and services """ - position = core_pb2.Position(x=event.x_position, y=event.y_position) - geo = core_pb2.Geo(lat=event.latitude, lon=event.longitude, alt=event.altitude) + node = node_data.node + x, y, _ = node.position.get() + position = core_pb2.Position(x=x, y=y) + lon, lat, alt = node.position.get_geo() + geo = core_pb2.Geo(lon=lon, lat=lat, alt=alt) + services = [x.name for x in node.services] node_proto = core_pb2.Node( - id=event.id, - name=event.name, - model=event.model, + id=node.id, + name=node.name, + model=node.type, position=position, geo=geo, - services=event.services, + services=services, ) - return core_pb2.NodeEvent(node=node_proto, source=event.source) + return core_pb2.NodeEvent(node=node_proto, source=node_data.source) def handle_link_event(event: LinkData) -> core_pb2.LinkEvent: diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 379b739e9..d01f15a3e 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -329,7 +329,6 @@ def handle_broadcast_node(self, node_data): """ logging.debug("handling broadcast node: %s", node_data) message = dataconversion.convert_node(node_data) - try: self.sendall(message) except IOError: diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index 62b51d397..8a26300ad 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -8,35 +8,39 @@ 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 +from core.emulator.data import ConfigData, NodeData -def convert_node(node_data): +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_data.services is not None: - services = "|".join([x for x in node_data.services]) + 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_data.id), - (NodeTlvs.TYPE, node_data.type.value), - (NodeTlvs.NAME, node_data.name), - (NodeTlvs.MODEL, node_data.model), - (NodeTlvs.EMULATION_SERVER, node_data.server), - (NodeTlvs.X_POSITION, int(node_data.x_position)), - (NodeTlvs.Y_POSITION, int(node_data.y_position)), - (NodeTlvs.CANVAS, node_data.canvas), + (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_data.latitude)), - (NodeTlvs.LONGITUDE, str(node_data.longitude)), - (NodeTlvs.ALTITUDE, str(node_data.altitude)), - (NodeTlvs.ICON, node_data.icon), + (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) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index c57f8b24f..5b6479ae4 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -12,11 +12,10 @@ ExceptionLevels, LinkTypes, MessageFlags, - NodeTypes, ) if TYPE_CHECKING: - from core.nodes.base import CoreNode + from core.nodes.base import CoreNode, NodeBase @dataclass @@ -121,23 +120,11 @@ def set_location(self, lat: float, lon: float, alt: float) -> None: @dataclass class NodeData: """ - Used to represent nodes being broadcasted. + Node to broadcast. """ + node: "NodeBase" message_type: MessageFlags = None - type: NodeTypes = None - id: int = None - name: str = None - model: str = None - server: str = None - icon: str = None - canvas: int = None - services: List[str] = None - x_position: float = None - y_position: float = None - latitude: float = None - longitude: float = None - altitude: float = None source: str = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index ccabeddb6..0b97da937 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -807,9 +807,9 @@ def broadcast_node( :param source: source of broadcast, None by default :return: nothing """ - node_data = node.data(message_type, source) - if not node_data: + if not node.apitype: return + node_data = NodeData(node=node, message_type=message_type, source=source) for handler in self.node_handlers: handler(node_data) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 378549ab8..8a5c579ac 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -14,7 +14,7 @@ from core import utils from core.configservice.dependencies import ConfigServiceDependencies from core.constants import MOUNT_BIN, VNODED_BIN -from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError from core.nodes.client import VnodeClient @@ -182,42 +182,6 @@ def next_iface_id(self) -> int: self.iface_id += 1 return iface_id - def data( - self, message_type: MessageFlags = MessageFlags.NONE, source: str = None - ) -> Optional[NodeData]: - """ - Build a data object for this node. - - :param message_type: purpose for the data object we are creating - :param source: source of node data - :return: node data object - """ - if self.apitype is None: - return None - x, y, _ = self.getposition() - model = self.type - server = None - if self.server is not None: - server = self.server.name - services = [x.name for x in self.services] - return NodeData( - message_type=message_type, - type=self.apitype, - id=self.id, - name=self.name, - model=model, - server=server, - canvas=self.canvas, - icon=self.icon, - x_position=x, - y_position=y, - latitude=self.position.lat, - longitude=self.position.lon, - altitude=self.position.alt, - services=services, - source=source, - ) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build link data for this node. diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 287723a76..680def1b2 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -8,7 +8,7 @@ from core import utils from core.emulator.data import LinkOptions -from core.emulator.enumerations import MessageFlags, TransportType +from core.emulator.enumerations import TransportType from core.errors import CoreCommandError from core.nodes.netclient import LinuxNetClient, get_net_client @@ -561,23 +561,4 @@ def shutdown(self) -> None: self.net_client.delete_device(self.localname) except CoreCommandError: logging.exception("error during shutdown") - self.localname = None - - def data(self, message_type: int) -> None: - """ - Data for a gre tap. - - :param message_type: message type for data - :return: None - """ - return None - - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List: - """ - Retrieve link data. - - :param flags: link flags - :return: link data - """ - return [] diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index ef9456db4..f5baf3269 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -11,7 +11,7 @@ from core import utils from core.constants import EBTABLES_BIN, TC_BIN -from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -862,19 +862,6 @@ def attach(self, iface: CoreInterface) -> None: ) super().attach(iface) - def data( - self, message_type: MessageFlags = MessageFlags.NONE, source: str = None - ) -> Optional[NodeData]: - """ - Do not generate a Node Message for point-to-point links. They are - built using a link message instead. - - :param message_type: purpose for the data object we are creating - :param source: source of node data - :return: node data object - """ - return None - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build CORE API TLVs for a point-to-point link. One Link message diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 04fff3e43..84c907302 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -314,26 +314,22 @@ def handle_node_update(self, node_data: NodeData) -> None: :param node_data: node data being updated :return: nothing """ - logging.debug("sdt handle node update: %s - %s", node_data.id, node_data.name) if not self.connect(): return - - # delete node + node = node_data.node + logging.debug("sdt handle node update: %s - %s", node.id, node.name) if node_data.message_type == MessageFlags.DELETE: - self.cmd(f"delete node,{node_data.id}") + self.cmd(f"delete node,{node.id}") else: - x = node_data.x_position - y = node_data.y_position - lat = node_data.latitude - lon = node_data.longitude - alt = node_data.altitude + x, y, _ = node.position.get() + lon, lat, alt = node.position.get_geo() if all([lat is not None, lon is not None, alt is not None]): pos = f"pos {lon:.6f},{lat:.6f},{alt:.6f}" - self.cmd(f"node {node_data.id} {pos}") + self.cmd(f"node {node.id} {pos}") elif node_data.message_type == 0: lat, lon, alt = self.session.location.getgeo(x, y, 0) pos = f"pos {lon:.6f},{lat:.6f},{alt:.6f}" - self.cmd(f"node {node_data.id} {pos}") + self.cmd(f"node {node.id} {pos}") def wireless_net_check(self, node_id: int) -> bool: """ diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index cff7cd85c..8abf33aa8 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1198,9 +1198,10 @@ def test_move_nodes_geo(self, grpc_server: CoreGrpcServer): queue = Queue() def node_handler(node_data: NodeData): - assert node_data.longitude == lon - assert node_data.latitude == lat - assert node_data.altitude == alt + n = node_data.node + assert n.position.lon == lon + assert n.position.lat == lat + assert n.position.alt == alt queue.put(node_data) session.node_handlers.append(node_handler) From e46a072f744c198e702e5fce5347538aa0ac1456 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 09:33:54 -0700 Subject: [PATCH 037/210] daemon: removed missing params from python docs, updated node ValueErrors to CoreErrors --- daemon/core/nodes/interface.py | 4 ++-- daemon/core/nodes/network.py | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 680def1b2..425223622 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -9,7 +9,7 @@ from core import utils from core.emulator.data import LinkOptions from core.emulator.enumerations import TransportType -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError from core.nodes.netclient import LinuxNetClient, get_net_client if TYPE_CHECKING: @@ -544,7 +544,7 @@ def __init__( if not start: return if remoteip is None: - raise ValueError("missing remote IP required for GRE TAP device") + raise CoreError("missing remote IP required for GRE TAP device") self.net_client.create_gretap(self.localname, remoteip, localip, ttl, key) self.net_client.device_up(self.localname) self.up = True diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index f5baf3269..f20b6dfb5 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -618,7 +618,6 @@ def __init__( :param localip: local address :param ttl: ttl value :param key: gre tap key - :param start: start flag :param server: remote server node will run on, default is None for localhost """ @@ -857,9 +856,7 @@ def attach(self, iface: CoreInterface) -> None: :return: nothing """ if len(self.ifaces) >= 2: - raise ValueError( - "Point-to-point links support at most 2 network interfaces" - ) + raise CoreError("ptp links support at most 2 network interfaces") super().attach(iface) def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: @@ -992,7 +989,6 @@ def __init__( :param session: core session instance :param _id: node id :param name: node name - :param start: start flag :param server: remote server node will run on, default is None for localhost :param policy: wlan policy From cd74a44558596d259e0b878b49ac605edf389665 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 12:54:36 -0700 Subject: [PATCH 038/210] daemon: added type hinting throughout all services and made small tweaks/fixes that were ran across --- daemon/core/nodes/base.py | 4 +- daemon/core/services/bird.py | 86 +++++------ daemon/core/services/emaneservices.py | 25 ++-- daemon/core/services/frr.py | 202 ++++++++++++------------- daemon/core/services/nrl.py | 154 +++++++++---------- daemon/core/services/quagga.py | 196 ++++++++++++------------ daemon/core/services/sdn.py | 44 +++--- daemon/core/services/security.py | 112 +++++++------- daemon/core/services/ucarp.py | 42 +++--- daemon/core/services/utility.py | 206 ++++++++++++-------------- daemon/core/services/xorp.py | 125 +++++++--------- 11 files changed, 560 insertions(+), 636 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 8a5c579ac..4fc6b8733 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union import netaddr @@ -27,7 +27,7 @@ from core.configservice.base import ConfigService from core.services.coreservices import CoreService - CoreServices = List[CoreService] + CoreServices = List[Union[CoreService, Type[CoreService]]] ConfigServiceType = Type[ConfigService] _DEFAULT_MTU = 1500 diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index 16f0bb84b..a50529426 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -1,8 +1,11 @@ """ bird.py: defines routing services provided by the BIRD Internet Routing Daemon. """ +from typing import Optional, Tuple + import netaddr +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -11,27 +14,27 @@ class Bird(CoreService): Bird router support """ - name = "bird" - executables = ("bird",) - group = "BIRD" - dirs = ("/etc/bird",) - configs = ("/etc/bird/bird.conf",) - startup = ("bird -c %s" % (configs[0]),) - shutdown = ("killall bird",) - validate = ("pidof bird",) + name: str = "bird" + group: str = "BIRD" + executables: Tuple[str, ...] = ("bird",) + dirs: Tuple[str, ...] = ("/etc/bird",) + configs: Tuple[str, ...] = ("/etc/bird/bird.conf",) + startup: Tuple[str, ...] = ("bird -c %s" % (configs[0]),) + shutdown: Tuple[str, ...] = ("killall bird",) + validate: Tuple[str, ...] = ("pidof bird",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the bird.conf file contents. """ if filename == cls.configs[0]: - return cls.generateBirdConf(node) + return cls.generate_bird_config(node) else: raise ValueError @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -40,15 +43,13 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @classmethod - def generateBirdConf(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on bird - will have generatebirdifcconfig() and generatebirdconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ cfg = """\ /* Main configuration file for BIRD. This is ony a template, @@ -75,15 +76,16 @@ def generateBirdConf(cls, node): """ % ( cls.name, - cls.routerid(node), + cls.router_id(node), ) - # Generate protocol specific configurations + # generate protocol specific configurations for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, BirdService) or issubclass(s, BirdService)): + continue cfg += s.generate_bird_config(node) - return cfg @@ -93,32 +95,26 @@ class BirdService(CoreService): common to Bird's routing daemons. """ - name = None - executables = ("bird",) - group = "BIRD" - dependencies = ("bird",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the bird service." + name: Optional[str] = None + group: str = "BIRD" + executables: Tuple[str, ...] = ("bird",) + dependencies: Tuple[str, ...] = ("bird",) + meta: str = "The config file for this service can be found in the bird service." @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: return "" @classmethod - def generate_bird_iface_config(cls, node): + def generate_bird_iface_config(cls, node: CoreNode) -> str: """ Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same everywhere. """ cfg = "" - for iface in node.get_ifaces(control=False): cfg += ' interface "%s";\n' % iface.name - return cfg @@ -127,11 +123,11 @@ class BirdBgp(BirdService): BGP BIRD Service (configuration generation) """ - name = "BIRD_BGP" - custom_needed = True + name: str = "BIRD_BGP" + custom_needed: bool = True @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: return """ /* This is a sample config that should be customized with appropriate AS numbers * and peers; add one section like this for each neighbor */ @@ -158,10 +154,10 @@ class BirdOspf(BirdService): OSPF BIRD Service (configuration generation) """ - name = "BIRD_OSPFv2" + name: str = "BIRD_OSPFv2" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "protocol ospf {\n" cfg += " export filter {\n" cfg += " if source = RTS_BGP then {\n" @@ -174,7 +170,6 @@ def generate_bird_config(cls, node): cfg += cls.generate_bird_iface_config(node) cfg += " };\n" cfg += "}\n\n" - return cfg @@ -183,12 +178,11 @@ class BirdRadv(BirdService): RADV BIRD Service (configuration generation) """ - name = "BIRD_RADV" + name: str = "BIRD_RADV" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that must be customized */\n" - cfg += "protocol radv {\n" cfg += " # auto configuration on all interfaces\n" cfg += cls.generate_bird_iface_config(node) @@ -202,7 +196,6 @@ def generate_bird_config(cls, node): cfg += "# ns 2001:0DB8:1234::12;\n" cfg += " };\n" cfg += "}\n\n" - return cfg @@ -211,10 +204,10 @@ class BirdRip(BirdService): RIP BIRD Service (configuration generation) """ - name = "BIRD_RIP" + name: str = "BIRD_RIP" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "protocol rip {\n" cfg += " period 10;\n" cfg += " garbage time 60;\n" @@ -224,7 +217,6 @@ def generate_bird_config(cls, node): cfg += " import all;\n" cfg += " export all;\n" cfg += "}\n\n" - return cfg @@ -233,11 +225,11 @@ class BirdStatic(BirdService): Static Bird Service (configuration generation) """ - name = "BIRD_static" - custom_needed = True + name: str = "BIRD_static" + custom_needed: bool = True @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol static {\n" cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n" diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index da438bab0..ef188faba 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -1,23 +1,26 @@ +from typing import Tuple + from core.emane.nodes import EmaneNet from core.errors import CoreError +from core.nodes.base import CoreNode from core.services.coreservices import CoreService from core.xml import emanexml class EmaneTransportService(CoreService): - name = "transportd" - executables = ("emanetransportd", "emanegentransportxml") - group = "EMANE" - dependencies = () - dirs = () - configs = ("emanetransport.sh",) - startup = ("sh %s" % configs[0],) - validate = ("pidof %s" % executables[0],) - validation_timer = 0.5 - shutdown = ("killall %s" % executables[0],) + name: str = "transportd" + group: str = "EMANE" + executables: Tuple[str, ...] = ("emanetransportd", "emanegentransportxml") + dependencies: Tuple[str, ...] = () + dirs: Tuple[str, ...] = () + configs: Tuple[str, ...] = ("emanetransport.sh",) + startup: Tuple[str, ...] = ("sh %s" % configs[0],) + validate: Tuple[str, ...] = ("pidof %s" % executables[0],) + validation_timer: float = 0.5 + shutdown: Tuple[str, ...] = ("killall %s" % executables[0],) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: if filename == cls.configs[0]: transport_commands = [] for iface in node.get_ifaces(): diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 97a8b3341..e75d8f565 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -2,60 +2,63 @@ frr.py: defines routing services provided by FRRouting. Assumes installation of FRR via https://deb.frrouting.org/ """ +from typing import Optional, Tuple + import netaddr from core import constants from core.emane.nodes import EmaneNet +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService class FRRZebra(CoreService): - name = "FRRzebra" - group = "FRR" - dirs = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") - configs = ( + name: str = "FRRzebra" + group: str = "FRR" + dirs: Tuple[str, ...] = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") + configs: Tuple[str, ...] = ( "/usr/local/etc/frr/frr.conf", "frrboot.sh", "/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/daemons", ) - startup = ("sh frrboot.sh zebra",) - shutdown = ("killall zebra",) - validate = ("pidof zebra",) + startup: Tuple[str, ...] = ("sh frrboot.sh zebra",) + shutdown: Tuple[str, ...] = ("killall zebra",) + validate: Tuple[str, ...] = ("pidof zebra",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the frr.conf or frrboot.sh file contents. """ if filename == cls.configs[0]: - return cls.generateFrrConf(node) + return cls.generate_frr_conf(node) elif filename == cls.configs[1]: - return cls.generateFrrBoot(node) + return cls.generate_frr_boot(node) elif filename == cls.configs[2]: - return cls.generateVtyshConf(node) + return cls.generate_vtysh_conf(node) elif filename == cls.configs[3]: - return cls.generateFrrDaemons(node) + return cls.generate_frr_daemons(node) else: raise ValueError( "file name (%s) is not a known configuration: %s", filename, cls.configs ) @classmethod - def generateVtyshConf(cls, node): + def generate_vtysh_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ return "service integrated-vtysh-config\n" @classmethod - def generateFrrConf(cls, node): + def generate_frr_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra - will have generatefrrifcconfig() and generatefrrconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ # we could verify here that filename == frr.conf cfg = "" @@ -108,7 +111,7 @@ def generateFrrConf(cls, node): return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to zebra config statements """ @@ -121,7 +124,7 @@ def addrstr(x): raise ValueError("invalid address: %s", x) @classmethod - def generateFrrBoot(cls, node): + def generate_frr_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the FRR daemons. """ @@ -244,7 +247,7 @@ def generateFrrBoot(cls, node): return cfg @classmethod - def generateFrrDaemons(cls, node): + def generate_frr_daemons(cls, node: CoreNode) -> str: """ Returns configuration file text. """ @@ -317,20 +320,15 @@ class FrrService(CoreService): common to FRR's routing daemons. """ - name = None - group = "FRR" - dependencies = ("FRRzebra",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the Zebra service." - - ipv4_routing = False - ipv6_routing = False + name: Optional[str] = None + group: str = "FRR" + dependencies: Tuple[str, ...] = ("FRRzebra",) + meta: str = "The config file for this service can be found in the Zebra service." + ipv4_routing: bool = False + ipv6_routing: bool = False @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -339,11 +337,10 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @staticmethod - def rj45check(iface): + def rj45check(iface: CoreInterface) -> bool: """ Helper to detect whether interface is connected an external RJ45 link. @@ -357,15 +354,15 @@ def rj45check(iface): return False @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: return "" @@ -376,14 +373,13 @@ class FRROspfv2(FrrService): unified frr.conf file. """ - name = "FRROSPFv2" - startup = () - shutdown = ("killall ospfd",) - validate = ("pidof ospfd",) - ipv4_routing = True + name: str = "FRROSPFv2" + shutdown: Tuple[str, ...] = ("killall ospfd",) + validate: Tuple[str, ...] = ("pidof ospfd",) + ipv4_routing: bool = True @staticmethod - def mtucheck(iface): + def mtu_check(iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a @@ -401,7 +397,7 @@ def mtucheck(iface): return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -411,9 +407,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): @@ -426,8 +422,8 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) class FRROspfv3(FrrService): @@ -437,15 +433,14 @@ class FRROspfv3(FrrService): unified frr.conf file. """ - name = "FRROSPFv3" - startup = () - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "FRROSPFv3" + shutdown: Tuple[str, ...] = ("killall ospf6d",) + validate: Tuple[str, ...] = ("pidof ospf6d",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def minmtu(iface): + def min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -459,20 +454,20 @@ def minmtu(iface): return mtu @classmethod - def mtucheck(cls, iface): + def mtu_check(cls, iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(iface) + minmtu = cls.min_mtu(iface) if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -482,9 +477,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): cfg += " interface %s area 0.0.0.0\n" % iface.name @@ -492,14 +487,13 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) # cfg = cls.mtucheck(ifc) # external RJ45 connections will use default OSPF timers # if cls.rj45check(ifc): # return cfg # cfg += cls.ptpcheck(ifc) - # return cfg + """\ @@ -516,21 +510,20 @@ class FRRBgp(FrrService): having the same AS number. """ - name = "FRRBGP" - startup = () - shutdown = ("killall bgpd",) - validate = ("pidof bgpd",) - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "FRRBGP" + shutdown: Tuple[str, ...] = ("killall bgpd",) + validate: Tuple[str, ...] = ("pidof bgpd",) + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" cfg += "router bgp %s\n" % node.id - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" @@ -542,14 +535,13 @@ class FRRRip(FrrService): The RIP service provides IPv4 routing for wired networks. """ - name = "FRRRIP" - startup = () - shutdown = ("killall ripd",) - validate = ("pidof ripd",) - ipv4_routing = True + name: str = "FRRRIP" + shutdown: Tuple[str, ...] = ("killall ripd",) + validate: Tuple[str, ...] = ("pidof ripd",) + ipv4_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = """\ router rip redistribute static @@ -566,14 +558,13 @@ class FRRRipng(FrrService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "FRRRIPNG" - startup = () - shutdown = ("killall ripngd",) - validate = ("pidof ripngd",) - ipv6_routing = True + name: str = "FRRRIPNG" + shutdown: Tuple[str, ...] = ("killall ripngd",) + validate: Tuple[str, ...] = ("pidof ripngd",) + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = """\ router ripng redistribute static @@ -591,14 +582,13 @@ class FRRBabel(FrrService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "FRRBabel" - startup = () - shutdown = ("killall babeld",) - validate = ("pidof babeld",) - ipv6_routing = True + name: str = "FRRBabel" + shutdown: Tuple[str, ...] = ("killall babeld",) + validate: Tuple[str, ...] = ("pidof babeld",) + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router babel\n" for iface in node.get_ifaces(control=False): cfg += " network %s\n" % iface.name @@ -606,7 +596,7 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)): return " babel wireless\n no babel split-horizon\n" else: @@ -618,14 +608,13 @@ class FRRpimd(FrrService): PIM multicast routing based on XORP. """ - name = "FRRpimd" - startup = () - shutdown = ("killall pimd",) - validate = ("pidof pimd",) - ipv4_routing = True + name: str = "FRRpimd" + shutdown: Tuple[str, ...] = ("killall pimd",) + validate: Tuple[str, ...] = ("pidof pimd",) + ipv4_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": @@ -641,7 +630,7 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return " ip mfea\n ip igmp\n ip pim\n" @@ -652,15 +641,14 @@ class FRRIsis(FrrService): unified frr.conf file. """ - name = "FRRISIS" - startup = () - shutdown = ("killall isisd",) - validate = ("pidof isisd",) - ipv4_routing = True - ipv6_routing = True + name: str = "FRRISIS" + shutdown: Tuple[str, ...] = ("killall isisd",) + validate: Tuple[str, ...] = ("pidof isisd",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -670,7 +658,7 @@ def ptpcheck(iface): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router isis DEFAULT\n" cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id cfg += " metric-style wide\n" @@ -679,9 +667,9 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: cfg = " ip router isis DEFAULT\n" cfg += " ipv6 router isis DEFAULT\n" cfg += " isis circuit-type level-2-only\n" - cfg += cls.ptpcheck(iface) + cfg += cls.ptp_check(iface) return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 38b90d488..9933b1300 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -2,9 +2,12 @@ nrl.py: defines services provided by NRL protolib tools hosted here: http://www.nrl.navy.mil/itd/ncs/products """ +from typing import Optional, Tuple + import netaddr from core import utils +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -14,19 +17,15 @@ class NrlService(CoreService): common to NRL's routing daemons. """ - name = None - group = "ProtoSvc" - dirs = () - configs = () - startup = () - shutdown = () + name: Optional[str] = None + group: str = "ProtoSvc" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @staticmethod - def firstipv4prefix(node, prefixlen=24): + def firstipv4prefix(node: CoreNode, prefixlen: int = 24) -> str: """ Similar to QuaggaService.routerid(). Helper to return the first IPv4 prefix of a node, using the supplied prefix length. This ignores the @@ -37,20 +36,19 @@ def firstipv4prefix(node, prefixlen=24): a = a.split("/")[0] if netaddr.valid_ipv4(a): return f"{a}/{prefixlen}" - # raise ValueError, "no IPv4 address found" return "0.0.0.0/%s" % prefixlen class MgenSinkService(NrlService): - name = "MGEN_Sink" - executables = ("mgen",) - configs = ("sink.mgen",) - startup = ("mgen input sink.mgen",) - validate = ("pidof mgen",) - shutdown = ("killall mgen",) + name: str = "MGEN_Sink" + executables: Tuple[str, ...] = ("mgen",) + configs: Tuple[str, ...] = ("sink.mgen",) + startup: Tuple[str, ...] = ("mgen input sink.mgen",) + validate: Tuple[str, ...] = ("pidof mgen",) + shutdown: Tuple[str, ...] = ("killall mgen",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "0.0 LISTEN UDP 5000\n" for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) @@ -58,7 +56,7 @@ def generate_config(cls, node, filename): return cfg @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: cmd = cls.startup[0] cmd += " output /tmp/mgen_%s.log" % node.name return (cmd,) @@ -69,32 +67,29 @@ class NrlNhdp(NrlService): NeighborHood Discovery Protocol for MANET networks. """ - name = "NHDP" - executables = ("nrlnhdp",) - startup = ("nrlnhdp",) - shutdown = ("killall nrlnhdp",) - validate = ("pidof nrlnhdp",) + name: str = "NHDP" + executables: Tuple[str, ...] = ("nrlnhdp",) + startup: Tuple[str, ...] = ("nrlnhdp",) + shutdown: Tuple[str, ...] = ("killall nrlnhdp",) + validate: Tuple[str, ...] = ("pidof nrlnhdp",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] cmd += " -l /var/log/nrlnhdp.log" cmd += " -rpipe %s_nhdp" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @@ -103,15 +98,15 @@ class NrlSmf(NrlService): Simplified Multicast Forwarding for MANET networks. """ - name = "SMF" - executables = ("nrlsmf",) - startup = ("sh startsmf.sh",) - shutdown = ("killall nrlsmf",) - validate = ("pidof nrlsmf",) - configs = ("startsmf.sh",) + name: str = "SMF" + executables: Tuple[str, ...] = ("nrlsmf",) + startup: Tuple[str, ...] = ("sh startsmf.sh",) + shutdown: Tuple[str, ...] = ("killall nrlsmf",) + validate: Tuple[str, ...] = ("pidof nrlsmf",) + configs: Tuple[str, ...] = ("startsmf.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for SMF. Because nrlsmf does not daemonize, it can cause problems in some situations when launched @@ -146,7 +141,6 @@ def generate_config(cls, node, filename): cmd += " hash MD5" cmd += " log /var/log/nrlsmf.log" - cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg @@ -156,14 +150,14 @@ class NrlOlsr(NrlService): Optimized Link State Routing protocol for MANET networks. """ - name = "OLSR" - executables = ("nrlolsrd",) - startup = ("nrlolsrd",) - shutdown = ("killall nrlolsrd",) - validate = ("pidof nrlolsrd",) + name: str = "OLSR" + executables: Tuple[str, ...] = ("nrlolsrd",) + startup: Tuple[str, ...] = ("nrlolsrd",) + shutdown: Tuple[str, ...] = ("killall nrlolsrd",) + validate: Tuple[str, ...] = ("pidof nrlolsrd",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ @@ -175,14 +169,12 @@ def get_startup(cls, node): cmd += " -i %s" % iface.name cmd += " -l /var/log/nrlolsrd.log" cmd += " -rpipe %s_olsr" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames and "NHDP" not in servicenames: cmd += " -flooding s-mpr" cmd += " -smfClient %s_smf" % node.name if "zebra" in servicenames: cmd += " -z" - return (cmd,) @@ -191,34 +183,30 @@ class NrlOlsrv2(NrlService): Optimized Link State Routing protocol version 2 for MANET networks. """ - name = "OLSRv2" - executables = ("nrlolsrv2",) - startup = ("nrlolsrv2",) - shutdown = ("killall nrlolsrv2",) - validate = ("pidof nrlolsrv2",) + name: str = "OLSRv2" + executables: Tuple[str, ...] = ("nrlolsrv2",) + startup: Tuple[str, ...] = ("nrlolsrv2",) + shutdown: Tuple[str, ...] = ("killall nrlolsrv2",) + validate: Tuple[str, ...] = ("pidof nrlolsrv2",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] cmd += " -l /var/log/nrlolsrv2.log" cmd += " -rpipe %s_olsrv2" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - cmd += " -p olsr" - ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @@ -227,16 +215,16 @@ class OlsrOrg(NrlService): Optimized Link State Routing protocol from olsr.org for MANET networks. """ - name = "OLSRORG" - executables = ("olsrd",) - configs = ("/etc/olsrd/olsrd.conf",) - dirs = ("/etc/olsrd",) - startup = ("olsrd",) - shutdown = ("killall olsrd",) - validate = ("pidof olsrd",) + name: str = "OLSRORG" + executables: Tuple[str, ...] = ("olsrd",) + configs: Tuple[str, ...] = ("/etc/olsrd/olsrd.conf",) + dirs: Tuple[str, ...] = ("/etc/olsrd",) + startup: Tuple[str, ...] = ("olsrd",) + shutdown: Tuple[str, ...] = ("killall olsrd",) + validate: Tuple[str, ...] = ("pidof olsrd",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ @@ -246,13 +234,13 @@ def get_startup(cls, node): iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ - Generate a default olsrd config file to use the broadcast address of 255.255.255.255. + Generate a default olsrd config file to use the broadcast address of + 255.255.255.255. """ cfg = """\ # @@ -577,24 +565,16 @@ class MgenActor(NrlService): """ # a unique name is required, without spaces - name = "MgenActor" - executables = ("mgen",) - # you can create your own group here - group = "ProtoSvc" - # per-node directories - dirs = () - # generated files (without a full path this file goes in the node's dir, - # e.g. /tmp/pycore.12345/n1.conf/) - configs = ("start_mgen_actor.sh",) - # list of startup commands, also may be generated during startup - startup = ("sh start_mgen_actor.sh",) - # list of validation commands - validate = ("pidof mgen",) - # list of shutdown commands - shutdown = ("killall mgen",) + name: str = "MgenActor" + group: str = "ProtoSvc" + executables: Tuple[str, ...] = ("mgen",) + configs: Tuple[str, ...] = ("start_mgen_actor.sh",) + startup: Tuple[str, ...] = ("sh start_mgen_actor.sh",) + validate: Tuple[str, ...] = ("pidof mgen",) + shutdown: Tuple[str, ...] = ("killall mgen",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for MgenActor. Because mgenActor does not daemonize, it can cause problems in some situations when launched @@ -604,11 +584,9 @@ def generate_config(cls, node, filename): cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n" comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name - ifaces = node.get_ifaces(control=False) if len(ifaces) == 0: return "" - cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg @@ -618,15 +596,15 @@ class Arouted(NrlService): Adaptive Routing """ - name = "arouted" - executables = ("arouted",) - configs = ("startarouted.sh",) - startup = ("sh startarouted.sh",) - shutdown = ("pkill arouted",) - validate = ("pidof arouted",) + name: str = "arouted" + executables: Tuple[str, ...] = ("arouted",) + configs: Tuple[str, ...] = ("startarouted.sh",) + startup: Tuple[str, ...] = ("sh startarouted.sh",) + shutdown: Tuple[str, ...] = ("pkill arouted",) + validate: Tuple[str, ...] = ("pidof arouted",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the Quagga.conf or quaggaboot.sh file contents. """ diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 41cfa3d88..30d14353e 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -1,65 +1,68 @@ """ quagga.py: defines routing services provided by Quagga. """ +from typing import Optional, Tuple + import netaddr from core import constants from core.emane.nodes import EmaneNet from core.emulator.enumerations import LinkTypes +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService class Zebra(CoreService): - name = "zebra" - group = "Quagga" - dirs = ("/usr/local/etc/quagga", "/var/run/quagga") - configs = ( + name: str = "zebra" + group: str = "Quagga" + dirs: Tuple[str, ...] = ("/usr/local/etc/quagga", "/var/run/quagga") + configs: Tuple[str, ...] = ( "/usr/local/etc/quagga/Quagga.conf", "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf", ) - startup = ("sh quaggaboot.sh zebra",) - shutdown = ("killall zebra",) - validate = ("pidof zebra",) + startup: Tuple[str, ...] = ("sh quaggaboot.sh zebra",) + shutdown: Tuple[str, ...] = ("killall zebra",) + validate: Tuple[str, ...] = ("pidof zebra",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the Quagga.conf or quaggaboot.sh file contents. """ if filename == cls.configs[0]: - return cls.generateQuaggaConf(node) + return cls.generate_quagga_conf(node) elif filename == cls.configs[1]: - return cls.generateQuaggaBoot(node) + return cls.generate_quagga_boot(node) elif filename == cls.configs[2]: - return cls.generateVtyshConf(node) + return cls.generate_vtysh_conf(node) else: raise ValueError( "file name (%s) is not a known configuration: %s", filename, cls.configs ) @classmethod - def generateVtyshConf(cls, node): + def generate_vtysh_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ return "service integrated-vtysh-config\n" @classmethod - def generateQuaggaConf(cls, node): + def generate_quagga_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra - will have generatequaggaifcconfig() and generatequaggaconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ # we could verify here that filename == Quagga.conf cfg = "" for iface in node.get_ifaces(): cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(iface, "control") and iface.control is True: + if getattr(iface, "control", False): cfg += " " cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" @@ -71,6 +74,8 @@ def generateQuaggaConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): + continue iface_config = s.generate_quagga_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True @@ -101,11 +106,13 @@ def generateQuaggaConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): + continue cfg += s.generate_quagga_config(node) return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to zebra config statements """ @@ -118,7 +125,7 @@ def addrstr(x): raise ValueError("invalid address: %s", x) @classmethod - def generateQuaggaBoot(cls, node): + def generate_quagga_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the Quagga daemons. """ @@ -235,20 +242,15 @@ class QuaggaService(CoreService): common to Quagga's routing daemons. """ - name = None - group = "Quagga" - dependencies = ("zebra",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the Zebra service." - - ipv4_routing = False - ipv6_routing = False + name: Optional[str] = None + group: str = "Quagga" + dependencies: Tuple[str, ...] = (Zebra.name,) + meta: str = "The config file for this service can be found in the Zebra service." + ipv4_routing: bool = False + ipv6_routing: bool = False @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -257,11 +259,10 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" - return "0.0.0.%d" % node.id + return f"0.0.0.{node.id:d}" @staticmethod - def rj45check(iface): + def rj45check(iface: CoreInterface) -> bool: """ Helper to detect whether interface is connected an external RJ45 link. @@ -275,15 +276,15 @@ def rj45check(iface): return False @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: return "" @@ -294,14 +295,13 @@ class Ospfv2(QuaggaService): unified Quagga.conf file. """ - name = "OSPFv2" - startup = () - shutdown = ("killall ospfd",) - validate = ("pidof ospfd",) - ipv4_routing = True + name: str = "OSPFv2" + shutdown: Tuple[str, ...] = ("killall ospfd",) + validate: Tuple[str, ...] = ("pidof ospfd",) + ipv4_routing: bool = True @staticmethod - def mtucheck(iface): + def mtu_check(iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a @@ -319,7 +319,7 @@ def mtucheck(iface): return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -329,9 +329,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): @@ -343,12 +343,12 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): - cfg = cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + cfg = cls.mtu_check(iface) # external RJ45 connections will use default OSPF timers if cls.rj45check(iface): return cfg - cfg += cls.ptpcheck(iface) + cfg += cls.ptp_check(iface) return ( cfg + """\ @@ -366,15 +366,14 @@ class Ospfv3(QuaggaService): unified Quagga.conf file. """ - name = "OSPFv3" - startup = () - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "OSPFv3" + shutdown: Tuple[str, ...] = ("killall ospf6d",) + validate: Tuple[str, ...] = ("pidof ospf6d",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def minmtu(iface): + def min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -388,20 +387,20 @@ def minmtu(iface): return mtu @classmethod - def mtucheck(cls, iface): + def mtu_check(cls, iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(iface) + minmtu = cls.min_mtu(iface) if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -411,9 +410,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " instance-id 65\n" cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): @@ -422,8 +421,8 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) class Ospfv3mdr(Ospfv3): @@ -434,12 +433,12 @@ class Ospfv3mdr(Ospfv3): unified Quagga.conf file. """ - name = "OSPFv3MDR" - ipv4_routing = True + name: str = "OSPFv3MDR" + ipv4_routing: bool = True @classmethod - def generate_quagga_iface_config(cls, node, iface): - cfg = cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + cfg = cls.mtu_check(iface) if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): return ( cfg @@ -464,21 +463,20 @@ class Bgp(QuaggaService): having the same AS number. """ - name = "BGP" - startup = () - shutdown = ("killall bgpd",) - validate = ("pidof bgpd",) - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "BGP" + shutdown: Tuple[str, ...] = ("killall bgpd",) + validate: Tuple[str, ...] = ("pidof bgpd",) + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" cfg += "router bgp %s\n" % node.id - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" @@ -490,14 +488,13 @@ class Rip(QuaggaService): The RIP service provides IPv4 routing for wired networks. """ - name = "RIP" - startup = () - shutdown = ("killall ripd",) - validate = ("pidof ripd",) - ipv4_routing = True + name: str = "RIP" + shutdown: Tuple[str, ...] = ("killall ripd",) + validate: Tuple[str, ...] = ("pidof ripd",) + ipv4_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = """\ router rip redistribute static @@ -514,14 +511,13 @@ class Ripng(QuaggaService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "RIPNG" - startup = () - shutdown = ("killall ripngd",) - validate = ("pidof ripngd",) - ipv6_routing = True + name: str = "RIPNG" + shutdown: Tuple[str, ...] = ("killall ripngd",) + validate: Tuple[str, ...] = ("pidof ripngd",) + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = """\ router ripng redistribute static @@ -539,14 +535,13 @@ class Babel(QuaggaService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "Babel" - startup = () - shutdown = ("killall babeld",) - validate = ("pidof babeld",) - ipv6_routing = True + name: str = "Babel" + shutdown: Tuple[str, ...] = ("killall babeld",) + validate: Tuple[str, ...] = ("pidof babeld",) + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router babel\n" for iface in node.get_ifaces(control=False): cfg += " network %s\n" % iface.name @@ -554,7 +549,7 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: if iface.net and iface.net.linktype == LinkTypes.WIRELESS: return " babel wireless\n no babel split-horizon\n" else: @@ -566,14 +561,13 @@ class Xpimd(QuaggaService): PIM multicast routing based on XORP. """ - name = "Xpimd" - startup = () - shutdown = ("killall xpimd",) - validate = ("pidof xpimd",) - ipv4_routing = True + name: str = "Xpimd" + shutdown: Tuple[str, ...] = ("killall xpimd",) + validate: Tuple[str, ...] = ("pidof xpimd",) + ipv4_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": @@ -589,5 +583,5 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return " ip mfea\n ip igmp\n ip pim\n" diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 71ab815ff..1f17201dd 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -3,9 +3,11 @@ """ import re +from typing import Tuple import netaddr +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -14,24 +16,28 @@ class SdnService(CoreService): Parent class for SDN services. """ - group = "SDN" + group: str = "SDN" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" class OvsService(SdnService): - name = "OvsService" - executables = ("ovs-ofctl", "ovs-vsctl") - group = "SDN" - dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch") - configs = ("OvsService.sh",) - startup = ("sh OvsService.sh",) - shutdown = ("killall ovs-vswitchd", "killall ovsdb-server") + name: str = "OvsService" + group: str = "SDN" + executables: Tuple[str, ...] = ("ovs-ofctl", "ovs-vsctl") + dirs: Tuple[str, ...] = ( + "/etc/openvswitch", + "/var/run/openvswitch", + "/var/log/openvswitch", + ) + configs: Tuple[str, ...] = ("OvsService.sh",) + startup: Tuple[str, ...] = ("sh OvsService.sh",) + shutdown: Tuple[str, ...] = ("killall ovs-vswitchd", "killall ovsdb-server") @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: # Check whether the node is running zebra has_zebra = 0 for s in node.services: @@ -46,8 +52,8 @@ def generate_config(cls, node, filename): cfg += "## this stops it from routing traffic without defined flows.\n" cfg += "## remove the -- and everything after if you want it to act as a regular switch\n" cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n" - cfg += "\n## Now add all our interfaces as ports to the switch\n" + portnum = 1 for iface in node.get_ifaces(control=False): ifnumstr = re.findall(r"\d+", iface.name) @@ -111,21 +117,19 @@ def generate_config(cls, node, filename): % (portnum + 1, portnum) ) portnum += 2 - return cfg class RyuService(SdnService): - name = "ryuService" - executables = ("ryu-manager",) - group = "SDN" - dirs = () - configs = ("ryuService.sh",) - startup = ("sh ryuService.sh",) - shutdown = ("killall ryu-manager",) + name: str = "ryuService" + group: str = "SDN" + executables: Tuple[str, ...] = ("ryu-manager",) + configs: Tuple[str, ...] = ("ryuService.sh",) + startup: Tuple[str, ...] = ("sh ryuService.sh",) + shutdown: Tuple[str, ...] = ("killall ryu-manager",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return a string that will be written to filename, or sent to the GUI for user customization. diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index 91c942f1c..b813579e7 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -4,78 +4,79 @@ """ import logging +from typing import Tuple from core import constants +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService class VPNClient(CoreService): - name = "VPNClient" - group = "Security" - configs = ("vpnclient.sh",) - startup = ("sh vpnclient.sh",) - shutdown = ("killall openvpn",) - validate = ("pidof openvpn",) - custom_needed = True + name: str = "VPNClient" + group: str = "Security" + configs: Tuple[str, ...] = ("vpnclient.sh",) + startup: Tuple[str, ...] = ("sh vpnclient.sh",) + shutdown: Tuple[str, ...] = ("killall openvpn",) + validate: Tuple[str, ...] = ("pidof openvpn",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the client.conf and vpnclient.sh file contents to """ cfg = "#!/bin/sh\n" cfg += "# custom VPN Client configuration for service (security.py)\n" - fname = "%s/examples/services/sampleVPNClient" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleVPNClient" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( - "Error opening VPN client configuration template (%s)", fname + "error opening VPN client configuration template (%s)", fname ) - return cfg class VPNServer(CoreService): - name = "VPNServer" - group = "Security" - configs = ("vpnserver.sh",) - startup = ("sh vpnserver.sh",) - shutdown = ("killall openvpn",) - validate = ("pidof openvpn",) - custom_needed = True + name: str = "VPNServer" + group: str = "Security" + configs: Tuple[str, ...] = ("vpnserver.sh",) + startup: Tuple[str, ...] = ("sh vpnserver.sh",) + shutdown: Tuple[str, ...] = ("killall openvpn",) + validate: Tuple[str, ...] = ("pidof openvpn",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the sample server.conf and vpnserver.sh file contents to GUI for user customization. """ cfg = "#!/bin/sh\n" cfg += "# custom VPN Server Configuration for service (security.py)\n" - fname = "%s/examples/services/sampleVPNServer" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleVPNServer" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( "Error opening VPN server configuration template (%s)", fname ) - return cfg class IPsec(CoreService): - name = "IPsec" - group = "Security" - configs = ("ipsec.sh",) - startup = ("sh ipsec.sh",) - shutdown = ("killall racoon",) - custom_needed = True + name: str = "IPsec" + group: str = "Security" + configs: Tuple[str, ...] = ("ipsec.sh",) + startup: Tuple[str, ...] = ("sh ipsec.sh",) + shutdown: Tuple[str, ...] = ("killall racoon",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the ipsec.conf and racoon.conf file contents to GUI for user customization. @@ -83,7 +84,7 @@ def generate_config(cls, node, filename): cfg = "#!/bin/sh\n" cfg += "# set up static tunnel mode security assocation for service " cfg += "(security.py)\n" - fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleIPsec" try: with open(fname, "r") as f: cfg += f.read() @@ -93,28 +94,27 @@ def generate_config(cls, node, filename): class Firewall(CoreService): - name = "Firewall" - group = "Security" - configs = ("firewall.sh",) - startup = ("sh firewall.sh",) - custom_needed = True + name: str = "Firewall" + group: str = "Security" + configs: Tuple[str, ...] = ("firewall.sh",) + startup: Tuple[str, ...] = ("sh firewall.sh",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the firewall rule examples to GUI for user customization. """ cfg = "#!/bin/sh\n" cfg += "# custom node firewall rules for service (security.py)\n" - fname = "%s/examples/services/sampleFirewall" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleFirewall" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( "Error opening Firewall configuration template (%s)", fname ) - return cfg @@ -123,30 +123,28 @@ class Nat(CoreService): IPv4 source NAT service. """ - name = "NAT" - executables = ("iptables",) - group = "Security" - configs = ("nat.sh",) - startup = ("sh nat.sh",) - custom_needed = False + name: str = "NAT" + group: str = "Security" + executables: Tuple[str, ...] = ("iptables",) + configs: Tuple[str, ...] = ("nat.sh",) + startup: Tuple[str, ...] = ("sh nat.sh",) + custom_needed: bool = False @classmethod - def generate_iface_nat_rule(cls, iface, line_prefix=""): + def generate_iface_nat_rule(cls, iface: CoreInterface, prefix: str = "") -> str: """ Generate a NAT line for one interface. """ - cfg = line_prefix + "iptables -t nat -A POSTROUTING -o " + cfg = prefix + "iptables -t nat -A POSTROUTING -o " cfg += iface.name + " -j MASQUERADE\n" - - cfg += line_prefix + "iptables -A FORWARD -i " + iface.name + cfg += prefix + "iptables -A FORWARD -i " + iface.name cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n" - - cfg += line_prefix + "iptables -A FORWARD -i " + cfg += prefix + "iptables -A FORWARD -i " cfg += iface.name + " -j DROP\n" return cfg @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ NAT out the first interface """ @@ -156,7 +154,7 @@ def generate_config(cls, node, filename): have_nat = False for iface in node.get_ifaces(control=False): if have_nat: - cfg += cls.generate_iface_nat_rule(iface, line_prefix="#") + cfg += cls.generate_iface_nat_rule(iface, prefix="#") else: have_nat = True cfg += "# NAT out the " + iface.name + " interface\n" diff --git a/daemon/core/services/ucarp.py b/daemon/core/services/ucarp.py index 1eb801797..8ac92dd34 100644 --- a/daemon/core/services/ucarp.py +++ b/daemon/core/services/ucarp.py @@ -1,52 +1,52 @@ """ ucarp.py: defines high-availability IP address controlled by ucarp """ +from typing import Tuple +from core.nodes.base import CoreNode from core.services.coreservices import CoreService UCARP_ETC = "/usr/local/etc/ucarp" class Ucarp(CoreService): - name = "ucarp" - group = "Utility" - dirs = (UCARP_ETC,) - configs = ( + name: str = "ucarp" + group: str = "Utility" + dirs: Tuple[str, ...] = (UCARP_ETC,) + configs: Tuple[str, ...] = ( UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh", ) - startup = ("sh ucarpboot.sh",) - shutdown = ("killall ucarp",) - validate = ("pidof ucarp",) + startup: Tuple[str, ...] = ("sh ucarpboot.sh",) + shutdown: Tuple[str, ...] = ("killall ucarp",) + validate: Tuple[str, ...] = ("pidof ucarp",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the default file contents """ if filename == cls.configs[0]: - return cls.generateUcarpConf(node) + return cls.generate_ucarp_conf(node) elif filename == cls.configs[1]: - return cls.generateVipUp(node) + return cls.generate_vip_up(node) elif filename == cls.configs[2]: - return cls.generateVipDown(node) + return cls.generate_vip_down(node) elif filename == cls.configs[3]: - return cls.generateUcarpBoot(node) + return cls.generate_ucarp_boot(node) else: raise ValueError @classmethod - def generateUcarpConf(cls, node): + def generate_ucarp_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ - try: - ucarp_bin = node.session.cfg["ucarp_bin"] - except KeyError: - ucarp_bin = "/usr/sbin/ucarp" - + ucarp_bin = node.session.options.get_config( + "ucarp_bin", default="/usr/sbin/ucarp" + ) return """\ #!/bin/sh # Location of UCARP executable @@ -110,7 +110,7 @@ def generateUcarpConf(cls, node): ) @classmethod - def generateUcarpBoot(cls, node): + def generate_ucarp_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the Ucarp daemons. """ @@ -130,7 +130,7 @@ def generateUcarpBoot(cls, node): ) @classmethod - def generateVipUp(cls, node): + def generate_vip_up(cls, node: CoreNode) -> str: """ Generate a shell script used to start the virtual ip """ @@ -152,7 +152,7 @@ def generateVipUp(cls, node): """ @classmethod - def generateVipDown(cls, node): + def generate_vip_down(cls, node: CoreNode) -> str: """ Generate a shell script used to stop the virtual ip """ diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 273318e15..a44037f6c 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -1,12 +1,13 @@ """ utility.py: defines miscellaneous utility services. """ -import os +from typing import Optional, Tuple import netaddr from core import constants, utils from core.errors import CoreCommandError +from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -15,32 +16,25 @@ class UtilService(CoreService): Parent class for utility services. """ - name = None - group = "Utility" - dirs = () - configs = () - startup = () - shutdown = () + name: Optional[str] = None + group: str = "Utility" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" class IPForwardService(UtilService): - name = "IPForward" - configs = ("ipforward.sh",) - startup = ("sh ipforward.sh",) + name: str = "IPForward" + configs: Tuple[str, ...] = ("ipforward.sh",) + startup: Tuple[str, ...] = ("sh ipforward.sh",) @classmethod - def generate_config(cls, node, filename): - if os.uname()[0] == "Linux": - return cls.generateconfiglinux(node, filename) - else: - raise Exception("unknown platform") + def generate_config(cls, node: CoreNode, filename: str) -> str: + return cls.generateconfiglinux(node, filename) @classmethod - def generateconfiglinux(cls, node, filename): + def generateconfiglinux(cls, node: CoreNode, filename: str) -> str: cfg = """\ #!/bin/sh # auto-generated by IPForward service (utility.py) @@ -70,12 +64,12 @@ def generateconfiglinux(cls, node, filename): class DefaultRouteService(UtilService): - name = "DefaultRoute" - configs = ("defaultroute.sh",) - startup = ("sh defaultroute.sh",) + name: str = "DefaultRoute" + configs: Tuple[str, ...] = ("defaultroute.sh",) + startup: Tuple[str, ...] = ("sh defaultroute.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: routes = [] ifaces = node.get_ifaces() if ifaces: @@ -93,22 +87,18 @@ def generate_config(cls, node, filename): class DefaultMulticastRouteService(UtilService): - name = "DefaultMulticastRoute" - configs = ("defaultmroute.sh",) - startup = ("sh defaultmroute.sh",) + name: str = "DefaultMulticastRoute" + configs: Tuple[str, ...] = ("defaultmroute.sh",) + startup: Tuple[str, ...] = ("sh defaultmroute.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n" cfg += "# the first interface is chosen below; please change it " cfg += "as needed\n" - for iface in node.get_ifaces(control=False): - if os.uname()[0] == "Linux": - rtcmd = "ip route add 224.0.0.0/4 dev" - else: - raise Exception("unknown platform") + rtcmd = "ip route add 224.0.0.0/4 dev" cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "\n" break @@ -116,13 +106,13 @@ def generate_config(cls, node, filename): class StaticRouteService(UtilService): - name = "StaticRoute" - configs = ("staticroute.sh",) - startup = ("sh staticroute.sh",) - custom_needed = True + name: str = "StaticRoute" + configs: Tuple[str, ...] = ("staticroute.sh",) + startup: Tuple[str, ...] = ("sh staticroute.sh",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# NOTE: this service must be customized to be of any use\n" @@ -133,7 +123,7 @@ def generate_config(cls, node, filename): return cfg @staticmethod - def routestr(x): + def routestr(x: str) -> str: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): dst = "3ffe:4::/64" @@ -143,24 +133,20 @@ def routestr(x): if net[-2] == net[1]: return "" else: - if os.uname()[0] == "Linux": - rtcmd = "#/sbin/ip route add %s via" % dst - else: - raise Exception("unknown platform") + rtcmd = "#/sbin/ip route add %s via" % dst return "%s %s" % (rtcmd, net[1]) class SshService(UtilService): - name = "SSH" - configs = ("startsshd.sh", "/etc/ssh/sshd_config") - dirs = ("/etc/ssh", "/var/run/sshd") - startup = ("sh startsshd.sh",) - shutdown = ("killall sshd",) - validate = () - validation_mode = ServiceMode.BLOCKING + name: str = "SSH" + configs: Tuple[str, ...] = ("startsshd.sh", "/etc/ssh/sshd_config") + dirs: Tuple[str, ...] = ("/etc/ssh", "/var/run/sshd") + startup: Tuple[str, ...] = ("sh startsshd.sh",) + shutdown: Tuple[str, ...] = ("killall sshd",) + validation_mode: ServiceMode = ServiceMode.BLOCKING @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Use a startup script for launching sshd in order to wait for host key generation. @@ -228,15 +214,15 @@ def generate_config(cls, node, filename): class DhcpService(UtilService): - name = "DHCP" - configs = ("/etc/dhcp/dhcpd.conf",) - dirs = ("/etc/dhcp", "/var/lib/dhcp") - startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") - shutdown = ("killall dhcpd",) - validate = ("pidof dhcpd",) + name: str = "DHCP" + configs: Tuple[str, ...] = ("/etc/dhcp/dhcpd.conf",) + dirs: Tuple[str, ...] = ("/etc/dhcp", "/var/lib/dhcp") + startup: Tuple[str, ...] = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") + shutdown: Tuple[str, ...] = ("killall dhcpd",) + validate: Tuple[str, ...] = ("pidof dhcpd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a dhcpd config file using the network address of each interface. @@ -261,7 +247,7 @@ def generate_config(cls, node, filename): return cfg @staticmethod - def subnetentry(x): + def subnetentry(x: str) -> str: """ Generate a subnet declaration block given an IPv4 prefix string for inclusion in the dhcpd3 config file. @@ -297,14 +283,14 @@ class DhcpClientService(UtilService): Use a DHCP client for all interfaces for addressing. """ - name = "DHCPClient" - configs = ("startdhcpclient.sh",) - startup = ("sh startdhcpclient.sh",) - shutdown = ("killall dhclient",) - validate = ("pidof dhclient",) + name: str = "DHCPClient" + configs: Tuple[str, ...] = ("startdhcpclient.sh",) + startup: Tuple[str, ...] = ("sh startdhcpclient.sh",) + shutdown: Tuple[str, ...] = ("killall dhclient",) + validate: Tuple[str, ...] = ("pidof dhclient",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a script to invoke dhclient on all interfaces. """ @@ -313,7 +299,6 @@ def generate_config(cls, node, filename): cfg += "# uncomment this mkdir line and symlink line to enable client-" cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n" - for iface in node.get_ifaces(control=False): cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += " /var/run/resolvconf/resolv.conf\n" @@ -327,15 +312,15 @@ class FtpService(UtilService): Start a vsftpd server. """ - name = "FTP" - configs = ("vsftpd.conf",) - dirs = ("/var/run/vsftpd/empty", "/var/ftp") - startup = ("vsftpd ./vsftpd.conf",) - shutdown = ("killall vsftpd",) - validate = ("pidof vsftpd",) + name: str = "FTP" + configs: Tuple[str, ...] = ("vsftpd.conf",) + dirs: Tuple[str, ...] = ("/var/run/vsftpd/empty", "/var/ftp") + startup: Tuple[str, ...] = ("vsftpd ./vsftpd.conf",) + shutdown: Tuple[str, ...] = ("killall vsftpd",) + validate: Tuple[str, ...] = ("pidof vsftpd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a vsftpd.conf configuration file. """ @@ -360,13 +345,13 @@ class HttpService(UtilService): Start an apache server. """ - name = "HTTP" - configs = ( + name: str = "HTTP" + configs: Tuple[str, ...] = ( "/etc/apache2/apache2.conf", "/etc/apache2/envvars", "/var/www/index.html", ) - dirs = ( + dirs: Tuple[str, ...] = ( "/etc/apache2", "/var/run/apache2", "/var/log/apache2", @@ -374,14 +359,14 @@ class HttpService(UtilService): "/var/lock/apache2", "/var/www", ) - startup = ("chown www-data /var/lock/apache2", "apache2ctl start") - shutdown = ("apache2ctl stop",) - validate = ("pidof apache2",) - - APACHEVER22, APACHEVER24 = (22, 24) + startup: Tuple[str, ...] = ("chown www-data /var/lock/apache2", "apache2ctl start") + shutdown: Tuple[str, ...] = ("apache2ctl stop",) + validate: Tuple[str, ...] = ("pidof apache2",) + APACHEVER22: int = 22 + APACHEVER24: int = 24 @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate an apache2.conf configuration file. """ @@ -395,7 +380,7 @@ def generate_config(cls, node, filename): return "" @classmethod - def detectversionfromcmd(cls): + def detectversionfromcmd(cls) -> int: """ Detect the apache2 version using the 'a2query' command. """ @@ -405,14 +390,12 @@ def detectversionfromcmd(cls): except CoreCommandError as e: status = e.returncode result = e.stderr - if status == 0 and result[:3] == "2.4": return cls.APACHEVER24 - return cls.APACHEVER22 @classmethod - def generateapache2conf(cls, node, filename): + def generateapache2conf(cls, node: CoreNode, filename: str) -> str: lockstr = { cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n", cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n", @@ -421,22 +404,18 @@ def generateapache2conf(cls, node, filename): cls.APACHEVER22: "", cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n", } - permstr = { cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n", cls.APACHEVER24: " Require all denied\n", } - authstr = { cls.APACHEVER22: "LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n", cls.APACHEVER24: "LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n", } - permstr2 = { cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n", cls.APACHEVER24: "\t\tRequire all granted\n", } - version = cls.detectversionfromcmd() cfg = "# apache2.conf generated by utility.py:HttpService\n" cfg += lockstr[version] @@ -552,7 +531,7 @@ def generateapache2conf(cls, node, filename): return cfg @classmethod - def generateenvvars(cls, node, filename): + def generateenvvars(cls, node: CoreNode, filename: str) -> str: return """\ # this file is used by apache2ctl - generated by utility.py:HttpService # these settings come from a default Ubuntu apache2 installation @@ -567,7 +546,7 @@ def generateenvvars(cls, node, filename): """ @classmethod - def generatehtml(cls, node, filename): + def generatehtml(cls, node: CoreNode, filename: str) -> str: body = ( """\ @@ -587,16 +566,15 @@ class PcapService(UtilService): Pcap service for logging packets. """ - name = "pcap" - configs = ("pcap.sh",) - dirs = () - startup = ("sh pcap.sh start",) - shutdown = ("sh pcap.sh stop",) - validate = ("pidof tcpdump",) - meta = "logs network traffic to pcap packet capture files" + name: str = "pcap" + configs: Tuple[str, ...] = ("pcap.sh",) + startup: Tuple[str, ...] = ("sh pcap.sh start",) + shutdown: Tuple[str, ...] = ("sh pcap.sh stop",) + validate: Tuple[str, ...] = ("pidof tcpdump",) + meta: str = "logs network traffic to pcap packet capture files" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startpcap.sh traffic logging script. """ @@ -630,15 +608,17 @@ def generate_config(cls, node, filename): class RadvdService(UtilService): - name = "radvd" - configs = ("/etc/radvd/radvd.conf",) - dirs = ("/etc/radvd",) - startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",) - shutdown = ("pkill radvd",) - validate = ("pidof radvd",) + name: str = "radvd" + configs: Tuple[str, ...] = ("/etc/radvd/radvd.conf",) + dirs: Tuple[str, ...] = ("/etc/radvd",) + startup: Tuple[str, ...] = ( + "radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log", + ) + shutdown: Tuple[str, ...] = ("pkill radvd",) + validate: Tuple[str, ...] = ("pidof radvd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a RADVD router advertisement daemon config file using the network address of each interface. @@ -678,7 +658,7 @@ def generate_config(cls, node, filename): return cfg @staticmethod - def subnetentry(x): + def subnetentry(x: str) -> str: """ Generate a subnet declaration block given an IPv6 prefix string for inclusion in the RADVD config file. @@ -695,14 +675,14 @@ class AtdService(UtilService): Atd service for scheduling at jobs """ - name = "atd" - configs = ("startatd.sh",) - dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") - startup = ("sh startatd.sh",) - shutdown = ("pkill atd",) + name: str = "atd" + configs: Tuple[str, ...] = ("startatd.sh",) + dirs: Tuple[str, ...] = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") + startup: Tuple[str, ...] = ("sh startatd.sh",) + shutdown: Tuple[str, ...] = ("pkill atd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return """ #!/bin/sh echo 00001 > /var/spool/cron/atjobs/.SEQ @@ -717,5 +697,5 @@ class UserDefinedService(UtilService): Dummy service allowing customization of anything. """ - name = "UserDefined" - meta = "Customize this service to do anything upon startup." + name: str = "UserDefined" + meta: str = "Customize this service to do anything upon startup." diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 776b1d166..420823773 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -2,10 +2,12 @@ xorp.py: defines routing services provided by the XORP routing suite. """ -import logging +from typing import Optional, Tuple import netaddr +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService @@ -15,20 +17,20 @@ class XorpRtrmgr(CoreService): enabled XORP services, and launches necessary daemons upon startup. """ - name = "xorp_rtrmgr" - executables = ("xorp_rtrmgr",) - group = "XORP" - dirs = ("/etc/xorp",) - configs = ("/etc/xorp/config.boot",) - startup = ( + name: str = "xorp_rtrmgr" + group: str = "XORP" + executables: Tuple[str, ...] = ("xorp_rtrmgr",) + dirs: Tuple[str, ...] = ("/etc/xorp",) + configs: Tuple[str, ...] = ("/etc/xorp/config.boot",) + startup: Tuple[str, ...] = ( "xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (configs[0], name, name), ) - shutdown = ("killall xorp_rtrmgr",) - validate = ("pidof xorp_rtrmgr",) + shutdown: Tuple[str, ...] = ("killall xorp_rtrmgr",) + validate: Tuple[str, ...] = ("pidof xorp_rtrmgr",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Returns config.boot configuration file text. Other services that depend on this will have generatexorpconfig() hooks that are @@ -45,16 +47,15 @@ def generate_config(cls, node, filename): cfg += "}\n\n" for s in node.services: - try: - s.dependencies.index(cls.name) - cfg += s.generatexorpconfig(node) - except ValueError: - logging.exception("error getting value from service: %s", cls.name) - + if cls.name not in s.dependencies: + continue + if not (isinstance(s, XorpService) or issubclass(s, XorpService)): + continue + cfg += s.generate_xorp_config(node) return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to XORP config statements """ @@ -65,7 +66,7 @@ def addrstr(x): return cfg @staticmethod - def lladdrstr(iface): + def lladdrstr(iface: CoreInterface) -> str: """ helper for adding link-local address entries (required by OSPFv3) """ @@ -81,18 +82,16 @@ class XorpService(CoreService): common to XORP's routing daemons. """ - name = None - executables = ("xorp_rtrmgr",) - group = "XORP" - dependencies = ("xorp_rtrmgr",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the xorp_rtrmgr service." + name: Optional[str] = None + group: str = "XORP" + executables: Tuple[str, ...] = ("xorp_rtrmgr",) + dependencies: Tuple[str, ...] = ("xorp_rtrmgr",) + meta: str = ( + "The config file for this service can be found in the xorp_rtrmgr service." + ) @staticmethod - def fea(forwarding): + def fea(forwarding: str) -> str: """ Helper to add a forwarding engine entry to the config file. """ @@ -104,17 +103,14 @@ def fea(forwarding): return cfg @staticmethod - def mfea(forwarding, ifaces): + def mfea(forwarding, node: CoreNode) -> str: """ Helper to add a multicast forwarding engine entry to the config file. """ names = [] - for iface in ifaces: - if hasattr(iface, "control") and iface.control is True: - continue + for iface in node.get_ifaces(control=False): names.append(iface.name) names.append("register_vif") - cfg = "plumbing {\n" cfg += " %s {\n" % forwarding for name in names: @@ -128,7 +124,7 @@ def mfea(forwarding, ifaces): return cfg @staticmethod - def policyexportconnected(): + def policyexportconnected() -> str: """ Helper to add a policy statement for exporting connected routes. """ @@ -144,7 +140,7 @@ def policyexportconnected(): return cfg @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -153,15 +149,14 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: return "" @@ -172,12 +167,12 @@ class XorpOspfv2(XorpService): unified XORP configuration file. """ - name = "XORP_OSPFv2" + name: str = "XORP_OSPFv2" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " ospf4 {\n" cfg += "\trouter-id: %s\n" % rtrid @@ -206,12 +201,12 @@ class XorpOspfv3(XorpService): unified XORP configuration file. """ - name = "XORP_OSPFv3" + name: str = "XORP_OSPFv3" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding6") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " ospf6 0 { /* Instance ID 0 */\n" cfg += "\trouter-id: %s\n" % rtrid @@ -232,16 +227,16 @@ class XorpBgp(XorpService): IPv4 inter-domain routing. AS numbers and peers must be customized. """ - name = "XORP_BGP" - custom_needed = True + name: str = "XORP_BGP" + custom_needed: bool = True @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that should be customized with\n" cfg += " appropriate AS numbers and peers */\n" cfg += cls.fea("unicast-forwarding4") cfg += cls.policyexportconnected() - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " bgp {\n" cfg += "\tbgp-id: %s\n" % rtrid @@ -262,10 +257,10 @@ class XorpRip(XorpService): RIP IPv4 unicast routing. """ - name = "XORP_RIP" + name: str = "XORP_RIP" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") cfg += cls.policyexportconnected() cfg += "\nprotocols {\n" @@ -293,10 +288,10 @@ class XorpRipng(XorpService): RIP NG IPv6 unicast routing. """ - name = "XORP_RIPNG" + name: str = "XORP_RIPNG" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding6") cfg += cls.policyexportconnected() cfg += "\nprotocols {\n" @@ -320,12 +315,11 @@ class XorpPimSm4(XorpService): PIM Sparse Mode IPv4 multicast routing. """ - name = "XORP_PIMSM4" + name: str = "XORP_PIMSM4" @classmethod - def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea4", node.get_ifaces()) - + def generate_xorp_config(cls, node: CoreNode) -> str: + cfg = cls.mfea("mfea4", node) cfg += "\nprotocols {\n" cfg += " igmp {\n" names = [] @@ -338,7 +332,6 @@ def generatexorpconfig(cls, node): cfg += "\t}\n" cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " pimsm4 {\n" @@ -361,10 +354,8 @@ def generatexorpconfig(cls, node): cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" - cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " fib2mrib {\n" cfg += "\tdisable: false\n" @@ -378,12 +369,11 @@ class XorpPimSm6(XorpService): PIM Sparse Mode IPv6 multicast routing. """ - name = "XORP_PIMSM6" + name: str = "XORP_PIMSM6" @classmethod - def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea6", node.get_ifaces()) - + def generate_xorp_config(cls, node: CoreNode) -> str: + cfg = cls.mfea("mfea6", node) cfg += "\nprotocols {\n" cfg += " mld {\n" names = [] @@ -396,7 +386,6 @@ def generatexorpconfig(cls, node): cfg += "\t}\n" cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " pimsm6 {\n" @@ -419,10 +408,8 @@ def generatexorpconfig(cls, node): cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" - cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " fib2mrib {\n" cfg += "\tdisable: false\n" @@ -436,12 +423,12 @@ class XorpOlsr(XorpService): OLSR IPv4 unicast MANET routing. """ - name = "XORP_OLSR" + name: str = "XORP_OLSR" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " olsr4 {\n" cfg += "\tmain-address: %s\n" % rtrid From b2ea8cbbf65c316ef2f06cd48f478df82028eb6d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:15:45 -0700 Subject: [PATCH 039/210] daemon: added type hinting throughout config services --- daemon/core/configservice/base.py | 16 +- daemon/core/configservice/dependencies.py | 14 +- daemon/core/configservice/manager.py | 11 +- .../configservices/frrservices/services.py | 122 +++---- .../configservices/nrlservices/services.py | 197 ++++++------ .../configservices/quaggaservices/services.py | 119 +++---- .../sercurityservices/services.py | 124 ++++---- daemon/core/configservices/simpleservice.py | 26 +- .../configservices/utilservices/services.py | 297 +++++++++--------- 9 files changed, 471 insertions(+), 455 deletions(-) diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index 825989885..bb97e3215 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -14,7 +14,7 @@ from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode -TEMPLATES_DIR = "templates" +TEMPLATES_DIR: str = "templates" class ConfigServiceMode(enum.Enum): @@ -33,10 +33,10 @@ class ConfigService(abc.ABC): """ # validation period in seconds, how frequent validation is attempted - validation_period = 0.5 + validation_period: float = 0.5 # time to wait in seconds for determining if service started successfully - validation_timer = 5 + validation_timer: int = 5 def __init__(self, node: CoreNode) -> None: """ @@ -44,13 +44,13 @@ def __init__(self, node: CoreNode) -> None: :param node: node this service is assigned to """ - self.node = node + self.node: CoreNode = node class_file = inspect.getfile(self.__class__) templates_path = pathlib.Path(class_file).parent.joinpath(TEMPLATES_DIR) - self.templates = TemplateLookup(directories=templates_path) - self.config = {} - self.custom_templates = {} - self.custom_config = {} + self.templates: TemplateLookup = TemplateLookup(directories=templates_path) + self.config: Dict[str, Configuration] = {} + self.custom_templates: Dict[str, str] = {} + self.custom_config: Dict[str, str] = {} configs = self.default_configs[:] self._define_config(configs) diff --git a/daemon/core/configservice/dependencies.py b/daemon/core/configservice/dependencies.py index 92eede79e..be1c45e7d 100644 --- a/daemon/core/configservice/dependencies.py +++ b/daemon/core/configservice/dependencies.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING, Dict, List, Set if TYPE_CHECKING: from core.configservice.base import ConfigService @@ -17,9 +17,9 @@ def __init__(self, services: Dict[str, "ConfigService"]) -> None: :param services: services for determining dependency sets """ # helpers to check validity - self.dependents = {} - self.started = set() - self.node_services = {} + self.dependents: Dict[str, Set[str]] = {} + self.started: Set[str] = set() + self.node_services: Dict[str, "ConfigService"] = {} for service in services.values(): self.node_services[service.name] = service for dependency in service.dependencies: @@ -27,9 +27,9 @@ def __init__(self, services: Dict[str, "ConfigService"]) -> None: dependents.add(service.name) # used to find paths - self.path = [] - self.visited = set() - self.visiting = set() + self.path: List["ConfigService"] = [] + self.visited: Set[str] = set() + self.visiting: Set[str] = set() def startup_paths(self) -> List[List["ConfigService"]]: """ diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index 1f806f7bb..ecea6e680 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -1,6 +1,6 @@ import logging import pathlib -from typing import List, Type +from typing import Dict, List, Type from core import utils from core.configservice.base import ConfigService @@ -16,7 +16,7 @@ def __init__(self): """ Create a ConfigServiceManager instance. """ - self.services = {} + self.services: Dict[str, Type[ConfigService]] = {} def get_service(self, name: str) -> Type[ConfigService]: """ @@ -31,7 +31,7 @@ def get_service(self, name: str) -> Type[ConfigService]: raise CoreError(f"service does not exit {name}") return service_class - def add(self, service: ConfigService) -> None: + def add(self, service: Type[ConfigService]) -> None: """ Add service to manager, checking service requirements have been met. @@ -40,7 +40,9 @@ def add(self, service: ConfigService) -> None: :raises CoreError: when service is a duplicate or has unmet executables """ name = service.name - logging.debug("loading service: class(%s) name(%s)", service.__class__, name) + logging.debug( + "loading service: class(%s) name(%s)", service.__class__.__name__, name + ) # avoid duplicate services if name in self.services: @@ -73,7 +75,6 @@ def load(self, path: str) -> List[str]: logging.debug("loading config services from: %s", subdir) services = utils.load_classes(str(subdir), ConfigService) for service in services: - logging.debug("found service: %s", service) try: self.add(service) except CoreError as e: diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index 8764e32ce..2e24b40a1 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -1,16 +1,17 @@ import abc -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import constants +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet from core.nodes.base import CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode -GROUP = "FRR" +GROUP: str = "FRR" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -29,7 +30,7 @@ def has_mtu_mismatch(iface: CoreInterface) -> bool: return False -def get_min_mtu(iface): +def get_min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -56,23 +57,23 @@ def get_router_id(node: CoreNodeBase) -> str: class FRRZebra(ConfigService): - name = "FRRzebra" - group = GROUP - directories = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"] - files = [ + name: str = "FRRzebra" + group: str = GROUP + directories: List[str] = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"] + files: List[str] = [ "/usr/local/etc/frr/frr.conf", "frrboot.sh", "/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/daemons", ] - executables = ["zebra"] - dependencies = [] - startup = ["sh frrboot.sh zebra"] - validate = ["pidof zebra"] - shutdown = ["killall zebra"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + executables: List[str] = ["zebra"] + dependencies: List[str] = [] + startup: List[str] = ["sh frrboot.sh zebra"] + validate: List[str] = ["pidof zebra"] + shutdown: List[str] = ["killall zebra"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: frr_conf = self.files[0] @@ -89,6 +90,8 @@ def data(self) -> Dict[str, Any]: for service in self.node.config_services.values(): if self.name not in service.dependencies: continue + if not isinstance(service, FrrService): + continue if service.ipv4_routing: want_ip4 = True if service.ipv6_routing: @@ -121,19 +124,19 @@ def data(self) -> Dict[str, Any]: class FrrService(abc.ABC): - group = GROUP - directories = [] - files = [] - executables = [] - dependencies = ["FRRzebra"] - startup = [] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} - ipv4_routing = False - ipv6_routing = False + group: str = GROUP + directories: List[str] = [] + files: List[str] = [] + executables: List[str] = [] + dependencies: List[str] = ["FRRzebra"] + startup: List[str] = [] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} + ipv4_routing: bool = False + ipv6_routing: bool = False @abc.abstractmethod def frr_iface_config(self, iface: CoreInterface) -> str: @@ -151,11 +154,10 @@ class FRROspfv2(FrrService, ConfigService): unified frr.conf file. """ - name = "FRROSPFv2" - startup = () - shutdown = ["killall ospfd"] - validate = ["pidof ospfd"] - ipv4_routing = True + name: str = "FRROSPFv2" + shutdown: List[str] = ["killall ospfd"] + validate: List[str] = ["pidof ospfd"] + ipv4_routing: bool = True def frr_config(self) -> str: router_id = get_router_id(self.node) @@ -190,11 +192,11 @@ class FRROspfv3(FrrService, ConfigService): unified frr.conf file. """ - name = "FRROSPFv3" - shutdown = ["killall ospf6d"] - validate = ["pidof ospf6d"] - ipv4_routing = True - ipv6_routing = True + name: str = "FRROSPFv3" + shutdown: List[str] = ["killall ospf6d"] + validate: List[str] = ["pidof ospf6d"] + ipv4_routing: bool = True + ipv6_routing: bool = True def frr_config(self) -> str: router_id = get_router_id(self.node) @@ -227,12 +229,12 @@ class FRRBgp(FrrService, ConfigService): having the same AS number. """ - name = "FRRBGP" - shutdown = ["killall bgpd"] - validate = ["pidof bgpd"] - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "FRRBGP" + shutdown: List[str] = ["killall bgpd"] + validate: List[str] = ["pidof bgpd"] + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True def frr_config(self) -> str: router_id = get_router_id(self.node) @@ -257,10 +259,10 @@ class FRRRip(FrrService, ConfigService): The RIP service provides IPv4 routing for wired networks. """ - name = "FRRRIP" - shutdown = ["killall ripd"] - validate = ["pidof ripd"] - ipv4_routing = True + name: str = "FRRRIP" + shutdown: List[str] = ["killall ripd"] + validate: List[str] = ["pidof ripd"] + ipv4_routing: bool = True def frr_config(self) -> str: text = """ @@ -282,10 +284,10 @@ class FRRRipng(FrrService, ConfigService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "FRRRIPNG" - shutdown = ["killall ripngd"] - validate = ["pidof ripngd"] - ipv6_routing = True + name: str = "FRRRIPNG" + shutdown: List[str] = ["killall ripngd"] + validate: List[str] = ["pidof ripngd"] + ipv6_routing: bool = True def frr_config(self) -> str: text = """ @@ -308,10 +310,10 @@ class FRRBabel(FrrService, ConfigService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "FRRBabel" - shutdown = ["killall babeld"] - validate = ["pidof babeld"] - ipv6_routing = True + name: str = "FRRBabel" + shutdown: List[str] = ["killall babeld"] + validate: List[str] = ["pidof babeld"] + ipv6_routing: bool = True def frr_config(self) -> str: ifnames = [] @@ -348,10 +350,10 @@ class FRRpimd(FrrService, ConfigService): PIM multicast routing based on XORP. """ - name = "FRRpimd" - shutdown = ["killall pimd"] - validate = ["pidof pimd"] - ipv4_routing = True + name: str = "FRRpimd" + shutdown: List[str] = ["killall pimd"] + validate: List[str] = ["pidof pimd"] + ipv4_routing: bool = True def frr_config(self) -> str: ifname = "eth0" diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index ca95b8f63..0a5e8baff 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -1,26 +1,27 @@ -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import utils +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode -GROUP = "ProtoSvc" +GROUP: str = "ProtoSvc" class MgenSinkService(ConfigService): - name = "MGEN_Sink" - group = GROUP - directories = [] - files = ["mgensink.sh", "sink.mgen"] - executables = ["mgen"] - dependencies = [] - startup = ["sh mgensink.sh"] - validate = ["pidof mgen"] - shutdown = ["killall mgen"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "MGEN_Sink" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["mgensink.sh", "sink.mgen"] + executables: List[str] = ["mgen"] + dependencies: List[str] = [] + startup: List[str] = ["sh mgensink.sh"] + validate: List[str] = ["pidof mgen"] + shutdown: List[str] = ["killall mgen"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] @@ -31,18 +32,18 @@ def data(self) -> Dict[str, Any]: class NrlNhdp(ConfigService): - name = "NHDP" - group = GROUP - directories = [] - files = ["nrlnhdp.sh"] - executables = ["nrlnhdp"] - dependencies = [] - startup = ["sh nrlnhdp.sh"] - validate = ["pidof nrlnhdp"] - shutdown = ["killall nrlnhdp"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "NHDP" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["nrlnhdp.sh"] + executables: List[str] = ["nrlnhdp"] + dependencies: List[str] = [] + startup: List[str] = ["sh nrlnhdp.sh"] + validate: List[str] = ["pidof nrlnhdp"] + shutdown: List[str] = ["killall nrlnhdp"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -53,18 +54,18 @@ def data(self) -> Dict[str, Any]: class NrlSmf(ConfigService): - name = "SMF" - group = GROUP - directories = [] - files = ["startsmf.sh"] - executables = ["nrlsmf", "killall"] - dependencies = [] - startup = ["sh startsmf.sh"] - validate = ["pidof nrlsmf"] - shutdown = ["killall nrlsmf"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "SMF" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["startsmf.sh"] + executables: List[str] = ["nrlsmf", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh startsmf.sh"] + validate: List[str] = ["pidof nrlsmf"] + shutdown: List[str] = ["killall nrlsmf"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_arouted = "arouted" in self.node.config_services @@ -91,18 +92,18 @@ def data(self) -> Dict[str, Any]: class NrlOlsr(ConfigService): - name = "OLSR" - group = GROUP - directories = [] - files = ["nrlolsrd.sh"] - executables = ["nrlolsrd"] - dependencies = [] - startup = ["sh nrlolsrd.sh"] - validate = ["pidof nrlolsrd"] - shutdown = ["killall nrlolsrd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "OLSR" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["nrlolsrd.sh"] + executables: List[str] = ["nrlolsrd"] + dependencies: List[str] = [] + startup: List[str] = ["sh nrlolsrd.sh"] + validate: List[str] = ["pidof nrlolsrd"] + shutdown: List[str] = ["killall nrlolsrd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -115,18 +116,18 @@ def data(self) -> Dict[str, Any]: class NrlOlsrv2(ConfigService): - name = "OLSRv2" - group = GROUP - directories = [] - files = ["nrlolsrv2.sh"] - executables = ["nrlolsrv2"] - dependencies = [] - startup = ["sh nrlolsrv2.sh"] - validate = ["pidof nrlolsrv2"] - shutdown = ["killall nrlolsrv2"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "OLSRv2" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["nrlolsrv2.sh"] + executables: List[str] = ["nrlolsrv2"] + dependencies: List[str] = [] + startup: List[str] = ["sh nrlolsrv2.sh"] + validate: List[str] = ["pidof nrlolsrv2"] + shutdown: List[str] = ["killall nrlolsrv2"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -137,18 +138,18 @@ def data(self) -> Dict[str, Any]: class OlsrOrg(ConfigService): - name = "OLSRORG" - group = GROUP - directories = ["/etc/olsrd"] - files = ["olsrd.sh", "/etc/olsrd/olsrd.conf"] - executables = ["olsrd"] - dependencies = [] - startup = ["sh olsrd.sh"] - validate = ["pidof olsrd"] - shutdown = ["killall olsrd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "OLSRORG" + group: str = GROUP + directories: List[str] = ["/etc/olsrd"] + files: List[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"] + executables: List[str] = ["olsrd"] + dependencies: List[str] = [] + startup: List[str] = ["sh olsrd.sh"] + validate: List[str] = ["pidof olsrd"] + shutdown: List[str] = ["killall olsrd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -159,33 +160,33 @@ def data(self) -> Dict[str, Any]: class MgenActor(ConfigService): - name = "MgenActor" - group = GROUP - directories = [] - files = ["start_mgen_actor.sh"] - executables = ["mgen"] - dependencies = [] - startup = ["sh start_mgen_actor.sh"] - validate = ["pidof mgen"] - shutdown = ["killall mgen"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "MgenActor" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["start_mgen_actor.sh"] + executables: List[str] = ["mgen"] + dependencies: List[str] = [] + startup: List[str] = ["sh start_mgen_actor.sh"] + validate: List[str] = ["pidof mgen"] + shutdown: List[str] = ["killall mgen"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class Arouted(ConfigService): - name = "arouted" - group = GROUP - directories = [] - files = ["startarouted.sh"] - executables = ["arouted"] - dependencies = [] - startup = ["sh startarouted.sh"] - validate = ["pidof arouted"] - shutdown = ["pkill arouted"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "arouted" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["startarouted.sh"] + executables: List[str] = ["arouted"] + dependencies: List[str] = [] + startup: List[str] = ["sh 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 diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 19e21476a..40a1d7d34 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -1,17 +1,18 @@ import abc import logging -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import constants +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet from core.nodes.base import CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode -GROUP = "Quagga" +GROUP: str = "Quagga" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -57,22 +58,22 @@ def get_router_id(node: CoreNodeBase) -> str: class Zebra(ConfigService): - name = "zebra" - group = GROUP - directories = ["/usr/local/etc/quagga", "/var/run/quagga"] - files = [ + name: str = "zebra" + group: str = GROUP + directories: List[str] = ["/usr/local/etc/quagga", "/var/run/quagga"] + files: List[str] = [ "/usr/local/etc/quagga/Quagga.conf", "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf", ] - executables = ["zebra"] - dependencies = [] - startup = ["sh quaggaboot.sh zebra"] - validate = ["pidof zebra"] - shutdown = ["killall zebra"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + executables: List[str] = ["zebra"] + dependencies: List[str] = [] + startup: List[str] = ["sh quaggaboot.sh zebra"] + validate: List[str] = ["pidof zebra"] + shutdown: List[str] = ["killall zebra"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: quagga_bin_search = self.node.session.options.get_config( @@ -90,6 +91,8 @@ def data(self) -> Dict[str, Any]: for service in self.node.config_services.values(): if self.name not in service.dependencies: continue + if not isinstance(service, QuaggaService): + continue if service.ipv4_routing: want_ip4 = True if service.ipv6_routing: @@ -122,19 +125,19 @@ def data(self) -> Dict[str, Any]: class QuaggaService(abc.ABC): - group = GROUP - directories = [] - files = [] - executables = [] - dependencies = ["zebra"] - startup = [] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} - ipv4_routing = False - ipv6_routing = False + group: str = GROUP + directories: List[str] = [] + files: List[str] = [] + executables: List[str] = [] + dependencies: List[str] = ["zebra"] + startup: List[str] = [] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} + ipv4_routing: bool = False + ipv6_routing: bool = False @abc.abstractmethod def quagga_iface_config(self, iface: CoreInterface) -> str: @@ -152,10 +155,10 @@ class Ospfv2(QuaggaService, ConfigService): unified Quagga.conf file. """ - name = "OSPFv2" - validate = ["pidof ospfd"] - shutdown = ["killall ospfd"] - ipv4_routing = True + name: str = "OSPFv2" + validate: List[str] = ["pidof ospfd"] + shutdown: List[str] = ["killall ospfd"] + ipv4_routing: bool = True def quagga_iface_config(self, iface: CoreInterface) -> str: if has_mtu_mismatch(iface): @@ -190,11 +193,11 @@ class Ospfv3(QuaggaService, ConfigService): unified Quagga.conf file. """ - name = "OSPFv3" - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "OSPFv3" + shutdown: List[str] = ["killall ospf6d"] + validate: List[str] = ["pidof ospf6d"] + ipv4_routing: bool = True + ipv6_routing: bool = True def quagga_iface_config(self, iface: CoreInterface) -> str: mtu = get_min_mtu(iface) @@ -229,7 +232,7 @@ class Ospfv3mdr(Ospfv3): unified Quagga.conf file. """ - name = "OSPFv3MDR" + name: str = "OSPFv3MDR" def data(self) -> Dict[str, Any]: for iface in self.node.get_ifaces(): @@ -262,11 +265,11 @@ class Bgp(QuaggaService, ConfigService): having the same AS number. """ - name = "BGP" - shutdown = ["killall bgpd"] - validate = ["pidof bgpd"] - ipv4_routing = True - ipv6_routing = True + name: str = "BGP" + shutdown: List[str] = ["killall bgpd"] + validate: List[str] = ["pidof bgpd"] + ipv4_routing: bool = True + ipv6_routing: bool = True def quagga_config(self) -> str: return "" @@ -291,10 +294,10 @@ class Rip(QuaggaService, ConfigService): The RIP service provides IPv4 routing for wired networks. """ - name = "RIP" - shutdown = ["killall ripd"] - validate = ["pidof ripd"] - ipv4_routing = True + name: str = "RIP" + shutdown: List[str] = ["killall ripd"] + validate: List[str] = ["pidof ripd"] + ipv4_routing: bool = True def quagga_config(self) -> str: text = """ @@ -316,10 +319,10 @@ class Ripng(QuaggaService, ConfigService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "RIPNG" - shutdown = ["killall ripngd"] - validate = ["pidof ripngd"] - ipv6_routing = True + name: str = "RIPNG" + shutdown: List[str] = ["killall ripngd"] + validate: List[str] = ["pidof ripngd"] + ipv6_routing: bool = True def quagga_config(self) -> str: text = """ @@ -342,10 +345,10 @@ class Babel(QuaggaService, ConfigService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "Babel" - shutdown = ["killall babeld"] - validate = ["pidof babeld"] - ipv6_routing = True + name: str = "Babel" + shutdown: List[str] = ["killall babeld"] + validate: List[str] = ["pidof babeld"] + ipv6_routing: bool = True def quagga_config(self) -> str: ifnames = [] @@ -382,10 +385,10 @@ class Xpimd(QuaggaService, ConfigService): PIM multicast routing based on XORP. """ - name = "Xpimd" - shutdown = ["killall xpimd"] - validate = ["pidof xpimd"] - ipv4_routing = True + name: str = "Xpimd" + shutdown: List[str] = ["killall xpimd"] + validate: List[str] = ["pidof xpimd"] + ipv4_routing: bool = True def quagga_config(self) -> str: ifname = "eth0" diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 6e92bf623..5766b0db7 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, List import netaddr @@ -6,21 +6,21 @@ from core.configservice.base import ConfigService, ConfigServiceMode from core.emulator.enumerations import ConfigDataTypes -GROUP_NAME = "Security" +GROUP_NAME: str = "Security" class VpnClient(ConfigService): - name = "VPNClient" - group = GROUP_NAME - directories = [] - files = ["vpnclient.sh"] - executables = ["openvpn", "ip", "killall"] - dependencies = [] - startup = ["sh vpnclient.sh"] - validate = ["pidof openvpn"] - shutdown = ["killall openvpn"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [ + name: str = "VPNClient" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["vpnclient.sh"] + executables: List[str] = ["openvpn", "ip", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh vpnclient.sh"] + validate: List[str] = ["pidof openvpn"] + shutdown: List[str] = ["killall openvpn"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [ Configuration( _id="keydir", _type=ConfigDataTypes.STRING, @@ -40,21 +40,21 @@ class VpnClient(ConfigService): default="10.0.2.10", ), ] - modes = {} + modes: Dict[str, Dict[str, str]] = {} class VpnServer(ConfigService): - name = "VPNServer" - group = GROUP_NAME - directories = [] - files = ["vpnserver.sh"] - executables = ["openvpn", "ip", "killall"] - dependencies = [] - startup = ["sh vpnserver.sh"] - validate = ["pidof openvpn"] - shutdown = ["killall openvpn"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [ + name: str = "VPNServer" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["vpnserver.sh"] + executables: List[str] = ["openvpn", "ip", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh vpnserver.sh"] + validate: List[str] = ["pidof openvpn"] + shutdown: List[str] = ["killall openvpn"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [ Configuration( _id="keydir", _type=ConfigDataTypes.STRING, @@ -74,7 +74,7 @@ class VpnServer(ConfigService): default="10.0.200.0", ), ] - modes = {} + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: address = None @@ -87,48 +87,48 @@ def data(self) -> Dict[str, Any]: class IPsec(ConfigService): - name = "IPsec" - group = GROUP_NAME - directories = [] - files = ["ipsec.sh"] - executables = ["racoon", "ip", "setkey", "killall"] - dependencies = [] - startup = ["sh ipsec.sh"] - validate = ["pidof racoon"] - shutdown = ["killall racoon"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "IPsec" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["ipsec.sh"] + executables: List[str] = ["racoon", "ip", "setkey", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh ipsec.sh"] + validate: List[str] = ["pidof racoon"] + shutdown: List[str] = ["killall racoon"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class Firewall(ConfigService): - name = "Firewall" - group = GROUP_NAME - directories = [] - files = ["firewall.sh"] - executables = ["iptables"] - dependencies = [] - startup = ["sh firewall.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "Firewall" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["firewall.sh"] + executables: List[str] = ["iptables"] + dependencies: List[str] = [] + startup: List[str] = ["sh firewall.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class Nat(ConfigService): - name = "NAT" - group = GROUP_NAME - directories = [] - files = ["nat.sh"] - executables = ["iptables"] - dependencies = [] - startup = ["sh nat.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "NAT" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["nat.sh"] + executables: List[str] = ["iptables"] + dependencies: List[str] = [] + startup: List[str] = ["sh nat.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] diff --git a/daemon/core/configservices/simpleservice.py b/daemon/core/configservices/simpleservice.py index e727fe82c..c2e7242f4 100644 --- a/daemon/core/configservices/simpleservice.py +++ b/daemon/core/configservices/simpleservice.py @@ -1,20 +1,22 @@ +from typing import Dict, List + from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emulator.enumerations import ConfigDataTypes class SimpleService(ConfigService): - name = "Simple" - group = "SimpleGroup" - directories = ["/etc/quagga", "/usr/local/lib"] - files = ["test1.sh", "test2.sh"] - executables = [] - dependencies = [] - startup = [] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [ + name: str = "Simple" + group: str = "SimpleGroup" + directories: List[str] = ["/etc/quagga", "/usr/local/lib"] + files: List[str] = ["test1.sh", "test2.sh"] + executables: List[str] = [] + dependencies: List[str] = [] + startup: List[str] = [] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [ Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Text"), Configuration(_id="value2", _type=ConfigDataTypes.BOOL, label="Boolean"), Configuration( @@ -24,7 +26,7 @@ class SimpleService(ConfigService): options=["value1", "value2", "value3"], ), ] - modes = { + modes: Dict[str, Dict[str, str]] = { "mode1": {"value1": "value1", "value2": "0", "value3": "value2"}, "mode2": {"value1": "value2", "value2": "1", "value3": "value3"}, "mode3": {"value1": "value3", "value2": "0", "value3": "value1"}, diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 5aa3bb543..983f6cff5 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -1,26 +1,27 @@ -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import utils +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode GROUP_NAME = "Utility" class DefaultRouteService(ConfigService): - name = "DefaultRoute" - group = GROUP_NAME - directories = [] - files = ["defaultroute.sh"] - executables = ["ip"] - dependencies = [] - startup = ["sh defaultroute.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DefaultRoute" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["defaultroute.sh"] + executables: List[str] = ["ip"] + dependencies: List[str] = [] + startup: List[str] = ["sh defaultroute.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: # only add default routes for linked routing nodes @@ -37,18 +38,18 @@ def data(self) -> Dict[str, Any]: class DefaultMulticastRouteService(ConfigService): - name = "DefaultMulticastRoute" - group = GROUP_NAME - directories = [] - files = ["defaultmroute.sh"] - executables = [] - dependencies = [] - startup = ["sh defaultmroute.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DefaultMulticastRoute" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["defaultmroute.sh"] + executables: List[str] = [] + dependencies: List[str] = [] + startup: List[str] = ["sh defaultmroute.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifname = None @@ -59,18 +60,18 @@ def data(self) -> Dict[str, Any]: class StaticRouteService(ConfigService): - name = "StaticRoute" - group = GROUP_NAME - directories = [] - files = ["staticroute.sh"] - executables = [] - dependencies = [] - startup = ["sh staticroute.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "StaticRoute" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["staticroute.sh"] + executables: List[str] = [] + dependencies: List[str] = [] + startup: List[str] = ["sh staticroute.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: routes = [] @@ -88,18 +89,18 @@ def data(self) -> Dict[str, Any]: class IpForwardService(ConfigService): - name = "IPForward" - group = GROUP_NAME - directories = [] - files = ["ipforward.sh"] - executables = ["sysctl"] - dependencies = [] - startup = ["sh ipforward.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "IPForward" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["ipforward.sh"] + executables: List[str] = ["sysctl"] + dependencies: List[str] = [] + startup: List[str] = ["sh ipforward.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: devnames = [] @@ -110,18 +111,18 @@ def data(self) -> Dict[str, Any]: class SshService(ConfigService): - name = "SSH" - group = GROUP_NAME - directories = ["/etc/ssh", "/var/run/sshd"] - files = ["startsshd.sh", "/etc/ssh/sshd_config"] - executables = ["sshd"] - dependencies = [] - startup = ["sh startsshd.sh"] - validate = [] - shutdown = ["killall sshd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "SSH" + group: str = GROUP_NAME + directories: List[str] = ["/etc/ssh", "/var/run/sshd"] + files: List[str] = ["startsshd.sh", "/etc/ssh/sshd_config"] + executables: List[str] = ["sshd"] + dependencies: List[str] = [] + startup: List[str] = ["sh startsshd.sh"] + validate: List[str] = [] + shutdown: List[str] = ["killall sshd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: return dict( @@ -132,18 +133,18 @@ def data(self) -> Dict[str, Any]: class DhcpService(ConfigService): - name = "DHCP" - group = GROUP_NAME - directories = ["/etc/dhcp", "/var/lib/dhcp"] - files = ["/etc/dhcp/dhcpd.conf"] - executables = ["dhcpd"] - dependencies = [] - startup = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"] - validate = ["pidof dhcpd"] - shutdown = ["killall dhcpd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DHCP" + group: str = GROUP_NAME + directories: List[str] = ["/etc/dhcp", "/var/lib/dhcp"] + files: List[str] = ["/etc/dhcp/dhcpd.conf"] + executables: List[str] = ["dhcpd"] + dependencies: List[str] = [] + startup: List[str] = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"] + validate: List[str] = ["pidof dhcpd"] + shutdown: List[str] = ["killall dhcpd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: subnets = [] @@ -161,18 +162,18 @@ def data(self) -> Dict[str, Any]: class DhcpClientService(ConfigService): - name = "DHCPClient" - group = GROUP_NAME - directories = [] - files = ["startdhcpclient.sh"] - executables = ["dhclient"] - dependencies = [] - startup = ["sh startdhcpclient.sh"] - validate = ["pidof dhclient"] - shutdown = ["killall dhclient"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DHCPClient" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["startdhcpclient.sh"] + executables: List[str] = ["dhclient"] + dependencies: List[str] = [] + startup: List[str] = ["sh startdhcpclient.sh"] + validate: List[str] = ["pidof dhclient"] + shutdown: List[str] = ["killall dhclient"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] @@ -182,33 +183,33 @@ def data(self) -> Dict[str, Any]: class FtpService(ConfigService): - name = "FTP" - group = GROUP_NAME - directories = ["/var/run/vsftpd/empty", "/var/ftp"] - files = ["vsftpd.conf"] - executables = ["vsftpd"] - dependencies = [] - startup = ["vsftpd ./vsftpd.conf"] - validate = ["pidof vsftpd"] - shutdown = ["killall vsftpd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "FTP" + group: str = GROUP_NAME + directories: List[str] = ["/var/run/vsftpd/empty", "/var/ftp"] + files: List[str] = ["vsftpd.conf"] + executables: List[str] = ["vsftpd"] + dependencies: List[str] = [] + startup: List[str] = ["vsftpd ./vsftpd.conf"] + validate: List[str] = ["pidof vsftpd"] + shutdown: List[str] = ["killall vsftpd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class PcapService(ConfigService): - name = "pcap" - group = GROUP_NAME - directories = [] - files = ["pcap.sh"] - executables = ["tcpdump"] - dependencies = [] - startup = ["sh pcap.sh start"] - validate = ["pidof tcpdump"] - shutdown = ["sh pcap.sh stop"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "pcap" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["pcap.sh"] + executables: List[str] = ["tcpdump"] + dependencies: List[str] = [] + startup: List[str] = ["sh pcap.sh start"] + validate: List[str] = ["pidof tcpdump"] + shutdown: List[str] = ["sh pcap.sh stop"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] @@ -218,18 +219,20 @@ def data(self) -> Dict[str, Any]: class RadvdService(ConfigService): - name = "radvd" - group = GROUP_NAME - directories = ["/etc/radvd"] - files = ["/etc/radvd/radvd.conf"] - executables = ["radvd"] - dependencies = [] - startup = ["radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log"] - validate = ["pidof radvd"] - shutdown = ["pkill radvd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "radvd" + group: str = GROUP_NAME + directories: List[str] = ["/etc/radvd"] + files: List[str] = ["/etc/radvd/radvd.conf"] + executables: List[str] = ["radvd"] + dependencies: List[str] = [] + startup: List[str] = [ + "radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log" + ] + validate: List[str] = ["pidof radvd"] + shutdown: List[str] = ["pkill radvd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifaces = [] @@ -246,24 +249,24 @@ def data(self) -> Dict[str, Any]: class AtdService(ConfigService): - name = "atd" - group = GROUP_NAME - directories = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"] - files = ["startatd.sh"] - executables = ["atd"] - dependencies = [] - startup = ["sh startatd.sh"] - validate = ["pidof atd"] - shutdown = ["pkill atd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "atd" + group: str = GROUP_NAME + directories: List[str] = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"] + files: List[str] = ["startatd.sh"] + executables: List[str] = ["atd"] + dependencies: List[str] = [] + startup: List[str] = ["sh startatd.sh"] + validate: List[str] = ["pidof atd"] + shutdown: List[str] = ["pkill atd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class HttpService(ConfigService): - name = "HTTP" - group = GROUP_NAME - directories = [ + name: str = "HTTP" + group: str = GROUP_NAME + directories: List[str] = [ "/etc/apache2", "/var/run/apache2", "/var/log/apache2", @@ -271,15 +274,19 @@ class HttpService(ConfigService): "/var/lock/apache2", "/var/www", ] - files = ["/etc/apache2/apache2.conf", "/etc/apache2/envvars", "/var/www/index.html"] - executables = ["apache2ctl"] - dependencies = [] - startup = ["chown www-data /var/lock/apache2", "apache2ctl start"] - validate = ["pidof apache2"] - shutdown = ["apache2ctl stop"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + files: List[str] = [ + "/etc/apache2/apache2.conf", + "/etc/apache2/envvars", + "/var/www/index.html", + ] + executables: List[str] = ["apache2ctl"] + dependencies: List[str] = [] + startup: List[str] = ["chown www-data /var/lock/apache2", "apache2ctl start"] + validate: List[str] = ["pidof apache2"] + shutdown: List[str] = ["apache2ctl stop"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifaces = [] From ca2b1c9e4cb90f82380492bbea61dd59a06f987a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 21:33:28 -0700 Subject: [PATCH 040/210] daemon: refactored all_link_data to links --- daemon/core/api/grpc/grpcutils.py | 6 +++--- daemon/core/api/tlv/corehandlers.py | 12 ++++++------ daemon/core/emane/nodes.py | 4 ++-- daemon/core/location/mobility.py | 4 ++-- daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/network.py | 12 ++++++------ daemon/core/plugins/sdt.py | 2 +- daemon/core/xml/corexml.py | 2 +- daemon/tests/test_core.py | 2 +- daemon/tests/test_grpc.py | 14 +++++++------- daemon/tests/test_gui.py | 22 +++++++++++----------- daemon/tests/test_links.py | 6 +++--- daemon/tests/test_xml.py | 8 ++++---- 13 files changed, 49 insertions(+), 49 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index d95b75559..2c13315ce 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -270,9 +270,9 @@ def get_links(node: NodeBase): :return: protobuf links """ links = [] - for link_data in node.all_link_data(): - link = convert_link(link_data) - links.append(link) + for link in node.links(): + link_proto = convert_link(link) + links.append(link_proto) return links diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index d01f15a3e..bb4f2ecd7 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -1824,16 +1824,16 @@ def send_objects(self): Return API messages that describe the current session. """ # find all nodes and links - links_data = [] + all_links = [] with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] self.session.broadcast_node(node, MessageFlags.ADD) - node_links = node.all_link_data(flags=MessageFlags.ADD) - links_data.extend(node_links) + links = node.links(flags=MessageFlags.ADD) + all_links.extend(links) - for link_data in links_data: - self.session.broadcast_link(link_data) + for link in all_links: + self.session.broadcast_link(link) # send mobility model info for node_id in self.session.mobility.nodes(): @@ -1940,7 +1940,7 @@ def send_objects(self): node_count = self.session.get_node_count() logging.info( - "informed GUI about %d nodes and %d links", node_count, len(links_data) + "informed GUI about %d nodes and %d links", node_count, len(all_links) ) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index c28f1382e..9173fbfc5 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -241,8 +241,8 @@ def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None: event.append(nemid, latitude=lat, longitude=lon, altitude=alt) self.session.emane.service.publish(0, event) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: - links = super().all_link_data(flags) + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + links = super().links(flags) # gather current emane links nem_ids = set(self.nemidmap.values()) emane_manager = self.session.emane diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 9bb2966e5..f2e0f470c 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -217,7 +217,7 @@ def __init__(self, session: "Session", _id: int) -> None: self.session: "Session" = session self.id: int = _id - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ May be used if the model can populate the GUI with wireless (green) link lines. @@ -509,7 +509,7 @@ def sendlinkmsg( link_data = self.create_link_data(iface, iface2, message_type) self.session.broadcast_link(link_data) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Return a list of wireless link messages for when the GUI reconnects. diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 4fc6b8733..2c8ca06ce 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -182,7 +182,7 @@ def next_iface_id(self) -> int: self.iface_id += 1 return iface_id - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build link data for this node. @@ -1021,7 +1021,7 @@ def detach(self, iface: CoreInterface) -> None: with self._linked_lock: del self._linked[iface] - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build link data objects for this network. Each link object describes a link between this network and a node. diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index f20b6dfb5..62443fb85 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -831,7 +831,7 @@ def shutdown(self) -> None: super().shutdown() - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Do not include CtrlNet in link messages describing this session. @@ -859,7 +859,7 @@ def attach(self, iface: CoreInterface) -> None: raise CoreError("ptp links support at most 2 network interfaces") super().attach(iface) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build CORE API TLVs for a point-to-point link. One Link message describes this network. @@ -1054,17 +1054,17 @@ def updatemodel(self, config: Dict[str, str]) -> None: for iface in self.get_ifaces(): iface.setposition() - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Retrieve all link data. :param flags: message flags :return: list of link data """ - all_links = super().all_link_data(flags) + links = super().links(flags) if self.model: - all_links.extend(self.model.all_link_data(flags)) - return all_links + links.extend(self.model.links(flags)) + return links class TunnelNode(GreTapBridge): diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 84c907302..ef36b0a45 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -225,7 +225,7 @@ def sendobjs(self) -> None: self.add_node(node) for net in nets: - all_links = net.all_link_data(flags=MessageFlags.ADD) + all_links = net.links(flags=MessageFlags.ADD) for link_data in all_links: is_wireless = isinstance(net, (WlanNode, EmaneNet)) if is_wireless and link_data.node1_id == net.id: diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 190cf8f77..d3cc85d85 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -465,7 +465,7 @@ def write_nodes(self) -> List[LinkData]: self.write_device(node) # add known links - links.extend(node.all_link_data()) + links.extend(node.links()) return links def write_network(self, node: NodeBase) -> None: diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 2623b0df6..c44658631 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -120,7 +120,7 @@ def test_iface(self, session: Session, ip_prefixes: IpPrefixes): session.instantiate() # check link data gets generated - assert ptp_node.all_link_data(MessageFlags.ADD) + assert ptp_node.links(MessageFlags.ADD) # check common nets exist between linked nodes assert node1.commonnets(node2) diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 8abf33aa8..a4efd6d9c 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -555,7 +555,7 @@ def test_add_link(self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelp session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - assert len(switch.all_link_data()) == 0 + assert len(switch.links()) == 0 # then iface = iface_helper.create_iface(node.id, 0) @@ -564,7 +564,7 @@ def test_add_link(self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelp # then assert response.result is True - assert len(switch.all_link_data()) == 1 + assert len(switch.links()) == 1 def test_add_link_exception( self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper @@ -589,7 +589,7 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): iface = ip_prefixes.create_iface(node) session.add_link(node.id, switch.id, iface) options = core_pb2.LinkOptions(bandwidth=30000) - link = switch.all_link_data()[0] + link = switch.links()[0] assert options.bandwidth != link.options.bandwidth # then @@ -600,7 +600,7 @@ def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # then assert response.result is True - link = switch.all_link_data()[0] + link = switch.links()[0] assert options.bandwidth == link.options.bandwidth def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): @@ -618,7 +618,7 @@ def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) if node.id not in {node1.id, node2.id}: link_node = node break - assert len(link_node.all_link_data()) == 1 + assert len(link_node.links()) == 1 # then with client.context_connect(): @@ -628,7 +628,7 @@ def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) # then assert response.result is True - assert len(link_node.all_link_data()) == 0 + assert len(link_node.links()) == 0 def test_get_wlan_config(self, grpc_server: CoreGrpcServer): # given @@ -1029,7 +1029,7 @@ def test_link_events(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes) node = session.add_node(CoreNode) iface = ip_prefixes.create_iface(node) session.add_link(node.id, wlan.id, iface) - link_data = wlan.all_link_data()[0] + link_data = wlan.links()[0] queue = Queue() def handle_event(event_data): diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 8f01a2bfa..a0b3bd8ab 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -122,7 +122,7 @@ def test_link_add_node_to_net(self, coretlv: CoreHandler): coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 def test_link_add_net_to_node(self, coretlv: CoreHandler): @@ -146,7 +146,7 @@ def test_link_add_net_to_node(self, coretlv: CoreHandler): coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 def test_link_add_node_to_node(self, coretlv: CoreHandler): @@ -176,7 +176,7 @@ def test_link_add_node_to_node(self, coretlv: CoreHandler): all_links = [] for node_id in coretlv.session.nodes: node = coretlv.session.nodes[node_id] - all_links += node.all_link_data() + all_links += node.links() assert len(all_links) == 1 def test_link_update(self, coretlv: CoreHandler): @@ -198,7 +198,7 @@ def test_link_update(self, coretlv: CoreHandler): ) coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 link = all_links[0] assert link.options.bandwidth is None @@ -216,7 +216,7 @@ def test_link_update(self, coretlv: CoreHandler): coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 link = all_links[0] assert link.options.bandwidth == bandwidth @@ -245,7 +245,7 @@ def test_link_delete_node_to_node(self, coretlv: CoreHandler): all_links = [] for node_id in coretlv.session.nodes: node = coretlv.session.nodes[node_id] - all_links += node.all_link_data() + all_links += node.links() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( @@ -262,7 +262,7 @@ def test_link_delete_node_to_node(self, coretlv: CoreHandler): all_links = [] for node_id in coretlv.session.nodes: node = coretlv.session.nodes[node_id] - all_links += node.all_link_data() + all_links += node.links() assert len(all_links) == 0 def test_link_delete_node_to_net(self, coretlv: CoreHandler): @@ -284,7 +284,7 @@ def test_link_delete_node_to_net(self, coretlv: CoreHandler): ) coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( @@ -298,7 +298,7 @@ def test_link_delete_node_to_net(self, coretlv: CoreHandler): coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 0 def test_link_delete_net_to_node(self, coretlv: CoreHandler): @@ -320,7 +320,7 @@ def test_link_delete_net_to_node(self, coretlv: CoreHandler): ) coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( @@ -334,7 +334,7 @@ def test_link_delete_net_to_node(self, coretlv: CoreHandler): coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 0 def test_session_update(self, coretlv: CoreHandler): diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 4078d8bc6..535ad8372 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -49,7 +49,7 @@ def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): session.add_link(node1.id, node2.id, iface1_data=iface1_data) # then - assert node2.all_link_data() + assert node2.links() assert node1.get_iface(iface1_data.id) def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): @@ -62,7 +62,7 @@ def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): session.add_link(node1.id, node2.id, iface2_data=iface2_data) # then - assert node1.all_link_data() + assert node1.links() assert node2.get_iface(iface2_data.id) def test_add_net_to_net(self, session): @@ -74,7 +74,7 @@ def test_add_net_to_net(self, session): session.add_link(node1.id, node2.id) # then - assert node1.all_link_data() + assert node1.links() def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 91b598f34..fb8bc4d9d 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -285,7 +285,7 @@ def test_network_to_network(self, session: Session, tmpdir: TemporaryFile): switch2 = session.get_node(node2_id, SwitchNode) assert switch1 assert switch2 - assert len(switch1.all_link_data() + switch2.all_link_data()) == 1 + assert len(switch1.links() + switch2.links()) == 1 def test_link_options( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -345,7 +345,7 @@ def test_link_options( links = [] for node_id in session.nodes: node = session.nodes[node_id] - links += node.all_link_data() + links += node.links() link = links[0] assert options.loss == link.options.loss assert options.bandwidth == link.options.bandwidth @@ -412,7 +412,7 @@ def test_link_options_ptp( links = [] for node_id in session.nodes: node = session.nodes[node_id] - links += node.all_link_data() + links += node.links() link = links[0] assert options.loss == link.options.loss assert options.bandwidth == link.options.bandwidth @@ -490,7 +490,7 @@ def test_link_options_bidirectional( links = [] for node_id in session.nodes: node = session.nodes[node_id] - links += node.all_link_data() + links += node.links() assert len(links) == 2 link1 = links[0] link2 = links[1] From d88f3a253548ff08f9204488b2c36f32b6d35a97 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 08:50:36 -0700 Subject: [PATCH 041/210] daemon: refactored CoreInterface.addrlist storing strings into CoreInterface.ip4s and ip6s, stored as netaddr.IPNetwork objects --- daemon/core/api/grpc/grpcutils.py | 19 +++---- .../configservices/frrservices/services.py | 25 ++++----- .../configservices/nrlservices/services.py | 24 +++------ .../configservices/quaggaservices/services.py | 25 ++++----- .../sercurityservices/services.py | 10 ++-- .../configservices/utilservices/services.py | 36 ++++++------- daemon/core/emane/linkmonitor.py | 12 ++--- daemon/core/emulator/session.py | 5 +- daemon/core/nodes/base.py | 21 ++++---- daemon/core/nodes/interface.py | 46 ++++++++++++---- daemon/core/nodes/network.py | 34 ++++++------ daemon/core/services/bird.py | 9 ++-- daemon/core/services/frr.py | 40 ++++++-------- daemon/core/services/nrl.py | 9 ++-- daemon/core/services/quagga.py | 39 ++++++-------- daemon/core/services/sdn.py | 22 +++----- daemon/core/services/utility.py | 53 ++++++++++--------- daemon/core/services/xorp.py | 37 +++++-------- daemon/core/xml/corexmldeployment.py | 3 +- daemon/tests/test_nodes.py | 2 +- 20 files changed, 209 insertions(+), 262 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 2c13315ce..adaf25491 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -3,7 +3,6 @@ from typing import Any, Dict, List, Tuple, Type, Union import grpc -import netaddr from grpc import ServicerContext from core import utils @@ -447,18 +446,16 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: net_id = iface.net.id ip4 = None ip4_mask = None + ip4_net = iface.get_ip4() + if ip4_net: + ip4 = str(ip4_net.ip) + ip4_mask = ip4_net.prefixlen ip6 = None ip6_mask = None - for addr in iface.addrlist: - network = netaddr.IPNetwork(addr) - mask = network.prefixlen - ip = str(network.ip) - if netaddr.valid_ipv4(ip) and not ip4: - ip4 = ip - ip4_mask = mask - elif netaddr.valid_ipv6(ip) and not ip6: - ip6 = ip - ip6_mask = mask + ip6_net = iface.get_ip6() + if ip6_net: + ip6 = str(ip6_net.ip) + ip6_mask = ip6_net.prefixlen return core_pb2.Interface( id=iface.node_id, net_id=net_id, diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index 2e24b40a1..ce8c305ca 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -1,8 +1,6 @@ import abc from typing import Any, Dict, List -import netaddr - from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode @@ -49,10 +47,9 @@ def get_router_id(node: CoreNodeBase) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @@ -102,12 +99,10 @@ def data(self) -> Dict[str, Any]: for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - ip4s.append(x) - else: - ip6s.append(x) + for ip4 in iface.ip4s: + ip4s.append(str(ip4.ip)) + for ip6 in iface.ip6s: + ip6s.append(str(ip6.ip)) is_control = getattr(iface, "control", False) ifaces.append((iface, ip4s, ip6s, is_control)) @@ -163,10 +158,8 @@ def frr_config(self) -> str: router_id = get_router_id(self.node) addresses = [] for iface in self.node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if netaddr.valid_ipv4(addr): - addresses.append(a) + for ip4 in iface.ip4s: + addresses.append(str(ip4.ip)) data = dict(router_id=router_id, addresses=addresses) text = """ router ospf diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index 0a5e8baff..cf9b4c883 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -1,7 +1,5 @@ from typing import Any, Dict, List -import netaddr - from core import utils from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode @@ -75,13 +73,10 @@ def data(self) -> Dict[str, Any]: ip4_prefix = None for iface in self.node.get_ifaces(control=False): ifnames.append(iface.name) - if ip4_prefix: - continue - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - ip4_prefix = f"{a}/{24}" - break + ip4 = iface.get_ip4() + if ip4: + ip4_prefix = f"{ip4.ip}/{24}" + break return dict( has_arouted=has_arouted, has_nhdp=has_nhdp, @@ -191,11 +186,8 @@ class Arouted(ConfigService): def data(self) -> Dict[str, Any]: ip4_prefix = None for iface in self.node.get_ifaces(control=False): - if ip4_prefix: - continue - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - ip4_prefix = f"{a}/{24}" - break + ip4 = iface.get_ip4() + if ip4: + ip4_prefix = f"{ip4.ip}/{24}" + break return dict(ip4_prefix=ip4_prefix) diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 40a1d7d34..e18e8a1a7 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -2,8 +2,6 @@ import logging from typing import Any, Dict, List -import netaddr - from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode @@ -50,10 +48,9 @@ def get_router_id(node: CoreNodeBase) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @@ -103,12 +100,10 @@ def data(self) -> Dict[str, Any]: for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - ip4s.append(x) - else: - ip6s.append(x) + for ip4 in iface.ip4s: + ip4s.append(str(ip4.ip)) + for ip6 in iface.ip6s: + ip6s.append(str(ip6.ip)) is_control = getattr(iface, "control", False) ifaces.append((iface, ip4s, ip6s, is_control)) @@ -170,10 +165,8 @@ def quagga_config(self) -> str: router_id = get_router_id(self.node) addresses = [] for iface in self.node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if netaddr.valid_ipv4(addr): - addresses.append(a) + for ip4 in iface.ip4s: + addresses.append(str(ip4.ip)) data = dict(router_id=router_id, addresses=addresses) text = """ router ospf diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 5766b0db7..4a58fd8c3 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -1,7 +1,5 @@ from typing import Any, Dict, List -import netaddr - from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emulator.enumerations import ConfigDataTypes @@ -79,10 +77,10 @@ class VpnServer(ConfigService): def data(self) -> Dict[str, Any]: address = None for iface in self.node.get_ifaces(control=False): - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - address = addr + ip4 = iface.get_ip4() + if ip4: + address = str(ip4.ip) + break return dict(address=address) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 983f6cff5..8013bc41c 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -29,8 +29,8 @@ def data(self) -> Dict[str, Any]: ifaces = self.node.get_ifaces() if ifaces: iface = ifaces[0] - for x in iface.addrlist: - net = netaddr.IPNetwork(x).cidr + for ip in iface.all_ips(): + net = ip.cidr if net.size > 1: router = net[1] routes.append(str(router)) @@ -76,15 +76,14 @@ class StaticRouteService(ConfigService): def data(self) -> Dict[str, Any]: routes = [] for iface in self.node.get_ifaces(control=False): - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): + for ip in iface.all_ips(): + address = str(ip.ip) + if netaddr.valid_ipv6(address): dst = "3ffe:4::/64" else: dst = "10.9.8.0/24" - net = netaddr.IPNetwork(x) - if net[-2] != net[1]: - routes.append((dst, net[1])) + if ip[-2] != ip[1]: + routes.append((dst, ip[1])) return dict(routes=routes) @@ -149,15 +148,12 @@ class DhcpService(ConfigService): def data(self) -> Dict[str, Any]: subnets = [] for iface in self.node.get_ifaces(control=False): - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - net = netaddr.IPNetwork(x) - # divide the address space in half - index = (net.size - 2) / 2 - rangelow = net[index] - rangehigh = net[-2] - subnets.append((net.ip, net.netmask, rangelow, rangehigh, addr)) + for ip4 in iface.ip4s: + # divide the address space in half + index = (ip4.size - 2) / 2 + rangelow = ip4[index] + rangehigh = ip4[-2] + subnets.append((ip4.ip, ip4.netmask, rangelow, rangehigh, str(ip4.ip))) return dict(subnets=subnets) @@ -238,10 +234,8 @@ def data(self) -> Dict[str, Any]: ifaces = [] for iface in self.node.get_ifaces(control=False): prefixes = [] - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): - prefixes.append(x) + for ip6 in iface.ip6s: + prefixes.append(str(ip6)) if not prefixes: continue ifaces.append((iface.name, prefixes)) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 1a9ac41a8..295aaa1ea 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -4,7 +4,6 @@ import time from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple -import netaddr from lxml import etree from core.emulator.data import LinkData @@ -214,13 +213,12 @@ def get_addresses(self) -> List[str]: for node in nodes: for iface in node.get_ifaces(): if isinstance(iface.net, CtrlNet): - ip4 = None - for x in iface.addrlist: - address, prefix = x.split("/") - if netaddr.valid_ipv4(address): - ip4 = address + address = None + ip4 = iface.get_ip4() if ip4: - addresses.append(ip4) + address = str(ip4.ip) + if address: + addresses.append(address) break return addresses diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 0b97da937..b0507269a 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1548,9 +1548,8 @@ def update_control_iface_hosts( entries = [] for iface in control_net.get_ifaces(): name = iface.node.name - for address in iface.addrlist: - address = address.split("/")[0] - entries.append(f"{address} {name}") + for ip in iface.all_ips(): + entries.append(f"{ip.ip} {name}") logging.info("Adding %d /etc/hosts file entries.", len(entries)) utils.file_munge("/etc/hosts", header, "\n".join(entries) + "\n") diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 2c8ca06ce..90be59af1 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1053,18 +1053,17 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: if uni: unidirectional = 1 - iface2 = InterfaceData( + iface2_data = InterfaceData( id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.mac ) - for address in iface.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if netaddr.valid_ipv4(ip): - iface2.ip4 = ip - iface2.ip4_mask = mask - else: - iface2.ip6 = ip - iface2.ip6_mask = mask + ip4 = iface.get_ip4() + if ip4: + iface2_data.ip4 = str(ip4.ip) + iface2_data.ip4_mask = ip4.prefixlen + ip6 = iface.get_ip6() + if ip6: + iface2_data.ip6 = str(ip6.ip) + iface2_data.ip6_mask = ip6.prefixlen options_data = iface.get_link_options(unidirectional) link_data = LinkData( @@ -1072,7 +1071,7 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: type=self.linktype, node1_id=self.id, node2_id=linked_node.id, - iface2=iface2, + iface2=iface2_data, options=options_data, ) all_links.append(link_data) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 425223622..c1603a21e 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -6,6 +6,8 @@ import time from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple +import netaddr + from core import utils from core.emulator.data import LinkOptions from core.emulator.enumerations import TransportType @@ -52,7 +54,8 @@ def __init__( self.net: Optional[CoreNetworkBase] = None self.othernet: Optional[CoreNetworkBase] = None self._params: Dict[str, float] = {} - self.addrlist: List[str] = [] + self.ip4s: List[netaddr.IPNetwork] = [] + self.ip6s: List[netaddr.IPNetwork] = [] self.mac: Optional[str] = None # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None @@ -131,15 +134,22 @@ def detachnet(self) -> None: if self.net is not None: self.net.detach(self) - def addaddr(self, addr: str) -> None: + def addaddr(self, address: str) -> None: """ - Add address. + Add ip address in the format "10.0.0.1/24". - :param addr: address to add + :param address: address to add :return: nothing """ - addr = utils.validate_ip(addr) - self.addrlist.append(addr) + try: + ip = netaddr.IPNetwork(address) + value = str(ip.ip) + if netaddr.valid_ipv4(value): + self.ip4s.append(ip) + else: + self.ip6s.append(ip) + except netaddr.AddrFormatError: + raise CoreError(f"adding invalid address {address}") def deladdr(self, addr: str) -> None: """ @@ -148,7 +158,23 @@ def deladdr(self, addr: str) -> None: :param addr: address to delete :return: nothing """ - self.addrlist.remove(addr) + if netaddr.valid_ipv4(addr): + ip4 = netaddr.IPNetwork(addr) + self.ip4s.remove(ip4) + elif netaddr.valid_ipv6(addr): + ip6 = netaddr.IPNetwork(addr) + self.ip6s.remove(ip6) + else: + raise CoreError(f"deleting invalid address {addr}") + + def get_ip4(self) -> Optional[netaddr.IPNetwork]: + return next(iter(self.ip4s), None) + + def get_ip6(self) -> Optional[netaddr.IPNetwork]: + return next(iter(self.ip6s), None) + + def all_ips(self) -> List[netaddr.IPNetwork]: + return self.ip4s + self.ip6s def set_mac(self, mac: str) -> None: """ @@ -487,13 +513,13 @@ def install(self) -> None: def setaddrs(self) -> None: """ - Set interface addresses based on self.addrlist. + Set interface addresses. :return: nothing """ self.waitfordevicenode() - for addr in self.addrlist: - self.node.node_net_client.create_address(self.name, str(addr)) + for ip in self.all_ips(): + self.node.node_net_client.create_address(self.name, str(ip)) class GreTap(CoreInterface): diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 62443fb85..3f4ebfbaa 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -881,28 +881,26 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: iface1_data = InterfaceData( id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.mac ) - for address in iface1.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if netaddr.valid_ipv4(ip): - iface1.ip4 = ip - iface1.ip4_mask = mask - else: - iface1.ip6 = ip - iface1.ip6_mask = mask + ip4 = iface1.get_ip4() + if ip4: + iface1_data.ip4 = str(ip4.ip) + iface1_data.ip4_mask = ip4.prefixlen + ip6 = iface1.get_ip6() + if ip6: + iface1_data.ip6 = str(ip6.ip) + iface1_data.ip6_mask = ip6.prefixlen iface2_data = InterfaceData( id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.mac ) - for address in iface2.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if netaddr.valid_ipv4(ip): - iface2.ip4 = ip - iface2.ip4_mask = mask - else: - iface2.ip6 = ip - iface2.ip6_mask = mask + ip4 = iface2.get_ip4() + if ip4: + iface2_data.ip4 = str(ip4.ip) + iface2_data.ip4_mask = ip4.prefixlen + ip6 = iface2.get_ip6() + if ip6: + iface2_data.ip6 = str(ip6.ip) + iface2_data.ip6_mask = ip6.prefixlen options_data = iface1.get_link_options(unidirectional) link_data = LinkData( diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index a50529426..ffb177f3e 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -3,8 +3,6 @@ """ from typing import Optional, Tuple -import netaddr - from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -39,10 +37,9 @@ def router_id(node: CoreNode) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @classmethod diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index e75d8f565..6b9ada3c2 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -67,7 +67,7 @@ def generate_frr_conf(cls, node: CoreNode) -> str: # include control interfaces in addressing but not routing daemons if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) cfg += "\n" continue cfgv4 = "" @@ -87,19 +87,13 @@ def generate_frr_conf(cls, node: CoreNode) -> str: cfgv4 += iface_config if want_ipv4: - ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv4list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip4s)) cfg += "\n" cfg += cfgv4 if want_ipv6: - ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv6list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip6s)) cfg += "\n" cfg += cfgv6 cfg += "!\n" @@ -111,17 +105,17 @@ def generate_frr_conf(cls, node: CoreNode) -> str: return cfg @staticmethod - def addrstr(x: str) -> str: + def addrstr(ip: netaddr.IPNetwork) -> str: """ helper for mapping IP addresses to zebra config statements """ - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - return "ip address %s" % x - elif netaddr.valid_ipv6(addr): - return "ipv6 address %s" % x + address = str(ip.ip) + if netaddr.valid_ipv4(address): + return "ip address %s" % ip + elif netaddr.valid_ipv6(address): + return "ipv6 address %s" % ip else: - raise ValueError("invalid address: %s", x) + raise ValueError("invalid address: %s", ip) @classmethod def generate_frr_boot(cls, node: CoreNode) -> str: @@ -333,10 +327,9 @@ def router_id(node: CoreNode) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @staticmethod @@ -413,11 +406,8 @@ def generate_frr_config(cls, node: CoreNode) -> str: cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += " network %s area 0\n" % a + for ip4 in iface.ip4s: + cfg += f" network {ip4} area 0\n" cfg += "!\n" return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 9933b1300..697f4eeef 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -4,8 +4,6 @@ """ from typing import Optional, Tuple -import netaddr - from core import utils from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -32,10 +30,9 @@ def firstipv4prefix(node: CoreNode, prefixlen: int = 24) -> str: interface's prefix length, so e.g. '/32' can turn into '/24'. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return f"{a}/{prefixlen}" + ip4 = iface.get_ip4() + if ip4: + return f"{ip4.ip}/{prefixlen}" return "0.0.0.0/%s" % prefixlen diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 30d14353e..7f717e595 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -64,7 +64,7 @@ def generate_quagga_conf(cls, node: CoreNode) -> str: # include control interfaces in addressing but not routing daemons if getattr(iface, "control", False): cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) cfg += "\n" continue cfgv4 = "" @@ -86,19 +86,13 @@ def generate_quagga_conf(cls, node: CoreNode) -> str: cfgv4 += iface_config if want_ipv4: - ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv4list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip4s)) cfg += "\n" cfg += cfgv4 if want_ipv6: - ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv6list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip6s)) cfg += "\n" cfg += cfgv6 cfg += "!\n" @@ -112,17 +106,17 @@ def generate_quagga_conf(cls, node: CoreNode) -> str: return cfg @staticmethod - def addrstr(x: str) -> str: + def addrstr(ip: netaddr.IPNetwork) -> str: """ helper for mapping IP addresses to zebra config statements """ - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - return "ip address %s" % x - elif netaddr.valid_ipv6(addr): - return "ipv6 address %s" % x + address = str(ip.ip) + if netaddr.valid_ipv4(address): + return "ip address %s" % ip + elif netaddr.valid_ipv6(address): + return "ipv6 address %s" % ip else: - raise ValueError("invalid address: %s", x) + raise ValueError("invalid address: %s", ip) @classmethod def generate_quagga_boot(cls, node: CoreNode) -> str: @@ -255,10 +249,9 @@ def router_id(node: CoreNode) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return f"0.0.0.{node.id:d}" @staticmethod @@ -335,10 +328,8 @@ def generate_quagga_config(cls, node: CoreNode) -> str: cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if netaddr.valid_ipv4(addr): - cfg += " network %s area 0\n" % a + for ip4 in iface.ip4s: + cfg += f" network {ip4} area 0\n" cfg += "!\n" return cfg diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 1f17201dd..ef077662a 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -5,8 +5,6 @@ import re from typing import Tuple -import netaddr - from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -65,18 +63,14 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: # remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces # or assign them manually to rtr interfaces if zebra is not running - for addr in iface.addrlist: - addr = addr.split("/")[0] - if netaddr.valid_ipv4(addr): - cfg += "ip addr del %s dev %s\n" % (addr, iface.name) - if has_zebra == 0: - cfg += "ip addr add %s dev rtr%s\n" % (addr, ifnum) - elif netaddr.valid_ipv6(addr): - cfg += "ip -6 addr del %s dev %s\n" % (addr, iface.name) - if has_zebra == 0: - cfg += "ip -6 addr add %s dev rtr%s\n" % (addr, ifnum) - else: - raise ValueError("invalid address: %s" % addr) + for ip4 in iface.ip4s: + cfg += "ip addr del %s dev %s\n" % (ip4.ip, iface.name) + if has_zebra == 0: + cfg += "ip addr add %s dev rtr%s\n" % (ip4.ip, ifnum) + for ip6 in iface.ip6s: + cfg += "ip -6 addr del %s dev %s\n" % (ip6.ip, iface.name) + if has_zebra == 0: + cfg += "ip -6 addr add %s dev rtr%s\n" % (ip6.ip, ifnum) # add interfaces to bridge # Make port numbers explicit so they're easier to follow in reading the script diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index a44037f6c..5efade1a2 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -74,8 +74,8 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: ifaces = node.get_ifaces() if ifaces: iface = ifaces[0] - for x in iface.addrlist: - net = netaddr.IPNetwork(x).cidr + for ip in iface.all_ips(): + net = ip.cidr if net.size > 1: router = net[1] routes.append(str(router)) @@ -118,23 +118,22 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.routestr, iface.addrlist)) + cfg += "\n".join(map(cls.routestr, iface.all_ips())) cfg += "\n" return cfg @staticmethod - def routestr(x: str) -> str: - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): + def routestr(ip: netaddr.IPNetwork) -> str: + address = str(ip.ip) + if netaddr.valid_ipv6(address): dst = "3ffe:4::/64" else: dst = "10.9.8.0/24" - net = netaddr.IPNetwork(x) - if net[-2] == net[1]: + if ip[-2] == ip[1]: return "" else: rtcmd = "#/sbin/ip route add %s via" % dst - return "%s %s" % (rtcmd, net[1]) + return "%s %s" % (rtcmd, ip[1]) class SshService(UtilService): @@ -242,25 +241,24 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: ddns-update-style none; """ for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.subnetentry, iface.addrlist)) + cfg += "\n".join(map(cls.subnetentry, iface.all_ips())) cfg += "\n" return cfg @staticmethod - def subnetentry(x: str) -> str: + def subnetentry(ip: netaddr.IPNetwork) -> str: """ Generate a subnet declaration block given an IPv4 prefix string for inclusion in the dhcpd3 config file. """ - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): + address = str(ip.ip) + if netaddr.valid_ipv6(address): return "" else: - net = netaddr.IPNetwork(x) # divide the address space in half - index = (net.size - 2) / 2 - rangelow = net[index] - rangehigh = net[-2] + index = (ip.size - 2) / 2 + rangelow = ip[index] + rangehigh = ip[-2] return """ subnet %s netmask %s { pool { @@ -270,11 +268,11 @@ def subnetentry(x: str) -> str: } } """ % ( - net.ip, - net.netmask, + ip.ip, + ip.netmask, rangelow, rangehigh, - addr, + address, ) @@ -557,7 +555,10 @@ def generatehtml(cls, node: CoreNode, filename: str) -> str: % node.name ) for iface in node.get_ifaces(control=False): - body += "
  • %s - %s
  • \n" % (iface.name, iface.addrlist) + body += "
  • %s - %s
  • \n" % ( + iface.name, + [str(x) for x in iface.all_ips()], + ) return "%s" % body @@ -625,7 +626,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: """ cfg = "# auto-generated by RADVD service (utility.py)\n" for iface in node.get_ifaces(control=False): - prefixes = list(map(cls.subnetentry, iface.addrlist)) + prefixes = list(map(cls.subnetentry, iface.all_ips())) if len(prefixes) < 1: continue cfg += ( @@ -658,14 +659,14 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: return cfg @staticmethod - def subnetentry(x: str) -> str: + def subnetentry(ip: netaddr.IPNetwork) -> str: """ Generate a subnet declaration block given an IPv6 prefix string for inclusion in the RADVD config file. """ - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): - return x + address = str(ip.ip) + if netaddr.valid_ipv6(address): + return str(ip) else: return "" diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 420823773..7c24478ad 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -40,7 +40,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: for iface in node.get_ifaces(): cfg += " interface %s {\n" % iface.name cfg += "\tvif %s {\n" % iface.name - cfg += "".join(map(cls.addrstr, iface.addrlist)) + cfg += "".join(map(cls.addrstr, iface.all_ips())) cfg += cls.lladdrstr(iface) cfg += "\t}\n" cfg += " }\n" @@ -55,13 +55,12 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: return cfg @staticmethod - def addrstr(x: str) -> str: + def addrstr(ip: netaddr.IPNetwork) -> str: """ helper for mapping IP addresses to XORP config statements """ - addr, plen = x.split("/") - cfg = "\t address %s {\n" % addr - cfg += "\t\tprefix-length: %s\n" % plen + cfg = "\t address %s {\n" % ip.ip + cfg += "\t\tprefix-length: %s\n" % ip.prefixlen cfg += "\t }\n" return cfg @@ -145,10 +144,9 @@ def router_id(node: CoreNode) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @classmethod @@ -180,11 +178,8 @@ def generate_xorp_config(cls, node: CoreNode) -> str: for iface in node.get_ifaces(control=False): cfg += "\t interface %s {\n" % iface.name cfg += "\t\tvif %s {\n" % iface.name - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += "\t\t address %s {\n" % addr + for ip4 in iface.ip4s: + cfg += "\t\t address %s {\n" % ip4.ip cfg += "\t\t }\n" cfg += "\t\t}\n" cfg += "\t }\n" @@ -269,11 +264,8 @@ def generate_xorp_config(cls, node: CoreNode) -> str: for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += "\t\taddress %s {\n" % addr + for ip4 in iface.ip4s: + cfg += "\t\taddress %s {\n" % ip4.ip cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" @@ -435,11 +427,8 @@ def generate_xorp_config(cls, node: CoreNode) -> str: for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += "\t\taddress %s {\n" % addr + for ip4 in iface.ip4s: + cfg += "\t\taddress %s {\n" % ip4.ip cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 7954b71a7..d84f2246f 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -164,6 +164,7 @@ def add_virtual_host(self, physical_host: etree.Element, node: NodeBase) -> None if emane_element is not None: parent_element = emane_element - for address in iface.addrlist: + for ip in iface.all_ips(): + address = str(ip.ip) address_type = get_address_type(address) add_address(parent_element, address_type, address, iface.name) diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 8af2e895c..1e89f5e43 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -87,7 +87,7 @@ def test_node_addaddr(self, session: Session): node.addaddr(iface.node_id, addr) # then - assert iface.addrlist[0] == addr + assert str(iface.get_ip4()) == addr def test_node_addaddr_exception(self, session): # given From 20feea8f12fe3abaf598d485f9c38395c0ab6299 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 10:54:58 -0700 Subject: [PATCH 042/210] daemon: refactored usages of addr to ip and updated functions to align --- .../configservices/utilservices/services.py | 4 +- daemon/core/emulator/session.py | 2 +- daemon/core/nodes/base.py | 43 ++++++----- daemon/core/nodes/interface.py | 56 ++++++++++----- daemon/core/nodes/network.py | 23 +++--- daemon/core/nodes/physical.py | 71 ++++++++++--------- daemon/core/services/frr.py | 2 +- daemon/core/services/quagga.py | 2 +- daemon/core/services/utility.py | 13 ++-- daemon/core/services/xorp.py | 2 +- daemon/core/xml/corexmldeployment.py | 2 +- daemon/tests/test_nodes.py | 14 ++-- 12 files changed, 127 insertions(+), 107 deletions(-) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 8013bc41c..b6bc0eb52 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -29,7 +29,7 @@ def data(self) -> Dict[str, Any]: ifaces = self.node.get_ifaces() if ifaces: iface = ifaces[0] - for ip in iface.all_ips(): + for ip in iface.ips(): net = ip.cidr if net.size > 1: router = net[1] @@ -76,7 +76,7 @@ class StaticRouteService(ConfigService): def data(self) -> Dict[str, Any]: routes = [] for iface in self.node.get_ifaces(control=False): - for ip in iface.all_ips(): + for ip in iface.ips(): address = str(ip.ip) if netaddr.valid_ipv6(address): dst = "3ffe:4::/64" diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index b0507269a..630e1a0f1 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1548,7 +1548,7 @@ def update_control_iface_hosts( entries = [] for iface in control_net.get_ifaces(): name = iface.node.name - for ip in iface.all_ips(): + for ip in iface.ips(): entries.append(f"{ip.ip} {name}") logging.info("Adding %d /etc/hosts file entries.", len(entries)) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 90be59af1..7eff9b12d 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union import netaddr @@ -138,6 +138,13 @@ def getposition(self) -> Tuple[float, float, float]: return self.position.get() def get_iface(self, iface_id: int) -> CoreInterface: + """ + Retrieve interface based on id. + + :param iface_id: id of interface to retrieve + :return: interface + :raises CoreError: when interface does not exist + """ if iface_id not in self.ifaces: raise CoreError(f"node({self.name}) does not have interface({iface_id})") return self.ifaces[iface_id] @@ -436,7 +443,6 @@ class CoreNode(CoreNodeBase): """ apitype: NodeTypes = NodeTypes.DEFAULT - valid_address_types: Set[str] = {"inet", "inet6", "inet6link"} def __init__( self, @@ -750,40 +756,39 @@ def set_mac(self, iface_id: int, mac: str) -> None: if self.up: self.node_net_client.device_mac(iface.name, mac) - def addaddr(self, iface_id: int, addr: str) -> None: + def add_ip(self, iface_id: int, ip: str) -> None: """ - Add interface address. + Add an ip address to an interface in the format "10.0.0.1/24". :param iface_id: id of interface to add address to - :param addr: address to add to interface + :param ip: address to add to interface :return: nothing + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_ip(addr) iface = self.get_iface(iface_id) - iface.addaddr(addr) + iface.add_ip(ip) if self.up: # ipv4 check broadcast = None - if netaddr.valid_ipv4(addr): + if netaddr.valid_ipv4(ip): broadcast = "+" - self.node_net_client.create_address(iface.name, addr, broadcast) + self.node_net_client.create_address(iface.name, ip, broadcast) - def deladdr(self, iface_id: int, addr: str) -> None: + def remove_ip(self, iface_id: int, ip: str) -> None: """ - Delete address from an interface. + Remove an ip address from an interface in the format "10.0.0.1/24". :param iface_id: id of interface to delete address from - :param addr: address to delete from interface + :param ip: ip address to remove from interface :return: nothing + :raises CoreError: when ip address provided is invalid :raises CoreCommandError: when a non-zero exit status occurs """ iface = self.get_iface(iface_id) - try: - iface.deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s", addr) + iface.remove_ip(ip) if self.up: - self.node_net_client.delete_address(iface.name, addr) + self.node_net_client.delete_address(iface.name, ip) def ifup(self, iface_id: int) -> None: """ @@ -819,14 +824,14 @@ def new_iface( iface = self.get_iface(iface_id) iface.set_mac(iface_data.mac) for address in addresses: - iface.addaddr(address) + iface.add_ip(address) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) for address in addresses: - self.addaddr(iface_id, address) + self.add_ip(iface_id, address) self.ifup(iface_id) iface = self.get_iface(iface_id) return iface diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index c1603a21e..c613f0cd9 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -134,46 +134,64 @@ def detachnet(self) -> None: if self.net is not None: self.net.detach(self) - def addaddr(self, address: str) -> None: + def add_ip(self, ip: str) -> None: """ Add ip address in the format "10.0.0.1/24". - :param address: address to add + :param ip: ip address to add :return: nothing + :raises CoreError: when ip address provided is invalid """ try: - ip = netaddr.IPNetwork(address) - value = str(ip.ip) - if netaddr.valid_ipv4(value): + ip = netaddr.IPNetwork(ip) + address = str(ip.ip) + if netaddr.valid_ipv4(address): self.ip4s.append(ip) else: self.ip6s.append(ip) except netaddr.AddrFormatError: - raise CoreError(f"adding invalid address {address}") + raise CoreError(f"adding invalid address {ip}") - def deladdr(self, addr: str) -> None: + def remove_ip(self, ip: str) -> None: """ - Delete address. + Remove ip address in the format "10.0.0.1/24". - :param addr: address to delete + :param ip: ip address to delete :return: nothing + :raises CoreError: when ip address provided is invalid """ - if netaddr.valid_ipv4(addr): - ip4 = netaddr.IPNetwork(addr) - self.ip4s.remove(ip4) - elif netaddr.valid_ipv6(addr): - ip6 = netaddr.IPNetwork(addr) - self.ip6s.remove(ip6) - else: - raise CoreError(f"deleting invalid address {addr}") + try: + ip = netaddr.IPNetwork(ip) + address = str(ip.ip) + if netaddr.valid_ipv4(address): + self.ip4s.remove(ip) + else: + self.ip6s.remove(ip) + except (netaddr.AddrFormatError, ValueError): + raise CoreError(f"deleting invalid address {ip}") def get_ip4(self) -> Optional[netaddr.IPNetwork]: + """ + Looks for the first ip4 address. + + :return: ip4 address, None otherwise + """ return next(iter(self.ip4s), None) def get_ip6(self) -> Optional[netaddr.IPNetwork]: + """ + Looks for the first ip6 address. + + :return: ip6 address, None otherwise + """ return next(iter(self.ip6s), None) - def all_ips(self) -> List[netaddr.IPNetwork]: + def ips(self) -> List[netaddr.IPNetwork]: + """ + Retrieve a list of all ip4 and ip6 addresses combined. + + :return: ip4 and ip6 addresses + """ return self.ip4s + self.ip6s def set_mac(self, mac: str) -> None: @@ -518,7 +536,7 @@ def setaddrs(self) -> None: :return: nothing """ self.waitfordevicenode() - for ip in self.all_ips(): + for ip in self.ips(): self.node.node_net_client.create_address(self.name, str(ip)) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 3f4ebfbaa..559b7ece8 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -575,18 +575,17 @@ def get_linked_iface(self, net: CoreNetworkBase) -> Optional[CoreInterface]: return iface return None - def addrconfig(self, addrlist: List[str]) -> None: + def add_ips(self, ips: List[str]) -> None: """ - Set addresses on the bridge. + Add ip addresses on the bridge in the format "10.0.0.1/24". - :param addrlist: address list + :param ips: ip address to add :return: nothing """ if not self.up: return - - for addr in addrlist: - self.net_client.create_address(self.brname, str(addr)) + for ip in ips: + self.net_client.create_address(self.brname, ip) class GreTapBridge(CoreNetwork): @@ -663,22 +662,22 @@ def shutdown(self) -> None: self.gretap = None super().shutdown() - def addrconfig(self, addrlist: List[str]) -> None: + def add_ips(self, ips: List[str]) -> None: """ Set the remote tunnel endpoint. This is a one-time method for creating the GreTap device, which requires the remoteip at startup. The 1st address in the provided list is remoteip, 2nd optionally specifies localip. - :param addrlist: address list + :param ips: address list :return: nothing """ if self.gretap: raise ValueError(f"gretap already exists for {self.name}") - remoteip = addrlist[0].split("/")[0] + remoteip = ips[0].split("/")[0] localip = None - if len(addrlist) > 1: - localip = addrlist[1].split("/")[0] + if len(ips) > 1: + localip = ips[1].split("/")[0] self.gretap = GreTap( session=self.session, remoteip=remoteip, @@ -700,7 +699,7 @@ def setkey(self, key: int, iface_data: InterfaceData) -> None: self.grekey = key addresses = iface_data.get_addresses() if addresses: - self.addrconfig(addresses) + self.add_ips(addresses) class CtrlNet(CoreNetwork): diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 0ce8946ae..96440bcbe 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -75,43 +75,43 @@ def set_mac(self, iface_id: int, mac: str) -> None: :raises CoreCommandError: when a non-zero exit status occurs """ mac = utils.validate_mac(mac) - iface = self.ifaces[iface_id] + iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: self.net_client.device_mac(iface.name, mac) - def addaddr(self, iface_id: int, addr: str) -> None: + def add_ip(self, iface_id: int, ip: str) -> None: """ - Add an address to an interface. + Add an ip address to an interface in the format "10.0.0.1/24". - :param iface_id: index of interface to add address to - :param addr: address to add + :param iface_id: id of interface to add address to + :param ip: address to add to interface :return: nothing + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_ip(addr) iface = self.get_iface(iface_id) + iface.add_ip(ip) if self.up: - self.net_client.create_address(iface.name, addr) - iface.addaddr(addr) + self.net_client.create_address(iface.name, ip) - def deladdr(self, iface_id: int, addr: str) -> None: + def remove_ip(self, iface_id: int, ip: str) -> None: """ - Delete an address from an interface. + Remove an ip address from an interface in the format "10.0.0.1/24". - :param iface_id: index of interface to delete - :param addr: address to delete + :param iface_id: id of interface to delete address from + :param ip: ip address to remove from interface :return: nothing + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - iface = self.ifaces[iface_id] - try: - iface.deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s", addr) + iface = self.get_iface(iface_id) + iface.remove_ip(ip) if self.up: - self.net_client.delete_address(iface.name, addr) + self.net_client.delete_address(iface.name, ip) def adopt_iface( - self, iface: CoreInterface, iface_id: int, mac: str, addrlist: List[str] + self, iface: CoreInterface, iface_id: int, mac: str, ips: List[str] ) -> None: """ When a link message is received linking this node to another part of @@ -128,8 +128,8 @@ def adopt_iface( iface.localname = iface.name if mac: self.set_mac(iface_id, mac) - for addr in addrlist: - self.addaddr(iface_id, addr) + for ip in ips: + self.add_ip(iface_id, ip) if self.up: self.net_client.device_up(iface.localname) @@ -317,7 +317,7 @@ def new_iface( if net is not None: self.iface.attachnet(net) for addr in iface_data.get_addresses(): - self.addaddr(addr) + self.add_ip(addr) return self.iface def delete_iface(self, iface_id: int) -> None: @@ -348,30 +348,31 @@ def get_iface_id(self, iface: CoreInterface) -> Optional[int]: raise CoreError(f"node({self.name}) does not have interface({iface.name})") return self.iface_id - def addaddr(self, addr: str) -> None: + def add_ip(self, ip: str) -> None: """ - Add address to to network interface. + Add an ip address to an interface in the format "10.0.0.1/24". - :param addr: address to add + :param ip: address to add to interface :return: nothing - :raises CoreCommandError: when there is a command exception + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_ip(addr) + self.iface.add_ip(ip) if self.up: - self.net_client.create_address(self.name, addr) - self.iface.addaddr(addr) + self.net_client.create_address(self.name, ip) - def deladdr(self, addr: str) -> None: + def remove_ip(self, ip: str) -> None: """ - Delete address from network interface. + Remove an ip address from an interface in the format "10.0.0.1/24". - :param addr: address to delete + :param ip: ip address to remove from interface :return: nothing - :raises CoreCommandError: when there is a command exception + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ + self.iface.remove_ip(ip) if self.up: - self.net_client.delete_address(self.name, addr) - self.iface.deladdr(addr) + self.net_client.delete_address(self.name, ip) def savestate(self) -> None: """ diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 6b9ada3c2..632b4557c 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -67,7 +67,7 @@ def generate_frr_conf(cls, node: CoreNode) -> str: # include control interfaces in addressing but not routing daemons if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) + cfg += "\n ".join(map(cls.addrstr, iface.ips())) cfg += "\n" continue cfgv4 = "" diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 7f717e595..cb9e6b08d 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -64,7 +64,7 @@ def generate_quagga_conf(cls, node: CoreNode) -> str: # include control interfaces in addressing but not routing daemons if getattr(iface, "control", False): cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) + cfg += "\n ".join(map(cls.addrstr, iface.ips())) cfg += "\n" continue cfgv4 = "" diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 5efade1a2..414f994e1 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -74,7 +74,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: ifaces = node.get_ifaces() if ifaces: iface = ifaces[0] - for ip in iface.all_ips(): + for ip in iface.ips(): net = ip.cidr if net.size > 1: router = net[1] @@ -118,7 +118,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.routestr, iface.all_ips())) + cfg += "\n".join(map(cls.routestr, iface.ips())) cfg += "\n" return cfg @@ -241,7 +241,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: ddns-update-style none; """ for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.subnetentry, iface.all_ips())) + cfg += "\n".join(map(cls.subnetentry, iface.ips())) cfg += "\n" return cfg @@ -555,10 +555,7 @@ def generatehtml(cls, node: CoreNode, filename: str) -> str: % node.name ) for iface in node.get_ifaces(control=False): - body += "
  • %s - %s
  • \n" % ( - iface.name, - [str(x) for x in iface.all_ips()], - ) + body += "
  • %s - %s
  • \n" % (iface.name, [str(x) for x in iface.ips()]) return "%s" % body @@ -626,7 +623,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: """ cfg = "# auto-generated by RADVD service (utility.py)\n" for iface in node.get_ifaces(control=False): - prefixes = list(map(cls.subnetentry, iface.all_ips())) + prefixes = list(map(cls.subnetentry, iface.ips())) if len(prefixes) < 1: continue cfg += ( diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 7c24478ad..a9687d45d 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -40,7 +40,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: for iface in node.get_ifaces(): cfg += " interface %s {\n" % iface.name cfg += "\tvif %s {\n" % iface.name - cfg += "".join(map(cls.addrstr, iface.all_ips())) + cfg += "".join(map(cls.addrstr, iface.ips())) cfg += cls.lladdrstr(iface) cfg += "\t}\n" cfg += " }\n" diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index d84f2246f..6035bd261 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -164,7 +164,7 @@ def add_virtual_host(self, physical_host: etree.Element, node: NodeBase) -> None if emane_element is not None: parent_element = emane_element - for ip in iface.all_ips(): + for ip in iface.ips(): address = str(ip.ip) address_type = get_address_type(address) add_address(parent_element, address_type, address, iface.name) diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 1e89f5e43..25a62c5f2 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -75,31 +75,31 @@ def test_node_set_mac_exception(self, session: Session): with pytest.raises(CoreError): node.set_mac(iface.node_id, mac) - def test_node_addaddr(self, session: Session): + def test_node_add_ip(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - addr = "192.168.0.1/24" + ip = "192.168.0.1/24" # when - node.addaddr(iface.node_id, addr) + node.add_ip(iface.node_id, ip) # then - assert str(iface.get_ip4()) == addr + assert str(iface.get_ip4()) == ip - def test_node_addaddr_exception(self, session): + def test_node_add_ip_exception(self, session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - addr = "256.168.0.1/24" + ip = "256.168.0.1/24" # when with pytest.raises(CoreError): - node.addaddr(iface.node_id, addr) + node.add_ip(iface.node_id, ip) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): From 9e4429fbbc0212b2810a4ff4892f88a300c7739e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 11:11:45 -0700 Subject: [PATCH 043/210] daemon: refactored InterfaceData.get_addresses to InterfaceData.get_ips --- daemon/core/emane/linkmonitor.py | 2 -- daemon/core/emulator/data.py | 12 ++++++------ daemon/core/nodes/base.py | 10 +++++----- daemon/core/nodes/network.py | 6 +++--- daemon/core/nodes/physical.py | 10 +++++----- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 295aaa1ea..56473f62c 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -213,11 +213,9 @@ def get_addresses(self) -> List[str]: for node in nodes: for iface in node.get_ifaces(): if isinstance(iface.net, CtrlNet): - address = None ip4 = iface.get_ip4() if ip4: address = str(ip4.ip) - if address: addresses.append(address) break return addresses diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 5b6479ae4..22d10d2df 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -142,18 +142,18 @@ class InterfaceData: ip6: str = None ip6_mask: int = None - def get_addresses(self) -> List[str]: + def get_ips(self) -> List[str]: """ Returns a list of ip4 and ip6 addresses when present. - :return: list of addresses + :return: list of ip addresses """ - addresses = [] + ips = [] if self.ip4 and self.ip4_mask: - addresses.append(f"{self.ip4}/{self.ip4_mask}") + ips.append(f"{self.ip4}/{self.ip4_mask}") if self.ip6 and self.ip6_mask: - addresses.append(f"{self.ip6}/{self.ip6_mask}") - return addresses + ips.append(f"{self.ip6}/{self.ip6_mask}") + return ips @dataclass diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 7eff9b12d..50f19a82c 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -811,7 +811,7 @@ def new_iface( :param iface_data: interface data for new interface :return: interface index """ - addresses = iface_data.get_addresses() + ips = iface_data.get_ips() with self.lock: # TODO: emane specific code if net.is_emane is True: @@ -823,15 +823,15 @@ def new_iface( self.attachnet(iface_id, net) iface = self.get_iface(iface_id) iface.set_mac(iface_data.mac) - for address in addresses: - iface.add_ip(address) + for ip in ips: + iface.add_ip(ip) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) - for address in addresses: - self.add_ip(iface_id, address) + for ip in ips: + self.add_ip(iface_id, ip) self.ifup(iface_id) iface = self.get_iface(iface_id) return iface diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 559b7ece8..5b95c3b2a 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -697,9 +697,9 @@ def setkey(self, key: int, iface_data: InterfaceData) -> None: :return: nothing """ self.grekey = key - addresses = iface_data.get_addresses() - if addresses: - self.add_ips(addresses) + ips = iface_data.get_ips() + if ips: + self.add_ips(ips) class CtrlNet(CoreNetwork): diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 96440bcbe..8fd828d86 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -156,7 +156,7 @@ def new_iface( self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: logging.info("creating interface") - addresses = iface_data.get_addresses() + ips = iface_data.get_ips() iface_id = iface_data.id if iface_id is None: iface_id = self.next_iface_id() @@ -167,12 +167,12 @@ def new_iface( # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adopt_iface(remote_tap, iface_id, iface_data.mac, addresses) + self.adopt_iface(remote_tap, iface_id, iface_data.mac, ips) return remote_tap else: # this is reached when configuring services (self.up=False) iface = GreTap(node=self, name=name, session=self.session, start=False) - self.adopt_iface(iface, iface_id, iface_data.mac, addresses) + self.adopt_iface(iface, iface_id, iface_data.mac, ips) return iface def privatedir(self, path: str) -> None: @@ -316,8 +316,8 @@ def new_iface( self.iface_id = iface_id if net is not None: self.iface.attachnet(net) - for addr in iface_data.get_addresses(): - self.add_ip(addr) + for ip in iface_data.get_ips(): + self.add_ip(ip) return self.iface def delete_iface(self, iface_id: int) -> None: From 19af9c3f51d7b088fcdaa2f4e7dd3ce670f99b0d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 11:18:39 -0700 Subject: [PATCH 044/210] daemon: added proper checks for FRRService calls --- daemon/core/services/frr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 632b4557c..13569772b 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -77,6 +77,8 @@ def generate_frr_conf(cls, node: CoreNode) -> str: for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, FrrService) or issubclass(s, FrrService)): + continue iface_config = s.generate_frr_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True @@ -101,6 +103,8 @@ def generate_frr_conf(cls, node: CoreNode) -> str: for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, FrrService) or issubclass(s, FrrService)): + continue cfg += s.generate_frr_config(node) return cfg From 88fe860f97dd519dbfbfca3f07bdcf20b16ccb58 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:25:47 -0700 Subject: [PATCH 045/210] fixed examples using IpPrefixes class --- daemon/examples/python/emane80211.py | 2 +- daemon/examples/python/switch.py | 2 +- daemon/examples/python/wlan.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index 9d6def4a7..48133ce0b 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -55,7 +55,7 @@ def main(): # get nodes to run example first_node = session.get_node(1, CoreNode) last_node = session.get_node(NODES, CoreNode) - address = prefixes.ip4_address(first_node) + address = prefixes.ip4_address(first_node.id) logging.info("node %s pinging %s", last_node.name, address) output = last_node.cmd(f"ping -c 3 {address}") logging.info(output) diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index f05176a30..c5e62e4ae 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -40,7 +40,7 @@ def main(): # get nodes to run example first_node = session.get_node(1, CoreNode) last_node = session.get_node(NODES, CoreNode) - address = prefixes.ip4_address(first_node) + address = prefixes.ip4_address(first_node.id) logging.info("node %s pinging %s", last_node.name, address) output = last_node.cmd(f"ping -c 3 {address}") logging.info(output) diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index de26ab975..7c16bad84 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -44,7 +44,7 @@ def main(): # get nodes for example run first_node = session.get_node(1, CoreNode) last_node = session.get_node(NODES, CoreNode) - address = prefixes.ip4_address(first_node) + address = prefixes.ip4_address(first_node.id) logging.info("node %s pinging %s", last_node.name, address) output = last_node.cmd(f"ping -c 3 {address}") logging.info(output) From df9216e0f0cba624137a19e64832a05fdef0fd47 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:28:11 -0700 Subject: [PATCH 046/210] updated scripting docs to use new naming and fixed out bad example --- docs/scripting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/scripting.md b/docs/scripting.md index f65d66a31..06ca483ac 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -61,8 +61,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_iface(node) - session.add_link(node.id, switch.id, iface1_data=interface) + iface_data = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -137,7 +137,7 @@ session = coreemu.create_session() # create node with custom services options = NodeOptions(services=["ServiceName"]) -node = session.add_node(options=options) +node = session.add_node(CoreNode, options=options) # set custom file data session.services.set_service_file(node.id, "ServiceName", "FileName", "custom file data") From cd6083aed95d2587923b039fcc426fdda506ed9d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:44:28 -0700 Subject: [PATCH 047/210] daemon: fixed issue not checking if an emane interface is a TunTap before using a specific function, fixed issue not looking for possible iface specific configuration for external --- daemon/core/emane/nodes.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 9173fbfc5..19d5a9e11 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -17,7 +17,7 @@ ) from core.errors import CoreError from core.nodes.base import CoreNetworkBase -from core.nodes.interface import CoreInterface +from core.nodes.interface import CoreInterface, TunTap if TYPE_CHECKING: from core.emane.emanemodel import EmaneModel @@ -151,18 +151,16 @@ def install_ifaces(self) -> None: warntxt = "unable to publish EMANE events because the eventservice " warntxt += "Python bindings failed to load" logging.error(warntxt) - for iface in self.get_ifaces(): - external = self.session.emane.get_config( - "external", self.id, self.model.name + config = self.session.emane.get_iface_config( + self.id, iface, self.model.name ) - if external == "0": + external = config["external"] + if isinstance(iface, TunTap) and external == "0": iface.setaddrs() - if not self.session.emane.genlocationevents(): iface.poshook = None continue - # at this point we register location handlers for generating # EMANE location events iface.poshook = self.setnemposition From f07176dd43aa06ad6724af5b82dda7b82c5f62f4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:51:11 -0700 Subject: [PATCH 048/210] daemon: provide safe fallback for emane install ifaces, in case external configuration does not exist --- daemon/core/emane/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 19d5a9e11..1186f9283 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -155,7 +155,7 @@ def install_ifaces(self) -> None: config = self.session.emane.get_iface_config( self.id, iface, self.model.name ) - external = config["external"] + external = config.get("external", "0") if isinstance(iface, TunTap) and external == "0": iface.setaddrs() if not self.session.emane.genlocationevents(): From cfda9509a2020b1cca86c2f7ba016a2ca0eced94 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:52:59 -0700 Subject: [PATCH 049/210] daemon: refactored TunTap setaddrs to set_ips to be more consistent with new naming --- daemon/core/emane/nodes.py | 2 +- daemon/core/nodes/interface.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 1186f9283..8cc9cd87a 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -157,7 +157,7 @@ def install_ifaces(self) -> None: ) external = config.get("external", "0") if isinstance(iface, TunTap) and external == "0": - iface.setaddrs() + iface.set_ips() if not self.session.emane.genlocationevents(): iface.poshook = None continue diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index c613f0cd9..d0e55c7ee 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -529,9 +529,9 @@ def install(self) -> None: self.node.node_net_client.device_name(self.localname, self.name) self.node.node_net_client.device_up(self.name) - def setaddrs(self) -> None: + def set_ips(self) -> None: """ - Set interface addresses. + Set interface ip addresses. :return: nothing """ From 1829a8e2f8dac16d3fd891f5d44194725c5cc1e0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:21:45 -0700 Subject: [PATCH 050/210] daemon: refactored CoreInterface.mac from a string to a netaddr.EUI object, providing more functionality --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/nodes/base.py | 3 +-- daemon/core/nodes/interface.py | 25 +++++++++++++++---------- daemon/core/nodes/network.py | 4 ++-- daemon/core/nodes/physical.py | 2 -- daemon/core/services/xorp.py | 4 ++-- daemon/core/xml/emanexml.py | 6 +++--- daemon/tests/test_nodes.py | 2 +- 8 files changed, 25 insertions(+), 23 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index adaf25491..b63cb8959 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -460,7 +460,7 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: id=iface.node_id, net_id=net_id, name=iface.name, - mac=iface.mac, + mac=str(iface.mac), mtu=iface.mtu, flow_id=iface.flow_id, ip4=ip4, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 50f19a82c..aae59b705 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -750,7 +750,6 @@ def set_mac(self, iface_id: int, mac: str) -> None: :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - mac = utils.validate_mac(mac) iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: @@ -1059,7 +1058,7 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: unidirectional = 1 iface2_data = InterfaceData( - id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.mac + id=linked_node.get_iface_id(iface), name=iface.name, mac=str(iface.mac) ) ip4 = iface.get_ip4() if ip4: diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index d0e55c7ee..22ecb6203 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -56,7 +56,7 @@ def __init__( self._params: Dict[str, float] = {} self.ip4s: List[netaddr.IPNetwork] = [] self.ip6s: List[netaddr.IPNetwork] = [] - self.mac: Optional[str] = None + self.mac: Optional[netaddr.EUI] = None # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE @@ -149,8 +149,8 @@ def add_ip(self, ip: str) -> None: self.ip4s.append(ip) else: self.ip6s.append(ip) - except netaddr.AddrFormatError: - raise CoreError(f"adding invalid address {ip}") + except netaddr.AddrFormatError as e: + raise CoreError(f"adding invalid address {ip}: {e}") def remove_ip(self, ip: str) -> None: """ @@ -167,8 +167,8 @@ def remove_ip(self, ip: str) -> None: self.ip4s.remove(ip) else: self.ip6s.remove(ip) - except (netaddr.AddrFormatError, ValueError): - raise CoreError(f"deleting invalid address {ip}") + except (netaddr.AddrFormatError, ValueError) as e: + raise CoreError(f"deleting invalid address {ip}: {e}") def get_ip4(self) -> Optional[netaddr.IPNetwork]: """ @@ -194,16 +194,21 @@ def ips(self) -> List[netaddr.IPNetwork]: """ return self.ip4s + self.ip6s - def set_mac(self, mac: str) -> None: + def set_mac(self, mac: Optional[str]) -> None: """ Set mac address. - :param mac: mac address to set + :param mac: mac address to set, None for random mac :return: nothing + :raises CoreError: when there is an invalid mac address """ - if mac is not None: - mac = utils.validate_mac(mac) - self.mac = mac + if mac is None: + self.mac = mac + else: + try: + self.mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) + except netaddr.AddrFormatError as e: + raise CoreError(f"invalid mac address({mac}): {e}") def getparam(self, key: str) -> float: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 5b95c3b2a..7d8f805eb 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -878,7 +878,7 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: unidirectional = 1 iface1_data = InterfaceData( - id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.mac + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=str(iface1.mac) ) ip4 = iface1.get_ip4() if ip4: @@ -890,7 +890,7 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: iface1_data.ip6_mask = ip6.prefixlen iface2_data = InterfaceData( - id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.mac + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=str(iface2.mac) ) ip4 = iface2.get_ip4() if ip4: diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 8fd828d86..3751d9eee 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -7,7 +7,6 @@ import threading from typing import IO, TYPE_CHECKING, List, Optional, Tuple -from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer @@ -74,7 +73,6 @@ def set_mac(self, iface_id: int, mac: str) -> None: :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - mac = utils.validate_mac(mac) iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index a9687d45d..485fe159f 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -69,7 +69,7 @@ def lladdrstr(iface: CoreInterface) -> str: """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % netaddr.EUI(iface.mac).eui64() + cfg = "\t address %s {\n" % iface.mac.eui64() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -292,7 +292,7 @@ def generate_xorp_config(cls, node: CoreNode) -> str: for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - cfg += "\t\taddress %s {\n" % netaddr.EUI(iface.mac).eui64() + cfg += "\t\taddress %s {\n" % iface.mac.eui64() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index d716777bc..eece57c99 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -230,9 +230,9 @@ def build_node_platform_xml( platform_element.append(nem_element) node.setnemid(iface, nem_id) - macstr = _MAC_PREFIX + ":00:00:" - macstr += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.set_mac(macstr) + mac = _MAC_PREFIX + ":00:00:" + mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" + iface.set_mac(mac) # increment nem id nem_id += 1 diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 25a62c5f2..a827fe258 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -61,7 +61,7 @@ def test_node_set_mac(self, session: Session): node.set_mac(iface.node_id, mac) # then - assert iface.mac == mac + assert str(iface.mac) == mac def test_node_set_mac_exception(self, session: Session): # given From 0d4a360e89319f568b9d10515da0f04781db1d0c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:32:17 -0700 Subject: [PATCH 051/210] daemon: removed utils.validate_ip and shifted tests to test_nodes --- daemon/core/utils.py | 14 -------------- daemon/tests/test_nodes.py | 17 ++++++++++++++--- daemon/tests/test_utils.py | 18 ------------------ 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 3b1ea46a3..4b9324851 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -444,17 +444,3 @@ def validate_mac(value: str) -> str: return str(mac) except netaddr.AddrFormatError as e: raise CoreError(f"invalid mac address {value}: {e}") - - -def validate_ip(value: str) -> str: - """ - Validate ip address with prefix and return formatted version. - - :param value: address to validate - :return: formatted ip address - """ - try: - ip = netaddr.IPNetwork(value) - return str(ip) - except (ValueError, netaddr.AddrFormatError) as e: - raise CoreError(f"invalid ip address {value}: {e}") diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index a827fe258..1741622e4 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -75,19 +75,30 @@ def test_node_set_mac_exception(self, session: Session): with pytest.raises(CoreError): node.set_mac(iface.node_id, mac) - def test_node_add_ip(self, session: Session): + @pytest.mark.parametrize( + "ip,expected,is_ip6", + [ + ("127", "127.0.0.0/32", False), + ("10.0.0.1/24", "10.0.0.1/24", False), + ("2001::", "2001::/128", True), + ("2001::/64", "2001::/64", True), + ], + ) + def test_node_add_ip(self, session: Session, ip: str, expected: str, is_ip6: bool): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - ip = "192.168.0.1/24" # when node.add_ip(iface.node_id, ip) # then - assert str(iface.get_ip4()) == ip + if is_ip6: + assert str(iface.get_ip6()) == expected + else: + assert str(iface.get_ip4()) == expected def test_node_add_ip_exception(self, session): # given diff --git a/daemon/tests/test_utils.py b/daemon/tests/test_utils.py index 3e43b7898..22bf0ee5c 100644 --- a/daemon/tests/test_utils.py +++ b/daemon/tests/test_utils.py @@ -25,24 +25,6 @@ def test_make_tuple_fromstr(self): assert len(two_args) == 2 assert len(unicode_args) == 3 - @pytest.mark.parametrize( - "data,expected", - [ - ("127", "127.0.0.0/32"), - ("10.0.0.1/24", "10.0.0.1/24"), - ("2001::", "2001::/128"), - ("2001::/64", "2001::/64"), - ], - ) - def test_validate_ip(self, data: str, expected: str): - value = utils.validate_ip(data) - assert value == expected - - @pytest.mark.parametrize("data", ["256", "1270.0.0.1", "127.0.0.0.1"]) - def test_validate_ip_exception(self, data: str): - with pytest.raises(CoreError): - utils.validate_ip("") - @pytest.mark.parametrize( "data,expected", [ From adfce5263232e89971173b864d6609ef99a1af64 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:41:41 -0700 Subject: [PATCH 052/210] daemon: removed utils.validate_mac and shifted tests to test_nodes --- daemon/core/utils.py | 16 +--------------- daemon/tests/test_nodes.py | 18 +++++++++++++----- daemon/tests/test_utils.py | 20 -------------------- 3 files changed, 14 insertions(+), 40 deletions(-) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 4b9324851..0e082187f 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -33,7 +33,7 @@ import netaddr -from core.errors import CoreCommandError, CoreError +from core.errors import CoreCommandError if TYPE_CHECKING: from core.emulator.session import Session @@ -430,17 +430,3 @@ def random_mac() -> str: value |= 0x00163E << 24 mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded) return str(mac) - - -def validate_mac(value: str) -> str: - """ - Validate mac and return unix formatted version. - - :param value: address to validate - :return: unix formatted mac - """ - try: - mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded) - return str(mac) - except netaddr.AddrFormatError as e: - raise CoreError(f"invalid mac address {value}: {e}") diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 1741622e4..8ed21f277 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -49,27 +49,35 @@ def test_node_delete(self, session: Session): with pytest.raises(CoreError): session.get_node(node.id, CoreNode) - def test_node_set_mac(self, session: Session): + @pytest.mark.parametrize( + "mac,expected", + [ + ("AA-AA-AA-FF-FF-FF", "aa:aa:aa:ff:ff:ff"), + ("00:00:00:FF:FF:FF", "00:00:00:ff:ff:ff"), + ], + ) + def test_node_set_mac(self, session: Session, mac: str, expected: str): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - mac = "aa:aa:aa:ff:ff:ff" # when node.set_mac(iface.node_id, mac) # then - assert str(iface.mac) == mac + assert str(iface.mac) == expected - def test_node_set_mac_exception(self, session: Session): + @pytest.mark.parametrize( + "mac", ["AAA:AA:AA:FF:FF:FF", "AA:AA:AA:FF:FF", "AA/AA/AA/FF/FF/FF"] + ) + def test_node_set_mac_exception(self, session: Session, mac: str): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - mac = "aa:aa:aa:ff:ff:fff" # when with pytest.raises(CoreError): diff --git a/daemon/tests/test_utils.py b/daemon/tests/test_utils.py index 22bf0ee5c..5a4f25a4d 100644 --- a/daemon/tests/test_utils.py +++ b/daemon/tests/test_utils.py @@ -1,8 +1,6 @@ import netaddr -import pytest from core import utils -from core.errors import CoreError class TestUtils: @@ -25,24 +23,6 @@ def test_make_tuple_fromstr(self): assert len(two_args) == 2 assert len(unicode_args) == 3 - @pytest.mark.parametrize( - "data,expected", - [ - ("AA-AA-AA-FF-FF-FF", "aa:aa:aa:ff:ff:ff"), - ("00:00:00:FF:FF:FF", "00:00:00:ff:ff:ff"), - ], - ) - def test_validate_mac(self, data: str, expected: str): - value = utils.validate_mac(data) - assert value == expected - - @pytest.mark.parametrize( - "data", ["AAA:AA:AA:FF:FF:FF", "AA:AA:AA:FF:FF", "AA/AA/AA/FF/FF/FF"] - ) - def test_validate_mac_exception(self, data: str): - with pytest.raises(CoreError): - utils.validate_mac(data) - def test_random_mac(self): value = utils.random_mac() assert netaddr.EUI(value) is not None From 0356f3b19c637a3a8f43fccc56e78418d34c63c0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 22:08:24 -0700 Subject: [PATCH 053/210] pygui: added type hinting to everything under base core.gui --- daemon/core/api/grpc/client.py | 4 +- daemon/core/gui/app.py | 32 ++--- daemon/core/gui/appconfig.py | 136 ++++++++++---------- daemon/core/gui/coreclient.py | 225 ++++++++++++++++++--------------- daemon/core/gui/graph/graph.py | 4 +- daemon/core/gui/images.py | 56 ++++---- daemon/core/gui/interface.py | 40 +++--- daemon/core/gui/menubar.py | 18 +-- daemon/core/gui/nodeutils.py | 60 +++++---- daemon/core/gui/observers.py | 10 +- daemon/core/gui/statusbar.py | 28 ++-- daemon/core/gui/task.py | 20 +-- daemon/core/gui/themes.py | 65 +++++----- daemon/core/gui/toolbar.py | 68 +++++----- daemon/core/gui/tooltip.py | 18 +-- daemon/core/gui/validation.py | 23 ++-- daemon/core/gui/widgets.py | 104 ++++++++------- 17 files changed, 477 insertions(+), 434 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index db908e050..5aa6713d2 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -436,7 +436,7 @@ def events( session_id: int, handler: Callable[[core_pb2.Event], None], events: List[core_pb2.Event] = None, - ) -> Any: + ) -> grpc.Channel: """ Listen for session events. @@ -453,7 +453,7 @@ def events( def throughputs( self, session_id: int, handler: Callable[[core_pb2.ThroughputsEvent], None] - ) -> Any: + ) -> grpc.Channel: """ Listen for throughput events with information for interfaces and bridges. diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index c795a46a1..cb385e9eb 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -3,10 +3,12 @@ import tkinter as tk from tkinter import PhotoImage, font, ttk from tkinter.ttk import Progressbar +from typing import Dict, Optional import grpc from core.gui import appconfig, themes +from core.gui.appconfig import GuiConfig from core.gui.coreclient import CoreClient from core.gui.dialogs.error import ErrorDialog from core.gui.graph.graph import CanvasGraph @@ -16,8 +18,8 @@ from core.gui.statusbar import StatusBar from core.gui.toolbar import Toolbar -WIDTH = 1000 -HEIGHT = 800 +WIDTH: int = 1000 +HEIGHT: int = 800 class Application(ttk.Frame): @@ -27,25 +29,25 @@ def __init__(self, proxy: bool) -> None: NodeUtils.setup() # widgets - self.menubar = None - self.toolbar = None - self.right_frame = None - self.canvas = None - self.statusbar = None - self.progress = None + self.menubar: Optional[Menubar] = None + self.toolbar: Optional[Toolbar] = None + self.right_frame: Optional[ttk.Frame] = None + self.canvas: Optional[CanvasGraph] = None + self.statusbar: Optional[StatusBar] = None + self.progress: Optional[Progressbar] = None # fonts - self.fonts_size = None - self.icon_text_font = None - self.edge_font = None + self.fonts_size: Dict[str, int] = {} + self.icon_text_font: Optional[font.Font] = None + self.edge_font: Optional[font.Font] = None # setup - self.guiconfig = appconfig.read() - self.app_scale = self.guiconfig.scale + self.guiconfig: GuiConfig = appconfig.read() + self.app_scale: float = self.guiconfig.scale self.setup_scaling() - self.style = ttk.Style() + self.style: ttk.Style = ttk.Style() self.setup_theme() - self.core = CoreClient(self, proxy) + self.core: CoreClient = CoreClient(self, proxy) self.setup_app() self.draw() self.core.setup() diff --git a/daemon/core/gui/appconfig.py b/daemon/core/gui/appconfig.py index 077f938d7..6bc213eb1 100644 --- a/daemon/core/gui/appconfig.py +++ b/daemon/core/gui/appconfig.py @@ -1,32 +1,32 @@ import os import shutil from pathlib import Path -from typing import List, Optional +from typing import Dict, List, Optional, Type import yaml from core.gui import themes -HOME_PATH = Path.home().joinpath(".coregui") -BACKGROUNDS_PATH = HOME_PATH.joinpath("backgrounds") -CUSTOM_EMANE_PATH = HOME_PATH.joinpath("custom_emane") -CUSTOM_SERVICE_PATH = HOME_PATH.joinpath("custom_services") -ICONS_PATH = HOME_PATH.joinpath("icons") -MOBILITY_PATH = HOME_PATH.joinpath("mobility") -XMLS_PATH = HOME_PATH.joinpath("xmls") -CONFIG_PATH = HOME_PATH.joinpath("config.yaml") -LOG_PATH = HOME_PATH.joinpath("gui.log") -SCRIPT_PATH = HOME_PATH.joinpath("scripts") +HOME_PATH: Path = Path.home().joinpath(".coregui") +BACKGROUNDS_PATH: Path = HOME_PATH.joinpath("backgrounds") +CUSTOM_EMANE_PATH: Path = HOME_PATH.joinpath("custom_emane") +CUSTOM_SERVICE_PATH: Path = HOME_PATH.joinpath("custom_services") +ICONS_PATH: Path = HOME_PATH.joinpath("icons") +MOBILITY_PATH: Path = HOME_PATH.joinpath("mobility") +XMLS_PATH: Path = HOME_PATH.joinpath("xmls") +CONFIG_PATH: Path = HOME_PATH.joinpath("config.yaml") +LOG_PATH: Path = HOME_PATH.joinpath("gui.log") +SCRIPT_PATH: Path = HOME_PATH.joinpath("scripts") # local paths -DATA_PATH = Path(__file__).parent.joinpath("data") -LOCAL_ICONS_PATH = DATA_PATH.joinpath("icons").absolute() -LOCAL_BACKGROUND_PATH = DATA_PATH.joinpath("backgrounds").absolute() -LOCAL_XMLS_PATH = DATA_PATH.joinpath("xmls").absolute() -LOCAL_MOBILITY_PATH = DATA_PATH.joinpath("mobility").absolute() +DATA_PATH: Path = Path(__file__).parent.joinpath("data") +LOCAL_ICONS_PATH: Path = DATA_PATH.joinpath("icons").absolute() +LOCAL_BACKGROUND_PATH: Path = DATA_PATH.joinpath("backgrounds").absolute() +LOCAL_XMLS_PATH: Path = DATA_PATH.joinpath("xmls").absolute() +LOCAL_MOBILITY_PATH: Path = DATA_PATH.joinpath("mobility").absolute() # configuration data -TERMINALS = { +TERMINALS: Dict[str, str] = { "xterm": "xterm -e", "aterm": "aterm -e", "eterm": "eterm -e", @@ -36,45 +36,45 @@ "xfce4-terminal": "xfce4-terminal -x", "gnome-terminal": "gnome-terminal --window --", } -EDITORS = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"] +EDITORS: List[str] = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"] class IndentDumper(yaml.Dumper): - def increase_indent(self, flow=False, indentless=False): - return super().increase_indent(flow, False) + def increase_indent(self, flow: bool = False, indentless: bool = False) -> None: + super().increase_indent(flow, False) class CustomNode(yaml.YAMLObject): - yaml_tag = "!CustomNode" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!CustomNode" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__(self, name: str, image: str, services: List[str]) -> None: - self.name = name - self.image = image - self.services = services + self.name: str = name + self.image: str = image + self.services: List[str] = services class CoreServer(yaml.YAMLObject): - yaml_tag = "!CoreServer" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!CoreServer" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__(self, name: str, address: str) -> None: - self.name = name - self.address = address + self.name: str = name + self.address: str = address class Observer(yaml.YAMLObject): - yaml_tag = "!Observer" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!Observer" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__(self, name: str, cmd: str) -> None: - self.name = name - self.cmd = cmd + self.name: str = name + self.cmd: str = cmd class PreferencesConfig(yaml.YAMLObject): - yaml_tag = "!PreferencesConfig" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!PreferencesConfig" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -85,17 +85,17 @@ def __init__( width: int = 1000, height: int = 750, ) -> None: - self.theme = theme - self.editor = editor - self.terminal = terminal - self.gui3d = gui3d - self.width = width - self.height = height + self.theme: str = theme + self.editor: str = editor + self.terminal: str = terminal + self.gui3d: str = gui3d + self.width: int = width + self.height: int = height class LocationConfig(yaml.YAMLObject): - yaml_tag = "!LocationConfig" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!LocationConfig" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -107,18 +107,18 @@ def __init__( alt: float = 2.0, scale: float = 150.0, ) -> None: - self.x = x - self.y = y - self.z = z - self.lat = lat - self.lon = lon - self.alt = alt - self.scale = scale + self.x: float = x + self.y: float = y + self.z: float = z + self.lat: float = lat + self.lon: float = lon + self.alt: float = alt + self.scale: float = scale class IpConfigs(yaml.YAMLObject): - yaml_tag = "!IpConfigs" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!IpConfigs" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -129,21 +129,21 @@ def __init__( ) -> None: if ip4s is None: ip4s = ["10.0.0.0", "192.168.0.0", "172.16.0.0"] - self.ip4s = ip4s + self.ip4s: List[str] = ip4s if ip6s is None: ip6s = ["2001::", "2002::", "a::"] - self.ip6s = ip6s + self.ip6s: List[str] = ip6s if ip4 is None: ip4 = self.ip4s[0] - self.ip4 = ip4 + self.ip4: str = ip4 if ip6 is None: ip6 = self.ip6s[0] - self.ip6 = ip6 + self.ip6: str = ip6 class GuiConfig(yaml.YAMLObject): - yaml_tag = "!GuiConfig" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!GuiConfig" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -159,30 +159,30 @@ def __init__( ) -> None: if preferences is None: preferences = PreferencesConfig() - self.preferences = preferences + self.preferences: PreferencesConfig = preferences if location is None: location = LocationConfig() - self.location = location + self.location: LocationConfig = location if servers is None: servers = [] - self.servers = servers + self.servers: List[CoreServer] = servers if nodes is None: nodes = [] - self.nodes = nodes + self.nodes: List[CustomNode] = nodes if recentfiles is None: recentfiles = [] - self.recentfiles = recentfiles + self.recentfiles: List[str] = recentfiles if observers is None: observers = [] - self.observers = observers - self.scale = scale + self.observers: List[Observer] = observers + self.scale: float = scale if ips is None: ips = IpConfigs() - self.ips = ips - self.mac = mac + self.ips: IpConfigs = ips + self.mac: str = mac -def copy_files(current_path, new_path) -> None: +def copy_files(current_path: Path, new_path: Path) -> None: for current_file in current_path.glob("*"): new_file = new_path.joinpath(current_file.name) shutil.copy(current_file, new_file) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8b0c423c2..247087690 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -4,18 +4,41 @@ import json import logging import os +import tkinter as tk from pathlib import Path from tkinter import messagebox -from typing import TYPE_CHECKING, Dict, Iterable, List, Optional +from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc -from core.api.grpc import client, common_pb2, configservices_pb2, core_pb2 +from core.api.grpc import client +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig +from core.api.grpc.core_pb2 import ( + Event, + ExceptionEvent, + Hook, + Interface, + Link, + LinkEvent, + LinkType, + MessageType, + Node, + NodeEvent, + NodeType, + Position, + SessionLocation, + SessionState, + StartSessionResponse, + StopSessionResponse, + ThroughputsEvent, +) from core.api.grpc.emane_pb2 import EmaneModelConfig from core.api.grpc.mobility_pb2 import MobilityConfig from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig from core.api.grpc.wlan_pb2 import WlanConfig from core.gui import appconfig +from core.gui.appconfig import CoreServer from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.mobilityplayer import MobilityPlayer @@ -34,47 +57,46 @@ class CoreClient: - def __init__(self, app: "Application", proxy: bool): + def __init__(self, app: "Application", proxy: bool) -> None: """ Create a CoreGrpc instance """ - self._client = client.CoreGrpcClient(proxy=proxy) - self.session_id = None - self.node_ids = [] - self.app = app - self.master = app.master - self.services = {} - self.config_services_groups = {} - self.config_services = {} - self.default_services = {} - self.emane_models = [] - self.observer = None + self.app: "Application" = app + self.master: tk.Tk = app.master + self._client: client.CoreGrpcClient = client.CoreGrpcClient(proxy=proxy) + self.session_id: Optional[int] = None + self.services: Dict[str, Set[str]] = {} + self.config_services_groups: Dict[str, Set[str]] = {} + self.config_services: Dict[str, ConfigService] = {} + self.default_services: Dict[NodeType, Set[str]] = {} + self.emane_models: List[str] = [] + self.observer: Optional[str] = None # loaded configuration data - self.servers = {} - self.custom_nodes = {} - self.custom_observers = {} + self.servers: Dict[str, CoreServer] = {} + self.custom_nodes: Dict[str, NodeDraw] = {} + self.custom_observers: Dict[str, str] = {} self.read_config() # helpers - self.iface_to_edge = {} - self.ifaces_manager = InterfaceManager(self.app) + self.iface_to_edge: Dict[Tuple[int, int], Tuple[int, int]] = {} + self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) # session data - self.state = None - self.canvas_nodes = {} - self.location = None - self.links = {} - self.hooks = {} - self.emane_config = None - self.mobility_players = {} - self.handling_throughputs = None - self.handling_events = None - self.xml_dir = None - self.xml_file = None + self.state: Optional[SessionState] = None + self.canvas_nodes: Dict[int, CanvasNode] = {} + self.location: Optional[SessionLocation] = None + self.links: Dict[Tuple[int, int], CanvasEdge] = {} + self.hooks: Dict[str, Hook] = {} + self.emane_config: Dict[str, ConfigOption] = {} + self.mobility_players: Dict[int, MobilityPlayer] = {} + self.handling_throughputs: Optional[grpc.Channel] = None + self.handling_events: Optional[grpc.Channel] = None + self.xml_dir: Optional[str] = None + self.xml_file: Optional[str] = None @property - def client(self): + def client(self) -> client.CoreGrpcClient: if self.session_id: response = self._client.check_session(self.session_id) if not response.result: @@ -89,7 +111,7 @@ def client(self): self.enable_throughputs() return self._client - def reset(self): + def reset(self) -> None: # helpers self.ifaces_manager.reset() self.iface_to_edge.clear() @@ -104,14 +126,14 @@ def reset(self): self.cancel_throughputs() self.cancel_events() - def close_mobility_players(self): + def close_mobility_players(self) -> None: for mobility_player in self.mobility_players.values(): mobility_player.close() - def set_observer(self, value: str): + def set_observer(self, value: Optional[str]) -> None: self.observer = value - def read_config(self): + def read_config(self) -> None: # read distributed servers for server in self.app.guiconfig.servers: self.servers[server.name] = server @@ -125,7 +147,7 @@ def read_config(self): for observer in self.app.guiconfig.observers: self.custom_observers[observer.name] = observer - def handle_events(self, event: core_pb2.Event): + def handle_events(self, event: Event) -> None: if event.session_id != self.session_id: logging.warning( "ignoring event session(%s) current(%s)", @@ -139,7 +161,7 @@ def handle_events(self, event: core_pb2.Event): elif event.HasField("session_event"): logging.info("session event: %s", event) session_event = event.session_event - if session_event.event <= core_pb2.SessionState.SHUTDOWN: + if session_event.event <= SessionState.SHUTDOWN: self.state = event.session_event.event elif session_event.event in {7, 8, 9}: node_id = session_event.node_id @@ -162,7 +184,7 @@ def handle_events(self, event: core_pb2.Event): else: logging.info("unhandled event: %s", event) - def handle_link_event(self, event: core_pb2.LinkEvent): + def handle_link_event(self, event: LinkEvent) -> None: logging.debug("Link event: %s", event) node1_id = event.link.node1_id node2_id = event.link.node2_id @@ -171,16 +193,16 @@ def handle_link_event(self, event: core_pb2.LinkEvent): return canvas_node1 = self.canvas_nodes[node1_id] canvas_node2 = self.canvas_nodes[node2_id] - if event.message_type == core_pb2.MessageType.ADD: + if event.message_type == MessageType.ADD: self.app.canvas.add_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == core_pb2.MessageType.DELETE: + elif event.message_type == MessageType.DELETE: self.app.canvas.delete_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == core_pb2.MessageType.NONE: + elif event.message_type == MessageType.NONE: self.app.canvas.update_wireless_edge(canvas_node1, canvas_node2, event.link) else: logging.warning("unknown link event: %s", event) - def handle_node_event(self, event: core_pb2.NodeEvent): + def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) if event.source == GUI_SOURCE: return @@ -190,22 +212,22 @@ def handle_node_event(self, event: core_pb2.NodeEvent): canvas_node = self.canvas_nodes[node_id] canvas_node.move(x, y) - def enable_throughputs(self): + def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( self.session_id, self.handle_throughputs ) - def cancel_throughputs(self): + def cancel_throughputs(self) -> None: if self.handling_throughputs: self.handling_throughputs.cancel() self.handling_throughputs = None - def cancel_events(self): + def cancel_events(self) -> None: if self.handling_events: self.handling_events.cancel() self.handling_events = None - def handle_throughputs(self, event: core_pb2.ThroughputsEvent): + def handle_throughputs(self, event: ThroughputsEvent) -> None: if event.session_id != self.session_id: logging.warning( "ignoring throughput event session(%s) current(%s)", @@ -216,11 +238,11 @@ def handle_throughputs(self, event: core_pb2.ThroughputsEvent): logging.debug("handling throughputs event: %s", event) self.app.after(0, self.app.canvas.set_throughputs, event) - def handle_exception_event(self, event: core_pb2.ExceptionEvent): + def handle_exception_event(self, event: ExceptionEvent) -> None: logging.info("exception event: %s", event) self.app.statusbar.core_alarms.append(event) - def join_session(self, session_id: int, query_location: bool = True): + def join_session(self, session_id: int, query_location: bool = True) -> None: logging.info("join session(%s)", session_id) # update session and title self.session_id = session_id @@ -331,9 +353,9 @@ def join_session(self, session_id: int, query_location: bool = True): self.app.after(0, self.app.joined_session_update) def is_runtime(self) -> bool: - return self.state == core_pb2.SessionState.RUNTIME + return self.state == SessionState.RUNTIME - def parse_metadata(self, config: Dict[str, str]): + def parse_metadata(self, config: Dict[str, str]) -> None: # canvas setting canvas_config = config.get("canvas") logging.debug("canvas metadata: %s", canvas_config) @@ -386,7 +408,7 @@ def parse_metadata(self, config: Dict[str, str]): except ValueError: logging.exception("unknown shape: %s", shape_type) - def create_new_session(self): + def create_new_session(self) -> None: """ Create a new session """ @@ -394,7 +416,7 @@ def create_new_session(self): response = self.client.create_session() logging.info("created session: %s", response) location_config = self.app.guiconfig.location - self.location = core_pb2.SessionLocation( + self.location = SessionLocation( x=location_config.x, y=location_config.y, z=location_config.z, @@ -407,7 +429,7 @@ def create_new_session(self): except grpc.RpcError as e: self.app.show_grpc_exception("New Session Error", e) - def delete_session(self, session_id: int = None): + def delete_session(self, session_id: int = None) -> None: if session_id is None: session_id = self.session_id try: @@ -416,7 +438,7 @@ def delete_session(self, session_id: int = None): except grpc.RpcError as e: self.app.show_grpc_exception("Delete Session Error", e) - def setup(self): + def setup(self) -> None: """ Query sessions, if there exist any, prompt whether to join one """ @@ -451,7 +473,7 @@ def setup(self): dialog.show() self.app.close() - def edit_node(self, core_node: core_pb2.Node): + def edit_node(self, core_node: Node) -> None: try: self.client.edit_node( self.session_id, core_node.id, core_node.position, source=GUI_SOURCE @@ -459,12 +481,12 @@ def edit_node(self, core_node: core_pb2.Node): except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) - def start_session(self) -> core_pb2.StartSessionResponse: + def start_session(self) -> StartSessionResponse: self.ifaces_manager.reset_mac() nodes = [x.core_node for x in self.canvas_nodes.values()] links = [] for edge in self.links.values(): - link = core_pb2.Link() + link = Link() link.CopyFrom(edge.link) if link.HasField("iface1") and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() @@ -485,7 +507,7 @@ def start_session(self) -> core_pb2.StartSessionResponse: emane_config = {x: self.emane_config[x].value for x in self.emane_config} else: emane_config = None - response = core_pb2.StartSessionResponse(result=False) + response = StartSessionResponse(result=False) try: response = self.client.start_session( self.session_id, @@ -511,10 +533,10 @@ def start_session(self) -> core_pb2.StartSessionResponse: self.app.show_grpc_exception("Start Session Error", e) return response - def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse: + def stop_session(self, session_id: int = None) -> StopSessionResponse: if not session_id: session_id = self.session_id - response = core_pb2.StopSessionResponse(result=False) + response = StopSessionResponse(result=False) try: response = self.client.stop_session(session_id) logging.info("stopped session(%s), result: %s", session_id, response) @@ -522,9 +544,9 @@ def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse: self.app.show_grpc_exception("Stop Session Error", e) return response - def show_mobility_players(self): + def show_mobility_players(self) -> None: for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + if canvas_node.core_node.type != NodeType.WIRELESS_LAN: continue if canvas_node.mobility_config: mobility_player = MobilityPlayer( @@ -534,7 +556,7 @@ def show_mobility_players(self): self.mobility_players[node_id] = mobility_player mobility_player.show() - def set_metadata(self): + def set_metadata(self) -> None: # create canvas data wallpaper = None if self.app.canvas.wallpaper_file: @@ -558,7 +580,7 @@ def set_metadata(self): response = self.client.set_session_metadata(self.session_id, metadata) logging.info("set session metadata %s, result: %s", metadata, response) - def launch_terminal(self, node_id: int): + def launch_terminal(self, node_id: int) -> None: try: terminal = self.app.guiconfig.preferences.terminal if not terminal: @@ -575,12 +597,12 @@ def launch_terminal(self, node_id: int): except grpc.RpcError as e: self.app.show_grpc_exception("Node Terminal Error", e) - def save_xml(self, file_path: str): + def save_xml(self, file_path: str) -> None: """ Save core session as to an xml file """ try: - if self.state != core_pb2.SessionState.RUNTIME: + if self.state != SessionState.RUNTIME: logging.debug("Send session data to the daemon") self.send_data() response = self.client.save_xml(self.session_id, file_path) @@ -588,7 +610,7 @@ def save_xml(self, file_path: str): except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) - def open_xml(self, file_path: str): + def open_xml(self, file_path: str) -> None: """ Open core xml """ @@ -627,7 +649,8 @@ def set_node_service( shutdown=shutdowns, ) logging.info( - "Set %s service for node(%s), files: %s, Startup: %s, Validation: %s, Shutdown: %s, Result: %s", + "Set %s service for node(%s), files: %s, Startup: %s, " + "Validation: %s, Shutdown: %s, Result: %s", service_name, node_id, files, @@ -656,7 +679,7 @@ def get_node_service_file( def set_node_service_file( self, node_id: int, service_name: str, file_name: str, data: str - ): + ) -> None: response = self.client.set_node_service_file( self.session_id, node_id, service_name, file_name, data ) @@ -669,18 +692,16 @@ def set_node_service_file( response, ) - def create_nodes_and_links(self): + def create_nodes_and_links(self) -> None: """ create nodes and links that have not been created yet """ node_protos = [x.core_node for x in self.canvas_nodes.values()] link_protos = [x.link for x in self.links.values()] - if self.state != core_pb2.SessionState.DEFINITION: - self.client.set_session_state( - self.session_id, core_pb2.SessionState.DEFINITION - ) + if self.state != SessionState.DEFINITION: + self.client.set_session_state(self.session_id, SessionState.DEFINITION) - self.client.set_session_state(self.session_id, core_pb2.SessionState.DEFINITION) + self.client.set_session_state(self.session_id, SessionState.DEFINITION) for node_proto in node_protos: response = self.client.add_node(self.session_id, node_proto) logging.debug("create node: %s", response) @@ -695,7 +716,7 @@ def create_nodes_and_links(self): ) logging.debug("create link: %s", response) - def send_data(self): + def send_data(self) -> None: """ send to daemon all session info, but don't start the session """ @@ -738,10 +759,9 @@ def send_data(self): if self.emane_config: config = {x: self.emane_config[x].value for x in self.emane_config} self.client.set_emane_config(self.session_id, config) - self.set_metadata() - def close(self): + def close(self) -> None: """ Clean ups when done using grpc """ @@ -760,31 +780,31 @@ def next_node_id(self) -> int: return i def create_node( - self, x: float, y: float, node_type: core_pb2.NodeType, model: str - ) -> Optional[core_pb2.Node]: + self, x: float, y: float, node_type: NodeType, model: str + ) -> Optional[Node]: """ Add node, with information filled in, to grpc manager """ node_id = self.next_node_id() - position = core_pb2.Position(x=x, y=y) + position = Position(x=x, y=y) image = None if NodeUtils.is_image_node(node_type): image = "ubuntu:latest" emane = None - if node_type == core_pb2.NodeType.EMANE: + if node_type == NodeType.EMANE: if not self.emane_models: dialog = EmaneInstallDialog(self.app) dialog.show() return emane = self.emane_models[0] name = f"EMANE{node_id}" - elif node_type == core_pb2.NodeType.WIRELESS_LAN: + elif node_type == NodeType.WIRELESS_LAN: name = f"WLAN{node_id}" - elif node_type in [core_pb2.NodeType.RJ45, core_pb2.NodeType.TUNNEL]: + elif node_type in [NodeType.RJ45, NodeType.TUNNEL]: name = "UNASSIGNED" else: name = f"n{node_id}" - node = core_pb2.Node( + node = Node( id=node_id, type=node_type, name=name, @@ -810,7 +830,7 @@ def create_node( ) return node - def deleted_graph_nodes(self, canvas_nodes: List[core_pb2.Node]): + def deleted_graph_nodes(self, canvas_nodes: List[Node]) -> None: """ remove the nodes selected by the user and anything related to that node such as link, configurations, interfaces @@ -826,14 +846,14 @@ def deleted_graph_edges(self, edges: Iterable[CanvasEdge]) -> None: links.append(edge.link) self.ifaces_manager.removed(links) - def create_iface(self, canvas_node: CanvasNode) -> core_pb2.Interface: + def create_iface(self, canvas_node: CanvasNode) -> Interface: node = canvas_node.core_node ip4, ip6 = self.ifaces_manager.get_ips(node) ip4_mask = self.ifaces_manager.ip4_mask ip6_mask = self.ifaces_manager.ip6_mask iface_id = canvas_node.next_iface_id() name = f"eth{iface_id}" - iface = core_pb2.Interface( + iface = Interface( id=iface_id, name=name, ip4=ip4, @@ -852,7 +872,7 @@ def create_iface(self, canvas_node: CanvasNode) -> core_pb2.Interface: def create_link( self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode - ): + ) -> None: """ Create core link for a pair of canvas nodes, with token referencing the canvas edge. @@ -873,8 +893,8 @@ def create_link( dst_iface = self.create_iface(canvas_dst_node) self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token - link = core_pb2.Link( - type=core_pb2.LinkType.WIRED, + link = Link( + type=LinkType.WIRED, node1_id=src_node.id, node2_id=dst_node.id, iface1=src_iface, @@ -896,7 +916,7 @@ def create_link( def get_wlan_configs_proto(self) -> List[WlanConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + if canvas_node.core_node.type != NodeType.WIRELESS_LAN: continue if not canvas_node.wlan_config: continue @@ -910,7 +930,7 @@ def get_wlan_configs_proto(self) -> List[WlanConfig]: def get_mobility_configs_proto(self) -> List[MobilityConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + if canvas_node.core_node.type != NodeType.WIRELESS_LAN: continue if not canvas_node.mobility_config: continue @@ -924,7 +944,7 @@ def get_mobility_configs_proto(self) -> List[MobilityConfig]: def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.EMANE: + if canvas_node.core_node.type != NodeType.EMANE: continue node_id = canvas_node.core_node.id for key, config in canvas_node.emane_model_configs.items(): @@ -975,9 +995,7 @@ def get_service_file_configs_proto(self) -> List[ServiceFileConfig]: configs.append(config_proto) return configs - def get_config_service_configs_proto( - self - ) -> List[configservices_pb2.ConfigServiceConfig]: + def get_config_service_configs_proto(self) -> List[ConfigServiceConfig]: config_service_protos = [] for canvas_node in self.canvas_nodes.values(): if not NodeUtils.is_container_node(canvas_node.core_node.type): @@ -987,7 +1005,7 @@ def get_config_service_configs_proto( node_id = canvas_node.core_node.id for name, service_config in canvas_node.config_service_configs.items(): config = service_config.get("config", {}) - config_proto = configservices_pb2.ConfigServiceConfig( + config_proto = ConfigServiceConfig( node_id=node_id, name=name, templates=service_config["templates"], @@ -1000,7 +1018,7 @@ def run(self, node_id: int) -> str: logging.info("running node(%s) cmd: %s", node_id, self.observer) return self.client.node_command(self.session_id, node_id, self.observer).output - def get_wlan_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: + def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]: response = self.client.get_wlan_config(self.session_id, node_id) config = response.config logging.debug( @@ -1010,7 +1028,7 @@ def get_wlan_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: ) return dict(config) - def get_mobility_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: + def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: response = self.client.get_mobility_config(self.session_id, node_id) config = response.config logging.debug( @@ -1022,7 +1040,7 @@ def get_mobility_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption def get_emane_model_config( self, node_id: int, model: str, iface_id: int = None - ) -> Dict[str, common_pb2.ConfigOption]: + ) -> Dict[str, ConfigOption]: if iface_id is None: iface_id = -1 response = self.client.get_emane_model_config( @@ -1030,7 +1048,8 @@ def get_emane_model_config( ) config = response.config logging.debug( - "get emane model config: node id: %s, EMANE model: %s, interface: %s, config: %s", + "get emane model config: node id: %s, EMANE model: %s, " + "interface: %s, config: %s", node_id, model, iface_id, @@ -1038,7 +1057,7 @@ def get_emane_model_config( ) return dict(config) - def execute_script(self, script): + def execute_script(self, script) -> None: response = self.client.execute_script(script) logging.info("execute python script %s", response) if response.session_id != -1: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 269e3973f..834220ead 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -2,7 +2,7 @@ import tkinter as tk from copy import deepcopy from tkinter import BooleanVar -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING, Optional, Tuple from PIL import Image, ImageTk @@ -864,7 +864,7 @@ def organize(self) -> None: for tag in tags.ORGANIZE_TAGS: self.tag_raise(tag) - def set_wallpaper(self, filename: str): + def set_wallpaper(self, filename: Optional[str]): logging.debug("setting wallpaper: %s", filename) if filename: img = Image.open(filename) diff --git a/daemon/core/gui/images.py b/daemon/core/gui/images.py index 3a9530544..227194578 100644 --- a/daemon/core/gui/images.py +++ b/daemon/core/gui/images.py @@ -1,46 +1,44 @@ from enum import Enum from tkinter import messagebox +from typing import Dict, Optional, Tuple -from PIL import Image, ImageTk +from PIL import Image +from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import NodeType from core.gui.appconfig import LOCAL_ICONS_PATH class Images: - images = {} + images: Dict[str, str] = {} @classmethod - def create(cls, file_path: str, width: int, height: int = None): + def create(cls, file_path: str, width: int, height: int = None) -> PhotoImage: if height is None: height = width image = Image.open(file_path) image = image.resize((width, height), Image.ANTIALIAS) - return ImageTk.PhotoImage(image) + return PhotoImage(image) @classmethod - def load_all(cls): + def load_all(cls) -> None: for image in LOCAL_ICONS_PATH.glob("*"): cls.images[image.stem] = str(image) @classmethod - def get( - cls, image_enum: Enum, width: int, height: int = None - ) -> ImageTk.PhotoImage: + def get(cls, image_enum: Enum, width: int, height: int = None) -> PhotoImage: file_path = cls.images[image_enum.value] return cls.create(file_path, width, height) @classmethod def get_with_image_file( cls, stem: str, width: int, height: int = None - ) -> ImageTk.PhotoImage: + ) -> PhotoImage: file_path = cls.images[stem] return cls.create(file_path, width, height) @classmethod - def get_custom( - cls, name: str, width: int, height: int = None - ) -> ImageTk.PhotoImage: + def get_custom(cls, name: str, width: int, height: int = None) -> PhotoImage: try: file_path = cls.images[name] return cls.create(file_path, width, height) @@ -95,22 +93,22 @@ class ImageEnum(Enum): class TypeToImage: - type_to_image = { - (core_pb2.NodeType.DEFAULT, "router"): ImageEnum.ROUTER, - (core_pb2.NodeType.DEFAULT, "PC"): ImageEnum.PC, - (core_pb2.NodeType.DEFAULT, "host"): ImageEnum.HOST, - (core_pb2.NodeType.DEFAULT, "mdr"): ImageEnum.MDR, - (core_pb2.NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER, - (core_pb2.NodeType.HUB, ""): ImageEnum.HUB, - (core_pb2.NodeType.SWITCH, ""): ImageEnum.SWITCH, - (core_pb2.NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN, - (core_pb2.NodeType.EMANE, ""): ImageEnum.EMANE, - (core_pb2.NodeType.RJ45, ""): ImageEnum.RJ45, - (core_pb2.NodeType.TUNNEL, ""): ImageEnum.TUNNEL, - (core_pb2.NodeType.DOCKER, ""): ImageEnum.DOCKER, - (core_pb2.NodeType.LXC, ""): ImageEnum.LXC, + type_to_image: Dict[Tuple[NodeType, str], ImageEnum] = { + (NodeType.DEFAULT, "router"): ImageEnum.ROUTER, + (NodeType.DEFAULT, "PC"): ImageEnum.PC, + (NodeType.DEFAULT, "host"): ImageEnum.HOST, + (NodeType.DEFAULT, "mdr"): ImageEnum.MDR, + (NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER, + (NodeType.HUB, ""): ImageEnum.HUB, + (NodeType.SWITCH, ""): ImageEnum.SWITCH, + (NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN, + (NodeType.EMANE, ""): ImageEnum.EMANE, + (NodeType.RJ45, ""): ImageEnum.RJ45, + (NodeType.TUNNEL, ""): ImageEnum.TUNNEL, + (NodeType.DOCKER, ""): ImageEnum.DOCKER, + (NodeType.LXC, ""): ImageEnum.LXC, } @classmethod - def get(cls, node_type, model): - return cls.type_to_image.get((node_type, model), None) + def get(cls, node_type, model) -> Optional[ImageEnum]: + return cls.type_to_image.get((node_type, model)) diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 6c82ca51f..f4f2e3cc8 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -1,18 +1,18 @@ import logging -from typing import TYPE_CHECKING, Any, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import netaddr from netaddr import EUI, IPNetwork +from core.api.grpc.core_pb2 import Interface, Link, Node +from core.gui.graph.node import CanvasNode from core.gui.nodeutils import NodeUtils if TYPE_CHECKING: from core.gui.app import Application - from core.api.grpc import core_pb2 - from core.gui.graph.node import CanvasNode -def get_index(iface: "core_pb2.Interface") -> Optional[int]: +def get_index(iface: Interface) -> Optional[int]: if not iface.ip4: return None net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4_mask}") @@ -44,18 +44,18 @@ def next(self) -> "Subnets": class InterfaceManager: def __init__(self, app: "Application") -> None: - self.app = app + self.app: "Application" = app ip4 = self.app.guiconfig.ips.ip4 ip6 = self.app.guiconfig.ips.ip6 - self.ip4_mask = 24 - self.ip6_mask = 64 - self.ip4_subnets = IPNetwork(f"{ip4}/{self.ip4_mask}") - self.ip6_subnets = IPNetwork(f"{ip6}/{self.ip6_mask}") + self.ip4_mask: int = 24 + self.ip6_mask: int = 64 + self.ip4_subnets: IPNetwork = IPNetwork(f"{ip4}/{self.ip4_mask}") + self.ip6_subnets: IPNetwork = IPNetwork(f"{ip6}/{self.ip6_mask}") mac = self.app.guiconfig.mac - self.mac = EUI(mac, dialect=netaddr.mac_unix_expanded) - self.current_mac = None - self.current_subnets = None - self.used_subnets = {} + self.mac: EUI = EUI(mac, dialect=netaddr.mac_unix_expanded) + self.current_mac: Optional[EUI] = None + self.current_subnets: Optional[Subnets] = None + self.used_subnets: Dict[Tuple[IPNetwork, IPNetwork], Subnets] = {} def update_ips(self, ip4: str, ip6: str) -> None: self.reset() @@ -84,7 +84,7 @@ def reset(self) -> None: self.current_subnets = None self.used_subnets.clear() - def removed(self, links: List["core_pb2.Link"]) -> None: + def removed(self, links: List[Link]) -> None: # get remaining subnets remaining_subnets = set() for edge in self.app.core.links.values(): @@ -114,7 +114,7 @@ def removed(self, links: List["core_pb2.Link"]) -> None: subnets.used_indexes.discard(index) self.current_subnets = None - def joined(self, links: List["core_pb2.Link"]) -> None: + def joined(self, links: List[Link]) -> None: ifaces = [] for link in links: if link.HasField("iface1"): @@ -132,7 +132,7 @@ def joined(self, links: List["core_pb2.Link"]) -> None: if subnets.key() not in self.used_subnets: self.used_subnets[subnets.key()] = subnets - def next_index(self, node: "core_pb2.Node") -> int: + def next_index(self, node: Node) -> int: if NodeUtils.is_router_node(node): index = 1 else: @@ -144,13 +144,13 @@ def next_index(self, node: "core_pb2.Node") -> int: index += 1 return index - def get_ips(self, node: "core_pb2.Node") -> [str, str]: + def get_ips(self, node: Node) -> [str, str]: index = self.next_index(node) ip4 = self.current_subnets.ip4[index] ip6 = self.current_subnets.ip6[index] return str(ip4), str(ip6) - def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: + def get_subnets(self, iface: Interface) -> Subnets: ip4_subnet = self.ip4_subnets if iface.ip4: ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4_mask}").cidr @@ -161,7 +161,7 @@ def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: return self.used_subnets.get(subnets.key(), subnets) def determine_subnets( - self, canvas_src_node: "CanvasNode", canvas_dst_node: "CanvasNode" + self, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode ) -> None: src_node = canvas_src_node.core_node dst_node = canvas_dst_node.core_node @@ -185,7 +185,7 @@ def determine_subnets( logging.info("ignoring subnet change for link between network nodes") def find_subnets( - self, canvas_node: "CanvasNode", visited: Set[int] = None + self, canvas_node: CanvasNode, visited: Set[int] = None ) -> Optional[IPNetwork]: logging.info("finding subnet for node: %s", canvas_node.core_node.name) canvas = self.app.canvas diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index cf4216d8d..523f8f110 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -4,9 +4,10 @@ import webbrowser from functools import partial from tkinter import filedialog, messagebox -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import XMLS_PATH +from core.gui.coreclient import CoreClient from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog from core.gui.dialogs.canvaswallpaper import CanvasWallpaperDialog @@ -22,6 +23,7 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.throughput import ThroughputDialog +from core.gui.graph.graph import CanvasGraph from core.gui.nodeutils import ICON_SIZE from core.gui.observers import ObserversMenu from core.gui.task import ProgressTask @@ -29,7 +31,7 @@ if TYPE_CHECKING: from core.gui.app import Application -MAX_FILES = 3 +MAX_FILES: int = 3 class Menubar(tk.Menu): @@ -42,12 +44,12 @@ def __init__(self, app: "Application") -> None: Create a CoreMenubar instance """ super().__init__(app) - self.app = app - self.core = app.core - self.canvas = app.canvas - self.recent_menu = None - self.edit_menu = None - self.observers_menu = None + self.app: "Application" = app + self.core: CoreClient = app.core + self.canvas: CanvasGraph = app.canvas + self.recent_menu: Optional[tk.Menu] = None + self.edit_menu: Optional[tk.Menu] = None + self.observers_menu: Optional[tk.Menu] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 402046621..402eca4da 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -1,38 +1,36 @@ import logging -from typing import TYPE_CHECKING, List, Optional, Set +from typing import List, Optional, Set + +from PIL.ImageTk import PhotoImage from core.api.grpc.core_pb2 import Node, NodeType from core.gui.appconfig import CustomNode, GuiConfig from core.gui.images import ImageEnum, Images, TypeToImage -if TYPE_CHECKING: - from core.api.grpc import core_pb2 - from PIL import ImageTk - -ICON_SIZE = 48 -ANTENNA_SIZE = 32 +ICON_SIZE: int = 48 +ANTENNA_SIZE: int = 32 class NodeDraw: - def __init__(self): + def __init__(self) -> None: self.custom: bool = False - self.image = None + self.image: Optional[str] = None self.image_enum: Optional[ImageEnum] = None - self.image_file = None - self.node_type: core_pb2.NodeType = None + self.image_file: Optional[str] = None + self.node_type: NodeType = None self.model: Optional[str] = None self.services: Set[str] = set() - self.label = None + self.label: Optional[str] = None @classmethod def from_setup( cls, image_enum: ImageEnum, - node_type: "core_pb2.NodeType", + node_type: NodeType, label: str, model: str = None, - tooltip=None, - ): + tooltip: str = None, + ) -> "NodeDraw": node_draw = NodeDraw() node_draw.image_enum = image_enum node_draw.image = Images.get(image_enum, ICON_SIZE) @@ -43,7 +41,7 @@ def from_setup( return node_draw @classmethod - def from_custom(cls, custom_node: CustomNode): + def from_custom(cls, custom_node: CustomNode) -> "NodeDraw": node_draw = NodeDraw() node_draw.custom = True node_draw.image_file = custom_node.image @@ -57,17 +55,17 @@ def from_custom(cls, custom_node: CustomNode): class NodeUtils: - NODES = [] - NETWORK_NODES = [] + NODES: List[NodeDraw] = [] + NETWORK_NODES: List[NodeDraw] = [] NODE_ICONS = {} - CONTAINER_NODES = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} - IMAGE_NODES = {NodeType.DOCKER, NodeType.LXC} - WIRELESS_NODES = {NodeType.WIRELESS_LAN, NodeType.EMANE} - RJ45_NODES = {NodeType.RJ45} - IGNORE_NODES = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER} - NODE_MODELS = {"router", "host", "PC", "mdr", "prouter"} - ROUTER_NODES = {"router", "mdr"} - ANTENNA_ICON = None + CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} + IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC} + WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} + RJ45_NODES: Set[NodeType] = {NodeType.RJ45} + IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER} + NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"} + ROUTER_NODES: Set[str] = {"router", "mdr"} + ANTENNA_ICON: PhotoImage = None @classmethod def is_router_node(cls, node: Node) -> bool: @@ -99,8 +97,8 @@ def is_rj45_node(cls, node_type: NodeType) -> bool: @classmethod def node_icon( - cls, node_type: NodeType, model: str, gui_config: GuiConfig, scale=1.0 - ) -> "ImageTk.PhotoImage": + cls, node_type: NodeType, model: str, gui_config: GuiConfig, scale: float = 1.0 + ) -> PhotoImage: image_enum = TypeToImage.get(node_type, model) if image_enum: @@ -112,8 +110,8 @@ def node_icon( @classmethod def node_image( - cls, core_node: "core_pb2.Node", gui_config: GuiConfig, scale=1.0 - ) -> "ImageTk.PhotoImage": + cls, core_node: Node, gui_config: GuiConfig, scale: float = 1.0 + ) -> PhotoImage: image = cls.node_icon(core_node.type, core_node.model, gui_config, scale) if core_node.icon: try: @@ -141,7 +139,7 @@ def get_image_file(cls, gui_config: GuiConfig, name: str) -> Optional[str]: return None @classmethod - def setup(cls): + def setup(cls) -> None: nodes = [ (ImageEnum.ROUTER, NodeType.DEFAULT, "Router", "router"), (ImageEnum.HOST, NodeType.DEFAULT, "Host", "host"), diff --git a/daemon/core/gui/observers.py b/daemon/core/gui/observers.py index 27d0a26ed..7879494b5 100644 --- a/daemon/core/gui/observers.py +++ b/daemon/core/gui/observers.py @@ -1,13 +1,13 @@ import tkinter as tk from functools import partial -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict from core.gui.dialogs.observers import ObserverDialog if TYPE_CHECKING: from core.gui.app import Application -OBSERVERS = { +OBSERVERS: Dict[str, str] = { "List Processes": "ps", "Show Interfaces": "ip address", "IPV4 Routes": "ip -4 route", @@ -23,9 +23,9 @@ class ObserversMenu(tk.Menu): def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(master) - self.app = app - self.observer = tk.StringVar(value=tk.NONE) - self.custom_index = 0 + self.app: "Application" = app + self.observer: tk.StringVar = tk.StringVar(value=tk.NONE) + self.custom_index: int = 0 self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 3f58e7a03..2b597b63d 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -3,8 +3,9 @@ """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional +from core.api.grpc.core_pb2 import ExceptionEvent from core.gui.dialogs.alerts import AlertsDialog from core.gui.themes import Styles @@ -13,20 +14,19 @@ class StatusBar(ttk.Frame): - def __init__(self, master: tk.Widget, app: "Application"): + def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) - self.app = app - self.status = None - self.statusvar = tk.StringVar() - self.zoom = None - self.cpu_usage = None - self.memory = None - self.alerts_button = None - self.running = False - self.core_alarms = [] + self.app: "Application" = app + self.status: Optional[ttk.Label] = None + self.statusvar: tk.StringVar = tk.StringVar() + self.zoom: Optional[ttk.Label] = None + self.cpu_usage: Optional[ttk.Label] = None + self.alerts_button: Optional[ttk.Button] = None + self.running: bool = False + self.core_alarms: List[ExceptionEvent] = [] self.draw() - def draw(self): + def draw(self) -> None: self.columnconfigure(0, weight=7) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) @@ -64,9 +64,9 @@ def draw(self): ) self.alerts_button.grid(row=0, column=3, sticky="ew") - def click_alerts(self): + def click_alerts(self) -> None: dialog = AlertsDialog(self.app) dialog.show() - def set_status(self, message: str): + def set_status(self, message: str) -> None: self.statusvar.set(message) diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index 2f055a903..b4a5f68f6 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -1,7 +1,7 @@ import logging import threading import time -from typing import TYPE_CHECKING, Any, Callable, Tuple +from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple if TYPE_CHECKING: from core.gui.app import Application @@ -16,14 +16,14 @@ def __init__( callback: Callable = None, args: Tuple[Any] = None, ): - self.app = app - self.title = title - self.task = task - self.callback = callback - self.args = args - if self.args is None: - self.args = () - self.time = None + self.app: "Application" = app + self.title: str = title + self.task: Callable = task + self.callback: Callable = callback + if args is None: + args = () + self.args: Tuple[Any] = args + self.time: Optional[float] = None def start(self) -> None: self.app.progress.grid(sticky="ew") @@ -49,7 +49,7 @@ def run(self) -> None: finally: self.app.after(0, self.complete) - def complete(self): + def complete(self) -> None: self.app.progress.stop() self.app.progress.grid_forget() total = time.perf_counter() - self.time diff --git a/daemon/core/gui/themes.py b/daemon/core/gui/themes.py index 141a7a5c3..93a0a5991 100644 --- a/daemon/core/gui/themes.py +++ b/daemon/core/gui/themes.py @@ -1,39 +1,40 @@ import tkinter as tk from tkinter import font, ttk +from typing import Dict, Tuple -THEME_DARK = "black" -PADX = (0, 5) -PADY = (0, 5) -FRAME_PAD = 5 -DIALOG_PAD = 5 +THEME_DARK: str = "black" +PADX: Tuple[int, int] = (0, 5) +PADY: Tuple[int, int] = (0, 5) +FRAME_PAD: int = 5 +DIALOG_PAD: int = 5 class Styles: - tooltip = "Tooltip.TLabel" - tooltip_frame = "Tooltip.TFrame" - service_checkbutton = "Service.TCheckbutton" - picker_button = "Picker.TButton" - green_alert = "GAlert.TButton" - red_alert = "RAlert.TButton" - yellow_alert = "YAlert.TButton" + tooltip: str = "Tooltip.TLabel" + tooltip_frame: str = "Tooltip.TFrame" + service_checkbutton: str = "Service.TCheckbutton" + picker_button: str = "Picker.TButton" + green_alert: str = "GAlert.TButton" + red_alert: str = "RAlert.TButton" + yellow_alert: str = "YAlert.TButton" class Colors: - disabledfg = "DarkGrey" - frame = "#424242" - dark = "#222222" - darker = "#121212" - darkest = "black" - lighter = "#626262" - lightest = "#ffffff" - selectbg = "#4a6984" - selectfg = "#ffffff" - white = "white" - black = "black" - listboxbg = "#f2f1f0" - - -def load(style: ttk.Style): + disabledfg: str = "DarkGrey" + frame: str = "#424242" + dark: str = "#222222" + darker: str = "#121212" + darkest: str = "black" + lighter: str = "#626262" + lightest: str = "#ffffff" + selectbg: str = "#4a6984" + selectfg: str = "#ffffff" + white: str = "white" + black: str = "black" + listboxbg: str = "#f2f1f0" + + +def load(style: ttk.Style) -> None: style.theme_create( THEME_DARK, "clam", @@ -139,13 +140,13 @@ def load(style: ttk.Style): ) -def theme_change_menu(event: tk.Event): +def theme_change_menu(event: tk.Event) -> None: if not isinstance(event.widget, tk.Menu): return style_menu(event.widget) -def style_menu(widget: tk.Widget): +def style_menu(widget: tk.Widget) -> None: style = ttk.Style() bg = style.lookup(".", "background") fg = style.lookup(".", "foreground") @@ -157,7 +158,7 @@ def style_menu(widget: tk.Widget): ) -def style_listbox(widget: tk.Widget): +def style_listbox(widget: tk.Widget) -> None: style = ttk.Style() bg = style.lookup(".", "background") fg = style.lookup(".", "foreground") @@ -174,7 +175,7 @@ def style_listbox(widget: tk.Widget): ) -def theme_change(event: tk.Event): +def theme_change(event: tk.Event) -> None: style = ttk.Style() style.configure(Styles.picker_button, font="TkSmallCaptionFont") style.configure( @@ -203,7 +204,7 @@ def theme_change(event: tk.Event): ) -def scale_fonts(fonts_size, scale): +def scale_fonts(fonts_size: Dict[str, int], scale: float) -> None: for name in font.names(): f = font.nametofont(name) if name in fonts_size: diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 54fac126f..c3e9067f8 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -3,7 +3,7 @@ from enum import Enum from functools import partial from tkinter import ttk -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, List, Optional from PIL.ImageTk import PhotoImage @@ -23,8 +23,8 @@ if TYPE_CHECKING: from core.gui.app import Application -TOOLBAR_SIZE = 32 -PICKER_SIZE = 24 +TOOLBAR_SIZE: int = 32 +PICKER_SIZE: int = 24 class NodeTypeEnum(Enum): @@ -42,8 +42,8 @@ def enable_buttons(frame: ttk.Frame, enabled: bool) -> None: class PickerFrame(ttk.Frame): def __init__(self, app: "Application", button: ttk.Button) -> None: super().__init__(app) - self.app = app - self.button = button + self.app: "Application" = app + self.button: ttk.Button = button def create_node_button(self, node_draw: NodeDraw, func: Callable) -> None: self.create_button( @@ -85,10 +85,10 @@ def _show(self) -> None: class ButtonBar(ttk.Frame): - def __init__(self, master: tk.Widget, app: "Application"): + def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) - self.app = app - self.radio_buttons = [] + self.app: "Application" = app + self.radio_buttons: List[ttk.Button] = [] def create_button( self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False @@ -109,14 +109,14 @@ def select_radio(self, selected: ttk.Button) -> None: class MarkerFrame(ttk.Frame): - PAD = 3 + PAD: int = 3 def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(master, padding=self.PAD) - self.app = app - self.color = "#000000" - self.size = tk.DoubleVar() - self.color_frame = None + self.app: "Application" = app + self.color: str = "#000000" + self.size: tk.DoubleVar = tk.DoubleVar() + self.color_frame: Optional[tk.Frame] = None self.draw() def draw(self) -> None: @@ -144,7 +144,7 @@ def draw(self) -> None: self.color_frame.bind("", self.click_color) Tooltip(self.color_frame, "Marker Color") - def click_clear(self): + def click_clear(self) -> None: self.app.canvas.delete(tags.MARKER) def click_color(self, _event: tk.Event) -> None: @@ -163,37 +163,37 @@ def __init__(self, app: "Application") -> None: Create a CoreToolbar instance """ super().__init__(app) - self.app = app + self.app: "Application" = app # design buttons - self.play_button = None - self.select_button = None - self.link_button = None - self.node_button = None - self.network_button = None - self.annotation_button = None + self.play_button: Optional[ttk.Button] = None + self.select_button: Optional[ttk.Button] = None + self.link_button: Optional[ttk.Button] = None + self.node_button: Optional[ttk.Button] = None + self.network_button: Optional[ttk.Button] = None + self.annotation_button: Optional[ttk.Button] = None # runtime buttons - self.runtime_select_button = None - self.stop_button = None - self.runtime_marker_button = None - self.run_command_button = None + self.runtime_select_button: Optional[ttk.Button] = None + self.stop_button: Optional[ttk.Button] = None + self.runtime_marker_button: Optional[ttk.Button] = None + self.run_command_button: Optional[ttk.Button] = None # frames - self.design_frame = None - self.runtime_frame = None - self.marker_frame = None - self.picker = None + self.design_frame: Optional[ButtonBar] = None + self.runtime_frame: Optional[ButtonBar] = None + self.marker_frame: Optional[MarkerFrame] = None + self.picker: Optional[PickerFrame] = None # observers - self.observers_menu = None + self.observers_menu: Optional[ObserversMenu] = None # these variables help keep track of what images being drawn so that scaling # is possible since PhotoImage does not have resize method - self.current_node = NodeUtils.NODES[0] - self.current_network = NodeUtils.NETWORK_NODES[0] - self.current_annotation = ShapeType.MARKER - self.annotation_enum = ImageEnum.MARKER + self.current_node: NodeDraw = NodeUtils.NODES[0] + self.current_network: NodeDraw = NodeUtils.NETWORK_NODES[0] + self.current_annotation: ShapeType = ShapeType.MARKER + self.annotation_enum: ImageEnum = ImageEnum.MARKER # draw components self.draw() diff --git a/daemon/core/gui/tooltip.py b/daemon/core/gui/tooltip.py index bc1ed9b5e..c29785106 100644 --- a/daemon/core/gui/tooltip.py +++ b/daemon/core/gui/tooltip.py @@ -1,5 +1,6 @@ import tkinter as tk from tkinter import ttk +from typing import Optional from core.gui.themes import Styles @@ -9,19 +10,19 @@ class Tooltip(object): Create tool tip for a given widget """ - def __init__(self, widget: tk.Widget, text: str = "widget info"): - self.widget = widget - self.text = text + def __init__(self, widget: tk.BaseWidget, text: str = "widget info") -> None: + self.widget: tk.BaseWidget = widget + self.text: str = text self.widget.bind("", self.on_enter) self.widget.bind("", self.on_leave) - self.waittime = 400 - self.id = None - self.tw = None + self.waittime: int = 400 + self.id: Optional[str] = None + self.tw: Optional[tk.Toplevel] = None - def on_enter(self, event: tk.Event = None): + def on_enter(self, event: tk.Event = None) -> None: self.schedule() - def on_leave(self, event: tk.Event = None): + def on_leave(self, event: tk.Event = None) -> None: self.unschedule() self.close(event) @@ -39,7 +40,6 @@ def enter(self, event: tk.Event = None): x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() y += self.widget.winfo_rooty() + 32 - self.tw = tk.Toplevel(self.widget) self.tw.wm_overrideredirect(True) self.tw.wm_geometry("+%d+%d" % (x, y)) diff --git a/daemon/core/gui/validation.py b/daemon/core/gui/validation.py index 873db1893..22f12bb8b 100644 --- a/daemon/core/gui/validation.py +++ b/daemon/core/gui/validation.py @@ -4,16 +4,23 @@ import re import tkinter as tk from tkinter import ttk +from typing import Any, Optional, Pattern -SMALLEST_SCALE = 0.5 -LARGEST_SCALE = 5.0 -HEX_REGEX = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$") +SMALLEST_SCALE: float = 0.5 +LARGEST_SCALE: float = 5.0 +HEX_REGEX: Pattern = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$") class ValidationEntry(ttk.Entry): - empty = None - - def __init__(self, master=None, widget=None, empty_enabled=True, **kwargs) -> None: + empty: Optional[str] = None + + def __init__( + self, + master: tk.BaseWidget = None, + widget: tk.BaseWidget = None, + empty_enabled: bool = True, + **kwargs: Any + ) -> None: super().__init__(master, widget, **kwargs) cmd = self.register(self.is_valid) self.configure(validate="key", validatecommand=(cmd, "%P")) @@ -30,7 +37,7 @@ def focus_out(self, _event: tk.Event) -> None: class PositiveIntEntry(ValidationEntry): - empty = "0" + empty: str = "0" def is_valid(self, s: str) -> bool: if not s: @@ -92,7 +99,7 @@ def is_valid(self, s: str) -> bool: class NodeNameEntry(ValidationEntry): - empty = "noname" + empty: str = "noname" def is_valid(self, s: str) -> bool: if len(s) < 0: diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 6f51bd8c0..2eded212d 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -1,53 +1,63 @@ import logging import tkinter as tk from functools import partial -from pathlib import PosixPath +from pathlib import Path from tkinter import filedialog, font, ttk -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type -from core.api.grpc import common_pb2, core_pb2 +from core.api.grpc import core_pb2 +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import ConfigOptionType from core.gui import themes, validation +from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY if TYPE_CHECKING: from core.gui.app import Application - from core.gui.dialogs.dialog import Dialog - -INT_TYPES = { - core_pb2.ConfigOptionType.UINT8, - core_pb2.ConfigOptionType.UINT16, - core_pb2.ConfigOptionType.UINT32, - core_pb2.ConfigOptionType.UINT64, - core_pb2.ConfigOptionType.INT8, - core_pb2.ConfigOptionType.INT16, - core_pb2.ConfigOptionType.INT32, - core_pb2.ConfigOptionType.INT64, + +INT_TYPES: Set[ConfigOptionType] = { + ConfigOptionType.UINT8, + ConfigOptionType.UINT16, + ConfigOptionType.UINT32, + ConfigOptionType.UINT64, + ConfigOptionType.INT8, + ConfigOptionType.INT16, + ConfigOptionType.INT32, + ConfigOptionType.INT64, } -def file_button_click(value: tk.StringVar, parent: tk.Widget): +def file_button_click(value: tk.StringVar, parent: tk.Widget) -> None: file_path = filedialog.askopenfilename(title="Select File", parent=parent) if file_path: value.set(file_path) class FrameScroll(ttk.Frame): - def __init__(self, master: tk.Widget, app: "Application", _cls=ttk.Frame, **kw): + def __init__( + self, + master: tk.Widget, + app: "Application", + _cls: Type[ttk.Frame] = ttk.Frame, + **kw: Any + ) -> None: super().__init__(master, **kw) - self.app = app + self.app: "Application" = app self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) bg = self.app.style.lookup(".", "background") - self.canvas = tk.Canvas(self, highlightthickness=0, background=bg) + self.canvas: tk.Canvas = tk.Canvas(self, highlightthickness=0, background=bg) self.canvas.grid(row=0, sticky="nsew", padx=2, pady=2) self.canvas.columnconfigure(0, weight=1) self.canvas.rowconfigure(0, weight=1) - self.scrollbar = ttk.Scrollbar( + self.scrollbar: ttk.Scrollbar = ttk.Scrollbar( self, orient="vertical", command=self.canvas.yview ) self.scrollbar.grid(row=0, column=1, sticky="ns") - self.frame = _cls(self.canvas) - self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) + self.frame: ttk.Frame = _cls(self.canvas) + self.frame_id: int = self.canvas.create_window( + 0, 0, anchor="nw", window=self.frame + ) self.canvas.update_idletasks() self.canvas.configure( scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set @@ -55,16 +65,16 @@ def __init__(self, master: tk.Widget, app: "Application", _cls=ttk.Frame, **kw): self.frame.bind("", self._configure_frame) self.canvas.bind("", self._configure_canvas) - def _configure_frame(self, event: tk.Event): + def _configure_frame(self, event: tk.Event) -> None: req_width = self.frame.winfo_reqwidth() if req_width != self.canvas.winfo_reqwidth(): self.canvas.configure(width=req_width) self.canvas.configure(scrollregion=self.canvas.bbox("all")) - def _configure_canvas(self, event: tk.Event): + def _configure_canvas(self, event: tk.Event) -> None: self.canvas.itemconfig(self.frame_id, width=event.width) - def clear(self): + def clear(self) -> None: for widget in self.frame.winfo_children(): widget.destroy() @@ -74,15 +84,15 @@ def __init__( self, master: tk.Widget, app: "Application", - config: Dict[str, common_pb2.ConfigOption], - **kw - ): + config: Dict[str, ConfigOption], + **kw: Any + ) -> None: super().__init__(master, **kw) - self.app = app - self.config = config - self.values = {} + self.app: "Application" = app + self.config: Dict[str, ConfigOption] = config + self.values: Dict[str, tk.StringVar] = {} - def draw_config(self): + def draw_config(self) -> None: group_mapping = {} for key in self.config: option = self.config[key] @@ -142,7 +152,7 @@ def draw_config(self): logging.error("unhandled config option type: %s", option.type) self.values[option.name] = value - def parse_config(self): + def parse_config(self) -> Dict[str, str]: for key in self.config: option = self.config[key] value = self.values[key] @@ -169,13 +179,13 @@ def set_values(self, config: Dict[str, str]) -> None: class ListboxScroll(ttk.Frame): - def __init__(self, master: tk.Widget = None, **kw): + def __init__(self, master: tk.BaseWidget = None, **kw: Any) -> None: super().__init__(master, **kw) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) + self.scrollbar: ttk.Scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) self.scrollbar.grid(row=0, column=1, sticky="ns") - self.listbox = tk.Listbox( + self.listbox: tk.Listbox = tk.Listbox( self, selectmode=tk.BROWSE, yscrollcommand=self.scrollbar.set, @@ -187,12 +197,18 @@ def __init__(self, master: tk.Widget = None, **kw): class CheckboxList(FrameScroll): - def __init__(self, master: ttk.Widget, app: "Application", clicked=None, **kw): + def __init__( + self, + master: ttk.Widget, + app: "Application", + clicked: Callable = None, + **kw: Any + ) -> None: super().__init__(master, app, **kw) - self.clicked = clicked + self.clicked: Callable = clicked self.frame.columnconfigure(0, weight=1) - def add(self, name: str, checked: bool): + def add(self, name: str, checked: bool) -> None: var = tk.BooleanVar(value=checked) func = partial(self.clicked, name, var) checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func) @@ -200,16 +216,16 @@ def add(self, name: str, checked: bool): class CodeFont(font.Font): - def __init__(self): + def __init__(self) -> None: super().__init__(font="TkFixedFont", color="green") class CodeText(ttk.Frame): - def __init__(self, master: tk.Widget, **kwargs): + def __init__(self, master: tk.BaseWidget, **kwargs: Any) -> None: super().__init__(master, **kwargs) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) - self.text = tk.Text( + self.text: tk.Text = tk.Text( self, bd=0, bg="black", @@ -229,14 +245,14 @@ def __init__(self, master: tk.Widget, **kwargs): class Spinbox(ttk.Entry): - def __init__(self, master: tk.Widget = None, **kwargs): + def __init__(self, master: tk.BaseWidget = None, **kwargs: Any) -> None: super().__init__(master, "ttk::spinbox", **kwargs) - def set(self, value): + def set(self, value: str) -> None: self.tk.call(self._w, "set", value) -def image_chooser(parent: "Dialog", path: PosixPath): +def image_chooser(parent: Dialog, path: Path) -> str: return filedialog.askopenfilename( parent=parent, initialdir=str(path), From 11be40bc90135040897da6a2b0e2372f1fb7f097 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 23:24:07 -0700 Subject: [PATCH 054/210] pygui: added class variable type hinting to core.gui.graph --- daemon/core/gui/coreclient.py | 2 +- daemon/core/gui/graph/edges.py | 69 +++++----- daemon/core/gui/graph/graph.py | 190 ++++++++++++++-------------- daemon/core/gui/graph/node.py | 105 ++++++++------- daemon/core/gui/graph/shape.py | 64 +++++----- daemon/core/gui/graph/shapeutils.py | 3 +- daemon/core/gui/graph/tags.py | 32 ++--- daemon/core/gui/graph/tooltip.py | 42 +++--- 8 files changed, 257 insertions(+), 250 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 247087690..5e1bf4c28 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -79,7 +79,7 @@ def __init__(self, app: "Application", proxy: bool) -> None: self.read_config() # helpers - self.iface_to_edge: Dict[Tuple[int, int], Tuple[int, int]] = {} + self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {} self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) # session data diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index ac637b281..e9ac25878 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -1,9 +1,10 @@ import logging import math import tkinter as tk -from typing import TYPE_CHECKING, Any, Tuple +from typing import TYPE_CHECKING, Optional, Tuple from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.graph import tags @@ -12,12 +13,12 @@ if TYPE_CHECKING: from core.gui.graph.graph import CanvasGraph -TEXT_DISTANCE = 0.30 -EDGE_WIDTH = 3 -EDGE_COLOR = "#ff0000" -WIRELESS_WIDTH = 1.5 -WIRELESS_COLOR = "#009933" -ARC_DISTANCE = 50 +TEXT_DISTANCE: float = 0.30 +EDGE_WIDTH: int = 3 +EDGE_COLOR: str = "#ff0000" +WIRELESS_WIDTH: float = 1.5 +WIRELESS_COLOR: str = "#009933" +ARC_DISTANCE: int = 50 def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]: @@ -57,20 +58,20 @@ def arc_edges(edges) -> None: class Edge: - tag = tags.EDGE + tag: str = tags.EDGE def __init__(self, canvas: "CanvasGraph", src: int, dst: int = None) -> None: self.canvas = canvas - self.id = None - self.src = src - self.dst = dst - self.arc = 0 - self.token = None - self.src_label = None - self.middle_label = None - self.dst_label = None - self.color = EDGE_COLOR - self.width = EDGE_WIDTH + self.id: Optional[int] = None + self.src: int = src + self.dst: int = dst + self.arc: int = 0 + self.token: Optional[Tuple[int, ...]] = None + self.src_label: Optional[int] = None + self.middle_label: Optional[int] = None + self.dst_label: Optional[int] = None + self.color: str = EDGE_COLOR + self.width: int = EDGE_WIDTH @classmethod def create_token(cls, src: int, dst: int) -> Tuple[int, ...]: @@ -120,7 +121,7 @@ def draw(self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]) -> No fill=self.color, ) - def redraw(self): + def redraw(self) -> None: self.canvas.itemconfig(self.id, width=self.scaled_width(), fill=self.color) src_x, src_y, _, _, _, _ = self.canvas.coords(self.id) src_pos = src_x, src_y @@ -233,13 +234,13 @@ def __init__( dst: int, src_pos: Tuple[float, float], dst_pos: Tuple[float, float], - token: Tuple[Any, ...], + token: Tuple[int, ...], ) -> None: logging.debug("drawing wireless link from node %s to node %s", src, dst) super().__init__(canvas, src, dst) - self.token = token - self.width = WIRELESS_WIDTH - self.color = WIRELESS_COLOR + self.token: Tuple[int, ...] = token + self.width: float = WIRELESS_WIDTH + self.color: str = WIRELESS_COLOR self.draw(src_pos, dst_pos) @@ -259,19 +260,19 @@ def __init__( Create an instance of canvas edge object """ super().__init__(canvas, src) - self.src_iface = None - self.dst_iface = None - self.text_src = None - self.text_dst = None - self.link = None - self.asymmetric_link = None - self.throughput = None + self.src_iface: Optional[Interface] = None + self.dst_iface: Optional[Interface] = None + self.text_src: Optional[int] = None + self.text_dst: Optional[int] = None + self.link: Optional[Link] = None + self.asymmetric_link: Optional[Link] = None + self.throughput: Optional[float] = None self.draw(src_pos, dst_pos) self.set_binding() - self.context = tk.Menu(self.canvas) + self.context: tk.Menu = tk.Menu(self.canvas) self.create_context() - def create_context(self): + def create_context(self) -> None: themes.style_menu(self.context) self.context.add_command(label="Configure", command=self.click_configure) self.context.add_command(label="Delete", command=self.click_delete) @@ -279,7 +280,7 @@ def create_context(self): def set_binding(self) -> None: self.canvas.tag_bind(self.id, "", self.show_context) - def set_link(self, link) -> None: + def set_link(self, link: Link) -> None: self.link = link self.draw_labels() @@ -383,7 +384,7 @@ def show_context(self, event: tk.Event) -> None: self.context.entryconfigure(1, state=state) self.context.tk_popup(event.x_root, event.y_root) - def click_delete(self): + def click_delete(self) -> None: self.canvas.delete_edge(self) def click_configure(self) -> None: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 834220ead..531157506 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -2,11 +2,12 @@ import tkinter as tk from copy import deepcopy from tkinter import BooleanVar -from typing import TYPE_CHECKING, Optional, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple -from PIL import Image, ImageTk +from PIL import Image +from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import Interface, Link, LinkType, Session, ThroughputsEvent from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.edges import ( @@ -21,7 +22,7 @@ from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.images import ImageEnum, TypeToImage -from core.gui.nodeutils import NodeUtils +from core.gui.nodeutils import NodeDraw, NodeUtils if TYPE_CHECKING: from core.gui.app import Application @@ -48,58 +49,59 @@ def click_handler(self) -> None: class CanvasGraph(tk.Canvas): - def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"): + def __init__( + self, master: tk.BaseWidget, app: "Application", core: "CoreClient" + ) -> None: super().__init__(master, highlightthickness=0, background="#cccccc") - self.app = app - self.core = core - self.mode = GraphMode.SELECT - self.annotation_type = None - self.selection = {} - self.select_box = None - self.selected = None - self.node_draw = None - self.nodes = {} - self.edges = {} - self.shapes = {} - self.wireless_edges = {} + self.app: "Application" = app + self.core: "CoreClient" = core + self.mode: GraphMode = GraphMode.SELECT + self.annotation_type: Optional[ShapeType] = None + self.selection: Dict[int, int] = {} + self.select_box: Optional[Shape] = None + self.selected: Optional[int] = None + self.node_draw: Optional[NodeDraw] = None + self.nodes: Dict[int, CanvasNode] = {} + self.edges: Dict[int, CanvasEdge] = {} + self.shapes: Dict[int, Shape] = {} + self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {} # map wireless/EMANE node to the set of MDRs connected to that node - self.wireless_network = {} + self.wireless_network: Dict[int, Set[int]] = {} - self.drawing_edge = None - self.rect = None - self.shape_drawing = False + self.drawing_edge: Optional[CanvasEdge] = None + self.rect: Optional[int] = None + self.shape_drawing: bool = False width = self.app.guiconfig.preferences.width height = self.app.guiconfig.preferences.height - self.default_dimensions = (width, height) - self.current_dimensions = self.default_dimensions - self.ratio = 1.0 - self.offset = (0, 0) - self.cursor = (0, 0) - self.marker_tool = None - self.to_copy = [] + self.default_dimensions: Tuple[int, int] = (width, height) + self.current_dimensions: Tuple[int, int] = self.default_dimensions + self.ratio: float = 1.0 + self.offset: Tuple[int, int] = (0, 0) + self.cursor: Tuple[int, int] = (0, 0) + self.to_copy: List[CanvasNode] = [] # background related - self.wallpaper_id = None - self.wallpaper = None - self.wallpaper_drawn = None - self.wallpaper_file = "" - self.scale_option = tk.IntVar(value=1) - self.adjust_to_dim = tk.BooleanVar(value=False) + self.wallpaper_id: Optional[int] = None + self.wallpaper: Optional[Image.Image] = None + self.wallpaper_drawn: Optional[PhotoImage] = None + self.wallpaper_file: str = "" + self.scale_option: tk.IntVar = tk.IntVar(value=1) + self.adjust_to_dim: tk.BooleanVar = tk.BooleanVar(value=False) # throughput related - self.throughput_threshold = 250.0 - self.throughput_width = 10 - self.throughput_color = "#FF0000" + self.throughput_threshold: float = 250.0 + self.throughput_width: int = 10 + self.throughput_color: str = "#FF0000" # drawing related - self.show_node_labels = ShowVar(self, tags.NODE_LABEL, value=True) - self.show_link_labels = ShowVar(self, tags.LINK_LABEL, value=True) - self.show_grid = ShowVar(self, tags.GRIDLINE, value=True) - self.show_annotations = ShowVar(self, tags.ANNOTATION, value=True) - self.show_iface_names = BooleanVar(value=False) - self.show_ip4s = BooleanVar(value=True) - self.show_ip6s = BooleanVar(value=True) + self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True) + self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True) + self.show_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True) + self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, value=True) + self.show_iface_names: BooleanVar = BooleanVar(value=False) + self.show_ip4s: BooleanVar = BooleanVar(value=True) + self.show_ip6s: BooleanVar = BooleanVar(value=True) # bindings self.setup_bindings() @@ -108,7 +110,7 @@ def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"): self.draw_canvas() self.draw_grid() - def draw_canvas(self, dimensions: Tuple[int, int] = None): + def draw_canvas(self, dimensions: Tuple[int, int] = None) -> None: if self.rect is not None: self.delete(self.rect) if not dimensions: @@ -125,7 +127,7 @@ def draw_canvas(self, dimensions: Tuple[int, int] = None): ) self.configure(scrollregion=self.bbox(tk.ALL)) - def reset_and_redraw(self, session: core_pb2.Session): + def reset_and_redraw(self, session: Session) -> None: """ Reset the private variables CanvasGraph object, redraw nodes given the new grpc client. @@ -157,7 +159,7 @@ def reset_and_redraw(self, session: core_pb2.Session): self.drawing_edge = None self.draw_session(session) - def setup_bindings(self): + def setup_bindings(self) -> None: """ Bind any mouse events or hot keys to the matching action """ @@ -173,28 +175,28 @@ def setup_bindings(self): self.bind("", lambda e: self.scan_mark(e.x, e.y)) self.bind("", lambda e: self.scan_dragto(e.x, e.y, gain=1)) - def get_actual_coords(self, x: float, y: float) -> [float, float]: + def get_actual_coords(self, x: float, y: float) -> Tuple[float, float]: actual_x = (x - self.offset[0]) / self.ratio actual_y = (y - self.offset[1]) / self.ratio return actual_x, actual_y - def get_scaled_coords(self, x: float, y: float) -> [float, float]: + def get_scaled_coords(self, x: float, y: float) -> Tuple[float, float]: scaled_x = (x * self.ratio) + self.offset[0] scaled_y = (y * self.ratio) + self.offset[1] return scaled_x, scaled_y - def inside_canvas(self, x: float, y: float) -> [bool, bool]: + def inside_canvas(self, x: float, y: float) -> Tuple[bool, bool]: x1, y1, x2, y2 = self.bbox(self.rect) valid_x = x1 <= x <= x2 valid_y = y1 <= y <= y2 return valid_x and valid_y - def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [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_bottomright = self.inside_canvas(x2, y2) return valid_topleft and valid_bottomright - def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent): + def set_throughputs(self, throughputs_event: ThroughputsEvent) -> None: for iface_throughput in throughputs_event.iface_throughputs: node_id = iface_throughput.node_id iface_id = iface_throughput.iface_id @@ -209,7 +211,7 @@ def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent): else: del self.core.iface_to_edge[iface_to_edge_id] - def draw_grid(self): + def draw_grid(self) -> None: """ Create grid. """ @@ -223,9 +225,7 @@ def draw_grid(self): self.tag_lower(tags.GRIDLINE) self.tag_lower(self.rect) - def add_wireless_edge( - self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link - ) -> None: + def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) if token in self.wireless_edges: @@ -248,7 +248,7 @@ def add_wireless_edge( arc_edges(common_edges) def delete_wireless_edge( - self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link + self, src: CanvasNode, dst: CanvasNode, link: Link ) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) @@ -263,7 +263,7 @@ def delete_wireless_edge( arc_edges(common_edges) def update_wireless_edge( - self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link + self, src: CanvasNode, dst: CanvasNode, link: Link ) -> None: if not link.label: return @@ -275,7 +275,7 @@ def update_wireless_edge( edge = self.wireless_edges[token] edge.middle_label_text(link.label) - def draw_session(self, session: core_pb2.Session): + def draw_session(self, session: Session) -> None: """ Draw existing session. """ @@ -306,7 +306,7 @@ def draw_session(self, session: core_pb2.Session): node2 = canvas_node2.core_node token = create_edge_token(canvas_node1.id, canvas_node2.id) - if link.type == core_pb2.LinkType.WIRELESS: + if link.type == LinkType.WIRELESS: self.add_wireless_edge(canvas_node1, canvas_node2, link) else: if token not in self.edges: @@ -337,7 +337,7 @@ def draw_session(self, session: core_pb2.Session): else: logging.error("duplicate link received: %s", link) - def stopped_session(self): + def stopped_session(self) -> None: # clear wireless edges for edge in self.wireless_edges.values(): edge.delete() @@ -351,7 +351,7 @@ def stopped_session(self): for edge in self.edges.values(): edge.reset() - def canvas_xy(self, event: tk.Event) -> [float, float]: + def canvas_xy(self, event: tk.Event) -> Tuple[float, float]: """ Convert window coordinate to canvas coordinate """ @@ -379,7 +379,7 @@ def get_selected(self, event: tk.Event) -> int: return selected - def click_release(self, event: tk.Event): + def click_release(self, event: tk.Event) -> None: """ Draw a node or finish drawing an edge according to the current graph mode """ @@ -418,7 +418,7 @@ def click_release(self, event: tk.Event): self.mode = GraphMode.NODE self.selected = None - def handle_edge_release(self, _event: tk.Event): + def handle_edge_release(self, _event: tk.Event) -> None: edge = self.drawing_edge self.drawing_edge = None @@ -454,7 +454,7 @@ def handle_edge_release(self, _event: tk.Event): node_dst.edges.add(edge) self.core.create_link(edge, node_src, node_dst) - def select_object(self, object_id: int, choose_multiple: bool = False): + def select_object(self, object_id: int, choose_multiple: bool = False) -> None: """ create a bounding box when a node is selected """ @@ -475,7 +475,7 @@ def select_object(self, object_id: int, choose_multiple: bool = False): selection_id = self.selection.pop(object_id) self.delete(selection_id) - def clear_selection(self): + def clear_selection(self) -> None: """ Clear current selection boxes. """ @@ -483,7 +483,7 @@ def clear_selection(self): self.delete(_id) self.selection.clear() - def move_selection(self, object_id: int, x_offset: float, y_offset: float): + def move_selection(self, object_id: int, x_offset: float, y_offset: float) -> None: select_id = self.selection.get(object_id) if select_id is not None: self.move(select_id, x_offset, y_offset) @@ -531,7 +531,7 @@ def delete_selected_objects(self) -> None: self.core.deleted_graph_nodes(nodes) self.core.deleted_graph_edges(edges) - def delete_edge(self, edge: CanvasEdge): + def delete_edge(self, edge: CanvasEdge) -> None: edge.delete() del self.edges[edge.token] src_node = self.nodes[edge.src] @@ -550,7 +550,7 @@ def delete_edge(self, edge: CanvasEdge): src_node.delete_antenna() self.core.deleted_graph_edges([edge]) - def zoom(self, event: tk.Event, factor: float = None): + def zoom(self, event: tk.Event, factor: float = None) -> None: if not factor: factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT event.x, event.y = self.canvasx(event.x), self.canvasy(event.y) @@ -568,7 +568,7 @@ def zoom(self, event: tk.Event, factor: float = None): if self.wallpaper: self.redraw_wallpaper() - def click_press(self, event: tk.Event): + def click_press(self, event: tk.Event) -> None: """ Start drawing an edge if mouse click is on a node """ @@ -630,7 +630,7 @@ def click_press(self, event: tk.Event): self.select_box = shape self.clear_selection() - def ctrl_click(self, event: tk.Event): + def ctrl_click(self, event: tk.Event) -> None: # update cursor location x, y = self.canvas_xy(event) if not self.inside_canvas(x, y): @@ -648,7 +648,7 @@ def ctrl_click(self, event: tk.Event): ): self.select_object(selected, choose_multiple=True) - def click_motion(self, event: tk.Event): + def click_motion(self, event: tk.Event) -> None: x, y = self.canvas_xy(event) if not self.inside_canvas(x, y): if self.select_box: @@ -701,7 +701,7 @@ def click_motion(self, event: tk.Event): if self.select_box and self.mode == GraphMode.SELECT: self.select_box.shape_motion(x, y) - def press_delete(self, _event: tk.Event): + def press_delete(self, _event: tk.Event) -> None: """ delete selected nodes and any data that relates to it """ @@ -711,7 +711,7 @@ def press_delete(self, _event: tk.Event): else: logging.debug("node deletion is disabled during runtime state") - def double_click(self, event: tk.Event): + def double_click(self, event: tk.Event) -> None: selected = self.get_selected(event) if selected is not None and selected in self.shapes: shape = self.shapes[selected] @@ -737,7 +737,7 @@ def add_node(self, x: float, y: float) -> None: self.core.canvas_nodes[core_node.id] = node self.nodes[node.id] = node - def width_and_height(self): + def width_and_height(self) -> Tuple[int, int]: """ retrieve canvas width and height in pixels """ @@ -753,8 +753,8 @@ def get_wallpaper_image(self) -> Image.Image: return image def draw_wallpaper( - self, image: ImageTk.PhotoImage, x: float = None, y: float = None - ): + self, image: PhotoImage, x: float = None, y: float = None + ) -> None: if x is None and y is None: x1, y1, x2, y2 = self.bbox(self.rect) x = (x1 + x2) / 2 @@ -762,7 +762,7 @@ def draw_wallpaper( self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER) self.wallpaper_drawn = image - def wallpaper_upper_left(self): + def wallpaper_upper_left(self) -> None: self.delete(self.wallpaper_id) # create new scaled image, cropped if needed @@ -775,7 +775,7 @@ def wallpaper_upper_left(self): if image.height > height: cropy = image.height cropped = image.crop((0, 0, cropx, cropy)) - image = ImageTk.PhotoImage(cropped) + image = PhotoImage(cropped) # draw on canvas x1, y1, _, _ = self.bbox(self.rect) @@ -783,7 +783,7 @@ def wallpaper_upper_left(self): y = (cropy / 2) + y1 self.draw_wallpaper(image, x, y) - def wallpaper_center(self): + def wallpaper_center(self) -> None: """ place the image at the center of canvas """ @@ -803,26 +803,26 @@ def wallpaper_center(self): x2 = image.width - cropx y2 = image.height - cropy cropped = image.crop((x1, y1, x2, y2)) - image = ImageTk.PhotoImage(cropped) + image = PhotoImage(cropped) self.draw_wallpaper(image) - def wallpaper_scaled(self): + def wallpaper_scaled(self) -> None: """ scale image based on canvas dimension """ self.delete(self.wallpaper_id) canvas_w, canvas_h = self.width_and_height() image = self.wallpaper.resize((int(canvas_w), int(canvas_h)), Image.ANTIALIAS) - image = ImageTk.PhotoImage(image) + image = PhotoImage(image) self.draw_wallpaper(image) - def resize_to_wallpaper(self): + def resize_to_wallpaper(self) -> None: self.delete(self.wallpaper_id) - image = ImageTk.PhotoImage(self.wallpaper) + image = PhotoImage(self.wallpaper) self.redraw_canvas((image.width(), image.height())) self.draw_wallpaper(image) - def redraw_canvas(self, dimensions: Tuple[int, int] = None): + def redraw_canvas(self, dimensions: Tuple[int, int] = None) -> None: logging.debug("redrawing canvas to dimensions: %s", dimensions) # reset scale and move back to original position @@ -843,7 +843,7 @@ def redraw_canvas(self, dimensions: Tuple[int, int] = None): self.draw_grid() self.app.canvas.show_grid.click_handler() - def redraw_wallpaper(self): + def redraw_wallpaper(self) -> None: if self.adjust_to_dim.get(): logging.debug("drawing wallpaper to canvas dimensions") self.resize_to_wallpaper() @@ -864,7 +864,7 @@ def organize(self) -> None: for tag in tags.ORGANIZE_TAGS: self.tag_raise(tag) - def set_wallpaper(self, filename: Optional[str]): + def set_wallpaper(self, filename: Optional[str]) -> None: logging.debug("setting wallpaper: %s", filename) if filename: img = Image.open(filename) @@ -880,7 +880,7 @@ def set_wallpaper(self, filename: Optional[str]): def is_selection_mode(self) -> bool: return self.mode == GraphMode.SELECT - def create_edge(self, source: CanvasNode, dest: CanvasNode): + def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None: """ create an edge between source node and destination node """ @@ -894,7 +894,7 @@ def create_edge(self, source: CanvasNode, dest: CanvasNode): self.nodes[dest.id].edges.add(edge) self.core.create_link(edge, source, dest) - def copy(self): + def copy(self) -> None: if self.core.is_runtime(): logging.debug("copy is disabled during runtime state") return @@ -905,7 +905,7 @@ def copy(self): canvas_node = self.nodes[node_id] self.to_copy.append(canvas_node) - def paste(self): + def paste(self) -> None: if self.core.is_runtime(): logging.debug("paste is disabled during runtime state") return @@ -972,11 +972,11 @@ def paste(self): else: asym_iface1 = None if iface1_id: - asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface1 = Interface(id=iface1_id) asym_iface2 = None if iface2_id: - asym_iface2 = core_pb2.Interface(id=iface2_id) - copy_edge.asymmetric_link = core_pb2.Link( + asym_iface2 = Interface(id=iface2_id) + copy_edge.asymmetric_link = Link( node1_id=copy_link.node2_id, node2_id=copy_link.node1_id, iface1=asym_iface1, @@ -990,7 +990,7 @@ def paste(self): ) self.tag_raise(tags.NODE) - def scale_graph(self): + def scale_graph(self) -> None: for nid, canvas_node in self.nodes.items(): img = None if NodeUtils.is_custom( diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 3ba4b3f7c..f936bc79f 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -1,12 +1,14 @@ import functools import logging import tkinter as tk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import grpc +from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 -from core.api.grpc.core_pb2 import NodeType +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Interface, Node, NodeType +from core.api.grpc.services_pb2 import NodeServiceData from core.gui import themes from core.gui.dialogs.emaneconfig import EmaneConfigDialog from core.gui.dialogs.mobilityconfig import MobilityConfigDialog @@ -15,36 +17,31 @@ from core.gui.dialogs.nodeservice import NodeServiceDialog from core.gui.dialogs.wlanconfig import WlanConfigDialog from core.gui.graph import tags -from core.gui.graph.edges import CanvasEdge +from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils if TYPE_CHECKING: from core.gui.app import Application - from PIL.ImageTk import PhotoImage + from core.gui.graph.graph import CanvasGraph -NODE_TEXT_OFFSET = 5 +NODE_TEXT_OFFSET: int = 5 class CanvasNode: def __init__( - self, - app: "Application", - x: float, - y: float, - core_node: core_pb2.Node, - image: "PhotoImage", + self, app: "Application", x: float, y: float, core_node: Node, image: PhotoImage ): - self.app = app - self.canvas = app.canvas - self.image = image - self.core_node = core_node - self.id = self.canvas.create_image( + self.app: "Application" = app + self.canvas: "CanvasGraph" = app.canvas + self.image: PhotoImage = image + self.core_node: Node = core_node + self.id: int = self.canvas.create_image( x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE ) label_y = self._get_label_y() - self.text_id = self.canvas.create_text( + self.text_id: int = self.canvas.create_text( x, label_y, text=self.core_node.name, @@ -53,21 +50,21 @@ def __init__( fill="#0000CD", state=self.canvas.show_node_labels.state(), ) - self.tooltip = CanvasTooltip(self.canvas) - self.edges = set() - self.ifaces = {} - self.wireless_edges = set() - self.antennas = [] - self.antenna_images = {} + self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas) + self.edges: Set[CanvasEdge] = set() + self.ifaces: Dict[int, Interface] = {} + self.wireless_edges: Set[CanvasWirelessEdge] = set() + self.antennas: List[int] = [] + self.antenna_images: Dict[int, PhotoImage] = {} # possible configurations - self.emane_model_configs = {} - self.wlan_config = {} - self.mobility_config = {} - self.service_configs = {} - self.service_file_configs = {} - self.config_service_configs = {} + self.emane_model_configs: Dict[Tuple[str, Optional[int]], ConfigOption] = {} + self.wlan_config: Dict[str, ConfigOption] = {} + self.mobility_config: Dict[str, ConfigOption] = {} + self.service_configs: Dict[str, NodeServiceData] = {} + self.service_file_configs: Dict[str, Dict[str, str]] = {} + self.config_service_configs: Dict[str, Any] = {} self.setup_bindings() - self.context = tk.Menu(self.canvas) + self.context: tk.Menu = tk.Menu(self.canvas) themes.style_menu(self.context) def next_iface_id(self) -> int: @@ -76,19 +73,19 @@ def next_iface_id(self) -> int: i += 1 return i - def setup_bindings(self): + def setup_bindings(self) -> None: self.canvas.tag_bind(self.id, "", self.double_click) self.canvas.tag_bind(self.id, "", self.on_enter) self.canvas.tag_bind(self.id, "", self.on_leave) self.canvas.tag_bind(self.id, "", self.show_context) - def delete(self): + def delete(self) -> None: logging.debug("Delete canvas node for %s", self.core_node) self.canvas.delete(self.id) self.canvas.delete(self.text_id) self.delete_antennas() - def add_antenna(self): + def add_antenna(self) -> None: x, y = self.canvas.coords(self.id) offset = len(self.antennas) * 8 * self.app.app_scale img = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE) @@ -102,7 +99,7 @@ def add_antenna(self): self.antennas.append(antenna_id) self.antenna_images[antenna_id] = img - def delete_antenna(self): + def delete_antenna(self) -> None: """ delete one antenna """ @@ -112,7 +109,7 @@ def delete_antenna(self): self.canvas.delete(antenna_id) self.antenna_images.pop(antenna_id, None) - def delete_antennas(self): + def delete_antennas(self) -> None: """ delete all antennas """ @@ -122,30 +119,30 @@ def delete_antennas(self): self.antennas.clear() self.antenna_images.clear() - def redraw(self): + def redraw(self) -> None: self.canvas.itemconfig(self.id, image=self.image) self.canvas.itemconfig(self.text_id, text=self.core_node.name) for edge in self.edges: edge.redraw() - def _get_label_y(self): + def _get_label_y(self) -> int: image_box = self.canvas.bbox(self.id) return image_box[3] + NODE_TEXT_OFFSET - def scale_text(self): + def scale_text(self) -> None: text_bound = self.canvas.bbox(self.text_id) prev_y = (text_bound[3] + text_bound[1]) / 2 new_y = self._get_label_y() self.canvas.move(self.text_id, 0, new_y - prev_y) - def move(self, x: int, y: int): + def move(self, x: int, y: int) -> None: x, y = self.canvas.get_scaled_coords(x, y) current_x, current_y = self.canvas.coords(self.id) x_offset = x - current_x y_offset = y - current_y self.motion(x_offset, y_offset, update=False) - def motion(self, x_offset: int, y_offset: int, update: bool = True): + def motion(self, x_offset: float, y_offset: float, update: bool = True) -> None: original_position = self.canvas.coords(self.id) self.canvas.move(self.id, x_offset, y_offset) pos = self.canvas.coords(self.id) @@ -177,7 +174,7 @@ def motion(self, x_offset: int, y_offset: int, update: bool = True): if self.app.core.is_runtime() and update: self.app.core.edit_node(self.core_node) - def on_enter(self, event: tk.Event): + def on_enter(self, event: tk.Event) -> None: if self.app.core.is_runtime() and self.app.core.observer: self.tooltip.text.set("waiting...") self.tooltip.on_enter(event) @@ -187,10 +184,10 @@ def on_enter(self, event: tk.Event): except grpc.RpcError as e: self.app.show_grpc_exception("Observer Error", e) - def on_leave(self, event: tk.Event): + def on_leave(self, event: tk.Event) -> None: self.tooltip.on_leave(event) - def double_click(self, event: tk.Event): + def double_click(self, event: tk.Event) -> None: if self.app.core.is_runtime(): self.canvas.core.launch_terminal(self.core_node.id) else: @@ -270,37 +267,37 @@ def canvas_copy(self) -> None: self.canvas.selection[self.id] = self self.canvas.copy() - def show_config(self): + def show_config(self) -> None: dialog = NodeConfigDialog(self.app, self) dialog.show() - def show_wlan_config(self): + def show_wlan_config(self) -> None: dialog = WlanConfigDialog(self.app, self) if not dialog.has_error: dialog.show() - def show_mobility_config(self): + def show_mobility_config(self) -> None: dialog = MobilityConfigDialog(self.app, self) if not dialog.has_error: dialog.show() - def show_mobility_player(self): + def show_mobility_player(self) -> None: mobility_player = self.app.core.mobility_players[self.core_node.id] mobility_player.show() - def show_emane_config(self): + def show_emane_config(self) -> None: dialog = EmaneConfigDialog(self.app, self) dialog.show() - def show_services(self): + def show_services(self) -> None: dialog = NodeServiceDialog(self.app, self) dialog.show() - def show_config_services(self): + def show_config_services(self) -> None: dialog = NodeConfigServiceDialog(self.app, self) dialog.show() - def has_emane_link(self, iface_id: int) -> core_pb2.Node: + def has_emane_link(self, iface_id: int) -> Node: result = None for edge in self.edges: if self.id == edge.src: @@ -317,14 +314,14 @@ def has_emane_link(self, iface_id: int) -> core_pb2.Node: break return result - def wireless_link_selected(self): + def wireless_link_selected(self) -> None: nodes = [x for x in self.canvas.selection if x in self.canvas.nodes] for node_id in nodes: canvas_node = self.canvas.nodes[node_id] self.canvas.create_edge(self, canvas_node) self.canvas.clear_selection() - def scale_antennas(self): + def scale_antennas(self) -> None: for i in range(len(self.antennas)): antenna_id = self.antennas[i] image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE) diff --git a/daemon/core/gui/graph/shape.py b/daemon/core/gui/graph/shape.py index 70f67d14e..36298655e 100644 --- a/daemon/core/gui/graph/shape.py +++ b/daemon/core/gui/graph/shape.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, Dict, List, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Union from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags @@ -23,17 +23,17 @@ def __init__( bold: bool = False, italic: bool = False, underline: bool = False, - ): - self.text = text - self.font = font - self.font_size = font_size - self.text_color = text_color - self.fill_color = fill_color - self.border_color = border_color - self.border_width = border_width - self.bold = bold - self.italic = italic - self.underline = underline + ) -> None: + self.text: str = text + self.font: str = font + self.font_size: int = font_size + self.text_color: str = text_color + self.fill_color: str = fill_color + self.border_color: str = border_color + self.border_width: int = border_width + self.bold: bool = bold + self.italic: bool = italic + self.underline: bool = underline class Shape: @@ -47,29 +47,29 @@ def __init__( x2: float = None, y2: float = None, data: AnnotationData = None, - ): - self.app = app - self.canvas = canvas - self.shape_type = shape_type - self.id = None - self.text_id = None - self.x1 = x1 - self.y1 = y1 + ) -> None: + self.app: "Application" = app + self.canvas: "CanvasGraph" = canvas + self.shape_type: ShapeType = shape_type + self.id: Optional[int] = None + self.text_id: Optional[int] = None + self.x1: float = x1 + self.y1: float = y1 if x2 is None: x2 = x1 - self.x2 = x2 + self.x2: float = x2 if y2 is None: y2 = y1 - self.y2 = y2 + self.y2: float = y2 if data is None: - self.created = False - self.shape_data = AnnotationData() + self.created: bool = False + self.shape_data: AnnotationData = AnnotationData() else: - self.created = True + self.created: bool = True self.shape_data = data self.draw() - def draw(self): + def draw(self) -> None: if self.created: dash = None else: @@ -127,7 +127,7 @@ def get_font(self) -> List[Union[int, str]]: font.append("underline") return font - def draw_shape_text(self): + def draw_shape_text(self) -> None: if self.shape_data.text: x = (self.x1 + self.x2) / 2 y = self.y1 + 1.5 * self.shape_data.font_size @@ -142,18 +142,18 @@ def draw_shape_text(self): state=self.canvas.show_annotations.state(), ) - def shape_motion(self, x1: float, y1: float): + def shape_motion(self, x1: float, y1: float) -> None: self.canvas.coords(self.id, self.x1, self.y1, x1, y1) - def shape_complete(self, x: float, y: float): + def shape_complete(self, x: float, y: float) -> None: self.canvas.organize() s = ShapeDialog(self.app, self) s.show() - def disappear(self): + def disappear(self) -> None: self.canvas.delete(self.id) - def motion(self, x_offset: float, y_offset: float): + def motion(self, x_offset: float, y_offset: float) -> None: original_position = self.canvas.coords(self.id) self.canvas.move(self.id, x_offset, y_offset) coords = self.canvas.coords(self.id) @@ -166,7 +166,7 @@ def motion(self, x_offset: float, y_offset: float): if self.text_id is not None: self.canvas.move(self.text_id, x_offset, y_offset) - def delete(self): + def delete(self) -> None: logging.debug("Delete shape, id(%s)", self.id) self.canvas.delete(self.id) self.canvas.delete(self.text_id) diff --git a/daemon/core/gui/graph/shapeutils.py b/daemon/core/gui/graph/shapeutils.py index ce2b7f96f..2b62a46c2 100644 --- a/daemon/core/gui/graph/shapeutils.py +++ b/daemon/core/gui/graph/shapeutils.py @@ -1,4 +1,5 @@ import enum +from typing import Set class ShapeType(enum.Enum): @@ -8,7 +9,7 @@ class ShapeType(enum.Enum): TEXT = "text" -SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE} +SHAPES: Set[ShapeType] = {ShapeType.OVAL, ShapeType.RECTANGLE} def is_draw_shape(shape_type: ShapeType) -> bool: diff --git a/daemon/core/gui/graph/tags.py b/daemon/core/gui/graph/tags.py index c07211931..b7b355172 100644 --- a/daemon/core/gui/graph/tags.py +++ b/daemon/core/gui/graph/tags.py @@ -1,17 +1,19 @@ -ANNOTATION = "annotation" -GRIDLINE = "gridline" -SHAPE = "shape" -SHAPE_TEXT = "shapetext" -EDGE = "edge" -LINK_LABEL = "linklabel" -WIRELESS_EDGE = "wireless" -ANTENNA = "antenna" -NODE_LABEL = "nodename" -NODE = "node" -WALLPAPER = "wallpaper" -SELECTION = "selectednodes" -MARKER = "marker" -ORGANIZE_TAGS = [ +from typing import List + +ANNOTATION: str = "annotation" +GRIDLINE: str = "gridline" +SHAPE: str = "shape" +SHAPE_TEXT: str = "shapetext" +EDGE: str = "edge" +LINK_LABEL: str = "linklabel" +WIRELESS_EDGE: str = "wireless" +ANTENNA: str = "antenna" +NODE_LABEL: str = "nodename" +NODE: str = "node" +WALLPAPER: str = "wallpaper" +SELECTION: str = "selectednodes" +MARKER: str = "marker" +ORGANIZE_TAGS: List[str] = [ WALLPAPER, GRIDLINE, SHAPE, @@ -25,7 +27,7 @@ SELECTION, MARKER, ] -RESET_TAGS = [ +RESET_TAGS: List[str] = [ EDGE, NODE, NODE_LABEL, diff --git a/daemon/core/gui/graph/tooltip.py b/daemon/core/gui/graph/tooltip.py index a2193901b..6e4aa62f5 100644 --- a/daemon/core/gui/graph/tooltip.py +++ b/daemon/core/gui/graph/tooltip.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple from core.gui.themes import Styles @@ -27,39 +27,45 @@ def __init__( self, canvas: "CanvasGraph", *, - pad=(5, 3, 5, 3), + pad: Tuple[int, int, int, int] = (5, 3, 5, 3), waittime: int = 400, wraplength: int = 600 - ): + ) -> None: # in miliseconds, originally 500 - self.waittime = waittime + self.waittime: int = waittime # in pixels, originally 180 - self.wraplength = wraplength - self.canvas = canvas - self.text = tk.StringVar() - self.pad = pad - self.id = None - self.tw = None - - def on_enter(self, event: tk.Event = None): + self.wraplength: int = wraplength + self.canvas: "CanvasGraph" = canvas + self.text: tk.StringVar = tk.StringVar() + self.pad: Tuple[int, int, int, int] = pad + self.id: Optional[str] = None + self.tw: Optional[tk.Toplevel] = None + + def on_enter(self, event: tk.Event = None) -> None: self.schedule() - def on_leave(self, event: tk.Event = None): + def on_leave(self, event: tk.Event = None) -> None: self.unschedule() self.hide() - def schedule(self): + def schedule(self) -> None: self.unschedule() self.id = self.canvas.after(self.waittime, self.show) - def unschedule(self): + def unschedule(self) -> None: id_ = self.id self.id = None if id_: self.canvas.after_cancel(id_) - def show(self, event: tk.Event = None): - def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): + def show(self, event: tk.Event = None) -> None: + def tip_pos_calculator( + canvas: "CanvasGraph", + label: ttk.Label, + *, + tip_delta: Tuple[int, int] = (10, 5), + pad: Tuple[int, int, int, int] = (5, 3, 5, 3) + ): c = canvas s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight() width, height = ( @@ -108,7 +114,7 @@ def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): x, y = tip_pos_calculator(canvas, label, pad=pad) self.tw.wm_geometry("+%d+%d" % (x, y)) - def hide(self): + def hide(self) -> None: if self.tw: self.tw.destroy() self.tw = None From 527d34e3746d781908abe6a256b90db89dd77d9a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 11:04:33 -0700 Subject: [PATCH 055/210] pygui: added type hinting to class variables for core.gui.dialogs --- daemon/core/gui/coreclient.py | 4 +- daemon/core/gui/dialogs/about.py | 4 +- daemon/core/gui/dialogs/alerts.py | 18 +-- daemon/core/gui/dialogs/canvassizeandscale.py | 49 +++--- daemon/core/gui/dialogs/canvaswallpaper.py | 43 ++--- daemon/core/gui/dialogs/colorpicker.py | 52 +++--- .../core/gui/dialogs/configserviceconfig.py | 115 +++++++------- daemon/core/gui/dialogs/copyserviceconfig.py | 15 +- daemon/core/gui/dialogs/customnodes.py | 72 +++++---- daemon/core/gui/dialogs/dialog.py | 12 +- daemon/core/gui/dialogs/emaneconfig.py | 78 ++++----- daemon/core/gui/dialogs/emaneinstall.py | 4 +- daemon/core/gui/dialogs/error.py | 8 +- daemon/core/gui/dialogs/executepython.py | 20 +-- daemon/core/gui/dialogs/find.py | 6 +- daemon/core/gui/dialogs/hooks.py | 40 ++--- daemon/core/gui/dialogs/ipdialog.py | 18 +-- daemon/core/gui/dialogs/linkconfig.py | 57 +++---- daemon/core/gui/dialogs/macdialog.py | 2 +- daemon/core/gui/dialogs/mobilityconfig.py | 29 ++-- daemon/core/gui/dialogs/mobilityplayer.py | 72 +++++---- daemon/core/gui/dialogs/nodeconfig.py | 50 +++--- daemon/core/gui/dialogs/nodeconfigservice.py | 32 ++-- daemon/core/gui/dialogs/nodeservice.py | 28 ++-- daemon/core/gui/dialogs/observers.py | 38 ++--- daemon/core/gui/dialogs/preferences.py | 28 ++-- daemon/core/gui/dialogs/runtool.py | 10 +- daemon/core/gui/dialogs/servers.py | 42 ++--- daemon/core/gui/dialogs/serviceconfig.py | 149 +++++++++--------- daemon/core/gui/dialogs/sessionoptions.py | 17 +- daemon/core/gui/dialogs/sessions.py | 19 +-- daemon/core/gui/dialogs/shapemod.py | 63 ++++---- daemon/core/gui/dialogs/throughput.py | 31 ++-- daemon/core/gui/dialogs/wlanconfig.py | 46 +++--- daemon/core/gui/graph/node.py | 6 +- daemon/core/gui/menubar.py | 2 +- daemon/core/gui/nodeutils.py | 2 +- 37 files changed, 666 insertions(+), 615 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 5e1bf4c28..39ee486ae 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -38,7 +38,7 @@ from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig from core.api.grpc.wlan_pb2 import WlanConfig from core.gui import appconfig -from core.gui.appconfig import CoreServer +from core.gui.appconfig import CoreServer, Observer from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.mobilityplayer import MobilityPlayer @@ -75,7 +75,7 @@ def __init__(self, app: "Application", proxy: bool) -> None: # loaded configuration data self.servers: Dict[str, CoreServer] = {} self.custom_nodes: Dict[str, NodeDraw] = {} - self.custom_observers: Dict[str, str] = {} + self.custom_observers: Dict[str, Observer] = {} self.read_config() # helpers diff --git a/daemon/core/gui/dialogs/about.py b/daemon/core/gui/dialogs/about.py index 2e6491691..fa96e218a 100644 --- a/daemon/core/gui/dialogs/about.py +++ b/daemon/core/gui/dialogs/about.py @@ -35,11 +35,11 @@ class AboutDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "About CORE") self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index a0c3e68bd..00ef1e8c6 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -3,9 +3,9 @@ """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional -from core.api.grpc.core_pb2 import ExceptionLevel +from core.api.grpc.core_pb2 import ExceptionEvent, ExceptionLevel from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import CodeText @@ -15,14 +15,14 @@ class AlertsDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Alerts") - self.tree = None - self.codetext = None - self.alarm_map = {} + self.tree: Optional[ttk.Treeview] = None + self.codetext: Optional[CodeText] = None + self.alarm_map: Dict[int, ExceptionEvent] = {} self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) @@ -97,13 +97,13 @@ def draw(self): button = ttk.Button(frame, text="Close", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def reset_alerts(self): + def reset_alerts(self) -> None: self.codetext.text.delete("1.0", tk.END) for item in self.tree.get_children(): self.tree.delete(item) self.app.statusbar.core_alarms.clear() - def click_select(self, event: tk.Event): + def click_select(self, event: tk.Event) -> None: current = self.tree.selection()[0] alarm = self.alarm_map[current] self.codetext.text.config(state=tk.NORMAL) diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index 6a63a1aee..b93bd9203 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -7,38 +7,43 @@ from core.gui import validation from core.gui.dialogs.dialog import Dialog +from core.gui.graph.graph import CanvasGraph from core.gui.themes import FRAME_PAD, PADX, PADY if TYPE_CHECKING: from core.gui.app import Application -PIXEL_SCALE = 100 +PIXEL_SCALE: int = 100 class SizeAndScaleDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: """ create an instance for size and scale object """ super().__init__(app, "Canvas Size and Scale") - self.canvas = self.app.canvas - self.section_font = font.Font(weight="bold") + self.canvas: CanvasGraph = self.app.canvas + self.section_font: font.Font = font.Font(weight="bold") width, height = self.canvas.current_dimensions - self.pixel_width = tk.IntVar(value=width) - self.pixel_height = tk.IntVar(value=height) + self.pixel_width: tk.IntVar = tk.IntVar(value=width) + self.pixel_height: tk.IntVar = tk.IntVar(value=height) location = self.app.core.location - self.x = tk.DoubleVar(value=location.x) - self.y = tk.DoubleVar(value=location.y) - self.lat = tk.DoubleVar(value=location.lat) - self.lon = tk.DoubleVar(value=location.lon) - self.alt = tk.DoubleVar(value=location.alt) - self.scale = tk.DoubleVar(value=location.scale) - self.meters_width = tk.IntVar(value=width / PIXEL_SCALE * location.scale) - self.meters_height = tk.IntVar(value=height / PIXEL_SCALE * location.scale) - self.save_default = tk.BooleanVar(value=False) + self.x: tk.DoubleVar = tk.DoubleVar(value=location.x) + self.y: tk.DoubleVar = tk.DoubleVar(value=location.y) + self.lat: tk.DoubleVar = tk.DoubleVar(value=location.lat) + self.lon: tk.DoubleVar = tk.DoubleVar(value=location.lon) + self.alt: tk.DoubleVar = tk.DoubleVar(value=location.alt) + self.scale: tk.DoubleVar = tk.DoubleVar(value=location.scale) + self.meters_width: tk.IntVar = tk.IntVar( + value=width / PIXEL_SCALE * location.scale + ) + self.meters_height: tk.IntVar = tk.IntVar( + value=height / PIXEL_SCALE * location.scale + ) + self.save_default: tk.BooleanVar = tk.BooleanVar(value=False) self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_size() self.draw_scale() @@ -47,7 +52,7 @@ def draw(self): self.draw_spacer() self.draw_buttons() - def draw_size(self): + def draw_size(self) -> None: label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -84,7 +89,7 @@ def draw_size(self): label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") - def draw_scale(self): + def draw_scale(self) -> None: label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -99,7 +104,7 @@ def draw_scale(self): label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") - def draw_reference_point(self): + def draw_reference_point(self) -> None: label_frame = ttk.Labelframe( self.top, text="Reference Point", padding=FRAME_PAD ) @@ -150,13 +155,13 @@ def draw_reference_point(self): entry = validation.FloatEntry(frame, textvariable=self.alt) entry.grid(row=0, column=5, sticky="ew") - def draw_save_as_default(self): + def draw_save_as_default(self) -> None: button = ttk.Checkbutton( self.top, text="Save as default?", variable=self.save_default ) button.grid(sticky="w", pady=PADY) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) @@ -168,7 +173,7 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: width, height = self.pixel_width.get(), self.pixel_height.get() self.canvas.redraw_canvas((width, height)) if self.canvas.wallpaper: diff --git a/daemon/core/gui/dialogs/canvaswallpaper.py b/daemon/core/gui/dialogs/canvaswallpaper.py index 5e8460bec..8a1e71d87 100644 --- a/daemon/core/gui/dialogs/canvaswallpaper.py +++ b/daemon/core/gui/dialogs/canvaswallpaper.py @@ -4,10 +4,11 @@ import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional from core.gui.appconfig import BACKGROUNDS_PATH from core.gui.dialogs.dialog import Dialog +from core.gui.graph.graph import CanvasGraph from core.gui.images import Images from core.gui.themes import PADX, PADY from core.gui.widgets import image_chooser @@ -17,20 +18,22 @@ class CanvasWallpaperDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: """ create an instance of CanvasWallpaper object """ super().__init__(app, "Canvas Background") - self.canvas = self.app.canvas - self.scale_option = tk.IntVar(value=self.canvas.scale_option.get()) - self.adjust_to_dim = tk.BooleanVar(value=self.canvas.adjust_to_dim.get()) - self.filename = tk.StringVar(value=self.canvas.wallpaper_file) - self.image_label = None - self.options = [] + self.canvas: CanvasGraph = self.app.canvas + self.scale_option: tk.IntVar = tk.IntVar(value=self.canvas.scale_option.get()) + self.adjust_to_dim: tk.BooleanVar = tk.BooleanVar( + value=self.canvas.adjust_to_dim.get() + ) + self.filename: tk.StringVar = tk.StringVar(value=self.canvas.wallpaper_file) + self.image_label: Optional[ttk.Label] = None + self.options: List[ttk.Radiobutton] = [] self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_image() self.draw_image_label() @@ -40,19 +43,19 @@ def draw(self): self.draw_spacer() self.draw_buttons() - def draw_image(self): + def draw_image(self) -> None: self.image_label = ttk.Label( self.top, text="(image preview)", width=32, anchor=tk.CENTER ) self.image_label.grid(pady=PADY) - def draw_image_label(self): + def draw_image_label(self) -> None: label = ttk.Label(self.top, text="Image filename: ") label.grid(sticky="ew") if self.filename.get(): self.draw_preview() - def draw_image_selection(self): + def draw_image_selection(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=1) @@ -69,7 +72,7 @@ def draw_image_selection(self): button = ttk.Button(frame, text="Clear", command=self.click_clear) button.grid(row=0, column=2, sticky="ew") - def draw_options(self): + def draw_options(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) @@ -101,7 +104,7 @@ def draw_options(self): button.grid(row=0, column=3, sticky="ew") self.options.append(button) - def draw_additional_options(self): + def draw_additional_options(self) -> None: checkbutton = ttk.Checkbutton( self.top, text="Adjust canvas size to image dimensions", @@ -110,7 +113,7 @@ def draw_additional_options(self): ) checkbutton.grid(sticky="ew", padx=PADX) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(pady=PADY, sticky="ew") frame.columnconfigure(0, weight=1) @@ -122,18 +125,18 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_open_image(self): + def click_open_image(self) -> None: filename = image_chooser(self, BACKGROUNDS_PATH) if filename: self.filename.set(filename) self.draw_preview() - def draw_preview(self): + def draw_preview(self) -> None: image = Images.create(self.filename.get(), 250, 135) self.image_label.config(image=image) self.image_label.image = image - def click_clear(self): + def click_clear(self) -> None: """ delete like shown in image link entry if there is any """ @@ -143,7 +146,7 @@ def click_clear(self): self.image_label.config(image="", width=32) self.image_label.image = None - def click_adjust_canvas(self): + def click_adjust_canvas(self) -> None: # deselect all radio buttons and grey them out if self.adjust_to_dim.get(): self.scale_option.set(0) @@ -155,7 +158,7 @@ def click_adjust_canvas(self): for option in self.options: option.config(state=tk.NORMAL) - def click_apply(self): + def click_apply(self) -> None: self.canvas.scale_option.set(self.scale_option.get()) self.canvas.adjust_to_dim.set(self.adjust_to_dim.get()) self.canvas.show_grid.click_handler() diff --git a/daemon/core/gui/dialogs/colorpicker.py b/daemon/core/gui/dialogs/colorpicker.py index b1968cd43..908b8acb7 100644 --- a/daemon/core/gui/dialogs/colorpicker.py +++ b/daemon/core/gui/dialogs/colorpicker.py @@ -3,7 +3,7 @@ """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple from core.gui import validation from core.gui.dialogs.dialog import Dialog @@ -18,23 +18,23 @@ def __init__( self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000" ): super().__init__(app, "Color Picker", master=master) - self.red_entry = None - self.blue_entry = None - self.green_entry = None - self.hex_entry = None - self.red_label = None - self.green_label = None - self.blue_label = None - self.display = None - self.color = initcolor + self.red_entry: Optional[validation.RgbEntry] = None + self.blue_entry: Optional[validation.RgbEntry] = None + self.green_entry: Optional[validation.RgbEntry] = None + self.hex_entry: Optional[validation.HexEntry] = None + self.red_label: Optional[ttk.Label] = None + self.green_label: Optional[ttk.Label] = None + self.blue_label: Optional[ttk.Label] = None + self.display: Optional[tk.Frame] = None + self.color: str = initcolor red, green, blue = self.get_rgb(initcolor) - self.red = tk.IntVar(value=red) - self.blue = tk.IntVar(value=blue) - self.green = tk.IntVar(value=green) - self.hex = tk.StringVar(value=initcolor) - self.red_scale = tk.IntVar(value=red) - self.green_scale = tk.IntVar(value=green) - self.blue_scale = tk.IntVar(value=blue) + self.red: tk.IntVar = tk.IntVar(value=red) + self.blue: tk.IntVar = tk.IntVar(value=blue) + self.green: tk.IntVar = tk.IntVar(value=green) + self.hex: tk.StringVar = tk.StringVar(value=initcolor) + self.red_scale: tk.IntVar = tk.IntVar(value=red) + self.green_scale: tk.IntVar = tk.IntVar(value=green) + self.blue_scale: tk.IntVar = tk.IntVar(value=blue) self.draw() self.set_bindings() @@ -42,7 +42,7 @@ def askcolor(self) -> str: self.show() return self.color - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(3, weight=1) @@ -136,7 +136,7 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def set_bindings(self): + def set_bindings(self) -> None: self.red_entry.bind("", lambda x: self.current_focus("rgb")) self.green_entry.bind("", lambda x: self.current_focus("rgb")) self.blue_entry.bind("", lambda x: self.current_focus("rgb")) @@ -146,7 +146,7 @@ def set_bindings(self): self.blue.trace_add("write", self.update_color) self.hex.trace_add("write", self.update_color) - def button_ok(self): + def button_ok(self) -> None: self.color = self.hex.get() self.destroy() @@ -159,10 +159,10 @@ def get_hex(self) -> str: green = self.green_entry.get() return "#%02x%02x%02x" % (int(red), int(green), int(blue)) - def current_focus(self, focus: str): + def current_focus(self, focus: str) -> None: self.focus = focus - def update_color(self, arg1=None, arg2=None, arg3=None): + def update_color(self, arg1=None, arg2=None, arg3=None) -> None: if self.focus == "rgb": red = self.red_entry.get() blue = self.blue_entry.get() @@ -184,7 +184,7 @@ def update_color(self, arg1=None, arg2=None, arg3=None): 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): + def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar) -> None: color_var.set(var.get()) self.focus = "rgb" self.update_color() @@ -194,17 +194,17 @@ def set_scale(self, red: int, green: int, blue: int): self.green_scale.set(green) self.blue_scale.set(blue) - def set_entry(self, red: int, green: int, blue: int): + def set_entry(self, red: int, green: int, blue: int) -> None: self.red.set(red) self.green.set(green) self.blue.set(blue) - def set_label(self, red: str, green: str, blue: str): + def set_label(self, red: str, green: str, blue: str) -> None: self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0)) self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0)) self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue))) - def get_rgb(self, hex_code: str) -> [int, int, int]: + def get_rgb(self, hex_code: str) -> Tuple[int, int, int]: """ convert a valid hex code to RGB values """ diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 42041a8e5..c2d42ee43 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -4,10 +4,11 @@ import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Dict, List, Optional, Set import grpc +from core.api.grpc.common_pb2 import ConfigOption from core.api.grpc.services_pb2 import ServiceValidationMode from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY @@ -16,6 +17,7 @@ if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode + from core.gui.coreclient import CoreClient class ConfigServiceConfigDialog(Dialog): @@ -26,56 +28,53 @@ def __init__( service_name: str, canvas_node: "CanvasNode", node_id: int, - ): + ) -> None: title = f"{service_name} Config Service" super().__init__(app, title, master=master) - self.core = app.core - self.canvas_node = canvas_node - self.node_id = node_id - self.service_name = service_name - self.radiovar = tk.IntVar() + self.core: "CoreClient" = app.core + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = node_id + self.service_name: str = service_name + self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(2) - self.directories = [] - self.templates = [] - self.dependencies = [] - self.executables = [] - self.startup_commands = [] - self.validation_commands = [] - self.shutdown_commands = [] - self.default_startup = [] - self.default_validate = [] - self.default_shutdown = [] - self.validation_mode = None - self.validation_time = None - self.validation_period = tk.StringVar() - self.modes = [] - self.mode_configs = {} - - self.notebook = None - self.templates_combobox = None - self.modes_combobox = None - self.startup_commands_listbox = None - self.shutdown_commands_listbox = None - self.validate_commands_listbox = None - self.validation_time_entry = None - self.validation_mode_entry = None - self.template_text = None - self.validation_period_entry = None - self.original_service_files = {} - self.temp_service_files = {} - self.modified_files = set() - self.config_frame = None - self.default_config = None - self.config = None - - self.has_error = False - + self.directories: List[str] = [] + self.templates: List[str] = [] + self.dependencies: List[str] = [] + self.executables: List[str] = [] + self.startup_commands: List[str] = [] + self.validation_commands: List[str] = [] + self.shutdown_commands: List[str] = [] + self.default_startup: List[str] = [] + self.default_validate: List[str] = [] + self.default_shutdown: List[str] = [] + self.validation_mode: Optional[ServiceValidationMode] = None + self.validation_time: Optional[int] = None + self.validation_period: tk.StringVar = tk.StringVar() + self.modes: List[str] = [] + self.mode_configs: Dict[str, str] = {} + + self.notebook: Optional[ttk.Notebook] = None + self.templates_combobox: Optional[ttk.Combobox] = None + self.modes_combobox: Optional[ttk.Combobox] = None + self.startup_commands_listbox: Optional[tk.Listbox] = None + self.shutdown_commands_listbox: Optional[tk.Listbox] = None + self.validate_commands_listbox: Optional[tk.Listbox] = None + self.validation_time_entry: Optional[ttk.Entry] = None + self.validation_mode_entry: Optional[ttk.Entry] = None + self.template_text: Optional[CodeText] = None + self.validation_period_entry: Optional[ttk.Entry] = None + self.original_service_files: Dict[str, str] = {} + self.temp_service_files: Dict[str, str] = {} + self.modified_files: Set[str] = set() + self.config_frame: Optional[ConfigFrame] = None + self.default_config: Dict[str, str] = {} + self.config: Dict[str, ConfigOption] = {} + self.has_error: bool = False self.load() - if not self.has_error: self.draw() - def load(self): + def load(self) -> None: try: self.core.create_nodes_and_links() service = self.core.config_services[self.service_name] @@ -116,7 +115,7 @@ def load(self): self.app.show_grpc_exception("Get Config Service Error", e) self.has_error = True - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -130,7 +129,7 @@ def draw(self): self.draw_tab_validation() self.draw_buttons() - def draw_tab_files(self): + def draw_tab_files(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -174,7 +173,7 @@ def draw_tab_files(self): ) self.template_text.text.bind("", self.update_template_file_data) - def draw_tab_config(self): + def draw_tab_config(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -198,7 +197,7 @@ def draw_tab_config(self): self.config_frame.grid(sticky="nsew", pady=PADY) tab.rowconfigure(self.config_frame.grid_info()["row"], weight=1) - def draw_tab_startstop(self): + def draw_tab_startstop(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -239,7 +238,7 @@ def draw_tab_startstop(self): elif i == 2: self.validate_commands_listbox = listbox_scroll.listbox - def draw_tab_validation(self): + def draw_tab_validation(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="ew") tab.columnconfigure(0, weight=1) @@ -298,7 +297,7 @@ def draw_tab_validation(self): for dependency in self.dependencies: listbox_scroll.listbox.insert("end", dependency) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(4): @@ -312,7 +311,7 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: current_listbox = self.master.current.listbox if not self.is_custom(): self.canvas_node.config_service_configs.pop(self.service_name, None) @@ -333,18 +332,18 @@ def click_apply(self): current_listbox.itemconfig(all_current.index(self.service_name), bg="green") self.destroy() - def handle_template_changed(self, event: tk.Event): + def handle_template_changed(self, event: tk.Event) -> None: template = self.templates_combobox.get() self.template_text.text.delete(1.0, "end") self.template_text.text.insert("end", self.temp_service_files[template]) - def handle_mode_changed(self, event: tk.Event): + def handle_mode_changed(self, event: tk.Event) -> None: mode = self.modes_combobox.get() config = self.mode_configs[mode] logging.info("mode config: %s", config) self.config_frame.set_values(config) - def update_template_file_data(self, event: tk.Event): + def update_template_file_data(self, event: tk.Event) -> None: scrolledtext = event.widget template = self.templates_combobox.get() self.temp_service_files[template] = scrolledtext.get(1.0, "end") @@ -353,7 +352,7 @@ def update_template_file_data(self, event: tk.Event): else: self.modified_files.discard(template) - def is_custom(self): + def is_custom(self) -> bool: has_custom_templates = len(self.modified_files) > 0 has_custom_config = False if self.config_frame: @@ -361,7 +360,7 @@ def is_custom(self): has_custom_config = self.default_config != current return has_custom_templates or has_custom_config - def click_defaults(self): + def click_defaults(self) -> None: self.canvas_node.config_service_configs.pop(self.service_name, None) logging.info( "cleared config service config: %s", self.canvas_node.config_service_configs @@ -374,12 +373,12 @@ def click_defaults(self): logging.info("resetting defaults: %s", self.default_config) self.config_frame.set_values(self.default_config) - def click_copy(self): + def click_copy(self) -> None: pass def append_commands( self, commands: List[str], listbox: tk.Listbox, to_add: List[str] - ): + ) -> None: for cmd in to_add: commands.append(cmd) listbox.insert(tk.END, cmd) diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index ff75a59a8..35559cb98 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -15,17 +15,16 @@ class CopyServiceConfigDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application", node_id: int): + def __init__(self, master: tk.BaseWidget, app: "Application", node_id: int) -> None: super().__init__(app, f"Copy services to node {node_id}", master=master) self.parent = master self.node_id = node_id self.service_configs = app.core.service_configs self.file_configs = app.core.file_configs - self.tree = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.tree = ttk.Treeview(self.top) self.tree.grid(row=0, column=0, sticky="ew", padx=PADX) @@ -88,7 +87,7 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=2, sticky="ew", padx=PADX) - def click_copy(self): + def click_copy(self) -> None: selected = self.tree.selection() if selected: item = self.tree.item(selected[0]) @@ -127,7 +126,7 @@ def click_copy(self): ) self.destroy() - def click_view(self): + def click_view(self) -> None: selected = self.tree.selection() data = "" if selected: @@ -159,7 +158,7 @@ def click_view(self): ) dialog.show() - def get_node_service(self, selected: Tuple[str]) -> [int, str]: + def get_node_service(self, selected: Tuple[str]) -> Tuple[int, str]: service_tree_id = self.tree.parent(selected[0]) service_name = self.tree.item(service_tree_id)["text"] node_tree_id = self.tree.parent(service_tree_id) @@ -175,14 +174,14 @@ def __init__( node_id: int, data: str, filename: str = None, - ): + ) -> None: super().__init__(app, f"n{node_id} config data", master=master) self.data = data self.service_data = None self.filepath = tk.StringVar(value=f"/tmp/services.tmp-n{node_id}-{filename}") self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) frame = ttk.Frame(self.top, padding=FRAME_PAD) frame.columnconfigure(0, weight=1) diff --git a/daemon/core/gui/dialogs/customnodes.py b/daemon/core/gui/dialogs/customnodes.py index 56012780b..df3bafa77 100644 --- a/daemon/core/gui/dialogs/customnodes.py +++ b/daemon/core/gui/dialogs/customnodes.py @@ -2,7 +2,9 @@ import tkinter as tk from pathlib import Path from tkinter import ttk -from typing import TYPE_CHECKING, Set +from typing import TYPE_CHECKING, Optional, Set + +from PIL.ImageTk import PhotoImage from core.gui import nodeutils from core.gui.appconfig import ICONS_PATH, CustomNode @@ -19,15 +21,15 @@ class ServicesSelectDialog(Dialog): def __init__( self, master: tk.BaseWidget, app: "Application", current_services: Set[str] - ): + ) -> None: super().__init__(app, "Node Services", master=master) - self.groups = None - self.services = None - self.current = None - self.current_services = set(current_services) + self.groups: Optional[ListboxScroll] = None + self.services: Optional[CheckboxList] = None + self.current: Optional[ListboxScroll] = None + self.current_services: Set[str] = current_services self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -77,7 +79,7 @@ def draw(self): # trigger group change self.groups.listbox.event_generate("<>") - def handle_group_change(self, event: tk.Event): + def handle_group_change(self, event: tk.Event) -> None: selection = self.groups.listbox.curselection() if selection: index = selection[0] @@ -87,7 +89,7 @@ def handle_group_change(self, event: tk.Event): checked = name in self.current_services self.services.add(name, checked) - def service_clicked(self, name: str, var: tk.BooleanVar): + def service_clicked(self, name: str, var: tk.BooleanVar) -> None: if var.get() and name not in self.current_services: self.current_services.add(name) elif not var.get() and name in self.current_services: @@ -96,34 +98,34 @@ def service_clicked(self, name: str, var: tk.BooleanVar): for name in sorted(self.current_services): self.current.listbox.insert(tk.END, name) - def click_cancel(self): + def click_cancel(self) -> None: self.current_services = None self.destroy() class CustomNodesDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Custom Nodes") - self.edit_button = None - self.delete_button = None - self.nodes_list = None - self.name = tk.StringVar() - self.image_button = None - self.image = None - self.image_file = None - self.services = set() - self.selected = None - self.selected_index = None + self.edit_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None + self.nodes_list: Optional[ListboxScroll] = None + self.name: tk.StringVar = tk.StringVar() + self.image_button: Optional[ttk.Button] = None + self.image: Optional[PhotoImage] = None + self.image_file: Optional[str] = None + self.services: Set[str] = set() + self.selected: Optional[str] = None + self.selected_index: Optional[int] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_node_config() self.draw_node_buttons() self.draw_buttons() - def draw_node_config(self): + def draw_node_config(self) -> None: frame = ttk.LabelFrame(self.top, text="Nodes", padding=FRAME_PAD) frame.grid(sticky="nsew", pady=PADY) frame.columnconfigure(0, weight=1) @@ -147,7 +149,7 @@ def draw_node_config(self): button = ttk.Button(frame, text="Services", command=self.click_services) button.grid(sticky="ew") - def draw_node_buttons(self): + def draw_node_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) for i in range(3): @@ -166,7 +168,7 @@ def draw_node_buttons(self): ) self.delete_button.grid(row=0, column=2, sticky="ew") - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -178,14 +180,14 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def reset_values(self): + def reset_values(self) -> None: self.name.set("") self.image = None self.image_file = None self.services = set() self.image_button.config(image="") - def click_icon(self): + def click_icon(self) -> None: file_path = image_chooser(self, ICONS_PATH) if file_path: image = Images.create(file_path, nodeutils.ICON_SIZE) @@ -193,24 +195,26 @@ def click_icon(self): self.image_file = file_path self.image_button.config(image=self.image) - def click_services(self): + def click_services(self) -> None: dialog = ServicesSelectDialog(self, self.app, self.services) dialog.show() if dialog.current_services is not None: self.services.clear() self.services.update(dialog.current_services) - def click_save(self): + def click_save(self) -> None: self.app.guiconfig.nodes.clear() for name in self.app.core.custom_nodes: node_draw = self.app.core.custom_nodes[name] - custom_node = CustomNode(name, node_draw.image_file, node_draw.services) + custom_node = CustomNode( + name, node_draw.image_file, list(node_draw.services) + ) self.app.guiconfig.nodes.append(custom_node) logging.info("saving custom nodes: %s", self.app.guiconfig.nodes) self.app.save_config() self.destroy() - def click_create(self): + def click_create(self) -> None: name = self.name.get() if name not in self.app.core.custom_nodes: image_file = Path(self.image_file).stem @@ -226,7 +230,7 @@ def click_create(self): self.nodes_list.listbox.insert(tk.END, name) self.reset_values() - def click_edit(self): + def click_edit(self) -> None: name = self.name.get() if self.selected: previous_name = self.selected @@ -247,7 +251,7 @@ def click_edit(self): self.nodes_list.listbox.insert(self.selected_index, name) self.nodes_list.listbox.selection_set(self.selected_index) - def click_delete(self): + def click_delete(self) -> None: if self.selected and self.selected in self.app.core.custom_nodes: self.nodes_list.listbox.delete(self.selected_index) del self.app.core.custom_nodes[self.selected] @@ -255,7 +259,7 @@ def click_delete(self): self.nodes_list.listbox.selection_clear(0, tk.END) self.nodes_list.listbox.event_generate("<>") - def handle_node_select(self, event: tk.Event): + def handle_node_select(self, event: tk.Event) -> None: selection = self.nodes_list.listbox.curselection() if selection: self.selected_index = selection[0] diff --git a/daemon/core/gui/dialogs/dialog.py b/daemon/core/gui/dialogs/dialog.py index f3742c509..962170e7f 100644 --- a/daemon/core/gui/dialogs/dialog.py +++ b/daemon/core/gui/dialogs/dialog.py @@ -16,23 +16,23 @@ def __init__( title: str, modal: bool = True, master: tk.BaseWidget = None, - ): + ) -> None: if master is None: master = app super().__init__(master) self.withdraw() - self.app = app - self.modal = modal + self.app: "Application" = app + self.modal: bool = modal self.title(title) self.protocol("WM_DELETE_WINDOW", self.destroy) image = Images.get(ImageEnum.CORE, 16) self.tk.call("wm", "iconphoto", self._w, image) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.top = ttk.Frame(self, padding=DIALOG_PAD) + self.top: ttk.Frame = ttk.Frame(self, padding=DIALOG_PAD) self.top.grid(sticky="nsew") - def show(self): + def show(self) -> None: self.transient(self.master) self.focus_force() self.update() @@ -42,7 +42,7 @@ def show(self): self.grab_set() self.wait_window() - def draw_spacer(self, row: int = None): + def draw_spacer(self, row: int = None) -> None: frame = ttk.Frame(self.top) frame.grid(row=row, sticky="nsew") frame.rowconfigure(0, weight=1) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 8f7ca089f..df6c61254 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -4,10 +4,12 @@ import tkinter as tk import webbrowser from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, List, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.themes import PADX, PADY @@ -19,12 +21,12 @@ class GlobalEmaneDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application"): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(app, "EMANE Configuration", master=master) - self.config_frame = None + self.config_frame: Optional[ConfigFrame] = None self.draw() - def draw(self): + 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.app.core.emane_config) @@ -33,7 +35,7 @@ def draw(self): self.draw_spacer() self.draw_buttons() - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -44,7 +46,7 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: self.config_frame.parse_config() self.destroy() @@ -57,31 +59,32 @@ def __init__( canvas_node: "CanvasNode", model: str, iface_id: int = None, - ): + ) -> None: super().__init__( app, f"{canvas_node.core_node.name} {model} Configuration", master=master ) - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.model = f"emane_{model}" - self.iface_id = iface_id - self.config_frame = None - self.has_error = False + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.model: str = f"emane_{model}" + self.iface_id: int = iface_id + self.config_frame: Optional[ConfigFrame] = None + self.has_error: bool = False try: - self.config = self.canvas_node.emane_model_configs.get( + config = self.canvas_node.emane_model_configs.get( (self.model, self.iface_id) ) - if not self.config: - self.config = self.app.core.get_emane_model_config( + if not config: + config = self.app.core.get_emane_model_config( self.node.id, self.model, self.iface_id ) + self.config: Dict[str, ConfigOption] = config self.draw() except grpc.RpcError as e: self.app.show_grpc_exception("Get EMANE Config Error", e) - self.has_error = True + self.has_error: bool = True self.destroy() - def draw(self): + 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) @@ -90,7 +93,7 @@ def draw(self): self.draw_spacer() self.draw_buttons() - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -101,7 +104,7 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: self.config_frame.parse_config() key = (self.model, self.iface_id) self.canvas_node.emane_model_configs[key] = self.config @@ -109,18 +112,21 @@ def click_apply(self): class EmaneConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: super().__init__(app, f"{canvas_node.core_node.name} EMANE Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.radiovar = tk.IntVar() + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) - self.emane_models = [x.split("_")[1] for x in self.app.core.emane_models] - self.emane_model = tk.StringVar(value=self.node.emane.split("_")[1]) - self.emane_model_button = None + self.emane_models: List[str] = [ + x.split("_")[1] for x in self.app.core.emane_models + ] + model = self.node.emane.split("_")[1] + self.emane_model: tk.StringVar = tk.StringVar(value=model) + self.emane_model_button: Optional[ttk.Button] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_emane_configuration() self.draw_emane_models() @@ -128,7 +134,7 @@ def draw(self): self.draw_spacer() self.draw_apply_and_cancel() - def draw_emane_configuration(self): + def draw_emane_configuration(self) -> None: """ draw the main frame for emane configuration """ @@ -153,7 +159,7 @@ def draw_emane_configuration(self): button.image = image button.grid(sticky="ew", pady=PADY) - def draw_emane_models(self): + def draw_emane_models(self) -> None: """ create a combobox that has all the known emane models """ @@ -174,7 +180,7 @@ def draw_emane_models(self): combobox.grid(row=0, column=1, sticky="ew") combobox.bind("<>", self.emane_model_change) - def draw_emane_buttons(self): + def draw_emane_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) for i in range(2): @@ -202,7 +208,7 @@ def draw_emane_buttons(self): button.image = image button.grid(row=0, column=1, sticky="ew") - def draw_apply_and_cancel(self): + def draw_apply_and_cancel(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -214,11 +220,11 @@ def draw_apply_and_cancel(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_emane_config(self): + def click_emane_config(self) -> None: dialog = GlobalEmaneDialog(self, self.app) dialog.show() - def click_model_config(self): + def click_model_config(self) -> None: """ draw emane model configuration """ @@ -227,13 +233,13 @@ def click_model_config(self): if not dialog.has_error: dialog.show() - def emane_model_change(self, event: tk.Event): + def emane_model_change(self, event: tk.Event) -> None: """ update emane model options button """ model_name = self.emane_model.get() self.emane_model_button.config(text=f"{model_name} options") - def click_apply(self): + def click_apply(self) -> None: self.node.emane = f"emane_{self.emane_model.get()}" self.destroy() diff --git a/daemon/core/gui/dialogs/emaneinstall.py b/daemon/core/gui/dialogs/emaneinstall.py index 93cf2ac48..3ad9396b2 100644 --- a/daemon/core/gui/dialogs/emaneinstall.py +++ b/daemon/core/gui/dialogs/emaneinstall.py @@ -10,7 +10,7 @@ def __init__(self, app) -> None: super().__init__(app, "EMANE Error") self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) label = ttk.Label(self.top, text="EMANE needs to be installed!") label.grid(sticky="ew", pady=PADY) @@ -21,5 +21,5 @@ def draw(self): button = ttk.Button(self.top, text="Close", command=self.destroy) button.grid(sticky="ew") - def click_doc(self): + def click_doc(self) -> None: webbrowser.open_new("https://coreemu.github.io/core/emane.html") diff --git a/daemon/core/gui/dialogs/error.py b/daemon/core/gui/dialogs/error.py index 5ff1dbc58..7fb81077a 100644 --- a/daemon/core/gui/dialogs/error.py +++ b/daemon/core/gui/dialogs/error.py @@ -1,5 +1,5 @@ from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images @@ -13,9 +13,9 @@ class ErrorDialog(Dialog): def __init__(self, app: "Application", title: str, details: str) -> None: super().__init__(app, "CORE Exception") - self.title = title - self.details = details - self.error_message = None + self.title: str = title + self.details: str = details + self.error_message: Optional[CodeText] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/dialogs/executepython.py b/daemon/core/gui/dialogs/executepython.py index dd60c778e..a4516df15 100644 --- a/daemon/core/gui/dialogs/executepython.py +++ b/daemon/core/gui/dialogs/executepython.py @@ -1,7 +1,7 @@ import logging import tkinter as tk from tkinter import filedialog, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import SCRIPT_PATH from core.gui.dialogs.dialog import Dialog @@ -12,15 +12,15 @@ class ExecutePythonDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Execute Python Script") - self.with_options = tk.IntVar(value=0) - self.options = tk.StringVar(value="") - self.option_entry = None - self.file_entry = None + self.with_options: tk.IntVar = tk.IntVar(value=0) + self.options: tk.StringVar = tk.StringVar(value="") + self.option_entry: Optional[ttk.Entry] = None + self.file_entry: Optional[ttk.Entry] = None self.draw() - def draw(self): + def draw(self) -> None: i = 0 frame = ttk.Frame(self.top, padding=FRAME_PAD) frame.columnconfigure(0, weight=1) @@ -63,13 +63,13 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew", padx=PADX) - def add_options(self): + def add_options(self) -> None: if self.with_options.get(): self.option_entry.configure(state="normal") else: self.option_entry.configure(state="disabled") - def select_file(self): + def select_file(self) -> None: file = filedialog.askopenfilename( parent=self.top, initialdir=str(SCRIPT_PATH), @@ -80,7 +80,7 @@ def select_file(self): self.file_entry.delete(0, "end") self.file_entry.insert("end", file) - def script_execute(self): + def script_execute(self) -> None: file = self.file_entry.get() options = self.option_entry.get() logging.info("Execute %s with options %s", file, options) diff --git a/daemon/core/gui/dialogs/find.py b/daemon/core/gui/dialogs/find.py index 25da4b19b..328f673e3 100644 --- a/daemon/core/gui/dialogs/find.py +++ b/daemon/core/gui/dialogs/find.py @@ -1,7 +1,7 @@ import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY @@ -13,8 +13,8 @@ class FindDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "Find", modal=False) - self.find_text = tk.StringVar(value="") - self.tree = None + self.find_text: tk.StringVar = tk.StringVar(value="") + self.tree: Optional[ttk.Treeview] = None self.draw() self.protocol("WM_DELETE_WINDOW", self.close_dialog) self.bind("", self.find_node) diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index 5895a2e1d..08d666ba0 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.api.grpc import core_pb2 from core.gui.dialogs.dialog import Dialog @@ -12,15 +12,15 @@ class HookDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application"): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(app, "Hook", master=master) - self.name = tk.StringVar() - self.codetext = None - self.hook = core_pb2.Hook() - self.state = tk.StringVar() + self.name: tk.StringVar = tk.StringVar() + self.codetext: Optional[CodeText] = None + self.hook: core_pb2.Hook = core_pb2.Hook() + self.state: tk.StringVar = tk.StringVar() self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) @@ -66,11 +66,11 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) button.grid(row=0, column=1, sticky="ew") - def state_change(self, event: tk.Event): + def state_change(self, event: tk.Event) -> None: state_name = self.state.get() self.name.set(f"{state_name.lower()}_hook.sh") - def set(self, hook: core_pb2.Hook): + def set(self, hook: core_pb2.Hook) -> None: self.hook = hook self.name.set(hook.file) self.codetext.text.delete(1.0, tk.END) @@ -78,7 +78,7 @@ def set(self, hook: core_pb2.Hook): state_name = core_pb2.SessionState.Enum.Name(hook.state) self.state.set(state_name) - def save(self): + def save(self) -> None: data = self.codetext.text.get("1.0", tk.END).strip() state_value = core_pb2.SessionState.Enum.Value(self.state.get()) self.hook.file = self.name.get() @@ -88,15 +88,15 @@ def save(self): class HooksDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Hooks") - self.listbox = None - self.edit_button = None - self.delete_button = None - self.selected = None + self.listbox: Optional[tk.Listbox] = None + self.edit_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None + self.selected: Optional[str] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -124,7 +124,7 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) button.grid(row=0, column=3, sticky="ew") - def click_create(self): + def click_create(self) -> None: dialog = HookDialog(self, self.app) dialog.show() hook = dialog.hook @@ -132,19 +132,19 @@ def click_create(self): self.app.core.hooks[hook.file] = hook self.listbox.insert(tk.END, hook.file) - def click_edit(self): + def click_edit(self) -> None: hook = self.app.core.hooks[self.selected] dialog = HookDialog(self, self.app) dialog.set(hook) dialog.show() - def click_delete(self): + def click_delete(self) -> None: del self.app.core.hooks[self.selected] self.listbox.delete(tk.ANCHOR) self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) - def select(self, event: tk.Event): + def select(self, event: tk.Event) -> None: if self.listbox.curselection(): index = self.listbox.curselection()[0] self.selected = self.listbox.get(index) diff --git a/daemon/core/gui/dialogs/ipdialog.py b/daemon/core/gui/dialogs/ipdialog.py index d31dcdff5..351bfffcd 100644 --- a/daemon/core/gui/dialogs/ipdialog.py +++ b/daemon/core/gui/dialogs/ipdialog.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional import netaddr @@ -15,14 +15,14 @@ class IpConfigDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "IP Configuration") - self.ip4 = self.app.guiconfig.ips.ip4 - self.ip6 = self.app.guiconfig.ips.ip6 - self.ip4s = self.app.guiconfig.ips.ip4s - self.ip6s = self.app.guiconfig.ips.ip6s - self.ip4_entry = None - self.ip4_listbox = None - self.ip6_entry = None - self.ip6_listbox = None + self.ip4: str = self.app.guiconfig.ips.ip4 + self.ip6: str = self.app.guiconfig.ips.ip6 + self.ip4s: List[str] = self.app.guiconfig.ips.ip4s + self.ip6s: List[str] = self.app.guiconfig.ips.ip6s + self.ip4_entry: Optional[ttk.Entry] = None + self.ip4_listbox: Optional[ListboxScroll] = None + self.ip6_entry: Optional[ttk.Entry] = None + self.ip6_listbox: Optional[ListboxScroll] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index adf8156f7..b7c618a3e 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -3,7 +3,7 @@ """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Optional from core.api.grpc import core_pb2 from core.gui import validation @@ -16,7 +16,7 @@ from core.gui.graph.graph import CanvasEdge -def get_int(var: tk.StringVar) -> Union[int, None]: +def get_int(var: tk.StringVar) -> Optional[int]: value = var.get() if value != "": return int(value) @@ -24,7 +24,7 @@ def get_int(var: tk.StringVar) -> Union[int, None]: return None -def get_float(var: tk.StringVar) -> Union[float, None]: +def get_float(var: tk.StringVar) -> Optional[float]: value = var.get() if value != "": return float(value) @@ -33,38 +33,39 @@ def get_float(var: tk.StringVar) -> Union[float, None]: class LinkConfigurationDialog(Dialog): - def __init__(self, app: "Application", edge: "CanvasEdge"): + def __init__(self, app: "Application", edge: "CanvasEdge") -> None: super().__init__(app, "Link Configuration") - self.edge = edge - self.is_symmetric = edge.link.options.unidirectional is False + self.edge: "CanvasEdge" = edge + self.is_symmetric: bool = edge.link.options.unidirectional is False if self.is_symmetric: - self.symmetry_var = tk.StringVar(value=">>") + symmetry_var = tk.StringVar(value=">>") else: - self.symmetry_var = tk.StringVar(value="<<") + symmetry_var = tk.StringVar(value="<<") + self.symmetry_var: tk.StringVar = symmetry_var - self.bandwidth = tk.StringVar() - self.delay = tk.StringVar() - self.jitter = tk.StringVar() - self.loss = tk.StringVar() - self.duplicate = tk.StringVar() + self.bandwidth: tk.StringVar = tk.StringVar() + self.delay: tk.StringVar = tk.StringVar() + self.jitter: tk.StringVar = tk.StringVar() + self.loss: tk.StringVar = tk.StringVar() + self.duplicate: tk.StringVar = tk.StringVar() - self.down_bandwidth = tk.StringVar() - self.down_delay = tk.StringVar() - self.down_jitter = tk.StringVar() - self.down_loss = tk.StringVar() - self.down_duplicate = tk.StringVar() + self.down_bandwidth: tk.StringVar = tk.StringVar() + self.down_delay: tk.StringVar = tk.StringVar() + self.down_jitter: tk.StringVar = tk.StringVar() + self.down_loss: tk.StringVar = tk.StringVar() + self.down_duplicate: tk.StringVar = tk.StringVar() - self.color = tk.StringVar(value="#000000") - self.color_button = None - self.width = tk.DoubleVar() + self.color: tk.StringVar = tk.StringVar(value="#000000") + self.color_button: Optional[tk.Button] = None + self.width: tk.DoubleVar = tk.DoubleVar() self.load_link_config() - self.symmetric_frame = None - self.asymmetric_frame = None + self.symmetric_frame: Optional[ttk.Frame] = None + self.asymmetric_frame: Optional[ttk.Frame] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) source_name = self.app.canvas.nodes[self.edge.src].core_node.name dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name @@ -207,13 +208,13 @@ def get_frame(self) -> ttk.Frame: return frame - def click_color(self): + def click_color(self) -> None: dialog = ColorPickerDialog(self, self.app, self.color.get()) color = dialog.askcolor() self.color.set(color) self.color_button.config(background=color) - def click_apply(self): + def click_apply(self) -> None: self.app.canvas.itemconfigure(self.edge.id, width=self.width.get()) self.app.canvas.itemconfigure(self.edge.id, fill=self.color.get()) link = self.edge.link @@ -288,7 +289,7 @@ def click_apply(self): self.destroy() - def change_symmetry(self): + def change_symmetry(self) -> None: if self.is_symmetric: self.is_symmetric = False self.symmetry_var.set("<<") @@ -304,7 +305,7 @@ def change_symmetry(self): self.asymmetric_frame.grid_forget() self.symmetric_frame.grid(row=2, column=0) - def load_link_config(self): + def load_link_config(self) -> None: """ populate link config to the table """ diff --git a/daemon/core/gui/dialogs/macdialog.py b/daemon/core/gui/dialogs/macdialog.py index 46414cf98..4d89439b5 100644 --- a/daemon/core/gui/dialogs/macdialog.py +++ b/daemon/core/gui/dialogs/macdialog.py @@ -15,7 +15,7 @@ class MacConfigDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "MAC Configuration") mac = self.app.guiconfig.mac - self.mac_var = tk.StringVar(value=mac) + self.mac_var: tk.StringVar = tk.StringVar(value=mac) self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index dced5e444..daaf9ea52 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -2,10 +2,12 @@ mobility configuration """ from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -16,23 +18,24 @@ class MobilityConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: super().__init__(app, f"{canvas_node.core_node.name} Mobility Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.config_frame = None - self.has_error = False + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.config_frame: Optional[ConfigFrame] = None + self.has_error: bool = False try: - self.config = self.canvas_node.mobility_config - if not self.config: - self.config = self.app.core.get_mobility_config(self.node.id) + config = self.canvas_node.mobility_config + if not config: + config = self.app.core.get_mobility_config(self.node.id) + self.config: Dict[str, ConfigOption] = config self.draw() except grpc.RpcError as e: self.app.show_grpc_exception("Get Mobility Config Error", e) - self.has_error = True + self.has_error: bool = True self.destroy() - def draw(self): + 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) @@ -40,7 +43,7 @@ def draw(self): self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_apply_buttons() - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -52,7 +55,7 @@ def draw_apply_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: self.config_frame.parse_config() self.canvas_node.mobility_config = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index b4801bcf2..e6ef62ea1 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -1,9 +1,11 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.api.grpc.mobility_pb2 import MobilityAction from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum @@ -13,18 +15,23 @@ from core.gui.app import Application from core.gui.graph.node import CanvasNode -ICON_SIZE = 16 +ICON_SIZE: int = 16 class MobilityPlayer: - def __init__(self, app: "Application", canvas_node: "CanvasNode", config): - self.app = app - self.canvas_node = canvas_node - self.config = config - self.dialog = None - self.state = None - - def show(self): + def __init__( + self, + app: "Application", + canvas_node: "CanvasNode", + config: Dict[str, ConfigOption], + ) -> None: + self.app: "Application" = app + self.canvas_node: "CanvasNode" = canvas_node + self.config: Dict[str, ConfigOption] = config + self.dialog: Optional[MobilityPlayerDialog] = None + self.state: Optional[MobilityAction] = None + + def show(self) -> None: if self.dialog: self.dialog.destroy() self.dialog = MobilityPlayerDialog(self.app, self.canvas_node, self.config) @@ -37,44 +44,49 @@ def show(self): self.set_stop() self.dialog.show() - def close(self): + def close(self) -> None: if self.dialog: self.dialog.destroy() self.dialog = None - def set_play(self): + def set_play(self) -> None: self.state = MobilityAction.START if self.dialog: self.dialog.set_play() - def set_pause(self): + def set_pause(self) -> None: self.state = MobilityAction.PAUSE if self.dialog: self.dialog.set_pause() - def set_stop(self): + def set_stop(self) -> None: self.state = MobilityAction.STOP if self.dialog: self.dialog.set_stop() class MobilityPlayerDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode", config): + def __init__( + self, + app: "Application", + canvas_node: "CanvasNode", + config: Dict[str, ConfigOption], + ) -> None: super().__init__( app, f"{canvas_node.core_node.name} Mobility Player", modal=False ) self.resizable(False, False) self.geometry("") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.config = config - self.play_button = None - self.pause_button = None - self.stop_button = None - self.progressbar = None + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.config: Dict[str, ConfigOption] = config + self.play_button: Optional[ttk.Button] = None + self.pause_button: Optional[ttk.Button] = None + self.stop_button: Optional[ttk.Button] = None + self.progressbar: Optional[ttk.Progressbar] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) file_name = self.config["file"].value @@ -114,27 +126,27 @@ def draw(self): label = ttk.Label(frame, text=f"rate {rate} ms") label.grid(row=0, column=4) - def clear_buttons(self): + def clear_buttons(self) -> None: self.play_button.state(["!pressed"]) self.pause_button.state(["!pressed"]) self.stop_button.state(["!pressed"]) - def set_play(self): + def set_play(self) -> None: self.clear_buttons() self.play_button.state(["pressed"]) self.progressbar.start() - def set_pause(self): + def set_pause(self) -> None: self.clear_buttons() self.pause_button.state(["pressed"]) self.progressbar.stop() - def set_stop(self): + def set_stop(self) -> None: self.clear_buttons() self.stop_button.state(["pressed"]) self.progressbar.stop() - def click_play(self): + def click_play(self) -> None: self.set_play() session_id = self.app.core.session_id try: @@ -144,7 +156,7 @@ def click_play(self): except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) - def click_pause(self): + def click_pause(self) -> None: self.set_pause() session_id = self.app.core.session_id try: @@ -154,7 +166,7 @@ def click_pause(self): except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) - def click_stop(self): + def click_stop(self) -> None: self.set_stop() session_id = self.app.core.session_id try: diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index cec9e9f92..9e9582839 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -2,10 +2,12 @@ import tkinter as tk from functools import partial from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import netaddr +from PIL.ImageTk import PhotoImage +from core.api.grpc.core_pb2 import Node from core.gui import nodeutils, validation from core.gui.appconfig import ICONS_PATH from core.gui.dialogs.dialog import Dialog @@ -86,35 +88,35 @@ def __init__( mac: tk.StringVar, ip4: tk.StringVar, ip6: tk.StringVar, - ): - self.is_auto = is_auto - self.mac = mac - self.ip4 = ip4 - self.ip6 = ip6 + ) -> None: + self.is_auto: tk.BooleanVar = is_auto + self.mac: tk.StringVar = mac + self.ip4: tk.StringVar = ip4 + self.ip6: tk.StringVar = ip6 class NodeConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: """ create an instance of node configuration """ super().__init__(app, f"{canvas_node.core_node.name} Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.image = canvas_node.image - self.image_file = None - self.image_button = None - self.name = tk.StringVar(value=self.node.name) - self.type = tk.StringVar(value=self.node.model) - self.container_image = tk.StringVar(value=self.node.image) + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.image: PhotoImage = canvas_node.image + self.image_file: Optional[str] = None + self.image_button: Optional[ttk.Button] = None + self.name: tk.StringVar = tk.StringVar(value=self.node.name) + self.type: tk.StringVar = tk.StringVar(value=self.node.model) + self.container_image: tk.StringVar = tk.StringVar(value=self.node.image) server = "localhost" if self.node.server: server = self.node.server - self.server = tk.StringVar(value=server) - self.ifaces = {} + self.server: tk.StringVar = tk.StringVar(value=server) + self.ifaces: Dict[int, InterfaceData] = {} self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) row = 0 @@ -202,7 +204,7 @@ def draw(self): self.draw_spacer() self.draw_buttons() - def draw_ifaces(self): + def draw_ifaces(self) -> None: notebook = ttk.Notebook(self.top) notebook.grid(sticky="nsew", pady=PADY) self.top.rowconfigure(notebook.grid_info()["row"], weight=1) @@ -265,7 +267,7 @@ def draw_ifaces(self): self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") frame.columnconfigure(0, weight=1) @@ -277,20 +279,20 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_emane_config(self, emane_model: str, iface_id: int): + def click_emane_config(self, emane_model: str, iface_id: int) -> None: dialog = EmaneModelDialog( self, self.app, self.canvas_node, emane_model, iface_id ) dialog.show() - def click_icon(self): + def click_icon(self) -> None: file_path = image_chooser(self, ICONS_PATH) if file_path: self.image = Images.create(file_path, nodeutils.ICON_SIZE) self.image_button.config(image=self.image) self.image_file = file_path - def click_apply(self): + def click_apply(self) -> None: error = False # update core node @@ -354,7 +356,7 @@ def click_apply(self): self.canvas_node.redraw() self.destroy() - def iface_select(self, event: tk.Event): + def iface_select(self, event: tk.Event) -> None: listbox = event.widget cur = listbox.curselection() if cur: diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 5f77ece34..b5250eba9 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -4,7 +4,7 @@ import logging import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING, Set +from typing import TYPE_CHECKING, Optional, Set from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog from core.gui.dialogs.dialog import Dialog @@ -19,20 +19,20 @@ class NodeConfigServiceDialog(Dialog): def __init__( self, app: "Application", canvas_node: "CanvasNode", services: Set[str] = None - ): + ) -> None: title = f"{canvas_node.core_node.name} Config Services" super().__init__(app, title) - self.canvas_node = canvas_node - self.node_id = canvas_node.core_node.id - self.groups = None - self.services = None - self.current = None + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = canvas_node.core_node.id + self.groups: Optional[ListboxScroll] = None + self.services: Optional[CheckboxList] = None + self.current: Optional[ListboxScroll] = None if services is None: services = set(canvas_node.core_node.config_services) - self.current_services = services + self.current_services: Set[str] = services self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -86,7 +86,7 @@ def draw(self): # trigger group change self.groups.listbox.event_generate("<>") - def handle_group_change(self, event: tk.Event = None): + def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() if selection: index = selection[0] @@ -96,7 +96,7 @@ def handle_group_change(self, event: tk.Event = None): checked = name in self.current_services self.services.add(name, checked) - def service_clicked(self, name: str, var: tk.IntVar): + def service_clicked(self, name: str, var: tk.IntVar) -> None: if var.get() and name not in self.current_services: self.current_services.add(name) elif not var.get() and name in self.current_services: @@ -104,7 +104,7 @@ def service_clicked(self, name: str, var: tk.IntVar): self.draw_current_services() self.canvas_node.core_node.config_services[:] = self.current_services - def click_configure(self): + def click_configure(self) -> None: current_selection = self.current.listbox.curselection() if len(current_selection): dialog = ConfigServiceConfigDialog( @@ -124,25 +124,25 @@ def click_configure(self): parent=self, ) - def draw_current_services(self): + def draw_current_services(self) -> None: self.current.listbox.delete(0, tk.END) for name in sorted(self.current_services): self.current.listbox.insert(tk.END, name) if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") - def click_save(self): + def click_save(self) -> None: self.canvas_node.core_node.config_services[:] = self.current_services logging.info( "saved node config services: %s", self.canvas_node.core_node.config_services ) self.destroy() - def click_cancel(self): + def click_cancel(self) -> None: self.current_services = None self.destroy() - def click_remove(self): + def click_remove(self) -> None: cur = self.current.listbox.curselection() if cur: service = self.current.listbox.get(cur[0]) diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 13490d8cd..f6f5e5cf9 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -3,7 +3,7 @@ """ import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Set from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.serviceconfig import ServiceConfigDialog @@ -16,19 +16,19 @@ class NodeServiceDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: title = f"{canvas_node.core_node.name} Services" super().__init__(app, title) - self.canvas_node = canvas_node - self.node_id = canvas_node.core_node.id - self.groups = None - self.services = None - self.current = None + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = canvas_node.core_node.id + self.groups: Optional[ListboxScroll] = None + self.services: Optional[CheckboxList] = None + self.current: Optional[ListboxScroll] = None services = set(canvas_node.core_node.services) - self.current_services = services + self.current_services: Set[str] = services self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -84,7 +84,7 @@ def draw(self): # trigger group change self.groups.listbox.event_generate("<>") - def handle_group_change(self, event: tk.Event = None): + def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() if selection: index = selection[0] @@ -94,7 +94,7 @@ def handle_group_change(self, event: tk.Event = None): checked = name in self.current_services self.services.add(name, checked) - def service_clicked(self, name: str, var: tk.IntVar): + def service_clicked(self, name: str, var: tk.IntVar) -> None: if var.get() and name not in self.current_services: self.current_services.add(name) elif not var.get() and name in self.current_services: @@ -106,7 +106,7 @@ def service_clicked(self, name: str, var: tk.IntVar): self.current.listbox.itemconfig(tk.END, bg="green") self.canvas_node.core_node.services[:] = self.current_services - def click_configure(self): + def click_configure(self) -> None: current_selection = self.current.listbox.curselection() if len(current_selection): dialog = ServiceConfigDialog( @@ -127,12 +127,12 @@ def click_configure(self): "Service Configuration", "Select a service to configure", parent=self ) - def click_save(self): + def click_save(self) -> None: core_node = self.canvas_node.core_node core_node.services[:] = self.current_services self.destroy() - def click_remove(self): + def click_remove(self) -> None: cur = self.current.listbox.curselection() if cur: service = self.current.listbox.get(cur[0]) diff --git a/daemon/core/gui/dialogs/observers.py b/daemon/core/gui/dialogs/observers.py index d1812b644..286fc2c9f 100644 --- a/daemon/core/gui/dialogs/observers.py +++ b/daemon/core/gui/dialogs/observers.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import Observer from core.gui.dialogs.dialog import Dialog @@ -12,18 +12,18 @@ class ObserverDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Observer Widgets") - self.observers = None - self.save_button = None - self.delete_button = None - self.selected = None - self.selected_index = None - self.name = tk.StringVar() - self.cmd = tk.StringVar() + self.observers: Optional[tk.Listbox] = None + self.save_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None + self.selected: Optional[str] = None + self.selected_index: Optional[int] = None + self.name: tk.StringVar = tk.StringVar() + self.cmd: tk.StringVar = tk.StringVar() self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_listbox() @@ -31,7 +31,7 @@ def draw(self): self.draw_config_buttons() self.draw_apply_buttons() - def draw_listbox(self): + def draw_listbox(self) -> None: listbox_scroll = ListboxScroll(self.top) listbox_scroll.grid(sticky="nsew", pady=PADY) listbox_scroll.columnconfigure(0, weight=1) @@ -42,7 +42,7 @@ def draw_listbox(self): for name in sorted(self.app.core.custom_observers): self.observers.insert(tk.END, name) - def draw_form_fields(self): + def draw_form_fields(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) frame.columnconfigure(1, weight=1) @@ -57,7 +57,7 @@ def draw_form_fields(self): entry = ttk.Entry(frame, textvariable=self.cmd) entry.grid(row=1, column=1, sticky="ew") - def draw_config_buttons(self): + def draw_config_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) for i in range(3): @@ -76,7 +76,7 @@ def draw_config_buttons(self): ) self.delete_button.grid(row=0, column=2, sticky="ew") - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -88,14 +88,14 @@ def draw_apply_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_save_config(self): + def click_save_config(self) -> None: self.app.guiconfig.observers.clear() for observer in self.app.core.custom_observers.values(): self.app.guiconfig.observers.append(observer) self.app.save_config() self.destroy() - def click_create(self): + def click_create(self) -> None: name = self.name.get() if name not in self.app.core.custom_observers: cmd = self.cmd.get() @@ -109,7 +109,7 @@ def click_create(self): else: messagebox.showerror("Observer Error", f"{name} already exists") - def click_save(self): + def click_save(self) -> None: name = self.name.get() if self.selected: previous_name = self.selected @@ -122,7 +122,7 @@ def click_save(self): self.observers.insert(self.selected_index, name) self.observers.selection_set(self.selected_index) - def click_delete(self): + def click_delete(self) -> None: if self.selected: self.observers.delete(self.selected_index) del self.app.core.custom_observers[self.selected] @@ -136,7 +136,7 @@ def click_delete(self): self.app.menubar.observers_menu.draw_custom() self.app.toolbar.observers_menu.draw_custom() - def handle_observer_change(self, event: tk.Event): + def handle_observer_change(self, event: tk.Event) -> None: selection = self.observers.curselection() if selection: self.selected_index = selection[0] diff --git a/daemon/core/gui/dialogs/preferences.py b/daemon/core/gui/dialogs/preferences.py index 11d1ba95a..839ebd3b5 100644 --- a/daemon/core/gui/dialogs/preferences.py +++ b/daemon/core/gui/dialogs/preferences.py @@ -12,27 +12,27 @@ if TYPE_CHECKING: from core.gui.app import Application -SCALE_INTERVAL = 0.01 +SCALE_INTERVAL: float = 0.01 class PreferencesDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Preferences") - self.gui_scale = tk.DoubleVar(value=self.app.app_scale) + self.gui_scale: tk.DoubleVar = tk.DoubleVar(value=self.app.app_scale) preferences = self.app.guiconfig.preferences - self.editor = tk.StringVar(value=preferences.editor) - self.theme = tk.StringVar(value=preferences.theme) - self.terminal = tk.StringVar(value=preferences.terminal) - self.gui3d = tk.StringVar(value=preferences.gui3d) + self.editor: tk.StringVar = tk.StringVar(value=preferences.editor) + self.theme: tk.StringVar = tk.StringVar(value=preferences.theme) + self.terminal: tk.StringVar = tk.StringVar(value=preferences.terminal) + self.gui3d: tk.StringVar = tk.StringVar(value=preferences.gui3d) self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_preferences() self.draw_buttons() - def draw_preferences(self): + def draw_preferences(self) -> None: frame = ttk.LabelFrame(self.top, text="Preferences", padding=FRAME_PAD) frame.grid(sticky="nsew", pady=PADY) frame.columnconfigure(1, weight=1) @@ -88,7 +88,7 @@ def draw_preferences(self): scrollbar = ttk.Scrollbar(scale_frame, command=self.adjust_scale) scrollbar.grid(row=0, column=2) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -100,12 +100,12 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def theme_change(self, event: tk.Event): + def theme_change(self, event: tk.Event) -> None: theme = self.theme.get() logging.info("changing theme: %s", theme) self.app.style.theme_use(theme) - def click_save(self): + def click_save(self) -> None: preferences = self.app.guiconfig.preferences preferences.terminal = self.terminal.get() preferences.editor = self.editor.get() @@ -118,7 +118,7 @@ def click_save(self): self.scale_adjust() self.destroy() - def scale_adjust(self): + def scale_adjust(self) -> None: app_scale = self.gui_scale.get() self.app.app_scale = app_scale self.app.master.tk.call("tk", "scaling", app_scale) @@ -136,7 +136,7 @@ def scale_adjust(self): self.app.toolbar.scale() self.app.canvas.scale_graph() - def adjust_scale(self, arg1: str, arg2: str, arg3: str): + def adjust_scale(self, arg1: str, arg2: str, arg3: str) -> None: scale_value = self.gui_scale.get() if arg2 == "-1": if scale_value <= LARGEST_SCALE - SCALE_INTERVAL: diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index 98be730f6..c66fea8f7 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional from core.gui.dialogs.dialog import Dialog from core.gui.nodeutils import NodeUtils @@ -14,10 +14,10 @@ class RunToolDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "Run Tool") - self.cmd = tk.StringVar(value="ps ax") - self.result = None - self.node_list = None - self.executable_nodes = {} + self.cmd: tk.StringVar = tk.StringVar(value="ps ax") + self.result: Optional[CodeText] = None + self.node_list: Optional[ListboxScroll] = None + self.executable_nodes: Dict[str, int] = {} self.store_nodes() self.draw() diff --git a/daemon/core/gui/dialogs/servers.py b/daemon/core/gui/dialogs/servers.py index 7ca96e9fc..45121a201 100644 --- a/daemon/core/gui/dialogs/servers.py +++ b/daemon/core/gui/dialogs/servers.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import CoreServer from core.gui.dialogs.dialog import Dialog @@ -10,24 +10,24 @@ if TYPE_CHECKING: from core.gui.app import Application -DEFAULT_NAME = "example" -DEFAULT_ADDRESS = "127.0.0.1" -DEFAULT_PORT = 50051 +DEFAULT_NAME: str = "example" +DEFAULT_ADDRESS: str = "127.0.0.1" +DEFAULT_PORT: int = 50051 class ServersDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "CORE Servers") - self.name = tk.StringVar(value=DEFAULT_NAME) - self.address = tk.StringVar(value=DEFAULT_ADDRESS) - self.servers = None - self.selected_index = None - self.selected = None - self.save_button = None - self.delete_button = None + self.name: tk.StringVar = tk.StringVar(value=DEFAULT_NAME) + self.address: tk.StringVar = tk.StringVar(value=DEFAULT_ADDRESS) + self.servers: Optional[tk.Listbox] = None + self.selected_index: Optional[int] = None + self.selected: Optional[str] = None + self.save_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_servers() @@ -35,7 +35,7 @@ def draw(self): self.draw_server_configuration() self.draw_apply_buttons() - def draw_servers(self): + def draw_servers(self) -> None: listbox_scroll = ListboxScroll(self.top) listbox_scroll.grid(pady=PADY, sticky="nsew") listbox_scroll.columnconfigure(0, weight=1) @@ -48,7 +48,7 @@ def draw_servers(self): for server in self.app.core.servers: self.servers.insert(tk.END, server) - def draw_server_configuration(self): + def draw_server_configuration(self) -> None: frame = ttk.LabelFrame(self.top, text="Server Configuration", padding=FRAME_PAD) frame.grid(pady=PADY, sticky="ew") frame.columnconfigure(1, weight=1) @@ -64,7 +64,7 @@ def draw_server_configuration(self): entry = ttk.Entry(frame, textvariable=self.address) entry.grid(row=0, column=3, sticky="ew") - def draw_servers_buttons(self): + def draw_servers_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(pady=PADY, sticky="ew") for i in range(3): @@ -83,7 +83,7 @@ def draw_servers_buttons(self): ) self.delete_button.grid(row=0, column=2, sticky="ew") - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -104,7 +104,7 @@ def click_save_configuration(self): self.app.save_config() self.destroy() - def click_create(self): + def click_create(self) -> None: name = self.name.get() if name not in self.app.core.servers: address = self.address.get() @@ -112,7 +112,7 @@ def click_create(self): self.app.core.servers[name] = server self.servers.insert(tk.END, name) - def click_save(self): + def click_save(self) -> None: name = self.name.get() if self.selected: previous_name = self.selected @@ -125,7 +125,7 @@ def click_save(self): self.servers.insert(self.selected_index, name) self.servers.selection_set(self.selected_index) - def click_delete(self): + def click_delete(self) -> None: if self.selected: self.servers.delete(self.selected_index) del self.app.core.servers[self.selected] @@ -137,7 +137,7 @@ def click_delete(self): self.save_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) - def handle_server_change(self, event: tk.Event): + def handle_server_change(self, event: tk.Event) -> None: selection = self.servers.curselection() if selection: self.selected_index = selection[0] diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index efeefa090..5faface72 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -2,11 +2,12 @@ import os import tkinter as tk from tkinter import filedialog, ttk -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import grpc +from PIL.ImageTk import PhotoImage -from core.api.grpc.services_pb2 import ServiceValidationMode +from core.api.grpc.services_pb2 import NodeServiceData, ServiceValidationMode from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images @@ -16,8 +17,9 @@ if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode + from core.gui.coreclient import CoreClient -ICON_SIZE = 16 +ICON_SIZE: int = 16 class ServiceConfigDialog(Dialog): @@ -28,54 +30,57 @@ def __init__( service_name: str, canvas_node: "CanvasNode", node_id: int, - ): + ) -> None: title = f"{service_name} Service" super().__init__(app, title, master=master) - self.core = app.core - self.canvas_node = canvas_node - self.node_id = node_id - self.service_name = service_name - self.radiovar = tk.IntVar() - self.radiovar.set(2) - self.metadata = "" - self.filenames = [] - self.dependencies = [] - self.executables = [] - self.startup_commands = [] - self.validation_commands = [] - self.shutdown_commands = [] - self.default_startup = [] - self.default_validate = [] - self.default_shutdown = [] - self.validation_mode = None - self.validation_time = None - self.validation_period = None - self.directory_entry = None - self.default_directories = [] - self.temp_directories = [] - self.documentnew_img = self.app.get_icon(ImageEnum.DOCUMENTNEW, ICON_SIZE) - self.editdelete_img = self.app.get_icon(ImageEnum.EDITDELETE, ICON_SIZE) - self.notebook = None - self.metadata_entry = None - self.filename_combobox = None - self.dir_list = None - self.startup_commands_listbox = None - self.shutdown_commands_listbox = None - self.validate_commands_listbox = None - self.validation_time_entry = None - self.validation_mode_entry = None - self.service_file_data = None - self.validation_period_entry = None - self.original_service_files = {} - self.default_config = None - self.temp_service_files = {} - self.modified_files = set() - self.has_error = False + self.core: "CoreClient" = app.core + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = node_id + self.service_name: str = service_name + self.radiovar: tk.IntVar = tk.IntVar(value=2) + self.metadata: str = "" + self.filenames: List[str] = [] + self.dependencies: List[str] = [] + self.executables: List[str] = [] + self.startup_commands: List[str] = [] + self.validation_commands: List[str] = [] + self.shutdown_commands: List[str] = [] + self.default_startup: List[str] = [] + self.default_validate: List[str] = [] + self.default_shutdown: List[str] = [] + self.validation_mode: Optional[ServiceValidationMode] = None + self.validation_time: Optional[int] = None + self.validation_period: Optional[float] = None + self.directory_entry: Optional[ttk.Entry] = None + self.default_directories: List[str] = [] + self.temp_directories: List[str] = [] + self.documentnew_img: PhotoImage = self.app.get_icon( + ImageEnum.DOCUMENTNEW, ICON_SIZE + ) + self.editdelete_img: PhotoImage = self.app.get_icon( + ImageEnum.EDITDELETE, ICON_SIZE + ) + self.notebook: Optional[ttk.Notebook] = None + self.metadata_entry: Optional[ttk.Entry] = None + self.filename_combobox: Optional[ttk.Combobox] = None + self.dir_list: Optional[ListboxScroll] = None + self.startup_commands_listbox: Optional[tk.Listbox] = None + self.shutdown_commands_listbox: Optional[tk.Listbox] = None + self.validate_commands_listbox: Optional[tk.Listbox] = None + self.validation_time_entry: Optional[ttk.Entry] = None + self.validation_mode_entry: Optional[ttk.Entry] = None + self.service_file_data: Optional[CodeText] = None + self.validation_period_entry: Optional[ttk.Entry] = None + self.original_service_files: Dict[str, str] = {} + self.default_config: NodeServiceData = None + self.temp_service_files: Dict[str, str] = {} + self.modified_files: Set[str] = set() + self.has_error: bool = False self.load() if not self.has_error: self.draw() - def load(self): + def load(self) -> None: try: self.app.core.create_nodes_and_links() default_config = self.app.core.get_node_service( @@ -119,7 +124,7 @@ def load(self): self.app.show_grpc_exception("Get Node Service Error", e) self.has_error = True - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) @@ -142,7 +147,7 @@ def draw(self): self.draw_buttons() - def draw_tab_files(self): + def draw_tab_files(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -222,7 +227,7 @@ def draw_tab_files(self): "", self.update_temp_service_file_data ) - def draw_tab_directories(self): + def draw_tab_directories(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -257,7 +262,7 @@ def draw_tab_directories(self): button = ttk.Button(frame, text="Remove", command=self.remove_directory) button.grid(row=0, column=1, sticky="ew") - def draw_tab_startstop(self): + def draw_tab_startstop(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -311,7 +316,7 @@ def draw_tab_startstop(self): elif i == 2: self.validate_commands_listbox = listbox_scroll.listbox - def draw_tab_configuration(self): + def draw_tab_configuration(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -370,7 +375,7 @@ def draw_tab_configuration(self): for dependency in self.dependencies: listbox_scroll.listbox.insert("end", dependency) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(4): @@ -384,7 +389,7 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="ew") - def add_filename(self): + def add_filename(self) -> None: filename = self.filename_combobox.get() if filename not in self.filename_combobox["values"]: self.filename_combobox["values"] += (filename,) @@ -395,7 +400,7 @@ def add_filename(self): else: logging.debug("file already existed") - def delete_filename(self): + def delete_filename(self) -> None: cbb = self.filename_combobox filename = cbb.get() if filename in cbb["values"]: @@ -407,7 +412,7 @@ def delete_filename(self): self.modified_files.remove(filename) @classmethod - def add_command(cls, event: tk.Event): + def add_command(cls, event: tk.Event) -> None: frame_contains_button = event.widget.master listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get() @@ -419,7 +424,7 @@ def add_command(cls, event: tk.Event): listbox.insert(tk.END, command_to_add) @classmethod - def update_entry(cls, event: tk.Event): + def update_entry(cls, event: tk.Event) -> None: listbox = event.widget current_selection = listbox.curselection() if len(current_selection) > 0: @@ -431,7 +436,7 @@ def update_entry(cls, event: tk.Event): entry.insert(0, cmd) @classmethod - def delete_command(cls, event: tk.Event): + def delete_command(cls, event: tk.Event) -> None: button = event.widget frame_contains_button = button.master listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox @@ -441,7 +446,7 @@ def delete_command(cls, event: tk.Event): entry = frame_contains_button.grid_slaves(row=0, column=0)[0] entry.delete(0, tk.END) - def click_apply(self): + def click_apply(self) -> None: if ( not self.is_custom_command() and not self.is_custom_service_file() @@ -484,12 +489,12 @@ def click_apply(self): self.app.show_grpc_exception("Save Service Config Error", e) self.destroy() - def display_service_file_data(self, event: tk.Event): + def display_service_file_data(self, event: tk.Event) -> None: filename = self.filename_combobox.get() self.service_file_data.text.delete(1.0, "end") self.service_file_data.text.insert("end", self.temp_service_files[filename]) - def update_temp_service_file_data(self, event: tk.Event): + def update_temp_service_file_data(self, event: tk.Event) -> None: filename = self.filename_combobox.get() self.temp_service_files[filename] = self.service_file_data.text.get(1.0, "end") if self.temp_service_files[filename] != self.original_service_files.get( @@ -499,7 +504,7 @@ def update_temp_service_file_data(self, event: tk.Event): else: self.modified_files.discard(filename) - def is_custom_command(self): + def is_custom_command(self) -> bool: startup, validate, shutdown = self.get_commands() return ( set(self.default_startup) != set(startup) @@ -507,16 +512,16 @@ def is_custom_command(self): or set(self.default_shutdown) != set(shutdown) ) - def has_new_files(self): + def has_new_files(self) -> bool: return set(self.filenames) != set(self.filename_combobox["values"]) - def is_custom_service_file(self): + def is_custom_service_file(self) -> bool: return len(self.modified_files) > 0 - def is_custom_directory(self): + def is_custom_directory(self) -> bool: return set(self.default_directories) != set(self.dir_list.listbox.get(0, "end")) - def click_defaults(self): + def click_defaults(self) -> None: """ clears out any custom configuration permanently """ @@ -557,37 +562,37 @@ def click_defaults(self): self.current_service_color("") - def click_copy(self): + def click_copy(self) -> None: dialog = CopyServiceConfigDialog(self, self.app, self.node_id) dialog.show() @classmethod def append_commands( cls, commands: List[str], listbox: tk.Listbox, to_add: List[str] - ): + ) -> None: for cmd in to_add: commands.append(cmd) listbox.insert(tk.END, cmd) - def get_commands(self): + def get_commands(self) -> Tuple[List[str], List[str], List[str]]: startup = self.startup_commands_listbox.get(0, "end") shutdown = self.shutdown_commands_listbox.get(0, "end") validate = self.validate_commands_listbox.get(0, "end") return startup, validate, shutdown - def find_directory_button(self): + def find_directory_button(self) -> None: d = filedialog.askdirectory(initialdir="/") self.directory_entry.delete(0, "end") self.directory_entry.insert("end", d) - def add_directory(self): + def add_directory(self) -> None: d = self.directory_entry.get() if os.path.isdir(d): if d not in self.temp_directories: self.dir_list.listbox.insert("end", d) self.temp_directories.append(d) - def remove_directory(self): + def remove_directory(self) -> None: d = self.directory_entry.get() dirs = self.dir_list.listbox.get(0, "end") if d and d in self.temp_directories: @@ -599,14 +604,14 @@ def remove_directory(self): logging.debug("directory is not in the list") self.directory_entry.delete(0, "end") - def directory_select(self, event): + def directory_select(self, event) -> None: i = self.dir_list.listbox.curselection() if i: d = self.dir_list.listbox.get(i) self.directory_entry.delete(0, "end") self.directory_entry.insert("end", d) - def current_service_color(self, color=""): + def current_service_color(self, color="") -> None: """ change the current service label color """ diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index d31a5fb58..8138d8545 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -1,9 +1,10 @@ import logging from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -13,15 +14,15 @@ class SessionOptionsDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Session Options") - self.config_frame = None - self.has_error = False - self.config = self.get_config() + self.config_frame: Optional[ConfigFrame] = None + self.has_error: bool = False + self.config: Dict[str, ConfigOption] = self.get_config() if not self.has_error: self.draw() - def get_config(self): + def get_config(self) -> Dict[str, ConfigOption]: try: session_id = self.app.core.session_id response = self.app.core.client.get_session_options(session_id) @@ -31,7 +32,7 @@ def get_config(self): self.has_error = True self.destroy() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -48,7 +49,7 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def save(self): + def save(self) -> None: config = self.config_frame.parse_config() try: session_id = self.app.core.session_id diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index 9aa71a131..a7d702ebc 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -1,11 +1,12 @@ import logging import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional import grpc from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import SessionSummary from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.task import ProgressTask @@ -18,17 +19,17 @@ class SessionsDialog(Dialog): def __init__(self, app: "Application", is_start_app: bool = False) -> None: super().__init__(app, "Sessions") - self.is_start_app = is_start_app - self.selected_session = None - self.selected_id = None - self.tree = None - self.sessions = self.get_sessions() - self.connect_button = None - self.delete_button = None + self.is_start_app: bool = is_start_app + self.selected_session: Optional[int] = None + self.selected_id: Optional[int] = None + self.tree: Optional[ttk.Treeview] = None + self.sessions: List[SessionSummary] = self.get_sessions() + self.connect_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None self.protocol("WM_DELETE_WINDOW", self.on_closing) self.draw() - def get_sessions(self) -> List[core_pb2.SessionSummary]: + def get_sessions(self) -> List[SessionSummary]: try: response = self.app.core.client.get_sessions() logging.info("sessions: %s", response) diff --git a/daemon/core/gui/dialogs/shapemod.py b/daemon/core/gui/dialogs/shapemod.py index 4c84991bc..2ca067721 100644 --- a/daemon/core/gui/dialogs/shapemod.py +++ b/daemon/core/gui/dialogs/shapemod.py @@ -3,7 +3,7 @@ """ import tkinter as tk from tkinter import font, ttk -from typing import TYPE_CHECKING, List, Union +from typing import TYPE_CHECKING, List, Optional, Union from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.dialog import Dialog @@ -13,40 +13,41 @@ if TYPE_CHECKING: from core.gui.app import Application + from core.gui.graph.graph import CanvasGraph from core.gui.graph.shape import Shape -FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] -BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +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] class ShapeDialog(Dialog): - def __init__(self, app: "Application", shape: "Shape"): + def __init__(self, app: "Application", shape: "Shape") -> None: if is_draw_shape(shape.shape_type): title = "Add Shape" else: title = "Add Text" super().__init__(app, title) - self.canvas = app.canvas - self.fill = None - self.border = None - self.shape = shape + self.canvas: "CanvasGraph" = app.canvas + self.fill: Optional[ttk.Label] = None + self.border: Optional[ttk.Label] = None + self.shape: "Shape" = shape data = shape.shape_data - self.shape_text = tk.StringVar(value=data.text) - self.font = tk.StringVar(value=data.font) - self.font_size = tk.IntVar(value=data.font_size) - self.text_color = data.text_color + self.shape_text: tk.StringVar = tk.StringVar(value=data.text) + self.font: tk.StringVar = tk.StringVar(value=data.font) + self.font_size: tk.IntVar = tk.IntVar(value=data.font_size) + self.text_color: str = data.text_color fill_color = data.fill_color if not fill_color: fill_color = "#CFCFFF" - self.fill_color = fill_color - self.border_color = data.border_color - self.border_width = tk.IntVar(value=0) - self.bold = tk.BooleanVar(value=data.bold) - self.italic = tk.BooleanVar(value=data.italic) - self.underline = tk.BooleanVar(value=data.underline) + self.fill_color: str = fill_color + self.border_color: str = data.border_color + self.border_width: tk.IntVar = tk.IntVar(value=0) + self.bold: tk.BooleanVar = tk.BooleanVar(value=data.bold) + self.italic: tk.BooleanVar = tk.BooleanVar(value=data.italic) + self.underline: tk.BooleanVar = tk.BooleanVar(value=data.underline) self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_label_options() if is_draw_shape(self.shape.shape_type): @@ -54,7 +55,7 @@ def draw(self): self.draw_spacer() self.draw_buttons() - def draw_label_options(self): + def draw_label_options(self) -> None: label_frame = ttk.LabelFrame(self.top, text="Label", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -94,7 +95,7 @@ def draw_label_options(self): button = ttk.Checkbutton(frame, variable=self.underline, text="Underline") button.grid(row=0, column=2, sticky="ew") - def draw_shape_options(self): + def draw_shape_options(self) -> None: label_frame = ttk.LabelFrame(self.top, text="Shape", padding=FRAME_PAD) label_frame.grid(sticky="ew", pady=PADY) label_frame.columnconfigure(0, weight=1) @@ -129,7 +130,7 @@ def draw_shape_options(self): ) combobox.grid(row=0, column=1, sticky="nsew") - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="nsew") frame.columnconfigure(0, weight=1) @@ -139,28 +140,28 @@ def draw_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.cancel) button.grid(row=0, column=1, sticky="ew") - def choose_text_color(self): + def choose_text_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.text_color) self.text_color = color_picker.askcolor() - def choose_fill_color(self): + def choose_fill_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.fill_color) color = color_picker.askcolor() self.fill_color = color self.fill.config(background=color, text=color) - def choose_border_color(self): + def choose_border_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.border_color) color = color_picker.askcolor() self.border_color = color self.border.config(background=color, text=color) - def cancel(self): + def cancel(self) -> None: self.shape.delete() self.canvas.shapes.pop(self.shape.id) self.destroy() - def click_add(self): + def click_add(self) -> None: if is_draw_shape(self.shape.shape_type): self.add_shape() elif is_shape_text(self.shape.shape_type): @@ -181,7 +182,7 @@ def make_font(self) -> List[Union[int, str]]: text_font.append("underline") return text_font - def save_text(self): + def save_text(self) -> None: """ save info related to text or shape label """ @@ -194,7 +195,7 @@ def save_text(self): data.italic = self.italic.get() data.underline = self.underline.get() - def save_shape(self): + def save_shape(self) -> None: """ save info related to shape """ @@ -203,7 +204,7 @@ def save_shape(self): data.border_color = self.border_color data.border_width = int(self.border_width.get()) - def add_text(self): + def add_text(self) -> None: """ add text to canvas """ @@ -214,7 +215,7 @@ def add_text(self): ) self.save_text() - def add_shape(self): + def add_shape(self) -> None: self.canvas.itemconfig( self.shape.id, fill=self.fill_color, diff --git a/daemon/core/gui/dialogs/throughput.py b/daemon/core/gui/dialogs/throughput.py index 5210fe597..5b3cc9b34 100644 --- a/daemon/core/gui/dialogs/throughput.py +++ b/daemon/core/gui/dialogs/throughput.py @@ -3,10 +3,11 @@ """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.dialog import Dialog +from core.gui.graph.graph import CanvasGraph from core.gui.themes import FRAME_PAD, PADX, PADY if TYPE_CHECKING: @@ -14,21 +15,23 @@ class ThroughputDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Throughput Config") - self.canvas = app.canvas - self.show_throughput = tk.IntVar(value=1) - self.exponential_weight = tk.IntVar(value=1) - self.transmission = tk.IntVar(value=1) - self.reception = tk.IntVar(value=1) - self.threshold = tk.DoubleVar(value=self.canvas.throughput_threshold) - self.width = tk.IntVar(value=self.canvas.throughput_width) - self.color = self.canvas.throughput_color - self.color_button = None + self.canvas: CanvasGraph = app.canvas + self.show_throughput: tk.IntVar = tk.IntVar(value=1) + self.exponential_weight: tk.IntVar = tk.IntVar(value=1) + self.transmission: tk.IntVar = tk.IntVar(value=1) + self.reception: tk.IntVar = tk.IntVar(value=1) + self.threshold: tk.DoubleVar = tk.DoubleVar( + value=self.canvas.throughput_threshold + ) + self.width: tk.IntVar = tk.IntVar(value=self.canvas.throughput_width) + self.color: str = self.canvas.throughput_color + self.color_button: Optional[tk.Button] = None self.top.columnconfigure(0, weight=1) self.draw() - def draw(self): + def draw(self) -> None: button = ttk.Checkbutton( self.top, variable=self.show_throughput, @@ -97,12 +100,12 @@ def draw(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_color(self): + def click_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.color) self.color = color_picker.askcolor() self.color_button.config(bg=self.color, text=self.color, bd=0) - def click_save(self): + def click_save(self) -> None: self.canvas.throughput_threshold = self.threshold.get() self.canvas.throughput_width = self.width.get() self.canvas.throughput_color = self.color diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index b0435a2fa..326b3195d 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -1,8 +1,10 @@ from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -10,34 +12,36 @@ if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode + from core.gui.graph.graph import CanvasGraph -RANGE_COLOR = "#009933" -RANGE_WIDTH = 3 +RANGE_COLOR: str = "#009933" +RANGE_WIDTH: int = 3 class WlanConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: super().__init__(app, f"{canvas_node.core_node.name} WLAN Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.config_frame = None - self.range_entry = None - self.has_error = False - self.canvas = app.canvas - self.ranges = {} - self.positive_int = self.app.master.register(self.validate_and_update) + self.canvas: "CanvasGraph" = app.canvas + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.config_frame: Optional[ConfigFrame] = None + self.range_entry: Optional[ttk.Entry] = None + self.has_error: bool = False + self.ranges: Dict[int, int] = {} + self.positive_int: int = self.app.master.register(self.validate_and_update) try: - self.config = self.canvas_node.wlan_config - if not self.config: - self.config = self.app.core.get_wlan_config(self.node.id) + config = self.canvas_node.wlan_config + if not config: + config = self.app.core.get_wlan_config(self.node.id) + self.config: Dict[str, ConfigOption] = config self.init_draw_range() self.draw() except grpc.RpcError as e: self.app.show_grpc_exception("WLAN Config Error", e) - self.has_error = True + self.has_error: bool = True self.destroy() - def init_draw_range(self): + def init_draw_range(self) -> None: if self.canvas_node.id in self.canvas.wireless_network: for cid in self.canvas.wireless_network[self.canvas_node.id]: x, y = self.canvas.coords(cid) @@ -46,7 +50,7 @@ def init_draw_range(self): ) self.ranges[cid] = range_id - def draw(self): + 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) @@ -55,7 +59,7 @@ def draw(self): self.draw_apply_buttons() self.top.bind("", self.remove_ranges) - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: """ create node configuration options """ @@ -75,7 +79,7 @@ def draw_apply_buttons(self): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: """ retrieve user's wlan configuration and store the new configuration values """ @@ -87,7 +91,7 @@ def click_apply(self): self.remove_ranges() self.destroy() - def remove_ranges(self, event=None): + def remove_ranges(self, event=None) -> None: for cid in self.canvas.find_withtag("range"): self.canvas.delete(cid) self.ranges.clear() diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index f936bc79f..833011d86 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -57,7 +57,9 @@ def __init__( self.antennas: List[int] = [] self.antenna_images: Dict[int, PhotoImage] = {} # possible configurations - self.emane_model_configs: Dict[Tuple[str, Optional[int]], ConfigOption] = {} + self.emane_model_configs: Dict[ + Tuple[str, Optional[int]], Dict[str, ConfigOption] + ] = {} self.wlan_config: Dict[str, ConfigOption] = {} self.mobility_config: Dict[str, ConfigOption] = {} self.service_configs: Dict[str, NodeServiceData] = {} @@ -135,7 +137,7 @@ def scale_text(self) -> None: new_y = self._get_label_y() self.canvas.move(self.text_id, 0, new_y - prev_y) - def move(self, x: int, y: int) -> None: + def move(self, x: float, y: float) -> None: x, y = self.canvas.get_scaled_coords(x, y) current_x, current_y = self.canvas.coords(self.id) x_offset = x - current_x diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 523f8f110..75312e950 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -49,7 +49,7 @@ def __init__(self, app: "Application") -> None: self.canvas: CanvasGraph = app.canvas self.recent_menu: Optional[tk.Menu] = None self.edit_menu: Optional[tk.Menu] = None - self.observers_menu: Optional[tk.Menu] = None + self.observers_menu: Optional[ObserversMenu] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 402eca4da..dbb403dfa 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -14,7 +14,7 @@ class NodeDraw: def __init__(self) -> None: self.custom: bool = False - self.image: Optional[str] = None + self.image: Optional[PhotoImage] = None self.image_enum: Optional[ImageEnum] = None self.image_file: Optional[str] = None self.node_type: NodeType = None From 344f35e93e8ab9a4f5e988b652a344f77a1af774 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 19:04:55 -0700 Subject: [PATCH 056/210] pygui: updated ConfigFrame to have a disabled display option, updated nodes to stil show emane config during runtime, updated emane dialog and config dialogs to be in a viewable but disabled state during runtime --- daemon/core/gui/dialogs/emaneconfig.py | 33 ++++++++++++++------------ daemon/core/gui/graph/node.py | 4 ++++ daemon/core/gui/widgets.py | 28 +++++++++++++++------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index df6c61254..bb3347572 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -24,12 +24,15 @@ class GlobalEmaneDialog(Dialog): def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(app, "EMANE Configuration", master=master) self.config_frame: Optional[ConfigFrame] = None + self.enabled: bool = not self.app.core.is_runtime() self.draw() 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.app.core.emane_config) + self.config_frame = ConfigFrame( + self.top, self.app, self.app.core.emane_config, self.enabled + ) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_spacer() @@ -40,9 +43,9 @@ def draw_buttons(self) -> None: frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = ttk.Button(frame, text="Apply", command=self.click_apply) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) button.grid(row=0, column=0, sticky="ew", padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") @@ -68,6 +71,7 @@ def __init__( self.model: str = f"emane_{model}" self.iface_id: int = iface_id self.config_frame: Optional[ConfigFrame] = None + self.enabled: bool = not self.app.core.is_runtime() self.has_error: bool = False try: config = self.canvas_node.emane_model_configs.get( @@ -87,7 +91,7 @@ def __init__( 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 = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_spacer() @@ -98,9 +102,9 @@ def draw_buttons(self) -> None: frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = ttk.Button(frame, text="Apply", command=self.click_apply) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) button.grid(row=0, column=0, sticky="ew", padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") @@ -124,6 +128,7 @@ def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: model = self.node.emane.split("_")[1] self.emane_model: tk.StringVar = tk.StringVar(value=model) self.emane_model_button: Optional[ttk.Button] = None + self.enabled: bool = not self.app.core.is_runtime() self.draw() def draw(self) -> None: @@ -140,8 +145,9 @@ def draw_emane_configuration(self) -> None: """ label = ttk.Label( self.top, - text="The EMANE emulation system provides more complex wireless radio emulation " - "\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details", + text="The EMANE emulation system provides more complex wireless radio " + "emulation \nusing pluggable MAC and PHY modules. Refer to the wiki " + "for configuration option details", justify=tk.CENTER, ) label.grid(pady=PADY) @@ -171,11 +177,9 @@ def draw_emane_models(self) -> None: label.grid(row=0, column=0, sticky="w") # create combo box and its binding + state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( - frame, - textvariable=self.emane_model, - values=self.emane_models, - state="readonly", + frame, textvariable=self.emane_model, values=self.emane_models, state=state ) combobox.grid(row=0, column=1, sticky="ew") combobox.bind("<>", self.emane_model_change) @@ -213,10 +217,9 @@ def draw_apply_and_cancel(self) -> None: frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - - button = ttk.Button(frame, text="Apply", command=self.click_apply) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) button.grid(row=0, column=0, padx=PADX, sticky="ew") - button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 833011d86..a86ce4a3e 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -202,6 +202,10 @@ def show_context(self, event: tk.Event) -> None: is_emane = self.core_node.type == NodeType.EMANE if self.app.core.is_runtime(): self.context.add_command(label="Configure", command=self.show_config) + if is_emane: + self.context.add_command( + label="EMANE Config", command=self.show_emane_config + ) if is_wlan: self.context.add_command( label="WLAN Config", command=self.show_wlan_config diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 2eded212d..81bad0f56 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -85,12 +85,14 @@ def __init__( master: tk.Widget, app: "Application", config: Dict[str, ConfigOption], + enabled: bool = True, **kw: Any ) -> None: super().__init__(master, **kw) self.app: "Application" = app self.config: Dict[str, ConfigOption] = config self.values: Dict[str, tk.StringVar] = {} + self.enabled: bool = enabled def draw_config(self) -> None: group_mapping = {} @@ -110,8 +112,9 @@ def draw_config(self) -> None: value = tk.StringVar() if option.type == core_pb2.ConfigOptionType.BOOL: select = ("On", "Off") + state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( - tab.frame, textvariable=value, values=select, state="readonly" + tab.frame, textvariable=value, values=select, state=state ) combobox.grid(row=index, column=1, sticky="ew") if option.value == "1": @@ -121,32 +124,41 @@ def draw_config(self) -> None: elif option.select: value.set(option.value) select = tuple(option.select) + state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( - tab.frame, textvariable=value, values=select, state="readonly" + tab.frame, textvariable=value, values=select, state=state ) combobox.grid(row=index, column=1, sticky="ew") elif option.type == core_pb2.ConfigOptionType.STRING: value.set(option.value) + state = tk.NORMAL if self.enabled else tk.DISABLED if "file" in option.label: file_frame = ttk.Frame(tab.frame) file_frame.grid(row=index, column=1, sticky="ew") file_frame.columnconfigure(0, weight=1) - entry = ttk.Entry(file_frame, textvariable=value) + entry = ttk.Entry(file_frame, textvariable=value, state=state) entry.grid(row=0, column=0, sticky="ew", padx=PADX) func = partial(file_button_click, value, self) - button = ttk.Button(file_frame, text="...", command=func) + button = ttk.Button( + file_frame, text="...", command=func, state=state + ) button.grid(row=0, column=1) else: - entry = ttk.Entry(tab.frame, textvariable=value) + entry = ttk.Entry(tab.frame, textvariable=value, state=state) entry.grid(row=index, column=1, sticky="ew") - elif option.type in INT_TYPES: value.set(option.value) - entry = validation.PositiveIntEntry(tab.frame, textvariable=value) + state = tk.NORMAL if self.enabled else tk.DISABLED + entry = validation.PositiveIntEntry( + tab.frame, textvariable=value, state=state + ) entry.grid(row=index, column=1, sticky="ew") elif option.type == core_pb2.ConfigOptionType.FLOAT: value.set(option.value) - entry = validation.PositiveFloatEntry(tab.frame, textvariable=value) + state = tk.NORMAL if self.enabled else tk.DISABLED + entry = validation.PositiveFloatEntry( + tab.frame, textvariable=value, state=state + ) entry.grid(row=index, column=1, sticky="ew") else: logging.error("unhandled config option type: %s", option.type) From 27e35a52135795c4b3e6da7cf968db08a924cad3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 19:40:42 -0700 Subject: [PATCH 057/210] pygui: session options dialog is disabled during runtime --- daemon/core/gui/dialogs/sessionoptions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 8138d8545..fd021fee1 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -1,4 +1,5 @@ import logging +import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Dict, Optional @@ -19,6 +20,7 @@ def __init__(self, app: "Application") -> None: self.config_frame: Optional[ConfigFrame] = None self.has_error: bool = False self.config: Dict[str, ConfigOption] = self.get_config() + self.enabled: bool = not self.app.core.is_runtime() if not self.has_error: self.draw() @@ -35,8 +37,7 @@ def get_config(self) -> Dict[str, ConfigOption]: def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) - - self.config_frame = ConfigFrame(self.top, self.app, config=self.config) + self.config_frame = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) @@ -44,7 +45,8 @@ def draw(self) -> None: frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = ttk.Button(frame, text="Save", command=self.save) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Save", command=self.save, state=state) button.grid(row=0, column=0, padx=PADX, sticky="ew") button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") From f39ab1dee65d811aa6aa077c4378b18a1434f830 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:13:24 -0700 Subject: [PATCH 058/210] pygui: limit rj45 node to 1 link --- daemon/core/gui/graph/graph.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 531157506..fdf9ba21c 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -428,8 +428,9 @@ def handle_edge_release(self, _event: tk.Event) -> None: # edge dst must be a node logging.debug("current selected: %s", self.selected) + src_node = self.nodes.get(edge.src) dst_node = self.nodes.get(self.selected) - if not dst_node: + if not dst_node or not src_node: edge.delete() return @@ -444,15 +445,21 @@ def handle_edge_release(self, _event: tk.Event) -> None: edge.delete() return + # rj45 nodes can only support one link + if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges: + edge.delete() + return + if NodeUtils.is_rj45_node(dst_node.core_node.type) and dst_node.edges: + edge.delete() + return + # set dst node and snap edge to center edge.complete(self.selected) self.edges[edge.token] = edge - node_src = self.nodes[edge.src] - node_src.edges.add(edge) - node_dst = self.nodes[edge.dst] - node_dst.edges.add(edge) - self.core.create_link(edge, node_src, node_dst) + src_node.edges.add(edge) + dst_node.edges.add(edge) + self.core.create_link(edge, src_node, dst_node) def select_object(self, object_id: int, choose_multiple: bool = False) -> None: """ From 2145c07cb797f4c74bf84f0114d237c84737e8a4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:36:39 -0700 Subject: [PATCH 059/210] daemon: moved FRR_STATE_DIR from constants.py to frr service files --- daemon/core/configservices/frrservices/services.py | 4 ++-- daemon/core/constants.py.in | 1 - daemon/core/services/frr.py | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index ce8c305ca..720500778 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -1,7 +1,6 @@ import abc from typing import Any, Dict, List -from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet @@ -10,6 +9,7 @@ from core.nodes.network import WlanNode GROUP: str = "FRR" +FRR_STATE_DIR: str = "/var/run/frr" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -110,7 +110,7 @@ def data(self) -> Dict[str, Any]: frr_conf=frr_conf, frr_sbin_search=frr_sbin_search, frr_bin_search=frr_bin_search, - frr_state_dir=constants.FRR_STATE_DIR, + frr_state_dir=FRR_STATE_DIR, ifaces=ifaces, want_ip4=want_ip4, want_ip6=want_ip6, diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index 54f3a1c3a..4bf600f3b 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -4,7 +4,6 @@ COREDPY_VERSION = "@PACKAGE_VERSION@" CORE_CONF_DIR = "@CORE_CONF_DIR@" CORE_DATA_DIR = "@CORE_DATA_DIR@" QUAGGA_STATE_DIR = "@CORE_STATE_DIR@/run/quagga" -FRR_STATE_DIR = "@CORE_STATE_DIR@/run/frr" VNODED_BIN = which("vnoded", required=True) VCMD_BIN = which("vcmd", required=True) diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 13569772b..ceb04f931 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -6,7 +6,6 @@ import netaddr -from core import constants from core.emane.nodes import EmaneNet from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface @@ -14,6 +13,8 @@ from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService +FRR_STATE_DIR: str = "/var/run/frr" + class FRRZebra(CoreService): name: str = "FRRzebra" @@ -236,7 +237,7 @@ def generate_frr_boot(cls, node: CoreNode) -> str: cls.configs[0], frr_sbin_search, frr_bin_search, - constants.FRR_STATE_DIR, + FRR_STATE_DIR, ) for iface in node.get_ifaces(): cfg += f"ip link set dev {iface.name} down\n" From 1ef66181c6674c5352693141395053a9f137ca07 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:39:29 -0700 Subject: [PATCH 060/210] daemon: moved QUAGGA_STATE_DIR from constants.py to quagga service files --- daemon/core/configservices/quaggaservices/services.py | 4 ++-- daemon/core/constants.py.in | 1 - daemon/core/services/quagga.py | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index e18e8a1a7..194306640 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -2,7 +2,6 @@ import logging from typing import Any, Dict, List -from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet @@ -11,6 +10,7 @@ from core.nodes.network import WlanNode GROUP: str = "Quagga" +QUAGGA_STATE_DIR: str = "/var/run/quagga" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -79,7 +79,7 @@ def data(self) -> Dict[str, Any]: quagga_sbin_search = self.node.session.options.get_config( "quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga" ).strip('"') - quagga_state_dir = constants.QUAGGA_STATE_DIR + quagga_state_dir = QUAGGA_STATE_DIR quagga_conf = self.files[0] services = [] diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index 4bf600f3b..dfefb1285 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -3,7 +3,6 @@ from core.utils import which COREDPY_VERSION = "@PACKAGE_VERSION@" CORE_CONF_DIR = "@CORE_CONF_DIR@" CORE_DATA_DIR = "@CORE_DATA_DIR@" -QUAGGA_STATE_DIR = "@CORE_STATE_DIR@/run/quagga" VNODED_BIN = which("vnoded", required=True) VCMD_BIN = which("vcmd", required=True) diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index cb9e6b08d..9e2c7cc00 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -5,7 +5,6 @@ import netaddr -from core import constants from core.emane.nodes import EmaneNet from core.emulator.enumerations import LinkTypes from core.nodes.base import CoreNode @@ -14,6 +13,8 @@ from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService +QUAGGA_STATE_DIR: str = "/var/run/quagga" + class Zebra(CoreService): name: str = "zebra" @@ -226,7 +227,7 @@ def generate_quagga_boot(cls, node: CoreNode) -> str: cls.configs[0], quagga_sbin_search, quagga_bin_search, - constants.QUAGGA_STATE_DIR, + QUAGGA_STATE_DIR, ) From c43dd60a42c36bc2a2516ff54deeda0ec2438136 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:47:03 -0700 Subject: [PATCH 061/210] daemon: small adjustment in sdt.py --- daemon/core/plugins/sdt.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index ef36b0a45..27e54ff33 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -8,8 +8,7 @@ from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple from urllib.parse import urlparse -from core import constants -from core.constants import CORE_DATA_DIR +from core.constants import CORE_CONF_DIR, CORE_DATA_DIR from core.emane.nodes import EmaneNet from core.emulator.data import LinkData, NodeData from core.emulator.enumerations import EventTypes, MessageFlags @@ -264,8 +263,8 @@ def add_node(self, node: NodeBase) -> None: icon = node.icon if icon: node_type = node.name - icon = icon.replace("$CORE_DATA_DIR", constants.CORE_DATA_DIR) - icon = icon.replace("$CORE_CONF_DIR", constants.CORE_CONF_DIR) + icon = icon.replace("$CORE_DATA_DIR", CORE_DATA_DIR) + icon = icon.replace("$CORE_CONF_DIR", CORE_CONF_DIR) self.cmd(f"sprite {node_type} image {icon}") self.cmd( f'node {node.id} nodeLayer "{NODE_LAYER}" ' From e0c9f9c8326228aed4134a51cb391fb5cb650d42 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 09:11:37 -0700 Subject: [PATCH 062/210] daemon: moved executable check to CoreEmu and separated them into their own module core.executables --- daemon/core/configservice/manager.py | 6 ++---- daemon/core/constants.py.in | 14 -------------- daemon/core/emulator/coreemu.py | 28 +++++++++++++++++++++++++++- daemon/core/executables.py | 24 ++++++++++++++++++++++++ daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/client.py | 2 +- daemon/core/nodes/netclient.py | 2 +- daemon/core/nodes/network.py | 2 +- daemon/core/nodes/physical.py | 4 ++-- daemon/core/services/coreservices.py | 10 ++++++---- daemon/core/services/utility.py | 17 ++++++----------- daemon/core/utils.py | 4 ++-- daemon/core/xml/corexmldeployment.py | 2 +- 13 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 daemon/core/executables.py diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index ecea6e680..836576557 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -52,10 +52,8 @@ def add(self, service: Type[ConfigService]) -> None: for executable in service.executables: try: utils.which(executable, required=True) - except ValueError: - raise CoreError( - f"service({service.name}) missing executable {executable}" - ) + except CoreError as e: + raise CoreError(f"config service({service.name}): {e}") # make service available self.services[name] = service diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index dfefb1285..cb566e400 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -1,17 +1,3 @@ -from core.utils import which - COREDPY_VERSION = "@PACKAGE_VERSION@" CORE_CONF_DIR = "@CORE_CONF_DIR@" CORE_DATA_DIR = "@CORE_DATA_DIR@" - -VNODED_BIN = which("vnoded", required=True) -VCMD_BIN = which("vcmd", required=True) -SYSCTL_BIN = which("sysctl", required=True) -IP_BIN = which("ip", required=True) -ETHTOOL_BIN = which("ethtool", required=True) -TC_BIN = which("tc", required=True) -EBTABLES_BIN = which("ebtables", required=True) -MOUNT_BIN = which("mount", required=True) -UMOUNT_BIN = which("umount", required=True) -OVS_BIN = which("ovs-vsctl", required=False) -OVS_FLOW_BIN = which("ovs-ofctl", required=False) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 6a7f8b80c..86652013d 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -6,9 +6,10 @@ from typing import Dict, List, Type import core.services -from core import configservices +from core import configservices, utils from core.configservice.manager import ConfigServiceManager from core.emulator.session import Session +from core.executables import COMMON_REQUIREMENTS, OVS_REQUIREMENTS, VCMD_REQUIREMENTS from core.services.coreservices import ServiceManager @@ -65,10 +66,35 @@ def __init__(self, config: Dict[str, str] = None) -> None: if custom_dir: self.service_manager.load(custom_dir) + # check executables exist on path + self._validate_env() + # catch exit event atexit.register(self.shutdown) + def _validate_env(self) -> None: + """ + Validates executables CORE depends on exist on path. + + :return: nothing + :raises core.errors.CoreError: when an executable does not exist on path + """ + for requirement in COMMON_REQUIREMENTS: + utils.which(requirement, required=True) + use_ovs = self.config.get("ovs") == "True" + if use_ovs: + for requirement in OVS_REQUIREMENTS: + utils.which(requirement, required=True) + else: + for requirement in VCMD_REQUIREMENTS: + utils.which(requirement, required=True) + def load_services(self) -> None: + """ + Loads default and custom services for use within CORE. + + :return: nothing + """ # load default services self.service_errors = core.services.load() diff --git a/daemon/core/executables.py b/daemon/core/executables.py new file mode 100644 index 000000000..00d9b40f4 --- /dev/null +++ b/daemon/core/executables.py @@ -0,0 +1,24 @@ +from typing import List + +VNODED_BIN: str = "vnoded" +VCMD_BIN: str = "vcmd" +SYSCTL_BIN: str = "sysctl" +IP_BIN: str = "ip" +ETHTOOL_BIN: str = "ethtool" +TC_BIN: str = "tc" +EBTABLES_BIN: str = "ebtables" +MOUNT_BIN: str = "mount" +UMOUNT_BIN: str = "umount" +OVS_BIN: str = "ovs-vsctl" + +COMMON_REQUIREMENTS: List[str] = [ + SYSCTL_BIN, + IP_BIN, + ETHTOOL_BIN, + TC_BIN, + EBTABLES_BIN, + MOUNT_BIN, + UMOUNT_BIN, +] +VCMD_REQUIREMENTS: List[str] = [VNODED_BIN, VCMD_BIN] +OVS_REQUIREMENTS: List[str] = [OVS_BIN] diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index aae59b705..a691e4f5d 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -13,10 +13,10 @@ from core import utils from core.configservice.dependencies import ConfigServiceDependencies -from core.constants import MOUNT_BIN, VNODED_BIN from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError +from core.executables import MOUNT_BIN, VNODED_BIN from core.nodes.client import VnodeClient from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.netclient import LinuxNetClient, get_net_client @@ -753,7 +753,7 @@ def set_mac(self, iface_id: int, mac: str) -> None: iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: - self.node_net_client.device_mac(iface.name, mac) + self.node_net_client.device_mac(iface.name, str(iface.mac)) def add_ip(self, iface_id: int, ip: str) -> None: """ diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index c004b814e..f8cd38131 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -5,7 +5,7 @@ """ from core import utils -from core.constants import VCMD_BIN +from core.executables import VCMD_BIN class VnodeClient: diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index b6c164b5c..4486bd8fd 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -5,7 +5,7 @@ import netaddr -from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN +from core.executables import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN class LinuxNetClient: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 7d8f805eb..2c0c1cca1 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -10,7 +10,6 @@ import netaddr from core import utils -from core.constants import EBTABLES_BIN, TC_BIN from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import ( LinkTypes, @@ -20,6 +19,7 @@ RegisterTlvs, ) from core.errors import CoreCommandError, CoreError +from core.executables import EBTABLES_BIN, TC_BIN from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 3751d9eee..a025a496d 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -7,11 +7,11 @@ import threading from typing import IO, TYPE_CHECKING, List, Optional, Tuple -from core.constants import MOUNT_BIN, UMOUNT_BIN from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError +from core.executables import MOUNT_BIN, UMOUNT_BIN from core.nodes.base import CoreNetworkBase, CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import CoreNetwork, GreTap @@ -76,7 +76,7 @@ def set_mac(self, iface_id: int, mac: str) -> None: iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: - self.net_client.device_mac(iface.name, mac) + self.net_client.device_mac(iface.name, str(iface.mac)) def add_ip(self, iface_id: int, ip: str) -> None: """ diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index d22bc7a59..8c41c57dc 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -13,10 +13,9 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple, Type from core import utils -from core.constants import which from core.emulator.data import FileData from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode if TYPE_CHECKING: @@ -262,7 +261,10 @@ def add(cls, service: Type["CoreService"]) -> None: # validate dependent executables are present for executable in service.executables: - which(executable, required=True) + try: + utils.which(executable, required=True) + except CoreError as e: + raise CoreError(f"service({name}): {e}") # validate service on load succeeds try: @@ -300,7 +302,7 @@ def add_services(cls, path: str) -> List[str]: try: cls.add(service) - except ValueError as e: + except (CoreError, ValueError) as e: service_errors.append(service.name) logging.debug("not loading service(%s): %s", service.name, e) return service_errors diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 414f994e1..cf76b092a 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -5,8 +5,9 @@ import netaddr -from core import constants, utils +from core import utils from core.errors import CoreCommandError +from core.executables import SYSCTL_BIN from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -47,19 +48,13 @@ def generateconfiglinux(cls, node: CoreNode, filename: str) -> str: %(sysctl)s -w net.ipv4.conf.all.rp_filter=0 %(sysctl)s -w net.ipv4.conf.default.rp_filter=0 """ % { - "sysctl": constants.SYSCTL_BIN + "sysctl": SYSCTL_BIN } for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) - cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % ( - constants.SYSCTL_BIN, - name, - ) - cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % ( - constants.SYSCTL_BIN, - name, - ) - cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (constants.SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL_BIN, name) return cfg diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 0e082187f..459b7d568 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -33,7 +33,7 @@ import netaddr -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError if TYPE_CHECKING: from core.emulator.session import Session @@ -154,7 +154,7 @@ def which(command: str, required: bool) -> str: """ found_path = shutil.which(command) if found_path is None and required: - raise ValueError(f"failed to find required executable({command}) in path") + raise CoreError(f"failed to find required executable({command}) in path") return found_path diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 6035bd261..2235a7982 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -6,8 +6,8 @@ from lxml import etree from core import utils -from core.constants import IP_BIN from core.emane.nodes import EmaneNet +from core.executables import IP_BIN from core.nodes.base import CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface From 8f19ad057c5305c9e24975a4462e80f11c08228a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 09:24:40 -0700 Subject: [PATCH 063/210] daemon: cleaned up requirement check, updated github workflow to modify correct file --- .github/workflows/daemon-checks.yml | 2 +- daemon/core/emulator/coreemu.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml index 854095680..d955ee582 100644 --- a/.github/workflows/daemon-checks.yml +++ b/.github/workflows/daemon-checks.yml @@ -18,7 +18,7 @@ jobs: cd daemon cp setup.py.in setup.py cp core/constants.py.in core/constants.py - sed -i 's/True/False/g' core/constants.py + sed -i 's/required=True/required=False/g' core/emulator/coreemu.py pipenv sync --dev - name: isort run: | diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 86652013d..717232682 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -79,15 +79,14 @@ def _validate_env(self) -> None: :return: nothing :raises core.errors.CoreError: when an executable does not exist on path """ - for requirement in COMMON_REQUIREMENTS: - utils.which(requirement, required=True) + requirements = COMMON_REQUIREMENTS use_ovs = self.config.get("ovs") == "True" if use_ovs: - for requirement in OVS_REQUIREMENTS: - utils.which(requirement, required=True) + requirements += OVS_REQUIREMENTS else: - for requirement in VCMD_REQUIREMENTS: - utils.which(requirement, required=True) + requirements += VCMD_REQUIREMENTS + for requirement in requirements: + utils.which(requirement, required=True) def load_services(self) -> None: """ From 6dd6bc87abcd0045e0a729ee871499c60a31f3cc Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 09:35:11 -0700 Subject: [PATCH 064/210] daemon: renamed executable variables to be simpler --- daemon/core/executables.py | 34 +++++------- daemon/core/nodes/base.py | 6 +-- daemon/core/nodes/client.py | 4 +- daemon/core/nodes/netclient.py | 78 ++++++++++++++-------------- daemon/core/nodes/network.py | 18 +++---- daemon/core/nodes/physical.py | 6 +-- daemon/core/services/utility.py | 10 ++-- daemon/core/xml/corexmldeployment.py | 4 +- 8 files changed, 74 insertions(+), 86 deletions(-) diff --git a/daemon/core/executables.py b/daemon/core/executables.py index 00d9b40f4..17aecc1d1 100644 --- a/daemon/core/executables.py +++ b/daemon/core/executables.py @@ -1,24 +1,16 @@ from typing import List -VNODED_BIN: str = "vnoded" -VCMD_BIN: str = "vcmd" -SYSCTL_BIN: str = "sysctl" -IP_BIN: str = "ip" -ETHTOOL_BIN: str = "ethtool" -TC_BIN: str = "tc" -EBTABLES_BIN: str = "ebtables" -MOUNT_BIN: str = "mount" -UMOUNT_BIN: str = "umount" -OVS_BIN: str = "ovs-vsctl" +VNODED: str = "vnoded" +VCMD: str = "vcmd" +SYSCTL: str = "sysctl" +IP: str = "ip" +ETHTOOL: str = "ethtool" +TC: str = "tc" +EBTABLES: str = "ebtables" +MOUNT: str = "mount" +UMOUNT: str = "umount" +OVS_VSCTL: str = "ovs-vsctl" -COMMON_REQUIREMENTS: List[str] = [ - SYSCTL_BIN, - IP_BIN, - ETHTOOL_BIN, - TC_BIN, - EBTABLES_BIN, - MOUNT_BIN, - UMOUNT_BIN, -] -VCMD_REQUIREMENTS: List[str] = [VNODED_BIN, VCMD_BIN] -OVS_REQUIREMENTS: List[str] = [OVS_BIN] +COMMON_REQUIREMENTS: List[str] = [SYSCTL, IP, ETHTOOL, TC, EBTABLES, MOUNT, UMOUNT] +VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD] +OVS_REQUIREMENTS: List[str] = [OVS_VSCTL] diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index a691e4f5d..3999046d6 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -16,7 +16,7 @@ from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError -from core.executables import MOUNT_BIN, VNODED_BIN +from core.executables import MOUNT, VNODED from core.nodes.client import VnodeClient from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.netclient import LinuxNetClient, get_net_client @@ -511,7 +511,7 @@ def startup(self) -> None: # create a new namespace for this node using vnoded vnoded = ( - f"{VNODED_BIN} -v -c {self.ctrlchnlname} -l {self.ctrlchnlname}.log " + f"{VNODED} -v -c {self.ctrlchnlname} -l {self.ctrlchnlname}.log " f"-p {self.ctrlchnlname}.pid" ) if self.nodedir: @@ -640,7 +640,7 @@ def mount(self, source: str, target: str) -> None: source = os.path.abspath(source) logging.debug("node(%s) mounting: %s at %s", self.name, source, target) self.cmd(f"mkdir -p {target}") - self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}") + self.cmd(f"{MOUNT} -n --bind {source} {target}") self._mounts.append((source, target)) def next_iface_id(self) -> int: diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index f8cd38131..93e099cf8 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -5,7 +5,7 @@ """ from core import utils -from core.executables import VCMD_BIN +from core.executables import VCMD class VnodeClient: @@ -50,7 +50,7 @@ def close(self) -> None: pass def create_cmd(self, args: str) -> str: - return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}" + return f"{VCMD} -c {self.ctrlchnlname} -- {args}" def check_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: """ diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 4486bd8fd..96a1f4beb 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -5,7 +5,7 @@ import netaddr -from core.executables import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN +from core.executables import ETHTOOL, IP, OVS_VSCTL, SYSCTL, TC class LinuxNetClient: @@ -38,7 +38,7 @@ def create_route(self, route: str, device: str) -> None: :param device: device to add route to :return: nothing """ - self.run(f"{IP_BIN} route add {route} dev {device}") + self.run(f"{IP} route add {route} dev {device}") def device_up(self, device: str) -> None: """ @@ -47,7 +47,7 @@ def device_up(self, device: str) -> None: :param device: device to bring up :return: nothing """ - self.run(f"{IP_BIN} link set {device} up") + self.run(f"{IP} link set {device} up") def device_down(self, device: str) -> None: """ @@ -56,7 +56,7 @@ def device_down(self, device: str) -> None: :param device: device to bring down :return: nothing """ - self.run(f"{IP_BIN} link set {device} down") + self.run(f"{IP} link set {device} down") def device_name(self, device: str, name: str) -> None: """ @@ -66,7 +66,7 @@ def device_name(self, device: str, name: str) -> None: :param name: name to set :return: nothing """ - self.run(f"{IP_BIN} link set {device} name {name}") + self.run(f"{IP} link set {device} name {name}") def device_show(self, device: str) -> str: """ @@ -75,7 +75,7 @@ def device_show(self, device: str) -> str: :param device: device to get information for :return: device information """ - return self.run(f"{IP_BIN} link show {device}") + return self.run(f"{IP} link show {device}") def address_show(self, device: str) -> str: """ @@ -84,7 +84,7 @@ def address_show(self, device: str) -> str: :param device: device name :return: address information """ - return self.run(f"{IP_BIN} address show {device}") + return self.run(f"{IP} address show {device}") def get_mac(self, device: str) -> str: """ @@ -112,7 +112,7 @@ def device_ns(self, device: str, namespace: str) -> None: :param namespace: namespace to set device to :return: nothing """ - self.run(f"{IP_BIN} link set {device} netns {namespace}") + self.run(f"{IP} link set {device} netns {namespace}") def device_flush(self, device: str) -> None: """ @@ -123,7 +123,7 @@ def device_flush(self, device: str) -> None: """ self.run( f"[ -e /sys/class/net/{device} ] && " - f"{IP_BIN} address flush dev {device} || true", + f"{IP} address flush dev {device} || true", shell=True, ) @@ -135,7 +135,7 @@ def device_mac(self, device: str, mac: str) -> None: :param mac: mac to set :return: nothing """ - self.run(f"{IP_BIN} link set dev {device} address {mac}") + self.run(f"{IP} link set dev {device} address {mac}") def delete_device(self, device: str) -> None: """ @@ -144,7 +144,7 @@ def delete_device(self, device: str) -> None: :param device: device to delete :return: nothing """ - self.run(f"{IP_BIN} link delete {device}") + self.run(f"{IP} link delete {device}") def delete_tc(self, device: str) -> None: """ @@ -153,7 +153,7 @@ def delete_tc(self, device: str) -> None: :param device: device to remove tc :return: nothing """ - self.run(f"{TC_BIN} qdisc delete dev {device} root") + self.run(f"{TC} qdisc delete dev {device} root") def checksums_off(self, iface_name: str) -> None: """ @@ -162,7 +162,7 @@ def checksums_off(self, iface_name: str) -> None: :param iface_name: interface to update :return: nothing """ - self.run(f"{ETHTOOL_BIN} -K {iface_name} rx off tx off") + self.run(f"{ETHTOOL} -K {iface_name} rx off tx off") def create_address(self, device: str, address: str, broadcast: str = None) -> None: """ @@ -174,15 +174,13 @@ def create_address(self, device: str, address: str, broadcast: str = None) -> No :return: nothing """ if broadcast is not None: - self.run( - f"{IP_BIN} address add {address} broadcast {broadcast} dev {device}" - ) + self.run(f"{IP} address add {address} broadcast {broadcast} dev {device}") else: - self.run(f"{IP_BIN} address add {address} dev {device}") + self.run(f"{IP} address add {address} dev {device}") if netaddr.valid_ipv6(address.split("/")[0]): # IPv6 addresses are removed by default on interface down. # Make sure that the IPv6 address we add is not removed - self.run(f"{SYSCTL_BIN} -w net.ipv6.conf.{device}.keep_addr_on_down=1") + self.run(f"{SYSCTL} -w net.ipv6.conf.{device}.keep_addr_on_down=1") def delete_address(self, device: str, address: str) -> None: """ @@ -192,7 +190,7 @@ def delete_address(self, device: str, address: str) -> None: :param address: address to remove :return: nothing """ - self.run(f"{IP_BIN} address delete {address} dev {device}") + self.run(f"{IP} address delete {address} dev {device}") def create_veth(self, name: str, peer: str) -> None: """ @@ -202,7 +200,7 @@ def create_veth(self, name: str, peer: str) -> None: :param peer: peer name :return: nothing """ - self.run(f"{IP_BIN} link add name {name} type veth peer name {peer}") + self.run(f"{IP} link add name {name} type veth peer name {peer}") def create_gretap( self, device: str, address: str, local: str, ttl: int, key: int @@ -217,7 +215,7 @@ def create_gretap( :param key: key for tap :return: nothing """ - cmd = f"{IP_BIN} link add {device} type gretap remote {address}" + cmd = f"{IP} link add {device} type gretap remote {address}" if local is not None: cmd += f" local {local}" if ttl is not None: @@ -233,11 +231,11 @@ def create_bridge(self, name: str) -> None: :param name: bridge name :return: nothing """ - self.run(f"{IP_BIN} link add name {name} type bridge") - self.run(f"{IP_BIN} link set {name} type bridge stp_state 0") - self.run(f"{IP_BIN} link set {name} type bridge forward_delay 0") - self.run(f"{IP_BIN} link set {name} type bridge mcast_snooping 0") - self.run(f"{IP_BIN} link set {name} type bridge group_fwd_mask 65528") + self.run(f"{IP} link add name {name} type bridge") + self.run(f"{IP} link set {name} type bridge stp_state 0") + self.run(f"{IP} link set {name} type bridge forward_delay 0") + self.run(f"{IP} link set {name} type bridge mcast_snooping 0") + self.run(f"{IP} link set {name} type bridge group_fwd_mask 65528") self.device_up(name) def delete_bridge(self, name: str) -> None: @@ -248,7 +246,7 @@ def delete_bridge(self, name: str) -> None: :return: nothing """ self.device_down(name) - self.run(f"{IP_BIN} link delete {name} type bridge") + self.run(f"{IP} link delete {name} type bridge") def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ @@ -258,7 +256,7 @@ def set_iface_master(self, bridge_name: str, iface_name: str) -> None: :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {iface_name} master {bridge_name}") + self.run(f"{IP} link set dev {iface_name} master {bridge_name}") self.device_up(iface_name) def delete_iface(self, bridge_name: str, iface_name: str) -> None: @@ -269,7 +267,7 @@ def delete_iface(self, bridge_name: str, iface_name: str) -> None: :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {iface_name} nomaster") + self.run(f"{IP} link set dev {iface_name} nomaster") def existing_bridges(self, _id: int) -> bool: """ @@ -278,7 +276,7 @@ def existing_bridges(self, _id: int) -> bool: :param _id: node id to check bridges for :return: True if there are existing bridges, False otherwise """ - output = self.run(f"{IP_BIN} -o link show type bridge") + output = self.run(f"{IP} -o link show type bridge") lines = output.split("\n") for line in lines: values = line.split(":") @@ -299,7 +297,7 @@ def disable_mac_learning(self, name: str) -> None: :param name: bridge name :return: nothing """ - self.run(f"{IP_BIN} link set {name} type bridge ageing_time 0") + self.run(f"{IP} link set {name} type bridge ageing_time 0") class OvsNetClient(LinuxNetClient): @@ -314,10 +312,10 @@ def create_bridge(self, name: str) -> None: :param name: bridge name :return: nothing """ - self.run(f"{OVS_BIN} add-br {name}") - self.run(f"{OVS_BIN} set bridge {name} stp_enable=false") - self.run(f"{OVS_BIN} set bridge {name} other_config:stp-max-age=6") - self.run(f"{OVS_BIN} set bridge {name} other_config:stp-forward-delay=4") + self.run(f"{OVS_VSCTL} add-br {name}") + self.run(f"{OVS_VSCTL} set bridge {name} stp_enable=false") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:stp-max-age=6") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:stp-forward-delay=4") self.device_up(name) def delete_bridge(self, name: str) -> None: @@ -328,7 +326,7 @@ def delete_bridge(self, name: str) -> None: :return: nothing """ self.device_down(name) - self.run(f"{OVS_BIN} del-br {name}") + self.run(f"{OVS_VSCTL} del-br {name}") def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ @@ -338,7 +336,7 @@ def set_iface_master(self, bridge_name: str, iface_name: str) -> None: :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} add-port {bridge_name} {iface_name}") + self.run(f"{OVS_VSCTL} add-port {bridge_name} {iface_name}") self.device_up(iface_name) def delete_iface(self, bridge_name: str, iface_name: str) -> None: @@ -349,7 +347,7 @@ def delete_iface(self, bridge_name: str, iface_name: str) -> None: :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} del-port {bridge_name} {iface_name}") + self.run(f"{OVS_VSCTL} del-port {bridge_name} {iface_name}") def existing_bridges(self, _id: int) -> bool: """ @@ -358,7 +356,7 @@ def existing_bridges(self, _id: int) -> bool: :param _id: node id to check bridges for :return: True if there are existing bridges, False otherwise """ - output = self.run(f"{OVS_BIN} list-br") + output = self.run(f"{OVS_VSCTL} list-br") if output: for line in output.split("\n"): fields = line.split(".") @@ -373,7 +371,7 @@ def disable_mac_learning(self, name: str) -> None: :param name: bridge name :return: nothing """ - self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:mac-aging-time=0") def get_net_client(use_ovs: bool, run: Callable[..., str]) -> LinuxNetClient: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 2c0c1cca1..d418a42c6 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -19,7 +19,7 @@ RegisterTlvs, ) from core.errors import CoreCommandError, CoreError -from core.executables import EBTABLES_BIN, TC_BIN +from core.executables import EBTABLES, TC from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client @@ -104,7 +104,7 @@ def ebatomiccmd(self, cmd: str) -> str: :param cmd: ebtable command :return: ebtable atomic command """ - return f"{EBTABLES_BIN} --atomic-file {self.atomic_file} {cmd}" + return f"{EBTABLES} --atomic-file {self.atomic_file} {cmd}" def lastupdate(self, wlan: "CoreNetwork") -> float: """ @@ -338,8 +338,8 @@ def shutdown(self) -> None: self.net_client.delete_bridge(self.brname) if self.has_ebtables_chain: cmds = [ - f"{EBTABLES_BIN} -D FORWARD --logical-in {self.brname} -j {self.brname}", - f"{EBTABLES_BIN} -X {self.brname}", + f"{EBTABLES} -D FORWARD --logical-in {self.brname} -j {self.brname}", + f"{EBTABLES} -X {self.brname}", ] ebtablescmds(self.host_cmd, cmds) except CoreCommandError: @@ -448,7 +448,7 @@ def linkconfig( :return: nothing """ devname = iface.localname - tc = f"{TC_BIN} qdisc replace dev {devname}" + tc = f"{TC} qdisc replace dev {devname}" parent = "root" changed = False bw = options.bandwidth @@ -466,7 +466,7 @@ def linkconfig( changed = True elif iface.getparam("has_tbf") and bw <= 0: if self.up: - cmd = f"{TC_BIN} qdisc delete dev {devname} {parent}" + cmd = f"{TC} qdisc delete dev {devname} {parent}" iface.host_cmd(cmd) iface.setparam("has_tbf", False) # removing the parent removes the child @@ -512,14 +512,12 @@ def linkconfig( if not iface.getparam("has_netem"): return if self.up: - cmd = f"{TC_BIN} qdisc delete dev {devname} {parent} handle 10:" + cmd = f"{TC} qdisc delete dev {devname} {parent} handle 10:" iface.host_cmd(cmd) iface.setparam("has_netem", False) elif len(netem) > 1: if self.up: - cmd = ( - f"{TC_BIN} qdisc replace dev {devname} {parent} handle 10: {netem}" - ) + cmd = f"{TC} qdisc replace dev {devname} {parent} handle 10: {netem}" iface.host_cmd(cmd) iface.setparam("has_netem", True) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index a025a496d..f48a0d10d 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -11,7 +11,7 @@ from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError -from core.executables import MOUNT_BIN, UMOUNT_BIN +from core.executables import MOUNT, UMOUNT from core.nodes.base import CoreNetworkBase, CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import CoreNetwork, GreTap @@ -186,13 +186,13 @@ def mount(self, source: str, target: str) -> None: source = os.path.abspath(source) logging.info("mounting %s at %s", source, target) os.makedirs(target) - self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir) + self.host_cmd(f"{MOUNT} --bind {source} {target}", cwd=self.nodedir) self._mounts.append((source, target)) def umount(self, target: str) -> None: logging.info("unmounting '%s'", target) try: - self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir) + self.host_cmd(f"{UMOUNT} -l {target}", cwd=self.nodedir) except CoreCommandError: logging.exception("unmounting failed for %s", target) diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index cf76b092a..774c41045 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -7,7 +7,7 @@ from core import utils from core.errors import CoreCommandError -from core.executables import SYSCTL_BIN +from core.executables import SYSCTL from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -48,13 +48,13 @@ def generateconfiglinux(cls, node: CoreNode, filename: str) -> str: %(sysctl)s -w net.ipv4.conf.all.rp_filter=0 %(sysctl)s -w net.ipv4.conf.default.rp_filter=0 """ % { - "sysctl": SYSCTL_BIN + "sysctl": SYSCTL } for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) - cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL_BIN, name) - cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (SYSCTL_BIN, name) - cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL, name) + cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (SYSCTL, name) + cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL, name) return cfg diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 2235a7982..51201787c 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -7,7 +7,7 @@ from core import utils from core.emane.nodes import EmaneNet -from core.executables import IP_BIN +from core.executables import IP from core.nodes.base import CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface @@ -83,7 +83,7 @@ def get_address_type(address: str) -> str: def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]: if hostname == "localhost": addresses = [] - args = f"{IP_BIN} -o -f inet address show" + args = f"{IP} -o -f inet address show" output = utils.cmd(args) for line in output.split(os.linesep): split = line.split() From 8e2cfa61c90413b9bfbb560dbcde89134d6c0380 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 10:09:16 -0700 Subject: [PATCH 065/210] pygui: size and scale meter width and height are no longer editable, but will dynamically update with changes to related size/scale values --- daemon/core/gui/dialogs/canvassizeandscale.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index b93bd9203..38cecc830 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -66,10 +66,12 @@ def draw_size(self) -> None: label.grid(row=0, column=0, sticky="w", padx=PADX) entry = validation.PositiveIntEntry(frame, textvariable=self.pixel_width) entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w", padx=PADX) entry = validation.PositiveIntEntry(frame, textvariable=self.pixel_height) entry.grid(row=0, column=3, sticky="ew", padx=PADX) + entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="Pixels") label.grid(row=0, column=4, sticky="w") @@ -80,11 +82,15 @@ def draw_size(self) -> None: frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") label.grid(row=0, column=0, sticky="w", padx=PADX) - entry = validation.PositiveFloatEntry(frame, textvariable=self.meters_width) + entry = validation.PositiveFloatEntry( + frame, textvariable=self.meters_width, state=tk.DISABLED + ) entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w", padx=PADX) - entry = validation.PositiveFloatEntry(frame, textvariable=self.meters_height) + entry = validation.PositiveFloatEntry( + frame, textvariable=self.meters_height, state=tk.DISABLED + ) entry.grid(row=0, column=3, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") @@ -101,6 +107,7 @@ def draw_scale(self) -> None: label.grid(row=0, column=0, sticky="w", padx=PADX) entry = validation.PositiveFloatEntry(frame, textvariable=self.scale) entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") @@ -173,6 +180,13 @@ def draw_buttons(self) -> None: button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + def size_scale_keyup(self, _event: tk.Event) -> None: + scale = self.scale.get() + width = self.pixel_width.get() + height = self.pixel_height.get() + self.meters_width.set(width / PIXEL_SCALE * scale) + self.meters_height.set(height / PIXEL_SCALE * scale) + def click_apply(self) -> None: width, height = self.pixel_width.get(), self.pixel_height.get() self.canvas.redraw_canvas((width, height)) From 14573184e01ea727ce958640a92be41025433ed3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 13:28:41 -0700 Subject: [PATCH 066/210] pygui: fixed syning session location settings when not in runtime mode, for saving xml --- daemon/core/gui/coreclient.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 39ee486ae..8050d7f0a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -718,7 +718,7 @@ def create_nodes_and_links(self) -> None: def send_data(self) -> None: """ - send to daemon all session info, but don't start the session + Send to daemon all session info, but don't start the session """ self.create_nodes_and_links() for config_proto in self.get_wlan_configs_proto(): @@ -759,6 +759,17 @@ def send_data(self) -> None: if self.emane_config: config = {x: self.emane_config[x].value for x in self.emane_config} self.client.set_emane_config(self.session_id, config) + if self.location: + self.client.set_session_location( + self.session_id, + self.location.x, + self.location.y, + self.location.z, + self.location.lat, + self.location.lon, + self.location.alt, + self.location.scale, + ) self.set_metadata() def close(self) -> None: From 9649337f185af4e7cd90ef93029871b2dcb92e60 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 13:31:31 -0700 Subject: [PATCH 067/210] daemon: updated xml to save links using consistent iface1/2 naming, still fallback to reading interface_one/two --- daemon/core/xml/corexml.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index d3cc85d85..340d81d0f 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -520,14 +520,14 @@ def create_link_element(self, link_data: LinkData) -> etree.Element: # check for interface one if link_data.iface1 is not None: iface1 = self.create_iface_element( - "interface1", link_data.node1_id, link_data.iface1 + "iface1", link_data.node1_id, link_data.iface1 ) link_element.append(iface1) # check for interface two if link_data.iface2 is not None: iface2 = self.create_iface_element( - "interface2", link_data.node2_id, link_data.iface2 + "iface2", link_data.node2_id, link_data.iface2 ) link_element.append(iface2) @@ -907,14 +907,14 @@ def read_links(self) -> None: node2_id = get_int(link_element, "node_two") node_set = frozenset((node1_id, node2_id)) - iface1_element = link_element.find("interface1") + iface1_element = link_element.find("iface1") if iface1_element is None: iface1_element = link_element.find("interface_one") iface1_data = None if iface1_element is not None: iface1_data = create_iface_data(iface1_element) - iface2_element = link_element.find("interface2") + iface2_element = link_element.find("iface2") if iface2_element is None: iface2_element = link_element.find("interface_two") iface2_data = None From 7215f852b8b591d20b4eaab39fbb8582ad46557e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 13:34:40 -0700 Subject: [PATCH 068/210] grpc: added check for emane pathloss when nem id is None and throw an error --- daemon/core/api/grpc/grpcutils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index b63cb8959..8df545cd6 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -487,4 +487,8 @@ def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: if not isinstance(net, EmaneNet): message = f"{node.name} interface {iface_id} is not an EMANE network" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) - return net.getnemid(iface) + nem_id = net.getnemid(iface) + if nem_id is None: + message = f"{node.name} interface {iface_id} nem id does not exist" + context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) + return nem_id From 60d9fe2026add397e9bf004a9ee7dd7057ad21c1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 14:48:27 -0700 Subject: [PATCH 069/210] pygui: clear throughput labels when disabling throughput --- daemon/core/gui/coreclient.py | 1 + daemon/core/gui/graph/edges.py | 7 +++++-- daemon/core/gui/graph/graph.py | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8050d7f0a..d35f62e5e 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -221,6 +221,7 @@ def cancel_throughputs(self) -> None: if self.handling_throughputs: self.handling_throughputs.cancel() self.handling_throughputs = None + self.app.canvas.clear_throughputs() def cancel_events(self) -> None: if self.handling_events: diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index e9ac25878..29632086a 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -145,6 +145,10 @@ def middle_label_text(self, text: str) -> None: else: self.canvas.itemconfig(self.middle_label, text=text) + def clear_middle_label(self) -> None: + self.canvas.delete(self.middle_label) + self.middle_label = None + def node_label_positions(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: src_x, src_y, _, _, dst_x, dst_y = self.canvas.coords(self.id) v1 = dst_x - src_x @@ -216,11 +220,10 @@ def delete(self) -> None: logging.debug("deleting canvas edge, id: %s", self.id) self.canvas.delete(self.id) self.canvas.delete(self.src_label) - self.canvas.delete(self.middle_label) self.canvas.delete(self.dst_label) + self.clear_middle_label() self.id = None self.src_label = None - self.middle_label = None self.dst_label = None diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index fdf9ba21c..07519c3fd 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -997,6 +997,10 @@ def paste(self) -> None: ) self.tag_raise(tags.NODE) + def clear_throughputs(self) -> None: + for edge in self.edges.values(): + edge.clear_middle_label() + def scale_graph(self) -> None: for nid, canvas_node in self.nodes.items(): img = None From 6490b5b9cbe6e39adb57aeb00675d6b828355746 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 16:11:39 -0700 Subject: [PATCH 070/210] pygui: fixed and changed custom service copy to focus only on copying the current file displayed from any other nodes with a customized version --- daemon/core/gui/dialogs/copyserviceconfig.py | 226 ++++++------------- daemon/core/gui/dialogs/serviceconfig.py | 6 +- 2 files changed, 77 insertions(+), 155 deletions(-) diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index 35559cb98..2a01249da 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -4,80 +4,58 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING, Dict, Optional from core.gui.dialogs.dialog import Dialog -from core.gui.themes import FRAME_PAD, PADX -from core.gui.widgets import CodeText +from core.gui.themes import PADX, PADY +from core.gui.widgets import CodeText, ListboxScroll if TYPE_CHECKING: from core.gui.app import Application + from core.gui.dialogs.serviceconfig import ServiceConfigDialog class CopyServiceConfigDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application", node_id: int) -> None: - super().__init__(app, f"Copy services to node {node_id}", master=master) - self.parent = master - self.node_id = node_id - self.service_configs = app.core.service_configs - self.file_configs = app.core.file_configs - self.tree = None + def __init__( + self, + app: "Application", + dialog: "ServiceConfigDialog", + name: str, + service: str, + file_name: str, + ) -> None: + super().__init__(app, f"Copy Custom File to {name}", master=dialog) + self.dialog: "ServiceConfigDialog" = dialog + self.service: str = service + self.file_name: str = file_name + self.listbox: Optional[tk.Listbox] = None + self.nodes: Dict[str, int] = {} self.draw() def draw(self) -> None: self.top.columnconfigure(0, weight=1) - self.tree = ttk.Treeview(self.top) - self.tree.grid(row=0, column=0, sticky="ew", padx=PADX) - self.tree["columns"] = () - self.tree.column("#0", width=270, minwidth=270, stretch=tk.YES) - self.tree.heading("#0", text="Service configuration items", anchor=tk.CENTER) - custom_nodes = set(self.service_configs).union(set(self.file_configs)) - for nid in custom_nodes: - treeid = self.tree.insert("", "end", text=f"n{nid}", tags="node") - services = self.service_configs.get(nid, None) - files = self.file_configs.get(nid, None) - tree_ids = {} - if services: - for service, config in services.items(): - serviceid = self.tree.insert( - treeid, "end", text=service, tags="service" - ) - tree_ids[service] = serviceid - cmdup = config.startup[:] - cmddown = config.shutdown[:] - cmdval = config.validate[:] - self.tree.insert( - serviceid, - "end", - text=f"cmdup=({str(cmdup)[1:-1]})", - tags=("cmd", "up"), - ) - self.tree.insert( - serviceid, - "end", - text=f"cmddown=({str(cmddown)[1:-1]})", - tags=("cmd", "down"), - ) - self.tree.insert( - serviceid, - "end", - text=f"cmdval=({str(cmdval)[1:-1]})", - tags=("cmd", "val"), - ) - if files: - for service, configs in files.items(): - if service in tree_ids: - serviceid = tree_ids[service] - else: - serviceid = self.tree.insert( - treeid, "end", text=service, tags="service" - ) - tree_ids[service] = serviceid - for filename, data in configs.items(): - self.tree.insert(serviceid, "end", text=filename, tags="file") + self.top.rowconfigure(1, weight=1) + label = ttk.Label( + self.top, text=f"{self.service} - {self.file_name}", anchor=tk.CENTER + ) + label.grid(sticky="ew", pady=PADY) + + listbox_scroll = ListboxScroll(self.top) + listbox_scroll.grid(sticky="nsew", pady=PADY) + self.listbox = listbox_scroll.listbox + for canvas_node in self.app.canvas.nodes.values(): + file_configs = canvas_node.service_file_configs.get(self.service) + if not file_configs: + continue + data = file_configs.get(self.file_name) + if not data: + continue + name = canvas_node.core_node.name + self.nodes[name] = canvas_node.id + self.listbox.insert(tk.END, name) frame = ttk.Frame(self.top) - frame.grid(row=1, column=0) + frame.grid(sticky="ew") for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Copy", command=self.click_copy) @@ -85,118 +63,58 @@ def draw(self) -> None: button = ttk.Button(frame, text="View", command=self.click_view) button.grid(row=0, column=1, sticky="ew", padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=2, sticky="ew", padx=PADX) + button.grid(row=0, column=2, sticky="ew") def click_copy(self) -> None: - selected = self.tree.selection() - if selected: - item = self.tree.item(selected[0]) - if "file" in item["tags"]: - filename = item["text"] - nid, service = self.get_node_service(selected) - data = self.file_configs[nid][service][filename] - if service == self.parent.service_name: - self.parent.temp_service_files[filename] = data - self.parent.modified_files.add(filename) - if self.parent.filename_combobox.get() == filename: - self.parent.service_file_data.text.delete(1.0, "end") - self.parent.service_file_data.text.insert("end", data) - if "cmd" in item["tags"]: - nid, service = self.get_node_service(selected) - if service == self.master.service_name: - cmds = self.service_configs[nid][service] - if "up" in item["tags"]: - self.master.append_commands( - self.master.startup_commands, - self.master.startup_commands_listbox, - cmds.startup, - ) - elif "down" in item["tags"]: - self.master.append_commands( - self.master.shutdown_commands, - self.master.shutdown_commands_listbox, - cmds.shutdown, - ) - - elif "val" in item["tags"]: - self.master.append_commands( - self.master.validate_commands, - self.master.validate_commands_listbox, - cmds.validate, - ) + selection = self.listbox.curselection() + if not selection: + return + name = self.listbox.get(selection) + canvas_node_id = self.nodes[name] + canvas_node = self.app.canvas.nodes[canvas_node_id] + data = canvas_node.service_file_configs[self.service][self.file_name] + self.dialog.temp_service_files[self.file_name] = data + self.dialog.modified_files.add(self.file_name) + self.dialog.service_file_data.text.delete(1.0, tk.END) + self.dialog.service_file_data.text.insert(tk.END, data) self.destroy() def click_view(self) -> None: - selected = self.tree.selection() - data = "" - if selected: - item = self.tree.item(selected[0]) - if "file" in item["tags"]: - nid, service = self.get_node_service(selected) - data = self.file_configs[nid][service][item["text"]] - dialog = ViewConfigDialog( - self, self.app, nid, data, item["text"].split("/")[-1] - ) - dialog.show() - if "cmd" in item["tags"]: - nid, service = self.get_node_service(selected) - cmds = self.service_configs[nid][service] - if "up" in item["tags"]: - data = f"({str(cmds.startup[:])[1:-1]})" - dialog = ViewConfigDialog( - self, self.app, self.node_id, data, "cmdup" - ) - elif "down" in item["tags"]: - data = f"({str(cmds.shutdown[:])[1:-1]})" - dialog = ViewConfigDialog( - self, self.app, self.node_id, data, "cmdup" - ) - elif "val" in item["tags"]: - data = f"({str(cmds.validate[:])[1:-1]})" - dialog = ViewConfigDialog( - self, self.app, self.node_id, data, "cmdup" - ) - dialog.show() - - def get_node_service(self, selected: Tuple[str]) -> Tuple[int, str]: - service_tree_id = self.tree.parent(selected[0]) - service_name = self.tree.item(service_tree_id)["text"] - node_tree_id = self.tree.parent(service_tree_id) - node_id = int(self.tree.item(node_tree_id)["text"][1:]) - return node_id, service_name + selection = self.listbox.curselection() + if not selection: + return + name = self.listbox.get(selection) + canvas_node_id = self.nodes[name] + canvas_node = self.app.canvas.nodes[canvas_node_id] + data = canvas_node.service_file_configs[self.service][self.file_name] + dialog = ViewConfigDialog( + self.app, self, name, self.service, self.file_name, data + ) + dialog.show() class ViewConfigDialog(Dialog): def __init__( self, - master: tk.BaseWidget, app: "Application", - node_id: int, + master: tk.BaseWidget, + name: str, + service: str, + file_name: str, data: str, - filename: str = None, ) -> None: - super().__init__(app, f"n{node_id} config data", master=master) + title = f"{name} Service({service}) File({file_name})" + super().__init__(app, title, master=master) self.data = data self.service_data = None - self.filepath = tk.StringVar(value=f"/tmp/services.tmp-n{node_id}-{filename}") self.draw() def draw(self) -> None: self.top.columnconfigure(0, weight=1) - frame = ttk.Frame(self.top, padding=FRAME_PAD) - frame.columnconfigure(0, weight=1) - frame.columnconfigure(1, weight=10) - frame.grid(row=0, column=0, sticky="ew") - label = ttk.Label(frame, text="File: ") - label.grid(row=0, column=0, sticky="ew", padx=PADX) - entry = ttk.Entry(frame, textvariable=self.filepath) - entry.config(state="disabled") - entry.grid(row=0, column=1, sticky="ew") - + self.top.rowconfigure(0, weight=1) self.service_data = CodeText(self.top) - self.service_data.grid(row=1, column=0, sticky="nsew") - self.service_data.text.insert("end", self.data) - self.service_data.text.config(state="disabled") - + self.service_data.grid(sticky="nsew", pady=PADY) + self.service_data.text.insert(tk.END, self.data) + self.service_data.text.config(state=tk.DISABLED) button = ttk.Button(self.top, text="Close", command=self.destroy) - button.grid(row=2, column=0, sticky="ew", padx=PADX) + button.grid(sticky="ew") diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 5faface72..4e615db04 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -563,7 +563,11 @@ def click_defaults(self) -> None: self.current_service_color("") def click_copy(self) -> None: - dialog = CopyServiceConfigDialog(self, self.app, self.node_id) + file_name = self.filename_combobox.get() + name = self.canvas_node.core_node.name + dialog = CopyServiceConfigDialog( + self.app, self, name, self.service_name, file_name + ) dialog.show() @classmethod From bb2ceaf99307ca75e21018712faf658c29187064 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 22:53:48 -0700 Subject: [PATCH 071/210] pygui: draw link options on edges --- daemon/core/gui/dialogs/linkconfig.py | 2 ++ daemon/core/gui/graph/edges.py | 35 +++++++++++++++++++++++++++ daemon/core/gui/graph/graph.py | 1 + 3 files changed, 38 insertions(+) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index b7c618a3e..28798ec10 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -287,6 +287,8 @@ def click_apply(self) -> None: iface2_id, ) + # update edge label + self.edge.draw_link_options() self.destroy() def change_symmetry(self) -> None: diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 29632086a..6c79787f1 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -57,6 +57,18 @@ def arc_edges(edges) -> None: edge.redraw() +def bandwidth_label(bandwidth: int) -> str: + size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} + unit = 1000 + i = 0 + while bandwidth > unit: + bandwidth /= unit + i += 1 + if i == 3: + break + return f"{bandwidth} {size[i]}" + + class Edge: tag: str = tags.EDGE @@ -140,6 +152,7 @@ def middle_label_text(self, text: str) -> None: font=self.canvas.app.edge_font, text=text, tags=tags.LINK_LABEL, + justify=tk.CENTER, state=self.canvas.show_link_labels.state(), ) else: @@ -312,6 +325,7 @@ def draw_labels(self) -> None: src_text, dst_text = self.create_node_labels() self.src_label_text(src_text) self.dst_label_text(dst_text) + self.draw_link_options() def redraw(self) -> None: super().redraw() @@ -393,3 +407,24 @@ def click_delete(self) -> None: def click_configure(self) -> None: dialog = LinkConfigurationDialog(self.canvas.app, self) dialog.show() + + def draw_link_options(self): + options = self.link.options + lines = [] + bandwidth = options.bandwidth + if bandwidth > 0: + lines.append(bandwidth_label(bandwidth)) + delay = options.delay + jitter = options.jitter + if delay > 0 and jitter > 0: + lines.append(f"{delay} us (\u00B1{jitter} us)") + elif jitter > 0: + lines.append(f"0 us (\u00B1{jitter} us)") + loss = options.loss + if loss > 0: + lines.append(f"loss={loss}%") + dup = options.dup + if dup > 0: + lines.append(f"dup={dup}%") + label = "\n".join(lines) + self.middle_label_text(label) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 07519c3fd..7d8ec0197 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -1000,6 +1000,7 @@ def paste(self) -> None: def clear_throughputs(self) -> None: for edge in self.edges.values(): edge.clear_middle_label() + edge.draw_link_options() def scale_graph(self) -> None: for nid, canvas_node in self.nodes.items(): From f582306bb97e5f2281093b9dfe3cd00e4e1285d3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 10:35:01 -0700 Subject: [PATCH 072/210] pygui: added support for a details pane, can be toggled on/off, can be used to quickly view details for nodes or links --- daemon/core/gui/app.py | 47 ++++++++++++++++++++++-- daemon/core/gui/frames/__init__.py | 0 daemon/core/gui/frames/base.py | 36 +++++++++++++++++++ daemon/core/gui/frames/default.py | 19 ++++++++++ daemon/core/gui/frames/link.py | 58 ++++++++++++++++++++++++++++++ daemon/core/gui/frames/node.py | 33 +++++++++++++++++ daemon/core/gui/graph/edges.py | 20 ++++------- daemon/core/gui/graph/graph.py | 1 + daemon/core/gui/graph/node.py | 6 ++++ daemon/core/gui/menubar.py | 11 ++++++ daemon/core/gui/task.py | 2 +- daemon/core/gui/utils.py | 10 ++++++ 12 files changed, 226 insertions(+), 17 deletions(-) create mode 100644 daemon/core/gui/frames/__init__.py create mode 100644 daemon/core/gui/frames/base.py create mode 100644 daemon/core/gui/frames/default.py create mode 100644 daemon/core/gui/frames/link.py create mode 100644 daemon/core/gui/frames/node.py create mode 100644 daemon/core/gui/utils.py diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index cb385e9eb..e0121d14e 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -3,7 +3,7 @@ import tkinter as tk from tkinter import PhotoImage, font, ttk from tkinter.ttk import Progressbar -from typing import Dict, Optional +from typing import Any, Dict, Optional, Type import grpc @@ -11,11 +11,14 @@ from core.gui.appconfig import GuiConfig from core.gui.coreclient import CoreClient from core.gui.dialogs.error import ErrorDialog +from core.gui.frames.base import InfoFrameBase +from core.gui.frames.default import DefaultInfoFrame from core.gui.graph.graph import CanvasGraph from core.gui.images import ImageEnum, Images from core.gui.menubar import Menubar from core.gui.nodeutils import NodeUtils from core.gui.statusbar import StatusBar +from core.gui.themes import PADY from core.gui.toolbar import Toolbar WIDTH: int = 1000 @@ -35,6 +38,9 @@ def __init__(self, proxy: bool) -> None: self.canvas: Optional[CanvasGraph] = None self.statusbar: Optional[StatusBar] = None self.progress: Optional[Progressbar] = None + self.infobar: Optional[ttk.Frame] = None + self.info_frame: Optional[InfoFrameBase] = None + self.show_infobar: tk.BooleanVar = tk.BooleanVar(value=False) # fonts self.fonts_size: Dict[str, int] = {} @@ -113,16 +119,27 @@ def draw(self) -> None: self.right_frame.rowconfigure(0, weight=1) self.right_frame.grid(row=0, column=1, sticky="nsew") self.draw_canvas() + self.draw_infobar() self.draw_status() self.progress = Progressbar(self.right_frame, mode="indeterminate") self.menubar = Menubar(self) self.master.config(menu=self.menubar) + def draw_infobar(self) -> None: + self.infobar = ttk.Frame(self.right_frame, padding=5, relief=tk.RAISED) + self.infobar.columnconfigure(0, weight=1) + self.infobar.rowconfigure(1, weight=1) + label_font = font.Font(weight=font.BOLD, underline=tk.TRUE) + label = ttk.Label( + self.infobar, text="Details", anchor=tk.CENTER, font=label_font + ) + label.grid(sticky=tk.EW, pady=PADY) + def draw_canvas(self) -> None: canvas_frame = ttk.Frame(self.right_frame) canvas_frame.rowconfigure(0, weight=1) canvas_frame.columnconfigure(0, weight=1) - canvas_frame.grid(sticky="nsew", pady=1) + canvas_frame.grid(row=0, column=0, sticky="nsew", pady=1) self.canvas = CanvasGraph(canvas_frame, self, self.core) self.canvas.grid(sticky="nsew") scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview) @@ -136,7 +153,31 @@ def draw_canvas(self) -> None: def draw_status(self) -> None: self.statusbar = StatusBar(self.right_frame, self) - self.statusbar.grid(sticky="ew") + self.statusbar.grid(sticky="ew", columnspan=2) + + def display_info(self, frame_class: Type[InfoFrameBase], **kwargs: Any) -> None: + if not self.show_infobar.get(): + return + self.clear_info() + self.info_frame = frame_class(self.infobar, **kwargs) + self.info_frame.draw() + self.info_frame.grid(sticky="nsew") + + def clear_info(self) -> None: + if self.info_frame: + self.info_frame.destroy() + self.info_frame = None + + def default_info(self) -> None: + self.clear_info() + self.display_info(DefaultInfoFrame, app=self) + + def show_info(self) -> None: + self.default_info() + self.infobar.grid(row=0, column=1, sticky="nsew") + + def hide_info(self) -> None: + self.infobar.grid_forget() def show_grpc_exception(self, title: str, e: grpc.RpcError) -> None: logging.exception("app grpc exception", exc_info=e) diff --git a/daemon/core/gui/frames/__init__.py b/daemon/core/gui/frames/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/daemon/core/gui/frames/base.py b/daemon/core/gui/frames/base.py new file mode 100644 index 000000000..8db952f16 --- /dev/null +++ b/daemon/core/gui/frames/base.py @@ -0,0 +1,36 @@ +import tkinter as tk +from tkinter import ttk +from typing import TYPE_CHECKING + +from core.gui.themes import FRAME_PAD, PADX, PADY + +if TYPE_CHECKING: + from core.gui.app import Application + + +class InfoFrameBase(ttk.Frame): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: + super().__init__(master, padding=FRAME_PAD) + self.app: "Application" = app + + def draw(self) -> None: + raise NotImplementedError + + +class DetailsFrame(ttk.Frame): + def __init__(self, master: tk.BaseWidget) -> None: + super().__init__(master) + self.columnconfigure(1, weight=1) + self.row = 0 + + def add_detail(self, label: str, value: str) -> None: + label = ttk.Label(self, text=label, anchor=tk.W) + label.grid(row=self.row, sticky=tk.EW, column=0, padx=PADX) + label = ttk.Label(self, text=value, anchor=tk.W, state=tk.DISABLED) + label.grid(row=self.row, sticky=tk.EW, column=1) + self.row += 1 + + def add_separator(self) -> None: + separator = ttk.Separator(self) + separator.grid(row=self.row, sticky=tk.EW, columnspan=2, pady=PADY) + self.row += 1 diff --git a/daemon/core/gui/frames/default.py b/daemon/core/gui/frames/default.py new file mode 100644 index 000000000..e84edb87a --- /dev/null +++ b/daemon/core/gui/frames/default.py @@ -0,0 +1,19 @@ +import tkinter as tk +from tkinter import ttk +from typing import TYPE_CHECKING + +from core.gui.frames.base import InfoFrameBase + +if TYPE_CHECKING: + from core.gui.app import Application + + +class DefaultInfoFrame(InfoFrameBase): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: + super().__init__(master, app) + + def draw(self) -> None: + label = ttk.Label(self, text="Click a Node/Link", anchor=tk.CENTER) + label.grid(sticky=tk.EW) + label = ttk.Label(self, text="to see details", anchor=tk.CENTER) + label.grid(sticky=tk.EW) diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py new file mode 100644 index 000000000..29b3df459 --- /dev/null +++ b/daemon/core/gui/frames/link.py @@ -0,0 +1,58 @@ +import tkinter as tk +from typing import TYPE_CHECKING + +from core.gui.frames.base import DetailsFrame, InfoFrameBase +from core.gui.utils import bandwidth_text + +if TYPE_CHECKING: + from core.gui.app import Application + from core.gui.graph.edges import CanvasEdge + + +class EdgeInfoFrame(InfoFrameBase): + def __init__( + self, master: tk.BaseWidget, app: "Application", edge: "CanvasEdge" + ) -> None: + super().__init__(master, app) + self.edge: "CanvasEdge" = edge + + def draw(self) -> None: + self.columnconfigure(0, weight=1) + link = self.edge.link + options = link.options + src_canvas_node = self.app.core.canvas_nodes[link.node1_id] + src_node = src_canvas_node.core_node + dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] + dst_node = dst_canvas_node.core_node + + frame = DetailsFrame(self) + frame.grid(sticky="ew") + frame.add_detail("Source", src_node.name) + iface1 = link.iface1 + if iface1: + mac = iface1.mac if iface1.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface1.ip4}/{iface1.ip4_mask}" if iface1.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface1.ip6}/{iface1.ip6_mask}" if iface1.ip6 else "" + frame.add_detail("IP6", ip6) + + frame.add_separator() + frame.add_detail("Destination", dst_node.name) + iface2 = link.iface2 + if iface2: + mac = iface2.mac if iface2.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface2.ip4}/{iface2.ip4_mask}" if iface2.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else "" + frame.add_detail("IP6", ip6) + + if link.HasField("options"): + frame.add_separator() + bandwidth = bandwidth_text(options.bandwidth) + frame.add_detail("Bandwidth", bandwidth) + frame.add_detail("Delay", f"{options.delay} us") + frame.add_detail("Jitter", f"\u00B1{options.jitter} us") + frame.add_detail("Loss", f"{options.loss}%") + frame.add_detail("Duplicate", f"{options.dup}%") diff --git a/daemon/core/gui/frames/node.py b/daemon/core/gui/frames/node.py new file mode 100644 index 000000000..44724f366 --- /dev/null +++ b/daemon/core/gui/frames/node.py @@ -0,0 +1,33 @@ +from typing import TYPE_CHECKING + +from core.api.grpc.core_pb2 import NodeType +from core.gui.frames.base import DetailsFrame, InfoFrameBase +from core.gui.nodeutils import NodeUtils + +if TYPE_CHECKING: + from core.gui.app import Application + from core.gui.graph.node import CanvasNode + + +class NodeInfoFrame(InfoFrameBase): + def __init__(self, master, app: "Application", canvas_node: "CanvasNode") -> None: + super().__init__(master, app) + self.canvas_node: "CanvasNode" = canvas_node + + def draw(self) -> None: + self.columnconfigure(0, weight=1) + node = self.canvas_node.core_node + frame = DetailsFrame(self) + frame.grid(sticky="ew") + frame.add_detail("ID", node.id) + frame.add_detail("Name", node.name) + if NodeUtils.is_model_node(node.type): + frame.add_detail("Type", node.model) + if node.type == NodeType.EMANE: + emane = node.emane.split("_")[1:] + frame.add_detail("EMANE", emane) + if NodeUtils.is_image_node(node.type): + frame.add_detail("Image", node.image) + if NodeUtils.is_container_node(node.type): + server = node.server if node.server else "localhost" + frame.add_detail("Server", server) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 6c79787f1..de063bac9 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -7,8 +7,10 @@ from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog +from core.gui.frames.link import EdgeInfoFrame from core.gui.graph import tags from core.gui.nodeutils import NodeUtils +from core.gui.utils import bandwidth_text if TYPE_CHECKING: from core.gui.graph.graph import CanvasGraph @@ -57,18 +59,6 @@ def arc_edges(edges) -> None: edge.redraw() -def bandwidth_label(bandwidth: int) -> str: - size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} - unit = 1000 - i = 0 - while bandwidth > unit: - bandwidth /= unit - i += 1 - if i == 3: - break - return f"{bandwidth} {size[i]}" - - class Edge: tag: str = tags.EDGE @@ -295,6 +285,7 @@ def create_context(self) -> None: def set_binding(self) -> None: self.canvas.tag_bind(self.id, "", self.show_context) + self.canvas.tag_bind(self.id, "", self.show_info) def set_link(self, link: Link) -> None: self.link = link @@ -396,6 +387,9 @@ def reset(self) -> None: self.middle_label = None self.canvas.itemconfig(self.id, fill=self.color, width=self.scaled_width()) + def show_info(self, _event: tk.Event) -> None: + self.canvas.app.display_info(EdgeInfoFrame, app=self.canvas.app, edge=self) + def show_context(self, event: tk.Event) -> None: state = tk.DISABLED if self.canvas.core.is_runtime() else tk.NORMAL self.context.entryconfigure(1, state=state) @@ -413,7 +407,7 @@ def draw_link_options(self): lines = [] bandwidth = options.bandwidth if bandwidth > 0: - lines.append(bandwidth_label(bandwidth)) + lines.append(bandwidth_text(bandwidth)) delay = options.delay jitter = options.jitter if delay > 0 and jitter > 0: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 7d8ec0197..1588f920d 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -715,6 +715,7 @@ def press_delete(self, _event: tk.Event) -> None: logging.debug("press delete key") if not self.app.core.is_runtime(): self.delete_selected_objects() + self.app.default_info() else: logging.debug("node deletion is disabled during runtime state") diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index a86ce4a3e..d98c4e48d 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -16,6 +16,7 @@ from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog from core.gui.dialogs.nodeservice import NodeServiceDialog from core.gui.dialogs.wlanconfig import WlanConfigDialog +from core.gui.frames.node import NodeInfoFrame from core.gui.graph import tags from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip @@ -80,6 +81,7 @@ def setup_bindings(self) -> None: self.canvas.tag_bind(self.id, "", self.on_enter) self.canvas.tag_bind(self.id, "", self.on_leave) self.canvas.tag_bind(self.id, "", self.show_context) + self.canvas.tag_bind(self.id, "", self.show_info) def delete(self) -> None: logging.debug("Delete canvas node for %s", self.core_node) @@ -195,6 +197,9 @@ def double_click(self, event: tk.Event) -> None: else: self.show_config() + def show_info(self, _event: tk.Event) -> None: + self.app.display_info(NodeInfoFrame, app=self.app, canvas_node=self) + def show_context(self, event: tk.Event) -> None: # clear existing menu self.context.delete(0, tk.END) @@ -262,6 +267,7 @@ def click_cut(self) -> None: def click_unlink(self, edge: CanvasEdge) -> None: self.canvas.delete_edge(edge) + self.app.default_info() def canvas_delete(self) -> None: self.canvas.clear_selection() diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 75312e950..3b85ac6f5 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -138,6 +138,11 @@ def draw_view_menu(self) -> None: Create view menu """ menu = tk.Menu(self) + menu.add_checkbutton( + label="Details Panel", + command=self.click_infobar_change, + variable=self.app.show_infobar, + ) menu.add_checkbutton( label="Interface Names", command=self.click_edge_label_change, @@ -443,6 +448,12 @@ def click_autogrid(self) -> None: y = (row * layout_size) + padding node.move(x, y) + def click_infobar_change(self) -> None: + if self.app.show_infobar.get(): + self.app.show_info() + else: + self.app.hide_info() + def click_edge_label_change(self) -> None: for edge in self.canvas.edges.values(): edge.draw_labels() diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index b4a5f68f6..c60350f94 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -26,7 +26,7 @@ def __init__( self.time: Optional[float] = None def start(self) -> None: - self.app.progress.grid(sticky="ew") + self.app.progress.grid(sticky="ew", columnspan=2) self.app.progress.start() self.time = time.perf_counter() thread = threading.Thread(target=self.run, daemon=True) diff --git a/daemon/core/gui/utils.py b/daemon/core/gui/utils.py new file mode 100644 index 000000000..ee5ad8cba --- /dev/null +++ b/daemon/core/gui/utils.py @@ -0,0 +1,10 @@ +def bandwidth_text(bandwidth: int) -> str: + size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} + unit = 1000 + i = 0 + while bandwidth > unit: + bandwidth /= unit + i += 1 + if i == 3: + break + return f"{bandwidth} {size[i]}" From 98e4baca046f4e8ea0b4ba254bd1dec444d067c2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:05:24 -0700 Subject: [PATCH 073/210] pygui: added services to node info panel --- daemon/core/gui/frames/node.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daemon/core/gui/frames/node.py b/daemon/core/gui/frames/node.py index 44724f366..7480e056d 100644 --- a/daemon/core/gui/frames/node.py +++ b/daemon/core/gui/frames/node.py @@ -23,6 +23,12 @@ def draw(self) -> None: frame.add_detail("Name", node.name) if NodeUtils.is_model_node(node.type): frame.add_detail("Type", node.model) + if NodeUtils.is_container_node(node.type): + for index, service in enumerate(sorted(node.services)): + if index == 0: + frame.add_detail("Services", service) + else: + frame.add_detail("", service) if node.type == NodeType.EMANE: emane = node.emane.split("_")[1:] frame.add_detail("EMANE", emane) From 3bfc299bfd96343b0a4afed6c7d19c95fec0c700 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 16:22:56 -0700 Subject: [PATCH 074/210] daemon: fixed typo in core.configservices.securityservices --- .../{sercurityservices => securityservices}/__init__.py | 0 .../{sercurityservices => securityservices}/services.py | 0 .../{sercurityservices => securityservices}/templates/firewall.sh | 0 .../{sercurityservices => securityservices}/templates/ipsec.sh | 0 .../{sercurityservices => securityservices}/templates/nat.sh | 0 .../templates/vpnclient.sh | 0 .../templates/vpnserver.sh | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename daemon/core/configservices/{sercurityservices => securityservices}/__init__.py (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/services.py (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/firewall.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/ipsec.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/nat.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/vpnclient.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/vpnserver.sh (100%) diff --git a/daemon/core/configservices/sercurityservices/__init__.py b/daemon/core/configservices/securityservices/__init__.py similarity index 100% rename from daemon/core/configservices/sercurityservices/__init__.py rename to daemon/core/configservices/securityservices/__init__.py diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/securityservices/services.py similarity index 100% rename from daemon/core/configservices/sercurityservices/services.py rename to daemon/core/configservices/securityservices/services.py diff --git a/daemon/core/configservices/sercurityservices/templates/firewall.sh b/daemon/core/configservices/securityservices/templates/firewall.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/firewall.sh rename to daemon/core/configservices/securityservices/templates/firewall.sh diff --git a/daemon/core/configservices/sercurityservices/templates/ipsec.sh b/daemon/core/configservices/securityservices/templates/ipsec.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/ipsec.sh rename to daemon/core/configservices/securityservices/templates/ipsec.sh diff --git a/daemon/core/configservices/sercurityservices/templates/nat.sh b/daemon/core/configservices/securityservices/templates/nat.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/nat.sh rename to daemon/core/configservices/securityservices/templates/nat.sh diff --git a/daemon/core/configservices/sercurityservices/templates/vpnclient.sh b/daemon/core/configservices/securityservices/templates/vpnclient.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/vpnclient.sh rename to daemon/core/configservices/securityservices/templates/vpnclient.sh diff --git a/daemon/core/configservices/sercurityservices/templates/vpnserver.sh b/daemon/core/configservices/securityservices/templates/vpnserver.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/vpnserver.sh rename to daemon/core/configservices/securityservices/templates/vpnserver.sh From b94d4d35071b8e07f01fbf4469ada87df054ad18 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 21:34:45 -0700 Subject: [PATCH 075/210] daemon: updated open xml with start flag to set instantiation state before running instantiate to be consistent with other cases --- daemon/core/emulator/session.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 630e1a0f1..92d4b5e1b 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -637,19 +637,16 @@ def open_xml(self, file_name: str, start: bool = False) -> None: # clear out existing session self.clear() - if start: - state = EventTypes.CONFIGURATION_STATE - else: - state = EventTypes.DEFINITION_STATE + # set state and read xml + state = EventTypes.CONFIGURATION_STATE if start else EventTypes.DEFINITION_STATE self.set_state(state) self.name = os.path.basename(file_name) self.file_name = file_name - - # write out xml file CoreXmlReader(self).read(file_name) # start session if needed if start: + self.set_state(EventTypes.INSTANTIATION_STATE) self.instantiate() def save_xml(self, file_name: str) -> None: From f4224d1b80060fd7e5c1bc4fd771be29c01f23b5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 22:05:10 -0700 Subject: [PATCH 076/210] daemon: updated ovs option to be a formal session option, will now display within gui, save to and be read from xml --- daemon/core/emulator/coreemu.py | 2 +- daemon/core/emulator/session.py | 3 +++ daemon/core/emulator/sessionconfig.py | 3 +++ daemon/core/nodes/base.py | 10 ++++++---- daemon/core/nodes/interface.py | 5 +++-- daemon/core/nodes/network.py | 2 +- daemon/scripts/core-daemon | 3 +++ 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 717232682..016f2e5b1 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -80,7 +80,7 @@ def _validate_env(self) -> None: :raises core.errors.CoreError: when an executable does not exist on path """ requirements = COMMON_REQUIREMENTS - use_ovs = self.config.get("ovs") == "True" + use_ovs = self.config.get("ovs") == "1" if use_ovs: requirements += OVS_REQUIREMENTS else: diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 92d4b5e1b..c2573578e 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -217,6 +217,9 @@ def _link_wireless( else: common_network.unlink(iface1, iface2) + def use_ovs(self) -> bool: + return self.options.get_config("ovs") == "1" + def add_link( self, node1_id: int, diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index e22e852e8..9b22bcc73 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -56,6 +56,9 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL", ), + Configuration( + _id="ovs", _type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" + ), ] config_type: RegisterTlvs = RegisterTlvs.UTILITY diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3999046d6..05ec87dc9 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -73,8 +73,9 @@ def __init__( self.icon: Optional[str] = None self.position: Position = Position() self.up: bool = False - use_ovs = session.options.get_config("ovs") == "True" - self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd) + self.net_client: LinuxNetClient = get_net_client( + self.session.use_ovs(), self.host_cmd + ) @abc.abstractmethod def startup(self) -> None: @@ -471,8 +472,9 @@ def __init__( self.pid: Optional[int] = None self.lock: RLock = RLock() self._mounts: List[Tuple[str, str]] = [] - use_ovs = session.options.get_config("ovs") == "True" - self.node_net_client: LinuxNetClient = self.create_node_net_client(use_ovs) + self.node_net_client: LinuxNetClient = self.create_node_net_client( + self.session.use_ovs() + ) def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 22ecb6203..e4d4d0ac4 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -68,8 +68,9 @@ def __init__( # id used to find flow data self.flow_id: Optional[int] = None self.server: Optional["DistributedServer"] = server - use_ovs = session.options.get_config("ovs") == "True" - self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd) + self.net_client: LinuxNetClient = get_net_client( + self.session.use_ovs(), self.host_cmd + ) def host_cmd( self, diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index d418a42c6..a55de4cf5 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -756,7 +756,7 @@ def add_addresses(self, index: int) -> None: :param index: starting address index :return: nothing """ - use_ovs = self.session.options.get_config("ovs") == "True" + use_ovs = self.session.use_ovs() address = self.prefix[index] current = f"{address}/{self.prefix.prefixlen}" net_client = get_net_client(use_ovs, utils.cmd) diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index a95e59fad..16b0ac59c 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -118,6 +118,9 @@ def get_merged_config(filename): # parse command line options args = parser.parse_args() + # convert ovs to internal format + args.ovs = "1" if args.ovs else "0" + # read the config file if args.configfile is not None: filename = args.configfile From eac941ce7265eb850ae9f153fd9ea0a88f8eedf1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:13:38 -0700 Subject: [PATCH 077/210] pygui: updates to show wireless edges in details panel, increased edge thickness to be the same as normal edges for selection to be easier --- daemon/core/gui/frames/link.py | 57 +++++++++++++++++++++++++++++++++- daemon/core/gui/graph/edges.py | 20 ++++++++++-- daemon/core/gui/graph/graph.py | 6 +--- 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py index 29b3df459..57b1bf66c 100644 --- a/daemon/core/gui/frames/link.py +++ b/daemon/core/gui/frames/link.py @@ -1,12 +1,26 @@ import tkinter as tk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional +from core.api.grpc.core_pb2 import Interface from core.gui.frames.base import DetailsFrame, InfoFrameBase from core.gui.utils import bandwidth_text if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.edges import CanvasEdge + from core.gui.graph.node import CanvasNode + from core.gui.graph.edges import CanvasWirelessEdge + + +def get_iface(canvas_node: "CanvasNode", net_id: int) -> Optional[Interface]: + iface = None + for edge in canvas_node.edges: + link = edge.link + if link.node1_id == net_id: + iface = link.iface2 + elif link.node2_id == net_id: + iface = link.iface1 + return iface class EdgeInfoFrame(InfoFrameBase): @@ -56,3 +70,44 @@ def draw(self) -> None: frame.add_detail("Jitter", f"\u00B1{options.jitter} us") frame.add_detail("Loss", f"{options.loss}%") frame.add_detail("Duplicate", f"{options.dup}%") + + +class WirelessEdgeInfoFrame(InfoFrameBase): + def __init__( + self, master: tk.BaseWidget, app: "Application", edge: "CanvasWirelessEdge" + ) -> None: + super().__init__(master, app) + self.edge: "CanvasWirelessEdge" = edge + + def draw(self) -> None: + link = self.edge.link + src_canvas_node = self.app.core.canvas_nodes[link.node1_id] + src_node = src_canvas_node.core_node + dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] + dst_node = dst_canvas_node.core_node + + # find interface for each node connected to network + net_id = link.network_id + iface1 = get_iface(src_canvas_node, net_id) + iface2 = get_iface(dst_canvas_node, net_id) + + frame = DetailsFrame(self) + frame.grid(sticky="ew") + frame.add_detail("Source", src_node.name) + if iface1: + mac = iface1.mac if iface1.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface1.ip4}/{iface1.ip4_mask}" if iface1.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface1.ip6}/{iface1.ip6_mask}" if iface1.ip6 else "" + frame.add_detail("IP6", ip6) + + frame.add_separator() + frame.add_detail("Destination", dst_node.name) + if iface2: + mac = iface2.mac if iface2.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface2.ip4}/{iface2.ip4_mask}" if iface2.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else "" + frame.add_detail("IP6", ip6) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index de063bac9..d90859103 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -7,7 +7,7 @@ from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog -from core.gui.frames.link import EdgeInfoFrame +from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.graph import tags from core.gui.nodeutils import NodeUtils from core.gui.utils import bandwidth_text @@ -18,7 +18,7 @@ TEXT_DISTANCE: float = 0.30 EDGE_WIDTH: int = 3 EDGE_COLOR: str = "#ff0000" -WIRELESS_WIDTH: float = 1.5 +WIRELESS_WIDTH: float = 3 WIRELESS_COLOR: str = "#009933" ARC_DISTANCE: int = 50 @@ -241,13 +241,27 @@ def __init__( src_pos: Tuple[float, float], dst_pos: Tuple[float, float], token: Tuple[int, ...], + link: Link, ) -> None: logging.debug("drawing wireless link from node %s to node %s", src, dst) super().__init__(canvas, src, dst) + self.link: Link = link self.token: Tuple[int, ...] = token self.width: float = WIRELESS_WIDTH - self.color: str = WIRELESS_COLOR + color = link.color if link.color else WIRELESS_COLOR + self.color: str = color self.draw(src_pos, dst_pos) + if link.label: + self.middle_label_text(link.label) + self.set_binding() + + def set_binding(self) -> None: + self.canvas.tag_bind(self.id, "", self.show_info) + + def show_info(self, _event: tk.Event) -> None: + self.canvas.app.display_info( + WirelessEdgeInfoFrame, app=self.canvas.app, edge=self + ) class CanvasEdge(Edge): diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 1588f920d..a3520d222 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -233,11 +233,7 @@ def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> Non return src_pos = self.coords(src.id) dst_pos = self.coords(dst.id) - edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token) - if link.label: - edge.middle_label_text(link.label) - if link.color: - edge.color = link.color + edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token, link) self.wireless_edges[token] = edge src.wireless_edges.add(edge) dst.wireless_edges.add(edge) From aebbff8c224a90a818417e5a512ad43204c6a493 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 14:39:12 -0700 Subject: [PATCH 078/210] grpc/pygui: shifted source field in node events to base event message to apply to all events, updated add_link/delete_link rpc calls to broadcast events, updated pygui to handle these events --- daemon/core/api/grpc/events.py | 132 ++++++++++++++------------ daemon/core/api/grpc/grpcutils.py | 18 ++++ daemon/core/api/grpc/server.py | 30 +++++- daemon/core/emulator/data.py | 1 + daemon/core/emulator/session.py | 3 +- daemon/core/gui/coreclient.py | 33 +++++-- daemon/core/gui/graph/graph.py | 69 ++++++++------ daemon/proto/core/api/grpc/core.proto | 4 +- 8 files changed, 185 insertions(+), 105 deletions(-) diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 75f9eb2ee..5c873a43b 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -1,6 +1,6 @@ import logging from queue import Empty, Queue -from typing import Iterable +from typing import Iterable, Optional from core.api.grpc import core_pb2 from core.api.grpc.grpcutils import convert_link @@ -15,7 +15,7 @@ from core.emulator.session import Session -def handle_node_event(node_data: NodeData) -> core_pb2.NodeEvent: +def handle_node_event(node_data: NodeData) -> core_pb2.Event: """ Handle node event when there is a node event @@ -36,98 +36,105 @@ def handle_node_event(node_data: NodeData) -> core_pb2.NodeEvent: geo=geo, services=services, ) - return core_pb2.NodeEvent(node=node_proto, source=node_data.source) + node_event = core_pb2.NodeEvent(node=node_proto) + return core_pb2.Event(node_event=node_event, source=node_data.source) -def handle_link_event(event: LinkData) -> core_pb2.LinkEvent: +def handle_link_event(link_data: LinkData) -> core_pb2.Event: """ Handle link event when there is a link event - :param event: link data + :param link_data: link data :return: link event that has message type and link information """ - link = convert_link(event) - return core_pb2.LinkEvent(message_type=event.message_type.value, link=link) + link = convert_link(link_data) + message_type = link_data.message_type.value + link_event = core_pb2.LinkEvent(message_type=message_type, link=link) + return core_pb2.Event(link_event=link_event, source=link_data.source) -def handle_session_event(event: EventData) -> core_pb2.SessionEvent: +def handle_session_event(event_data: EventData) -> core_pb2.Event: """ Handle session event when there is a session event - :param event: event data + :param event_data: event data :return: session event """ - event_time = event.time + event_time = event_data.time if event_time is not None: event_time = float(event_time) - return core_pb2.SessionEvent( - node_id=event.node, - event=event.event_type.value, - name=event.name, - data=event.data, + session_event = core_pb2.SessionEvent( + node_id=event_data.node, + event=event_data.event_type.value, + name=event_data.name, + data=event_data.data, time=event_time, ) + return core_pb2.Event(session_event=session_event) -def handle_config_event(event: ConfigData) -> core_pb2.ConfigEvent: +def handle_config_event(config_data: ConfigData) -> core_pb2.Event: """ Handle configuration event when there is configuration event - :param event: configuration data + :param config_data: configuration data :return: configuration event """ - return core_pb2.ConfigEvent( - message_type=event.message_type, - node_id=event.node, - object=event.object, - type=event.type, - captions=event.captions, - bitmap=event.bitmap, - data_values=event.data_values, - possible_values=event.possible_values, - groups=event.groups, - iface_id=event.iface_id, - network_id=event.network_id, - opaque=event.opaque, - data_types=event.data_types, + config_event = core_pb2.ConfigEvent( + message_type=config_data.message_type, + node_id=config_data.node, + object=config_data.object, + type=config_data.type, + captions=config_data.captions, + bitmap=config_data.bitmap, + data_values=config_data.data_values, + possible_values=config_data.possible_values, + groups=config_data.groups, + iface_id=config_data.iface_id, + network_id=config_data.network_id, + opaque=config_data.opaque, + data_types=config_data.data_types, ) + return core_pb2.Event(config_event=config_event) -def handle_exception_event(event: ExceptionData) -> core_pb2.ExceptionEvent: +def handle_exception_event(exception_data: ExceptionData) -> core_pb2.Event: """ Handle exception event when there is exception event - :param event: exception data + :param exception_data: exception data :return: exception event """ - return core_pb2.ExceptionEvent( - node_id=event.node, - level=event.level.value, - source=event.source, - date=event.date, - text=event.text, - opaque=event.opaque, + exception_event = core_pb2.ExceptionEvent( + node_id=exception_data.node, + level=exception_data.level.value, + source=exception_data.source, + date=exception_data.date, + text=exception_data.text, + opaque=exception_data.opaque, ) + return core_pb2.Event(exception_event=exception_event) -def handle_file_event(event: FileData) -> core_pb2.FileEvent: +def handle_file_event(file_data: FileData) -> core_pb2.Event: """ Handle file event - :param event: file data + :param file_data: file data :return: file event """ - return core_pb2.FileEvent( - message_type=event.message_type.value, - node_id=event.node, - name=event.name, - mode=event.mode, - number=event.number, - type=event.type, - source=event.source, - data=event.data, - compressed_data=event.compressed_data, + file_event = core_pb2.FileEvent( + message_type=file_data.message_type.value, + node_id=file_data.node, + name=file_data.name, + mode=file_data.mode, + number=file_data.number, + type=file_data.type, + source=file_data.source, + data=file_data.data, + compressed_data=file_data.compressed_data, ) + return core_pb2.Event(file_event=file_event) class EventStreamer: @@ -168,32 +175,33 @@ def add_handlers(self) -> None: if core_pb2.EventType.SESSION in self.event_types: self.session.event_handlers.append(self.queue.put) - def process(self) -> core_pb2.Event: + def process(self) -> Optional[core_pb2.Event]: """ Process the next event in the queue. :return: grpc event, or None when invalid event or queue timeout """ - event = core_pb2.Event(session_id=self.session.id) + event = None try: data = self.queue.get(timeout=1) if isinstance(data, NodeData): - event.node_event.CopyFrom(handle_node_event(data)) + event = handle_node_event(data) elif isinstance(data, LinkData): - event.link_event.CopyFrom(handle_link_event(data)) + event = handle_link_event(data) elif isinstance(data, EventData): - event.session_event.CopyFrom(handle_session_event(data)) + event = handle_session_event(data) elif isinstance(data, ConfigData): - event.config_event.CopyFrom(handle_config_event(data)) + event = handle_config_event(data) elif isinstance(data, ExceptionData): - event.exception_event.CopyFrom(handle_exception_event(data)) + event = handle_exception_event(data) elif isinstance(data, FileData): - event.file_event.CopyFrom(handle_file_event(data)) + event = handle_file_event(data) else: logging.error("unknown event: %s", data) - event = None except Empty: - event = None + pass + if event: + event.session_id = self.session.id return event def remove_handlers(self) -> None: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 8df545cd6..ed40a75b7 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -435,6 +435,24 @@ def get_service_configuration(service: CoreService) -> NodeServiceData: ) +def iface_to_data(iface: CoreInterface) -> InterfaceData: + 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(iface: CoreInterface) -> core_pb2.Interface: """ Convenience for converting a core interface to the protobuf representation. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 1964b6e81..2883103d0 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -108,7 +108,7 @@ WlanLinkResponse, ) from core.emulator.coreemu import CoreEmu -from core.emulator.data import LinkData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError @@ -853,6 +853,22 @@ def AddLink( node1_iface, node2_iface = session.add_link( node1_id, node2_id, iface1_data, iface2_data, options, link_type ) + iface1_data = None + if node1_iface: + iface1_data = grpcutils.iface_to_data(node1_iface) + iface2_data = None + if node2_iface: + iface2_data = grpcutils.iface_to_data(node2_iface) + source = request.source if request.source else None + link_data = LinkData( + message_type=MessageFlags.ADD, + node1_id=node1_id, + node2_id=node2_id, + iface1=iface1_data, + iface2=iface2_data, + source=source, + ) + session.broadcast_link(link_data) iface1_proto = None iface2_proto = None if node1_iface: @@ -912,6 +928,18 @@ def DeleteLink( iface1_id = request.iface1_id iface2_id = request.iface2_id session.delete_link(node1_id, node2_id, iface1_id, iface2_id) + iface1 = InterfaceData(id=iface1_id) + iface2 = InterfaceData(id=iface2_id) + source = request.source if request.source else None + link_data = LinkData( + message_type=MessageFlags.DELETE, + node1_id=node1_id, + node2_id=node2_id, + iface1=iface1, + iface2=iface2, + source=source, + ) + session.broadcast_link(link_data) return core_pb2.DeleteLinkResponse(result=True) def GetHooks( diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 22d10d2df..15d922a9e 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -190,6 +190,7 @@ class LinkData: iface2: InterfaceData = None options: LinkOptions = LinkOptions() color: str = None + source: str = None class IpPrefixes: diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index c2573578e..d2f64ddef 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -833,11 +833,12 @@ def broadcast_config(self, config_data: ConfigData) -> None: for handler in self.config_handlers: handler(config_data) - def broadcast_link(self, link_data: LinkData) -> None: + def broadcast_link(self, link_data: LinkData, source: str = None) -> None: """ Handle link data that should be provided to link handlers. :param link_data: link data to send out + :param source: source of broadcast, None by default :return: nothing """ for handler in self.link_handlers: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index d35f62e5e..a5b96e175 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -148,6 +148,8 @@ def read_config(self) -> None: self.custom_observers[observer.name] = observer def handle_events(self, event: Event) -> None: + if event.source == GUI_SOURCE: + return if event.session_id != self.session_id: logging.warning( "ignoring event session(%s) current(%s)", @@ -193,19 +195,32 @@ def handle_link_event(self, event: LinkEvent) -> None: return canvas_node1 = self.canvas_nodes[node1_id] canvas_node2 = self.canvas_nodes[node2_id] - if event.message_type == MessageType.ADD: - self.app.canvas.add_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == MessageType.DELETE: - self.app.canvas.delete_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == MessageType.NONE: - self.app.canvas.update_wireless_edge(canvas_node1, canvas_node2, event.link) + if event.link.type == LinkType.WIRELESS: + if event.message_type == MessageType.ADD: + self.app.canvas.add_wireless_edge( + canvas_node1, canvas_node2, event.link + ) + elif event.message_type == MessageType.DELETE: + self.app.canvas.delete_wireless_edge( + canvas_node1, canvas_node2, event.link + ) + elif event.message_type == MessageType.NONE: + self.app.canvas.update_wireless_edge( + canvas_node1, canvas_node2, event.link + ) + else: + logging.warning("unknown link event: %s", event) else: - logging.warning("unknown link event: %s", event) + if event.message_type == MessageType.ADD: + self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link) + self.app.canvas.organize() + elif event.message_type == MessageType.DELETE: + self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2) + else: + logging.warning("unknown link event: %s", event) def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) - if event.source == GUI_SOURCE: - return node_id = event.node.id x = event.node.position.x y = event.node.position.y diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index a3520d222..4e0358a51 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -225,6 +225,43 @@ def draw_grid(self) -> None: self.tag_lower(tags.GRIDLINE) self.tag_lower(self.rect) + def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: + token = create_edge_token(src.id, dst.id) + if token in self.edges and link.options.unidirectional: + edge = self.edges[token] + edge.asymmetric_link = link + elif token not in self.edges: + node1 = src.core_node + node2 = dst.core_node + src_pos = (node1.position.x, node1.position.y) + dst_pos = (node2.position.x, node2.position.y) + edge = CanvasEdge(self, src.id, src_pos, dst_pos) + edge.token = token + edge.dst = dst.id + edge.set_link(link) + edge.check_wireless() + src.edges.add(edge) + dst.edges.add(edge) + self.edges[edge.token] = edge + self.core.links[edge.token] = edge + if link.HasField("iface1"): + iface1 = link.iface1 + self.core.iface_to_edge[(node1.id, iface1.id)] = token + src.ifaces[iface1.id] = iface1 + edge.src_iface = iface1 + if link.HasField("iface2"): + iface2 = link.iface2 + self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token + dst.ifaces[iface2.id] = iface2 + edge.dst_iface = iface2 + + def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None: + token = create_edge_token(src.id, dst.id) + edge = self.edges.get(token) + if not edge: + return + self.delete_edge(edge) + def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) @@ -297,41 +334,11 @@ def draw_session(self, session: Session) -> None: for link in session.links: logging.debug("drawing link: %s", link) canvas_node1 = self.core.canvas_nodes[link.node1_id] - node1 = canvas_node1.core_node canvas_node2 = self.core.canvas_nodes[link.node2_id] - node2 = canvas_node2.core_node - token = create_edge_token(canvas_node1.id, canvas_node2.id) - if link.type == LinkType.WIRELESS: self.add_wireless_edge(canvas_node1, canvas_node2, link) else: - if token not in self.edges: - src_pos = (node1.position.x, node1.position.y) - dst_pos = (node2.position.x, node2.position.y) - edge = CanvasEdge(self, canvas_node1.id, src_pos, dst_pos) - edge.token = token - edge.dst = canvas_node2.id - edge.set_link(link) - edge.check_wireless() - canvas_node1.edges.add(edge) - canvas_node2.edges.add(edge) - self.edges[edge.token] = edge - self.core.links[edge.token] = edge - if link.HasField("iface1"): - iface1 = link.iface1 - self.core.iface_to_edge[(node1.id, iface1.id)] = token - canvas_node1.ifaces[iface1.id] = iface1 - edge.src_iface = iface1 - if link.HasField("iface2"): - iface2 = link.iface2 - self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token - canvas_node2.ifaces[iface2.id] = iface2 - edge.dst_iface = iface2 - elif link.options.unidirectional: - edge = self.edges[token] - edge.asymmetric_link = link - else: - logging.error("duplicate link received: %s", link) + self.add_wired_edge(canvas_node1, canvas_node2, link) def stopped_session(self) -> None: # clear wireless edges diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 46e1da914..6b1b304c7 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -343,11 +343,11 @@ message Event { FileEvent file_event = 6; } int32 session_id = 7; + string source = 8; } message NodeEvent { Node node = 1; - string source = 2; } message LinkEvent { @@ -488,6 +488,7 @@ message GetNodeLinksResponse { message AddLinkRequest { int32 session_id = 1; Link link = 2; + string source = 3; } message AddLinkResponse { @@ -515,6 +516,7 @@ message DeleteLinkRequest { int32 node2_id = 3; int32 iface1_id = 4; int32 iface2_id = 5; + string source = 6; } message DeleteLinkResponse { From f921fa45c549ea95932a28497f12094c7a18d92a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 14:44:13 -0700 Subject: [PATCH 079/210] grpc: updated client methods to allow passing source for add_link/delete_link, None by default --- daemon/core/api/grpc/client.py | 9 ++++++++- daemon/core/emulator/session.py | 3 +-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 5aa6713d2..939d7eefa 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -614,6 +614,7 @@ def add_link( iface1: core_pb2.Interface = None, iface2: core_pb2.Interface = None, options: core_pb2.LinkOptions = None, + source: str = None, ) -> core_pb2.AddLinkResponse: """ Add a link between nodes. @@ -624,6 +625,7 @@ def add_link( :param iface1: node one interface data :param iface2: node two interface data :param options: options for link (jitter, bandwidth, etc) + :param source: application source adding link :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -635,7 +637,9 @@ def add_link( iface2=iface2, options=options, ) - request = core_pb2.AddLinkRequest(session_id=session_id, link=link) + request = core_pb2.AddLinkRequest( + session_id=session_id, link=link, source=source + ) return self.stub.AddLink(request) def edit_link( @@ -676,6 +680,7 @@ def delete_link( node2_id: int, iface1_id: int = None, iface2_id: int = None, + source: str = None, ) -> core_pb2.DeleteLinkResponse: """ Delete a link between nodes. @@ -685,6 +690,7 @@ def delete_link( :param node2_id: node two id :param iface1_id: node one interface id :param iface2_id: node two interface id + :param source: application source deleting link :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ @@ -694,6 +700,7 @@ def delete_link( node2_id=node2_id, iface1_id=iface1_id, iface2_id=iface2_id, + source=source, ) return self.stub.DeleteLink(request) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index d2f64ddef..c2573578e 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -833,12 +833,11 @@ def broadcast_config(self, config_data: ConfigData) -> None: for handler in self.config_handlers: handler(config_data) - def broadcast_link(self, link_data: LinkData, source: str = None) -> None: + def broadcast_link(self, link_data: LinkData) -> None: """ Handle link data that should be provided to link handlers. :param link_data: link data to send out - :param source: source of broadcast, None by default :return: nothing """ for handler in self.link_handlers: From f4a3fe6b7b0ee2dc060cb8758cc4d1c4414c454e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 15:14:30 -0700 Subject: [PATCH 080/210] grpc/pygui: edit_link will now broadcast link changes, pygui now handles receiving this data --- daemon/core/api/grpc/client.py | 9 ++++++--- daemon/core/api/grpc/server.py | 14 ++++++++++++++ daemon/core/gui/coreclient.py | 4 ++++ daemon/core/gui/graph/graph.py | 12 +++++++++--- daemon/proto/core/api/grpc/core.proto | 1 + 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 939d7eefa..e73b9fc2a 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -509,7 +509,7 @@ def edit_node( :param node_id: node id :param position: position to set node to :param icon: path to icon for gui to use for node - :param source: application source editing node + :param source: application source :param geo: lon,lat,alt location for node :return: response with result of success or failure :raises grpc.RpcError: when session or node doesn't exist @@ -625,7 +625,7 @@ def add_link( :param iface1: node one interface data :param iface2: node two interface data :param options: options for link (jitter, bandwidth, etc) - :param source: application source adding link + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -650,6 +650,7 @@ def edit_link( options: core_pb2.LinkOptions, iface1_id: int = None, iface2_id: int = None, + source: str = None, ) -> core_pb2.EditLinkResponse: """ Edit a link between nodes. @@ -660,6 +661,7 @@ def edit_link( :param options: options for link (jitter, bandwidth, etc) :param iface1_id: node one interface id :param iface2_id: node two interface id + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -670,6 +672,7 @@ def edit_link( options=options, iface1_id=iface1_id, iface2_id=iface2_id, + source=source, ) return self.stub.EditLink(request) @@ -690,7 +693,7 @@ def delete_link( :param node2_id: node two id :param iface1_id: node one interface id :param iface2_id: node two interface id - :param source: application source deleting link + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 2883103d0..65c7281ef 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -866,6 +866,7 @@ def AddLink( node2_id=node2_id, iface1=iface1_data, iface2=iface2_data, + options=options, source=source, ) session.broadcast_link(link_data) @@ -909,6 +910,19 @@ def EditLink( key=options_proto.key, ) session.update_link(node1_id, node2_id, iface1_id, iface2_id, options) + iface1 = InterfaceData(id=iface1_id) + iface2 = InterfaceData(id=iface2_id) + source = request.source if request.source else None + link_data = LinkData( + message_type=MessageFlags.NONE, + node1_id=node1_id, + node2_id=node2_id, + iface1=iface1, + iface2=iface2, + options=options, + source=source, + ) + session.broadcast_link(link_data) return core_pb2.EditLinkResponse(result=True) def DeleteLink( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index a5b96e175..b29c044e9 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -216,6 +216,10 @@ def handle_link_event(self, event: LinkEvent) -> None: self.app.canvas.organize() elif event.message_type == MessageType.DELETE: self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2) + elif event.message_type == MessageType.NONE: + self.app.canvas.update_wired_edge( + canvas_node1, canvas_node2, event.link + ) else: logging.warning("unknown link event: %s", event) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 4e0358a51..436de383e 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -262,6 +262,13 @@ def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None: return self.delete_edge(edge) + def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: + token = create_edge_token(src.id, dst.id) + edge = self.edges.get(token) + if not edge: + return + edge.link.options.CopyFrom(link.options) + def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) @@ -350,9 +357,8 @@ def stopped_session(self) -> None: dst_node.wireless_edges.remove(edge) self.wireless_edges.clear() - # clear all middle edge labels - for edge in self.edges.values(): - edge.reset() + # clear throughputs + self.clear_throughputs() def canvas_xy(self, event: tk.Event) -> Tuple[float, float]: """ diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 6b1b304c7..7d7592b11 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -504,6 +504,7 @@ message EditLinkRequest { int32 iface1_id = 4; int32 iface2_id = 5; LinkOptions options = 6; + string source = 7; } message EditLinkResponse { From e79645013be7733fabb9e0aaa68ce6873805e89b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 21:45:29 -0700 Subject: [PATCH 081/210] grpc/pygui: updated delete_node to use the source, updated pygui to support delete node events --- daemon/core/api/grpc/client.py | 9 +++++++-- daemon/core/api/grpc/events.py | 3 ++- daemon/core/api/grpc/server.py | 9 ++++++++- daemon/core/gui/coreclient.py | 13 ++++++++++--- daemon/core/gui/graph/node.py | 4 ++-- daemon/proto/core/api/grpc/core.proto | 2 ++ 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e73b9fc2a..2740e7701 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -536,16 +536,21 @@ def move_nodes( """ return self.stub.MoveNodes(move_iterator) - def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse: + def delete_node( + self, session_id: int, node_id: int, source: str = None + ) -> core_pb2.DeleteNodeResponse: """ Delete node from session. :param session_id: session id :param node_id: node id + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id) + request = core_pb2.DeleteNodeRequest( + session_id=session_id, node_id=node_id, source=source + ) return self.stub.DeleteNode(request) def node_command( diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 5c873a43b..fb6eaff8d 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -36,7 +36,8 @@ def handle_node_event(node_data: NodeData) -> core_pb2.Event: geo=geo, services=services, ) - node_event = core_pb2.NodeEvent(node=node_proto) + message_type = node_data.message_type.value + node_event = core_pb2.NodeEvent(message_type=message_type, node=node_proto) return core_pb2.Event(node_event=node_event, source=node_data.source) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 65c7281ef..8851116d5 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -775,7 +775,14 @@ def DeleteNode( """ logging.debug("delete node: %s", request) session = self.get_session(request.session_id, context) - result = session.delete_node(request.node_id) + result = False + try: + node = self.get_node(session, request.node_id, context, NodeBase) + result = session.delete_node(node.id) + source = request.source if request.source else None + session.broadcast_node(node, MessageFlags.DELETE, source) + except grpc.RpcError: + pass return core_pb2.DeleteNodeResponse(result=result) def NodeCommand( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index b29c044e9..cf870cf26 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -226,10 +226,17 @@ def handle_link_event(self, event: LinkEvent) -> None: def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) node_id = event.node.id - x = event.node.position.x - y = event.node.position.y canvas_node = self.canvas_nodes[node_id] - canvas_node.move(x, y) + if event.message_type == MessageType.NONE: + x = event.node.position.x + y = event.node.position.y + canvas_node.move(x, y) + elif event.message_type == MessageType.DELETE: + self.app.canvas.clear_selection() + self.app.canvas.select_object(canvas_node.id) + self.app.canvas.delete_selected_objects() + else: + logging.warning("unknown node event: %s", event) def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index d98c4e48d..6e8185b89 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -271,12 +271,12 @@ def click_unlink(self, edge: CanvasEdge) -> None: def canvas_delete(self) -> None: self.canvas.clear_selection() - self.canvas.selection[self.id] = self + self.canvas.select_object(self.id) self.canvas.delete_selected_objects() def canvas_copy(self) -> None: self.canvas.clear_selection() - self.canvas.selection[self.id] = self + self.canvas.select_object(self.id) self.canvas.copy() def show_config(self) -> None: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 7d7592b11..22cd9c54a 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -348,6 +348,7 @@ message Event { message NodeEvent { Node node = 1; + MessageType.Enum message_type = 2; } message LinkEvent { @@ -435,6 +436,7 @@ message EditNodeResponse { message DeleteNodeRequest { int32 session_id = 1; int32 node_id = 2; + string source = 3; } message DeleteNodeResponse { From 5eae67aac59fb6e93259750517588901acb651bb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:11:36 -0700 Subject: [PATCH 082/210] grpc/pygui: updated add_node source support, updated pygui to handle add_node events --- daemon/core/api/grpc/client.py | 7 +++-- daemon/core/api/grpc/server.py | 2 ++ daemon/core/gui/coreclient.py | 6 +++-- daemon/core/gui/graph/graph.py | 39 +++++++++++++++++---------- daemon/proto/core/api/grpc/core.proto | 1 + 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 2740e7701..82164fe33 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -468,17 +468,20 @@ def throughputs( return stream def add_node( - self, session_id: int, node: core_pb2.Node + self, session_id: int, node: core_pb2.Node, source: str = None ) -> core_pb2.AddNodeResponse: """ Add node to session. :param session_id: session id :param node: node to add + :param source: source application :return: response with node id :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.AddNodeRequest(session_id=session_id, node=node) + request = core_pb2.AddNodeRequest( + session_id=session_id, node=node, source=source + ) return self.stub.AddNode(request) def get_node(self, session_id: int, node_id: int) -> core_pb2.GetNodeResponse: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8851116d5..27702629b 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -668,6 +668,8 @@ def AddNode( _type, _id, options = grpcutils.add_node_data(request.node) _class = session.get_node_class(_type) node = session.add_node(_class, _id, options) + source = request.source if request.source else None + session.broadcast_node(node, MessageFlags.ADD, source) return core_pb2.AddNodeResponse(node_id=node.id) def GetNode( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index cf870cf26..cf331676c 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -225,16 +225,18 @@ def handle_link_event(self, event: LinkEvent) -> None: def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) - node_id = event.node.id - canvas_node = self.canvas_nodes[node_id] if event.message_type == MessageType.NONE: + canvas_node = self.canvas_nodes[event.node.id] x = event.node.position.x y = event.node.position.y canvas_node.move(x, y) elif event.message_type == MessageType.DELETE: + canvas_node = self.canvas_nodes[event.node.id] self.app.canvas.clear_selection() self.app.canvas.select_object(canvas_node.id) self.app.canvas.delete_selected_objects() + elif event.message_type == MessageType.ADD: + self.app.canvas.add_core_node(event.node) else: logging.warning("unknown node event: %s", event) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 436de383e..9cb3b1097 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -7,7 +7,14 @@ from PIL import Image from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import Interface, Link, LinkType, Session, ThroughputsEvent +from core.api.grpc.core_pb2 import ( + Interface, + Link, + LinkType, + Node, + Session, + ThroughputsEvent, +) from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.edges import ( @@ -315,29 +322,33 @@ def update_wireless_edge( edge = self.wireless_edges[token] edge.middle_label_text(link.label) + def add_core_node(self, core_node: Node) -> None: + if core_node.id in self.core.canvas_nodes: + logging.error("core node already exists: %s", core_node) + return + logging.debug("adding node %s", core_node) + # if the gui can't find node's image, default to the "edit-node" image + image = NodeUtils.node_image(core_node, self.app.guiconfig, self.app.app_scale) + if not image: + image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE) + x = core_node.position.x + y = core_node.position.y + node = CanvasNode(self.app, x, y, core_node, image) + self.nodes[node.id] = node + self.core.canvas_nodes[core_node.id] = node + def draw_session(self, session: Session) -> None: """ Draw existing session. """ # draw existing nodes for core_node in session.nodes: - logging.debug("drawing node %s", core_node) # peer to peer node is not drawn on the GUI if NodeUtils.is_ignore_node(core_node.type): continue - image = NodeUtils.node_image( - core_node, self.app.guiconfig, self.app.app_scale - ) - # if the gui can't find node's image, default to the "edit-node" image - if not image: - image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE) - x = core_node.position.x - y = core_node.position.y - node = CanvasNode(self.app, x, y, core_node, image) - self.nodes[node.id] = node - self.core.canvas_nodes[core_node.id] = node + self.add_core_node(core_node) - # draw existing links + # draw existing links for link in session.links: logging.debug("drawing link: %s", link) canvas_node1 = self.core.canvas_nodes[link.node1_id] diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 22cd9c54a..8112c9d11 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -404,6 +404,7 @@ message FileEvent { message AddNodeRequest { int32 session_id = 1; Node node = 2; + string source = 3; } message AddNodeResponse { From c8daeb02d82db91914e13753db3d785ad145df1a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:29:17 -0700 Subject: [PATCH 083/210] grpc: fixed issue with not catching error in delete_node from broadcast changes --- daemon/core/api/grpc/server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 27702629b..e8469177b 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -778,13 +778,11 @@ def DeleteNode( logging.debug("delete node: %s", request) session = self.get_session(request.session_id, context) result = False - try: + if request.node_id in session.nodes: node = self.get_node(session, request.node_id, context, NodeBase) result = session.delete_node(node.id) source = request.source if request.source else None session.broadcast_node(node, MessageFlags.DELETE, source) - except grpc.RpcError: - pass return core_pb2.DeleteNodeResponse(result=result) def NodeCommand( From 59e7395a4f30cccbaaac0e693922c11333bd7f5a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 29 Jun 2020 23:00:33 -0700 Subject: [PATCH 084/210] initial addition of core-cli script that can be used to run commands and query information with sessions using grpc, similar in concept to coresendmsg --- daemon/scripts/core-cli | 447 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100755 daemon/scripts/core-cli diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli new file mode 100755 index 000000000..ca9011b5d --- /dev/null +++ b/daemon/scripts/core-cli @@ -0,0 +1,447 @@ +#!/usr/bin/env python3 +import sys +from argparse import ( + ArgumentDefaultsHelpFormatter, + ArgumentParser, + ArgumentTypeError, + Namespace, + _SubParsersAction, +) +from typing import Tuple + +import netaddr +from google.protobuf.json_format import MessageToJson + +from core.api.grpc.client import CoreGrpcClient +from core.api.grpc.core_pb2 import ( + Geo, + Interface, + LinkOptions, + Node, + NodeType, + Position, + SessionState, +) + +NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] + + +def mac_type(value: str) -> str: + if not netaddr.valid_mac(value): + raise ArgumentTypeError("invalid mac address") + return value + + +def ip4_type(value: str) -> str: + if not netaddr.valid_ipv4(value): + raise ArgumentTypeError("invalid ip4 address") + return value + + +def ip6_type(value: str) -> str: + if not netaddr.valid_ipv6(value): + raise ArgumentTypeError("invalid ip6 address") + return value + + +def position_type(value: str) -> Tuple[float, float]: + error = "invalid position, must be in the format: float,float" + try: + values = [float(x) for x in value.split(",")] + except ValueError: + raise ArgumentTypeError(error) + if len(values) != 2: + raise ArgumentTypeError(error) + x, y = values + return x, y + + +def geo_type(value: str) -> Tuple[float, float, float]: + error = "invalid geo, must be in the format: float,float,float" + try: + values = [float(x) for x in value.split(",")] + except ValueError: + raise ArgumentTypeError(error) + if len(values) != 3: + raise ArgumentTypeError(error) + lon, lat, alt = values + return lon, lat, alt + + +def get_current_session() -> int: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_sessions() + if not response.sessions: + print("no current session to interact with") + sys.exit(1) + return response.sessions[0].id + + +def print_interface_header() -> None: + print("ID | MAC Address | IP4 Address | IP6 Address") + + +def print_interface(iface: Interface) -> None: + iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else None + iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else None + print(f"{iface.id:<3} | {iface.mac:<11} | {iface_ip4:<18} | {iface_ip6}") + + +def query_sessions(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_sessions() + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print("Session ID | Session State | Nodes") + for s in response.sessions: + state = SessionState.Enum.Name(s.state) + print(f"{s.id:<10} | {state:<13} | {s.nodes}") + + +def query_session(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_session(args.id) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print("Nodes") + print("Node ID | Node Name | Node Type") + names = {} + for node in response.session.nodes: + names[node.id] = node.name + node_type = NodeType.Enum.Name(node.type) + print(f"{node.id:<7} | {node.name:<9} | {node_type}") + + print("\nLinks") + for link in response.session.links: + n1 = names[link.node1_id] + n2 = names[link.node2_id] + print(f"Node | ", end="") + print_interface_header() + if link.HasField("iface1"): + print(f"{n1:<6} | ", end="") + print_interface(link.iface1) + if link.HasField("iface2"): + print(f"{n2:<6} | ", end="") + print_interface(link.iface2) + print() + + +def query_node(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_node(args.id, args.node) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + node = response.node + node_type = NodeType.Enum.Name(node.type) + print("ID | Name | Type") + print(f"{node.id:<4} | {node.name:<7} | {node_type}") + print("Interfaces") + print_interface_header() + for iface in response.ifaces: + print_interface(iface) + + +def add_node(args: Namespace) -> None: + session_id = get_current_session() + node_type = NodeType.Enum.Value(args.type) + pos = None + if args.pos: + x, y = args.pos + pos = Position(x=x, y=y) + geo = None + if args.geo: + lon, lat, alt = args.geo + geo = Geo(lon=lon, lat=lat, alt=alt) + core = CoreGrpcClient() + with core.context_connect(): + node = Node( + id=args.id, + name=args.name, + type=node_type, + model=args.model, + emane=args.emane, + icon=args.icon, + image=args.image, + position=pos, + geo=geo, + ) + response = core.add_node(session_id, node) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"created node: {response.node_id}") + + +def edit_node(args: Namespace) -> None: + session_id = get_current_session() + pos = None + if args.pos: + x, y = args.pos + pos = Position(x=x, y=y) + geo = None + if args.geo: + lon, lat, alt = args.geo + geo = Geo(lon=lon, lat=lat, alt=alt) + core = CoreGrpcClient() + with core.context_connect(): + response = core.edit_node(session_id, args.id, pos, args.icon, geo=geo) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"edit node: {response.result}") + + +def delete_node(args: Namespace) -> None: + session_id = get_current_session() + core = CoreGrpcClient() + with core.context_connect(): + response = core.delete_node(session_id, args.id) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"deleted node: {response.result}") + + +def add_link(args: Namespace) -> None: + session_id = get_current_session() + iface1 = None + if args.iface1_id is not None: + iface1 = Interface( + id=args.iface1_id, + mac=args.iface1_mac, + ip4=args.iface1_ip4, + ip4_mask=args.iface1_ip4_mask, + ip6=args.iface1_ip4, + ip6_mask=args.iface1_ip6_mask, + ) + iface2 = None + if args.iface2_id is not None: + iface2 = Interface( + id=args.iface2_id, + mac=args.iface2_mac, + ip4=args.iface2_ip4, + ip4_mask=args.iface2_ip4_mask, + ip6=args.iface2_ip4, + ip6_mask=args.iface2_ip6_mask, + ) + options = LinkOptions( + bandwidth=args.bandwidth, + loss=args.loss, + jitter=args.jitter, + delay=args.delay, + dup=args.dup, + unidirectional=args.uni, + ) + core = CoreGrpcClient() + with core.context_connect(): + response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"edit link: {response.result}") + + +def edit_link(args: Namespace) -> None: + session_id = get_current_session() + options = LinkOptions( + bandwidth=args.bandwidth, + loss=args.loss, + jitter=args.jitter, + delay=args.delay, + dup=args.dup, + unidirectional=args.uni, + ) + core = CoreGrpcClient() + with core.context_connect(): + response = core.edit_link( + session_id, args.node1, args.node2, options, args.iface1, args.iface2 + ) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"edit link: {response.result}") + + +def delete_link(args: Namespace) -> None: + session_id = get_current_session() + core = CoreGrpcClient() + with core.context_connect(): + response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"delete link: {response.result}") + + +def setup_node_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("node", help="node interactions") + parser.add_argument("--session", type=int, help="session to interact with") + subparsers = parser.add_subparsers(help="node commands") + subparsers.required = True + subparsers.dest = "command" + + add_parser = subparsers.add_parser("add", help="add a node") + add_parser.formatter_class = ArgumentDefaultsHelpFormatter + add_parser.add_argument("--id", type=int, help="id to use, optional") + add_parser.add_argument("--name", help="name to use, optional") + add_parser.add_argument( + "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" + ) + add_parser.add_argument("--model", help="used to determine services, optional") + group = add_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--pos", type=position_type, help="x,y position") + group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") + add_parser.add_argument("--icon", help="icon to use, optional") + add_parser.add_argument("--image", help="container image, optional") + add_parser.add_argument( + "--emane", help="emane model, only required for emane nodes" + ) + add_parser.set_defaults(func=add_node) + + edit_parser = subparsers.add_parser("edit", help="edit a node") + edit_parser.formatter_class = ArgumentDefaultsHelpFormatter + edit_parser.add_argument("--id", type=int, help="id to use, optional") + group = edit_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--pos", type=position_type, help="x,y position") + group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") + edit_parser.add_argument("--icon", help="icon to use, optional") + edit_parser.set_defaults(func=edit_node) + + delete_parser = subparsers.add_parser("delete", help="delete a node") + delete_parser.formatter_class = ArgumentDefaultsHelpFormatter + delete_parser.add_argument( + "--id", type=int, help="node id to delete", required=True + ) + delete_parser.set_defaults(func=delete_node) + + +def setup_link_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("link", help="link interactions") + parser.add_argument("--session", type=int, help="session to interact with") + subparsers = parser.add_subparsers(help="link commands") + subparsers.required = True + subparsers.dest = "command" + + add_parser = subparsers.add_parser("add", help="add a node") + add_parser.formatter_class = ArgumentDefaultsHelpFormatter + add_parser.add_argument( + "--node1", type=int, help="node1 id for link", required=True + ) + add_parser.add_argument( + "--node2", type=int, help="node1 id for link", required=True + ) + add_parser.add_argument("--iface1-id", type=int, help="node1 interface id for link") + add_parser.add_argument("--iface1-mac", type=mac_type, help="node1 interface mac") + add_parser.add_argument("--iface1-ip4", type=ip4_type, help="node1 interface ip4") + add_parser.add_argument( + "--iface1-ip4-mask", type=int, help="node1 interface ip4 mask" + ) + add_parser.add_argument("--iface1-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument( + "--iface1-ip6-mask", type=int, help="node1 interface ip6 mask" + ) + add_parser.add_argument("--iface2-id", type=int, help="node1 interface id for link") + add_parser.add_argument("--iface2-mac", type=mac_type, help="node1 interface mac") + add_parser.add_argument("--iface2-ip4", type=ip4_type, help="node1 interface ip4") + add_parser.add_argument( + "--iface2-ip4-mask", type=int, help="node1 interface ip4 mask" + ) + add_parser.add_argument("--iface2-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument( + "--iface2-ip6-mask", type=int, help="node1 interface ip6 mask" + ) + add_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") + add_parser.add_argument("--loss", type=float, help="loss (%) for link") + add_parser.add_argument("--jitter", type=int, help="jitter (us) for link") + add_parser.add_argument("--delay", type=int, help="delay (us) for link") + add_parser.add_argument("--dup", type=int, help="duplicate (%) for link") + add_parser.add_argument( + "--uni", action="store_true", help="is link unidirectional?" + ) + add_parser.set_defaults(func=add_link) + + edit_parser = subparsers.add_parser("edit", help="edit a link") + edit_parser.formatter_class = ArgumentDefaultsHelpFormatter + edit_parser.add_argument( + "--node1", type=int, help="node1 id for link", required=True + ) + edit_parser.add_argument( + "--node2", type=int, help="node1 id for link", required=True + ) + edit_parser.add_argument("--iface1", type=int, help="node1 interface id for link") + edit_parser.add_argument("--iface2", type=int, help="node2 interface id for link") + edit_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") + edit_parser.add_argument("--loss", type=float, help="loss (%) for link") + edit_parser.add_argument("--jitter", type=int, help="jitter (us) for link") + edit_parser.add_argument("--delay", type=int, help="delay (us) for link") + edit_parser.add_argument("--dup", type=int, help="duplicate (%) for link") + edit_parser.add_argument( + "--uni", action="store_true", help="is link unidirectional?" + ) + edit_parser.set_defaults(func=edit_link) + + delete_parser = subparsers.add_parser("delete", help="delete a link") + delete_parser.formatter_class = ArgumentDefaultsHelpFormatter + delete_parser.add_argument( + "--node1", type=int, help="node1 id for link", required=True + ) + delete_parser.add_argument( + "--node2", type=int, help="node1 id for link", required=True + ) + delete_parser.add_argument("--iface1", type=int, help="node1 interface id for link") + delete_parser.add_argument("--iface2", type=int, help="node2 interface id for link") + delete_parser.set_defaults(func=delete_link) + + +def setup_query_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("query", help="query interactions") + subparsers = parser.add_subparsers(help="query commands") + subparsers.required = True + subparsers.dest = "command" + + sessions_parser = subparsers.add_parser("sessions", help="query current sessions") + sessions_parser.set_defaults(func=query_sessions) + + session_parser = subparsers.add_parser("session", help="query session") + session_parser.add_argument("--id", type=int, help="session to query", required=True) + session_parser.set_defaults(func=query_session) + + node_parser = subparsers.add_parser("node", help="query node") + node_parser.add_argument("--id", type=int, help="session to query", required=True) + node_parser.add_argument("--node", type=int, help="node to query", required=True) + node_parser.set_defaults(func=query_node) + + +def main() -> None: + parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) + parser.add_argument( + "-j", "--json", action="store_true", help="print responses to terminal as json" + ) + subparsers = parser.add_subparsers(help="supported commands") + subparsers.required = True + subparsers.dest = "command" + setup_node_parser(subparsers) + setup_link_parser(subparsers) + setup_query_parser(subparsers) + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() From ec845b920c4b55340fb579dbafa849b60470b6e4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 08:27:40 -0700 Subject: [PATCH 085/210] removed ip mask options from core-cli add link, combined with ip and will parse input to provide simpler interface --- daemon/scripts/core-cli | 92 ++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index ca9011b5d..a8354f653 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -11,6 +11,7 @@ from typing import Tuple import netaddr from google.protobuf.json_format import MessageToJson +from netaddr import EUI, AddrFormatError, IPNetwork from core.api.grpc.client import CoreGrpcClient from core.api.grpc.core_pb2 import ( @@ -27,21 +28,31 @@ NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] def mac_type(value: str) -> str: - if not netaddr.valid_mac(value): - raise ArgumentTypeError("invalid mac address") - return value + try: + mac = EUI(value, dialect=netaddr.mac_unix_expanded) + return str(mac) + except AddrFormatError: + raise ArgumentTypeError(f"invalid mac address: {value}") -def ip4_type(value: str) -> str: - if not netaddr.valid_ipv4(value): - raise ArgumentTypeError("invalid ip4 address") - return value +def ip4_type(value: str) -> IPNetwork: + try: + ip = IPNetwork(value) + if not netaddr.valid_ipv4(str(ip.ip)): + raise ArgumentTypeError(f"invalid ip4 address: {value}") + return ip + except AddrFormatError: + raise ArgumentTypeError(f"invalid ip4 address: {value}") -def ip6_type(value: str) -> str: - if not netaddr.valid_ipv6(value): - raise ArgumentTypeError("invalid ip6 address") - return value +def ip6_type(value: str) -> IPNetwork: + try: + ip = IPNetwork(value) + if not netaddr.valid_ipv6(str(ip.ip)): + raise ArgumentTypeError(f"invalid ip6 address: {value}") + return ip + except AddrFormatError: + raise ArgumentTypeError(f"invalid ip6 address: {value}") def position_type(value: str) -> Tuple[float, float]: @@ -78,11 +89,26 @@ def get_current_session() -> int: return response.sessions[0].id -def print_interface_header() -> None: +def create_iface(iface_id: int, mac: str, ip4_net: IPNetwork, ip6_net: IPNetwork) -> Interface: + ip4 = str(ip4_net.ip) if ip4_net else None + ip4_mask = ip4_net.prefixlen if ip4_net else None + ip6 = str(ip6_net.ip) if ip6_net else None + ip6_mask = ip6_net.prefixlen if ip6_net else None + return Interface( + id=iface_id, + mac=mac, + ip4=ip4, + ip4_mask=ip4_mask, + ip6=ip6, + ip6_mask=ip6_mask, + ) + + +def print_iface_header() -> None: print("ID | MAC Address | IP4 Address | IP6 Address") -def print_interface(iface: Interface) -> None: +def print_iface(iface: Interface) -> None: iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else None iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else None print(f"{iface.id:<3} | {iface.mac:<11} | {iface_ip4:<18} | {iface_ip6}") @@ -123,13 +149,13 @@ def query_session(args: Namespace) -> None: n1 = names[link.node1_id] n2 = names[link.node2_id] print(f"Node | ", end="") - print_interface_header() + print_iface_header() if link.HasField("iface1"): print(f"{n1:<6} | ", end="") - print_interface(link.iface1) + print_iface(link.iface1) if link.HasField("iface2"): print(f"{n2:<6} | ", end="") - print_interface(link.iface2) + print_iface(link.iface2) print() @@ -146,9 +172,9 @@ def query_node(args: Namespace) -> None: print("ID | Name | Type") print(f"{node.id:<4} | {node.name:<7} | {node_type}") print("Interfaces") - print_interface_header() + print_iface_header() for iface in response.ifaces: - print_interface(iface) + print_iface(iface) def add_node(args: Namespace) -> None: @@ -219,24 +245,10 @@ def add_link(args: Namespace) -> None: session_id = get_current_session() iface1 = None if args.iface1_id is not None: - iface1 = Interface( - id=args.iface1_id, - mac=args.iface1_mac, - ip4=args.iface1_ip4, - ip4_mask=args.iface1_ip4_mask, - ip6=args.iface1_ip4, - ip6_mask=args.iface1_ip6_mask, - ) + iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) iface2 = None if args.iface2_id is not None: - iface2 = Interface( - id=args.iface2_id, - mac=args.iface2_mac, - ip4=args.iface2_ip4, - ip4_mask=args.iface2_ip4_mask, - ip6=args.iface2_ip4, - ip6_mask=args.iface2_ip6_mask, - ) + iface2 = create_iface(args.iface2_id, args.iface2_mac, args.iface2_ip4, args.iface2_ip6) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -349,23 +361,11 @@ def setup_link_parser(parent: _SubParsersAction) -> None: add_parser.add_argument("--iface1-id", type=int, help="node1 interface id for link") add_parser.add_argument("--iface1-mac", type=mac_type, help="node1 interface mac") add_parser.add_argument("--iface1-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument( - "--iface1-ip4-mask", type=int, help="node1 interface ip4 mask" - ) add_parser.add_argument("--iface1-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument( - "--iface1-ip6-mask", type=int, help="node1 interface ip6 mask" - ) add_parser.add_argument("--iface2-id", type=int, help="node1 interface id for link") add_parser.add_argument("--iface2-mac", type=mac_type, help="node1 interface mac") add_parser.add_argument("--iface2-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument( - "--iface2-ip4-mask", type=int, help="node1 interface ip4 mask" - ) add_parser.add_argument("--iface2-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument( - "--iface2-ip6-mask", type=int, help="node1 interface ip6 mask" - ) add_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") add_parser.add_argument("--loss", type=float, help="loss (%) for link") add_parser.add_argument("--jitter", type=int, help="jitter (us) for link") From aef3fe8d50e47089abf974e4a28f6bafa65e14c4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:25:36 -0700 Subject: [PATCH 086/210] updated core-cli to use consistent shorthand options and existing longform options --- daemon/scripts/core-cli | 122 +++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index a8354f653..65a929942 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -254,7 +254,7 @@ def add_link(args: Namespace) -> None: loss=args.loss, jitter=args.jitter, delay=args.delay, - dup=args.dup, + dup=args.duplicate, unidirectional=args.uni, ) core = CoreGrpcClient() @@ -274,7 +274,7 @@ def edit_link(args: Namespace) -> None: loss=args.loss, jitter=args.jitter, delay=args.delay, - dup=args.dup, + dup=args.duplicate, unidirectional=args.uni, ) core = CoreGrpcClient() @@ -303,109 +303,91 @@ def delete_link(args: Namespace) -> None: def setup_node_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("node", help="node interactions") - parser.add_argument("--session", type=int, help="session to interact with") + parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="node commands") subparsers.required = True subparsers.dest = "command" add_parser = subparsers.add_parser("add", help="add a node") add_parser.formatter_class = ArgumentDefaultsHelpFormatter - add_parser.add_argument("--id", type=int, help="id to use, optional") - add_parser.add_argument("--name", help="name to use, optional") + add_parser.add_argument("-i", "--id", type=int, help="id to use, optional") + add_parser.add_argument("-n", "--name", help="name to use, optional") add_parser.add_argument( - "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" + "-t", "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" ) - add_parser.add_argument("--model", help="used to determine services, optional") + add_parser.add_argument("-m", "--model", help="used to determine services, optional") group = add_parser.add_mutually_exclusive_group(required=True) - group.add_argument("--pos", type=position_type, help="x,y position") - group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") - add_parser.add_argument("--icon", help="icon to use, optional") - add_parser.add_argument("--image", help="container image, optional") - add_parser.add_argument( - "--emane", help="emane model, only required for emane nodes" - ) + group.add_argument("-p", "--pos", type=position_type, help="x,y position") + group.add_argument("-g", "--geo", type=geo_type, help="lon,lat,alt position") + add_parser.add_argument("-ic", "--icon", help="icon to use, optional") + add_parser.add_argument("-im", "--image", help="container image, optional") + add_parser.add_argument("-e", "--emane", help="emane model, only required for emane nodes") add_parser.set_defaults(func=add_node) edit_parser = subparsers.add_parser("edit", help="edit a node") edit_parser.formatter_class = ArgumentDefaultsHelpFormatter - edit_parser.add_argument("--id", type=int, help="id to use, optional") + edit_parser.add_argument("-i", "--id", type=int, help="id to use, optional") group = edit_parser.add_mutually_exclusive_group(required=True) - group.add_argument("--pos", type=position_type, help="x,y position") - group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") - edit_parser.add_argument("--icon", help="icon to use, optional") + group.add_argument("-p", "--pos", type=position_type, help="x,y position") + group.add_argument("-g", "--geo", type=geo_type, help="lon,lat,alt position") + edit_parser.add_argument("-ic", "--icon", help="icon to use, optional") edit_parser.set_defaults(func=edit_node) delete_parser = subparsers.add_parser("delete", help="delete a node") delete_parser.formatter_class = ArgumentDefaultsHelpFormatter - delete_parser.add_argument( - "--id", type=int, help="node id to delete", required=True - ) + delete_parser.add_argument("-i", "--id", type=int, help="node id", required=True) delete_parser.set_defaults(func=delete_node) def setup_link_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("link", help="link interactions") - parser.add_argument("--session", type=int, help="session to interact with") + parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="link commands") subparsers.required = True subparsers.dest = "command" add_parser = subparsers.add_parser("add", help="add a node") add_parser.formatter_class = ArgumentDefaultsHelpFormatter - add_parser.add_argument( - "--node1", type=int, help="node1 id for link", required=True - ) - add_parser.add_argument( - "--node2", type=int, help="node1 id for link", required=True - ) - add_parser.add_argument("--iface1-id", type=int, help="node1 interface id for link") - add_parser.add_argument("--iface1-mac", type=mac_type, help="node1 interface mac") - add_parser.add_argument("--iface1-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument("--iface1-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument("--iface2-id", type=int, help="node1 interface id for link") - add_parser.add_argument("--iface2-mac", type=mac_type, help="node1 interface mac") - add_parser.add_argument("--iface2-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument("--iface2-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") - add_parser.add_argument("--loss", type=float, help="loss (%) for link") - add_parser.add_argument("--jitter", type=int, help="jitter (us) for link") - add_parser.add_argument("--delay", type=int, help="delay (us) for link") - add_parser.add_argument("--dup", type=int, help="duplicate (%) for link") - add_parser.add_argument( - "--uni", action="store_true", help="is link unidirectional?" - ) + add_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) + add_parser.add_argument("-n2", "--node2", type=int, help="node2 id", required=True) + add_parser.add_argument("-i1-i", "--iface1-id", type=int, help="node1 interface id") + add_parser.add_argument("-i1-m", "--iface1-mac", type=mac_type, help="node1 interface mac") + add_parser.add_argument("-i1-4", "--iface1-ip4", type=ip4_type, help="node1 interface ip4") + add_parser.add_argument("-i1-6", "--iface1-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument("-i2-i", "--iface2-id", type=int, help="node2 interface id") + add_parser.add_argument("-i2-m", "--iface2-mac", type=mac_type, help="node2 interface mac") + add_parser.add_argument("-i2-4", "--iface2-ip4", type=ip4_type, help="node2 interface ip4") + add_parser.add_argument("-i2-6", "--iface2-ip6", type=ip6_type, help="node2 interface ip6") + add_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") + add_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") + add_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") + add_parser.add_argument("-de", "--delay", type=int, help="delay (us)") + add_parser.add_argument("-du", "--duplicate", type=int, help="duplicate (%%)") + add_parser.add_argument("-u", "--uni", action="store_true", help="is link unidirectional?") add_parser.set_defaults(func=add_link) edit_parser = subparsers.add_parser("edit", help="edit a link") edit_parser.formatter_class = ArgumentDefaultsHelpFormatter + edit_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) + edit_parser.add_argument("-n2", "--node2", type=int, help="node2 id", required=True) + edit_parser.add_argument("-i1", "--iface1", type=int, help="node1 interface id") + edit_parser.add_argument("-i2", "--iface2", type=int, help="node2 interface id") + edit_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") + edit_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") + edit_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") + edit_parser.add_argument("-de", "--delay", type=int, help="delay (us)") + edit_parser.add_argument("-du", "--duplicate", type=int, help="duplicate (%%)") edit_parser.add_argument( - "--node1", type=int, help="node1 id for link", required=True - ) - edit_parser.add_argument( - "--node2", type=int, help="node1 id for link", required=True - ) - edit_parser.add_argument("--iface1", type=int, help="node1 interface id for link") - edit_parser.add_argument("--iface2", type=int, help="node2 interface id for link") - edit_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") - edit_parser.add_argument("--loss", type=float, help="loss (%) for link") - edit_parser.add_argument("--jitter", type=int, help="jitter (us) for link") - edit_parser.add_argument("--delay", type=int, help="delay (us) for link") - edit_parser.add_argument("--dup", type=int, help="duplicate (%) for link") - edit_parser.add_argument( - "--uni", action="store_true", help="is link unidirectional?" + "-u", "--uni", action="store_true", help="is link unidirectional?" ) edit_parser.set_defaults(func=edit_link) delete_parser = subparsers.add_parser("delete", help="delete a link") delete_parser.formatter_class = ArgumentDefaultsHelpFormatter - delete_parser.add_argument( - "--node1", type=int, help="node1 id for link", required=True - ) - delete_parser.add_argument( - "--node2", type=int, help="node1 id for link", required=True - ) - delete_parser.add_argument("--iface1", type=int, help="node1 interface id for link") - delete_parser.add_argument("--iface2", type=int, help="node2 interface id for link") + delete_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) + delete_parser.add_argument("-n2", "--node2", type=int, help="node1 id", required=True) + delete_parser.add_argument("-i1", "--iface1", type=int, help="node1 interface id") + delete_parser.add_argument("-i2", "--iface2", type=int, help="node2 interface id") delete_parser.set_defaults(func=delete_link) @@ -419,19 +401,19 @@ def setup_query_parser(parent: _SubParsersAction) -> None: sessions_parser.set_defaults(func=query_sessions) session_parser = subparsers.add_parser("session", help="query session") - session_parser.add_argument("--id", type=int, help="session to query", required=True) + session_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) session_parser.set_defaults(func=query_session) node_parser = subparsers.add_parser("node", help="query node") - node_parser.add_argument("--id", type=int, help="session to query", required=True) - node_parser.add_argument("--node", type=int, help="node to query", required=True) + node_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) + node_parser.add_argument("-n", "--node", type=int, help="node to query", required=True) node_parser.set_defaults(func=query_node) def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( - "-j", "--json", action="store_true", help="print responses to terminal as json" + "-js", "--json", action="store_true", help="print responses to terminal as json" ) subparsers = parser.add_subparsers(help="supported commands") subparsers.required = True From 69721dc1290a53340df120e4bb27af3993911f5b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:32:56 -0700 Subject: [PATCH 087/210] grpc: updated client edit_node to have source as last parameter to be consistent with source placement on all other functions --- daemon/core/api/grpc/client.py | 4 ++-- daemon/scripts/core-cli | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 82164fe33..20e193eb2 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -502,8 +502,8 @@ def edit_node( node_id: int, position: core_pb2.Position = None, icon: str = None, - source: str = None, geo: core_pb2.Geo = None, + source: str = None, ) -> core_pb2.EditNodeResponse: """ Edit a node, currently only changes position. @@ -512,8 +512,8 @@ def edit_node( :param node_id: node id :param position: position to set node to :param icon: path to icon for gui to use for node - :param source: application source :param geo: lon,lat,alt location for node + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session or node doesn't exist """ diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 65a929942..df4535ac7 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -221,7 +221,7 @@ def edit_node(args: Namespace) -> None: geo = Geo(lon=lon, lat=lat, alt=alt) core = CoreGrpcClient() with core.context_connect(): - response = core.edit_node(session_id, args.id, pos, args.icon, geo=geo) + response = core.edit_node(session_id, args.id, pos, args.icon, geo) if args.json: json = MessageToJson(response, preserving_proto_field_name=True) print(json) From d480a1dd4c92458bf3236fa1dd03dedfeb088146 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:38:22 -0700 Subject: [PATCH 088/210] grpc: removed LinkOptions opaque as it was not being used --- daemon/proto/core/api/grpc/core.proto | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 8112c9d11..3828d4749 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -719,17 +719,16 @@ message Link { } message LinkOptions { - string opaque = 1; - int64 jitter = 2; - int32 key = 3; - int32 mburst = 4; - int32 mer = 5; - float loss = 6; - int64 bandwidth = 7; - int32 burst = 8; - int64 delay = 9; - int32 dup = 10; - bool unidirectional = 11; + int64 jitter = 1; + int32 key = 2; + int32 mburst = 3; + int32 mer = 4; + float loss = 5; + int64 bandwidth = 6; + int32 burst = 7; + int64 delay = 8; + int32 dup = 9; + bool unidirectional = 10; } message Interface { From ab17cb1053facc612ed22b2547d0f77686cae304 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:50:28 -0700 Subject: [PATCH 089/210] grpc: grpc get_session will no longer return peer to peer nodes, they should be invisible to users, updated core-cli to print human readable links better --- daemon/core/api/grpc/server.py | 4 ++-- daemon/core/gui/nodeutils.py | 2 +- daemon/scripts/core-cli | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index e8469177b..4f741b22e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -114,7 +114,7 @@ from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes.base import CoreNode, CoreNodeBase, NodeBase -from core.nodes.network import WlanNode +from core.nodes.network import PtpNet, WlanNode from core.services.coreservices import ServiceManager _ONE_DAY_IN_SECONDS: int = 60 * 60 * 24 @@ -543,7 +543,7 @@ def GetSession( nodes = [] for _id in session.nodes: node = session.nodes[_id] - if not isinstance(node.id, int): + if isinstance(node, PtpNet): continue node_proto = grpcutils.get_node_proto(session, node) nodes.append(node_proto) diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index dbb403dfa..08c8f31ce 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -62,7 +62,7 @@ class NodeUtils: IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC} WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} RJ45_NODES: Set[NodeType] = {NodeType.RJ45} - IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER} + IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET} NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"} ROUTER_NODES: Set[str] = {"router", "mdr"} ANTENNA_ICON: PhotoImage = None diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index df4535ac7..05e495e58 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -150,12 +150,16 @@ def query_session(args: Namespace) -> None: n2 = names[link.node2_id] print(f"Node | ", end="") print_iface_header() + print(f"{n1:<6} | ", end="") if link.HasField("iface1"): - print(f"{n1:<6} | ", end="") print_iface(link.iface1) + else: + print() + print(f"{n2:<6} | ", end="") if link.HasField("iface2"): - print(f"{n2:<6} | ", end="") print_iface(link.iface2) + else: + print() print() From beaebcfa2496703308bfc7d7fa732155e5d11020 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 12:34:20 -0700 Subject: [PATCH 090/210] grpc: added node_id and net2_id data to interface protos to allow querying a node to provide the node and networks an interface is associated with --- daemon/core/api/grpc/grpcutils.py | 35 +++++++++++++++------------ daemon/core/api/grpc/server.py | 6 ++--- daemon/proto/core/api/grpc/core.proto | 2 ++ daemon/scripts/core-cli | 22 ++++++++++++++--- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index ed40a75b7..bd9e808dc 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -453,32 +453,35 @@ def iface_to_data(iface: CoreInterface) -> InterfaceData: ) -def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: +def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface: """ Convenience for converting a core interface to the protobuf representation. + + :param node_id: id of node to convert interface for :param iface: interface to convert :return: interface proto """ - net_id = None - if iface.net: - net_id = iface.net.id - ip4 = None - ip4_mask = None + 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() - if ip4_net: - ip4 = str(ip4_net.ip) - ip4_mask = ip4_net.prefixlen - ip6 = None - ip6_mask = None + ip4 = str(ip4_net.ip) if ip4_net else None + ip4_mask = ip4_net.prefixlen if ip4_net else None ip6_net = iface.get_ip6() - if ip6_net: - ip6 = str(ip6_net.ip) - ip6_mask = ip6_net.prefixlen + ip6 = str(ip6_net.ip) if ip6_net else None + ip6_mask = ip6_net.prefixlen if ip6_net else None + mac = str(iface.mac) if iface.mac else None return core_pb2.Interface( - id=iface.node_id, + id=_id, net_id=net_id, + net2_id=net2_id, + node_id=node_id, name=iface.name, - mac=str(iface.mac), + mac=mac, mtu=iface.mtu, flow_id=iface.flow_id, ip4=ip4, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4f741b22e..c447ee7cc 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -688,7 +688,7 @@ def GetNode( ifaces = [] for iface_id in node.ifaces: iface = node.ifaces[iface_id] - iface_proto = grpcutils.iface_to_proto(iface) + iface_proto = grpcutils.iface_to_proto(request.node_id, iface) ifaces.append(iface_proto) node_proto = grpcutils.get_node_proto(session, node) return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces) @@ -880,9 +880,9 @@ def AddLink( iface1_proto = None iface2_proto = None if node1_iface: - iface1_proto = grpcutils.iface_to_proto(node1_iface) + iface1_proto = grpcutils.iface_to_proto(node1_id, node1_iface) if node2_iface: - iface2_proto = grpcutils.iface_to_proto(node2_iface) + iface2_proto = grpcutils.iface_to_proto(node2_id, node2_iface) return core_pb2.AddLinkResponse( result=True, iface1=iface1_proto, iface2=iface2_proto ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 3828d4749..f01fca509 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -742,6 +742,8 @@ message Interface { int32 net_id = 8; int32 flow_id = 9; int32 mtu = 10; + int32 node_id = 11; + int32 net2_id = 12; } message SessionLocation { diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 05e495e58..c4d97a8af 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -109,9 +109,9 @@ def print_iface_header() -> None: def print_iface(iface: Interface) -> None: - iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else None - iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else None - print(f"{iface.id:<3} | {iface.mac:<11} | {iface_ip4:<18} | {iface_ip6}") + iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else "" + iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else "" + print(f"{iface.id:<3} | {iface.mac:<17} | {iface_ip4:<18} | {iface_ip6}") def query_sessions(args: Namespace) -> None: @@ -166,6 +166,11 @@ def query_session(args: Namespace) -> None: def query_node(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): + names = {} + response = core.get_session(args.id) + for node in response.session.nodes: + names[node.id] = node.name + response = core.get_node(args.id, args.node) if args.json: json = MessageToJson(response, preserving_proto_field_name=True) @@ -176,8 +181,17 @@ def query_node(args: Namespace) -> None: print("ID | Name | Type") print(f"{node.id:<4} | {node.name:<7} | {node_type}") print("Interfaces") + print("Connected To | ", end="") print_iface_header() for iface in response.ifaces: + if iface.net_id == node.id: + if iface.node_id: + name = names[iface.node_id] + else: + name = names[iface.net2_id] + else: + name = names[iface.net_id] + print(f"{name:<12} | ", end="") print_iface(iface) @@ -268,7 +282,7 @@ def add_link(args: Namespace) -> None: json = MessageToJson(response, preserving_proto_field_name=True) print(json) else: - print(f"edit link: {response.result}") + print(f"add link: {response.result}") def edit_link(args: Namespace) -> None: From 4a0fdf3307347065cb41052581efa673af08b106 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 15:21:33 -0700 Subject: [PATCH 091/210] core-cli: add function for printing protobuf responses as json --- daemon/scripts/core-cli | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index c4d97a8af..6fe83cf0f 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -7,7 +7,7 @@ from argparse import ( Namespace, _SubParsersAction, ) -from typing import Tuple +from typing import Any, Tuple import netaddr from google.protobuf.json_format import MessageToJson @@ -114,13 +114,17 @@ def print_iface(iface: Interface) -> None: print(f"{iface.id:<3} | {iface.mac:<17} | {iface_ip4:<18} | {iface_ip6}") +def print_json(message: Any) -> None: + json = MessageToJson(message, preserving_proto_field_name=True) + print(json) + + def query_sessions(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): response = core.get_sessions() if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print("Session ID | Session State | Nodes") for s in response.sessions: @@ -133,8 +137,7 @@ def query_session(args: Namespace) -> None: with core.context_connect(): response = core.get_session(args.id) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print("Nodes") print("Node ID | Node Name | Node Type") @@ -173,8 +176,7 @@ def query_node(args: Namespace) -> None: response = core.get_node(args.id, args.node) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: node = response.node node_type = NodeType.Enum.Name(node.type) @@ -221,8 +223,7 @@ def add_node(args: Namespace) -> None: ) response = core.add_node(session_id, node) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"created node: {response.node_id}") @@ -241,8 +242,7 @@ def edit_node(args: Namespace) -> None: with core.context_connect(): response = core.edit_node(session_id, args.id, pos, args.icon, geo) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"edit node: {response.result}") @@ -253,8 +253,7 @@ def delete_node(args: Namespace) -> None: with core.context_connect(): response = core.delete_node(session_id, args.id) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"deleted node: {response.result}") @@ -279,8 +278,7 @@ def add_link(args: Namespace) -> None: with core.context_connect(): response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"add link: {response.result}") @@ -301,8 +299,7 @@ def edit_link(args: Namespace) -> None: session_id, args.node1, args.node2, options, args.iface1, args.iface2 ) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"edit link: {response.result}") @@ -313,8 +310,7 @@ def delete_link(args: Namespace) -> None: with core.context_connect(): response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"delete link: {response.result}") From f22edd1d25b9d003385751e491c693989bdc956e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 16:16:58 -0700 Subject: [PATCH 092/210] grpc: fixed accidental breakage for get_session ptp links --- daemon/core/api/grpc/server.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index c447ee7cc..50b157710 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -543,10 +543,9 @@ def GetSession( nodes = [] for _id in session.nodes: node = session.nodes[_id] - if isinstance(node, PtpNet): - continue - node_proto = grpcutils.get_node_proto(session, node) - nodes.append(node_proto) + if not isinstance(node, PtpNet): + node_proto = grpcutils.get_node_proto(session, node) + nodes.append(node_proto) node_links = get_links(node) links.extend(node_links) From 537291b219e731454423fdd2c63775d8ce7c01ba Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 22:16:00 -0700 Subject: [PATCH 093/210] core-cli: added open xml command to a session xml and optionally start it --- daemon/scripts/core-cli | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 6fe83cf0f..e4c729960 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -7,6 +7,7 @@ from argparse import ( Namespace, _SubParsersAction, ) +from pathlib import Path from typing import Any, Tuple import netaddr @@ -79,6 +80,13 @@ def geo_type(value: str) -> Tuple[float, float, float]: return lon, lat, alt +def file_type(value: str) -> str: + path = Path(value) + if not path.is_file(): + raise ArgumentTypeError(f"invalid file: {value}") + return str(path.absolute()) + + def get_current_session() -> int: core = CoreGrpcClient() with core.context_connect(): @@ -119,6 +127,16 @@ def print_json(message: Any) -> None: print(json) +def open_xml(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.open_xml(args.file, args.start) + if args.json: + print_json(response) + else: + print(f"opened xml: {response.result}") + + def query_sessions(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): @@ -317,6 +335,7 @@ def delete_link(args: Namespace) -> None: def setup_node_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("node", help="node interactions") + parser.formatter_class = ArgumentDefaultsHelpFormatter parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="node commands") subparsers.required = True @@ -355,6 +374,7 @@ def setup_node_parser(parent: _SubParsersAction) -> None: def setup_link_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("link", help="link interactions") + parser.formatter_class = ArgumentDefaultsHelpFormatter parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="link commands") subparsers.required = True @@ -412,18 +432,29 @@ def setup_query_parser(parent: _SubParsersAction) -> None: subparsers.dest = "command" sessions_parser = subparsers.add_parser("sessions", help="query current sessions") + sessions_parser.formatter_class = ArgumentDefaultsHelpFormatter sessions_parser.set_defaults(func=query_sessions) session_parser = subparsers.add_parser("session", help="query session") + session_parser.formatter_class = ArgumentDefaultsHelpFormatter session_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) session_parser.set_defaults(func=query_session) node_parser = subparsers.add_parser("node", help="query node") + node_parser.formatter_class = ArgumentDefaultsHelpFormatter node_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) node_parser.add_argument("-n", "--node", type=int, help="node to query", required=True) node_parser.set_defaults(func=query_node) +def setup_xml_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("xml", help="open session xml") + parser.formatter_class = ArgumentDefaultsHelpFormatter + parser.add_argument("file", type=file_type, help="xml file to open") + parser.add_argument("-s", "--start", action="store_true", help="start the session?") + parser.set_defaults(func=open_xml) + + def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( @@ -435,6 +466,7 @@ def main() -> None: setup_node_parser(subparsers) setup_link_parser(subparsers) setup_query_parser(subparsers) + setup_xml_parser(subparsers) args = parser.parse_args() args.func(args) From 3477e84e9d029bed25fe4d6398e2a515cccb79bb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 09:30:05 -0700 Subject: [PATCH 094/210] core-cli: added wlan set/get config, fixed session option for node/link interactions --- daemon/core/api/grpc/server.py | 11 ++--- daemon/scripts/core-cli | 88 ++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 50b157710..aa5ec5394 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -1343,13 +1343,12 @@ def SetWlanConfig( """ logging.debug("set wlan config: %s", request) session = self.get_session(request.session_id, context) - wlan_config = request.wlan_config - session.mobility.set_model_config( - wlan_config.node_id, BasicRangeModel.name, wlan_config.config - ) + node_id = request.wlan_config.node_id + config = request.wlan_config.config + session.mobility.set_model_config(node_id, BasicRangeModel.name, config) if session.state == EventTypes.RUNTIME_STATE: - node = self.get_node(session, wlan_config.node_id, context, WlanNode) - node.updatemodel(wlan_config.config) + node = self.get_node(session, node_id, context, WlanNode) + node.updatemodel(config) return SetWlanConfigResponse(result=True) def GetEmaneConfig( diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index e4c729960..9dfadfccf 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -8,8 +8,9 @@ from argparse import ( _SubParsersAction, ) from pathlib import Path -from typing import Any, Tuple +from typing import Any, Optional, Tuple +import grpc import netaddr from google.protobuf.json_format import MessageToJson from netaddr import EUI, AddrFormatError, IPNetwork @@ -87,7 +88,9 @@ def file_type(value: str) -> str: return str(path.absolute()) -def get_current_session() -> int: +def get_current_session(session_id: Optional[int]) -> int: + if session_id: + return session_id core = CoreGrpcClient() with core.context_connect(): response = core.get_sessions() @@ -127,6 +130,50 @@ def print_json(message: Any) -> None: print(json) +def get_wlan_config(args: Namespace) -> None: + session_id = get_current_session(args.session) + core = CoreGrpcClient() + try: + with core.context_connect(): + response = core.get_wlan_config(session_id, args.node) + if args.json: + print_json(response) + else: + size = 0 + for option in response.config.values(): + size = max(size, len(option.name)) + print(f"{'Name':<{size}.{size}} | Value") + for option in response.config.values(): + print(f"{option.name:<{size}.{size}} | {option.value}") + except grpc.RpcError as e: + print(f"grpc error: {e.details()}") + + +def set_wlan_config(args: Namespace) -> None: + session_id = get_current_session(args.session) + config = {} + if args.bandwidth: + config["bandwidth"] = str(args.bandwidth) + if args.delay: + config["delay"] = str(args.delay) + if args.loss: + config["error"] = str(args.loss) + if args.jitter: + config["jitter"] = str(args.jitter) + if args.range: + config["range"] = str(args.range) + core = CoreGrpcClient() + try: + with core.context_connect(): + response = core.set_wlan_config(session_id, args.node, config) + if args.json: + print_json(response) + else: + print(f"set wlan config: {response.result}") + except grpc.RpcError as e: + print(f"grpc error: {e.details()}") + + def open_xml(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): @@ -216,7 +263,7 @@ def query_node(args: Namespace) -> None: def add_node(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) node_type = NodeType.Enum.Value(args.type) pos = None if args.pos: @@ -247,7 +294,7 @@ def add_node(args: Namespace) -> None: def edit_node(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) pos = None if args.pos: x, y = args.pos @@ -266,7 +313,7 @@ def edit_node(args: Namespace) -> None: def delete_node(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) core = CoreGrpcClient() with core.context_connect(): response = core.delete_node(session_id, args.id) @@ -277,7 +324,7 @@ def delete_node(args: Namespace) -> None: def add_link(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) iface1 = None if args.iface1_id is not None: iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) @@ -302,7 +349,7 @@ def add_link(args: Namespace) -> None: def edit_link(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -323,7 +370,7 @@ def edit_link(args: Namespace) -> None: def delete_link(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) core = CoreGrpcClient() with core.context_connect(): response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) @@ -455,6 +502,30 @@ def setup_xml_parser(parent: _SubParsersAction) -> None: parser.set_defaults(func=open_xml) +def setup_wlan_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("wlan", help="wlan specific interactions") + parser.formatter_class = ArgumentDefaultsHelpFormatter + parser.add_argument("-s", "--session", type=int, help="session to interact with") + subparsers = parser.add_subparsers(help="link commands") + subparsers.required = True + subparsers.dest = "command" + + get_parser = subparsers.add_parser("get", help="get wlan configuration") + get_parser.formatter_class = ArgumentDefaultsHelpFormatter + get_parser.add_argument("-n", "--node", type=int, help="wlan node", required=True) + get_parser.set_defaults(func=get_wlan_config) + + set_parser = subparsers.add_parser("set", help="set wlan configuration") + set_parser.formatter_class = ArgumentDefaultsHelpFormatter + set_parser.add_argument("-n", "--node", type=int, help="wlan node", required=True) + set_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") + set_parser.add_argument("-d", "--delay", type=int, help="delay (us)") + set_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") + set_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") + set_parser.add_argument("-r", "--range", type=int, help="range (pixels)") + set_parser.set_defaults(func=set_wlan_config) + + def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( @@ -467,6 +538,7 @@ def main() -> None: setup_link_parser(subparsers) setup_query_parser(subparsers) setup_xml_parser(subparsers) + setup_wlan_parser(subparsers) args = parser.parse_args() args.func(args) From 7a6c602369ebb44777df7502e63dac348b693b45 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 11:01:44 -0700 Subject: [PATCH 095/210] core-cli: cleaned up core client usage by way of a decorator, helps provide convenient grpc error catching --- daemon/scripts/core-cli | 378 ++++++++++++++++++++-------------------- 1 file changed, 186 insertions(+), 192 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 9dfadfccf..61b47ae4f 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -7,6 +7,7 @@ from argparse import ( Namespace, _SubParsersAction, ) +from functools import wraps from pathlib import Path from typing import Any, Optional, Tuple @@ -29,6 +30,19 @@ from core.api.grpc.core_pb2 import ( NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] +def coreclient(func): + @wraps(func) + def wrapper(*args, **kwargs): + core = CoreGrpcClient() + try: + with core.context_connect(): + return func(core, *args, **kwargs) + except grpc.RpcError as e: + print(f"grpc error: {e.details()}") + + return wrapper + + def mac_type(value: str) -> str: try: mac = EUI(value, dialect=netaddr.mac_unix_expanded) @@ -88,12 +102,10 @@ def file_type(value: str) -> str: return str(path.absolute()) -def get_current_session(session_id: Optional[int]) -> int: +def get_current_session(core: CoreGrpcClient, session_id: Optional[int]) -> int: if session_id: return session_id - core = CoreGrpcClient() - with core.context_connect(): - response = core.get_sessions() + response = core.get_sessions() if not response.sessions: print("no current session to interact with") sys.exit(1) @@ -130,27 +142,24 @@ def print_json(message: Any) -> None: print(json) -def get_wlan_config(args: Namespace) -> None: - session_id = get_current_session(args.session) - core = CoreGrpcClient() - try: - with core.context_connect(): - response = core.get_wlan_config(session_id, args.node) - if args.json: - print_json(response) - else: - size = 0 - for option in response.config.values(): - size = max(size, len(option.name)) - print(f"{'Name':<{size}.{size}} | Value") - for option in response.config.values(): - print(f"{option.name:<{size}.{size}} | {option.value}") - except grpc.RpcError as e: - print(f"grpc error: {e.details()}") - - -def set_wlan_config(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def get_wlan_config(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + response = core.get_wlan_config(session_id, args.node) + if args.json: + print_json(response) + else: + size = 0 + for option in response.config.values(): + size = max(size, len(option.name)) + print(f"{'Name':<{size}.{size}} | Value") + for option in response.config.values(): + print(f"{option.name:<{size}.{size}} | {option.value}") + + +@coreclient +def set_wlan_config(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) config = {} if args.bandwidth: config["bandwidth"] = str(args.bandwidth) @@ -162,108 +171,100 @@ def set_wlan_config(args: Namespace) -> None: config["jitter"] = str(args.jitter) if args.range: config["range"] = str(args.range) - core = CoreGrpcClient() - try: - with core.context_connect(): - response = core.set_wlan_config(session_id, args.node, config) - if args.json: - print_json(response) - else: - print(f"set wlan config: {response.result}") - except grpc.RpcError as e: - print(f"grpc error: {e.details()}") - - -def open_xml(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): - response = core.open_xml(args.file, args.start) - if args.json: - print_json(response) - else: - print(f"opened xml: {response.result}") - - -def query_sessions(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): - response = core.get_sessions() - if args.json: - print_json(response) - else: - print("Session ID | Session State | Nodes") - for s in response.sessions: - state = SessionState.Enum.Name(s.state) - print(f"{s.id:<10} | {state:<13} | {s.nodes}") - - -def query_session(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): - response = core.get_session(args.id) - if args.json: - print_json(response) - else: - print("Nodes") - print("Node ID | Node Name | Node Type") - names = {} - for node in response.session.nodes: - names[node.id] = node.name - node_type = NodeType.Enum.Name(node.type) - print(f"{node.id:<7} | {node.name:<9} | {node_type}") - - print("\nLinks") - for link in response.session.links: - n1 = names[link.node1_id] - n2 = names[link.node2_id] - print(f"Node | ", end="") - print_iface_header() - print(f"{n1:<6} | ", end="") - if link.HasField("iface1"): - print_iface(link.iface1) - else: - print() - print(f"{n2:<6} | ", end="") - if link.HasField("iface2"): - print_iface(link.iface2) - else: - print() - print() - - -def query_node(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): + response = core.set_wlan_config(session_id, args.node, config) + if args.json: + print_json(response) + else: + print(f"set wlan config: {response.result}") + + +@coreclient +def open_xml(core: CoreGrpcClient, args: Namespace) -> None: + response = core.open_xml(args.file, args.start) + if args.json: + print_json(response) + else: + print(f"opened xml: {response.result}") + + +@coreclient +def query_sessions(core: CoreGrpcClient, args: Namespace) -> None: + response = core.get_sessions() + if args.json: + print_json(response) + else: + print("Session ID | Session State | Nodes") + for s in response.sessions: + state = SessionState.Enum.Name(s.state) + print(f"{s.id:<10} | {state:<13} | {s.nodes}") + + +@coreclient +def query_session(core: CoreGrpcClient, args: Namespace) -> None: + response = core.get_session(args.id) + if args.json: + print_json(response) + else: + print("Nodes") + print("Node ID | Node Name | Node Type") names = {} - response = core.get_session(args.id) for node in response.session.nodes: names[node.id] = node.name - - response = core.get_node(args.id, args.node) - if args.json: - print_json(response) - else: - node = response.node node_type = NodeType.Enum.Name(node.type) - print("ID | Name | Type") - print(f"{node.id:<4} | {node.name:<7} | {node_type}") - print("Interfaces") - print("Connected To | ", end="") + print(f"{node.id:<7} | {node.name:<9} | {node_type}") + + print("\nLinks") + for link in response.session.links: + n1 = names[link.node1_id] + n2 = names[link.node2_id] + print(f"Node | ", end="") print_iface_header() - for iface in response.ifaces: - if iface.net_id == node.id: - if iface.node_id: - name = names[iface.node_id] - else: - name = names[iface.net2_id] + print(f"{n1:<6} | ", end="") + if link.HasField("iface1"): + print_iface(link.iface1) + else: + print() + print(f"{n2:<6} | ", end="") + if link.HasField("iface2"): + print_iface(link.iface2) + else: + print() + print() + + +@coreclient +def query_node(core: CoreGrpcClient, args: Namespace) -> None: + names = {} + response = core.get_session(args.id) + for node in response.session.nodes: + names[node.id] = node.name + + response = core.get_node(args.id, args.node) + if args.json: + print_json(response) + else: + node = response.node + node_type = NodeType.Enum.Name(node.type) + print("ID | Name | Type") + print(f"{node.id:<4} | {node.name:<7} | {node_type}") + print("Interfaces") + print("Connected To | ", end="") + print_iface_header() + for iface in response.ifaces: + if iface.net_id == node.id: + if iface.node_id: + name = names[iface.node_id] else: - name = names[iface.net_id] - print(f"{name:<12} | ", end="") - print_iface(iface) + name = names[iface.net2_id] + else: + name = names[iface.net_id] + print(f"{name:<12} | ", end="") + print_iface(iface) -def add_node(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def add_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) node_type = NodeType.Enum.Value(args.type) pos = None if args.pos: @@ -273,28 +274,27 @@ def add_node(args: Namespace) -> None: if args.geo: lon, lat, alt = args.geo geo = Geo(lon=lon, lat=lat, alt=alt) - core = CoreGrpcClient() - with core.context_connect(): - node = Node( - id=args.id, - name=args.name, - type=node_type, - model=args.model, - emane=args.emane, - icon=args.icon, - image=args.image, - position=pos, - geo=geo, - ) - response = core.add_node(session_id, node) - if args.json: - print_json(response) - else: - print(f"created node: {response.node_id}") - - -def edit_node(args: Namespace) -> None: - session_id = get_current_session(args.session) + node = Node( + id=args.id, + name=args.name, + type=node_type, + model=args.model, + emane=args.emane, + icon=args.icon, + image=args.image, + position=pos, + geo=geo, + ) + response = core.add_node(session_id, node) + if args.json: + print_json(response) + else: + print(f"created node: {response.node_id}") + + +@coreclient +def edit_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) pos = None if args.pos: x, y = args.pos @@ -303,28 +303,26 @@ def edit_node(args: Namespace) -> None: if args.geo: lon, lat, alt = args.geo geo = Geo(lon=lon, lat=lat, alt=alt) - core = CoreGrpcClient() - with core.context_connect(): - response = core.edit_node(session_id, args.id, pos, args.icon, geo) - if args.json: - print_json(response) - else: - print(f"edit node: {response.result}") - - -def delete_node(args: Namespace) -> None: - session_id = get_current_session(args.session) - core = CoreGrpcClient() - with core.context_connect(): - response = core.delete_node(session_id, args.id) - if args.json: - print_json(response) - else: - print(f"deleted node: {response.result}") - - -def add_link(args: Namespace) -> None: - session_id = get_current_session(args.session) + response = core.edit_node(session_id, args.id, pos, args.icon, geo) + if args.json: + print_json(response) + else: + print(f"edit node: {response.result}") + + +@coreclient +def delete_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + response = core.delete_node(session_id, args.id) + if args.json: + print_json(response) + else: + print(f"deleted node: {response.result}") + + +@coreclient +def add_link(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) iface1 = None if args.iface1_id is not None: iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) @@ -339,17 +337,16 @@ def add_link(args: Namespace) -> None: dup=args.duplicate, unidirectional=args.uni, ) - core = CoreGrpcClient() - with core.context_connect(): - response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) - if args.json: - print_json(response) - else: - print(f"add link: {response.result}") + response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) + if args.json: + print_json(response) + else: + print(f"add link: {response.result}") -def edit_link(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def edit_link(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -358,26 +355,23 @@ def edit_link(args: Namespace) -> None: dup=args.duplicate, unidirectional=args.uni, ) - core = CoreGrpcClient() - with core.context_connect(): - response = core.edit_link( - session_id, args.node1, args.node2, options, args.iface1, args.iface2 - ) - if args.json: - print_json(response) - else: - print(f"edit link: {response.result}") - - -def delete_link(args: Namespace) -> None: - session_id = get_current_session(args.session) - core = CoreGrpcClient() - with core.context_connect(): - response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) - if args.json: - print_json(response) - else: - print(f"delete link: {response.result}") + response = core.edit_link( + session_id, args.node1, args.node2, options, args.iface1, args.iface2 + ) + if args.json: + print_json(response) + else: + print(f"edit link: {response.result}") + + +@coreclient +def delete_link(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) + if args.json: + print_json(response) + else: + print(f"delete link: {response.result}") def setup_node_parser(parent: _SubParsersAction) -> None: From 08bbaf463b576270b617e16fbaa58996d3f1311d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 11:06:09 -0700 Subject: [PATCH 096/210] core-cli: updated xml command to use a flag argument to be consistent for now --- daemon/scripts/core-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 61b47ae4f..471facb30 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -491,7 +491,7 @@ def setup_query_parser(parent: _SubParsersAction) -> None: def setup_xml_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("xml", help="open session xml") parser.formatter_class = ArgumentDefaultsHelpFormatter - parser.add_argument("file", type=file_type, help="xml file to open") + parser.add_argument("-f", "--file", type=file_type, help="xml file to open", required=True) parser.add_argument("-s", "--start", action="store_true", help="start the session?") parser.set_defaults(func=open_xml) From a870c15b43a386a439ee6ef7815714d1a9bf4f07 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 12:11:34 -0700 Subject: [PATCH 097/210] pygui: fixed joining sessions with mobility players --- daemon/core/gui/coreclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index cf331676c..7cf8b1238 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -377,7 +377,7 @@ def join_session(self, session_id: int, query_location: bool = True) -> None: # organize canvas self.app.canvas.organize() - + self.show_mobility_players() # update ui to represent current state self.app.after(0, self.app.joined_session_update) From da9c0d066083e580482822cbe2c42ad08d55c036 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 14:40:19 -0700 Subject: [PATCH 098/210] daemon: initial changes to breakout custom interface creation for networks that require it, without being emane specific --- daemon/core/emane/nodes.py | 36 +++++++++++++++++++++++++++++++++--- daemon/core/nodes/base.py | 36 +++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 8cc9cd87a..be95e6d06 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -6,9 +6,10 @@ import logging from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type -from core.emulator.data import LinkData, LinkOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer from core.emulator.enumerations import ( + EventTypes, LinkTypes, MessageFlags, NodeTypes, @@ -16,7 +17,7 @@ TransportType, ) from core.errors import CoreError -from core.nodes.base import CoreNetworkBase +from core.nodes.base import CoreNetworkBase, CoreNode from core.nodes.interface import CoreInterface, TunTap if TYPE_CHECKING: @@ -47,7 +48,7 @@ class EmaneNet(CoreNetworkBase): apitype: NodeTypes = NodeTypes.EMANE linktype: LinkTypes = LinkTypes.WIRED type: str = "wlan" - is_emane: bool = True + has_custom_iface: bool = True def __init__( self, @@ -262,3 +263,32 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: if link: links.append(link) return links + + def custom_iface(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 + # namespace after it has been bound removes addressing; + # save addresses with the interface now + iface_id = node.newtuntap(iface_data.id, iface_data.name) + node.attachnet(iface_id, self) + iface = node.get_iface(iface_id) + iface.set_mac(iface_data.mac) + for ip in iface_data.get_ips(): + iface.add_ip(ip) + # TODO: if added during runtime start EMANE + if self.session.state == EventTypes.RUNTIME_STATE: + logging.info("startup emane for node: %s", node.name) + # create specific xml if needed + config = self.session.emane.get_iface_config( + self.model.id, iface, self.model.name + ) + if config: + self.model.build_xml_files(config, iface) + + # start emane daemon + + # install netif + + # add nem to nemfile + + return iface diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 05ec87dc9..039008ef6 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -812,30 +812,29 @@ def new_iface( :param iface_data: interface data for new interface :return: interface index """ - ips = iface_data.get_ips() with self.lock: - # TODO: emane specific code - if net.is_emane is True: - iface_id = self.newtuntap(iface_data.id, iface_data.name) - # TUN/TAP is not ready for addressing yet; the device may - # take some time to appear, and installing it into a - # namespace after it has been bound removes addressing; - # save addresses with the interface now - self.attachnet(iface_id, net) - iface = self.get_iface(iface_id) - iface.set_mac(iface_data.mac) - for ip in ips: - iface.add_ip(ip) + if net.has_custom_iface: + return net.custom_iface(self, iface_data) + # if net.is_emane is True: + # iface_id = self.newtuntap(iface_data.id, iface_data.name) + # # TUN/TAP is not ready for addressing yet; the device may + # # take some time to appear, and installing it into a + # # namespace after it has been bound removes addressing; + # # save addresses with the interface now + # self.attachnet(iface_id, net) + # iface = self.get_iface(iface_id) + # iface.set_mac(iface_data.mac) + # for ip in ips: + # iface.add_ip(ip) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) - for ip in ips: + for ip in iface_data.get_ips(): self.add_ip(iface_id, ip) self.ifup(iface_id) - iface = self.get_iface(iface_id) - return iface + return self.get_iface(iface_id) def addfile(self, srcname: str, filename: str) -> None: """ @@ -925,7 +924,7 @@ class CoreNetworkBase(NodeBase): """ linktype: LinkTypes = LinkTypes.WIRED - is_emane: bool = False + has_custom_iface: bool = False def __init__( self, @@ -990,6 +989,9 @@ def linkconfig( """ raise NotImplementedError + def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface: + raise NotImplementedError + def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: """ Return the interface that links this net with another net. From e549830e3342effb9ad5aaeea9d2972fa355902b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 15:20:53 -0700 Subject: [PATCH 099/210] core-cli: fix to avoid errors for querying nodes with peer to peer links, until there is a proper way to get the other ends node name --- daemon/scripts/core-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 471facb30..a75714712 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -257,7 +257,7 @@ def query_node(core: CoreGrpcClient, args: Namespace) -> None: else: name = names[iface.net2_id] else: - name = names[iface.net_id] + name = names.get(iface.net_id, "") print(f"{name:<12} | ", end="") print_iface(iface) From bd48e14348c05800930618aaa02f1bf616fb393c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 15:37:51 -0700 Subject: [PATCH 100/210] daemon: initial changes to rework logic to start emane for a given interface --- daemon/core/emane/commeffect.py | 19 +- daemon/core/emane/emanemanager.py | 279 +++++++++-------------- daemon/core/emane/emanemodel.py | 27 +-- daemon/core/emane/nodes.py | 66 +----- daemon/core/emulator/session.py | 4 +- daemon/core/xml/emanexml.py | 365 +++++++++++------------------- 6 files changed, 263 insertions(+), 497 deletions(-) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 610099f1a..100af9a7f 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -62,9 +62,7 @@ def configurations(cls) -> List[Configuration]: def config_groups(cls) -> List[ConfigGroup]: return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))] - def build_xml_files( - self, config: Dict[str, str], iface: CoreInterface = None - ) -> None: + def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: """ Build the necessary nem and commeffect XMLs in the given path. If an individual NEM has a nonstandard config, we need to build @@ -75,22 +73,25 @@ def build_xml_files( :param iface: interface for the emane node :return: nothing """ + # interface node + node = iface.node + # retrieve xml names - nem_name = emanexml.nem_file_name(self, iface) - shim_name = emanexml.shim_file_name(self, iface) + nem_name = emanexml.nem_file_name(iface) + shim_name = emanexml.shim_file_name(iface) # create and write nem document nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") transport_type = TransportType.VIRTUAL - if iface and iface.transport_type == TransportType.RAW: + if iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW - transport_file = emanexml.transport_file_name(self.id, transport_type) + transport_file = emanexml.transport_file_name(iface, transport_type) etree.SubElement(nem_element, "transport", definition=transport_file) # set shim configuration etree.SubElement(nem_element, "shim", definition=shim_name) - nem_file = os.path.join(self.session.session_dir, nem_name) + nem_file = os.path.join(node.nodedir, nem_name) emanexml.create_file(nem_element, "nem", nem_file) # create and write shim document @@ -111,7 +112,7 @@ def build_xml_files( if ff.strip() != "": emanexml.add_param(shim_element, "filterfile", ff) - shim_file = os.path.join(self.session.session_dir, shim_name) + shim_file = os.path.join(node.nodedir, shim_name) emanexml.create_file(shim_element, "shim", shim_file) def linkconfig( diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index fc561b5f4..3317a5db1 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -6,6 +6,7 @@ import os import threading from collections import OrderedDict +from enum import Enum from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type from core import utils @@ -25,11 +26,11 @@ LinkTypes, MessageFlags, RegisterTlvs, + TransportType, ) from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode, NodeBase -from core.nodes.interface import CoreInterface -from core.nodes.network import CtrlNet +from core.nodes.interface import CoreInterface, TunTap from core.nodes.physical import Rj45Node from core.xml import emanexml @@ -63,6 +64,12 @@ DEFAULT_DEV = "ctrl0" +class EmaneState(Enum): + SUCCESS = 0 + NOT_NEEDED = 1 + NOT_READY = 2 + + class EmaneManager(ModelManager): """ EMANE controller object. Lives in a Session instance and is used for @@ -72,8 +79,6 @@ class EmaneManager(ModelManager): name: str = "emane" config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER - SUCCESS: int = 0 - NOT_NEEDED: int = 1 NOT_READY: int = 2 EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG" DEFAULT_LOG_LEVEL: int = 3 @@ -87,6 +92,7 @@ def __init__(self, session: "Session") -> None: """ super().__init__() self.session: "Session" = session + self.nems: Dict[int, CoreInterface] = {} self._emane_nets: Dict[int, EmaneNet] = {} self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters @@ -111,46 +117,47 @@ def __init__(self, session: "Session") -> None: self.event_device: Optional[str] = None self.emane_check() + def next_nem_id(self) -> int: + nem_id = int(self.get_config("nem_id_start")) + while nem_id in self.nems: + nem_id += 1 + return nem_id + def get_iface_config( - self, node_id: int, iface: CoreInterface, model_name: str + self, emane_net: EmaneNet, iface: CoreInterface ) -> Dict[str, str]: """ - Retrieve interface configuration or node configuration if not provided. + Retrieve configuration for a given interface. - :param node_id: node id - :param iface: node interface - :param model_name: model to get configuration for - :return: node/interface model configuration + :param emane_net: emane network the interface is connected to + :param iface: interface running emane + :return: net, node, or interface model configuration """ + model_name = emane_net.model.name # use the network-wide config values or interface(NEM)-specific values? if iface is None: - return self.get_configs(node_id=node_id, config_type=model_name) + return self.get_configs(node_id=emane_net.id, config_type=model_name) else: # don"t use default values when interface config is the same as net # note here that using iface.node.id as key allows for only one type # of each model per node; # TODO: use both node and interface as key - # Adamson change: first check for iface config keyed by "node:iface.name" # (so that nodes w/ multiple interfaces of same conftype can have # different configs for each separate interface) key = 1000 * iface.node.id if iface.node_id is not None: key += iface.node_id - # try retrieve interface specific configuration, avoid getting defaults config = self.get_configs(node_id=key, config_type=model_name) - # otherwise retrieve the interfaces node configuration, avoid using defaults if not config: config = self.get_configs(node_id=iface.node.id, config_type=model_name) - # get non interface config, when none found if not config: # with EMANE 0.9.2+, we need an extra NEM XML from # model.buildnemxmlfiles(), so defaults are returned here - config = self.get_configs(node_id=node_id, config_type=model_name) - + config = self.get_configs(node_id=emane_net.id, config_type=model_name) return config def config_reset(self, node_id: int = None) -> None: @@ -260,14 +267,13 @@ def getnodes(self) -> Set[CoreNode]: Return a set of CoreNodes that are linked to an EMANE network, e.g. containers having one or more radio interfaces. """ - # assumes self._objslock already held nodes = set() for emane_net in self._emane_nets.values(): for iface in emane_net.get_ifaces(): nodes.add(iface.node) return nodes - def setup(self) -> int: + def setup(self) -> EmaneState: """ Setup duties for EMANE manager. @@ -288,7 +294,7 @@ def setup(self) -> int: if not self._emane_nets: logging.debug("no emane nodes in session") - return EmaneManager.NOT_NEEDED + return EmaneState.NOT_NEEDED # check if bindings were installed if EventService is None: @@ -304,7 +310,7 @@ def setup(self) -> int: "EMANE cannot start, check core config. invalid OTA device provided: %s", otadev, ) - return EmaneManager.NOT_READY + return EmaneState.NOT_READY self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False @@ -319,16 +325,16 @@ def setup(self) -> int: "EMANE cannot start, check core config. invalid event service device: %s", eventdev, ) - return EmaneManager.NOT_READY + return EmaneState.NOT_READY self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False ) self.check_node_models() - return EmaneManager.SUCCESS + return EmaneState.SUCCESS - def startup(self) -> int: + def startup(self) -> EmaneState: """ After all the EMANE networks have been added, build XML files and start the daemons. @@ -337,39 +343,49 @@ def startup(self) -> int: instantiation """ self.reset() - r = self.setup() - - # NOT_NEEDED or NOT_READY - if r != EmaneManager.SUCCESS: - return r - - nems = [] + status = self.setup() + if status != EmaneState.SUCCESS: + return status + self.starteventmonitor() + self.buildeventservicexml() with self._emane_node_lock: - self.buildxml() - self.starteventmonitor() - - if self.numnems() > 0: - self.startdaemons() - self.install_ifaces() - - for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] - for iface in emane_node.get_ifaces(): - nems.append( - (iface.node.name, iface.name, emane_node.getnemid(iface)) + # on master, control network bridge added earlier in startup() + control_net = self.session.add_remove_control_net( + 0, remove=False, conf_required=False + ) + logging.info("emane building xmls...") + for node_id in sorted(self._emane_nets): + emane_net = self._emane_nets[node_id] + if not emane_net.model: + logging.error("emane net(%s) has no model", emane_net.name) + continue + for iface in emane_net.get_ifaces(): + if not iface.node: + logging.error( + "emane net(%s) connected interface missing node", + emane_net.name, + ) + continue + nem_id = self.next_nem_id() + self.nems[nem_id] = iface + self.write_nem(iface, nem_id) + emanexml.build_platform_xml( + self, control_net, emane_net, iface, nem_id ) - - if nems: - emane_nems_filename = os.path.join(self.session.session_dir, "emane_nems") - try: - with open(emane_nems_filename, "w") as f: - for nodename, ifname, nemid in nems: - f.write(f"{nodename} {ifname} {nemid}\n") - except IOError: - logging.exception("Error writing EMANE NEMs file: %s") + emanexml.build_model_xmls(self, emane_net, iface) + self.start_daemon(iface) + self.install_iface(emane_net, iface) if self.links_enabled(): self.link_monitor.start() - return EmaneManager.SUCCESS + return EmaneState.SUCCESS + + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: + path = os.path.join(self.session.session_dir, "emane_nems") + try: + with open(path, "a") as f: + f.write(f"{iface.node.name} {iface.name} {nem_id}\n") + except IOError: + logging.exception("error writing to emane nem file") def links_enabled(self) -> bool: return self.get_config("link_enabled") == "1" @@ -380,17 +396,14 @@ def poststartup(self) -> None: """ if not self.genlocationevents(): return - with self._emane_node_lock: - for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] + for node_id in sorted(self._emane_nets): + emane_net = self._emane_nets[node_id] logging.debug( - "post startup for emane node: %s - %s", - emane_node.id, - emane_node.name, + "post startup for emane node: %s - %s", emane_net.id, emane_net.name ) - emane_node.model.post_startup() - for iface in emane_node.get_ifaces(): + emane_net.model.post_startup() + for iface in emane_net.get_ifaces(): iface.setposition() def reset(self) -> None: @@ -400,13 +413,7 @@ def reset(self) -> None: """ with self._emane_node_lock: self._emane_nets.clear() - - self.platformport = self.session.options.get_config_int( - "emane_platform_port", 8100 - ) - self.transformport = self.session.options.get_config_int( - "emane_transform_port", 8200 - ) + self.nems.clear() def shutdown(self) -> None: """ @@ -422,40 +429,23 @@ def shutdown(self) -> None: self.stopdaemons() self.stopeventmonitor() - def buildxml(self) -> None: - """ - Build XML files required to run EMANE on each node. - NEMs run inside containers using the control network for passing - events and data. - """ - # assume self._objslock is already held here - logging.info("emane building xml...") - # on master, control network bridge added earlier in startup() - ctrlnet = self.session.add_remove_control_net( - net_index=0, remove=False, conf_required=False - ) - self.buildplatformxml(ctrlnet) - self.buildnemxml() - self.buildeventservicexml() - def check_node_models(self) -> None: """ Associate EMANE model classes with EMANE network nodes. """ for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] + emane_net = self._emane_nets[node_id] logging.debug("checking emane model for node: %s", node_id) # skip nodes that already have a model set - if emane_node.model: + if emane_net.model: logging.debug( - "node(%s) already has model(%s)", - emane_node.id, - emane_node.model.name, + "node(%s) already has model(%s)", emane_net.id, emane_net.model.name ) continue - # set model configured for node, due to legacy messaging configuration before nodes exist + # set model configured for node, due to legacy messaging configuration + # before nodes exist model_name = self.node_models.get(node_id) if not model_name: logging.error("emane node(%s) has no node model", node_id) @@ -464,7 +454,7 @@ def check_node_models(self) -> None: config = self.get_model_config(node_id=node_id, model_name=model_name) logging.debug("setting emane model(%s) config(%s)", model_name, config) model_class = self.models[model_name] - emane_node.setmodel(model_class, config) + emane_net.setmodel(model_class, config) def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]: """ @@ -473,7 +463,6 @@ def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]] """ emane_node = None iface = None - for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] iface = emane_node.get_nem_iface(nemid) @@ -481,7 +470,6 @@ def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]] break else: emane_node = None - return emane_node, iface def get_nem_link( @@ -507,38 +495,6 @@ def get_nem_link( color=color, ) - def numnems(self) -> int: - """ - Return the number of NEMs emulated locally. - """ - count = 0 - for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] - count += len(emane_node.ifaces) - return count - - def buildplatformxml(self, ctrlnet: CtrlNet) -> None: - """ - Build a platform.xml file now that all nodes are configured. - """ - nemid = int(self.get_config("nem_id_start")) - platform_xmls = {} - - # assume self._objslock is already held here - for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] - nemid = emanexml.build_node_platform_xml( - self, ctrlnet, emane_node, nemid, platform_xmls - ) - - def buildnemxml(self) -> None: - """ - Builds the nem, mac, and phy xml files for each EMANE network. - """ - for key in sorted(self._emane_nets): - emane_net = self._emane_nets[key] - emanexml.build_xml_files(self, emane_net) - def buildeventservicexml(self) -> None: """ Build the libemaneeventservice.xml file if event service options @@ -571,7 +527,7 @@ def buildeventservicexml(self) -> None: ) ) - def startdaemons(self) -> None: + def start_daemon(self, iface: CoreInterface) -> None: """ Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. @@ -583,69 +539,51 @@ def startdaemons(self) -> None: if cfgloglevel: logging.info("setting user-defined EMANE log level: %d", cfgloglevel) loglevel = str(cfgloglevel) - emanecmd = f"emane -d -l {loglevel}" if realtime: emanecmd += " -r" - otagroup, _otaport = self.get_config("otamanagergroup").split(":") otadev = self.get_config("otamanagerdevice") otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.get_config("eventservicegroup").split(":") eventdev = self.get_config("eventservicedevice") eventservicenetidx = self.session.get_control_net_index(eventdev) - - run_emane_on_host = False - for node in self.getnodes(): - if isinstance(node, Rj45Node): - run_emane_on_host = True - continue - path = self.session.session_dir - n = node.id - + node = iface.node + if not isinstance(node, Rj45Node): # control network not yet started here self.session.add_remove_control_iface( node, 0, remove=False, conf_required=False ) - if otanetidx > 0: logging.info("adding ota device ctrl%d", otanetidx) self.session.add_remove_control_iface( node, otanetidx, remove=False, conf_required=False ) - if eventservicenetidx >= 0: logging.info("adding event service device ctrl%d", eventservicenetidx) self.session.add_remove_control_iface( node, eventservicenetidx, remove=False, conf_required=False ) - # multicast route is needed for OTA data node.node_net_client.create_route(otagroup, otadev) - # multicast route is also needed for event data if on control network if eventservicenetidx >= 0 and eventgroup != otagroup: node.node_net_client.create_route(eventgroup, eventdev) - # start emane - log_file = os.path.join(path, f"emane{n}.log") - platform_xml = os.path.join(path, f"platform{n}.xml") + log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log") + platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml") args = f"{emanecmd} -f {log_file} {platform_xml}" output = node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) logging.debug("node(%s) emane daemon output: %s", node.name, output) - - if not run_emane_on_host: - return - - path = self.session.session_dir - log_file = os.path.join(path, "emane.log") - platform_xml = os.path.join(path, "platform.xml") - emanecmd += f" -f {log_file} {platform_xml}" - utils.cmd(emanecmd, cwd=path) - self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path)) - logging.info("host emane daemon running: %s", emanecmd) + else: + path = self.session.session_dir + log_file = os.path.join(path, f"{iface.name}-emane.log") + platform_xml = os.path.join(path, f"{iface.name}-platform.xml") + emanecmd += f" -f {log_file} {platform_xml}" + utils.cmd(emanecmd, cwd=path) + self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path)) + logging.info("host emane daemon running: %s", emanecmd) def stopdaemons(self) -> None: """ @@ -674,23 +612,27 @@ def stopdaemons(self) -> None: except CoreCommandError: logging.exception("error shutting down emane daemons") - def install_ifaces(self) -> None: - """ - Install TUN/TAP virtual interfaces into their proper namespaces - now that the EMANE daemons are running. - """ - for key in sorted(self._emane_nets.keys()): - node = self._emane_nets[key] - logging.info("emane install interface for node(%s): %d", node.name, key) - node.install_ifaces() + def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: + config = self.get_iface_config(emane_net, iface) + external = config.get("external", "0") + if isinstance(iface, TunTap) and external == "0": + iface.set_ips() + # at this point we register location handlers for generating + # EMANE location events + if self.genlocationevents(): + iface.poshook = emane_net.setnemposition + iface.setposition() def deinstall_ifaces(self) -> None: """ Uninstall TUN/TAP virtual interfaces. """ - for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] - emane_node.deinstall_ifaces() + for key in sorted(self._emane_nets): + emane_net = self._emane_nets[key] + for iface in emane_net.get_ifaces(): + if iface.transport_type == TransportType.VIRTUAL: + iface.shutdown() + iface.poshook = None def doeventmonitor(self) -> bool: """ @@ -718,7 +660,6 @@ def starteventmonitor(self) -> None: logging.info("emane start event monitor") if not self.doeventmonitor(): return - if self.service is None: logging.error( "Warning: EMANE events will not be generated " diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 43fbc0fba..0576d6c37 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -96,9 +96,7 @@ def config_groups(cls) -> List[ConfigGroup]: ConfigGroup("External Parameters", phy_len + 1, config_len), ] - def build_xml_files( - self, config: Dict[str, str], iface: CoreInterface = None - ) -> 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 both mac.xml and phy.xml definitions. @@ -107,33 +105,30 @@ def build_xml_files( :param iface: interface for the emane node :return: nothing """ - nem_name = emanexml.nem_file_name(self, iface) - mac_name = emanexml.mac_file_name(self, iface) - phy_name = emanexml.phy_file_name(self, iface) - - # remote server for file - server = None - if iface is not None: - server = iface.node.server + nem_name = emanexml.nem_file_name(iface) + mac_name = emanexml.mac_file_name(iface) + phy_name = emanexml.phy_file_name(iface) # check if this is external transport_type = TransportType.VIRTUAL - if iface and iface.transport_type == TransportType.RAW: + if iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW - transport_name = emanexml.transport_file_name(self.id, transport_type) + transport_name = emanexml.transport_file_name(iface, transport_type) + node = iface.node + server = node.server # create nem xml file - nem_file = os.path.join(self.session.session_dir, nem_name) + nem_file = os.path.join(node.nodedir, nem_name) emanexml.create_nem_xml( self, config, nem_file, transport_name, mac_name, phy_name, server ) # create mac xml file - mac_file = os.path.join(self.session.session_dir, mac_name) + mac_file = os.path.join(node.nodedir, mac_name) emanexml.create_mac_xml(self, config, mac_file, server) # create phy xml file - phy_file = os.path.join(self.session.session_dir, phy_name) + phy_file = os.path.join(node.nodedir, phy_name) emanexml.create_phy_xml(self, config, phy_file, server) def post_startup(self) -> None: diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index be95e6d06..dca857856 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -8,17 +8,10 @@ from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.enumerations import ( - EventTypes, - LinkTypes, - MessageFlags, - NodeTypes, - RegisterTlvs, - TransportType, -) +from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs from core.errors import CoreError from core.nodes.base import CoreNetworkBase, CoreNode -from core.nodes.interface import CoreInterface, TunTap +from core.nodes.interface import CoreInterface if TYPE_CHECKING: from core.emane.emanemodel import EmaneModel @@ -139,45 +132,6 @@ def get_nem_iface(self, nemid: int) -> Optional[CoreInterface]: return iface return None - def install_ifaces(self) -> None: - """ - Install TAP devices into their namespaces. This is done after - EMANE daemons have been started, because that is their only chance - to bind to the TAPs. - """ - if ( - self.session.emane.genlocationevents() - and self.session.emane.service is None - ): - warntxt = "unable to publish EMANE events because the eventservice " - warntxt += "Python bindings failed to load" - logging.error(warntxt) - for iface in self.get_ifaces(): - config = self.session.emane.get_iface_config( - self.id, iface, self.model.name - ) - external = config.get("external", "0") - if isinstance(iface, TunTap) and external == "0": - iface.set_ips() - if not self.session.emane.genlocationevents(): - iface.poshook = None - continue - # at this point we register location handlers for generating - # EMANE location events - iface.poshook = self.setnemposition - iface.setposition() - - def deinstall_ifaces(self) -> None: - """ - Uninstall TAP devices. This invokes their shutdown method for - any required cleanup; the device may be actually removed when - emanetransportd terminates. - """ - for iface in self.get_ifaces(): - if iface.transport_type == TransportType.VIRTUAL: - iface.shutdown() - iface.poshook = None - def _nem_position( self, iface: CoreInterface ) -> Optional[Tuple[int, float, float, float]]: @@ -275,20 +229,4 @@ def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterfa iface.set_mac(iface_data.mac) for ip in iface_data.get_ips(): iface.add_ip(ip) - # TODO: if added during runtime start EMANE - if self.session.state == EventTypes.RUNTIME_STATE: - logging.info("startup emane for node: %s", node.name) - # create specific xml if needed - config = self.session.emane.get_iface_config( - self.model.id, iface, self.model.name - ) - if config: - self.model.build_xml_files(config, iface) - - # start emane daemon - - # install netif - - # add nem to nemfile - return iface diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index c2573578e..9f5364b90 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -15,7 +15,7 @@ from core import constants, utils from core.configservice.manager import ConfigServiceManager -from core.emane.emanemanager import EmaneManager +from core.emane.emanemanager import EmaneManager, EmaneState from core.emane.nodes import EmaneNet from core.emulator.data import ( ConfigData, @@ -1181,7 +1181,7 @@ def instantiate(self) -> List[Exception]: self.distributed.start() # instantiate will be invoked again upon emane configure - if self.emane.startup() == self.emane.NOT_READY: + if self.emane.startup() == EmaneState.NOT_READY: return [] # boot node services and then start mobility diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index eece57c99..32ca0f67c 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -10,6 +10,7 @@ from core.emane.nodes import EmaneNet from core.emulator.distributed import DistributedServer from core.emulator.enumerations import TransportType +from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet from core.xml import corexml @@ -40,15 +41,11 @@ def _value_to_params(value: str) -> Optional[Tuple[str]]: """ try: values = utils.make_tuple_fromstr(value, str) - if not hasattr(values, "__iter__"): return None - if len(values) < 2: return None - return values - except SyntaxError: logging.exception("error in value string to param list") return None @@ -127,13 +124,13 @@ def add_configurations( add_param(xml_element, name, value) -def build_node_platform_xml( +def build_platform_xml( emane_manager: "EmaneManager", control_net: CtrlNet, - node: EmaneNet, + emane_net: EmaneNet, + iface: CoreInterface, nem_id: int, - platform_xmls: Dict[str, etree.Element], -) -> int: +) -> None: """ Create platform xml for a specific node. @@ -141,175 +138,121 @@ def build_node_platform_xml( configurations :param control_net: control net node for this emane network - :param node: node to write platform xml for - :param nem_id: nem id to use for interfaces for this node - :param platform_xmls: stores platform xml elements to append nem entries to + :param emane_net: emane network associated with interface + :param iface: interface running emane + :param nem_id: nem id to use for this interface :return: the next nem id that can be used for creating platform xml files """ - logging.debug( - "building emane platform xml for node(%s) nem_id(%s): %s", - node, - nem_id, - node.name, + # build nem xml + nem_definition = nem_file_name(iface) + nem_element = etree.Element( + "nem", id=str(nem_id), name=iface.localname, definition=nem_definition ) - nem_entries = {} - - if node.model is None: - logging.warning("warning: EMANE network %s has no associated model", node.name) - return nem_id - for iface in node.get_ifaces(): - logging.debug( - "building platform xml for interface(%s) nem_id(%s)", iface.name, nem_id - ) - # build nem xml - nem_definition = nem_file_name(node.model, iface) - nem_element = etree.Element( - "nem", id=str(nem_id), name=iface.localname, definition=nem_definition + # check if this is an external transport, get default config if an interface + # specific one does not exist + config = emane_manager.get_iface_config(emane_net, iface) + if is_external(config): + nem_element.set("transport", "external") + platform_endpoint = "platformendpoint" + add_param(nem_element, platform_endpoint, config[platform_endpoint]) + transport_endpoint = "transportendpoint" + add_param(nem_element, transport_endpoint, config[transport_endpoint]) + else: + # build transport xml + transport_type = iface.transport_type + if not transport_type: + logging.info("warning: %s interface type unsupported!", iface.name) + transport_type = TransportType.RAW + transport_file = transport_file_name(iface, transport_type) + transport_element = etree.SubElement( + nem_element, "transport", definition=transport_file ) - - # check if this is an external transport, get default config if an interface - # specific one does not exist - config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) - - if is_external(config): - nem_element.set("transport", "external") - platform_endpoint = "platformendpoint" - add_param(nem_element, platform_endpoint, config[platform_endpoint]) - transport_endpoint = "transportendpoint" - add_param(nem_element, transport_endpoint, config[transport_endpoint]) - else: - # build transport xml - transport_type = iface.transport_type - if not transport_type: - logging.info("warning: %s interface type unsupported!", iface.name) - transport_type = TransportType.RAW - transport_file = transport_file_name(node.id, transport_type) - transport_element = etree.SubElement( - nem_element, "transport", definition=transport_file - ) - - # add transport parameter - add_param(transport_element, "device", iface.name) - - # add nem entry - nem_entries[iface] = nem_element - - # merging code - key = iface.node.id - if iface.transport_type == TransportType.RAW: - key = "host" - otadev = control_net.brname - eventdev = control_net.brname - else: - otadev = None - eventdev = None - - platform_element = platform_xmls.get(key) - if platform_element is None: - platform_element = etree.Element("platform") - - if otadev: - emane_manager.set_config("otamanagerdevice", otadev) - - if eventdev: - emane_manager.set_config("eventservicedevice", eventdev) - - # append all platform options (except starting id) to doc - for configuration in emane_manager.emane_config.emulator_config: - name = configuration.id - if name == "platform_id_start": - continue - - value = emane_manager.get_config(name) - add_param(platform_element, name, value) - - # add platform xml - platform_xmls[key] = platform_element - - platform_element.append(nem_element) - - node.setnemid(iface, nem_id) - mac = _MAC_PREFIX + ":00:00:" - mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.set_mac(mac) - - # increment nem id - nem_id += 1 + add_param(transport_element, "device", iface.name) + + # determine platform element to add xml to + key = iface.node.id + if iface.transport_type == TransportType.RAW: + key = "host" + otadev = control_net.brname + eventdev = control_net.brname + else: + otadev = None + eventdev = None + platform_element = etree.Element("platform") + if otadev: + emane_manager.set_config("otamanagerdevice", otadev) + if eventdev: + emane_manager.set_config("eventservicedevice", eventdev) + for configuration in emane_manager.emane_config.emulator_config: + name = configuration.id + value = emane_manager.get_config(name) + add_param(platform_element, name, value) + platform_element.append(nem_element) + emane_net.setnemid(iface, nem_id) + mac = _MAC_PREFIX + ":00:00:" + mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" + iface.set_mac(mac) doc_name = "platform" - for key in sorted(platform_xmls.keys()): - platform_element = platform_xmls[key] - if key == "host": - file_name = "platform.xml" - file_path = os.path.join(emane_manager.session.session_dir, file_name) - create_file(platform_element, doc_name, file_path) - else: - file_name = f"platform{key}.xml" - file_path = os.path.join(emane_manager.session.session_dir, file_name) - linked_node = emane_manager.session.nodes[key] - create_file(platform_element, doc_name, file_path, linked_node.server) - - return nem_id + server = None + if key == "host": + file_name = "platform.xml" + file_path = os.path.join(emane_manager.session.session_dir, file_name) + else: + node = iface.node + file_name = f"{iface.name}-platform.xml" + file_path = os.path.join(node.nodedir, file_name) + server = node.server + create_file(platform_element, doc_name, file_path, server) -def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None: +def build_model_xmls( + manager: "EmaneManager", emane_net: EmaneNet, iface: CoreInterface +) -> None: """ Generate emane xml files required for node. - :param emane_manager: emane manager with emane + :param manager: emane manager with emane configurations - :param node: node to write platform xml for + :param emane_net: emane network associated with interface + :param iface: interface to create emane xml for :return: nothing """ - logging.debug("building all emane xml for node(%s): %s", node, node.name) - if node.model is None: - return - - # get model configurations - config = emane_manager.get_configs(node.model.id, node.model.name) - if not config: - return - - # build XML for overall network EMANE configs - node.model.build_xml_files(config) - # build XML for specific interface (NEM) configs + # check for interface specific emane configuration and write xml files + config = manager.get_iface_config(emane_net, iface) + emane_net.model.build_xml_files(config, iface) + + # check transport type needed for interface need_virtual = False need_raw = False vtype = TransportType.VIRTUAL rtype = TransportType.RAW - - for iface in node.get_ifaces(): - # check for interface specific emane configuration and write xml files - config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) - if config: - node.model.build_xml_files(config, iface) - - # check transport type needed for interface - if iface.transport_type == TransportType.VIRTUAL: - need_virtual = True - vtype = iface.transport_type - else: - need_raw = True - rtype = iface.transport_type - + if iface.transport_type == TransportType.VIRTUAL: + need_virtual = True + vtype = iface.transport_type + else: + need_raw = True + rtype = iface.transport_type if need_virtual: - build_transport_xml(emane_manager, node, vtype) - + build_transport_xml(manager, emane_net, iface, vtype) if need_raw: - build_transport_xml(emane_manager, node, rtype) + build_transport_xml(manager, emane_net, iface, rtype) def build_transport_xml( - emane_manager: "EmaneManager", node: EmaneNet, transport_type: TransportType + manager: "EmaneManager", + emane_net: EmaneNet, + iface: CoreInterface, + transport_type: TransportType, ) -> None: """ Build transport xml file for node and transport type. - :param emane_manager: emane manager with emane - configurations - :param node: node to write platform xml for + :param manager: emane manager with emane configurations + :param emane_net: emane network associated with interface + :param iface: interface to build transport xml for :param transport_type: transport type to build xml for :return: nothing """ @@ -318,28 +261,24 @@ def build_transport_xml( name=f"{transport_type.value.capitalize()} Transport", library=f"trans{transport_type.value.lower()}", ) - - # add bitrate add_param(transport_element, "bitrate", "0") # get emane model cnfiguration - config = emane_manager.get_configs(node.id, node.model.name) + config = manager.get_iface_config(emane_net, iface) flowcontrol = config.get("flowcontrolenable", "0") == "1" - if transport_type == TransportType.VIRTUAL: device_path = "/dev/net/tun_flowctl" if not os.path.exists(device_path): device_path = "/dev/net/tun" add_param(transport_element, "devicepath", device_path) - if flowcontrol: add_param(transport_element, "flowcontrolenable", "on") - doc_name = "transport" - file_name = transport_file_name(node.id, transport_type) - file_path = os.path.join(emane_manager.session.session_dir, file_name) + node = iface.node + file_name = transport_file_name(iface, transport_type) + file_path = os.path.join(node.nodedir, file_name) create_file(transport_element, doc_name, file_path) - emane_manager.session.distributed.execute( + manager.session.distributed.execute( lambda x: create_file(transport_element, doc_name, file_path, x) ) @@ -348,7 +287,7 @@ def create_phy_xml( emane_model: "EmaneModel", config: Dict[str, str], file_path: str, - server: DistributedServer, + server: Optional[DistributedServer], ) -> None: """ Create the phy xml document. @@ -363,25 +302,17 @@ def create_phy_xml( phy_element = etree.Element("phy", name=f"{emane_model.name} PHY") if emane_model.phy_library: phy_element.set("library", emane_model.phy_library) - add_configurations( phy_element, emane_model.phy_config, config, emane_model.config_ignore ) - create_file(phy_element, "phy", file_path) - if server is not None: - create_file(phy_element, "phy", file_path, server) - else: - create_file(phy_element, "phy", file_path) - emane_model.session.distributed.execute( - lambda x: create_file(phy_element, "phy", file_path, x) - ) + create_file(phy_element, "phy", file_path, server) def create_mac_xml( emane_model: "EmaneModel", config: Dict[str, str], file_path: str, - server: DistributedServer, + server: Optional[DistributedServer], ) -> None: """ Create the mac xml document. @@ -394,22 +325,14 @@ def create_mac_xml( :return: nothing """ if not emane_model.mac_library: - raise ValueError("must define emane model library") - + raise CoreError("must define emane model library") mac_element = etree.Element( "mac", name=f"{emane_model.name} MAC", library=emane_model.mac_library ) add_configurations( mac_element, emane_model.mac_config, config, emane_model.config_ignore ) - create_file(mac_element, "mac", file_path) - if server is not None: - create_file(mac_element, "mac", file_path, server) - else: - create_file(mac_element, "mac", file_path) - emane_model.session.distributed.execute( - lambda x: create_file(mac_element, "mac", file_path, x) - ) + create_file(mac_element, "mac", file_path, server) def create_nem_xml( @@ -419,7 +342,7 @@ def create_nem_xml( transport_definition: str, mac_definition: str, phy_definition: str, - server: DistributedServer, + server: Optional[DistributedServer], ) -> None: """ Create the nem xml document. @@ -441,13 +364,7 @@ def create_nem_xml( etree.SubElement(nem_element, "transport", definition=transport_definition) etree.SubElement(nem_element, "mac", definition=mac_definition) etree.SubElement(nem_element, "phy", definition=phy_definition) - if server is not None: - create_file(nem_element, "nem", nem_file, server) - else: - create_file(nem_element, "nem", nem_file) - emane_model.session.distributed.execute( - lambda x: create_file(nem_element, "nem", nem_file, x) - ) + create_file(nem_element, "nem", nem_file, server) def create_event_service_xml( @@ -483,81 +400,55 @@ def create_event_service_xml( create_file(event_element, "emaneeventmsgsvc", file_path, server) -def transport_file_name(node_id: int, transport_type: TransportType) -> str: +def transport_file_name(iface: CoreInterface, transport_type: TransportType) -> str: """ Create name for a transport xml file. - :param node_id: node id to generate transport file name for + :param iface: interface running emane :param transport_type: transport type to generate transport file - :return: - """ - return f"n{node_id}trans{transport_type.value}.xml" - - -def _basename(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: + :return: transport xml file name """ - Create name that is leveraged for configuration file creation. - - :param emane_model: emane model to create name for - :param iface: interface for this model - :return: basename used for file creation - """ - name = f"n{emane_model.id}" - - if iface: - node_id = iface.node.id - if emane_model.session.emane.get_iface_config(node_id, iface, emane_model.name): - name = iface.localname.replace(".", "_") - - return f"{name}{emane_model.name}" + return f"{iface.name}-trans-{transport_type.value}.xml" -def nem_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def nem_file_name(iface: CoreInterface) -> str: """ - Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml" + Return the string name for the NEM XML file, e.g. "eth0-nem.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: nem xml filename + :param iface: interface running emane + :return: nem xm file name """ - basename = _basename(emane_model, iface) append = "" if iface and iface.transport_type == TransportType.RAW: - append = "_raw" - return f"{basename}nem{append}.xml" + append = "-raw" + return f"{iface.name}-nem{append}.xml" -def shim_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def shim_file_name(iface: CoreInterface = None) -> str: """ - Return the string name for the SHIM XML file, e.g. "commeffectshim.xml" + Return the string name for the SHIM XML file, e.g. "eth0-shim.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: shim xml filename + :param iface: interface running emane + :return: shim xml file name """ - name = _basename(emane_model, iface) - return f"{name}shim.xml" + return f"{iface.name}-shim.xml" -def mac_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def mac_file_name(iface: CoreInterface) -> str: """ - Return the string name for the MAC XML file, e.g. "n3rfpipemac.xml" + Return the string name for the MAC XML file, e.g. "eth0-mac.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: mac xml filename + :param iface: interface running emane + :return: mac xml file name """ - name = _basename(emane_model, iface) - return f"{name}mac.xml" + return f"{iface.name}-mac.xml" -def phy_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def phy_file_name(iface: CoreInterface) -> str: """ - Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml" + Return the string name for the PHY XML file, e.g. "eth0-phy.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: phy xml filename + :param iface: interface running emane + :return: phy xml file name """ - name = _basename(emane_model, iface) - return f"{name}phy.xml" + return f"{iface.name}-phy.xml" From ce4b61d3b21afe5a4ac4402698f0eb09af1c57ac Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 17:49:56 -0700 Subject: [PATCH 101/210] daemon: further heavy cleanup to how emane generates and runs xml files --- daemon/core/emane/commeffect.py | 26 ++--- daemon/core/emane/emanemanager.py | 46 ++++---- daemon/core/emane/emanemodel.py | 34 ++---- daemon/core/emane/nodes.py | 1 - daemon/core/xml/emanexml.py | 175 +++++++++++------------------- 5 files changed, 103 insertions(+), 179 deletions(-) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 100af9a7f..a812b66dd 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -12,7 +12,6 @@ from core.emane import emanemanifest, emanemodel from core.emane.nodes import EmaneNet from core.emulator.data import LinkOptions -from core.emulator.enumerations import TransportType from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -73,26 +72,16 @@ def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: :param iface: interface for the emane node :return: nothing """ - # interface node - node = iface.node - - # retrieve xml names - nem_name = emanexml.nem_file_name(iface) - shim_name = emanexml.shim_file_name(iface) - # create and write nem document nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") - transport_type = TransportType.VIRTUAL - if iface.transport_type == TransportType.RAW: - transport_type = TransportType.RAW - transport_file = emanexml.transport_file_name(iface, transport_type) - etree.SubElement(nem_element, "transport", definition=transport_file) + transport_name = emanexml.transport_file_name(iface) + etree.SubElement(nem_element, "transport", definition=transport_name) # set shim configuration + nem_name = emanexml.nem_file_name(iface) + shim_name = emanexml.shim_file_name(iface) etree.SubElement(nem_element, "shim", definition=shim_name) - - nem_file = os.path.join(node.nodedir, nem_name) - emanexml.create_file(nem_element, "nem", nem_file) + emanexml.create_iface_file(iface, nem_element, "nem", nem_name) # create and write shim document shim_element = etree.Element( @@ -111,9 +100,10 @@ def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: ff = config["filterfile"] if ff.strip() != "": emanexml.add_param(shim_element, "filterfile", ff) + emanexml.create_iface_file(iface, shim_element, "shim", shim_name) - shim_file = os.path.join(node.nodedir, shim_name) - emanexml.create_file(shim_element, "shim", shim_file) + # create transport xml + emanexml.create_transport_xml(iface, config) def linkconfig( self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 3317a5db1..4e2984a0c 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -281,8 +281,6 @@ def setup(self) -> EmaneState: instantiation """ logging.debug("emane setup") - - # TODO: drive this from the session object with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] @@ -291,7 +289,6 @@ def setup(self) -> EmaneState: "adding emane node: id(%s) name(%s)", node.id, node.name ) self.add_node(node) - if not self._emane_nets: logging.debug("no emane nodes in session") return EmaneState.NOT_NEEDED @@ -322,7 +319,7 @@ def setup(self) -> EmaneState: logging.debug("emane event service device index: %s", netidx) if netidx < 0: logging.error( - "EMANE cannot start, check core config. invalid event service device: %s", + "emane cannot start due to invalid event service device: %s", eventdev, ) return EmaneState.NOT_READY @@ -330,7 +327,6 @@ def setup(self) -> EmaneState: self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False ) - self.check_node_models() return EmaneState.SUCCESS @@ -349,10 +345,6 @@ def startup(self) -> EmaneState: self.starteventmonitor() self.buildeventservicexml() with self._emane_node_lock: - # on master, control network bridge added earlier in startup() - control_net = self.session.add_remove_control_net( - 0, remove=False, conf_required=False - ) logging.info("emane building xmls...") for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] @@ -360,25 +352,31 @@ def startup(self) -> EmaneState: logging.error("emane net(%s) has no model", emane_net.name) continue for iface in emane_net.get_ifaces(): - if not iface.node: - logging.error( - "emane net(%s) connected interface missing node", - emane_net.name, - ) - continue - nem_id = self.next_nem_id() - self.nems[nem_id] = iface - self.write_nem(iface, nem_id) - emanexml.build_platform_xml( - self, control_net, emane_net, iface, nem_id - ) - emanexml.build_model_xmls(self, emane_net, iface) - self.start_daemon(iface) - self.install_iface(emane_net, iface) + self.start_iface(emane_net, iface) if self.links_enabled(): self.link_monitor.start() return EmaneState.SUCCESS + def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: + if not iface.node: + logging.error( + "emane net(%s) connected interface(%s) missing node", + emane_net.name, + iface.name, + ) + return + control_net = self.session.add_remove_control_net( + 0, remove=False, conf_required=False + ) + nem_id = self.next_nem_id() + self.nems[nem_id] = iface + self.write_nem(iface, nem_id) + emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id) + config = self.get_iface_config(emane_net, iface) + emane_net.model.build_xml_files(config, iface) + self.start_daemon(iface) + self.install_iface(emane_net, iface) + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = os.path.join(self.session.session_dir, "emane_nems") try: diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 0576d6c37..8672163d4 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -9,7 +9,7 @@ from core.emane import emanemanifest from core.emane.nodes import EmaneNet from core.emulator.data import LinkOptions -from core.emulator.enumerations import ConfigDataTypes, TransportType +from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreError from core.location.mobility import WirelessModel from core.nodes.base import CoreNode @@ -102,34 +102,14 @@ def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: both mac.xml and phy.xml definitions. :param config: emane model configuration for the node and interface - :param iface: interface for the emane node + :param iface: interface to run emane for :return: nothing """ - nem_name = emanexml.nem_file_name(iface) - mac_name = emanexml.mac_file_name(iface) - phy_name = emanexml.phy_file_name(iface) - - # check if this is external - transport_type = TransportType.VIRTUAL - if iface.transport_type == TransportType.RAW: - transport_type = TransportType.RAW - transport_name = emanexml.transport_file_name(iface, transport_type) - - node = iface.node - server = node.server - # create nem xml file - nem_file = os.path.join(node.nodedir, nem_name) - emanexml.create_nem_xml( - self, config, nem_file, transport_name, mac_name, phy_name, server - ) - - # create mac xml file - mac_file = os.path.join(node.nodedir, mac_name) - emanexml.create_mac_xml(self, config, mac_file, server) - - # create phy xml file - phy_file = os.path.join(node.nodedir, phy_name) - emanexml.create_phy_xml(self, config, phy_file, server) + # create nem, mac, and phy xml files + emanexml.create_nem_xml(self, iface, config) + emanexml.create_mac_xml(self, iface, config) + emanexml.create_phy_xml(self, iface, config) + emanexml.create_transport_xml(iface, config) def post_startup(self) -> None: """ diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index dca857856..7e8a0a4ff 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -96,7 +96,6 @@ def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None: """ set the EmaneModel associated with this node """ - logging.info("adding model: %s", model.name) if model.config_type == RegisterTlvs.WIRELESS: # EmaneModel really uses values from ConfigurableManager # when buildnemxml() is called, not during init() diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 32ca0f67c..cb605b214 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -13,6 +13,7 @@ from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet +from core.nodes.physical import Rj45Node from core.xml import corexml if TYPE_CHECKING: @@ -63,16 +64,15 @@ def create_file( :param xml_element: root element to write to file :param doc_name: name to use in the emane doctype :param file_path: file path to write xml file to - :param server: remote server node - will run on, default is None for localhost + :param server: remote server to create file on :return: nothing """ doctype = ( f'' ) - if server is not None: + if server: temp = NamedTemporaryFile(delete=False) - create_file(xml_element, doc_name, temp.name) + corexml.write_xml_file(xml_element, temp.name, doctype=doctype) temp.close() server.remote_put(temp.name, file_path) os.unlink(temp.name) @@ -80,6 +80,26 @@ def create_file( corexml.write_xml_file(xml_element, file_path, doctype=doctype) +def create_iface_file( + iface: CoreInterface, xml_element: etree.Element, doc_name: str, file_name: str +) -> None: + """ + Create emane xml for an interface. + + :param iface: interface running emane + :param xml_element: root element to write to file + :param doc_name: name to use in the emane doctype + :param file_name: name of xml file + :return: + """ + node = iface.node + if isinstance(node, Rj45Node): + file_path = os.path.join(node.session.session_dir, file_name) + else: + file_path = os.path.join(node.nodedir, file_name) + create_file(xml_element, doc_name, file_path, node.server) + + def add_param(xml_element: etree.Element, name: str, value: str) -> None: """ Add emane configuration parameter to xml element. @@ -159,21 +179,14 @@ def build_platform_xml( transport_endpoint = "transportendpoint" add_param(nem_element, transport_endpoint, config[transport_endpoint]) else: - # build transport xml - transport_type = iface.transport_type - if not transport_type: - logging.info("warning: %s interface type unsupported!", iface.name) - transport_type = TransportType.RAW - transport_file = transport_file_name(iface, transport_type) + transport_name = transport_file_name(iface) transport_element = etree.SubElement( - nem_element, "transport", definition=transport_file + nem_element, "transport", definition=transport_name ) add_param(transport_element, "device", iface.name) # determine platform element to add xml to - key = iface.node.id if iface.transport_type == TransportType.RAW: - key = "host" otadev = control_net.brname eventdev = control_net.brname else: @@ -195,67 +208,19 @@ def build_platform_xml( iface.set_mac(mac) doc_name = "platform" - server = None - if key == "host": - file_name = "platform.xml" - file_path = os.path.join(emane_manager.session.session_dir, file_name) - else: - node = iface.node - file_name = f"{iface.name}-platform.xml" - file_path = os.path.join(node.nodedir, file_name) - server = node.server - create_file(platform_element, doc_name, file_path, server) + file_name = f"{iface.name}-platform.xml" + create_iface_file(iface, platform_element, doc_name, file_name) -def build_model_xmls( - manager: "EmaneManager", emane_net: EmaneNet, iface: CoreInterface -) -> None: - """ - Generate emane xml files required for node. - - :param manager: emane manager with emane - configurations - :param emane_net: emane network associated with interface - :param iface: interface to create emane xml for - :return: nothing - """ - # build XML for specific interface (NEM) configs - # check for interface specific emane configuration and write xml files - config = manager.get_iface_config(emane_net, iface) - emane_net.model.build_xml_files(config, iface) - - # check transport type needed for interface - need_virtual = False - need_raw = False - vtype = TransportType.VIRTUAL - rtype = TransportType.RAW - if iface.transport_type == TransportType.VIRTUAL: - need_virtual = True - vtype = iface.transport_type - else: - need_raw = True - rtype = iface.transport_type - if need_virtual: - build_transport_xml(manager, emane_net, iface, vtype) - if need_raw: - build_transport_xml(manager, emane_net, iface, rtype) - - -def build_transport_xml( - manager: "EmaneManager", - emane_net: EmaneNet, - iface: CoreInterface, - transport_type: TransportType, -) -> None: +def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: """ Build transport xml file for node and transport type. - :param manager: emane manager with emane configurations - :param emane_net: emane network associated with interface :param iface: interface to build transport xml for - :param transport_type: transport type to build xml for + :param config: all current configuration values :return: nothing """ + transport_type = get_transport_type(iface) transport_element = etree.Element( "transport", name=f"{transport_type.value.capitalize()} Transport", @@ -264,7 +229,6 @@ def build_transport_xml( add_param(transport_element, "bitrate", "0") # get emane model cnfiguration - config = manager.get_iface_config(emane_net, iface) flowcontrol = config.get("flowcontrolenable", "0") == "1" if transport_type == TransportType.VIRTUAL: device_path = "/dev/net/tun_flowctl" @@ -274,29 +238,19 @@ def build_transport_xml( if flowcontrol: add_param(transport_element, "flowcontrolenable", "on") doc_name = "transport" - node = iface.node - file_name = transport_file_name(iface, transport_type) - file_path = os.path.join(node.nodedir, file_name) - create_file(transport_element, doc_name, file_path) - manager.session.distributed.execute( - lambda x: create_file(transport_element, doc_name, file_path, x) - ) + transport_name = transport_file_name(iface) + create_iface_file(iface, transport_element, doc_name, transport_name) def create_phy_xml( - emane_model: "EmaneModel", - config: Dict[str, str], - file_path: str, - server: Optional[DistributedServer], + emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str] ) -> None: """ Create the phy xml document. :param emane_model: emane model to create xml + :param iface: interface to create xml for :param config: all current configuration values - :param file_path: path to write file to - :param server: remote server node - will run on, default is None for localhost :return: nothing """ phy_element = etree.Element("phy", name=f"{emane_model.name} PHY") @@ -305,23 +259,19 @@ def create_phy_xml( add_configurations( phy_element, emane_model.phy_config, config, emane_model.config_ignore ) - create_file(phy_element, "phy", file_path, server) + file_name = phy_file_name(iface) + create_iface_file(iface, phy_element, "phy", file_name) def create_mac_xml( - emane_model: "EmaneModel", - config: Dict[str, str], - file_path: str, - server: Optional[DistributedServer], + emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str] ) -> None: """ Create the mac xml document. :param emane_model: emane model to create xml + :param iface: interface to create xml for :param config: all current configuration values - :param file_path: path to write file to - :param server: remote server node - will run on, default is None for localhost :return: nothing """ if not emane_model.mac_library: @@ -332,39 +282,33 @@ def create_mac_xml( add_configurations( mac_element, emane_model.mac_config, config, emane_model.config_ignore ) - create_file(mac_element, "mac", file_path, server) + file_name = mac_file_name(iface) + create_iface_file(iface, mac_element, "mac", file_name) def create_nem_xml( - emane_model: "EmaneModel", - config: Dict[str, str], - nem_file: str, - transport_definition: str, - mac_definition: str, - phy_definition: str, - server: Optional[DistributedServer], + emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str] ) -> None: """ Create the nem xml document. :param emane_model: emane model to create xml + :param iface: interface to create xml for :param config: all current configuration values - :param nem_file: nem file path to write - :param transport_definition: transport file definition path - :param mac_definition: mac file definition path - :param phy_definition: phy file definition path - :param server: remote server node - will run on, default is None for localhost :return: nothing """ nem_element = etree.Element("nem", name=f"{emane_model.name} NEM") if is_external(config): nem_element.set("type", "unstructured") else: - etree.SubElement(nem_element, "transport", definition=transport_definition) - etree.SubElement(nem_element, "mac", definition=mac_definition) - etree.SubElement(nem_element, "phy", definition=phy_definition) - create_file(nem_element, "nem", nem_file, server) + transport_name = transport_file_name(iface) + etree.SubElement(nem_element, "transport", definition=transport_name) + mac_name = mac_file_name(iface) + etree.SubElement(nem_element, "mac", definition=mac_name) + phy_name = phy_file_name(iface) + etree.SubElement(nem_element, "phy", definition=phy_name) + nem_name = nem_file_name(iface) + create_iface_file(iface, nem_element, "nem", nem_name) def create_event_service_xml( @@ -400,14 +344,27 @@ def create_event_service_xml( create_file(event_element, "emaneeventmsgsvc", file_path, server) -def transport_file_name(iface: CoreInterface, transport_type: TransportType) -> str: +def get_transport_type(iface: CoreInterface) -> TransportType: + """ + Get transport type for a given interface. + + :param iface: interface to get transport type for + :return: transport type + """ + transport_type = TransportType.VIRTUAL + if iface.transport_type == TransportType.RAW: + transport_type = TransportType.RAW + return transport_type + + +def transport_file_name(iface: CoreInterface) -> str: """ Create name for a transport xml file. :param iface: interface running emane - :param transport_type: transport type to generate transport file :return: transport xml file name """ + transport_type = get_transport_type(iface) return f"{iface.name}-trans-{transport_type.value}.xml" From 5f676b27bacafcd4893564160fe87c5c37b0fbd9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 22:15:12 -0700 Subject: [PATCH 102/210] tests: removed invalid patch due to emane refactoring --- daemon/tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index be62fc035..665f2c1ac 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -12,7 +12,6 @@ from core.api.grpc.client import InterfaceHelper from core.api.grpc.server import CoreGrpcServer from core.api.tlv.corehandlers import CoreHandler -from core.emane.emanemanager import EmaneManager from core.emulator.coreemu import CoreEmu from core.emulator.data import IpPrefixes from core.emulator.distributed import DistributedServer @@ -63,7 +62,6 @@ def patcher(request): patch_manager.patch_obj(CoreNode, "nodefile") patch_manager.patch_obj(Session, "write_state") patch_manager.patch_obj(Session, "write_nodes") - patch_manager.patch_obj(EmaneManager, "buildxml") yield patch_manager patch_manager.shutdown() From 2b3e26b7c2af9f724a9a060a06a35a94a7445d6c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 23:19:40 -0700 Subject: [PATCH 103/210] daemon: cleanup emane transport service in relation to refactoring, silenced stopdaemons for rj45 nodes --- daemon/core/emane/emanemanager.py | 56 +++++++++++---------------- daemon/core/services/emaneservices.py | 46 +++++++--------------- 2 files changed, 37 insertions(+), 65 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 4e2984a0c..808e8020f 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -134,31 +134,27 @@ def get_iface_config( :return: net, node, or interface model configuration """ model_name = emane_net.model.name - # use the network-wide config values or interface(NEM)-specific values? - if iface is None: - return self.get_configs(node_id=emane_net.id, config_type=model_name) - else: - # don"t use default values when interface config is the same as net - # note here that using iface.node.id as key allows for only one type - # of each model per node; - # TODO: use both node and interface as key - # Adamson change: first check for iface config keyed by "node:iface.name" - # (so that nodes w/ multiple interfaces of same conftype can have - # different configs for each separate interface) - key = 1000 * iface.node.id - if iface.node_id is not None: - key += iface.node_id - # try retrieve interface specific configuration, avoid getting defaults - config = self.get_configs(node_id=key, config_type=model_name) - # otherwise retrieve the interfaces node configuration, avoid using defaults - if not config: - config = self.get_configs(node_id=iface.node.id, config_type=model_name) - # get non interface config, when none found - if not config: - # with EMANE 0.9.2+, we need an extra NEM XML from - # model.buildnemxmlfiles(), so defaults are returned here - config = self.get_configs(node_id=emane_net.id, config_type=model_name) - return config + # don"t use default values when interface config is the same as net + # note here that using iface.node.id as key allows for only one type + # of each model per node; + # TODO: use both node and interface as key + # Adamson change: first check for iface config keyed by "node:iface.name" + # (so that nodes w/ multiple interfaces of same conftype can have + # different configs for each separate interface) + key = 1000 * iface.node.id + if iface.node_id is not None: + key += iface.node_id + # try retrieve interface specific configuration, avoid getting defaults + config = self.get_configs(node_id=key, config_type=model_name) + # otherwise retrieve the interfaces node configuration, avoid using defaults + if not config: + config = self.get_configs(node_id=iface.node.id, config_type=model_name) + # get non interface config, when none found + if not config: + # with EMANE 0.9.2+, we need an extra NEM XML from + # model.buildnemxmlfiles(), so defaults are returned here + config = self.get_configs(node_id=emane_net.id, config_type=model_name) + return config def config_reset(self, node_id: int = None) -> None: super().config_reset(node_id) @@ -587,26 +583,18 @@ def stopdaemons(self) -> None: """ Kill the appropriate EMANE daemons. """ - # TODO: we may want to improve this if we had the PIDs from the specific EMANE - # daemons that we"ve started kill_emaned = "killall -q emane" - kill_transortd = "killall -q emanetransportd" stop_emane_on_host = False for node in self.getnodes(): if isinstance(node, Rj45Node): stop_emane_on_host = True continue - if node.up: node.cmd(kill_emaned, wait=False) - # TODO: RJ45 node - if stop_emane_on_host: try: - utils.cmd(kill_emaned) - utils.cmd(kill_transortd) + utils.cmd(kill_emaned, wait=False) self.session.distributed.execute(lambda x: x.remote_cmd(kill_emaned)) - self.session.distributed.execute(lambda x: x.remote_cmd(kill_transortd)) except CoreCommandError: logging.exception("error shutting down emane daemons") diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index ef188faba..e734851df 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -1,7 +1,6 @@ from typing import Tuple from core.emane.nodes import EmaneNet -from core.errors import CoreError from core.nodes.base import CoreNode from core.services.coreservices import CoreService from core.xml import emanexml @@ -14,37 +13,22 @@ class EmaneTransportService(CoreService): dependencies: Tuple[str, ...] = () dirs: Tuple[str, ...] = () configs: Tuple[str, ...] = ("emanetransport.sh",) - startup: Tuple[str, ...] = ("sh %s" % configs[0],) - validate: Tuple[str, ...] = ("pidof %s" % executables[0],) + startup: Tuple[str, ...] = (f"sh {configs[0]}",) + validate: Tuple[str, ...] = (f"pidof {executables[0]}",) validation_timer: float = 0.5 - shutdown: Tuple[str, ...] = ("killall %s" % executables[0],) + shutdown: Tuple[str, ...] = (f"killall {executables[0]}",) @classmethod def generate_config(cls, node: CoreNode, filename: str) -> str: - if filename == cls.configs[0]: - transport_commands = [] - for iface in node.get_ifaces(): - try: - network_node = node.session.get_node(iface.net.id, EmaneNet) - config = node.session.emane.get_configs( - network_node.id, network_node.model.name - ) - if config and emanexml.is_external(config): - nem_id = network_node.getnemid(iface) - command = ( - "emanetransportd -r -l 0 -d ../transportdaemon%s.xml" - % nem_id - ) - transport_commands.append(command) - except CoreError: - pass - transport_commands = "\n".join(transport_commands) - return """ -emanegentransportxml -o ../ ../platform%s.xml -%s -""" % ( - node.id, - transport_commands, - ) - else: - raise ValueError + emane_manager = node.session.emane + cfg = "" + for iface in node.get_ifaces(): + if not isinstance(iface.net, EmaneNet): + continue + emane_net = iface.net + config = emane_manager.get_iface_config(emane_net, iface) + if emanexml.is_external(config): + nem_id = emane_net.getnemid(iface) + cfg += f"emanegentransportxml {iface.name}-platform.xml\n" + cfg += f"emanetransportd -r -l 0 -d transportdaemon{nem_id}.xml\n" + return cfg From ddcb0205f35a3b3a3ac064a4c79356f7ddf845ee Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 23:32:59 -0700 Subject: [PATCH 104/210] daemon: cleaned up emane stopdaemons logic --- daemon/core/emane/emanemanager.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 808e8020f..ca59ad04b 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -567,36 +567,31 @@ def start_daemon(self, iface: CoreInterface) -> None: log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log") platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml") args = f"{emanecmd} -f {log_file} {platform_xml}" - output = node.cmd(args) + node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) - logging.debug("node(%s) emane daemon output: %s", node.name, output) else: path = self.session.session_dir log_file = os.path.join(path, f"{iface.name}-emane.log") platform_xml = os.path.join(path, f"{iface.name}-platform.xml") emanecmd += f" -f {log_file} {platform_xml}" - utils.cmd(emanecmd, cwd=path) - self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path)) - logging.info("host emane daemon running: %s", emanecmd) + node.host_cmd(emanecmd, cwd=path) + logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd) def stopdaemons(self) -> None: """ Kill the appropriate EMANE daemons. """ kill_emaned = "killall -q emane" - stop_emane_on_host = False - for node in self.getnodes(): - if isinstance(node, Rj45Node): - stop_emane_on_host = True - continue - if node.up: - node.cmd(kill_emaned, wait=False) - if stop_emane_on_host: - try: - utils.cmd(kill_emaned, wait=False) - self.session.distributed.execute(lambda x: x.remote_cmd(kill_emaned)) - except CoreCommandError: - logging.exception("error shutting down emane daemons") + for node_id in sorted(self._emane_nets): + emane_net = self._emane_nets[node_id] + for iface in emane_net.get_ifaces(): + node = iface.node + if not node.up: + continue + if isinstance(node, Rj45Node): + node.host_cmd(kill_emaned, wait=False) + else: + node.cmd(kill_emaned, wait=False) def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: config = self.get_iface_config(emane_net, iface) From ac1c27b1c8b74da40c79ed9e95765191c0e71605 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 3 Jul 2020 08:51:17 -0700 Subject: [PATCH 105/210] daemon: fixed issues when emane generated platform.xml for raw interfaces --- daemon/core/emane/emanemanager.py | 23 +++++++++++++---------- daemon/core/xml/emanexml.py | 22 ++++++++-------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index ca59ad04b..476010cbb 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -31,7 +31,6 @@ from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode, NodeBase from core.nodes.interface import CoreInterface, TunTap -from core.nodes.physical import Rj45Node from core.xml import emanexml if TYPE_CHECKING: @@ -531,19 +530,21 @@ def start_daemon(self, iface: CoreInterface) -> None: cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) if cfgloglevel: - logging.info("setting user-defined EMANE log level: %d", cfgloglevel) + logging.info("setting user-defined emane log level: %d", cfgloglevel) loglevel = str(cfgloglevel) emanecmd = f"emane -d -l {loglevel}" if realtime: emanecmd += " -r" - otagroup, _otaport = self.get_config("otamanagergroup").split(":") - otadev = self.get_config("otamanagerdevice") - otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.get_config("eventservicegroup").split(":") - eventdev = self.get_config("eventservicedevice") - eventservicenetidx = self.session.get_control_net_index(eventdev) node = iface.node - if not isinstance(node, Rj45Node): + transport_type = emanexml.get_transport_type(iface) + if not transport_type == TransportType.RAW: + otagroup, _otaport = self.get_config("otamanagergroup").split(":") + otadev = self.get_config("otamanagerdevice") + otanetidx = self.session.get_control_net_index(otadev) + eventgroup, _eventport = self.get_config("eventservicegroup").split(":") + eventdev = self.get_config("eventservicedevice") + eventservicenetidx = self.session.get_control_net_index(eventdev) + # control network not yet started here self.session.add_remove_control_iface( node, 0, remove=False, conf_required=False @@ -559,6 +560,7 @@ def start_daemon(self, iface: CoreInterface) -> None: node, eventservicenetidx, remove=False, conf_required=False ) # multicast route is needed for OTA data + logging.info("OTA GROUP(%s) OTA DEV(%s)", otagroup, otadev) node.node_net_client.create_route(otagroup, otadev) # multicast route is also needed for event data if on control network if eventservicenetidx >= 0 and eventgroup != otagroup: @@ -588,7 +590,8 @@ def stopdaemons(self) -> None: node = iface.node if not node.up: continue - if isinstance(node, Rj45Node): + transport_type = emanexml.get_transport_type(iface) + if transport_type == TransportType.RAW: node.host_cmd(kill_emaned, wait=False) else: node.cmd(kill_emaned, wait=False) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index cb605b214..cf973f34d 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -13,7 +13,6 @@ from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet -from core.nodes.physical import Rj45Node from core.xml import corexml if TYPE_CHECKING: @@ -93,7 +92,8 @@ def create_iface_file( :return: """ node = iface.node - if isinstance(node, Rj45Node): + transport_type = get_transport_type(iface) + if transport_type == TransportType.RAW: file_path = os.path.join(node.session.session_dir, file_name) else: file_path = os.path.join(node.nodedir, file_name) @@ -185,21 +185,15 @@ def build_platform_xml( ) add_param(transport_element, "device", iface.name) - # determine platform element to add xml to - if iface.transport_type == TransportType.RAW: - otadev = control_net.brname - eventdev = control_net.brname - else: - otadev = None - eventdev = None + transport_type = get_transport_type(iface) + transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") - if otadev: - emane_manager.set_config("otamanagerdevice", otadev) - if eventdev: - emane_manager.set_config("eventservicedevice", eventdev) for configuration in emane_manager.emane_config.emulator_config: name = configuration.id - value = emane_manager.get_config(name) + if transport_type == TransportType.RAW and name in transport_configs: + value = control_net.brname + else: + value = emane_manager.get_config(name) add_param(platform_element, name, value) platform_element.append(nem_element) emane_net.setnemid(iface, nem_id) From fcda1f9f14432aab23c5bf937964b3054f5eca7b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 3 Jul 2020 09:08:36 -0700 Subject: [PATCH 106/210] daemon: CoreInterface now defaults to a virtual transport type, added utility methods to check if an interface is virtual/raw, cleaned up all emane code using these types of checks --- daemon/core/emane/emanemanager.py | 9 +++------ daemon/core/nodes/interface.py | 19 +++++++++++++++++-- daemon/core/xml/emanexml.py | 31 ++++++------------------------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 476010cbb..15faedcc2 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -26,7 +26,6 @@ LinkTypes, MessageFlags, RegisterTlvs, - TransportType, ) from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode, NodeBase @@ -536,8 +535,7 @@ def start_daemon(self, iface: CoreInterface) -> None: if realtime: emanecmd += " -r" node = iface.node - transport_type = emanexml.get_transport_type(iface) - if not transport_type == TransportType.RAW: + if iface.is_virtual(): otagroup, _otaport = self.get_config("otamanagergroup").split(":") otadev = self.get_config("otamanagerdevice") otanetidx = self.session.get_control_net_index(otadev) @@ -590,8 +588,7 @@ def stopdaemons(self) -> None: node = iface.node if not node.up: continue - transport_type = emanexml.get_transport_type(iface) - if transport_type == TransportType.RAW: + if iface.is_raw(): node.host_cmd(kill_emaned, wait=False) else: node.cmd(kill_emaned, wait=False) @@ -614,7 +611,7 @@ def deinstall_ifaces(self) -> None: for key in sorted(self._emane_nets): emane_net = self._emane_nets[key] for iface in emane_net.get_ifaces(): - if iface.transport_type == TransportType.VIRTUAL: + if iface.is_virtual(): iface.shutdown() iface.poshook = None diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index e4d4d0ac4..7f33973e3 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -60,7 +60,7 @@ def __init__( # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE - self.transport_type: Optional[TransportType] = None + self.transport_type: TransportType = TransportType.VIRTUAL # id of interface for node self.node_id: Optional[int] = None # id of interface for network @@ -310,6 +310,22 @@ def __lt__(self, other: "CoreInterface") -> bool: """ return id(self) < id(other) + def is_raw(self) -> bool: + """ + Used to determine if this interface is considered a raw interface. + + :return: True if raw interface, False otherwise + """ + return self.transport_type == TransportType.RAW + + def is_virtual(self) -> bool: + """ + Used to determine if this interface is considered a virtual interface. + + :return: True if virtual interface, False otherwise + """ + return self.transport_type == TransportType.VIRTUAL + class Veth(CoreInterface): """ @@ -404,7 +420,6 @@ def __init__( :param start: start flag """ super().__init__(session, node, name, localname, mtu, server) - self.transport_type = TransportType.VIRTUAL if start: self.startup() diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index cf973f34d..0ef13a806 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -9,7 +9,6 @@ from core.config import Configuration from core.emane.nodes import EmaneNet from core.emulator.distributed import DistributedServer -from core.emulator.enumerations import TransportType from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet @@ -92,8 +91,7 @@ def create_iface_file( :return: """ node = iface.node - transport_type = get_transport_type(iface) - if transport_type == TransportType.RAW: + if iface.is_raw(): file_path = os.path.join(node.session.session_dir, file_name) else: file_path = os.path.join(node.nodedir, file_name) @@ -185,12 +183,11 @@ def build_platform_xml( ) add_param(transport_element, "device", iface.name) - transport_type = get_transport_type(iface) transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") for configuration in emane_manager.emane_config.emulator_config: name = configuration.id - if transport_type == TransportType.RAW and name in transport_configs: + if iface.is_raw() and name in transport_configs: value = control_net.brname else: value = emane_manager.get_config(name) @@ -214,7 +211,7 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: :param config: all current configuration values :return: nothing """ - transport_type = get_transport_type(iface) + transport_type = iface.transport_type transport_element = etree.Element( "transport", name=f"{transport_type.value.capitalize()} Transport", @@ -224,7 +221,7 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: # get emane model cnfiguration flowcontrol = config.get("flowcontrolenable", "0") == "1" - if transport_type == TransportType.VIRTUAL: + if iface.is_virtual(): device_path = "/dev/net/tun_flowctl" if not os.path.exists(device_path): device_path = "/dev/net/tun" @@ -338,19 +335,6 @@ def create_event_service_xml( create_file(event_element, "emaneeventmsgsvc", file_path, server) -def get_transport_type(iface: CoreInterface) -> TransportType: - """ - Get transport type for a given interface. - - :param iface: interface to get transport type for - :return: transport type - """ - transport_type = TransportType.VIRTUAL - if iface.transport_type == TransportType.RAW: - transport_type = TransportType.RAW - return transport_type - - def transport_file_name(iface: CoreInterface) -> str: """ Create name for a transport xml file. @@ -358,8 +342,7 @@ def transport_file_name(iface: CoreInterface) -> str: :param iface: interface running emane :return: transport xml file name """ - transport_type = get_transport_type(iface) - return f"{iface.name}-trans-{transport_type.value}.xml" + return f"{iface.name}-trans-{iface.transport_type.value}.xml" def nem_file_name(iface: CoreInterface) -> str: @@ -369,9 +352,7 @@ def nem_file_name(iface: CoreInterface) -> str: :param iface: interface running emane :return: nem xm file name """ - append = "" - if iface and iface.transport_type == TransportType.RAW: - append = "-raw" + append = "-raw" if iface.is_raw() else "" return f"{iface.name}-nem{append}.xml" From 5cc4d92760137796e9b66f45f0705a67ebed8869 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:29:03 -0700 Subject: [PATCH 107/210] daemon: removed nem map from individual emane networks, all nems are stored and generated from the emane manager --- daemon/core/api/grpc/grpcutils.py | 7 +++- daemon/core/api/grpc/server.py | 22 +++++----- daemon/core/emane/commeffect.py | 10 ++--- daemon/core/emane/emanemanager.py | 59 ++++++++++++++------------- daemon/core/emane/nodes.py | 41 ++++--------------- daemon/core/services/emaneservices.py | 2 +- daemon/core/xml/corexml.py | 4 +- daemon/core/xml/corexmldeployment.py | 7 ++-- daemon/core/xml/emanexml.py | 1 - 9 files changed, 64 insertions(+), 89 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index bd9e808dc..bd3519f77 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -491,10 +491,13 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface: ) -def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: +def get_nem_id( + session: Session, node: CoreNode, iface_id: int, context: ServicerContext +) -> int: """ Get nem id for a given node and interface id. + :param session: session node belongs to :param node: node to get nem id for :param iface_id: id of interface on node to get nem id for :param context: request context @@ -508,7 +511,7 @@ def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: if not isinstance(net, EmaneNet): message = f"{node.name} interface {iface_id} is not an EMANE network" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) - nem_id = net.getnemid(iface) + nem_id = session.emane.get_nem_id(iface) if nem_id is None: message = f"{node.name} interface {iface_id} nem id does not exist" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index aa5ec5394..5bdebac62 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -1551,29 +1551,29 @@ def EmaneLink( logging.debug("emane link: %s", request) session = self.get_session(request.session_id, context) nem1 = request.nem1 - emane1, iface = session.emane.nemlookup(nem1) - if not emane1 or not iface: + iface1 = session.emane.get_iface(nem1) + if not iface1: context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found") - node1 = iface.node + node1 = iface1.node nem2 = request.nem2 - emane2, iface = session.emane.nemlookup(nem2) - if not emane2 or not iface: + iface2 = session.emane.get_iface(nem2) + if not iface2: context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found") - node2 = iface.node + node2 = iface2.node - if emane1.id == emane2.id: + if iface1.net == iface2.net: if request.linked: flag = MessageFlags.ADD else: flag = MessageFlags.DELETE - color = session.get_link_color(emane1.id) + 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=emane1.id, + network_id=iface1.net.id, color=color, ) session.broadcast_link(link) @@ -1796,8 +1796,8 @@ def EmanePathlosses( for request in request_iterator: session = self.get_session(request.session_id, context) node1 = self.get_node(session, request.node1_id, context, CoreNode) - nem1 = grpcutils.get_nem_id(node1, request.iface1_id, context) + nem1 = grpcutils.get_nem_id(session, node1, request.iface1_id, context) node2 = self.get_node(session, request.node2_id, context, CoreNode) - nem2 = grpcutils.get_nem_id(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) return EmanePathlossesResponse() diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index a812b66dd..0fa70a92b 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -10,7 +10,6 @@ from core.config import ConfigGroup, Configuration from core.emane import emanemanifest, emanemodel -from core.emane.nodes import EmaneNet from core.emulator.data import LinkOptions from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -124,12 +123,11 @@ def linkconfig( # TODO: batch these into multiple events per transmission # TODO: may want to split out seconds portion of delay and jitter event = CommEffectEvent() - emane_node = self.session.get_node(self.id, EmaneNet) - nemid = emane_node.getnemid(iface) - nemid2 = emane_node.getnemid(iface2) + nem1 = self.session.emane.get_nem_id(iface) + nem2 = self.session.emane.get_nem_id(iface2) logging.info("sending comm effect event") event.append( - nemid, + nem1, latency=convert_none(options.delay), jitter=convert_none(options.jitter), loss=convert_none(options.loss), @@ -137,4 +135,4 @@ def linkconfig( unicast=int(convert_none(options.bandwidth)), broadcast=int(convert_none(options.bandwidth)), ) - service.publish(nemid2, event) + service.publish(nem2, event) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 15faedcc2..2a7f9844d 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -90,7 +90,8 @@ def __init__(self, session: "Session") -> None: """ super().__init__() self.session: "Session" = session - self.nems: Dict[int, CoreInterface] = {} + self.nems_to_ifaces: Dict[int, CoreInterface] = {} + self.ifaces_to_nems: Dict[CoreInterface, int] = {} self._emane_nets: Dict[int, EmaneNet] = {} self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters @@ -117,7 +118,7 @@ def __init__(self, session: "Session") -> None: def next_nem_id(self) -> int: nem_id = int(self.get_config("nem_id_start")) - while nem_id in self.nems: + while nem_id in self.nems_to_ifaces: nem_id += 1 return nem_id @@ -363,7 +364,7 @@ def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: 0, remove=False, conf_required=False ) nem_id = self.next_nem_id() - self.nems[nem_id] = iface + self.set_nem(nem_id, iface) self.write_nem(iface, nem_id) emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id) config = self.get_iface_config(emane_net, iface) @@ -371,6 +372,18 @@ def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: self.start_daemon(iface) self.install_iface(emane_net, iface) + def set_nem(self, nem_id: int, iface: CoreInterface) -> None: + if nem_id in self.nems_to_ifaces: + raise CoreError(f"adding duplicate nem: {nem_id}") + self.nems_to_ifaces[nem_id] = iface + self.ifaces_to_nems[iface] = nem_id + + def get_iface(self, nem_id: int) -> Optional[CoreInterface]: + return self.nems_to_ifaces.get(nem_id) + + def get_nem_id(self, iface: CoreInterface) -> Optional[int]: + return self.ifaces_to_nems.get(iface) + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = os.path.join(self.session.session_dir, "emane_nems") try: @@ -405,7 +418,8 @@ def reset(self) -> None: """ with self._emane_node_lock: self._emane_nets.clear() - self.nems.clear() + self.nems_to_ifaces.clear() + self.ifaces_to_nems.clear() def shutdown(self) -> None: """ @@ -448,42 +462,29 @@ def check_node_models(self) -> None: model_class = self.models[model_name] emane_net.setmodel(model_class, config) - def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]: - """ - Look for the given numerical NEM ID and return the first matching - EMANE network and NEM interface. - """ - emane_node = None - iface = None - for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] - iface = emane_node.get_nem_iface(nemid) - if iface is not None: - break - else: - emane_node = None - return emane_node, iface - def get_nem_link( self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE ) -> Optional[LinkData]: - emane1, iface = self.nemlookup(nem1) - if not emane1 or not iface: + iface1 = self.get_iface(nem1) + if not iface1: logging.error("invalid nem: %s", nem1) return None - node1 = iface.node - emane2, iface = self.nemlookup(nem2) - if not emane2 or not iface: + node1 = iface1.node + iface2 = self.get_iface(nem2) + if not iface2: logging.error("invalid nem: %s", nem2) return None - node2 = iface.node - color = self.session.get_link_color(emane1.id) + node2 = iface2.node + if iface1.net != iface2.net: + return None + emane_net = iface1.net + color = self.session.get_link_color(emane_net.id) return LinkData( message_type=flags, type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, - network_id=emane1.id, + network_id=emane_net.id, color=color, ) @@ -728,7 +729,7 @@ def handlelocationeventtoxyz( Returns True if successfully parsed and a Node Message was sent. """ # convert nemid to node number - _emanenode, iface = self.nemlookup(nemid) + iface = self.get_iface(nemid) if iface is None: logging.info("location event for unknown NEM %s", nemid) return False diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 7e8a0a4ff..cfb3342e1 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -52,7 +52,6 @@ def __init__( ) -> None: super().__init__(session, _id, name, server) self.conf: str = "" - self.nemidmap: Dict[CoreInterface, int] = {} self.model: "OptionalEmaneModel" = None self.mobility: Optional[WayPointMobility] = None @@ -105,32 +104,6 @@ def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None: self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) - def setnemid(self, iface: CoreInterface, nemid: int) -> None: - """ - Record an interface to numerical ID mapping. The Emane controller - object manages and assigns these IDs for all NEMs. - """ - self.nemidmap[iface] = nemid - - def getnemid(self, iface: CoreInterface) -> Optional[int]: - """ - Given an interface, return its numerical ID. - """ - if iface not in self.nemidmap: - return None - else: - return self.nemidmap[iface] - - def get_nem_iface(self, nemid: int) -> Optional[CoreInterface]: - """ - Given a numerical NEM ID, return its interface. This returns the - first interface that matches the given NEM ID. - """ - for iface in self.nemidmap: - if self.nemidmap[iface] == nemid: - return iface - return None - def _nem_position( self, iface: CoreInterface ) -> Optional[Tuple[int, float, float, float]]: @@ -140,9 +113,9 @@ def _nem_position( :param iface: interface to get nem emane position for :return: nem position tuple, None otherwise """ - nemid = self.getnemid(iface) + nem_id = self.session.emane.get_nem_id(iface) ifname = iface.localname - if nemid is None: + if nem_id is None: logging.info("nemid for %s is unknown", ifname) return node = iface.node @@ -153,7 +126,7 @@ def _nem_position( node.position.set_geo(lon, lat, alt) # altitude must be an integer or warning is printed alt = int(round(alt)) - return nemid, lon, lat, alt + return nem_id, lon, lat, alt def setnemposition(self, iface: CoreInterface) -> None: """ @@ -164,7 +137,6 @@ def setnemposition(self, iface: CoreInterface) -> None: if self.session.emane.service is None: logging.info("position service not available") return - position = self._nem_position(iface) if position: nemid, lon, lat, alt = position @@ -195,9 +167,12 @@ def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None: def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: links = super().links(flags) - # gather current emane links - nem_ids = set(self.nemidmap.values()) emane_manager = self.session.emane + # gather current emane links + nem_ids = set() + for iface in self.get_ifaces(): + nem_id = emane_manager.get_nem_id(iface) + nem_ids.add(nem_id) emane_links = emane_manager.link_monitor.links considered = set() for link_key in emane_links: diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index e734851df..d694317a2 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -28,7 +28,7 @@ def generate_config(cls, node: CoreNode, filename: str) -> str: emane_net = iface.net config = emane_manager.get_iface_config(emane_net, iface) if emanexml.is_external(config): - nem_id = emane_net.getnemid(iface) + nem_id = emane_manager.get_nem_id(iface) cfg += f"emanegentransportxml {iface.name}-platform.xml\n" cfg += f"emanetransportd -r -l 0 -d transportdaemon{nem_id}.xml\n" return cfg diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 340d81d0f..ffd07ebde 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -501,8 +501,8 @@ def create_iface_element( iface = node.get_iface(iface_data.id) # check if emane interface if isinstance(iface.net, EmaneNet): - nem = iface.net.getnemid(iface) - add_attribute(iface_element, "nem", nem) + nem_id = self.session.emane.get_nem_id(iface) + add_attribute(iface_element, "nem", nem_id) add_attribute(iface_element, "id", iface_data.id) add_attribute(iface_element, "name", iface_data.name) add_attribute(iface_element, "mac", iface_data.mac) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 51201787c..c062a1d2a 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -9,7 +9,6 @@ from core.emane.nodes import EmaneNet from core.executables import IP from core.nodes.base import CoreNodeBase, NodeBase -from core.nodes.interface import CoreInterface if TYPE_CHECKING: from core.emulator.session import Session @@ -38,11 +37,10 @@ def add_mapping(parent_element: etree.Element, maptype: str, mapref: str) -> Non def add_emane_iface( host_element: etree.Element, - iface: CoreInterface, + nem_id: int, platform_name: str = "p1", transport_name: str = "t1", ) -> etree.Element: - nem_id = iface.net.nemidmap[iface] host_id = host_element.get("id") # platform data @@ -158,7 +156,8 @@ def add_virtual_host(self, physical_host: etree.Element, node: NodeBase) -> None for iface in node.get_ifaces(): emane_element = None if isinstance(iface.net, EmaneNet): - emane_element = add_emane_iface(host_element, iface) + nem_id = self.session.emane.get_nem_id(iface) + emane_element = add_emane_iface(host_element, nem_id) parent_element = host_element if emane_element is not None: diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 0ef13a806..88aeaa97b 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -193,7 +193,6 @@ def build_platform_xml( value = emane_manager.get_config(name) add_param(platform_element, name, value) platform_element.append(nem_element) - emane_net.setnemid(iface, nem_id) mac = _MAC_PREFIX + ":00:00:" mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" iface.set_mac(mac) From b3a4b1cb10a47f8a5b7de36e1135096468cf8d61 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:56:22 -0700 Subject: [PATCH 108/210] daemon: updates to support running emane on the fly for a newly connected link --- daemon/core/emane/emanemanager.py | 4 ++-- daemon/core/emane/nodes.py | 10 +++++++++- daemon/core/emulator/session.py | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 2a7f9844d..3765ba443 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -252,8 +252,8 @@ def add_node(self, emane_net: EmaneNet) -> None: """ with self._emane_node_lock: if emane_net.id in self._emane_nets: - raise KeyError( - f"non-unique EMANE object id {emane_net.id} for {emane_net}" + raise CoreError( + f"duplicate emane network({emane_net.id}): {emane_net.name}" ) self._emane_nets[emane_net.id] = emane_net diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index cfb3342e1..5791f46ad 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -8,7 +8,13 @@ from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs +from core.emulator.enumerations import ( + EventTypes, + LinkTypes, + MessageFlags, + NodeTypes, + RegisterTlvs, +) from core.errors import CoreError from core.nodes.base import CoreNetworkBase, CoreNode from core.nodes.interface import CoreInterface @@ -203,4 +209,6 @@ def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterfa iface.set_mac(iface_data.mac) for ip in iface_data.get_ips(): iface.add_ip(ip) + if self.session.state == EventTypes.RUNTIME_STATE: + self.session.emane.start_iface(self, iface) return iface diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 9f5364b90..cad6ae3ce 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -531,7 +531,7 @@ def add_node( self.set_node_position(node, options) # add services to needed nodes - if isinstance(node, (CoreNode, PhysicalNode, DockerNode, LxcNode)): + if isinstance(node, (CoreNode, PhysicalNode)): node.type = options.model logging.debug("set node type: %s", node.type) self.services.add_services(node, node.type, options.services) @@ -545,6 +545,8 @@ def add_node( # ensure default emane configuration if isinstance(node, EmaneNet) and options.emane: self.emane.set_model_config(_id, options.emane) + if self.state == EventTypes.RUNTIME_STATE: + self.emane.add_node(node) # set default wlan config if needed if isinstance(node, WlanNode): self.mobility.set_model_config(_id, BasicRangeModel.name) From 8dc570a98d7843337ace6d7ef22a37c7f27a309f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:13:54 -0700 Subject: [PATCH 109/210] daemon: removed commented out code --- daemon/core/nodes/base.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 039008ef6..7f444480d 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -815,17 +815,6 @@ def new_iface( with self.lock: if net.has_custom_iface: return net.custom_iface(self, iface_data) - # if net.is_emane is True: - # iface_id = self.newtuntap(iface_data.id, iface_data.name) - # # TUN/TAP is not ready for addressing yet; the device may - # # take some time to appear, and installing it into a - # # namespace after it has been bound removes addressing; - # # save addresses with the interface now - # self.attachnet(iface_id, net) - # iface = self.get_iface(iface_id) - # iface.set_mac(iface_data.mac) - # for ip in ips: - # iface.add_ip(ip) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) From 6f7e42d310164bbe1bef9b98de3f48f8355df8b3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:32:43 -0700 Subject: [PATCH 110/210] daemon: avoid command error logging when checking for emane version as validation for checking if emane is installed --- daemon/core/emane/emanemanager.py | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 3765ba443..ec39137d7 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -165,23 +165,24 @@ def emane_check(self) -> None: :return: nothing """ - try: - # check for emane - args = "emane --version" - emane_version = utils.cmd(args) - logging.info("using EMANE: %s", emane_version) - self.session.distributed.execute(lambda x: x.remote_cmd(args)) - - # load default emane models - self.load_models(EMANE_MODELS) - - # load custom models - custom_models_path = self.session.options.get_config("emane_models_dir") - if custom_models_path: - emane_models = utils.load_classes(custom_models_path, EmaneModel) - self.load_models(emane_models) - except CoreCommandError: + # check for emane + path = utils.which("emane", required=False) + if not path: logging.info("emane is not installed") + return + + # get version + emane_version = utils.cmd("emane --version") + logging.info("using emane: %s", emane_version) + + # load default emane models + self.load_models(EMANE_MODELS) + + # load custom models + custom_models_path = self.session.options.get_config("emane_models_dir") + if custom_models_path: + emane_models = utils.load_classes(custom_models_path, EmaneModel) + self.load_models(emane_models) def deleteeventservice(self) -> None: if self.service: From 0045c8d79c53c89f5079f64fe8b536c7fa38f43c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:37:04 -0700 Subject: [PATCH 111/210] pygui: avoid trying to bring up a terminal for rj45 nodes --- daemon/core/gui/graph/node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 6e8185b89..dfe724bda 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -193,7 +193,8 @@ def on_leave(self, event: tk.Event) -> None: def double_click(self, event: tk.Event) -> None: if self.app.core.is_runtime(): - self.canvas.core.launch_terminal(self.core_node.id) + if NodeUtils.is_container_node(self.core_node.type): + self.canvas.core.launch_terminal(self.core_node.id) else: self.show_config() From c761c55ebc5d7f89116cbfe81031953394657b85 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:47:46 -0700 Subject: [PATCH 112/210] tests: patch utils.which --- daemon/tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 665f2c1ac..0e25dee97 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -55,6 +55,7 @@ def patcher(request): if request.config.getoption("mock"): patch_manager.patch("os.mkdir") patch_manager.patch("core.utils.cmd") + patch_manager.patch("core.utils.which") patch_manager.patch("core.nodes.netclient.get_net_client") patch_manager.patch_obj( LinuxNetClient, "get_mac", return_value="00:00:00:00:00:00" From 6648dc7825279bf59ec857725545aca9ed9809e0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 08:46:47 -0700 Subject: [PATCH 113/210] pygui: service and config service dialogs will now properly show services for default group selected --- daemon/core/gui/dialogs/nodeconfigservice.py | 2 +- daemon/core/gui/dialogs/nodeservice.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index b5250eba9..b9a9a1f58 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -84,7 +84,7 @@ def draw(self) -> None: button.grid(row=0, column=3, sticky="ew") # trigger group change - self.groups.listbox.event_generate("<>") + self.handle_group_change() def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index f6f5e5cf9..6fcc29128 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -82,7 +82,7 @@ def draw(self) -> None: button.grid(row=0, column=3, sticky="ew") # trigger group change - self.groups.listbox.event_generate("<>") + self.handle_group_change() def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() From f1ff1a65770dc446e6cd75f113ced9eacd93d3ea Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 14:24:43 -0700 Subject: [PATCH 114/210] pygui: only attempt to run observer commands on container nodes --- daemon/core/gui/graph/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index dfe724bda..f765816d4 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -179,7 +179,10 @@ def motion(self, x_offset: float, y_offset: float, update: bool = True) -> None: self.app.core.edit_node(self.core_node) def on_enter(self, event: tk.Event) -> None: - if self.app.core.is_runtime() and self.app.core.observer: + is_runtime = self.app.core.is_runtime() + has_observer = self.app.core.observer is not None + is_container = NodeUtils.is_container_node(self.core_node.type) + if is_runtime and has_observer and is_container: self.tooltip.text.set("waiting...") self.tooltip.on_enter(event) try: From bb4514b93e315c9b5682ee893679b9bb1b764a4c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 15:16:17 -0700 Subject: [PATCH 115/210] daemon: changes to saving and restoring server used for nodes in xml --- daemon/core/xml/corexml.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index ffd07ebde..d1c43d9b3 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -128,6 +128,8 @@ def __init__(self, session: "Session", node: NodeBase, element_name: str) -> Non self.element: etree.Element = etree.Element(element_name) add_attribute(self.element, "id", node.id) add_attribute(self.element, "name", node.name) + server = self.node.server.name if self.node.server else None + add_attribute(self.element, "server", server) add_attribute(self.element, "icon", node.icon) add_attribute(self.element, "canvas", node.canvas) self.add_position() @@ -801,8 +803,10 @@ def read_device(self, device_element: etree.Element) -> None: icon = device_element.get("icon") clazz = device_element.get("class") image = device_element.get("image") - options = NodeOptions(name=name, model=model, image=image, icon=icon) - + server = device_element.get("server") + options = NodeOptions( + name=name, model=model, image=image, icon=icon, server=server + ) node_type = NodeTypes.DEFAULT if clazz == "docker": node_type = NodeTypes.DOCKER @@ -842,7 +846,8 @@ def read_network(self, network_element: etree.Element) -> None: node_type = NodeTypes[network_element.get("type")] _class = self.session.get_node_class(node_type) icon = network_element.get("icon") - options = NodeOptions(name=name, icon=icon) + server = network_element.get("server") + options = NodeOptions(name=name, icon=icon, server=server) position_element = network_element.find("position") if position_element is not None: From fb21909dadaed688ae027313cea34bfe065772f5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 23:38:12 -0700 Subject: [PATCH 116/210] invoke/poetry: updated version in toml file and added invoke commands --- daemon/pyproject.toml | 2 +- tasks.py | 52 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 6df5f10e8..609fcb08d 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "6.4.0" +version = "6.6.0" description = "" authors = [] diff --git a/tasks.py b/tasks.py index 74e6af76c..b19e8925d 100644 --- a/tasks.py +++ b/tasks.py @@ -2,13 +2,51 @@ @task -def core(c): - c.run( - "poetry run sudo python3 scripts/core-daemon " - "-f data/core.conf -l data/logging.conf" - ) +def daemon(c): + """ + Runs core-daemon. + """ + with c.cd("daemon"): + poetry = c.run("which poetry").stdout.strip() + c.run( + f"sudo {poetry} run scripts/core-daemon " + "-f data/core.conf -l data/logging.conf" + ) @task -def core_pygui(c): - c.run("poetry run python3 scripts/core-pygui") +def gui(c): + """ + Run core-pygui. + """ + with c.cd("daemon"): + c.run("poetry run scripts/core-pygui") + + +@task +def test(c): + """ + Run core tests. + """ + with c.cd("daemon"): + poetry = c.run("which poetry").stdout.strip() + c.run(f"sudo {poetry} run pytest -v --lf -x tests", pty=True) + + +@task +def test_mock(c): + """ + Run core tests using mock to avoid running as sudo. + """ + with c.cd("daemon"): + c.run("poetry run pytest -v --mock --lf -x tests", pty=True) + + +@task +def test_emane(c): + """ + Run core emane tests. + """ + with c.cd("daemon"): + poetry = c.run("which poetry").stdout.strip() + c.run(f"sudo {poetry} run pytest -v --lf -x tests/emane", pty=True) From 43b586a1a1d65d93dd028cae3de044899d56cf76 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 8 Jul 2020 08:24:23 -0700 Subject: [PATCH 117/210] daemon: updated xml to write and read session configured distributed servers, updated pygui to send servers before session start or saving xml --- daemon/core/gui/coreclient.py | 6 ++++++ daemon/core/xml/corexml.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 7cf8b1238..9479cbcbe 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -510,6 +510,10 @@ def edit_node(self, core_node: Node) -> None: except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) + def send_servers(self) -> None: + for server in self.servers.values(): + self.client.add_session_server(self.session_id, server.name, server.address) + def start_session(self) -> StartSessionResponse: self.ifaces_manager.reset_mac() nodes = [x.core_node for x in self.canvas_nodes.values()] @@ -538,6 +542,7 @@ def start_session(self) -> StartSessionResponse: emane_config = None response = StartSessionResponse(result=False) try: + self.send_servers() response = self.client.start_session( self.session_id, nodes, @@ -749,6 +754,7 @@ def send_data(self) -> None: """ Send to daemon all session info, but don't start the session """ + self.send_servers() self.create_nodes_and_links() for config_proto in self.get_wlan_configs_proto(): self.client.set_wlan_config( diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index d1c43d9b3..7e3b35a21 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -284,6 +284,7 @@ def write_session(self) -> None: self.write_service_configs() self.write_configservice_configs() self.write_session_origin() + self.write_servers() self.write_session_hooks() self.write_session_options() self.write_session_metadata() @@ -318,6 +319,15 @@ def write_session_origin(self) -> None: add_attribute(origin, "y", y) add_attribute(origin, "z", z) + def write_servers(self) -> None: + servers = etree.Element("servers") + for server in self.session.distributed.servers.values(): + server_element = etree.SubElement(servers, "server") + add_attribute(server_element, "name", server.name) + add_attribute(server_element, "address", server.host) + if servers.getchildren(): + self.scenario.append(servers) + def write_session_hooks(self) -> None: # hook scripts hooks = etree.Element("session_hooks") @@ -572,6 +582,7 @@ def read(self, file_name: str) -> None: self.read_session_metadata() self.read_session_options() self.read_session_hooks() + self.read_servers() self.read_session_origin() self.read_service_configs() self.read_mobility_configs() @@ -635,6 +646,16 @@ def read_session_hooks(self) -> None: logging.info("reading hook: state(%s) name(%s)", state, name) self.session.add_hook(state, name, data) + def read_servers(self) -> None: + servers = self.scenario.find("servers") + if servers is None: + return + for server in servers.iterchildren(): + name = server.get("name") + address = server.get("address") + logging.info("reading server: name(%s) address(%s)", name, address) + self.session.distributed.add_server(name, address) + def read_session_origin(self) -> None: session_origin = self.scenario.find("session_origin") if session_origin is None: From 7a21affbd4514b56f187a2e9cb93ec9066bec1fa Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 8 Jul 2020 08:46:30 -0700 Subject: [PATCH 118/210] pygui: update nodes to display assigned server name when not localhost --- daemon/core/gui/graph/node.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index f765816d4..7b5cd2f3d 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -42,10 +42,11 @@ def __init__( x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE ) label_y = self._get_label_y() + label = self.get_label() self.text_id: int = self.canvas.create_text( x, label_y, - text=self.core_node.name, + text=label, tags=tags.NODE_LABEL, font=self.app.icon_text_font, fill="#0000CD", @@ -123,9 +124,16 @@ def delete_antennas(self) -> None: self.antennas.clear() self.antenna_images.clear() + def get_label(self) -> str: + label = self.core_node.name + if self.core_node.server: + label = f"{self.core_node.name}({self.core_node.server})" + return label + def redraw(self) -> None: self.canvas.itemconfig(self.id, image=self.image) - self.canvas.itemconfig(self.text_id, text=self.core_node.name) + label = self.get_label() + self.canvas.itemconfig(self.text_id, text=label) for edge in self.edges: edge.redraw() From 9fed90832284a716640248c90e3365f852e30ca4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 8 Jul 2020 11:56:23 -0700 Subject: [PATCH 119/210] docs: adjustments to distributed documentation to be more complete --- docs/distributed.md | 47 +++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/docs/distributed.md b/docs/distributed.md index f36efc722..ad3d61f8c 100644 --- a/docs/distributed.md +++ b/docs/distributed.md @@ -12,6 +12,28 @@ run on one of the emulation servers or on a separate machine. Each machine that will act as an emulation will require the installation of a distributed CORE package and some configuration to allow SSH as root. +## CORE Configuration + +CORE configuration settings required for using distributed functionality. + +Edit **/etc/core/core.conf** or specific configuration file being used. + +```shell +# uncomment and set this to the address that remote servers +# use to get back to the main host, example below +distributed_address = 129.168.0.101 +``` + +### EMANE Specific Configurations + +EMANE needs to have controlnet configured in **core.conf** in order to startup correctly. +The names before the addresses need to match the names of distributed servers configured. + +```shell +controlnet = core1:172.16.1.0/24 core2:172.16.2.0/24 core3:172.16.3.0/24 core4:172.16.4.0/24 core5:172.16.5.0/24 +emane_event_generate = True +``` + ## Configuring SSH Distributed CORE works using the python fabric library to run commands on @@ -88,6 +110,16 @@ PermitRootLogin without-password sudo systemctl restart sshd ``` +### Fabric Config File + +Make sure the value used below is the absolute path to the file +generated above **~/.ssh/core**" + +Add/update the fabric configuration file **/etc/fabric.yml**: +```yaml +connect_kwargs: {"key_filename": "/home/user/.ssh/core"} +``` + ## Add Emulation Servers in GUI Within the core-gui navigate to menu option: @@ -152,26 +184,13 @@ to arrange the topology such that the number of tunnels is minimized. The tunnels carry data between servers to connect nodes as specified in the topology. These tunnels are created using GRE tunneling, similar to the Tunnel Tool. -### EMANE Configuration and Issues - -EMANE needs to have controlnet configured in **core.conf** in order to startup correctly. -The names before the addresses need to match the servers configured in -**~/.core/servers.conf** previously. - -```shell -controlnet = core1:172.16.1.0/24 core2:172.16.2.0/24 core3:172.16.3.0/24 core4:172.16.4.0/24 core5:172.16.5.0/24 -``` - -```shell -emane_event_generate = True -``` - ## Distributed Checklist 1. Install CORE on master server 1. Install distributed CORE package on all servers needed 1. Installed and configure public-key SSH access on all servers (if you want to use double-click shells or Widgets.) for both the GUI user (for terminals) and root for running CORE commands +1. Update CORE configuration as needed 1. Choose the servers that participate in distributed emulation. 1. Assign nodes to desired servers, empty for master server. 1. Press the **Start** button to launch the distributed emulation. From a236ea2455901140ace5d1cd809f792811e24ec7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 9 Jul 2020 23:01:28 -0700 Subject: [PATCH 120/210] updates to poetry based installation --- configure.ac | 12 ------ docs/install2.md | 27 ++++++++++++ install2.sh | 30 +++++++++++++ tasks.py | 107 +++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 docs/install2.md create mode 100755 install2.sh diff --git a/configure.ac b/configure.ac index ae2d0c8d4..021027608 100644 --- a/configure.ac +++ b/configure.ac @@ -167,18 +167,6 @@ if test "x$enable_daemon" = "xyes"; then if test "x$ovs_of_path" = "xno" ; then AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS mode]) fi - - CFLAGS_save=$CFLAGS - CPPFLAGS_save=$CPPFLAGS - if test "x$PYTHON_INCLUDE_DIR" = "x"; then - PYTHON_INCLUDE_DIR=`$PYTHON -c "import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())"` - fi - CFLAGS="-I$PYTHON_INCLUDE_DIR" - CPPFLAGS="-I$PYTHON_INCLUDE_DIR" - AC_CHECK_HEADERS([Python.h], [], - AC_MSG_ERROR([Python bindings require Python development headers (try installing your 'python-devel' or 'python-dev' package)])) - CFLAGS=$CFLAGS_save - CPPFLAGS=$CPPFLAGS_save fi if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then diff --git a/docs/install2.md b/docs/install2.md new file mode 100644 index 000000000..b8f7c0994 --- /dev/null +++ b/docs/install2.md @@ -0,0 +1,27 @@ +# Commands Used Ubuntu + +```shell +# get pip +sudo apt install python3-pip python3-venv + +# install pipx +python3 -m pip install --user pipx +python3 -m pipx ensurepath + +# install invoke +pipx install invoke + +# install core +inv install + +# run daemon +inv daemon + +# run gui +inv gui +``` + +Commands Used CentOS + +```shell +``` diff --git a/install2.sh b/install2.sh new file mode 100755 index 000000000..a8366670e --- /dev/null +++ b/install2.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# exit on error +set -e + +# detect os/ver for install type +os="" +if [[ -f /etc/os-release ]]; then + . /etc/os-release + os=${ID} +fi + +echo "installing CORE for ${os}" +case ${os} in +"ubuntu") + sudo apt install -y python3-pip + + ;; +"centos") + sudo yum install -y python3-pip + ;; +*) + echo "unknown OS ID ${os} cannot install" + ;; +esac + +python3 -m pip install --user pipx +python3 -m pipx ensurepath +python3 -m pipx install invoke +inv install diff --git a/tasks.py b/tasks.py index b19e8925d..37b2e10c1 100644 --- a/tasks.py +++ b/tasks.py @@ -1,52 +1,123 @@ +import os + from invoke import task +UBUNTU = "ubuntu" +CENTOS = "centos" +DAEMON_DIR = "daemon" +VCMD_DIR = "netns" +GUI_DIR = "gui" + + +def get_python(c): + with c.cd(DAEMON_DIR): + venv = c.run("poetry env info -p", hide=True).stdout.strip() + return os.path.join(venv, "bin", "python") + + +def get_pytest(c): + with c.cd(DAEMON_DIR): + venv = c.run("poetry env info -p", hide=True).stdout.strip() + return os.path.join(venv, "bin", "pytest") + + +def get_os(): + d = {} + with open("/etc/os-release", "r") as f: + for line in f.readlines(): + line = line.strip() + key, value = line.split("=") + d[key] = value + return d["ID"] + + +@task +def install(c): + """ + install core + """ + # get os + os_name = get_os() + # install system dependencies + print("installing system dependencies...") + if os_name == UBUNTU: + c.run( + "sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 " + "ethtool tk python3-tk", hide=True + ) + else: + raise Exception(f"unsupported os: {os_name}") + # install grpcio-tools for building proto files + print("installing grpcio-tools...") + c.run("python3 -m pip install --user grpcio-tools", hide=True) + # build core + print("building core...") + c.run("./bootstrap.sh", hide=True) + c.run("./configure", hide=True) + c.run("make -j", hide=True) + # install vcmd + print("installing vcmd...") + with c.cd(VCMD_DIR): + c.run("sudo make install", hide=True) + # install vcmd + print("installing gui...") + with c.cd(GUI_DIR): + c.run("sudo make install", hide=True) + # install poetry environment + print("installing poetry...") + c.run("pipx install poetry", hide=True) + with c.cd(DAEMON_DIR): + print("installing core environment using poetry...") + c.run("poetry install", hide=True) + @task def daemon(c): """ - Runs core-daemon. + start core-daemon """ - with c.cd("daemon"): - poetry = c.run("which poetry").stdout.strip() + python = get_python(c) + with c.cd(DAEMON_DIR): c.run( - f"sudo {poetry} run scripts/core-daemon " - "-f data/core.conf -l data/logging.conf" + f"sudo {python} scripts/core-daemon " + "-f data/core.conf -l data/logging.conf", + pty=True ) @task def gui(c): """ - Run core-pygui. + start core-pygui """ - with c.cd("daemon"): - c.run("poetry run scripts/core-pygui") + with c.cd(DAEMON_DIR): + c.run("poetry run scripts/core-pygui", pty=True) @task def test(c): """ - Run core tests. + run core tests """ - with c.cd("daemon"): - poetry = c.run("which poetry").stdout.strip() - c.run(f"sudo {poetry} run pytest -v --lf -x tests", pty=True) + pytest = get_pytest(c) + with c.cd(DAEMON_DIR): + c.run(f"sudo {pytest} -v --lf -x tests", pty=True) @task def test_mock(c): """ - Run core tests using mock to avoid running as sudo. + run core tests using mock to avoid running as sudo """ - with c.cd("daemon"): + with c.cd(DAEMON_DIR): c.run("poetry run pytest -v --mock --lf -x tests", pty=True) @task def test_emane(c): """ - Run core emane tests. + run core emane tests """ - with c.cd("daemon"): - poetry = c.run("which poetry").stdout.strip() - c.run(f"sudo {poetry} run pytest -v --lf -x tests/emane", pty=True) + pytest = get_pytest(c) + with c.cd(DAEMON_DIR): + c.run(f"{pytest} -v --lf -x tests/emane", pty=True) From 139323146e437e796603cd01178f29a8af667d3d Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 9 Jul 2020 23:27:05 -0700 Subject: [PATCH 121/210] Update install2.sh update to account for missing python3-venv package and updating PATH in script to run newly installed commands --- install2.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install2.sh b/install2.sh index a8366670e..496906e8a 100755 --- a/install2.sh +++ b/install2.sh @@ -13,8 +13,7 @@ fi echo "installing CORE for ${os}" case ${os} in "ubuntu") - sudo apt install -y python3-pip - + sudo apt install -y python3-pip python3-venv ;; "centos") sudo yum install -y python3-pip @@ -26,5 +25,6 @@ esac python3 -m pip install --user pipx python3 -m pipx ensurepath -python3 -m pipx install invoke +export PATH=$PATH:~/.local/bin +pipx install invoke inv install From d4ac9e618f1177da57fcad0c75bbb1f8761fa953 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 08:32:47 -0700 Subject: [PATCH 122/210] improvements to invoke tasks for installation --- tasks.py | 121 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 30 deletions(-) diff --git a/tasks.py b/tasks.py index 37b2e10c1..9949512c7 100644 --- a/tasks.py +++ b/tasks.py @@ -1,74 +1,135 @@ import os +import sys +from enum import Enum -from invoke import task +from invoke import task, Context -UBUNTU = "ubuntu" -CENTOS = "centos" -DAEMON_DIR = "daemon" -VCMD_DIR = "netns" -GUI_DIR = "gui" +DAEMON_DIR: str = "daemon" +VCMD_DIR: str = "netns" +GUI_DIR: str = "gui" -def get_python(c): +class OsName(Enum): + UBUNTU = "ubuntu" + CENTOS = "centos" + + +class OsLike(Enum): + DEBIAN = "debian" + + +class OsInfo: + def __init__(self, name: OsName, like: OsLike, version: str) -> None: + self.name: OsName = name + self.like: OsLike = like + self.version: str = version + + +def get_python(c: Context) -> str: with c.cd(DAEMON_DIR): venv = c.run("poetry env info -p", hide=True).stdout.strip() return os.path.join(venv, "bin", "python") -def get_pytest(c): +def get_pytest(c: Context) -> str: with c.cd(DAEMON_DIR): venv = c.run("poetry env info -p", hide=True).stdout.strip() return os.path.join(venv, "bin", "pytest") -def get_os(): +def get_os() -> OsInfo: d = {} with open("/etc/os-release", "r") as f: for line in f.readlines(): line = line.strip() key, value = line.split("=") - d[key] = value - return d["ID"] - - -@task -def install(c): - """ - install core - """ - # get os - os_name = get_os() - # install system dependencies + d[key] = value.strip('"') + name_value = d["ID"] + like_value = d["ID_LIKE"] + try: + name = OsName(name_value) + like = OsLike(like_value) + except ValueError: + print(f"unsupported os({name_value}) like({like_value})") + sys.exit(1) + version = d["VERSION_ID"] + return OsInfo(name, like, version) + + +def install_system(c: Context, os_info: OsInfo) -> None: print("installing system dependencies...") - if os_name == UBUNTU: + if os_info.like == OsLike.DEBIAN: c.run( "sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 " "ethtool tk python3-tk", hide=True ) - else: - raise Exception(f"unsupported os: {os_name}") - # install grpcio-tools for building proto files + + +def install_grpcio(c: Context) -> None: print("installing grpcio-tools...") c.run("python3 -m pip install --user grpcio-tools", hide=True) - # build core + + +def build(c: Context) -> None: print("building core...") c.run("./bootstrap.sh", hide=True) c.run("./configure", hide=True) c.run("make -j", hide=True) - # install vcmd + + +def install_core(c: Context) -> None: print("installing vcmd...") with c.cd(VCMD_DIR): c.run("sudo make install", hide=True) - # install vcmd print("installing gui...") with c.cd(GUI_DIR): c.run("sudo make install", hide=True) - # install poetry environment + + +def install_poetry(c: Context, dev: bool) -> None: print("installing poetry...") c.run("pipx install poetry", hide=True) + args = "" if dev else "--no-dev" with c.cd(DAEMON_DIR): print("installing core environment using poetry...") - c.run("poetry install", hide=True) + c.run(f"poetry install {args}", hide=True) + if dev: + c.run("poetry run pre-commit install") + + +def install_ospf_mdr(c: Context, os_info: OsInfo) -> None: + if c.run("which zebra"): + print("quagga already installed, skipping ospf mdr") + return + if os_info.like == OsLike.DEBIAN: + c.run("sudo apt install -y libtool gawk libreadline-dev") + clone_dir = "/tmp/ospf-mdr" + c.run( + f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}" + ) + with c.cd(clone_dir): + c.run("./bootstrap.sh") + c.run( + "./configure --disable-doc --enable-user=root --enable-group=root " + "--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh " + "--localstatedir=/var/run/quagga" + ) + c.run("make -j") + c.run("sudo make install") + + +@task +def install(c, dev=False): + """ + install core + """ + os_info = get_os() + install_system(c, os_info) + install_grpcio(c) + build(c) + install_core(c) + install_poetry(c, dev) + install_ospf_mdr(c, os_info) @task From 8357cddbab3db1d7244ede08f1cce1cd29dd8463 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 08:51:40 -0700 Subject: [PATCH 123/210] added developer and verbose flags to poetry install --- install2.sh | 24 ++++++++++++++++++++- tasks.py | 61 ++++++++++++++++++++++++++++------------------------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/install2.sh b/install2.sh index 496906e8a..4efaee53a 100755 --- a/install2.sh +++ b/install2.sh @@ -10,6 +10,28 @@ if [[ -f /etc/os-release ]]; then os=${ID} fi +# parse arguments +dev="" +verbose="" +while getopts "drv:" opt; do + case ${opt} in + d) + dev="-d" + ;; + v) + verbose="-v" + ;; + \?) + echo "script usage: $(basename $0) [-d] [-v]" >&2 + echo "" >&2 + echo "-v enable verbose install" >&2 + echo "-d enable developer install" >&2 + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + echo "installing CORE for ${os}" case ${os} in "ubuntu") @@ -27,4 +49,4 @@ python3 -m pip install --user pipx python3 -m pipx ensurepath export PATH=$PATH:~/.local/bin pipx install invoke -inv install +inv install $(dev) $(verbose) diff --git a/tasks.py b/tasks.py index 9949512c7..da8fef217 100644 --- a/tasks.py +++ b/tasks.py @@ -56,80 +56,83 @@ def get_os() -> OsInfo: return OsInfo(name, like, version) -def install_system(c: Context, os_info: OsInfo) -> None: +def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: print("installing system dependencies...") if os_info.like == OsLike.DEBIAN: c.run( "sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 " - "ethtool tk python3-tk", hide=True + "ethtool tk python3-tk", hide=hide ) -def install_grpcio(c: Context) -> None: +def install_grpcio(c: Context, hide: bool) -> None: print("installing grpcio-tools...") - c.run("python3 -m pip install --user grpcio-tools", hide=True) + c.run("python3 -m pip install --user grpcio-tools", hide=hide) -def build(c: Context) -> None: +def build(c: Context, hide: bool) -> None: print("building core...") - c.run("./bootstrap.sh", hide=True) - c.run("./configure", hide=True) - c.run("make -j", hide=True) + c.run("./bootstrap.sh", hide=hide) + c.run("./configure", hide=hide) + c.run("make -j", hide=hide) -def install_core(c: Context) -> None: +def install_core(c: Context, hide: bool) -> None: print("installing vcmd...") with c.cd(VCMD_DIR): - c.run("sudo make install", hide=True) + c.run("sudo make install", hide=hide) print("installing gui...") with c.cd(GUI_DIR): - c.run("sudo make install", hide=True) + c.run("sudo make install", hide=hide) -def install_poetry(c: Context, dev: bool) -> None: +def install_poetry(c: Context, dev: bool, hide: bool) -> None: print("installing poetry...") - c.run("pipx install poetry", hide=True) + c.run("pipx install poetry", hide=hide) args = "" if dev else "--no-dev" with c.cd(DAEMON_DIR): print("installing core environment using poetry...") - c.run(f"poetry install {args}", hide=True) + c.run(f"poetry install {args}", hide=hide) if dev: c.run("poetry run pre-commit install") -def install_ospf_mdr(c: Context, os_info: OsInfo) -> None: - if c.run("which zebra"): +def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: + if c.run("which zebra", warn=True, hide=hide): print("quagga already installed, skipping ospf mdr") return if os_info.like == OsLike.DEBIAN: - c.run("sudo apt install -y libtool gawk libreadline-dev") + c.run("sudo apt install -y libtool gawk libreadline-dev", hide=hide) clone_dir = "/tmp/ospf-mdr" c.run( - f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}" + f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", + hide=hide ) with c.cd(clone_dir): - c.run("./bootstrap.sh") + c.run("./bootstrap.sh", hide=hide) c.run( "./configure --disable-doc --enable-user=root --enable-group=root " "--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh " - "--localstatedir=/var/run/quagga" + "--localstatedir=/var/run/quagga", + hide=hide ) - c.run("make -j") - c.run("sudo make install") + c.run("make -j", hide=hide) + c.run("sudo make install", hide=hide) @task -def install(c, dev=False): +def install(c, dev=False, verbose=False): """ install core """ + hide = not verbose os_info = get_os() - install_system(c, os_info) - install_grpcio(c) - build(c) - install_core(c) - install_poetry(c, dev) - install_ospf_mdr(c, os_info) + install_system(c, os_info, hide) + install_grpcio(c, hide) + build(c, hide) + install_core(c, hide) + install_poetry(c, dev, hide) + install_ospf_mdr(c, os_info, hide) @task From 41f0c8ef95bd914a92209c6c4a8d19bdc19d5eb1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 08:56:16 -0700 Subject: [PATCH 124/210] fixed bad arguments being passed in install2.sh --- install2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install2.sh b/install2.sh index 4efaee53a..13876776b 100755 --- a/install2.sh +++ b/install2.sh @@ -49,4 +49,4 @@ python3 -m pip install --user pipx python3 -m pipx ensurepath export PATH=$PATH:~/.local/bin pipx install invoke -inv install $(dev) $(verbose) +inv install ${dev} ${verbose} From 7dd2b6668016f6c625a3dc4556d0b75c73050100 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 08:58:09 -0700 Subject: [PATCH 125/210] added message for installing ospf mdr in install task --- tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks.py b/tasks.py index da8fef217..50440e4d6 100644 --- a/tasks.py +++ b/tasks.py @@ -101,6 +101,7 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: if c.run("which zebra", warn=True, hide=hide): print("quagga already installed, skipping ospf mdr") return + print("installing ospf mdr...") if os_info.like == OsLike.DEBIAN: c.run("sudo apt install -y libtool gawk libreadline-dev", hide=hide) clone_dir = "/tmp/ospf-mdr" From 51200cf930ca44415a2326a9574168446d24f7d6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 09:20:13 -0700 Subject: [PATCH 126/210] added more messages to ospf mdr invoke install --- tasks.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 50440e4d6..af8abfba8 100644 --- a/tasks.py +++ b/tasks.py @@ -101,15 +101,17 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: if c.run("which zebra", warn=True, hide=hide): print("quagga already installed, skipping ospf mdr") return - print("installing ospf mdr...") + print("installing ospf mdr dependencies...") if os_info.like == OsLike.DEBIAN: c.run("sudo apt install -y libtool gawk libreadline-dev", hide=hide) + print("cloning ospf mdr...") clone_dir = "/tmp/ospf-mdr" c.run( f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", hide=hide ) with c.cd(clone_dir): + print("building ospf mdr...") c.run("./bootstrap.sh", hide=hide) c.run( "./configure --disable-doc --enable-user=root --enable-group=root " @@ -118,6 +120,7 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: hide=hide ) c.run("make -j", hide=hide) + print("installing ospf mdr...") c.run("sudo make install", hide=hide) From a2a825e91df6ee5da5b29adafdbd41f2ff5ed96f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 09:55:30 -0700 Subject: [PATCH 127/210] better invoke output and removed -j from building ospf mdr --- tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index af8abfba8..59f8b1bd9 100644 --- a/tasks.py +++ b/tasks.py @@ -74,14 +74,14 @@ def build(c: Context, hide: bool) -> None: print("building core...") c.run("./bootstrap.sh", hide=hide) c.run("./configure", hide=hide) - c.run("make -j", hide=hide) + c.run("make", hide=hide) def install_core(c: Context, hide: bool) -> None: - print("installing vcmd...") + print("installing core vcmd...") with c.cd(VCMD_DIR): c.run("sudo make install", hide=hide) - print("installing gui...") + print("installing core gui...") with c.cd(GUI_DIR): c.run("sudo make install", hide=hide) @@ -119,7 +119,7 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: "--localstatedir=/var/run/quagga", hide=hide ) - c.run("make -j", hide=hide) + c.run("make", hide=hide) print("installing ospf mdr...") c.run("sudo make install", hide=hide) From 85cd31ae52a9ef203f6fdac117112f2e5cd0d131 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 10:23:45 -0700 Subject: [PATCH 128/210] fixed install2.sh argument parsing --- install2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install2.sh b/install2.sh index 13876776b..bdfd2f7b4 100755 --- a/install2.sh +++ b/install2.sh @@ -13,7 +13,7 @@ fi # parse arguments dev="" verbose="" -while getopts "drv:" opt; do +while getopts "dv" opt; do case ${opt} in d) dev="-d" From 9b7dce0861beca7a4ae8ab03870026c6a1241da6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 10:27:17 -0700 Subject: [PATCH 129/210] added example output after installation and note about getting a new terminal --- tasks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasks.py b/tasks.py index 59f8b1bd9..8d04dd72b 100644 --- a/tasks.py +++ b/tasks.py @@ -137,6 +137,11 @@ def install(c, dev=False, verbose=False): install_core(c, hide) install_poetry(c, dev, hide) install_ospf_mdr(c, os_info, hide) + print("please open a new terminal or re-login to leverage invoke for running core") + print("# run daemon") + print("inv daemon") + print("# run gui") + print("inv gui") @task From 38e68386970ad2e7579f38ecc257220457e6521c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 10:39:14 -0700 Subject: [PATCH 130/210] avoid empty lines when parsing os-release --- tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasks.py b/tasks.py index 8d04dd72b..05f23d5fb 100644 --- a/tasks.py +++ b/tasks.py @@ -42,6 +42,8 @@ def get_os() -> OsInfo: with open("/etc/os-release", "r") as f: for line in f.readlines(): line = line.strip() + if not line: + continue key, value = line.split("=") d[key] = value.strip('"') name_value = d["ID"] From cd9ecd22570d7ec9f4076e645aa4072bb58f6e18 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 10:45:03 -0700 Subject: [PATCH 131/210] added redhat like os to invoke task --- tasks.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index 05f23d5fb..272db1df0 100644 --- a/tasks.py +++ b/tasks.py @@ -16,13 +16,14 @@ class OsName(Enum): class OsLike(Enum): DEBIAN = "debian" + REDHAT = "rhel fedora" class OsInfo: - def __init__(self, name: OsName, like: OsLike, version: str) -> None: + def __init__(self, name: OsName, like: OsLike, version: float) -> None: self.name: OsName = name self.like: OsLike = like - self.version: str = version + self.version: float = version def get_python(c: Context) -> str: @@ -48,13 +49,16 @@ def get_os() -> OsInfo: d[key] = value.strip('"') name_value = d["ID"] like_value = d["ID_LIKE"] + version_value = d["VERSION_ID"] try: name = OsName(name_value) like = OsLike(like_value) + version = float(version_value) except ValueError: - print(f"unsupported os({name_value}) like({like_value})") + print( + f"unsupported os({name_value}) like({like_value}) version({version_value}" + ) sys.exit(1) - version = d["VERSION_ID"] return OsInfo(name, like, version) From 7821ffb642ab9d5d302e05cb20006c427d4c34e4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 10:51:06 -0700 Subject: [PATCH 132/210] python-devel is needed on centos --- tasks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 272db1df0..938fb5678 100644 --- a/tasks.py +++ b/tasks.py @@ -71,8 +71,10 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: ) -def install_grpcio(c: Context, hide: bool) -> None: +def install_grpcio(c: Context, os_info: OsInfo, hide: bool) -> None: print("installing grpcio-tools...") + if os_info.like == OsLike.REDHAT: + c.run("sudo yum install -y python3-devel", hide=hide) c.run("python3 -m pip install --user grpcio-tools", hide=hide) @@ -138,7 +140,7 @@ def install(c, dev=False, verbose=False): hide = not verbose os_info = get_os() install_system(c, os_info, hide) - install_grpcio(c, hide) + install_grpcio(c, os_info, hide) build(c, hide) install_core(c, hide) install_poetry(c, dev, hide) From 9bf5756a0352be784da5ac38fea3fe5fb248b6ab Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 11:20:27 -0700 Subject: [PATCH 133/210] added invoke system dependencies for redhat --- tasks.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tasks.py b/tasks.py index 938fb5678..d910d5dcd 100644 --- a/tasks.py +++ b/tasks.py @@ -67,14 +67,19 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: if os_info.like == OsLike.DEBIAN: c.run( "sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 " - "ethtool tk python3-tk", hide=hide + "ethtool tk python3-tk", + hide=hide + ) + elif os_info.like == OsLike.REDHAT: + c.run( + "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel " + "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool", + hide=hide ) -def install_grpcio(c: Context, os_info: OsInfo, hide: bool) -> None: +def install_grpcio(c: Context, hide: bool) -> None: print("installing grpcio-tools...") - if os_info.like == OsLike.REDHAT: - c.run("sudo yum install -y python3-devel", hide=hide) c.run("python3 -m pip install --user grpcio-tools", hide=hide) @@ -140,7 +145,7 @@ def install(c, dev=False, verbose=False): hide = not verbose os_info = get_os() install_system(c, os_info, hide) - install_grpcio(c, os_info, hide) + install_grpcio(c, hide) build(c, hide) install_core(c, hide) install_poetry(c, dev, hide) From 626b977505719d23d484a690f745f21101b34fd4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 11:30:12 -0700 Subject: [PATCH 134/210] added ospf mdr redhat dependencies to invoke install --- tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasks.py b/tasks.py index d910d5dcd..0e9f625c0 100644 --- a/tasks.py +++ b/tasks.py @@ -117,6 +117,8 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: print("installing ospf mdr dependencies...") if os_info.like == OsLike.DEBIAN: c.run("sudo apt install -y libtool gawk libreadline-dev", hide=hide) + elif os_info.like == OsLike.REDHAT: + c.run("sudo yum install -y libtool gawk readline-devel", hide=hide) print("cloning ospf mdr...") clone_dir = "/tmp/ospf-mdr" c.run( From fe362a10d6b97e0d3aea5c11e2d2f9ffdefc14a9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:05:11 -0700 Subject: [PATCH 135/210] poetry changes to help force installing grpcio from binary packages, causing long build times on centos --- daemon/poetry.lock | 156 ++++++++++++++++++++++++------------------ daemon/pyproject.toml | 2 +- 2 files changed, 91 insertions(+), 67 deletions(-) diff --git a/daemon/poetry.lock b/daemon/poetry.lock index c5e1ebb65..c72bc364d 100644 --- a/daemon/poetry.lock +++ b/daemon/poetry.lock @@ -180,7 +180,7 @@ description = "HTTP/2-based RPC framework" name = "grpcio" optional = false python-versions = "*" -version = "1.29.0" +version = "1.27.2" [package.dependencies] six = ">=1.5.2" @@ -191,10 +191,10 @@ description = "Protobuf code generator for gRPC" name = "grpcio-tools" optional = false python-versions = "*" -version = "1.29.0" +version = "1.27.2" [package.dependencies] -grpcio = ">=1.29.0" +grpcio = ">=1.27.2" protobuf = ">=3.5.0.post1" [[package]] @@ -602,7 +602,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "ff2407f8ca447047101b8e0c8656027d07d2f15e51b3a950f2c2d789f929da6b" +content-hash = "260c6612feb7c884d03b3b98e5fb22ad4d06a58559876f239bd5c677d14a7ba1" python-versions = "^3.6" [metadata.files] @@ -725,70 +725,94 @@ flake8 = [ {file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"}, ] grpcio = [ - {file = "grpcio-1.29.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e90f3d11185c36593186e5ff1f581acc6ddfa4190f145b0366e579de1f52803b"}, - {file = "grpcio-1.29.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5024b26e17a1bfc9390fb3b8077bf886eee02970af780fd23072970ef08cefe8"}, - {file = "grpcio-1.29.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:23bc395a32c2465564cb242e48bdd2fdbe5a4aebf307649a800da1b971ee7f29"}, - {file = "grpcio-1.29.0-cp27-cp27m-win32.whl", hash = "sha256:886d48c32960b39e059494637eb0157a694956248d03b0de814447c188b74799"}, - {file = "grpcio-1.29.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da0ca9b1089d00e39a8b83deec799a4e5c37ec1b44d804495424acde50531868"}, - {file = "grpcio-1.29.0-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:ebf0ccb782027ef9e213e03b6d00bbd8dabd80959db7d468c0738e6d94b5204c"}, - {file = "grpcio-1.29.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2637ce96b7c954d2b71060f50eb4c72f81668f1b2faa6cbdc74677e405978901"}, - {file = "grpcio-1.29.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:75b2247307a7ecaf6abc9eb2bd04af8f88816c111b87bf0044d7924396e9549c"}, - {file = "grpcio-1.29.0-cp35-cp35m-linux_armv7l.whl", hash = "sha256:7bf3cb1e0f4a9c89f7b748583b994bdce183103d89d5ff486da48a7668a052c7"}, - {file = "grpcio-1.29.0-cp35-cp35m-macosx_10_7_intel.whl", hash = "sha256:a6dddb177b3cfa0cfe299fb9e07d6a3382cc79466bef48fe9c4326d5c5b1dcb8"}, - {file = "grpcio-1.29.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:b49f243936b0f6ae8eb6adf88a1e54e736f1c6724a1bff6b591d105d708263ad"}, - {file = "grpcio-1.29.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9cfb4b71cc3c8757f137d47000f9d90d4bd818733f9ab4f78bd447e052a4cb9a"}, - {file = "grpcio-1.29.0-cp35-cp35m-win32.whl", hash = "sha256:10cdc8946a7c2284bbc8e16d346eaa2beeaae86ea598f345df86d4ef7dfedb84"}, - {file = "grpcio-1.29.0-cp35-cp35m-win_amd64.whl", hash = "sha256:806c9759f5589b3761561187408e0313a35c5c53f075c7590effab8d27d67dfe"}, - {file = "grpcio-1.29.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:57c8cc2ae8cb94c3a89671af7e1380a4cdfcd6bab7ba303f4461ec32ded250ae"}, - {file = "grpcio-1.29.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:97b72bf2242a351a89184134adbb0ae3b422e6893c6c712bc7669e2eab21501b"}, - {file = "grpcio-1.29.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:517538a54afdd67162ea2af1ac3326c0752c5d13e6ddadbc4885f6a28e91ab28"}, - {file = "grpcio-1.29.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:eede3039c3998e2cc0f6713f4ac70f235bd32967c9b958a17bf937aceebc12c3"}, - {file = "grpcio-1.29.0-cp36-cp36m-win32.whl", hash = "sha256:54e4658c09084b09cd83a5ea3a8bce78e4031ff1010bb8908c399a22a76a6f08"}, - {file = "grpcio-1.29.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7e02a7c40304eecee203f809a982732bd37fad4e798acad98fe73c66e44ff2db"}, - {file = "grpcio-1.29.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ff7931241351521b8df01d7448800ce0d59364321d8d82c49b826d455678ff08"}, - {file = "grpcio-1.29.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:5fd9ffe938e9225c654c60eb21ff011108cc27302db85200413807e0eda99a4a"}, - {file = "grpcio-1.29.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:9ef0370bcf629ece4e7e37796e4604e2514b920669be2911fc3f9c163a73a57b"}, - {file = "grpcio-1.29.0-cp37-cp37m-win32.whl", hash = "sha256:3d8c510b6eabce5192ce126003d74d7751c7218d3e2ad39fcf02400d7ec43abe"}, - {file = "grpcio-1.29.0-cp37-cp37m-win_amd64.whl", hash = "sha256:81bbf78a399e0ee516c81ddad8601f12af3fc9b30f2e4b2fbd64efd327304a4d"}, - {file = "grpcio-1.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:80e9f9f6265149ca7c84e1c8c31c2cf3e2869c45776fbe8880a3133a11d6d290"}, - {file = "grpcio-1.29.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:524ae8d3da61b856cf08abb3d0947df05402919e4be1f88328e0c1004031f72e"}, - {file = "grpcio-1.29.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c3a0ef12ee86f6e72db50e01c3dba7735a76d8c30104b9b0f7fd9d65ceb9d93f"}, - {file = "grpcio-1.29.0-cp38-cp38-win32.whl", hash = "sha256:97fcbdf1f12e0079d26db73da11ee35a09adc870b1e72fbff0211f6a8003a4e8"}, - {file = "grpcio-1.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:b85f355fc24b68a6c52f2750e7141110d1fcd07dfdc9b282de0000550fe0511b"}, - {file = "grpcio-1.29.0.tar.gz", hash = "sha256:a97ea91e31863c9a3879684b5fb3c6ab4b17c5431787548fc9f52b9483ea9c25"}, + {file = "grpcio-1.27.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbec0a3a154dbf2eb85b38abaddf24964fa1c059ee0a4ad55d6f39211b1a4bca"}, + {file = "grpcio-1.27.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1ef949b15a1f5f30651532a9b54edf3bd7c0b699a10931505fa2c80b2d395942"}, + {file = "grpcio-1.27.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:ed123037896a8db6709b8ad5acc0ed435453726ea0b63361d12de369624c2ab5"}, + {file = "grpcio-1.27.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:f9d632ce9fd485119c968ec6a7a343de698c5e014d17602ae2f110f1b05925ed"}, + {file = "grpcio-1.27.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:80c3d1ce8820dd819d1c9d6b63b6f445148480a831173b572a9174a55e7abd47"}, + {file = "grpcio-1.27.2-cp27-cp27m-win32.whl", hash = "sha256:07f82aefb4a56c7e1e52b78afb77d446847d27120a838a1a0489260182096045"}, + {file = "grpcio-1.27.2-cp27-cp27m-win_amd64.whl", hash = "sha256:28f27c64dd699b8b10f70da5f9320c1cffcaefca7dd76275b44571bd097f276c"}, + {file = "grpcio-1.27.2-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:a25b84e10018875a0f294a7649d07c43e8bc3e6a821714e39e5cd607a36386d7"}, + {file = "grpcio-1.27.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:57949756a3ce1f096fa2b00f812755f5ab2effeccedb19feeb7d0deafa3d1de7"}, + {file = "grpcio-1.27.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f3614dabd2cc8741850597b418bcf644d4f60e73615906c3acc407b78ff720b3"}, + {file = "grpcio-1.27.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:25c77692ea8c0929d4ad400ea9c3dcbcc4936cee84e437e0ef80da58fa73d88a"}, + {file = "grpcio-1.27.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5dab393ab96b2ce4012823b2f2ed4ee907150424d2f02b97bd6f8dd8f17cc866"}, + {file = "grpcio-1.27.2-cp35-cp35m-linux_armv7l.whl", hash = "sha256:bb2987eb3af9bcf46019be39b82c120c3d35639a95bc4ee2d08f36ecdf469345"}, + {file = "grpcio-1.27.2-cp35-cp35m-macosx_10_7_intel.whl", hash = "sha256:6f328a3faaf81a2546a3022b3dfc137cc6d50d81082dbc0c94d1678943f05df3"}, + {file = "grpcio-1.27.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:5ebc13451246de82f130e8ee7e723e8d7ae1827f14b7b0218867667b1b12c88d"}, + {file = "grpcio-1.27.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:355bd7d7ce5ff2917d217f0e8ddac568cb7403e1ce1639b35a924db7d13a39b6"}, + {file = "grpcio-1.27.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:d1e5563e3b7f844dbc48d709c9e4a75647e11d0387cc1fa0c861d3e9d34bc844"}, + {file = "grpcio-1.27.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:1ec8fc865d8da6d0713e2092a27eee344cd54628b2c2065a0e77fff94df4ae00"}, + {file = "grpcio-1.27.2-cp35-cp35m-win32.whl", hash = "sha256:706e2dea3de33b0d8884c4d35ecd5911b4ff04d0697c4138096666ce983671a6"}, + {file = "grpcio-1.27.2-cp35-cp35m-win_amd64.whl", hash = "sha256:d18b4c8cacbb141979bb44355ee5813dd4d307e9d79b3a36d66eca7e0a203df8"}, + {file = "grpcio-1.27.2-cp36-cp36m-linux_armv7l.whl", hash = "sha256:02aef8ef1a5ac5f0836b543e462eb421df6048a7974211a906148053b8055ea6"}, + {file = "grpcio-1.27.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b78af4d42985ab3143d9882d0006f48d12f1bc4ba88e78f23762777c3ee64571"}, + {file = "grpcio-1.27.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9c0669ba9aebad540fb05a33beb7e659ea6e5ca35833fc5229c20f057db760e8"}, + {file = "grpcio-1.27.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:68a149a0482d0bc697aac702ec6efb9d380e0afebf9484db5b7e634146528371"}, + {file = "grpcio-1.27.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:a71138366d57901597bfcc52af7f076ab61c046f409c7b429011cd68de8f9fe6"}, + {file = "grpcio-1.27.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9e9cfe55dc7ac2aa47e0fd3285ff829685f96803197042c9d2f0fb44e4b39b2c"}, + {file = "grpcio-1.27.2-cp36-cp36m-win32.whl", hash = "sha256:d22c897b65b1408509099f1c3334bd3704f5e4eb7c0486c57d0e212f71cb8f54"}, + {file = "grpcio-1.27.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c59b9280284b791377b3524c8e39ca7b74ae2881ba1a6c51b36f4f1bb94cee49"}, + {file = "grpcio-1.27.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e545908bcc2ae28e5b190ce3170f92d0438cf26a82b269611390114de0106eb"}, + {file = "grpcio-1.27.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6db7ded10b82592c472eeeba34b9f12d7b0ab1e2dcad12f081b08ebdea78d7d6"}, + {file = "grpcio-1.27.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4d3b6e66f32528bf43ca2297caca768280a8e068820b1c3dca0fcf9f03c7d6f1"}, + {file = "grpcio-1.27.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:586d931736912865c9790c60ca2db29e8dc4eace160d5a79fec3e58df79a9386"}, + {file = "grpcio-1.27.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c03ce53690fe492845e14f4ab7e67d5a429a06db99b226b5c7caa23081c1e2bb"}, + {file = "grpcio-1.27.2-cp37-cp37m-win32.whl", hash = "sha256:209927e65395feb449783943d62a3036982f871d7f4045fadb90b2d82b153ea8"}, + {file = "grpcio-1.27.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9713578f187fb1c4d00ac554fe1edcc6b3ddd62f5d4eb578b81261115802df8e"}, + {file = "grpcio-1.27.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4efde5524579a9ce0459ca35a57a48ca878a4973514b8bb88cb80d7c9d34c85"}, + {file = "grpcio-1.27.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:fb62996c61eeff56b59ab8abfcaa0859ec2223392c03d6085048b576b567459b"}, + {file = "grpcio-1.27.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a22daaf30037b8e59d6968c76fe0f7ff062c976c7a026e92fbefc4c4bf3fc5a4"}, + {file = "grpcio-1.27.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:4a0a33ada3f6f94f855f92460896ef08c798dcc5f17d9364d1735c5adc9d7e4a"}, + {file = "grpcio-1.27.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8111b61eee12d7af5c58f82f2c97c2664677a05df9225ef5cbc2f25398c8c454"}, + {file = "grpcio-1.27.2-cp38-cp38-win32.whl", hash = "sha256:5121fa96c79fc0ec81825091d0be5c16865f834f41b31da40b08ee60552f9961"}, + {file = "grpcio-1.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:1cff47297ee614e7ef66243dc34a776883ab6da9ca129ea114a802c5e58af5c1"}, + {file = "grpcio-1.27.2.tar.gz", hash = "sha256:5ae532b93cf9ce5a2a549b74a2c35e3b690b171ece9358519b3039c7b84c887e"}, ] grpcio-tools = [ - {file = "grpcio-tools-1.29.0.tar.gz", hash = "sha256:0f681c1ebd5472b804baa391b16dc59d92b065903999566f4776bfbd010bcec9"}, - {file = "grpcio_tools-1.29.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b504e844e6f3610f279e0fba719052a73d5acc858a82d5a1151155b3c2304478"}, - {file = "grpcio_tools-1.29.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c52bcc2e5e9d93b805e6f292e543cbabeb9a751dc9d4d451c39d4c30ee311142"}, - {file = "grpcio_tools-1.29.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5beffd530b496866b8e8dc811e942815a6e637669350c1341b5972bb692465cc"}, - {file = "grpcio_tools-1.29.0-cp27-cp27m-win32.whl", hash = "sha256:49dcf4c11ba2766d065c90a61eb1cefc55d5d094f93c1f66a4d98bfcbc5f740c"}, - {file = "grpcio_tools-1.29.0-cp27-cp27m-win_amd64.whl", hash = "sha256:bab2a3d627f114091a758d8a7ae48af54bff717f84bb34538fed5114982e73a5"}, - {file = "grpcio_tools-1.29.0-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:2a1f27a21d09e864cdfcff22265af86d9a548ea9a775e5d6a27d7abb71c3b5aa"}, - {file = "grpcio_tools-1.29.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:56aade8ed52a6cca74a4703279aaae4aa2e2b87d0ccb5778f95d31267e74fc6b"}, - {file = "grpcio_tools-1.29.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:78075ee7459001cf5c81b1f2e3f047b63d35ed018b9e15e3abeda59b70af0a4e"}, - {file = "grpcio_tools-1.29.0-cp35-cp35m-linux_armv7l.whl", hash = "sha256:1626cd01a484f29cc9b33c3902851490149d40a550b92a382978571ca7e712cf"}, - {file = "grpcio_tools-1.29.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2f1d80e3988d86477633fb39442a2310513d02fcc48881b359257a4be3cfd336"}, - {file = "grpcio_tools-1.29.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:8ffdcb1cbbc1bdfe249eb08c9fc6557b4f83b9f6145b5914bfd2973013d6dc1f"}, - {file = "grpcio_tools-1.29.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:7e52c8ed5e0157ff85493f93540e3c897c7d97be03afc73230d1022ba7b80528"}, - {file = "grpcio_tools-1.29.0-cp35-cp35m-win32.whl", hash = "sha256:f464d2efe04a46a17cf9493d67e6839aa535bb8a904cc6a2b588f1b156c9265d"}, - {file = "grpcio_tools-1.29.0-cp35-cp35m-win_amd64.whl", hash = "sha256:9de112c090ab67e90b8c36eee5876278c8d037bf7c55052848886c1e8a2dd1c2"}, - {file = "grpcio_tools-1.29.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:38ab9e8afdf34289eab85ce2343c451c36837bf2521b927b30d9a845304abf4c"}, - {file = "grpcio_tools-1.29.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1038b3d6cfd7206caf7c0a54ed06896e2aeb0a7d213a40d9000a70595e2fca21"}, - {file = "grpcio_tools-1.29.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:2a681ebfde0d83b70117cac745a97a3e5dc258fd817c1c1dd2bf99579b663a28"}, - {file = "grpcio_tools-1.29.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:47d13ddbbc2bd0e21a6109f74e731049b1d8738b5d0124580efca3721fe77fd2"}, - {file = "grpcio_tools-1.29.0-cp36-cp36m-win32.whl", hash = "sha256:fb9c46b8a0ee1a5990f29d891d6023cb92fdab9aed408194667df04f72e9caf6"}, - {file = "grpcio_tools-1.29.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f672a606a59145bacc58cf4c4bb407f107abe1289f607c09e9224c99e897ed1a"}, - {file = "grpcio_tools-1.29.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1a606f2f5b23822e2e5271bf0df98c140ceed154ea6bf5c04ea85a37a0317771"}, - {file = "grpcio_tools-1.29.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d89a43d14fb3043c1876e78d7ad5018c762b0ce51c199c588fa9142442546005"}, - {file = "grpcio_tools-1.29.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:faf845f71fcb6cb5088429c676ae644116d56e5de41c639be4d7399bf71b9637"}, - {file = "grpcio_tools-1.29.0-cp37-cp37m-win32.whl", hash = "sha256:05f214bc904c8e4ebf0240993a868895ff96184172243c0c61b323f6f029863d"}, - {file = "grpcio_tools-1.29.0-cp37-cp37m-win_amd64.whl", hash = "sha256:afcb030067ba1b6c371a7bfd1ffd77375534144000d47d245ca77ebbd195901d"}, - {file = "grpcio_tools-1.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b55346fa75df4b1581627022a2c79cfeb58cdaebf719cdbf63ff8ae6d7d7704b"}, - {file = "grpcio_tools-1.29.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:22d91ceb853f6846bcc23f15d8a936574eeb9fc7e8941bb8a1a5f8fcf4f566b2"}, - {file = "grpcio_tools-1.29.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6eddefcd10f261d2aef6c122fb0651a53fcaee86e47d407492c9acf57107c91a"}, - {file = "grpcio_tools-1.29.0-cp38-cp38-win32.whl", hash = "sha256:658e131e983f4c3bec2e096c3cc048e6420acad2b19fad82328c481088ce0d1a"}, - {file = "grpcio_tools-1.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c52f68e864f60ed51ea59a3fd18d0989720bbf2e32d47b4096eba7b0b7f7086"}, + {file = "grpcio-tools-1.27.2.tar.gz", hash = "sha256:845a51305af9fc7f9e2078edaec9a759153195f6cf1fbb12b1fa6f077e56b260"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:7a2d5fb558ac153a326e742ebfd7020eb781c43d3ffd920abd42b2e6c6fdfb37"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:99961156a36aae4a402d6b14c1e7efde642794b3ddbf32c51db0cb3a199e8b11"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:069826dd02ce1886444cf4519c4fe1b05ac9ef41491f26e97400640531db47f6"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fae91f30dc050a8d0b32d20dc700e6092f0bd2138d83e9570fff3f0372c1b27e"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a14dc7a36c845991d908a7179502ca47bcba5ae1817c4426ce68cf2c97b20ad9"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-win32.whl", hash = "sha256:d1a5e5fa47ba9557a7d3b31605631805adc66cdba9d95b5d10dfc52cca1fed53"}, + {file = "grpcio_tools-1.27.2-cp27-cp27m-win_amd64.whl", hash = "sha256:7b54b283ec83190680903a9037376dc915e1f03852a2d574ba4d981b7a1fd3d0"}, + {file = "grpcio_tools-1.27.2-cp27-cp27mu-linux_armv7l.whl", hash = "sha256:4698c6b6a57f73b14d91a542c69ff33a2da8729691b7060a5d7f6383624d045e"}, + {file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:87e8ca2c2d2d3e09b2a2bed5d740d7b3e64028dafb7d6be543b77eec85590736"}, + {file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bd7f59ff1252a3db8a143b13ea1c1e93d4b8cf4b852eb48b22ef1e6942f62a84"}, + {file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:a8f892378b0b02526635b806f59141abbb429d19bec56e869e04f396502c9651"}, + {file = "grpcio_tools-1.27.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:69c4a63919b9007e845d9f8980becd2f89d808a4a431ca32b9723ee37b521cb1"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-linux_armv7l.whl", hash = "sha256:dcbc06556f3713a9348c4fce02d05d91e678fc320fb2bcf0ddf8e4bb11d17867"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:16dc3fad04fe18d50777c56af7b2d9b9984cd1cfc71184646eb431196d1645c6"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1de5a273eaffeb3d126a63345e9e848ea7db740762f700eb8b5d84c5e3e7687d"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6016c07d6566e3109a3c032cf3861902d66501ecc08a5a84c47e43027302f367"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:915a695bc112517af48126ee0ecdb6aff05ed33f3eeef28f0d076f1f6b52ef5e"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ea4b3ad696d976d5eac74ec8df9a2c692113e455446ee38d5b3bd87f8e034fa6"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-win32.whl", hash = "sha256:a140bf853edb2b5e8692fe94869e3e34077d7599170c113d07a58286c604f4fe"}, + {file = "grpcio_tools-1.27.2-cp35-cp35m-win_amd64.whl", hash = "sha256:77e25c241e33b75612f2aa62985f746c6f6803ec4e452da508bb7f8d90a69db4"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-linux_armv7l.whl", hash = "sha256:5fd7efc2fd3370bd2c72dc58f31a407a5dff5498befa145da211b2e8c6a52c63"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9ba88c2d99bcaf7b9cb720925e3290d73b2367d238c5779363fd5598b2dc98c7"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b56caecc16307b088a431a4038c3b3bb7d0e7f9988cbd0e9fa04ac937455ea38"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f8514453411d72cc3cf7d481f2b6057e5b7436736d0cd39ee2b2f72088bbf497"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c1bb8f47d58e9f7c4825abfe01e6b85eda53c8b31d2267ca4cddf3c4d0829b80"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e17b2e0936b04ced99769e26111e1e86ba81619d1b2691b1364f795e45560953"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-win32.whl", hash = "sha256:520b7dafddd0f82cb7e4f6e9c6ba1049aa804d0e207870def9fe7f94d1e14090"}, + {file = "grpcio_tools-1.27.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ee50b0cf0d28748ef9f941894eb50fc464bd61b8e96aaf80c5056bea9b80d580"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:627c91923df75091d8c4d244af38d5ab7ed8d786d480751d6c2b9267fbb92fe0"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ef624b6134aef737b3daa4fb7e806cb8c5749efecd0b1fa9ce4f7e060c7a0221"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e6932518db389ede8bf06b4119bbd3e17f42d4626e72dec2b8955b20ec732cb6"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:43a1573400527a23e4174d88604fde7a9d9a69bf9473c21936b7f409858f8ebb"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:57f8b9e2c7f55cd45f6dd930d6de61deb42d3eb7f9788137fbc7155cf724132a"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-win32.whl", hash = "sha256:2ca280af2cae1a014a238057bd3c0a254527569a6a9169a01c07f0590081d530"}, + {file = "grpcio_tools-1.27.2-cp37-cp37m-win_amd64.whl", hash = "sha256:59fbeb5bb9a7b94eb61642ac2cee1db5233b8094ca76fc56d4e0c6c20b5dd85f"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:00c5080cfb197ed20ecf0d0ff2d07f1fc9c42c724cad21c40ff2d048de5712b1"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f5450aa904e720f9c6407b59e96a8951ed6a95463f49444b6d2594b067d39588"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:aaa5ae26883c3d58d1a4323981f96b941fa09bb8f0f368d97c6225585280cf04"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1266b577abe7c720fd16a83d0a4999a192e87c4a98fc9f97e0b99b106b3e155f"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a3d2aec4b09c8e59fee8b0d1ed668d09e8c48b738f03f5d8401d7eb409111c47"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-win32.whl", hash = "sha256:8e7738a4b93842bca1158cde81a3587c9b7111823e40a1ddf73292ca9d58e08b"}, + {file = "grpcio_tools-1.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:84724458c86ff9b14c29b49e321f34d80445b379f4cd4d0494c694b49b1d6f88"}, ] identify = [ {file = "identify-1.4.18-py2.py3-none-any.whl", hash = "sha256:9f53e80371f2ac7c969eefda8efaabd4f77c6300f5f8fc4b634744a0db8fe5cc"}, diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 609fcb08d..0cb32e919 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -8,7 +8,7 @@ authors = [] python = "^3.6" dataclasses = { version = "*", python = "3.6" } fabric = "*" -grpcio = "*" +grpcio = "1.27.2" invoke = "*" lxml = "*" mako = "*" From 9b541d0316f9556f839acdf943d55c0cae82cbd3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:09:04 -0700 Subject: [PATCH 136/210] adding invoke change to support grpcio binary install --- tasks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 0e9f625c0..d70df69e0 100644 --- a/tasks.py +++ b/tasks.py @@ -80,7 +80,9 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: def install_grpcio(c: Context, hide: bool) -> None: print("installing grpcio-tools...") - c.run("python3 -m pip install --user grpcio-tools", hide=hide) + c.run( + "python3 -m pip install --only-binary \":all:\" --user grpcio-tools", hide=hide + ) def build(c: Context, hide: bool) -> None: From 7c3e42396ac7e97e431340a331cbff5ee665d8f2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:20:27 -0700 Subject: [PATCH 137/210] invoke install acount for centos prefix issues, add usage of nproc for make -j --- tasks.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tasks.py b/tasks.py index d70df69e0..24e2599cc 100644 --- a/tasks.py +++ b/tasks.py @@ -85,11 +85,12 @@ def install_grpcio(c: Context, hide: bool) -> None: ) -def build(c: Context, hide: bool) -> None: +def build(c: Context, os_info: OsInfo, hide: bool) -> None: print("building core...") c.run("./bootstrap.sh", hide=hide) - c.run("./configure", hide=hide) - c.run("make", hide=hide) + prefix = "--prefix=/usr" if os_info.like == OsLike.REDHAT else "" + c.run(f"./configure {prefix}", hide=hide) + c.run("make -j$(nproc)", hide=hide) def install_core(c: Context, hide: bool) -> None: @@ -109,7 +110,7 @@ def install_poetry(c: Context, dev: bool, hide: bool) -> None: print("installing core environment using poetry...") c.run(f"poetry install {args}", hide=hide) if dev: - c.run("poetry run pre-commit install") + c.run("poetry run pre-commit install", hide=hide) def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: @@ -136,7 +137,7 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: "--localstatedir=/var/run/quagga", hide=hide ) - c.run("make", hide=hide) + c.run("make -j$(nproc)", hide=hide) print("installing ospf mdr...") c.run("sudo make install", hide=hide) From 8a60a4739fe8c4ec3f9658a0be28240d948d3c7b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:23:26 -0700 Subject: [PATCH 138/210] fixed missing invoke install argument --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 24e2599cc..5ef0f938d 100644 --- a/tasks.py +++ b/tasks.py @@ -151,7 +151,7 @@ def install(c, dev=False, verbose=False): os_info = get_os() install_system(c, os_info, hide) install_grpcio(c, hide) - build(c, hide) + build(c, os_info, hide) install_core(c, hide) install_poetry(c, dev, hide) install_ospf_mdr(c, os_info, hide) From 75acbf4daef775a4a017846daa2f1296c02ee4b9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:41:39 -0700 Subject: [PATCH 139/210] invoke install account for ebtables based on nf_tables --- tasks.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tasks.py b/tasks.py index 5ef0f938d..e79f132fa 100644 --- a/tasks.py +++ b/tasks.py @@ -76,6 +76,12 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool", hide=hide ) + r = c.run("ebtables -V", hide=hide) + if "nf_tables" in r.stdout: + c.run( + "sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy", + hide=hide + ) def install_grpcio(c: Context, hide: bool) -> None: From 980ab1526ddb8ecb75e210de1fd9028b295ebef2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:52:12 -0700 Subject: [PATCH 140/210] added invoke cleanup task --- tasks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tasks.py b/tasks.py index e79f132fa..dd37cb78f 100644 --- a/tasks.py +++ b/tasks.py @@ -191,6 +191,14 @@ def gui(c): c.run("poetry run scripts/core-pygui", pty=True) +@task +def cleanup(c): + """ + run core-cleanup removing leftover core nodes, bridges, directories + """ + c.run(f"sudo daemon/scripts/core-cleanup", pty=True) + + @task def test(c): """ From ece2f1c43f286b39759128ec4a1e9b198c77da88 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 12:53:21 -0700 Subject: [PATCH 141/210] added invoke cleanup message --- tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks.py b/tasks.py index dd37cb78f..e24fa415e 100644 --- a/tasks.py +++ b/tasks.py @@ -196,6 +196,7 @@ def cleanup(c): """ run core-cleanup removing leftover core nodes, bridges, directories """ + print("running core-cleanup...") c.run(f"sudo daemon/scripts/core-cleanup", pty=True) From 637f7740d63c051203518a7de4924cf085144065 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 13:00:38 -0700 Subject: [PATCH 142/210] added git as invoke install dependency for ospf-mdr just in case core was a source tarball --- tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index e24fa415e..f5ed9c2a4 100644 --- a/tasks.py +++ b/tasks.py @@ -125,9 +125,9 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: return print("installing ospf mdr dependencies...") if os_info.like == OsLike.DEBIAN: - c.run("sudo apt install -y libtool gawk libreadline-dev", hide=hide) + c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide) elif os_info.like == OsLike.REDHAT: - c.run("sudo yum install -y libtool gawk readline-devel", hide=hide) + c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide) print("cloning ospf mdr...") clone_dir = "/tmp/ospf-mdr" c.run( From a9ec21c6044704dee586c05b62e3d76150f5a223 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 14:20:13 -0700 Subject: [PATCH 143/210] add make dependency to redhat based invoke installs, since centos 8 does not have it by default --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index f5ed9c2a4..4ca8e3cf3 100644 --- a/tasks.py +++ b/tasks.py @@ -73,7 +73,7 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: elif os_info.like == OsLike.REDHAT: c.run( "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel " - "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool", + "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool make", hide=hide ) r = c.run("ebtables -V", hide=hide) From d2fe7fcff0321149ec756f1043fdbb2abb946ce8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 16:50:37 -0700 Subject: [PATCH 144/210] invoke install account for centos 8 netem not being installed/enabled and add warning for failed ebtables legacy support --- tasks.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tasks.py b/tasks.py index 4ca8e3cf3..1b6023d51 100644 --- a/tasks.py +++ b/tasks.py @@ -73,15 +73,23 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: elif os_info.like == OsLike.REDHAT: c.run( "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel " - "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool make", + "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool make " + "kernel-modules-extra", hide=hide ) + # centos 8+ does not support netem by default + if os_info.name == OsName.CENTOS and os_info.version >= 8: + c.run("sudo yum install -y kernel-modules-extra", hide=hide) + c.run("sudo modprobe sch_netem", hide=hide) + # attempt to setup legacy ebtables when an nftables based version is found r = c.run("ebtables -V", hide=hide) if "nf_tables" in r.stdout: - c.run( + if not c.run( "sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy", + warn=True, hide=hide - ) + ): + print("ERROR: unable to setup required ebtables-legacy, WLAN will not work") def install_grpcio(c: Context, hide: bool) -> None: From 737dae1224526a38552b93814395082b5fb090aa Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 17:56:30 -0700 Subject: [PATCH 145/210] invoke install, added message for failed kernel netem enable in centos 8 and exit with error --- tasks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 1b6023d51..9e4d393fb 100644 --- a/tasks.py +++ b/tasks.py @@ -80,7 +80,11 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: # centos 8+ does not support netem by default if os_info.name == OsName.CENTOS and os_info.version >= 8: c.run("sudo yum install -y kernel-modules-extra", hide=hide) - c.run("sudo modprobe sch_netem", hide=hide) + if not c.run("sudo modprobe sch_netem", warn=True, hide=hide): + print("ERROR: you need to install the latest kernel") + print("run the following, restart, and try again") + print("sudo yum update") + sys.exit(1) # attempt to setup legacy ebtables when an nftables based version is found r = c.run("ebtables -V", hide=hide) if "nf_tables" in r.stdout: From 8cf89fa114e1552eb56a958b5d7be3e448c4d1d2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 17:57:10 -0700 Subject: [PATCH 146/210] invoke install, change ebtables-legacy from error to warning, since we dont exit --- tasks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 9e4d393fb..0255ffd9a 100644 --- a/tasks.py +++ b/tasks.py @@ -93,7 +93,9 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: warn=True, hide=hide ): - print("ERROR: unable to setup required ebtables-legacy, WLAN will not work") + print( + "WARNING: unable to setup required ebtables-legacy, WLAN will not work" + ) def install_grpcio(c: Context, hide: bool) -> None: From 80eaa274697470cf428759727681ddd32a8f6f00 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 20:02:41 -0700 Subject: [PATCH 147/210] created baseline doc to support poetry based installations --- docs/install2.md | 68 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/docs/install2.md b/docs/install2.md index b8f7c0994..c877086cc 100644 --- a/docs/install2.md +++ b/docs/install2.md @@ -1,27 +1,63 @@ -# Commands Used Ubuntu +# Installing CORE -```shell -# get pip -sudo apt install python3-pip python3-venv +## Overview -# install pipx -python3 -m pip install --user pipx -python3 -m pipx ensurepath +CORE provides a script to help automate installing all required software +to build and run, including a python virtual environment to run it all in. -# install invoke -pipx install invoke +The following tools will be leveraged during installation: -# install core -inv install +|Tool|Description| +|---|---| +|pip|used to install pipx| +|pipx|used to install standalone python tools (invoke, poetry)| +|invoke|used to run provided tasks (install, daemon, gui, tests, etc)| +|poetry|used to install the managed python virtual environment for running CORE| -# run daemon -inv daemon +## Supported Linux Distributions + +Plan is to support recent Ubuntu and CentOS LTS releases. + +Verified: +* Ubuntu - 18.04, 20.04 +* CentOS - 7.8, 8.0* + +> **NOTE:** Ubuntu 20.04 requires installing legacy ebtables for WLAN +> functionality + +> **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not +> function properly + +## Running Installation -# run gui -inv gui +```shell +# clone CORE repo +git clone https://github.com/coreemu/core.git +cd core +git checkout enhancement/poetry-invoke + +# run install script +./install2.sh ``` -Commands Used CentOS +## Using Invoke Tasks + +The invoke tool installed by way of pipx provides conveniences for running +CORE tasks to help ensure usage of the create python virtual environment. ```shell +Available tasks: + + cleanup run core-cleanup removing leftover core nodes, bridges, directories + daemon start core-daemon + gui start core-pygui + install install core + test run core tests + test-emane run core emane tests + test-mock run core tests using mock to avoid running as sudo +``` + +Example running the core-daemon task from the root of the repo: +```shell +inv daemon ``` From d0e9cee6503e9c2217558752984d5450c068e21c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 10 Jul 2020 20:29:47 -0700 Subject: [PATCH 148/210] added invoke task to help wrap core-cli --- docs/install2.md | 18 ++++++++++++++++++ tasks.py | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/docs/install2.md b/docs/install2.md index c877086cc..86e3db92b 100644 --- a/docs/install2.md +++ b/docs/install2.md @@ -61,3 +61,21 @@ Example running the core-daemon task from the root of the repo: ```shell inv daemon ``` + +Some tasks are wrappers around command line tools and requires running +them with a slight variation for compatibility. You can enter the +poetry shell to run the script natively. + +```shell +# running core-cli as a task requires all options to be provided +# within a string +inv cli "query session -i 1" + +# entering the poetry shell to use core-cli natively +cd $REPO/daemon +poetry shell +core-cli query session -i 1 + +# exit the shell +exit +``` diff --git a/tasks.py b/tasks.py index 0255ffd9a..b180688de 100644 --- a/tasks.py +++ b/tasks.py @@ -205,6 +205,15 @@ def gui(c): c.run("poetry run scripts/core-pygui", pty=True) +@task +def cli(c, args): + """ + run core-cli used to query and modify a running session + """ + with c.cd(DAEMON_DIR): + c.run(f"poetry run scripts/core-cli {args}", pty=True) + + @task def cleanup(c): """ From 7398196dcca5473a5e1d3a2a43b51bfb7cc4d631 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 11 Jul 2020 14:06:53 -0700 Subject: [PATCH 149/210] pygui: dont show mobility player when joining sessions not in runtime --- daemon/core/gui/coreclient.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 9479cbcbe..255192be9 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -377,7 +377,8 @@ def join_session(self, session_id: int, query_location: bool = True) -> None: # organize canvas self.app.canvas.organize() - self.show_mobility_players() + if self.is_runtime(): + self.show_mobility_players() # update ui to represent current state self.app.after(0, self.app.joined_session_update) From e70448352740393f02973ed784dae297752db0f9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 11 Jul 2020 14:26:06 -0700 Subject: [PATCH 150/210] update install script to avoid issues with recent grpcio-tools and pip binary packages --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index a12072f1f..e4ac66e8b 100755 --- a/install.sh +++ b/install.sh @@ -86,7 +86,7 @@ if [ -z "${reinstall}" ]; then echo "installing core system dependencies" sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ python${ubuntu_py} python${ubuntu_py}-dev python3-pip python3-tk tk libtk-img ethtool autoconf - python3 -m pip install grpcio-tools + python3 -m pip install grpcio-tools==1.27.2 echo "installing ospf-mdr system dependencies" sudo apt install -y libtool gawk libreadline-dev install_ospf_mdr @@ -108,7 +108,7 @@ if [ -z "${reinstall}" ]; then echo "installing core system dependencies" sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel iptables-ebtables iproute \ python${centos_py} python${centos_py}-devel python3-pip python3-tkinter tk ethtool autoconf - sudo python3 -m pip install grpcio-tools + python3 -m pip install grpcio-tools==1.27.2 echo "installing ospf-mdr system dependencies" sudo yum install -y libtool gawk readline-devel install_ospf_mdr From 68ff7a86c8c00f31fa158041be9a71efed538781 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 11 Jul 2020 15:00:09 -0700 Subject: [PATCH 151/210] fixed install script issues with grpcio-tools and updated documentation --- docs/install.md | 4 +++- install.sh | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/install.md b/docs/install.md index 4a39218dd..b78046337 100644 --- a/docs/install.md +++ b/docs/install.md @@ -232,9 +232,11 @@ git clone https://github.com/coreemu/core.git ### Install grpcio-tools Python module grpcio-tools is currently needed to generate gRPC protobuf code. +Specifically leveraging 1.27.2 to avoid compatibility issues with older versions +of pip pulling down binary files. ```shell -sudo python3 -m pip install grpcio-tools +python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 ``` ### Build and Install diff --git a/install.sh b/install.sh index e4ac66e8b..3eb6694e8 100755 --- a/install.sh +++ b/install.sh @@ -86,7 +86,7 @@ if [ -z "${reinstall}" ]; then echo "installing core system dependencies" sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ python${ubuntu_py} python${ubuntu_py}-dev python3-pip python3-tk tk libtk-img ethtool autoconf - python3 -m pip install grpcio-tools==1.27.2 + python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 echo "installing ospf-mdr system dependencies" sudo apt install -y libtool gawk libreadline-dev install_ospf_mdr @@ -108,7 +108,7 @@ if [ -z "${reinstall}" ]; then echo "installing core system dependencies" sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel iptables-ebtables iproute \ python${centos_py} python${centos_py}-devel python3-pip python3-tkinter tk ethtool autoconf - python3 -m pip install grpcio-tools==1.27.2 + python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 echo "installing ospf-mdr system dependencies" sudo yum install -y libtool gawk readline-devel install_ospf_mdr From 5a35431bcb9a278929c617ab031ff16cc1afadfb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 11 Jul 2020 15:08:04 -0700 Subject: [PATCH 152/210] updated grpcio-tools installation to specifically specify binary requirement and updated doc --- docs/install.md | 2 +- install.sh | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/install.md b/docs/install.md index b78046337..227f877c4 100644 --- a/docs/install.md +++ b/docs/install.md @@ -236,7 +236,7 @@ Specifically leveraging 1.27.2 to avoid compatibility issues with older versions of pip pulling down binary files. ```shell -python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 +python3 -m pip install --only-binary ":all:" --user grpcio-tools ``` ### Build and Install diff --git a/install.sh b/install.sh index 3eb6694e8..6cc193ed6 100755 --- a/install.sh +++ b/install.sh @@ -11,6 +11,10 @@ function install_python_depencencies() { sudo python3 -m pip install -r daemon/requirements.txt } +function install_grpcio_tools() { + python3 -m pip install --only-binary ":all:" --user grpcio-tools +} + function install_ospf_mdr() { rm -rf /tmp/ospf-mdr git clone https://github.com/USNavalResearchLaboratory/ospf-mdr /tmp/ospf-mdr @@ -86,7 +90,7 @@ if [ -z "${reinstall}" ]; then echo "installing core system dependencies" sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ python${ubuntu_py} python${ubuntu_py}-dev python3-pip python3-tk tk libtk-img ethtool autoconf - python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 + install_grpcio_tools echo "installing ospf-mdr system dependencies" sudo apt install -y libtool gawk libreadline-dev install_ospf_mdr @@ -108,7 +112,7 @@ if [ -z "${reinstall}" ]; then echo "installing core system dependencies" sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel iptables-ebtables iproute \ python${centos_py} python${centos_py}-devel python3-pip python3-tkinter tk ethtool autoconf - python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 + install_grpcio_tools echo "installing ospf-mdr system dependencies" sudo yum install -y libtool gawk readline-devel install_ospf_mdr From ec45d7198b682a9a7af67e0eb52234ad1589451b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 11 Jul 2020 16:17:50 -0700 Subject: [PATCH 153/210] ci: changes to switch to poetry --- .github/workflows/daemon-checks.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml index d955ee582..9e9f7aa71 100644 --- a/.github/workflows/daemon-checks.yml +++ b/.github/workflows/daemon-checks.yml @@ -11,32 +11,32 @@ jobs: uses: actions/setup-python@v1 with: python-version: 3.6 - - name: Install pipenv + - name: install poetry run: | python -m pip install --upgrade pip - pip install pipenv + pip install poetry cd daemon cp setup.py.in setup.py cp core/constants.py.in core/constants.py sed -i 's/required=True/required=False/g' core/emulator/coreemu.py - pipenv sync --dev + poetry install - name: isort run: | cd daemon - pipenv run isort -c -df + poetry run isort -c -df - name: black run: | cd daemon - pipenv run black --check --exclude ".+_pb2.*.py|doc|build|utm\.py|setup\.py" . + poetry run black --check --exclude ".+_pb2.*.py|doc|build|utm\.py|setup\.py" . - name: flake8 run: | cd daemon - pipenv run flake8 + poetry run flake8 - name: grpc run: | cd daemon/proto - pipenv run python -m grpc_tools.protoc -I . --python_out=.. --grpc_python_out=.. core/api/grpc/*.proto + poetry run python -m grpc_tools.protoc -I . --python_out=.. --grpc_python_out=.. core/api/grpc/*.proto - name: test run: | cd daemon - pipenv run test --mock + poetry run pytest --mock tests From 5c58e99ad41d45e1a70c001d9932321623c626db Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 11 Jul 2020 22:11:23 -0700 Subject: [PATCH 154/210] updated pre-commit file to use poetry environment --- daemon/.pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/.pre-commit-config.yaml b/daemon/.pre-commit-config.yaml index 73566c9d7..13a6955ba 100644 --- a/daemon/.pre-commit-config.yaml +++ b/daemon/.pre-commit-config.yaml @@ -5,19 +5,19 @@ repos: name: isort stages: [commit] language: system - entry: bash -c 'cd daemon && pipenv run isort --atomic -y' + entry: bash -c 'cd daemon && poetry run isort --atomic -y' types: [python] - id: black name: black stages: [commit] language: system - entry: bash -c 'cd daemon && pipenv run black --exclude ".+_pb2.*.py|doc|build|utm\.py" .' + entry: bash -c 'cd daemon && poetry run black --exclude ".+_pb2.*.py|doc|build|utm\.py" .' types: [python] - id: flake8 name: flake8 stages: [commit] language: system - entry: bash -c 'cd daemon && pipenv run flake8' + entry: bash -c 'cd daemon && poetry run flake8' types: [python] From dcf35680984068fca2e3d35dfa9cdad66fe19082 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 13 Jul 2020 08:58:32 -0700 Subject: [PATCH 155/210] force grpcio related installations to all use the same version to avoid any version conflicts --- daemon/poetry.lock | 2 +- daemon/pyproject.toml | 2 +- docs/install.md | 5 ++--- tasks.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/daemon/poetry.lock b/daemon/poetry.lock index c72bc364d..9de19d13a 100644 --- a/daemon/poetry.lock +++ b/daemon/poetry.lock @@ -602,7 +602,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "260c6612feb7c884d03b3b98e5fb22ad4d06a58559876f239bd5c677d14a7ba1" +content-hash = "94df87a12a92ccb6512e4c30965e7ba1fe54b4fa3ff75827ca55b3de8472b30e" python-versions = "^3.6" [metadata.files] diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 0cb32e919..165fb34cf 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -21,7 +21,7 @@ pyyaml = "*" [tool.poetry.dev-dependencies] black = "==19.3b0" flake8 = "*" -grpcio-tools = "*" +grpcio-tools = "1.27.2" isort = "*" mock = "*" pre-commit = "*" diff --git a/docs/install.md b/docs/install.md index 227f877c4..99cee9f68 100644 --- a/docs/install.md +++ b/docs/install.md @@ -232,11 +232,10 @@ git clone https://github.com/coreemu/core.git ### Install grpcio-tools Python module grpcio-tools is currently needed to generate gRPC protobuf code. -Specifically leveraging 1.27.2 to avoid compatibility issues with older versions -of pip pulling down binary files. +Specifically leveraging 1.27.2 as that is what will be used during runtime. ```shell -python3 -m pip install --only-binary ":all:" --user grpcio-tools +python3 -m pip install --user grpcio==12.7.2 grpcio-tools==12.7.2 ``` ### Build and Install diff --git a/tasks.py b/tasks.py index b180688de..c512d3882 100644 --- a/tasks.py +++ b/tasks.py @@ -101,7 +101,7 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: def install_grpcio(c: Context, hide: bool) -> None: print("installing grpcio-tools...") c.run( - "python3 -m pip install --only-binary \":all:\" --user grpcio-tools", hide=hide + "python3 -m pip install --user grpcio==12.7.2 grpcio-tools==12.7.2", hide=hide ) From 32c7808cab79d7f12cc6fbefedb4a92feb341395 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 13 Jul 2020 09:01:55 -0700 Subject: [PATCH 156/210] fixed bad version for grpcio tools --- docs/install.md | 2 +- tasks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/install.md b/docs/install.md index 99cee9f68..6abd09454 100644 --- a/docs/install.md +++ b/docs/install.md @@ -235,7 +235,7 @@ Python module grpcio-tools is currently needed to generate gRPC protobuf code. Specifically leveraging 1.27.2 as that is what will be used during runtime. ```shell -python3 -m pip install --user grpcio==12.7.2 grpcio-tools==12.7.2 +python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 ``` ### Build and Install diff --git a/tasks.py b/tasks.py index c512d3882..cc16b9435 100644 --- a/tasks.py +++ b/tasks.py @@ -101,7 +101,7 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: def install_grpcio(c: Context, hide: bool) -> None: print("installing grpcio-tools...") c.run( - "python3 -m pip install --user grpcio==12.7.2 grpcio-tools==12.7.2", hide=hide + "python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2", hide=hide ) From 63f09e02543a463b728399f0d39c51e739f41dd9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 13 Jul 2020 13:14:13 -0700 Subject: [PATCH 157/210] added installation of modified scripts and service to invoke task --- tasks.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tasks.py b/tasks.py index cc16b9435..c2028f35e 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,9 @@ +import inspect import os import sys from enum import Enum +from pathlib import Path +from tempfile import NamedTemporaryFile from invoke import task, Context @@ -162,6 +165,61 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: c.run("sudo make install", hide=hide) +def install_files(c: Context, hide: bool, prefix="/usr/local") -> None: + # install all scripts + python = get_python(c) + bin_dir = Path(prefix).joinpath("bin") + for script in Path("daemon/scripts").iterdir(): + dest = bin_dir.joinpath(script.name) + print(f"installing {script} to {dest}") + with open(script, "r") as f: + lines = f.readlines() + first = lines[0].strip() + # modify python scripts to point to virtual environment + if first == "#!/usr/bin/env python3": + lines[0] = f"#!{python}\n" + temp = NamedTemporaryFile("w", delete=False) + for line in lines: + temp.write(line) + temp.close() + c.run(f"sudo cp {temp.name} {dest}", hide=hide) + c.run(f"sudo chmod 755 {dest}", hide=hide) + os.unlink(temp.name) + # copy normal links + else: + c.run(f"sudo cp {script} {dest}", hide=hide) + + # install core configuration file + config_dir = "/etc/core" + print(f"installing core configuration files under {config_dir}") + c.run(f"sudo mkdir -p {config_dir}", hide=hide) + c.run(f"sudo cp -n daemon/data/core.conf {config_dir}", hide=hide) + c.run(f"sudo cp -n daemon/data/logging.conf {config_dir}", hide=hide) + + # install service + systemd_dir = Path("/lib/systemd/system/") + service_file = systemd_dir.joinpath("core-daemon.service") + if systemd_dir.exists(): + print(f"installing core-daemon.service for systemd to {service_file}") + service_data = inspect.cleandoc(f""" + [Unit] + Description=Common Open Research Emulator Service + After=network.target + + [Service] + Type=simple + ExecStart={bin_dir}/core-daemon + TasksMax=infinity + + [Install] + WantedBy=multi-user.target + """) + temp = NamedTemporaryFile("w", delete=False) + temp.write(service_data) + temp.close() + c.run(f"sudo cp {temp.name} {service_file}", hide=hide) + + @task def install(c, dev=False, verbose=False): """ @@ -174,6 +232,7 @@ def install(c, dev=False, verbose=False): build(c, os_info, hide) install_core(c, hide) install_poetry(c, dev, hide) + install_files(c, hide) install_ospf_mdr(c, os_info, hide) print("please open a new terminal or re-login to leverage invoke for running core") print("# run daemon") From 79058810c2b827a482880cb4fa907bf19290fedf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 13 Jul 2020 13:48:27 -0700 Subject: [PATCH 158/210] added uninstall invoke task to uninstall core files added with the invoke install command, beyond system packages --- tasks.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index c2028f35e..2f9bd5565 100644 --- a/tasks.py +++ b/tasks.py @@ -29,10 +29,14 @@ def __init__(self, name: OsName, like: OsLike, version: float) -> None: self.version: float = version -def get_python(c: Context) -> str: +def get_python(c: Context, warn: bool = False) -> str: with c.cd(DAEMON_DIR): - venv = c.run("poetry env info -p", hide=True).stdout.strip() - return os.path.join(venv, "bin", "python") + r = c.run("poetry env info -p", warn=warn, hide=True) + if r.ok: + venv = r.stdout.strip() + return os.path.join(venv, "bin", "python") + else: + return "" def get_pytest(c: Context) -> str: @@ -165,7 +169,7 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: c.run("sudo make install", hide=hide) -def install_files(c: Context, hide: bool, prefix="/usr/local") -> None: +def install_files(c: Context, hide: bool, prefix: str = "/usr/local") -> None: # install all scripts python = get_python(c) bin_dir = Path(prefix).joinpath("bin") @@ -241,6 +245,47 @@ def install(c, dev=False, verbose=False): print("inv gui") +@task +def uninstall(c, dev=False, verbose=False, prefix="/usr/local"): + """ + uninstall core + """ + hide = not verbose + print("uninstalling core-gui") + with c.cd(GUI_DIR): + c.run("sudo make uninstall", hide=hide) + print("uninstalling vcmd") + with c.cd(VCMD_DIR): + c.run("sudo make uninstall", hide=hide) + print("cleaning build directory") + c.run("make clean", hide=hide) + c.run("./bootstrap.sh clean", hide=hide) + python = get_python(c, warn=True) + if python: + with c.cd(DAEMON_DIR): + if dev: + print("uninstalling pre-commit") + c.run("poetry run pre-commit uninstall", hide=hide) + print("uninstalling poetry virtual environment") + c.run(f"poetry env remove {python}", hide=hide) + + # remove installed files + bin_dir = Path(prefix).joinpath("bin") + for script in Path("daemon/scripts").iterdir(): + dest = bin_dir.joinpath(script.name) + print(f"uninstalling {dest}") + c.run(f"sudo rm -f {dest}", hide=hide) + + # install service + systemd_dir = Path("/lib/systemd/system/") + service_name = "core-daemon.service" + service_file = systemd_dir.joinpath(service_name) + if service_file.exists(): + print(f"uninstalling service {service_file}") + c.run(f"sudo systemctl disable {service_name}", hide=hide) + c.run(f"sudo rm -f {service_file}", hide=hide) + + @task def daemon(c): """ From bd87403ae5d968473fe651db4cd0f69b46a1ec00 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 13 Jul 2020 14:13:32 -0700 Subject: [PATCH 159/210] add prefix option to install2.sh script --- install2.sh | 9 +++++++-- tasks.py | 10 ++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/install2.sh b/install2.sh index bdfd2f7b4..5e5a9b117 100755 --- a/install2.sh +++ b/install2.sh @@ -13,7 +13,8 @@ fi # parse arguments dev="" verbose="" -while getopts "dv" opt; do +prefix="" +while getopts "dvp:" opt; do case ${opt} in d) dev="-d" @@ -21,11 +22,15 @@ while getopts "dv" opt; do v) verbose="-v" ;; + p) + prefix="-p ${OPTARG}" + ;; \?) echo "script usage: $(basename $0) [-d] [-v]" >&2 echo "" >&2 echo "-v enable verbose install" >&2 echo "-d enable developer install" >&2 + echo "-p install prefix, defaults to /usr/local" >&2 exit 1 ;; esac @@ -49,4 +54,4 @@ python3 -m pip install --user pipx python3 -m pipx ensurepath export PATH=$PATH:~/.local/bin pipx install invoke -inv install ${dev} ${verbose} +inv install ${dev} ${verbose} ${prefix} diff --git a/tasks.py b/tasks.py index 2f9bd5565..b4f376798 100644 --- a/tasks.py +++ b/tasks.py @@ -10,6 +10,7 @@ DAEMON_DIR: str = "daemon" VCMD_DIR: str = "netns" GUI_DIR: str = "gui" +DEFAULT_PREFIX: str = "/usr/local" class OsName(Enum): @@ -169,7 +170,7 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: c.run("sudo make install", hide=hide) -def install_files(c: Context, hide: bool, prefix: str = "/usr/local") -> None: +def install_files(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: # install all scripts python = get_python(c) bin_dir = Path(prefix).joinpath("bin") @@ -225,10 +226,11 @@ def install_files(c: Context, hide: bool, prefix: str = "/usr/local") -> None: @task -def install(c, dev=False, verbose=False): +def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ install core """ + print(f"installing core with prefix: {prefix}") hide = not verbose os_info = get_os() install_system(c, os_info, hide) @@ -236,7 +238,7 @@ def install(c, dev=False, verbose=False): build(c, os_info, hide) install_core(c, hide) install_poetry(c, dev, hide) - install_files(c, hide) + install_files(c, hide, prefix) install_ospf_mdr(c, os_info, hide) print("please open a new terminal or re-login to leverage invoke for running core") print("# run daemon") @@ -246,7 +248,7 @@ def install(c, dev=False, verbose=False): @task -def uninstall(c, dev=False, verbose=False, prefix="/usr/local"): +def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ uninstall core """ From 125d74e7d5287b944200e9f145c0b4bb20a3bb1f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 08:34:02 -0700 Subject: [PATCH 160/210] removed pipenv specific files, wont be needed with poetry --- daemon/Pipfile | 23 -- daemon/Pipfile.lock | 732 -------------------------------------------- 2 files changed, 755 deletions(-) delete mode 100644 daemon/Pipfile delete mode 100644 daemon/Pipfile.lock diff --git a/daemon/Pipfile b/daemon/Pipfile deleted file mode 100644 index 8bf52787d..000000000 --- a/daemon/Pipfile +++ /dev/null @@ -1,23 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[scripts] -core = "python scripts/core-daemon -f data/core.conf -l data/logging.conf" -core-pygui = "python scripts/core-pygui" -test = "pytest -v tests" -test-mock = "pytest -v --mock tests" -test-emane = "pytest -v tests/emane" - -[dev-packages] -grpcio-tools = "*" -isort = "*" -pre-commit = "*" -flake8 = "*" -black = "==19.3b0" -pytest = "*" -mock = "*" - -[packages] -core = {editable = true,path = "."} diff --git a/daemon/Pipfile.lock b/daemon/Pipfile.lock deleted file mode 100644 index 2fb5c3b89..000000000 --- a/daemon/Pipfile.lock +++ /dev/null @@ -1,732 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "199897f713f6f338316b33fcbbe0001e9e55fcd5e5e24b2245a89454ce13321f" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "bcrypt": { - "hashes": [ - "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89", - "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42", - "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294", - "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161", - "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752", - "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31", - "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5", - "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c", - "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0", - "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de", - "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e", - "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052", - "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09", - "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105", - "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133", - "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1", - "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7", - "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc" - ], - "version": "==3.1.7" - }, - "cffi": { - "hashes": [ - "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", - "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", - "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", - "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", - "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", - "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", - "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", - "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", - "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", - "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", - "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", - "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", - "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", - "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", - "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", - "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", - "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", - "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", - "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", - "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", - "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", - "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", - "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", - "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", - "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", - "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", - "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", - "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" - ], - "version": "==1.14.0" - }, - "core": { - "editable": true, - "path": "." - }, - "cryptography": { - "hashes": [ - "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", - "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", - "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", - "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", - "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", - "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", - "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", - "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", - "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", - "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", - "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", - "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", - "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", - "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", - "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", - "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", - "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", - "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", - "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", - "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", - "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8" - ], - "version": "==2.8" - }, - "dataclasses": { - "hashes": [ - "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836", - "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6" - ], - "index": "pypi", - "markers": "python_version == '3.6'", - "version": "==0.7" - }, - "fabric": { - "hashes": [ - "sha256:160331934ea60036604928e792fa8e9f813266b098ef5562aa82b88527740389", - "sha256:24842d7d51556adcabd885ac3cf5e1df73fc622a1708bf3667bf5927576cdfa6" - ], - "version": "==2.5.0" - }, - "grpcio": { - "hashes": [ - "sha256:02aef8ef1a5ac5f0836b543e462eb421df6048a7974211a906148053b8055ea6", - "sha256:07f82aefb4a56c7e1e52b78afb77d446847d27120a838a1a0489260182096045", - "sha256:1cff47297ee614e7ef66243dc34a776883ab6da9ca129ea114a802c5e58af5c1", - "sha256:1ec8fc865d8da6d0713e2092a27eee344cd54628b2c2065a0e77fff94df4ae00", - "sha256:1ef949b15a1f5f30651532a9b54edf3bd7c0b699a10931505fa2c80b2d395942", - "sha256:209927e65395feb449783943d62a3036982f871d7f4045fadb90b2d82b153ea8", - "sha256:25c77692ea8c0929d4ad400ea9c3dcbcc4936cee84e437e0ef80da58fa73d88a", - "sha256:28f27c64dd699b8b10f70da5f9320c1cffcaefca7dd76275b44571bd097f276c", - "sha256:355bd7d7ce5ff2917d217f0e8ddac568cb7403e1ce1639b35a924db7d13a39b6", - "sha256:4a0a33ada3f6f94f855f92460896ef08c798dcc5f17d9364d1735c5adc9d7e4a", - "sha256:4d3b6e66f32528bf43ca2297caca768280a8e068820b1c3dca0fcf9f03c7d6f1", - "sha256:5121fa96c79fc0ec81825091d0be5c16865f834f41b31da40b08ee60552f9961", - "sha256:57949756a3ce1f096fa2b00f812755f5ab2effeccedb19feeb7d0deafa3d1de7", - "sha256:586d931736912865c9790c60ca2db29e8dc4eace160d5a79fec3e58df79a9386", - "sha256:5ae532b93cf9ce5a2a549b74a2c35e3b690b171ece9358519b3039c7b84c887e", - "sha256:5dab393ab96b2ce4012823b2f2ed4ee907150424d2f02b97bd6f8dd8f17cc866", - "sha256:5ebc13451246de82f130e8ee7e723e8d7ae1827f14b7b0218867667b1b12c88d", - "sha256:68a149a0482d0bc697aac702ec6efb9d380e0afebf9484db5b7e634146528371", - "sha256:6db7ded10b82592c472eeeba34b9f12d7b0ab1e2dcad12f081b08ebdea78d7d6", - "sha256:6e545908bcc2ae28e5b190ce3170f92d0438cf26a82b269611390114de0106eb", - "sha256:6f328a3faaf81a2546a3022b3dfc137cc6d50d81082dbc0c94d1678943f05df3", - "sha256:706e2dea3de33b0d8884c4d35ecd5911b4ff04d0697c4138096666ce983671a6", - "sha256:80c3d1ce8820dd819d1c9d6b63b6f445148480a831173b572a9174a55e7abd47", - "sha256:8111b61eee12d7af5c58f82f2c97c2664677a05df9225ef5cbc2f25398c8c454", - "sha256:9713578f187fb1c4d00ac554fe1edcc6b3ddd62f5d4eb578b81261115802df8e", - "sha256:9c0669ba9aebad540fb05a33beb7e659ea6e5ca35833fc5229c20f057db760e8", - "sha256:9e9cfe55dc7ac2aa47e0fd3285ff829685f96803197042c9d2f0fb44e4b39b2c", - "sha256:a22daaf30037b8e59d6968c76fe0f7ff062c976c7a026e92fbefc4c4bf3fc5a4", - "sha256:a25b84e10018875a0f294a7649d07c43e8bc3e6a821714e39e5cd607a36386d7", - "sha256:a71138366d57901597bfcc52af7f076ab61c046f409c7b429011cd68de8f9fe6", - "sha256:b4efde5524579a9ce0459ca35a57a48ca878a4973514b8bb88cb80d7c9d34c85", - "sha256:b78af4d42985ab3143d9882d0006f48d12f1bc4ba88e78f23762777c3ee64571", - "sha256:bb2987eb3af9bcf46019be39b82c120c3d35639a95bc4ee2d08f36ecdf469345", - "sha256:c03ce53690fe492845e14f4ab7e67d5a429a06db99b226b5c7caa23081c1e2bb", - "sha256:c59b9280284b791377b3524c8e39ca7b74ae2881ba1a6c51b36f4f1bb94cee49", - "sha256:d18b4c8cacbb141979bb44355ee5813dd4d307e9d79b3a36d66eca7e0a203df8", - "sha256:d1e5563e3b7f844dbc48d709c9e4a75647e11d0387cc1fa0c861d3e9d34bc844", - "sha256:d22c897b65b1408509099f1c3334bd3704f5e4eb7c0486c57d0e212f71cb8f54", - "sha256:dbec0a3a154dbf2eb85b38abaddf24964fa1c059ee0a4ad55d6f39211b1a4bca", - "sha256:ed123037896a8db6709b8ad5acc0ed435453726ea0b63361d12de369624c2ab5", - "sha256:f3614dabd2cc8741850597b418bcf644d4f60e73615906c3acc407b78ff720b3", - "sha256:f9d632ce9fd485119c968ec6a7a343de698c5e014d17602ae2f110f1b05925ed", - "sha256:fb62996c61eeff56b59ab8abfcaa0859ec2223392c03d6085048b576b567459b" - ], - "version": "==1.27.2" - }, - "invoke": { - "hashes": [ - "sha256:87b3ef9d72a1667e104f89b159eaf8a514dbf2f3576885b2bbdefe74c3fb2132", - "sha256:93e12876d88130c8e0d7fd6618dd5387d6b36da55ad541481dfa5e001656f134", - "sha256:de3f23bfe669e3db1085789fd859eb8ca8e0c5d9c20811e2407fa042e8a5e15d" - ], - "version": "==1.4.1" - }, - "lxml": { - "hashes": [ - "sha256:06d4e0bbb1d62e38ae6118406d7cdb4693a3fa34ee3762238bcb96c9e36a93cd", - "sha256:0701f7965903a1c3f6f09328c1278ac0eee8f56f244e66af79cb224b7ef3801c", - "sha256:1f2c4ec372bf1c4a2c7e4bb20845e8bcf8050365189d86806bad1e3ae473d081", - "sha256:4235bc124fdcf611d02047d7034164897ade13046bda967768836629bc62784f", - "sha256:5828c7f3e615f3975d48f40d4fe66e8a7b25f16b5e5705ffe1d22e43fb1f6261", - "sha256:585c0869f75577ac7a8ff38d08f7aac9033da2c41c11352ebf86a04652758b7a", - "sha256:5d467ce9c5d35b3bcc7172c06320dddb275fea6ac2037f72f0a4d7472035cea9", - "sha256:63dbc21efd7e822c11d5ddbedbbb08cd11a41e0032e382a0fd59b0b08e405a3a", - "sha256:7bc1b221e7867f2e7ff1933165c0cec7153dce93d0cdba6554b42a8beb687bdb", - "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60", - "sha256:8a0ebda56ebca1a83eb2d1ac266649b80af8dd4b4a3502b2c1e09ac2f88fe128", - "sha256:90ed0e36455a81b25b7034038e40880189169c308a3df360861ad74da7b68c1a", - "sha256:95e67224815ef86924fbc2b71a9dbd1f7262384bca4bc4793645794ac4200717", - "sha256:afdb34b715daf814d1abea0317b6d672476b498472f1e5aacbadc34ebbc26e89", - "sha256:b4b2c63cc7963aedd08a5f5a454c9f67251b1ac9e22fd9d72836206c42dc2a72", - "sha256:d068f55bda3c2c3fcaec24bd083d9e2eede32c583faf084d6e4b9daaea77dde8", - "sha256:d5b3c4b7edd2e770375a01139be11307f04341ec709cf724e0f26ebb1eef12c3", - "sha256:deadf4df349d1dcd7b2853a2c8796593cc346600726eff680ed8ed11812382a7", - "sha256:df533af6f88080419c5a604d0d63b2c33b1c0c4409aba7d0cb6de305147ea8c8", - "sha256:e4aa948eb15018a657702fee0b9db47e908491c64d36b4a90f59a64741516e77", - "sha256:e5d842c73e4ef6ed8c1bd77806bf84a7cb535f9c0cf9b2c74d02ebda310070e1", - "sha256:ebec08091a22c2be870890913bdadd86fcd8e9f0f22bcb398abd3af914690c15", - "sha256:edc15fcfd77395e24543be48871c251f38132bb834d9fdfdad756adb6ea37679", - "sha256:f2b74784ed7e0bc2d02bd53e48ad6ba523c9b36c194260b7a5045071abbb1012", - "sha256:fa071559f14bd1e92077b1b5f6c22cf09756c6de7139370249eb372854ce51e6", - "sha256:fd52e796fee7171c4361d441796b64df1acfceb51f29e545e812f16d023c4bbc", - "sha256:fe976a0f1ef09b3638778024ab9fb8cde3118f203364212c198f71341c0715ca" - ], - "version": "==4.5.0" - }, - "mako": { - "hashes": [ - "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", - "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" - ], - "version": "==1.1.2" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "version": "==1.1.1" - }, - "netaddr": { - "hashes": [ - "sha256:38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd", - "sha256:56b3558bd71f3f6999e4c52e349f38660e54a7a8a9943335f73dfc96883e08ca" - ], - "version": "==0.7.19" - }, - "paramiko": { - "hashes": [ - "sha256:920492895db8013f6cc0179293147f830b8c7b21fdfc839b6bad760c27459d9f", - "sha256:9c980875fa4d2cb751604664e9a2d0f69096643f5be4db1b99599fe114a97b2f" - ], - "version": "==2.7.1" - }, - "pillow": { - "hashes": [ - "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", - "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", - "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", - "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", - "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", - "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", - "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", - "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", - "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", - "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", - "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", - "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", - "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", - "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", - "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", - "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", - "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", - "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", - "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", - "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", - "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", - "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" - ], - "version": "==7.0.0" - }, - "pycparser": { - "hashes": [ - "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", - "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" - ], - "version": "==2.20" - }, - "pynacl": { - "hashes": [ - "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255", - "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c", - "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e", - "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae", - "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621", - "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56", - "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39", - "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310", - "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1", - "sha256:53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5", - "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a", - "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786", - "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b", - "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b", - "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f", - "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20", - "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415", - "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715", - "sha256:bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92", - "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1", - "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0" - ], - "version": "==1.3.0" - }, - "pyproj": { - "hashes": [ - "sha256:0d8196a5ac75fee2cf71c21066b3344427abfa8ad69b536d3404d5c7c9c0b886", - "sha256:12e378a0a21c73f96177f6cf64520f17e6b7aa02fc9cb27bd5c2d5b06ce170af", - "sha256:17738836128704d8f80b771572d77b8733841f0cb0ca42620549236ea62c4663", - "sha256:1a39175944710b225fd1943cb3b8ea0c8e059d3016360022ca10bbb7a6bfc9ae", - "sha256:2566bffb5395c9fbdb02077a0bc3e3ed0b2e4e3cadf65019e3139a8dfe27dd1d", - "sha256:3f43277f21ddaabed93b9885a4e494b785dca56e31fd37a935519d99b07807f0", - "sha256:424304beca6e0b0bc12aa46fc6d14a481ea47b1a4edec4854bb281656de38948", - "sha256:48128d794c8f52fcff2433a481e3aa2ccb0e0b3ccd51d3ad7cc10cc488c3f547", - "sha256:4a16b650722982cddedd45dfc36435b96e0ba83a2aebd4a4c247e5a68c852442", - "sha256:5161f1b5ece8a5263b64d97a32fbc473a4c6fdca5c95478e58e519ef1e97528e", - "sha256:6839ce14635ebfb01c67e456148f4f1fa04b03ef9645551b89d36593f2a3e57d", - "sha256:80e9f85ab81da75289308f23a62e1426a38411a07b0da738958d65ae8cc6c59c", - "sha256:881b44e94c781d02ecf1d9314fc7f44c09e6d54a8eac281869365999ac4db7a1", - "sha256:977542d2f8cf2981cf3ad72cedfebcd6ac56977c7aa830d9b49fa7888b56e83d", - "sha256:9bba6cbff7e23bb6d9062786d516602681b4414e9e423c138a7360e4d2a193e8", - "sha256:9bf64bba03ddc534ed3c6271ba8f9d31040f40cf8e9e7e458b6b1524a6f59082", - "sha256:9c712ceaa01488ebe6e357e1dfa2434c2304aad8a810e5d4c3d2abe21def6d58", - "sha256:b7da17e5a5c6039f85843e88c2f1ca8606d1a4cc13a87e7b68b9f51a54ef201a", - "sha256:bcdf81b3f13d2cc0354a4c3f7a567b71fcf6fe8098e519aaaee8e61f05c9de10", - "sha256:bebd3f987b7196e9d2ccfe55911b0c76ba9ce309bcabfb629ef205cbaaad37c5", - "sha256:c244e923073cd0bab74ba861ba31724aab90efda35b47a9676603c1a8e80b3ba", - "sha256:dacb94a9d570f4d9fc9369a22d44d7b3071cfe4d57d0ff2f57abd7ef6127fe41" - ], - "version": "==2.6.0" - }, - "pyyaml": { - "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" - }, - "six": { - "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" - ], - "version": "==1.14.0" - } - }, - "develop": { - "appdirs": { - "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" - ], - "version": "==1.4.3" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "black": { - "hashes": [ - "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", - "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c" - ], - "index": "pypi", - "version": "==19.3b0" - }, - "cfgv": { - "hashes": [ - "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53", - "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513" - ], - "version": "==3.1.0" - }, - "click": { - "hashes": [ - "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", - "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" - ], - "version": "==7.1.1" - }, - "distlib": { - "hashes": [ - "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" - ], - "version": "==0.3.0" - }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" - }, - "flake8": { - "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" - ], - "index": "pypi", - "version": "==3.7.9" - }, - "grpcio": { - "hashes": [ - "sha256:02aef8ef1a5ac5f0836b543e462eb421df6048a7974211a906148053b8055ea6", - "sha256:07f82aefb4a56c7e1e52b78afb77d446847d27120a838a1a0489260182096045", - "sha256:1cff47297ee614e7ef66243dc34a776883ab6da9ca129ea114a802c5e58af5c1", - "sha256:1ec8fc865d8da6d0713e2092a27eee344cd54628b2c2065a0e77fff94df4ae00", - "sha256:1ef949b15a1f5f30651532a9b54edf3bd7c0b699a10931505fa2c80b2d395942", - "sha256:209927e65395feb449783943d62a3036982f871d7f4045fadb90b2d82b153ea8", - "sha256:25c77692ea8c0929d4ad400ea9c3dcbcc4936cee84e437e0ef80da58fa73d88a", - "sha256:28f27c64dd699b8b10f70da5f9320c1cffcaefca7dd76275b44571bd097f276c", - "sha256:355bd7d7ce5ff2917d217f0e8ddac568cb7403e1ce1639b35a924db7d13a39b6", - "sha256:4a0a33ada3f6f94f855f92460896ef08c798dcc5f17d9364d1735c5adc9d7e4a", - "sha256:4d3b6e66f32528bf43ca2297caca768280a8e068820b1c3dca0fcf9f03c7d6f1", - "sha256:5121fa96c79fc0ec81825091d0be5c16865f834f41b31da40b08ee60552f9961", - "sha256:57949756a3ce1f096fa2b00f812755f5ab2effeccedb19feeb7d0deafa3d1de7", - "sha256:586d931736912865c9790c60ca2db29e8dc4eace160d5a79fec3e58df79a9386", - "sha256:5ae532b93cf9ce5a2a549b74a2c35e3b690b171ece9358519b3039c7b84c887e", - "sha256:5dab393ab96b2ce4012823b2f2ed4ee907150424d2f02b97bd6f8dd8f17cc866", - "sha256:5ebc13451246de82f130e8ee7e723e8d7ae1827f14b7b0218867667b1b12c88d", - "sha256:68a149a0482d0bc697aac702ec6efb9d380e0afebf9484db5b7e634146528371", - "sha256:6db7ded10b82592c472eeeba34b9f12d7b0ab1e2dcad12f081b08ebdea78d7d6", - "sha256:6e545908bcc2ae28e5b190ce3170f92d0438cf26a82b269611390114de0106eb", - "sha256:6f328a3faaf81a2546a3022b3dfc137cc6d50d81082dbc0c94d1678943f05df3", - "sha256:706e2dea3de33b0d8884c4d35ecd5911b4ff04d0697c4138096666ce983671a6", - "sha256:80c3d1ce8820dd819d1c9d6b63b6f445148480a831173b572a9174a55e7abd47", - "sha256:8111b61eee12d7af5c58f82f2c97c2664677a05df9225ef5cbc2f25398c8c454", - "sha256:9713578f187fb1c4d00ac554fe1edcc6b3ddd62f5d4eb578b81261115802df8e", - "sha256:9c0669ba9aebad540fb05a33beb7e659ea6e5ca35833fc5229c20f057db760e8", - "sha256:9e9cfe55dc7ac2aa47e0fd3285ff829685f96803197042c9d2f0fb44e4b39b2c", - "sha256:a22daaf30037b8e59d6968c76fe0f7ff062c976c7a026e92fbefc4c4bf3fc5a4", - "sha256:a25b84e10018875a0f294a7649d07c43e8bc3e6a821714e39e5cd607a36386d7", - "sha256:a71138366d57901597bfcc52af7f076ab61c046f409c7b429011cd68de8f9fe6", - "sha256:b4efde5524579a9ce0459ca35a57a48ca878a4973514b8bb88cb80d7c9d34c85", - "sha256:b78af4d42985ab3143d9882d0006f48d12f1bc4ba88e78f23762777c3ee64571", - "sha256:bb2987eb3af9bcf46019be39b82c120c3d35639a95bc4ee2d08f36ecdf469345", - "sha256:c03ce53690fe492845e14f4ab7e67d5a429a06db99b226b5c7caa23081c1e2bb", - "sha256:c59b9280284b791377b3524c8e39ca7b74ae2881ba1a6c51b36f4f1bb94cee49", - "sha256:d18b4c8cacbb141979bb44355ee5813dd4d307e9d79b3a36d66eca7e0a203df8", - "sha256:d1e5563e3b7f844dbc48d709c9e4a75647e11d0387cc1fa0c861d3e9d34bc844", - "sha256:d22c897b65b1408509099f1c3334bd3704f5e4eb7c0486c57d0e212f71cb8f54", - "sha256:dbec0a3a154dbf2eb85b38abaddf24964fa1c059ee0a4ad55d6f39211b1a4bca", - "sha256:ed123037896a8db6709b8ad5acc0ed435453726ea0b63361d12de369624c2ab5", - "sha256:f3614dabd2cc8741850597b418bcf644d4f60e73615906c3acc407b78ff720b3", - "sha256:f9d632ce9fd485119c968ec6a7a343de698c5e014d17602ae2f110f1b05925ed", - "sha256:fb62996c61eeff56b59ab8abfcaa0859ec2223392c03d6085048b576b567459b" - ], - "version": "==1.27.2" - }, - "grpcio-tools": { - "hashes": [ - "sha256:00c5080cfb197ed20ecf0d0ff2d07f1fc9c42c724cad21c40ff2d048de5712b1", - "sha256:069826dd02ce1886444cf4519c4fe1b05ac9ef41491f26e97400640531db47f6", - "sha256:1266b577abe7c720fd16a83d0a4999a192e87c4a98fc9f97e0b99b106b3e155f", - "sha256:16dc3fad04fe18d50777c56af7b2d9b9984cd1cfc71184646eb431196d1645c6", - "sha256:1de5a273eaffeb3d126a63345e9e848ea7db740762f700eb8b5d84c5e3e7687d", - "sha256:2ca280af2cae1a014a238057bd3c0a254527569a6a9169a01c07f0590081d530", - "sha256:43a1573400527a23e4174d88604fde7a9d9a69bf9473c21936b7f409858f8ebb", - "sha256:4698c6b6a57f73b14d91a542c69ff33a2da8729691b7060a5d7f6383624d045e", - "sha256:520b7dafddd0f82cb7e4f6e9c6ba1049aa804d0e207870def9fe7f94d1e14090", - "sha256:57f8b9e2c7f55cd45f6dd930d6de61deb42d3eb7f9788137fbc7155cf724132a", - "sha256:59fbeb5bb9a7b94eb61642ac2cee1db5233b8094ca76fc56d4e0c6c20b5dd85f", - "sha256:5fd7efc2fd3370bd2c72dc58f31a407a5dff5498befa145da211b2e8c6a52c63", - "sha256:6016c07d6566e3109a3c032cf3861902d66501ecc08a5a84c47e43027302f367", - "sha256:627c91923df75091d8c4d244af38d5ab7ed8d786d480751d6c2b9267fbb92fe0", - "sha256:69c4a63919b9007e845d9f8980becd2f89d808a4a431ca32b9723ee37b521cb1", - "sha256:77e25c241e33b75612f2aa62985f746c6f6803ec4e452da508bb7f8d90a69db4", - "sha256:7a2d5fb558ac153a326e742ebfd7020eb781c43d3ffd920abd42b2e6c6fdfb37", - "sha256:7b54b283ec83190680903a9037376dc915e1f03852a2d574ba4d981b7a1fd3d0", - "sha256:845a51305af9fc7f9e2078edaec9a759153195f6cf1fbb12b1fa6f077e56b260", - "sha256:84724458c86ff9b14c29b49e321f34d80445b379f4cd4d0494c694b49b1d6f88", - "sha256:87e8ca2c2d2d3e09b2a2bed5d740d7b3e64028dafb7d6be543b77eec85590736", - "sha256:8e7738a4b93842bca1158cde81a3587c9b7111823e40a1ddf73292ca9d58e08b", - "sha256:915a695bc112517af48126ee0ecdb6aff05ed33f3eeef28f0d076f1f6b52ef5e", - "sha256:99961156a36aae4a402d6b14c1e7efde642794b3ddbf32c51db0cb3a199e8b11", - "sha256:9ba88c2d99bcaf7b9cb720925e3290d73b2367d238c5779363fd5598b2dc98c7", - "sha256:a140bf853edb2b5e8692fe94869e3e34077d7599170c113d07a58286c604f4fe", - "sha256:a14dc7a36c845991d908a7179502ca47bcba5ae1817c4426ce68cf2c97b20ad9", - "sha256:a3d2aec4b09c8e59fee8b0d1ed668d09e8c48b738f03f5d8401d7eb409111c47", - "sha256:a8f892378b0b02526635b806f59141abbb429d19bec56e869e04f396502c9651", - "sha256:aaa5ae26883c3d58d1a4323981f96b941fa09bb8f0f368d97c6225585280cf04", - "sha256:b56caecc16307b088a431a4038c3b3bb7d0e7f9988cbd0e9fa04ac937455ea38", - "sha256:bd7f59ff1252a3db8a143b13ea1c1e93d4b8cf4b852eb48b22ef1e6942f62a84", - "sha256:c1bb8f47d58e9f7c4825abfe01e6b85eda53c8b31d2267ca4cddf3c4d0829b80", - "sha256:d1a5e5fa47ba9557a7d3b31605631805adc66cdba9d95b5d10dfc52cca1fed53", - "sha256:dcbc06556f3713a9348c4fce02d05d91e678fc320fb2bcf0ddf8e4bb11d17867", - "sha256:e17b2e0936b04ced99769e26111e1e86ba81619d1b2691b1364f795e45560953", - "sha256:e6932518db389ede8bf06b4119bbd3e17f42d4626e72dec2b8955b20ec732cb6", - "sha256:ea4b3ad696d976d5eac74ec8df9a2c692113e455446ee38d5b3bd87f8e034fa6", - "sha256:ee50b0cf0d28748ef9f941894eb50fc464bd61b8e96aaf80c5056bea9b80d580", - "sha256:ef624b6134aef737b3daa4fb7e806cb8c5749efecd0b1fa9ce4f7e060c7a0221", - "sha256:f5450aa904e720f9c6407b59e96a8951ed6a95463f49444b6d2594b067d39588", - "sha256:f8514453411d72cc3cf7d481f2b6057e5b7436736d0cd39ee2b2f72088bbf497", - "sha256:fae91f30dc050a8d0b32d20dc700e6092f0bd2138d83e9570fff3f0372c1b27e" - ], - "index": "pypi", - "version": "==1.27.2" - }, - "identify": { - "hashes": [ - "sha256:a7577a1f55cee1d21953a5cf11a3c839ab87f5ef909a4cba6cf52ed72b4c6059", - "sha256:ab246293e6585a1c6361a505b68d5b501a0409310932b7de2c2ead667b564d89" - ], - "version": "==1.4.13" - }, - "importlib-metadata": { - "hashes": [ - "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", - "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" - ], - "markers": "python_version < '3.8'", - "version": "==1.6.0" - }, - "importlib-resources": { - "hashes": [ - "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2", - "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8" - ], - "markers": "python_version < '3.7'", - "version": "==1.4.0" - }, - "isort": { - "hashes": [ - "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", - "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" - ], - "index": "pypi", - "version": "==4.3.21" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "mock": { - "hashes": [ - "sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0", - "sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72" - ], - "index": "pypi", - "version": "==4.0.2" - }, - "more-itertools": { - "hashes": [ - "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", - "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" - ], - "version": "==8.2.0" - }, - "nodeenv": { - "hashes": [ - "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212" - ], - "version": "==1.3.5" - }, - "packaging": { - "hashes": [ - "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", - "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" - ], - "version": "==20.3" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "version": "==0.13.1" - }, - "pre-commit": { - "hashes": [ - "sha256:487c675916e6f99d355ec5595ad77b325689d423ef4839db1ed2f02f639c9522", - "sha256:c0aa11bce04a7b46c5544723aedf4e81a4d5f64ad1205a30a9ea12d5e81969e1" - ], - "index": "pypi", - "version": "==2.2.0" - }, - "protobuf": { - "hashes": [ - "sha256:0bae429443cc4748be2aadfdaf9633297cfaeb24a9a02d0ab15849175ce90fab", - "sha256:24e3b6ad259544d717902777b33966a1a069208c885576254c112663e6a5bb0f", - "sha256:310a7aca6e7f257510d0c750364774034272538d51796ca31d42c3925d12a52a", - "sha256:52e586072612c1eec18e1174f8e3bb19d08f075fc2e3f91d3b16c919078469d0", - "sha256:73152776dc75f335c476d11d52ec6f0f6925774802cd48d6189f4d5d7fe753f4", - "sha256:7774bbbaac81d3ba86de646c39f154afc8156717972bf0450c9dbfa1dc8dbea2", - "sha256:82d7ac987715d8d1eb4068bf997f3053468e0ce0287e2729c30601feb6602fee", - "sha256:8eb9c93798b904f141d9de36a0ba9f9b73cc382869e67c9e642c0aba53b0fc07", - "sha256:adf0e4d57b33881d0c63bb11e7f9038f98ee0c3e334c221f0858f826e8fb0151", - "sha256:c40973a0aee65422d8cb4e7d7cbded95dfeee0199caab54d5ab25b63bce8135a", - "sha256:c77c974d1dadf246d789f6dad1c24426137c9091e930dbf50e0a29c1fcf00b1f", - "sha256:dd9aa4401c36785ea1b6fff0552c674bdd1b641319cb07ed1fe2392388e9b0d7", - "sha256:e11df1ac6905e81b815ab6fd518e79be0a58b5dc427a2cf7208980f30694b956", - "sha256:e2f8a75261c26b2f5f3442b0525d50fd79a71aeca04b5ec270fc123536188306", - "sha256:e512b7f3a4dd780f59f1bf22c302740e27b10b5c97e858a6061772668cd6f961", - "sha256:ef2c2e56aaf9ee914d3dccc3408d42661aaf7d9bb78eaa8f17b2e6282f214481", - "sha256:fac513a9dc2a74b99abd2e17109b53945e364649ca03d9f7a0b96aa8d1807d0a", - "sha256:fdfb6ad138dbbf92b5dbea3576d7c8ba7463173f7d2cb0ca1bd336ec88ddbd80" - ], - "version": "==3.11.3" - }, - "py": { - "hashes": [ - "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", - "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" - ], - "version": "==1.8.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" - ], - "version": "==2.5.0" - }, - "pyflakes": { - "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" - ], - "version": "==2.1.1" - }, - "pyparsing": { - "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" - ], - "version": "==2.4.6" - }, - "pytest": { - "hashes": [ - "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", - "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" - ], - "index": "pypi", - "version": "==5.4.1" - }, - "pyyaml": { - "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" - }, - "six": { - "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" - ], - "version": "==1.14.0" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" - ], - "version": "==0.10.0" - }, - "virtualenv": { - "hashes": [ - "sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25", - "sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5" - ], - "version": "==20.0.15" - }, - "wcwidth": { - "hashes": [ - "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", - "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" - ], - "version": "==0.1.9" - }, - "zipp": { - "hashes": [ - "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", - "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" - ], - "markers": "python_version < '3.8'", - "version": "==3.1.0" - } - } -} From cb66ba60a6eb8195b2f8136e75aa4e8c891ef83f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 08:54:08 -0700 Subject: [PATCH 161/210] removed kernel-modules-extra, so it is only attempted in centos8 --- tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index b4f376798..bdabc837a 100644 --- a/tasks.py +++ b/tasks.py @@ -81,8 +81,7 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: elif os_info.like == OsLike.REDHAT: c.run( "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel " - "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool make " - "kernel-modules-extra", + "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool make", hide=hide ) # centos 8+ does not support netem by default From e283c5ec7d0624d2931424fefafe1e41f842017d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:24:59 -0700 Subject: [PATCH 162/210] broke out invoke tasks for installing scripts and service, testing centos not needing prefix --- tasks.py | 69 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/tasks.py b/tasks.py index bdabc837a..9168542cc 100644 --- a/tasks.py +++ b/tasks.py @@ -115,7 +115,8 @@ def install_grpcio(c: Context, hide: bool) -> None: def build(c: Context, os_info: OsInfo, hide: bool) -> None: print("building core...") c.run("./bootstrap.sh", hide=hide) - prefix = "--prefix=/usr" if os_info.like == OsLike.REDHAT else "" + # prefix = "--prefix=/usr" if os_info.like == OsLike.REDHAT else "" + prefix = "" c.run(f"./configure {prefix}", hide=hide) c.run("make -j$(nproc)", hide=hide) @@ -169,7 +170,43 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: c.run("sudo make install", hide=hide) -def install_files(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: +@task +def install_service(c, hide, prefix=DEFAULT_PREFIX): + """ + install systemd core service + """ + # install service + bin_dir = Path(prefix).joinpath("bin") + systemd_dir = Path("/lib/systemd/system/") + service_file = systemd_dir.joinpath("core-daemon.service") + if systemd_dir.exists(): + print(f"installing core-daemon.service for systemd to {service_file}") + service_data = inspect.cleandoc(f""" + [Unit] + Description=Common Open Research Emulator Service + After=network.target + + [Service] + Type=simple + ExecStart={bin_dir}/core-daemon + TasksMax=infinity + + [Install] + WantedBy=multi-user.target + """) + temp = NamedTemporaryFile("w", delete=False) + temp.write(service_data) + temp.close() + c.run(f"sudo cp {temp.name} {service_file}", hide=hide) + else: + print(f"ERROR: systemd service path not found: {systemd_dir}") + + +@task +def install_scripts(c, hide, prefix=DEFAULT_PREFIX): + """ + install core script files, modified to leverage virtual environment + """ # install all scripts python = get_python(c) bin_dir = Path(prefix).joinpath("bin") @@ -200,34 +237,11 @@ def install_files(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: c.run(f"sudo cp -n daemon/data/core.conf {config_dir}", hide=hide) c.run(f"sudo cp -n daemon/data/logging.conf {config_dir}", hide=hide) - # install service - systemd_dir = Path("/lib/systemd/system/") - service_file = systemd_dir.joinpath("core-daemon.service") - if systemd_dir.exists(): - print(f"installing core-daemon.service for systemd to {service_file}") - service_data = inspect.cleandoc(f""" - [Unit] - Description=Common Open Research Emulator Service - After=network.target - - [Service] - Type=simple - ExecStart={bin_dir}/core-daemon - TasksMax=infinity - - [Install] - WantedBy=multi-user.target - """) - temp = NamedTemporaryFile("w", delete=False) - temp.write(service_data) - temp.close() - c.run(f"sudo cp {temp.name} {service_file}", hide=hide) - @task def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ - install core + install core, poetry, scripts, service, and ospf mdr """ print(f"installing core with prefix: {prefix}") hide = not verbose @@ -237,7 +251,8 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): build(c, os_info, hide) install_core(c, hide) install_poetry(c, dev, hide) - install_files(c, hide, prefix) + install_scripts(c, hide, prefix) + install_service(c, hide, prefix) install_ospf_mdr(c, os_info, hide) print("please open a new terminal or re-login to leverage invoke for running core") print("# run daemon") From 5d23be4a9d26e326d9c3468ac4c4cdd67199231b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:30:04 -0700 Subject: [PATCH 163/210] updates to install2.md to replace install docs --- docs/install2.md | 130 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/docs/install2.md b/docs/install2.md index 86e3db92b..921c80f08 100644 --- a/docs/install2.md +++ b/docs/install2.md @@ -1,4 +1,7 @@ -# Installing CORE +# CORE Installation + +* Table of Contents +{:toc} ## Overview @@ -14,6 +17,11 @@ The following tools will be leveraged during installation: |invoke|used to run provided tasks (install, daemon, gui, tests, etc)| |poetry|used to install the managed python virtual environment for running CORE| +## Required Hardware + +Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous +containers, as a general rule you should select a machine having as much RAM and CPU resources as possible. + ## Supported Linux Distributions Plan is to support recent Ubuntu and CentOS LTS releases. @@ -28,16 +36,130 @@ Verified: > **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not > function properly -## Running Installation +## Utility Requirements + +* iproute2 4.5+ is a requirement for bridge related commands +* ebtables not backed by nftables + +## Automated Installation + +> **NOTE:** installs OSPF MDR +> **NOTE:** sets up script files using the prefix provided +> **NOTE:** install a systemd service file to /lib/systemd/system/core-daemon.service ```shell # clone CORE repo git clone https://github.com/coreemu/core.git cd core -git checkout enhancement/poetry-invoke # run install script -./install2.sh +# script usage: install.sh [-d] [-v] +# +# -v enable verbose install +# -d enable developer install +# -p install prefix, defaults to /usr/local +./install.sh +``` + +## Manual Installation + +> **NOTE:** install OSPF MDR by manual instructions below + +```shell +# clone CORE repo +git clone https://github.com/coreemu/core.git +cd core + +# install python3 and venv support +# ubuntu +sudo apt install -y python3-pip python3-venv +# centos +sudo yum install -y python3-pip + +# install system dependencies +# ubuntu +sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ + ethtool tk python3-tk +# centos +sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel \ + iptables-ebtables iproute python3-devel python3-tkinter tk ethtool \ + make kernel-modules-extra + +# install grpcio-tools +python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 + +# build core +./bootstrap.sh +./configure +make +sudo make install + +# install pipx, may need to restart terminal after ensurepath +python3 -m pip install --user pipx +python3 -m pipx ensurepath + +# install poetry +pipx install poetry + +# install poetry virtual environment +cd daemon +poetry install --no-dev +cd .. + +# install invoke to run helper tasks +pipx install invoke + +# install core scripts leveraging poetry virtual environment +inv install-scripts + +# optionally install systemd service file +inv install-service +``` + +## Manually Install OSPF MDR (Routing Support) + +Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing +tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by +default when the blue router node type is used. + +* [OSPF MANET Designated Routers](https://github.com/USNavalResearchLaboratory/ospf-mdr) (MDR) - the Quagga routing +suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The **mdr** node type +(and the MDR service) requires this variant of Quagga. + +```shell +# system dependencies +# ubuntu +sudo apt install -y libtool gawk libreadline-dev +# centos +sudo yum install -y libtool gawk readline-devel + +# build and install +git clone https://github.com/USNavalResearchLaboratory/ospf-mdr +cd 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 +sudo make install +``` + +## Manually Install EMANE + +EMANE can be installed from deb or RPM packages or from source. See the +[EMANE GitHub](https://github.com/adjacentlink/emane) for full details. + +Here are quick instructions for installing all EMANE packages for Ubuntu 18.04: +```shell +# install dependencies +# ubuntu +sudo apt-get install libssl-dev libxml-libxml-perl libxml-simple-perl +wget https://adjacentlink.com/downloads/emane/emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz +tar xzf emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz +# install base emane packages +sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/emane*.deb +# install python3 bindings +sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/python3*.deb ``` ## Using Invoke Tasks From 6c7e760f4ea9e986a26ef785f982b9705358da54 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:31:05 -0700 Subject: [PATCH 164/210] tweak to install doc --- docs/install2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/install2.md b/docs/install2.md index 921c80f08..9d8d97a46 100644 --- a/docs/install2.md +++ b/docs/install2.md @@ -44,7 +44,9 @@ Verified: ## Automated Installation > **NOTE:** installs OSPF MDR + > **NOTE:** sets up script files using the prefix provided + > **NOTE:** install a systemd service file to /lib/systemd/system/core-daemon.service ```shell From 35b6f5297a95faeb2700b1d1ea382d7df05cb14d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:42:14 -0700 Subject: [PATCH 165/210] update doc and install to properly set and provide options for OSs like centos who need a different prefix --- docs/install2.md | 3 +++ tasks.py | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/install2.md b/docs/install2.md index 9d8d97a46..1eb94695e 100644 --- a/docs/install2.md +++ b/docs/install2.md @@ -92,6 +92,7 @@ python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 # build core ./bootstrap.sh +# centos requires --prefix=/usr ./configure make sudo make install @@ -112,9 +113,11 @@ cd .. pipx install invoke # install core scripts leveraging poetry virtual environment +# centos requires --prefix=/usr inv install-scripts # optionally install systemd service file +# centos requires --prefix=/usr inv install-service ``` diff --git a/tasks.py b/tasks.py index 9168542cc..5c335644c 100644 --- a/tasks.py +++ b/tasks.py @@ -115,8 +115,7 @@ def install_grpcio(c: Context, hide: bool) -> None: def build(c: Context, os_info: OsInfo, hide: bool) -> None: print("building core...") c.run("./bootstrap.sh", hide=hide) - # prefix = "--prefix=/usr" if os_info.like == OsLike.REDHAT else "" - prefix = "" + prefix = "--prefix=/usr" if os_info.like == OsLike.REDHAT else "" c.run(f"./configure {prefix}", hide=hide) c.run("make -j$(nproc)", hide=hide) @@ -171,11 +170,11 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: @task -def install_service(c, hide, prefix=DEFAULT_PREFIX): +def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): """ install systemd core service """ - # install service + hide = not verbose bin_dir = Path(prefix).joinpath("bin") systemd_dir = Path("/lib/systemd/system/") service_file = systemd_dir.joinpath("core-daemon.service") @@ -203,11 +202,11 @@ def install_service(c, hide, prefix=DEFAULT_PREFIX): @task -def install_scripts(c, hide, prefix=DEFAULT_PREFIX): +def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX): """ install core script files, modified to leverage virtual environment """ - # install all scripts + hide = not verbose python = get_python(c) bin_dir = Path(prefix).joinpath("bin") for script in Path("daemon/scripts").iterdir(): From 08105cf4b3603fd134bdfe46208c87dc0cbde5bc Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:44:28 -0700 Subject: [PATCH 166/210] updated list of invoke tasks in doc --- docs/install2.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/install2.md b/docs/install2.md index 1eb94695e..42716fd3a 100644 --- a/docs/install2.md +++ b/docs/install2.md @@ -175,13 +175,17 @@ CORE tasks to help ensure usage of the create python virtual environment. ```shell Available tasks: - cleanup run core-cleanup removing leftover core nodes, bridges, directories - daemon start core-daemon - gui start core-pygui - install install core - test run core tests - test-emane run core emane tests - test-mock run core tests using mock to avoid running as sudo + cleanup run core-cleanup removing leftover core nodes, bridges, directories + cli run core-cli used to query and modify a running session + daemon start core-daemon + gui start core-pygui + install install core, poetry, scripts, service, and ospf mdr + install-scripts install core script files, modified to leverage virtual environment + install-service install systemd core service + test run core tests + test-emane run core emane tests + test-mock run core tests using mock to avoid running as sudo + uninstall uninstall core ``` Example running the core-daemon task from the root of the repo: From 6b5aaa6b19ca09852bacc6da360d3a5df9a16859 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:01:57 -0700 Subject: [PATCH 167/210] adjust how invoke install prefix is used for core configure --- tasks.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index 5c335644c..9da66faac 100644 --- a/tasks.py +++ b/tasks.py @@ -112,11 +112,10 @@ def install_grpcio(c: Context, hide: bool) -> None: ) -def build(c: Context, os_info: OsInfo, hide: bool) -> None: +def build(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: print("building core...") c.run("./bootstrap.sh", hide=hide) - prefix = "--prefix=/usr" if os_info.like == OsLike.REDHAT else "" - c.run(f"./configure {prefix}", hide=hide) + c.run(f"./configure --prefix={prefix}", hide=hide) c.run("make -j$(nproc)", hide=hide) @@ -247,7 +246,7 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): os_info = get_os() install_system(c, os_info, hide) install_grpcio(c, hide) - build(c, os_info, hide) + build(c, hide, prefix) install_core(c, hide) install_poetry(c, dev, hide) install_scripts(c, hide, prefix) From dfb3e0c4242c209ed35c4eb6f63f3f284696ab83 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:42:24 -0700 Subject: [PATCH 168/210] default install docs and script to poetry based installs --- docs/install.md | 351 ++++++++++++++++++----------------------------- docs/install2.md | 212 ---------------------------- install.sh | 166 +++++----------------- install2.sh | 57 -------- 4 files changed, 162 insertions(+), 624 deletions(-) delete mode 100644 docs/install2.md delete mode 100755 install2.sh diff --git a/docs/install.md b/docs/install.md index 6abd09454..42716fd3a 100644 --- a/docs/install.md +++ b/docs/install.md @@ -5,102 +5,123 @@ ## Overview -This section will describe how to install CORE from source or from a pre-built package. -CORE has been vetted on Ubuntu 18 and CentOS 7.6. Other versions and distributions -can work, assuming you can get the required packages and versions similar to those -noted below for the tested distributions. +CORE provides a script to help automate installing all required software +to build and run, including a python virtual environment to run it all in. -> **NOTE:** iproute2 4.5+ is a requirement for bridge related commands +The following tools will be leveraged during installation: + +|Tool|Description| +|---|---| +|pip|used to install pipx| +|pipx|used to install standalone python tools (invoke, poetry)| +|invoke|used to run provided tasks (install, daemon, gui, tests, etc)| +|poetry|used to install the managed python virtual environment for running CORE| ## Required Hardware Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous containers, as a general rule you should select a machine having as much RAM and CPU resources as possible. -## Operating System +## Supported Linux Distributions + +Plan is to support recent Ubuntu and CentOS LTS releases. -CORE requires a Linux operating system because it uses namespacing provided by the kernel. It does not run on -Windows or Mac OS X operating systems (unless it is running within a virtual machine guest.) The -technology that CORE currently uses is Linux network namespaces. +Verified: +* Ubuntu - 18.04, 20.04 +* CentOS - 7.8, 8.0* -Ubuntu and CentOS Linux are the recommended distributions for running CORE. However, these distributions are -not strictly required. CORE will likely work on other flavors of Linux as well, assuming dependencies are met. +> **NOTE:** Ubuntu 20.04 requires installing legacy ebtables for WLAN +> functionality -> **NOTE:** CORE Services determine what run on each node. You may require other software packages depending on the -services you wish to use. For example, the HTTP service will require the apache2 package. +> **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not +> function properly -## Installed Files +## Utility Requirements -CORE files are installed to the following directories by default, when the installation prefix is **/usr**. +* iproute2 4.5+ is a requirement for bridge related commands +* ebtables not backed by nftables -Install Path | Description --------------|------------ -/usr/bin/core-gui|GUI startup command -/usr/bin/coretk-gui|BETA Python GUI -/usr/bin/core-daemon|Daemon startup command -/usr/bin/{core-cleanup, coresendmsg, core-manage}|Misc. helper commands/scripts -/usr/lib/core|GUI files -/usr/lib/python{3.6+}/dist-packages/core|Python modules for daemon/scripts -/etc/core/|Daemon and log configuration files -~/.core/|User-specific GUI preferences and scenario files -/usr/share/core/|Example scripts and scenarios -/usr/share/man/man1/|Command man pages -/etc/init.d/core-daemon|SysV startup script for daemon -/usr/lib/systemd/system/core-daemon.service|Systemd startup script for daemon +## Automated Installation -## Automated Install +> **NOTE:** installs OSPF MDR -There is a helper script in the root of the repository that can help automate -the CORE installation. Some steps require commands be ran as sudo and you -will be prompted for a password. This should work on Ubuntu/CentOS and will -install system dependencies, python dependencies, and CORE. This will target -system installations of python 3.6. +> **NOTE:** sets up script files using the prefix provided + +> **NOTE:** install a systemd service file to /lib/systemd/system/core-daemon.service ```shell +# clone CORE repo git clone https://github.com/coreemu/core.git cd core + +# run install script +# script usage: install.sh [-d] [-v] +# +# -v enable verbose install +# -d enable developer install +# -p install prefix, defaults to /usr/local ./install.sh ``` -You can target newer system python versions using the **-v** flag. Assuming -these versions are actually available on your system. +## Manual Installation -```shell -# ubuntu 3.7 -./install.sh -v 3.7 -# centos 3.7 -./install.sh -v 37 -``` +> **NOTE:** install OSPF MDR by manual instructions below -## Pre-Req Installing Python +```shell +# clone CORE repo +git clone https://github.com/coreemu/core.git +cd core -Python 3.6 is the minimum required python version. Newer versions can be used if available. -These steps are needed, since the system packages can not provide all the -dependencies needed by CORE. +# install python3 and venv support +# ubuntu +sudo apt install -y python3-pip python3-venv +# centos +sudo yum install -y python3-pip + +# install system dependencies +# ubuntu +sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ + ethtool tk python3-tk +# centos +sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel \ + iptables-ebtables iproute python3-devel python3-tkinter tk ethtool \ + make kernel-modules-extra + +# install grpcio-tools +python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 -### Ubuntu +# build core +./bootstrap.sh +# centos requires --prefix=/usr +./configure +make +sudo make install -```shell -sudo apt install python3.6 -sudo apt install python3-pip -``` +# install pipx, may need to restart terminal after ensurepath +python3 -m pip install --user pipx +python3 -m pipx ensurepath -### CentOS +# install poetry +pipx install poetry -```shell -sudo yum install python36 -sudo yum install python3-pip -``` +# install poetry virtual environment +cd daemon +poetry install --no-dev +cd .. -### Dependencies +# install invoke to run helper tasks +pipx install invoke -Install the current python dependencies. +# install core scripts leveraging poetry virtual environment +# centos requires --prefix=/usr +inv install-scripts -```shell -sudo python3 -m pip install -r requirements.txt +# optionally install systemd service file +# centos requires --prefix=/usr +inv install-service ``` -## Pre-Req Installing OSPF MDR +## Manually Install OSPF MDR (Routing Support) Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by @@ -110,21 +131,14 @@ default when the blue router node type is used. suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The **mdr** node type (and the MDR service) requires this variant of Quagga. -### Ubuntu - -```shell -sudo apt install libtool gawk libreadline-dev -``` - -### CentOS - ```shell -sudo yum install libtool gawk readline-devel -``` +# system dependencies +# ubuntu +sudo apt install -y libtool gawk libreadline-dev +# centos +sudo yum install -y libtool gawk readline-devel -### Build and Install - -```shell +# build and install git clone https://github.com/USNavalResearchLaboratory/ospf-mdr cd ospf-mdr ./bootstrap.sh @@ -135,167 +149,64 @@ make sudo make install ``` -Note that the configuration directory */usr/local/etc/quagga* shown for Quagga above could be */etc/quagga*, -if you create a symbolic link from */etc/quagga/Quagga.conf -> /usr/local/etc/quagga/Quagga.conf* on the host. -The *quaggaboot.sh* script in a Linux network namespace will try and do this for you if needed. - -If you try to run quagga after installing from source and get an error such as: - -```shell -error while loading shared libraries libzebra.so.0 -``` - -this is usually a sign that you have to run ```sudo ldconfig```` to refresh the cache file. - -## Installing from Packages - -The easiest way to install CORE is using the pre-built packages. The package managers on Ubuntu or CentOS -will help in automatically installing most dependencies, except for the python ones described previously. - -You can obtain the CORE packages from [CORE Releases](https://github.com/coreemu/core/releases). - -### Ubuntu - -Ubuntu package defaults to using systemd for running as a service. - -```shell -sudo apt install ./core_$VERSION_amd64.deb -``` - -### CentOS - -**NOTE: tkimg is not required for the core-gui, but if you get an error message about it you can install the package -on CentOS <= 6, or build from source otherwise** - -```shell -yum install ./core_$VERSION_x86_64.rpm -``` - -Disabling SELINUX: - -```shell -# change the following in /etc/sysconfig/selinux -SELINUX=disabled - -# add the following to the kernel line in /etc/grub.conf -selinux=0 -``` - -Turn off firewalls: - -```shell -systemctl disable firewalld -systemctl disable iptables.service -systemctl disable ip6tables.service -chkconfig iptables off -chkconfig ip6tables off -``` - -You need to reboot after making these changes, or flush the firewall using - -```shell -iptables -F -ip6tables -F -``` - -## Installing from Source - -Steps for building from cloned source code. Python 3.6 is the minimum required version -a newer version can be used below if available. - -### Distro Requirements - -System packages required to build from source. - -#### Ubuntu - -```shell -sudo apt install git automake pkg-config gcc libev-dev ebtables iproute2 \ - python3.6 python3.6-dev python3-pip python3-tk tk libtk-img ethtool autoconf -``` - -#### CentOS - -```shell -sudo yum install git automake pkgconf-pkg-config gcc gcc-c++ libev-devel iptables-ebtables iproute \ - python36 python36-devel python3-pip python3-tkinter tk ethtool autoconf -``` - -### Clone Repository - -Clone the CORE repository for building from source. - -```shell -git clone https://github.com/coreemu/core.git -``` - -### Install grpcio-tools +## Manually Install EMANE -Python module grpcio-tools is currently needed to generate gRPC protobuf code. -Specifically leveraging 1.27.2 as that is what will be used during runtime. +EMANE can be installed from deb or RPM packages or from source. See the +[EMANE GitHub](https://github.com/adjacentlink/emane) for full details. +Here are quick instructions for installing all EMANE packages for Ubuntu 18.04: ```shell -python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 +# install dependencies +# ubuntu +sudo apt-get install libssl-dev libxml-libxml-perl libxml-simple-perl +wget https://adjacentlink.com/downloads/emane/emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz +tar xzf emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz +# install base emane packages +sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/emane*.deb +# install python3 bindings +sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/python3*.deb ``` -### Build and Install +## Using Invoke Tasks -```shell -./bootstrap.sh -./configure -make -sudo make install -``` - -## Building Documentation - -Building documentation requires python-sphinx not noted above. +The invoke tool installed by way of pipx provides conveniences for running +CORE tasks to help ensure usage of the create python virtual environment. ```shell -sudo apt install python3-sphinx -sudo yum install python3-sphinx +Available tasks: -./bootstrap.sh -./configure -make doc + cleanup run core-cleanup removing leftover core nodes, bridges, directories + cli run core-cli used to query and modify a running session + daemon start core-daemon + gui start core-pygui + install install core, poetry, scripts, service, and ospf mdr + install-scripts install core script files, modified to leverage virtual environment + install-service install systemd core service + test run core tests + test-emane run core emane tests + test-mock run core tests using mock to avoid running as sudo + uninstall uninstall core ``` -## Building Packages -Build package commands, DESTDIR is used to make install into and then for packaging by fpm. - -**NOTE: clean the DESTDIR if re-using the same directory** - -* Install [fpm](http://fpm.readthedocs.io/en/latest/installing.html) - +Example running the core-daemon task from the root of the repo: ```shell -./bootstrap.sh -./configure -make -mkdir /tmp/core-build -make fpm DESTDIR=/tmp/core-build +inv daemon ``` -This will produce and RPM and Deb package for the currently configured python version. - -## Running CORE - -Start the CORE daemon. +Some tasks are wrappers around command line tools and requires running +them with a slight variation for compatibility. You can enter the +poetry shell to run the script natively. ```shell -# systemd -sudo systemctl daemon-reload -sudo systemctl start core-daemon - -# sysv -sudo service core-daemon start -``` - -Run the GUI +# running core-cli as a task requires all options to be provided +# within a string +inv cli "query session -i 1" -```shell -# default gui -core-gui +# entering the poetry shell to use core-cli natively +cd $REPO/daemon +poetry shell +core-cli query session -i 1 -# new beta gui -coretk-gui +# exit the shell +exit ``` diff --git a/docs/install2.md b/docs/install2.md deleted file mode 100644 index 42716fd3a..000000000 --- a/docs/install2.md +++ /dev/null @@ -1,212 +0,0 @@ -# CORE Installation - -* Table of Contents -{:toc} - -## Overview - -CORE provides a script to help automate installing all required software -to build and run, including a python virtual environment to run it all in. - -The following tools will be leveraged during installation: - -|Tool|Description| -|---|---| -|pip|used to install pipx| -|pipx|used to install standalone python tools (invoke, poetry)| -|invoke|used to run provided tasks (install, daemon, gui, tests, etc)| -|poetry|used to install the managed python virtual environment for running CORE| - -## Required Hardware - -Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous -containers, as a general rule you should select a machine having as much RAM and CPU resources as possible. - -## Supported Linux Distributions - -Plan is to support recent Ubuntu and CentOS LTS releases. - -Verified: -* Ubuntu - 18.04, 20.04 -* CentOS - 7.8, 8.0* - -> **NOTE:** Ubuntu 20.04 requires installing legacy ebtables for WLAN -> functionality - -> **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not -> function properly - -## Utility Requirements - -* iproute2 4.5+ is a requirement for bridge related commands -* ebtables not backed by nftables - -## Automated Installation - -> **NOTE:** installs OSPF MDR - -> **NOTE:** sets up script files using the prefix provided - -> **NOTE:** install a systemd service file to /lib/systemd/system/core-daemon.service - -```shell -# clone CORE repo -git clone https://github.com/coreemu/core.git -cd core - -# run install script -# script usage: install.sh [-d] [-v] -# -# -v enable verbose install -# -d enable developer install -# -p install prefix, defaults to /usr/local -./install.sh -``` - -## Manual Installation - -> **NOTE:** install OSPF MDR by manual instructions below - -```shell -# clone CORE repo -git clone https://github.com/coreemu/core.git -cd core - -# install python3 and venv support -# ubuntu -sudo apt install -y python3-pip python3-venv -# centos -sudo yum install -y python3-pip - -# install system dependencies -# ubuntu -sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ - ethtool tk python3-tk -# centos -sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel \ - iptables-ebtables iproute python3-devel python3-tkinter tk ethtool \ - make kernel-modules-extra - -# install grpcio-tools -python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 - -# build core -./bootstrap.sh -# centos requires --prefix=/usr -./configure -make -sudo make install - -# install pipx, may need to restart terminal after ensurepath -python3 -m pip install --user pipx -python3 -m pipx ensurepath - -# install poetry -pipx install poetry - -# install poetry virtual environment -cd daemon -poetry install --no-dev -cd .. - -# install invoke to run helper tasks -pipx install invoke - -# install core scripts leveraging poetry virtual environment -# centos requires --prefix=/usr -inv install-scripts - -# optionally install systemd service file -# centos requires --prefix=/usr -inv install-service -``` - -## Manually Install OSPF MDR (Routing Support) - -Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing -tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by -default when the blue router node type is used. - -* [OSPF MANET Designated Routers](https://github.com/USNavalResearchLaboratory/ospf-mdr) (MDR) - the Quagga routing -suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The **mdr** node type -(and the MDR service) requires this variant of Quagga. - -```shell -# system dependencies -# ubuntu -sudo apt install -y libtool gawk libreadline-dev -# centos -sudo yum install -y libtool gawk readline-devel - -# build and install -git clone https://github.com/USNavalResearchLaboratory/ospf-mdr -cd 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 -sudo make install -``` - -## Manually Install EMANE - -EMANE can be installed from deb or RPM packages or from source. See the -[EMANE GitHub](https://github.com/adjacentlink/emane) for full details. - -Here are quick instructions for installing all EMANE packages for Ubuntu 18.04: -```shell -# install dependencies -# ubuntu -sudo apt-get install libssl-dev libxml-libxml-perl libxml-simple-perl -wget https://adjacentlink.com/downloads/emane/emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz -tar xzf emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz -# install base emane packages -sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/emane*.deb -# install python3 bindings -sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/python3*.deb -``` - -## Using Invoke Tasks - -The invoke tool installed by way of pipx provides conveniences for running -CORE tasks to help ensure usage of the create python virtual environment. - -```shell -Available tasks: - - cleanup run core-cleanup removing leftover core nodes, bridges, directories - cli run core-cli used to query and modify a running session - daemon start core-daemon - gui start core-pygui - install install core, poetry, scripts, service, and ospf mdr - install-scripts install core script files, modified to leverage virtual environment - install-service install systemd core service - test run core tests - test-emane run core emane tests - test-mock run core tests using mock to avoid running as sudo - uninstall uninstall core -``` - -Example running the core-daemon task from the root of the repo: -```shell -inv daemon -``` - -Some tasks are wrappers around command line tools and requires running -them with a slight variation for compatibility. You can enter the -poetry shell to run the script natively. - -```shell -# running core-cli as a task requires all options to be provided -# within a string -inv cli "query session -i 1" - -# entering the poetry shell to use core-cli natively -cd $REPO/daemon -poetry shell -core-cli query session -i 1 - -# exit the shell -exit -``` diff --git a/install.sh b/install.sh index 6cc193ed6..5e5a9b117 100755 --- a/install.sh +++ b/install.sh @@ -3,57 +3,6 @@ # exit on error set -e -ubuntu_py=3.6 -centos_py=36 -reinstall= - -function install_python_depencencies() { - sudo python3 -m pip install -r daemon/requirements.txt -} - -function install_grpcio_tools() { - python3 -m pip install --only-binary ":all:" --user grpcio-tools -} - -function install_ospf_mdr() { - rm -rf /tmp/ospf-mdr - git clone https://github.com/USNavalResearchLaboratory/ospf-mdr /tmp/ospf-mdr - cd /tmp/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 -j8 - sudo make install - cd - -} - -function build_core() { - ./bootstrap.sh - ./configure $1 - make -j8 -} - -function install_core() { - sudo make install -} - -function uninstall_core() { - sudo make uninstall - make clean - ./bootstrap.sh clean -} - -function install_dev_core() { - cd gui - sudo make install - cd - - cd netns - sudo make install - cd - - cd daemon -} - # detect os/ver for install type os="" if [[ -f /etc/os-release ]]; then @@ -62,100 +11,47 @@ if [[ -f /etc/os-release ]]; then fi # parse arguments -while getopts "drv:" opt; do +dev="" +verbose="" +prefix="" +while getopts "dvp:" opt; do case ${opt} in d) - dev=1 + dev="-d" ;; v) - ubuntu_py=${OPTARG} - centos_py=${OPTARG} + verbose="-v" ;; - r) - reinstall=1 + p) + prefix="-p ${OPTARG}" ;; \?) - echo "script usage: $(basename $0) [-d] [-r] [-v python version]" >&2 + echo "script usage: $(basename $0) [-d] [-v]" >&2 + echo "" >&2 + echo "-v enable verbose install" >&2 + echo "-d enable developer install" >&2 + echo "-p install prefix, defaults to /usr/local" >&2 exit 1 ;; esac done shift $((OPTIND - 1)) -# check if we are reinstalling or installing -if [ -z "${reinstall}" ]; then - echo "installing CORE for ${os}" - case ${os} in - "ubuntu") - echo "installing core system dependencies" - sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ - python${ubuntu_py} python${ubuntu_py}-dev python3-pip python3-tk tk libtk-img ethtool autoconf - install_grpcio_tools - echo "installing ospf-mdr system dependencies" - sudo apt install -y libtool gawk libreadline-dev - install_ospf_mdr - if [[ -z ${dev} ]]; then - echo "normal install" - install_python_depencencies - build_core - install_core - else - echo "dev install" - python3 -m pip install pipenv - build_core - install_dev_core - python3 -m pipenv sync --dev - python3 -m pipenv run pre-commit install - fi - ;; - "centos") - echo "installing core system dependencies" - sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel iptables-ebtables iproute \ - python${centos_py} python${centos_py}-devel python3-pip python3-tkinter tk ethtool autoconf - install_grpcio_tools - echo "installing ospf-mdr system dependencies" - sudo yum install -y libtool gawk readline-devel - install_ospf_mdr - if [[ -z ${dev} ]]; then - echo "normal install" - install_python_depencencies - build_core --prefix=/usr - install_core - else - echo "dev install" - sudo python3 -m pip install pipenv - build_core --prefix=/usr - install_dev_core - sudo python3 -m pipenv sync --dev - python3 -m pipenv sync --dev - python3 -m pipenv run pre-commit install - fi - ;; - *) - echo "unknown OS ID ${os} cannot install" - ;; - esac -else - branch=$(git symbolic-ref --short HEAD) - echo "reinstalling CORE on ${os} with latest ${branch}" - echo "uninstalling CORE" - uninstall_core - echo "pulling latest code" - git pull - echo "installing python dependencies" - install_python_depencencies - echo "building CORE" - case ${os} in - "ubuntu") - build_core - ;; - "centos") - build_core --prefix=/usr - ;; - *) - echo "unknown OS ID ${os} cannot reinstall" - ;; - esac - echo "installing CORE" - install_core -fi +echo "installing CORE for ${os}" +case ${os} in +"ubuntu") + sudo apt install -y python3-pip python3-venv + ;; +"centos") + sudo yum install -y python3-pip + ;; +*) + echo "unknown OS ID ${os} cannot install" + ;; +esac + +python3 -m pip install --user pipx +python3 -m pipx ensurepath +export PATH=$PATH:~/.local/bin +pipx install invoke +inv install ${dev} ${verbose} ${prefix} diff --git a/install2.sh b/install2.sh deleted file mode 100755 index 5e5a9b117..000000000 --- a/install2.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -# exit on error -set -e - -# detect os/ver for install type -os="" -if [[ -f /etc/os-release ]]; then - . /etc/os-release - os=${ID} -fi - -# parse arguments -dev="" -verbose="" -prefix="" -while getopts "dvp:" opt; do - case ${opt} in - d) - dev="-d" - ;; - v) - verbose="-v" - ;; - p) - prefix="-p ${OPTARG}" - ;; - \?) - echo "script usage: $(basename $0) [-d] [-v]" >&2 - echo "" >&2 - echo "-v enable verbose install" >&2 - echo "-d enable developer install" >&2 - echo "-p install prefix, defaults to /usr/local" >&2 - exit 1 - ;; - esac -done -shift $((OPTIND - 1)) - -echo "installing CORE for ${os}" -case ${os} in -"ubuntu") - sudo apt install -y python3-pip python3-venv - ;; -"centos") - sudo yum install -y python3-pip - ;; -*) - echo "unknown OS ID ${os} cannot install" - ;; -esac - -python3 -m pip install --user pipx -python3 -m pipx ensurepath -export PATH=$PATH:~/.local/bin -pipx install invoke -inv install ${dev} ${verbose} ${prefix} From f00d4aef0b527886b494444b559a8d04f49744d0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 11:02:00 -0700 Subject: [PATCH 169/210] update install doc to note centos 8 and netem --- docs/install.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/install.md b/docs/install.md index 42716fd3a..2bf1a3b10 100644 --- a/docs/install.md +++ b/docs/install.md @@ -36,6 +36,16 @@ Verified: > **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not > function properly +> **NOTE:** CentOS 8 does not have the netem kernel mod available by default + +CentOS 8 Enabled netem: +```shell +sudo yum update +# restart into updated kernel +sudo yum install -y kernel-modules-extra +sudo modprobe sch_netem +``` + ## Utility Requirements * iproute2 4.5+ is a requirement for bridge related commands From 05830c68304a038920309fb2d46d61327659b4b3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 11:25:40 -0700 Subject: [PATCH 170/210] removed fpm packaging, as it will not be used anymore, beyond distributed packages --- Makefile.am | 58 ----------------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/Makefile.am b/Makefile.am index 4db054086..fbdf573eb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,58 +44,6 @@ DISTCLEANFILES = aclocal.m4 \ MAINTAINERCLEANFILES = .version \ .version.date -define fpm-rpm = -fpm -s dir -t rpm -n core \ - -m "$(PACKAGE_MAINTAINERS)" \ - --license "BSD" \ - --description "Common Open Research Emulator" \ - --url https://github.com/coreemu/core \ - --vendor "$(PACKAGE_VENDOR)" \ - -p core_VERSION_ARCH.rpm \ - -v $(PACKAGE_VERSION) \ - --rpm-init scripts/core-daemon \ - --config-files "/etc/core" \ - -d "ethtool" \ - -d "tcl" \ - -d "tk" \ - -d "procps-ng" \ - -d "bash >= 3.0" \ - -d "ebtables" \ - -d "iproute" \ - -d "libev" \ - -d "net-tools" \ - -d "python3 >= 3.6" \ - -d "python3-tkinter" \ - -C $(DESTDIR) -endef - -define fpm-deb = -fpm -s dir -t deb -n core \ - -m "$(PACKAGE_MAINTAINERS)" \ - --license "BSD" \ - --description "Common Open Research Emulator" \ - --url https://github.com/coreemu/core \ - --vendor "$(PACKAGE_VENDOR)" \ - -p core_VERSION_ARCH.deb \ - -v $(PACKAGE_VERSION) \ - --deb-systemd scripts/core-daemon.service \ - --deb-no-default-config-files \ - --config-files "/etc/core" \ - -d "ethtool" \ - -d "tcl" \ - -d "tk" \ - -d "libtk-img" \ - -d "procps" \ - -d "libc6 >= 2.14" \ - -d "bash >= 3.0" \ - -d "ebtables" \ - -d "iproute2" \ - -d "libev4" \ - -d "python3 >= 3.6" \ - -d "python3-tk" \ - -C $(DESTDIR) -endef - define fpm-distributed-deb = fpm -s dir -t deb -n core-distributed \ -m "$(PACKAGE_MAINTAINERS)" \ @@ -138,12 +86,6 @@ fpm -s dir -t rpm -n core-distributed \ -C $(DESTDIR) endef -.PHONY: fpm -fpm: clean-local-fpm - $(MAKE) install DESTDIR=$(DESTDIR) - $(call fpm-deb) - $(call fpm-rpm) - .PHONY: fpm-distributed fpm-distributed: clean-local-fpm $(MAKE) -C netns install DESTDIR=$(DESTDIR) From 50f331d93ef9b80e7fa031830b9797ffb2c39851 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 11:40:20 -0700 Subject: [PATCH 171/210] removed references to building and dealing with service files, as that will now be limited to the invoke task --- Makefile.am | 4 +- configure.ac | 14 ----- scripts/.gitignore | 2 - scripts/Makefile.am | 31 --------- scripts/core-daemon.in | 112 --------------------------------- scripts/core-daemon.service.in | 11 ---- 6 files changed, 1 insertion(+), 173 deletions(-) delete mode 100644 scripts/.gitignore delete mode 100644 scripts/Makefile.am delete mode 100644 scripts/core-daemon.in delete mode 100644 scripts/core-daemon.service.in diff --git a/Makefile.am b/Makefile.am index fbdf573eb..20191438f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,7 +11,7 @@ if WANT_GUI endif if WANT_DAEMON - DAEMON = scripts daemon + DAEMON = daemon endif if WANT_NETNS @@ -124,8 +124,6 @@ all: change-files .PHONY: change-files change-files: $(call change-files,gui/core-gui) - $(call change-files,scripts/core-daemon.service) - $(call change-files,scripts/core-daemon) $(call change-files,daemon/core/constants.py) $(call change-files,netns/setup.py) $(call change-files,daemon/setup.py) diff --git a/configure.ac b/configure.ac index 021027608..10d30c20e 100644 --- a/configure.ac +++ b/configure.ac @@ -208,22 +208,12 @@ if [test "x$want_python" = "xyes" && test "x$enable_docs" = "xyes"] ; then AS_IF([$PYTHON -c "import sphinx_rtd_theme" &> /dev/null], [], [AC_MSG_ERROR([doc dependency missing, please install python3 -m pip install sphinx-rtd-theme])]) fi -AC_ARG_WITH([startup], - [AS_HELP_STRING([--with-startup=option], - [option=systemd,suse,none to install systemd/SUSE init scripts])], - [with_startup=$with_startup], - [with_startup=initd]) -AC_SUBST(with_startup) -AC_MSG_RESULT([using startup option $with_startup]) - # Variable substitutions AM_CONDITIONAL(WANT_GUI, test x$enable_gui = xyes) AM_CONDITIONAL(WANT_DAEMON, test x$enable_daemon = xyes) AM_CONDITIONAL(WANT_DOCS, test x$want_docs = xyes) AM_CONDITIONAL(WANT_PYTHON, test x$want_python = xyes) AM_CONDITIONAL(WANT_NETNS, test x$want_linux_netns = xyes) -AM_CONDITIONAL(WANT_INITD, test x$with_startup = xinitd) -AM_CONDITIONAL(WANT_SYSTEMD, test x$with_startup = xsystemd) AM_CONDITIONAL(WANT_VNODEDONLY, test x$enable_vnodedonly = xyes) if test $cross_compiling = no; then @@ -237,7 +227,6 @@ AC_CONFIG_FILES([Makefile gui/version.tcl gui/Makefile gui/icons/Makefile - scripts/Makefile man/Makefile docs/Makefile daemon/Makefile @@ -267,9 +256,6 @@ Daemon: Daemon path: ${bindir} Daemon config: ${CORE_CONF_DIR} Python: ${PYTHON} - Logs: ${CORE_STATE_DIR}/log - -Startup: ${with_startup} Features to build: Build GUI: ${enable_gui} diff --git a/scripts/.gitignore b/scripts/.gitignore deleted file mode 100644 index 86129f95b..000000000 --- a/scripts/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -core-daemon -core-daemon.service diff --git a/scripts/Makefile.am b/scripts/Makefile.am deleted file mode 100644 index abdef40da..000000000 --- a/scripts/Makefile.am +++ /dev/null @@ -1,31 +0,0 @@ -# CORE -# (c)2011-2013 the Boeing Company. -# See the LICENSE file included in this distribution. -# -# author: Jeff Ahrenholz -# -# Makefile for installing scripts. -# - -CLEANFILES = core-daemon - -DISTCLEANFILES = Makefile.in core-daemon.service core-daemon - -EXTRA_DIST = core-daemon.in core-daemon.service.in - -SUBDIRS = - -# install startup scripts based on --with-startup=option configure option -# init.d (default), systemd -if WANT_INITD -startupdir = /etc/init.d -startup_SCRIPTS = core-daemon -endif -if WANT_SYSTEMD -startupdir = /usr/lib/systemd/system -startup_SCRIPTS = core-daemon.service -endif - -# remove extra scripts and their directories if they are empty -uninstall-hook: - rmdir -p $(startupdir) || true diff --git a/scripts/core-daemon.in b/scripts/core-daemon.in deleted file mode 100644 index 0a988f0f2..000000000 --- a/scripts/core-daemon.in +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/sh -### BEGIN INIT INFO -# Provides: core-daemon -# Required-Start: $network $remote_fs -# Required-Stop: $network $remote_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start the core-daemon CORE daemon at boot time -# Description: Starts and stops the core-daemon CORE daemon used to -# provide network emulation services for the CORE GUI -# or scripts. -### END INIT INFO -# -# chkconfig: 35 90 03 -# description: Starts and stops the CORE daemon \ -# used to provide network emulation services. -# -# config: /etc/core/ - -NAME=`basename $0` -PIDFILE="@CORE_STATE_DIR@/run/$NAME.pid" -LOG="@CORE_STATE_DIR@/log/$NAME.log" -CMD="@bindir@/$NAME" - -get_pid() { - cat "$PIDFILE" -} - -is_alive() { - [ -f "$PIDFILE" ] && ps -p `get_pid` > /dev/null 2>&1 -} - -corestart() { - if is_alive; then - echo "$NAME already started" - else - echo "starting $NAME" - $CMD 2>&1 >> "$LOG" & - fi - - echo $! > "$PIDFILE" - if ! is_alive; then - echo "unable to start $NAME, see $LOG" - exit 1 - fi -} - -corestop() { - if is_alive; then - echo -n "stopping $NAME.." - kill `get_pid` - for i in 1 2 3 4 5; do - sleep 1 - if ! is_alive; then - break - fi - echo -n "." - done - echo - - if is_alive; then - echo "not stopped; may still be shutting down" - exit 1 - else - echo "stopped" - if [ -f "$PIDFILE" ]; then - rm -f "$PIDFILE" - fi - fi - else - echo "$NAME not running" - fi -} - -corerestart() { - corestop - corestart -} - -corestatus() { - if is_alive; then - echo "$NAME is running" - else - echo "$NAME is stopped" - exit 1 - fi -} - - -case "$1" in - start) - corestart - ;; - stop) - corestop - ;; - restart) - corerestart - ;; - force-reload) - corerestart - ;; - status) - corestatus - ;; - *) - echo "Usage: $0 {start|stop|restart|status}" - exit 1 -esac - -exit $? - diff --git a/scripts/core-daemon.service.in b/scripts/core-daemon.service.in deleted file mode 100644 index cd53cfad2..000000000 --- a/scripts/core-daemon.service.in +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Common Open Research Emulator Service -After=network.target - -[Service] -Type=simple -ExecStart=@bindir@/core-daemon -TasksMax=infinity - -[Install] -WantedBy=multi-user.target From df01f0444444d51f936c482d802e52a95513e397 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 12:08:05 -0700 Subject: [PATCH 172/210] removed python buid/installation from makefiles, poetry will handle --- .gitignore | 4 +--- daemon/.gitignore | 2 -- daemon/Makefile.am | 38 ++++---------------------------------- 3 files changed, 5 insertions(+), 39 deletions(-) delete mode 100644 daemon/.gitignore diff --git a/.gitignore b/.gitignore index bcfbadebc..2012df9d0 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ coverage.xml # python files *.egg-info +*.pyc # ignore package files *.rpm @@ -55,8 +56,5 @@ coverage.xml netns/setup.py daemon/setup.py -# ignore corefx build -corefx/target - # python __pycache__ diff --git a/daemon/.gitignore b/daemon/.gitignore deleted file mode 100644 index 27ffc2f17..000000000 --- a/daemon/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -build diff --git a/daemon/Makefile.am b/daemon/Makefile.am index a56636549..1cf4d2331 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -7,43 +7,12 @@ # Makefile for building netns components. # -SETUPPY = setup.py -SETUPPYFLAGS = -v - if WANT_DOCS DOCS = doc endif SUBDIRS = proto $(DOCS) -SCRIPT_FILES := $(notdir $(wildcard scripts/*)) -MAN_FILES := $(notdir $(wildcard ../man/*.1)) - -# Python package build -noinst_SCRIPTS = build -build: - $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) build - -# Python package install -install-exec-hook: - $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) install \ - --root=/$(DESTDIR) \ - --prefix=$(prefix) \ - --single-version-externally-managed - -# Python package uninstall -uninstall-hook: - rm -rf $(DESTDIR)/etc/core - rm -rf $(DESTDIR)/$(datadir)/core - rm -f $(addprefix $(DESTDIR)/$(datarootdir)/man/man1/, $(MAN_FILES)) - rm -f $(addprefix $(DESTDIR)/$(bindir)/,$(SCRIPT_FILES)) - rm -rf $(DESTDIR)/$(pythondir)/core-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info - rm -rf $(DESTDIR)/$(pythondir)/core - -# Python package cleanup -clean-local: - -rm -rf build - # because we include entire directories with EXTRA_DIST, we need to clean up # the source control files dist-hook: @@ -52,17 +21,18 @@ dist-hook: distclean-local: -rm -rf core.egg-info - DISTCLEANFILES = Makefile.in # files to include with distribution tarball -EXTRA_DIST = $(SETUPPY) \ +EXTRA_DIST = setup.py \ core \ data \ doc/conf.py.in \ examples \ scripts \ tests \ - test.py \ setup.cfg \ + MANIFEST.in \ + poetry.lock \ + pyproject.toml \ requirements.txt From 7b3f934e954691416b7c67700bb318eedc66c40f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 12:13:41 -0700 Subject: [PATCH 173/210] updated pyproject.toml to align with setup.py as is --- daemon/pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 165fb34cf..d36b341c6 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,8 +1,11 @@ [tool.poetry] name = "core" version = "6.6.0" -description = "" -authors = [] +description = "CORE Common Open Research Emulator" +authors = ["Boeing Research & Technology"] +license = "BSD-2-Clause" +repository = "https://github.com/coreemu/core" +documentation = "https://coreemu.github.io/core/" [tool.poetry.dependencies] python = "^3.6" From 8c50d08121a01b30bd0b7aeeb8d02cfa146b6a3c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 12:33:13 -0700 Subject: [PATCH 174/210] removed setup.py and requirements.txt as poetry will be where this information will live --- Makefile.am | 1 - daemon/Makefile.am | 6 ++--- daemon/pyproject.toml | 2 +- daemon/requirements.txt | 19 ------------- daemon/setup.py.in | 60 ----------------------------------------- 5 files changed, 3 insertions(+), 85 deletions(-) delete mode 100644 daemon/requirements.txt delete mode 100644 daemon/setup.py.in diff --git a/Makefile.am b/Makefile.am index 20191438f..7a3799fc0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -126,7 +126,6 @@ change-files: $(call change-files,gui/core-gui) $(call change-files,daemon/core/constants.py) $(call change-files,netns/setup.py) - $(call change-files,daemon/setup.py) CORE_DOC_SRC = core-python-$(PACKAGE_VERSION) .PHONY: doc diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 1cf4d2331..04f48a920 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -24,8 +24,7 @@ distclean-local: DISTCLEANFILES = Makefile.in # files to include with distribution tarball -EXTRA_DIST = setup.py \ - core \ +EXTRA_DIST = core \ data \ doc/conf.py.in \ examples \ @@ -34,5 +33,4 @@ EXTRA_DIST = setup.py \ setup.cfg \ MANIFEST.in \ poetry.lock \ - pyproject.toml \ - requirements.txt + pyproject.toml diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index d36b341c6..da22690b0 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -2,7 +2,7 @@ name = "core" version = "6.6.0" description = "CORE Common Open Research Emulator" -authors = ["Boeing Research & Technology"] +authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" repository = "https://github.com/coreemu/core" documentation = "https://coreemu.github.io/core/" diff --git a/daemon/requirements.txt b/daemon/requirements.txt deleted file mode 100644 index 19d155e59..000000000 --- a/daemon/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -bcrypt==3.1.7 -cffi==1.14.0 -cryptography==2.8 -dataclasses==0.7; python_version == "3.6" -fabric==2.5.0 -grpcio==1.27.2 -invoke==1.4.1 -lxml==4.5.0 -Mako==1.1.1 -MarkupSafe==1.1.1 -netaddr==0.7.19 -paramiko==2.7.1 -Pillow==7.0.0 -protobuf==3.11.3 -pycparser==2.19 -PyNaCl==1.3.0 -pyproj==2.5.0 -PyYAML==5.3 -six==1.14.0 diff --git a/daemon/setup.py.in b/daemon/setup.py.in deleted file mode 100644 index e8c99e679..000000000 --- a/daemon/setup.py.in +++ /dev/null @@ -1,60 +0,0 @@ -""" -Defines how CORE will be built for installation. -""" - -import glob -import os - -from setuptools import find_packages, setup - -_CORE_DIR = "/etc/core" -_MAN_DIR = "share/man/man1" -_EXAMPLES_DIR = "share/core" - - -def recursive_files(data_path, files_path): - all_files = [] - for path, _directories, filenames in os.walk(files_path): - directory = os.path.join(data_path, path) - files = [] - for filename in filenames: - files.append(os.path.join(path, filename)) - all_files.append((directory, files)) - return all_files - - -data_files = [ - (_CORE_DIR, glob.glob("data/*")), - (_MAN_DIR, glob.glob("../man/**.1")), -] -data_files.extend(recursive_files(_EXAMPLES_DIR, "examples")) - -setup( - name="core", - version="@PACKAGE_VERSION@", - packages=find_packages(), - install_requires=[ - 'dataclasses;python_version=="3.6"', - "fabric", - "grpcio", - "invoke", - "lxml", - "mako", - "netaddr", - "pillow", - "protobuf", - "pyproj", - "pyyaml", - ], - tests_require=[ - "pytest", - ], - data_files=data_files, - scripts=glob.glob("scripts/*"), - include_package_data=True, - description="Python components of CORE", - url="https://github.com/coreemu/core", - author="Boeing Research & Technology", - license="BSD", - long_description="Python scripts and modules for building virtual emulated networks.", -) From 0cd3f6115dfb4eca5b262fecfd84b011f2e88edb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 12:37:29 -0700 Subject: [PATCH 175/210] remove setup.py reference from github action --- .github/workflows/daemon-checks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml index 9e9f7aa71..6cb121249 100644 --- a/.github/workflows/daemon-checks.yml +++ b/.github/workflows/daemon-checks.yml @@ -16,7 +16,6 @@ jobs: python -m pip install --upgrade pip pip install poetry cd daemon - cp setup.py.in setup.py cp core/constants.py.in core/constants.py sed -i 's/required=True/required=False/g' core/emulator/coreemu.py poetry install From 873fc0e4683eb23a60a163f8bf0eff5a2f066182 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 12:49:40 -0700 Subject: [PATCH 176/210] removed daemon MANIFEST.in, poetry will provide --- daemon/MANIFEST.in | 2 -- daemon/Makefile.am | 1 - daemon/pyproject.toml | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 daemon/MANIFEST.in diff --git a/daemon/MANIFEST.in b/daemon/MANIFEST.in deleted file mode 100644 index c46dc828f..000000000 --- a/daemon/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -graft core/gui/data -graft core/configservices/*/templates diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 04f48a920..7528dc01a 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -31,6 +31,5 @@ EXTRA_DIST = core \ scripts \ tests \ setup.cfg \ - MANIFEST.in \ poetry.lock \ pyproject.toml diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index da22690b0..d9f204b87 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -6,6 +6,7 @@ authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" repository = "https://github.com/coreemu/core" documentation = "https://coreemu.github.io/core/" +include = ["core/gui/data/**/*", "core/configservices/*/templates"] [tool.poetry.dependencies] python = "^3.6" From fdd2e6f1f11f8a55f4e4c7804864b03a3114156f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 12:54:13 -0700 Subject: [PATCH 177/210] removed references for excluding utm.py as it is no longer present --- .github/workflows/daemon-checks.yml | 2 +- daemon/.pre-commit-config.yaml | 2 +- daemon/setup.cfg | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml index 6cb121249..5ea8d1c2b 100644 --- a/.github/workflows/daemon-checks.yml +++ b/.github/workflows/daemon-checks.yml @@ -26,7 +26,7 @@ jobs: - name: black run: | cd daemon - poetry run black --check --exclude ".+_pb2.*.py|doc|build|utm\.py|setup\.py" . + poetry run black --check --exclude ".+_pb2.*.py|doc|build" . - name: flake8 run: | cd daemon diff --git a/daemon/.pre-commit-config.yaml b/daemon/.pre-commit-config.yaml index 13a6955ba..fe810f04a 100644 --- a/daemon/.pre-commit-config.yaml +++ b/daemon/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: name: black stages: [commit] language: system - entry: bash -c 'cd daemon && poetry run black --exclude ".+_pb2.*.py|doc|build|utm\.py" .' + entry: bash -c 'cd daemon && poetry run black --exclude ".+_pb2.*.py|doc|build" .' types: [python] - id: flake8 diff --git a/daemon/setup.cfg b/daemon/setup.cfg index a3084b8be..f2c2a3aa1 100644 --- a/daemon/setup.cfg +++ b/daemon/setup.cfg @@ -2,7 +2,7 @@ test=pytest [isort] -skip_glob=*_pb2*.py,utm.py,doc,build +skip_glob=*_pb2*.py,doc,build multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 @@ -14,7 +14,7 @@ ignore=E501,W503,E203 max-line-length=88 max-complexity=26 select=B,C,E,F,W,T4 -exclude=*_pb2*.py,utm.py,doc,build +exclude=*_pb2*.py,doc,build [tool:pytest] norecursedirs=distributed emane From f8b0ab6ec3ed88359c4f3384335f8e0337541022 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 13:24:16 -0700 Subject: [PATCH 178/210] moved isort config from setup.cfg to pyproject.toml --- daemon/pyproject.toml | 8 ++++++++ daemon/setup.cfg | 11 ----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index d9f204b87..f7a9874ae 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -31,6 +31,14 @@ mock = "*" pre-commit = "*" pytest = "*" +[tool.isort] +skip_glob = "*_pb2*.py,doc,build" +multi_line_output = 3 +include_trailing_comma = "True" +force_grid_wrap = 0 +use_parentheses = "True" +line_length = 88 + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/daemon/setup.cfg b/daemon/setup.cfg index f2c2a3aa1..89c968b90 100644 --- a/daemon/setup.cfg +++ b/daemon/setup.cfg @@ -1,14 +1,3 @@ -[aliases] -test=pytest - -[isort] -skip_glob=*_pb2*.py,doc,build -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 - [flake8] ignore=E501,W503,E203 max-line-length=88 From 80194b3e38bc75a3e9de61264db6fc4a5787f354 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 13:33:40 -0700 Subject: [PATCH 179/210] moved python black configuration to pyproject.toml and fixed bad exclude --- .github/workflows/daemon-checks.yml | 2 +- daemon/.pre-commit-config.yaml | 2 +- daemon/core/nodes/docker.py | 6 ++---- daemon/pyproject.toml | 5 +++++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml index 5ea8d1c2b..52440467d 100644 --- a/.github/workflows/daemon-checks.yml +++ b/.github/workflows/daemon-checks.yml @@ -26,7 +26,7 @@ jobs: - name: black run: | cd daemon - poetry run black --check --exclude ".+_pb2.*.py|doc|build" . + poetry run black --check . - name: flake8 run: | cd daemon diff --git a/daemon/.pre-commit-config.yaml b/daemon/.pre-commit-config.yaml index fe810f04a..bc9ead080 100644 --- a/daemon/.pre-commit-config.yaml +++ b/daemon/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: name: black stages: [commit] language: system - entry: bash -c 'cd daemon && poetry run black --exclude ".+_pb2.*.py|doc|build" .' + entry: bash -c 'cd daemon && poetry run black .' types: [python] - id: flake8 diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 1ef814ee3..ce34bd986 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -78,7 +78,7 @@ def __init__( name: str = None, nodedir: str = None, server: DistributedServer = None, - image: str = None + image: str = None, ) -> None: """ Create a DockerNode instance. @@ -209,9 +209,7 @@ def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: if self.server is not None: self.host_cmd(f"rm -f {temp.name}") os.unlink(temp.name) - logging.debug( - "node(%s) added file: %s; mode: 0%o", self.name, filename, mode - ) + logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode) def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: """ diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index f7a9874ae..3e37e4f90 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -39,6 +39,11 @@ force_grid_wrap = 0 use_parentheses = "True" line_length = 88 +[tool.black] +line_length = 88 +exclude = ".+_pb2.*.py|doc/|build/|__pycache__/" + + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" From be2f7e1cae302ad608c55433b66308cf9f053d5d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 13:42:59 -0700 Subject: [PATCH 180/210] simplified invoke install/uninstall task, since daemon no longer formally installs --- daemon/pyproject.toml | 1 - tasks.py | 16 +++------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 3e37e4f90..1fdc9d1a1 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -43,7 +43,6 @@ line_length = 88 line_length = 88 exclude = ".+_pb2.*.py|doc/|build/|__pycache__/" - [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/tasks.py b/tasks.py index 9da66faac..6ed956c2e 100644 --- a/tasks.py +++ b/tasks.py @@ -8,8 +8,6 @@ from invoke import task, Context DAEMON_DIR: str = "daemon" -VCMD_DIR: str = "netns" -GUI_DIR: str = "gui" DEFAULT_PREFIX: str = "/usr/local" @@ -121,11 +119,7 @@ def build(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: def install_core(c: Context, hide: bool) -> None: print("installing core vcmd...") - with c.cd(VCMD_DIR): - c.run("sudo make install", hide=hide) - print("installing core gui...") - with c.cd(GUI_DIR): - c.run("sudo make install", hide=hide) + c.run("sudo make install", hide=hide) def install_poetry(c: Context, dev: bool, hide: bool) -> None: @@ -265,12 +259,8 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): uninstall core """ hide = not verbose - print("uninstalling core-gui") - with c.cd(GUI_DIR): - c.run("sudo make uninstall", hide=hide) - print("uninstalling vcmd") - with c.cd(VCMD_DIR): - c.run("sudo make uninstall", hide=hide) + print("uninstalling core") + c.run("sudo make uninstall", hide=hide) print("cleaning build directory") c.run("make clean", hide=hide) c.run("./bootstrap.sh clean", hide=hide) From 1cadf8362fbec42379938449c3e21a9061cf0ddf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 22:09:00 -0700 Subject: [PATCH 181/210] added a text spinner while installing/uninstalling --- tasks.py | 177 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 109 insertions(+), 68 deletions(-) diff --git a/tasks.py b/tasks.py index 6ed956c2e..0b727f58a 100644 --- a/tasks.py +++ b/tasks.py @@ -1,9 +1,14 @@ import inspect +import itertools import os import sys +import threading +import time +from contextlib import contextmanager from enum import Enum from pathlib import Path from tempfile import NamedTemporaryFile +from typing import Optional from invoke import task, Context @@ -11,6 +16,40 @@ DEFAULT_PREFIX: str = "/usr/local" +class Progress: + cycles = itertools.cycle(["-", "/", "|", "\\"]) + + def __init__(self, verbose: bool) -> None: + self.verbose: bool = verbose + self.thread: Optional[threading.Thread] = None + self.running: bool = False + + @contextmanager + def start(self, message: str) -> None: + if not self.verbose: + print(f"{message} ... ", end="") + self.running = True + self.thread = threading.Thread(target=self.run, daemon=True) + self.thread.start() + yield + self.stop() + + def run(self) -> None: + while self.running: + sys.stdout.write(next(self.cycles)) + sys.stdout.flush() + sys.stdout.write("\b") + time.sleep(0.1) + + def stop(self) -> None: + if not self.verbose: + print("done") + if self.thread: + self.running = False + self.thread.join() + self.thread = None + + class OsName(Enum): UBUNTU = "ubuntu" CENTOS = "centos" @@ -52,7 +91,7 @@ def get_os() -> OsInfo: if not line: continue key, value = line.split("=") - d[key] = value.strip('"') + d[key] = value.strip("\"") name_value = d["ID"] like_value = d["ID_LIKE"] version_value = d["VERSION_ID"] @@ -69,27 +108,28 @@ def get_os() -> OsInfo: def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: - print("installing system dependencies...") if os_info.like == OsLike.DEBIAN: c.run( - "sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 " - "ethtool tk python3-tk", + "sudo apt install -y automake pkg-config gcc libev-dev ebtables " + "iproute2 ethtool tk python3-tk", hide=hide ) elif os_info.like == OsLike.REDHAT: c.run( - "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel " - "iptables-ebtables iproute python3-devel python3-tkinter tk ethtool make", + "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ " + "libev-devel iptables-ebtables iproute python3-devel python3-tkinter " + "tk ethtool make", hide=hide ) # centos 8+ does not support netem by default if os_info.name == OsName.CENTOS and os_info.version >= 8: c.run("sudo yum install -y kernel-modules-extra", hide=hide) if not c.run("sudo modprobe sch_netem", warn=True, hide=hide): - print("ERROR: you need to install the latest kernel") + print("\nERROR: you need to install the latest kernel") print("run the following, restart, and try again") print("sudo yum update") sys.exit(1) + # attempt to setup legacy ebtables when an nftables based version is found r = c.run("ebtables -V", hide=hide) if "nf_tables" in r.stdout: @@ -99,35 +139,31 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: hide=hide ): print( - "WARNING: unable to setup required ebtables-legacy, WLAN will not work" + "\nWARNING: unable to setup ebtables-legacy, WLAN will not work" ) def install_grpcio(c: Context, hide: bool) -> None: - print("installing grpcio-tools...") c.run( - "python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2", hide=hide + "python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2", + hide=hide, ) -def build(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: - print("building core...") +def build_core(c: Context, hide: bool, prefix: str = DEFAULT_PREFIX) -> None: c.run("./bootstrap.sh", hide=hide) c.run(f"./configure --prefix={prefix}", hide=hide) c.run("make -j$(nproc)", hide=hide) def install_core(c: Context, hide: bool) -> None: - print("installing core vcmd...") c.run("sudo make install", hide=hide) def install_poetry(c: Context, dev: bool, hide: bool) -> None: - print("installing poetry...") c.run("pipx install poetry", hide=hide) args = "" if dev else "--no-dev" with c.cd(DAEMON_DIR): - print("installing core environment using poetry...") c.run(f"poetry install {args}", hide=hide) if dev: c.run("poetry run pre-commit install", hide=hide) @@ -135,31 +171,29 @@ def install_poetry(c: Context, dev: bool, hide: bool) -> None: def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: if c.run("which zebra", warn=True, hide=hide): - print("quagga already installed, skipping ospf mdr") + print("\nquagga already installed, skipping ospf mdr") return - print("installing ospf mdr dependencies...") - if os_info.like == OsLike.DEBIAN: - c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide) - elif os_info.like == OsLike.REDHAT: - c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide) - print("cloning ospf mdr...") - clone_dir = "/tmp/ospf-mdr" - c.run( - f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", - hide=hide - ) - with c.cd(clone_dir): - print("building ospf mdr...") - c.run("./bootstrap.sh", hide=hide) + p = Progress(not hide) + with p.start("installing ospf mdr dependencies"): + if os_info.like == OsLike.DEBIAN: + c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide) + elif os_info.like == OsLike.REDHAT: + c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide) + clone_dir = "/tmp/ospf-mdr" c.run( - "./configure --disable-doc --enable-user=root --enable-group=root " - "--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh " - "--localstatedir=/var/run/quagga", + f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", hide=hide ) - c.run("make -j$(nproc)", hide=hide) - print("installing ospf mdr...") - c.run("sudo make install", hide=hide) + with c.cd(clone_dir): + c.run("./bootstrap.sh", hide=hide) + c.run( + "./configure --disable-doc --enable-user=root --enable-group=root " + "--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh " + "--localstatedir=/var/run/quagga", + hide=hide + ) + c.run("make -j$(nproc)", hide=hide) + c.run("sudo make install", hide=hide) @task @@ -172,7 +206,6 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): systemd_dir = Path("/lib/systemd/system/") service_file = systemd_dir.joinpath("core-daemon.service") if systemd_dir.exists(): - print(f"installing core-daemon.service for systemd to {service_file}") service_data = inspect.cleandoc(f""" [Unit] Description=Common Open Research Emulator Service @@ -204,7 +237,6 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX): bin_dir = Path(prefix).joinpath("bin") for script in Path("daemon/scripts").iterdir(): dest = bin_dir.joinpath(script.name) - print(f"installing {script} to {dest}") with open(script, "r") as f: lines = f.readlines() first = lines[0].strip() @@ -224,7 +256,6 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX): # install core configuration file config_dir = "/etc/core" - print(f"installing core configuration files under {config_dir}") c.run(f"sudo mkdir -p {config_dir}", hide=hide) c.run(f"sudo cp -n daemon/data/core.conf {config_dir}", hide=hide) c.run(f"sudo cp -n daemon/data/logging.conf {config_dir}", hide=hide) @@ -235,22 +266,28 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ install core, poetry, scripts, service, and ospf mdr """ + c.run("sudo -v", hide=True) print(f"installing core with prefix: {prefix}") + p = Progress(verbose) hide = not verbose os_info = get_os() - install_system(c, os_info, hide) - install_grpcio(c, hide) - build(c, hide, prefix) - install_core(c, hide) - install_poetry(c, dev, hide) - install_scripts(c, hide, prefix) - install_service(c, hide, prefix) - install_ospf_mdr(c, os_info, hide) - print("please open a new terminal or re-login to leverage invoke for running core") - print("# run daemon") - print("inv daemon") - print("# run gui") - print("inv gui") + with p.start("installing system dependencies"): + install_system(c, os_info, hide) + with p.start("installing system grpcio-tools"): + install_grpcio(c, hide) + with p.start("building core"): + build_core(c, hide, prefix) + with p.start("installing vcmd/gui"): + install_core(c, hide) + with p.start("installing poetry virtual environment"): + install_poetry(c, dev, hide) + with p.start("installing scripts and /etc/core"): + install_scripts(c, hide, prefix) + with p.start("installing systemd service"): + install_service(c, hide, prefix) + with p.start("installing ospf mdr"): + install_ospf_mdr(c, os_info, hide) + print("\nyou may need to open a new terminal to leverage invoke for running core") @task @@ -259,35 +296,39 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): uninstall core """ hide = not verbose - print("uninstalling core") - c.run("sudo make uninstall", hide=hide) - print("cleaning build directory") - c.run("make clean", hide=hide) - c.run("./bootstrap.sh clean", hide=hide) + p = Progress(verbose) + c.run("sudo -v", hide=True) + with p.start("uninstalling core"): + c.run("sudo make uninstall", hide=hide) + + with p.start("cleaning build directory"): + c.run("make clean", hide=hide) + c.run("./bootstrap.sh clean", hide=hide) + python = get_python(c, warn=True) if python: with c.cd(DAEMON_DIR): if dev: - print("uninstalling pre-commit") - c.run("poetry run pre-commit uninstall", hide=hide) - print("uninstalling poetry virtual environment") - c.run(f"poetry env remove {python}", hide=hide) + with p.start("uninstalling pre-commit"): + c.run("poetry run pre-commit uninstall", hide=hide) + with p.start("uninstalling poetry virtual environment"): + c.run(f"poetry env remove {python}", hide=hide) # remove installed files bin_dir = Path(prefix).joinpath("bin") - for script in Path("daemon/scripts").iterdir(): - dest = bin_dir.joinpath(script.name) - print(f"uninstalling {dest}") - c.run(f"sudo rm -f {dest}", hide=hide) + with p.start("uninstalling script files"): + for script in Path("daemon/scripts").iterdir(): + dest = bin_dir.joinpath(script.name) + c.run(f"sudo rm -f {dest}", hide=hide) # install service systemd_dir = Path("/lib/systemd/system/") service_name = "core-daemon.service" service_file = systemd_dir.joinpath(service_name) if service_file.exists(): - print(f"uninstalling service {service_file}") - c.run(f"sudo systemctl disable {service_name}", hide=hide) - c.run(f"sudo rm -f {service_file}", hide=hide) + with p.start(f"uninstalling service {service_file}"): + c.run(f"sudo systemctl disable {service_name}", hide=hide) + c.run(f"sudo rm -f {service_file}", hide=hide) @task From 119a3640e4b8a443bb1b21995c277452347c83e4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 14 Jul 2020 22:26:39 -0700 Subject: [PATCH 182/210] remove duplicated progress usage when installing ospf mdr --- tasks.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/tasks.py b/tasks.py index 0b727f58a..a8e8b3719 100644 --- a/tasks.py +++ b/tasks.py @@ -173,27 +173,25 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: if c.run("which zebra", warn=True, hide=hide): print("\nquagga already installed, skipping ospf mdr") return - p = Progress(not hide) - with p.start("installing ospf mdr dependencies"): - if os_info.like == OsLike.DEBIAN: - c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide) - elif os_info.like == OsLike.REDHAT: - c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide) - clone_dir = "/tmp/ospf-mdr" + if os_info.like == OsLike.DEBIAN: + c.run("sudo apt install -y libtool gawk libreadline-dev git", hide=hide) + elif os_info.like == OsLike.REDHAT: + c.run("sudo yum install -y libtool gawk readline-devel git", hide=hide) + clone_dir = "/tmp/ospf-mdr" + c.run( + f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", + hide=hide + ) + with c.cd(clone_dir): + c.run("./bootstrap.sh", hide=hide) c.run( - f"git clone https://github.com/USNavalResearchLaboratory/ospf-mdr {clone_dir}", + "./configure --disable-doc --enable-user=root --enable-group=root " + "--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh " + "--localstatedir=/var/run/quagga", hide=hide ) - with c.cd(clone_dir): - c.run("./bootstrap.sh", hide=hide) - c.run( - "./configure --disable-doc --enable-user=root --enable-group=root " - "--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh " - "--localstatedir=/var/run/quagga", - hide=hide - ) - c.run("make -j$(nproc)", hide=hide) - c.run("sudo make install", hide=hide) + c.run("make -j$(nproc)", hide=hide) + c.run("sudo make install", hide=hide) @task From a1ea762b8956488a1a97c5a05396ca0e67423e01 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 15 Jul 2020 00:08:22 -0700 Subject: [PATCH 183/210] updates to help provide better install related documentation --- docs/install.md | 65 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/docs/install.md b/docs/install.md index 2bf1a3b10..5e8bddf0b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -53,11 +53,14 @@ sudo modprobe sch_netem ## Automated Installation -> **NOTE:** installs OSPF MDR +The automated install will install the various tools needed to help automate +the CORE installation (python3, pip, pipx, invoke, poetry). The script will +also automatically clone, build, and install the latest version of OSPF MDR. +Finally it will install CORE scripts and a systemd service, which have +been modified to use the installed poetry created virtual environment. -> **NOTE:** sets up script files using the prefix provided - -> **NOTE:** install a systemd service file to /lib/systemd/system/core-daemon.service +After installation has completed you should be able to run the various +CORE scripts for running core. ```shell # clone CORE repo @@ -75,6 +78,13 @@ cd core ## Manual Installation +Below is an example of more formal manual steps that can be taken to install +CORE. You can also just install invoke and run `inv install` alone to simulate +what is done using `install.sh`. + +The last two steps help install core scripts modified to leverage the installed +poetry virtual environment and setup a systemd based service, if desired. + > **NOTE:** install OSPF MDR by manual instructions below ```shell @@ -131,6 +141,24 @@ inv install-scripts inv install-service ``` +## Installed Scripts + +These scripts will be installed from the automated `install.sh` script or +using `inv install` manually. + +| Name | Description | +|---|---| +| core-daemon | runs the backed core server providing TLV and gRPC APIs | +| core-gui | runs the legacy tcl/tk based GUI | +| core-pygui | runs the new python/tk based GUI | +| core-cleanup | tool to help removed lingering core created containers, bridges, directories | +| core-imn-to-xml | tool to help automate converting a .imn file to .xml format | +| core-route-monitor | tool to help monitor traffic across nodes and feed that to SDT | +| core-service-update | tool to update automate modifying a legacy service to match current naming | +| coresendmsg | tool to send TLV API commands from command line | +| core-cli | tool to query, open xml files, and send commands using gRPC | +| core-manage | tool to add, remove, or check for services, models, and node types | + ## Manually Install OSPF MDR (Routing Support) Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing @@ -171,10 +199,10 @@ Here are quick instructions for installing all EMANE packages for Ubuntu 18.04: sudo apt-get install libssl-dev libxml-libxml-perl libxml-simple-perl wget https://adjacentlink.com/downloads/emane/emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz tar xzf emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz -# install base emane packages -sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/emane*.deb -# install python3 bindings -sudo dpkg -i emane-1.2.5-release-1/deb/ubuntu-18_04/amd64/python3*.deb + +# install emane python bindings into the core virtual environment +cd $REPO/daemon +poetry run pip install $EMANE_REPO/src/python ``` ## Using Invoke Tasks @@ -220,3 +248,24 @@ core-cli query session -i 1 # exit the shell exit ``` + +## Running User Scripts + +If you create your own scripts to run CORE directly in python or using gRPC/TLV +APIs you will need to make sure you are running them within context of the +poetry install virtual environment. + +> **NOTE:** the following assumes CORE has been installed successfully + +One way to do this would be to enable to environments shell. +```shell +cd $REPO/daemon +poetry shell +python run /path/to/script.py +``` + +Another way would be to run the script directly by way of poetry. +```shell +cd $REPO/daemon +poetry run python /path/to/script.py +``` From 642af4fe47ad70fff9fa67195c8cf1c6e921c26a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 15 Jul 2020 16:22:03 -0700 Subject: [PATCH 184/210] slimmed down install documentation and added links to relevant tools and files --- docs/install.md | 185 ++++++++++++++---------------------------------- 1 file changed, 54 insertions(+), 131 deletions(-) diff --git a/docs/install.md b/docs/install.md index 5e8bddf0b..0bff31c97 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,4 +1,4 @@ -# CORE Installation +# Installation * Table of Contents {:toc} @@ -12,10 +12,10 @@ The following tools will be leveraged during installation: |Tool|Description| |---|---| -|pip|used to install pipx| -|pipx|used to install standalone python tools (invoke, poetry)| -|invoke|used to run provided tasks (install, daemon, gui, tests, etc)| -|poetry|used to install the managed python virtual environment for running CORE| +|[pip](https://pip.pypa.io/en/stable/)|used to install pipx| +|[pipx](https://pipxproject.github.io/pipx/)|used to install standalone python tools (invoke, poetry)| +|[invoke](http://www.pyinvoke.org/)|used to run provided tasks (install, daemon, gui, tests, etc)| +|[poetry](https://python-poetry.org/)|used to install the managed python virtual environment for running CORE| ## Required Hardware @@ -51,6 +51,27 @@ sudo modprobe sch_netem * iproute2 4.5+ is a requirement for bridge related commands * ebtables not backed by nftables +## Upgrading + +Please make sure to uninstall the previous installation of CORE cleanly +before proceeding to install. + +Previous install was built from source: +```shell +cd $REPO +sudo make uninstall +make clean +./bootstrap.sh clean +``` + +Installed from previously built packages: +```shell +# centos +sudo yum remove core +# ubuntu +sudo apt remove core +``` + ## Automated Installation The automated install will install the various tools needed to help automate @@ -62,6 +83,9 @@ been modified to use the installed poetry created virtual environment. After installation has completed you should be able to run the various CORE scripts for running core. +> **NOTE:** provide a prefix that will be found on path when running as sudo +> if the default prefix is not valid + ```shell # clone CORE repo git clone https://github.com/coreemu/core.git @@ -76,75 +100,20 @@ cd core ./install.sh ``` -## Manual Installation - -Below is an example of more formal manual steps that can be taken to install -CORE. You can also just install invoke and run `inv install` alone to simulate -what is done using `install.sh`. - -The last two steps help install core scripts modified to leverage the installed -poetry virtual environment and setup a systemd based service, if desired. - -> **NOTE:** install OSPF MDR by manual instructions below - -```shell -# clone CORE repo -git clone https://github.com/coreemu/core.git -cd core +### Unsupported Linux Distribution -# install python3 and venv support -# ubuntu -sudo apt install -y python3-pip python3-venv -# centos -sudo yum install -y python3-pip +If you are on an unsupported distribution, you can look into the +[install.sh](https://github.com/coreemu/core/blob/master/install.sh) +and +[tasks.py](https://github.com/coreemu/core/blob/master/tasks.py) +files to see the various commands ran to install CORE and translate them to +your use case, assuming it is possible. -# install system dependencies -# ubuntu -sudo apt install -y automake pkg-config gcc libev-dev ebtables iproute2 \ - ethtool tk python3-tk -# centos -sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ libev-devel \ - iptables-ebtables iproute python3-devel python3-tkinter tk ethtool \ - make kernel-modules-extra - -# install grpcio-tools -python3 -m pip install --user grpcio==1.27.2 grpcio-tools==1.27.2 - -# build core -./bootstrap.sh -# centos requires --prefix=/usr -./configure -make -sudo make install - -# install pipx, may need to restart terminal after ensurepath -python3 -m pip install --user pipx -python3 -m pipx ensurepath - -# install poetry -pipx install poetry - -# install poetry virtual environment -cd daemon -poetry install --no-dev -cd .. - -# install invoke to run helper tasks -pipx install invoke - -# install core scripts leveraging poetry virtual environment -# centos requires --prefix=/usr -inv install-scripts - -# optionally install systemd service file -# centos requires --prefix=/usr -inv install-service -``` +If you get install down entirely, feel free to contribute and help others. ## Installed Scripts -These scripts will be installed from the automated `install.sh` script or -using `inv install` manually. +After the installation complete it will have installed the following scripts. | Name | Description | |---|---| @@ -159,32 +128,25 @@ using `inv install` manually. | core-cli | tool to query, open xml files, and send commands using gRPC | | core-manage | tool to add, remove, or check for services, models, and node types | -## Manually Install OSPF MDR (Routing Support) +## Running User Scripts -Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing -tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by -default when the blue router node type is used. +If you create your own python scripts to run CORE directly or using the gRPC/TLV +APIs you will need to make sure you are running them within context of the +installed virtual environment. -* [OSPF MANET Designated Routers](https://github.com/USNavalResearchLaboratory/ospf-mdr) (MDR) - the Quagga routing -suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The **mdr** node type -(and the MDR service) requires this variant of Quagga. +> **NOTE:** the following assumes CORE has been installed successfully +One way to do this would be to enable the core virtual environment shell. ```shell -# system dependencies -# ubuntu -sudo apt install -y libtool gawk libreadline-dev -# centos -sudo yum install -y libtool gawk readline-devel - -# build and install -git clone https://github.com/USNavalResearchLaboratory/ospf-mdr -cd 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 -sudo make install +cd $REPO/daemon +poetry shell +python run /path/to/script.py +``` + +Another way would be to run the script directly by way of poetry. +```shell +cd $REPO/daemon +poetry run python /path/to/script.py ``` ## Manually Install EMANE @@ -217,7 +179,7 @@ Available tasks: cli run core-cli used to query and modify a running session daemon start core-daemon gui start core-pygui - install install core, poetry, scripts, service, and ospf mdr + install install core, scripts, service, and ospf mdr install-scripts install core script files, modified to leverage virtual environment install-service install systemd core service test run core tests @@ -230,42 +192,3 @@ Example running the core-daemon task from the root of the repo: ```shell inv daemon ``` - -Some tasks are wrappers around command line tools and requires running -them with a slight variation for compatibility. You can enter the -poetry shell to run the script natively. - -```shell -# running core-cli as a task requires all options to be provided -# within a string -inv cli "query session -i 1" - -# entering the poetry shell to use core-cli natively -cd $REPO/daemon -poetry shell -core-cli query session -i 1 - -# exit the shell -exit -``` - -## Running User Scripts - -If you create your own scripts to run CORE directly in python or using gRPC/TLV -APIs you will need to make sure you are running them within context of the -poetry install virtual environment. - -> **NOTE:** the following assumes CORE has been installed successfully - -One way to do this would be to enable to environments shell. -```shell -cd $REPO/daemon -poetry shell -python run /path/to/script.py -``` - -Another way would be to run the script directly by way of poetry. -```shell -cd $REPO/daemon -poetry run python /path/to/script.py -``` From 1c876819f19977ae9da5fb220a0e3547197a1b78 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 15 Jul 2020 16:50:04 -0700 Subject: [PATCH 185/210] task to automate installing emane --- tasks.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tasks.py b/tasks.py index a8e8b3719..4b6f2ca67 100644 --- a/tasks.py +++ b/tasks.py @@ -288,6 +288,49 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): print("\nyou may need to open a new terminal to leverage invoke for running core") +@task +def install_emane(c, verbose=False): + """ + install emane and the python bindings + """ + c.run("sudo -v", hide=True) + p = Progress(verbose) + hide = not verbose + os_info = get_os() + emane_dir = "/tmp/emane" + with p.start("installing system dependencies"): + if os_info.like == OsLike.DEBIAN: + c.run( + "sudo apt install gcc g++ automake libtool libxml2-dev libprotobuf-dev " + "libpcap-dev libpcre3-dev uuid-dev pkg-config protobuf-compiler git " + "python3-protobuf python3-setuptools", + hide=hide, + ) + elif os_info.like == OsLike.REDHAT: + c.run( + "sudo yum install autoconf automake git libtool libxml2-devel " + "libpcap-devel pcre-devel libuuid-devel make gcc-c++ " + "python3-setuptools", + hide=hide, + ) + with p.start("cloning emane"): + c.run( + f"git clone https://github.com/adjacentlink/emane.git {emane_dir}", + hide=hide + ) + with p.start("building emane"): + with c.cd(emane_dir): + c.run("./autogen.sh", hide=hide) + c.run("PYTHON=python3 ./configure --prefix=/usr", hide=hide) + c.run("make -j$(nproc)", hide=hide) + with p.start("installing emane"): + with c.cd(emane_dir): + c.run("sudo make install", hide=hide) + with p.start("installing python binding for core"): + with c.cd(DAEMON_DIR): + c.run(f"poetry run pip install {emane_dir}/src/python", hide=hide) + + @task def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ From 33d100acffb8716f396e29c069a9dbf155c7d98b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 15 Jul 2020 17:09:32 -0700 Subject: [PATCH 186/210] fix bad links in generated docs for grpc to point to latest on master --- docs/grpc.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/grpc.md b/docs/grpc.md index 69cf4aedc..ca80256ee 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -15,20 +15,23 @@ properly account for this issue or clear out your proxy when running if needed. ## Python Client A python client wrapper is provided at -[CoreGrpcClient](../daemon/core/api/grpc/client.py) to help provide some -conveniences when using the API. +[CoreGrpcClient](https://github.com/coreemu/core/blob/master/daemon/core/api/grpc/client.py) +to help provide some conveniences when using the API. ## Proto Files Proto files are used to define the API and protobuf messages that are used for interfaces with this API. -They can be found [here](../daemon/proto/core/api/grpc) to see the specifics of +They can be found +[here](https://github.com/coreemu/core/tree/master/daemon/proto/core/api/grpc) +to see the specifics of what is going on and response message values that would be returned. ## Examples -Example usage of this API can be found [here](../daemon/examples/grpc). These -examples will create a session using the gRPC API when the core-daemon is running. +Example usage of this API can be found +[here](https://github.com/coreemu/core/tree/master/daemon/examples/grpc). +These examples will create a session using the gRPC API when the core-daemon is running. You can then switch to and attach to these sessions using either of the CORE GUIs. From 897ecc6d356c07041b71f1ba35b28c9f3238cee0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 15 Jul 2020 17:52:34 -0700 Subject: [PATCH 187/210] updated install emane task to auto answer yes to installing system packages --- tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index 4b6f2ca67..6f5b7ac37 100644 --- a/tasks.py +++ b/tasks.py @@ -301,14 +301,14 @@ def install_emane(c, verbose=False): with p.start("installing system dependencies"): if os_info.like == OsLike.DEBIAN: c.run( - "sudo apt install gcc g++ automake libtool libxml2-dev libprotobuf-dev " - "libpcap-dev libpcre3-dev uuid-dev pkg-config protobuf-compiler git " - "python3-protobuf python3-setuptools", + "sudo apt install -y gcc g++ automake libtool libxml2-dev " + "libprotobuf-dev libpcap-dev libpcre3-dev uuid-dev pkg-config " + "protobuf-compiler git python3-protobuf python3-setuptools", hide=hide, ) elif os_info.like == OsLike.REDHAT: c.run( - "sudo yum install autoconf automake git libtool libxml2-devel " + "sudo yum install -y autoconf automake git libtool libxml2-devel " "libpcap-devel pcre-devel libuuid-devel make gcc-c++ " "python3-setuptools", hide=hide, From 495fbe5632731168ba02fc233fcc78dfc4536815 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 15 Jul 2020 21:50:35 -0700 Subject: [PATCH 188/210] added protobuf-compiler to install emane task --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 6f5b7ac37..d044deb81 100644 --- a/tasks.py +++ b/tasks.py @@ -309,7 +309,7 @@ def install_emane(c, verbose=False): elif os_info.like == OsLike.REDHAT: c.run( "sudo yum install -y autoconf automake git libtool libxml2-devel " - "libpcap-devel pcre-devel libuuid-devel make gcc-c++ " + "libpcap-devel pcre-devel libuuid-devel make gcc-c++ protobuf-compiler " "python3-setuptools", hide=hide, ) From c884ee27cd08b51607e6a694efe257ab93b2ecbe Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 08:42:36 -0700 Subject: [PATCH 189/210] removed invoke tasks wrapping scripts, since they can be used directly, added invoke task help strings, add invoke task to run user scripts --- tasks.py | 75 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/tasks.py b/tasks.py index d044deb81..1dde1cea5 100644 --- a/tasks.py +++ b/tasks.py @@ -194,7 +194,12 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: c.run("sudo make install", hide=hide) -@task +@task( + help={ + "verbose": "enable verbose", + "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}" + }, +) def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): """ install systemd core service @@ -225,7 +230,12 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): print(f"ERROR: systemd service path not found: {systemd_dir}") -@task +@task( + help={ + "verbose": "enable verbose", + "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}" + }, +) def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX): """ install core script files, modified to leverage virtual environment @@ -259,7 +269,13 @@ def install_scripts(c, verbose=False, prefix=DEFAULT_PREFIX): c.run(f"sudo cp -n daemon/data/logging.conf {config_dir}", hide=hide) -@task +@task( + help={ + "dev": "install development mode", + "verbose": "enable verbose", + "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}" + }, +) def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ install core, poetry, scripts, service, and ospf mdr @@ -288,7 +304,11 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): print("\nyou may need to open a new terminal to leverage invoke for running core") -@task +@task( + help={ + "verbose": "enable verbose", + }, +) def install_emane(c, verbose=False): """ install emane and the python bindings @@ -331,7 +351,13 @@ def install_emane(c, verbose=False): c.run(f"poetry run pip install {emane_dir}/src/python", hide=hide) -@task +@task( + help={ + "dev": "uninstall development mode", + "verbose": "enable verbose", + "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}" + }, +) def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ uninstall core @@ -386,31 +412,26 @@ def daemon(c): ) -@task -def gui(c): +@task( + help={ + "sudo": "run script as sudo", + "file": "script file to run in the core virtual environment" + }, +) +def run(c, file, sudo=False): """ - start core-pygui - """ - with c.cd(DAEMON_DIR): - c.run("poetry run scripts/core-pygui", pty=True) - - -@task -def cli(c, args): - """ - run core-cli used to query and modify a running session + convenience for running a core related script """ + if not file: + print("no script was provided") + return + python = get_python(c) + path = Path(file).absolute() with c.cd(DAEMON_DIR): - c.run(f"poetry run scripts/core-cli {args}", pty=True) - - -@task -def cleanup(c): - """ - run core-cleanup removing leftover core nodes, bridges, directories - """ - print("running core-cleanup...") - c.run(f"sudo daemon/scripts/core-cleanup", pty=True) + cmd = f"{python} {path}" + if sudo: + cmd = f"sudo {cmd}" + c.run(cmd, pty=True) @task From d1fd19edc6cd739a845e3722fa9e296a858c125a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 08:47:18 -0700 Subject: [PATCH 190/210] updated doc examples for invoke tasks --- docs/install.md | 30 +++++++++++++++++++++++------- tasks.py | 4 ++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/install.md b/docs/install.md index 0bff31c97..6a9fb393b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -173,22 +173,38 @@ The invoke tool installed by way of pipx provides conveniences for running CORE tasks to help ensure usage of the create python virtual environment. ```shell +inv --list + Available tasks: - cleanup run core-cleanup removing leftover core nodes, bridges, directories - cli run core-cli used to query and modify a running session daemon start core-daemon - gui start core-pygui - install install core, scripts, service, and ospf mdr + install install core, poetry, scripts, service, and ospf mdr + install-emane install emane and the python bindings install-scripts install core script files, modified to leverage virtual environment install-service install systemd core service + run runs a user script in the core virtual environment test run core tests test-emane run core emane tests test-mock run core tests using mock to avoid running as sudo - uninstall uninstall core + uninstall uninstall core, scripts, service, virtual environment, and clean build directory +``` + +Print help for a given task: +```shell +inv -h install + +Usage: inv[oke] [--core-opts] install [--options] [other tasks here ...] + +Docstring: + install core, poetry, scripts, service, and ospf mdr + +Options: + -d, --dev install development mode + -p STRING, --prefix=STRING prefix where scripts are installed, default is /usr/local + -v, --verbose enable verbose ``` -Example running the core-daemon task from the root of the repo: +Example running a core user script: ```shell -inv daemon +inv run /path/to/core/grpc/script.py ``` diff --git a/tasks.py b/tasks.py index 1dde1cea5..e1b539a4f 100644 --- a/tasks.py +++ b/tasks.py @@ -360,7 +360,7 @@ def install_emane(c, verbose=False): ) def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ - uninstall core + uninstall core, scripts, service, virtual environment, and clean build directory """ hide = not verbose p = Progress(verbose) @@ -420,7 +420,7 @@ def daemon(c): ) def run(c, file, sudo=False): """ - convenience for running a core related script + runs a user script in the core virtual environment """ if not file: print("no script was provided") From 1212e5ddf8dce4bf797b8fe2be3c3486020e5943 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 08:59:57 -0700 Subject: [PATCH 191/210] fix to avoid setting interface data for a mac to the string None, when not present --- daemon/core/nodes/base.py | 3 ++- daemon/core/nodes/network.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 7f444480d..cea1e81bd 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1050,8 +1050,9 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: if uni: unidirectional = 1 + mac = str(iface.mac) if iface.mac else None iface2_data = InterfaceData( - id=linked_node.get_iface_id(iface), name=iface.name, mac=str(iface.mac) + id=linked_node.get_iface_id(iface), name=iface.name, mac=mac ) ip4 = iface.get_ip4() if ip4: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index a55de4cf5..58c1e195b 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -875,8 +875,9 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: if iface1.getparams() != iface2.getparams(): unidirectional = 1 + mac = str(iface1.mac) if iface1.mac else None iface1_data = InterfaceData( - id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=str(iface1.mac) + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=mac ) ip4 = iface1.get_ip4() if ip4: @@ -887,8 +888,9 @@ def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: iface1_data.ip6 = str(ip6.ip) iface1_data.ip6_mask = ip6.prefixlen + mac = str(iface2.mac) if iface2.mac else None iface2_data = InterfaceData( - id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=str(iface2.mac) + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=mac ) ip4 = iface2.get_ip4() if ip4: From 0be1972a29c7600ffce663348f169b0027deffe7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 09:16:32 -0700 Subject: [PATCH 192/210] update to running user scripts in install doc --- docs/install.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/install.md b/docs/install.md index 6a9fb393b..6129c17f8 100644 --- a/docs/install.md +++ b/docs/install.md @@ -136,17 +136,26 @@ installed virtual environment. > **NOTE:** the following assumes CORE has been installed successfully -One way to do this would be to enable the core virtual environment shell. +There is an invoke task to help with this case. ```shell -cd $REPO/daemon -poetry shell -python run /path/to/script.py +cd $REPO +inv -h run +Usage: inv[oke] [--core-opts] run [--options] [other tasks here ...] + +Docstring: + runs a user script in the core virtual environment + +Options: + -f STRING, --file=STRING script file to run in the core virtual environment + -s, --sudo run script as sudo ``` -Another way would be to run the script directly by way of poetry. +Another way would be to enable the core virtual environment shell. Which +would allow you to run scripts in a more **normal** way. ```shell cd $REPO/daemon -poetry run python /path/to/script.py +poetry shell +python run /path/to/script.py ``` ## Manually Install EMANE @@ -203,8 +212,3 @@ Options: -p STRING, --prefix=STRING prefix where scripts are installed, default is /usr/local -v, --verbose enable verbose ``` - -Example running a core user script: -```shell -inv run /path/to/core/grpc/script.py -``` From b50f05837476e1b524e8cd465bc98bb1865c8ee7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 09:26:08 -0700 Subject: [PATCH 193/210] improved emane section in install doc --- docs/install.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/install.md b/docs/install.md index 6129c17f8..12d478025 100644 --- a/docs/install.md +++ b/docs/install.md @@ -163,15 +163,23 @@ python run /path/to/script.py EMANE can be installed from deb or RPM packages or from source. See the [EMANE GitHub](https://github.com/adjacentlink/emane) for full details. -Here are quick instructions for installing all EMANE packages for Ubuntu 18.04: +There is an invoke task to help with installing EMANE, but has issues, +which attempts to build EMANE from source, but has issue on systems with + older protobuf-compilers. + ```shell -# install dependencies -# ubuntu -sudo apt-get install libssl-dev libxml-libxml-perl libxml-simple-perl -wget https://adjacentlink.com/downloads/emane/emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz -tar xzf emane-1.2.5-release-1.ubuntu-18_04.amd64.tar.gz +cd $REPO +inv install-emane +``` + +Alternatively, you can +[build EMANE](https://github.com/adjacentlink/emane/wiki/Build) +from source and install the python +bindings into the core virtual environment. -# install emane python bindings into the core virtual environment +The following would install the EMANE python bindings after being +successfully built. +```shell cd $REPO/daemon poetry run pip install $EMANE_REPO/src/python ``` From db4ef2b42e3582b74d17f01b0f3a9f747b8c97c2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:02:56 -0700 Subject: [PATCH 194/210] fixed core.conf commented out example path for core-pygui to use .coregui instead of .coretk --- daemon/data/core.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/data/core.conf b/daemon/data/core.conf index 5ff0be7ff..20ee5d1f1 100644 --- a/daemon/data/core.conf +++ b/daemon/data/core.conf @@ -13,7 +13,7 @@ frr_sbin_search = "/usr/local/sbin /usr/sbin /usr/lib/frr" # this may be a comma-separated list, and directory names should be unique # and not named 'services' #custom_services_dir = /home/username/.core/myservices -#custom_config_services_dir = /home/username/.coretk/custom_services +#custom_config_services_dir = /home/username/.coregui/custom_services # uncomment to establish a standalone control backchannel for accessing nodes # (overriden by the session option of the same name) From 6b550618572fe175ffe9a8178ec93a6cc715c64a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:09:26 -0700 Subject: [PATCH 195/210] update dev gui doc for new installation --- docs/devguide.md | 59 ++++++++++++------------------------------------ 1 file changed, 15 insertions(+), 44 deletions(-) diff --git a/docs/devguide.md b/docs/devguide.md index c10bb0077..9b9d61c87 100644 --- a/docs/devguide.md +++ b/docs/devguide.md @@ -16,7 +16,6 @@ daemon. Here is a brief description of the source directories. |gui|Tcl/Tk GUI| |man|Template files for creating man pages for various CORE command line utilities| |netns|C program for creating CORE containers| -|scripts|Template files used for running CORE as a service| ## Getting started @@ -34,21 +33,11 @@ git checkout develop ## Install the Development Environment This command will automatically install system dependencies, clone and build OSPF-MDR, -build CORE, setup the CORE pipenv environment, and install pre-commit hooks. - -This script is currently compatible with Ubuntu and CentOS, tested on Ubuntu 18.04 and -CentOS 7.6. The script also currently defaults to using python3.6, but a different -version of python can be targeted if python3.6 is not available on your system. +build CORE, setup the CORE poetry environment, and install pre-commit hooks. You can +refer to the [install docs](install.md) for issues related to different distributions. ```shell -# default dev install using python3.6 -./install.sh -d - -# providing a newer python version for ubuntu -./install.sh -d -v 3.7 - -# providing a newer python version for centos -./install.sh -d -v 37 +./install -d ``` ### pre-commit @@ -57,42 +46,24 @@ pre-commit hooks help automate running tools to check modified code. Every time python utilities will be ran to check validity of code, potentially failing and backing out the commit. These changes are currently mandated as part of the current CI, so add the changes and commit again. -### Adding EMANE to Pipenv - -EMANE bindings are not available through pip, you will need to build and install from source. - -[Build EMANE](https://github.com/adjacentlink/emane/wiki/Build#general-build-instructions) - -```shell -# clone emane repo -git clone https://github.com/adjacentlink/emane.git - -# install emane build deps -sudo apt install libxml2-dev libprotobuf-dev uuid-dev libpcap-dev protobuf-compiler - -# build emane -./autogen.sh -./configure --prefix=/usr -make -j8 - -# install emane binding in pipenv -# NOTE: this will mody pipenv Pipfiles and we do not want that, use git checkout -- Pipfile*, to remove changes -python3 -m pipenv pip install $EMANEREPO/src/python -``` - ## Running CORE -Commands below can be used to run the core-daemon, the new core gui, and tests. +You can now run core as you normally would, or leverage some of the invoke tasks to +conveniently run tests, etc. ```shell -# runs for daemon -sudo python3 -m pipenv run core +# run core-daemon +sudo core-daemon + +# run python gui +core-pygui -# runs coretk gui -python3 -m pipenv run core-pygui +# run tcl gui +core-gui -# runs mocked unit tests -python3 -m pipenv run test-mock +# run mocked unit tests +cd $REPO +inv test-mock ``` ## Linux Network Namespace Commands From 1c2d7c6d12be0cf3be9b3822b88d373af933fc36 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:35:16 -0700 Subject: [PATCH 196/210] added reinstall invoke task, added some simple detections for old core installations in install task --- tasks.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tasks.py b/tasks.py index e1b539a4f..931aaf93b 100644 --- a/tasks.py +++ b/tasks.py @@ -107,6 +107,15 @@ def get_os() -> OsInfo: return OsInfo(name, like, version) +def check_existing_core(c: Context, hide: bool) -> None: + if c.run("python -c \"import core\"", warn=True, hide=hide): + raise SystemError("existing python2 core installation detected, please remove") + if c.run("python3 -c \"import core\"", warn=True, hide=hide): + raise SystemError("existing python3 core installation detected, please remove") + if c.run("which core-daemon", warn=True, hide=hide): + raise SystemError("core scripts found, please remove old installation") + + def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: if os_info.like == OsLike.DEBIAN: c.run( @@ -285,6 +294,8 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): p = Progress(verbose) hide = not verbose os_info = get_os() + with p.start("checking for old installations"): + check_existing_core(c, hide) with p.start("installing system dependencies"): install_system(c, os_info, hide) with p.start("installing system grpcio-tools"): @@ -398,6 +409,33 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): c.run(f"sudo rm -f {service_file}", hide=hide) +@task( + help={ + "dev": "reinstall development mode", + "verbose": "enable verbose", + "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}", + "branch": "branch to install latest code from, default is current branch" + }, +) +def reinstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX, branch=None): + """ + run the uninstall task, get latest from specified branch, and run install task + """ + uninstall(c, dev, verbose, prefix) + hide = not verbose + p = Progress(verbose) + with p.start("pulling latest code"): + current = c.run("git rev-parse --abbrev-ref HEAD", hide=hide).stdout.strip() + if branch and branch != current: + c.run(f"git checkout {branch}") + else: + branch = current + c.run("git pull", hide=hide) + if not Path("tasks.py").exists(): + raise FileNotFoundError(f"missing tasks.py on branch: {branch}") + install(c, dev, verbose, prefix) + + @task def daemon(c): """ From 6219d08416362e5aa6c2a41b53a967270408888f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 11:04:52 -0700 Subject: [PATCH 197/210] enable centos 8 check to enable powertools repo for centos 8 when installing emane --- tasks.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 931aaf93b..5f52f4440 100644 --- a/tasks.py +++ b/tasks.py @@ -289,8 +289,8 @@ def install(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ install core, poetry, scripts, service, and ospf mdr """ - c.run("sudo -v", hide=True) print(f"installing core with prefix: {prefix}") + c.run("sudo -v", hide=True) p = Progress(verbose) hide = not verbose os_info = get_os() @@ -338,10 +338,12 @@ def install_emane(c, verbose=False): hide=hide, ) elif os_info.like == OsLike.REDHAT: + if os_info.name == OsName.CENTOS and os_info.version >= 8: + c.run("sudo yum config-manager --set-enabled PowerTools", hide=hide) c.run( "sudo yum install -y autoconf automake git libtool libxml2-devel " "libpcap-devel pcre-devel libuuid-devel make gcc-c++ protobuf-compiler " - "python3-setuptools", + "protobuf-devel python3-setuptools", hide=hide, ) with p.start("cloning emane"): @@ -373,6 +375,7 @@ def uninstall(c, dev=False, verbose=False, prefix=DEFAULT_PREFIX): """ uninstall core, scripts, service, virtual environment, and clean build directory """ + print(f"uninstalling core with prefix: {prefix}") hide = not verbose p = Progress(verbose) c.run("sudo -v", hide=True) From 35b4c157a097528718e731a575db483e3fb83793 Mon Sep 17 00:00:00 2001 From: Shawn Kelly O'Shea Date: Thu, 16 Jul 2020 15:22:33 -0400 Subject: [PATCH 198/210] Increase height of options dialogue in TCL gui We have emane models with a large list of options. Without this modification, a user cannot access all of the options provided by the emane model (some of the options are cutoff and cannot be scrolled-down to). --- gui/plugins.tcl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/plugins.tcl b/gui/plugins.tcl index 95c1a2037..fdb5c4541 100644 --- a/gui/plugins.tcl +++ b/gui/plugins.tcl @@ -672,11 +672,11 @@ proc popupCapabilityConfig { channel wlan model types values captions bmp possib pack $windowScroll -fill y -side right pack $windowCanvas -expand yes -fill both -side top - frame $windowCanvas.notebookFrame -width 700 -height 1200 + frame $windowCanvas.notebookFrame -width 700 -height 2400 set notebookFrame $windowCanvas.notebookFrame pack $notebookFrame -fill both -expand yes -padx 5 -pady 5 - ttk::notebook $notebookFrame.vals -width 690 -height 1200 + ttk::notebook $notebookFrame.vals -width 690 -height 2400 set configNotebook $notebookFrame.vals ttk::notebook::enableTraversal $configNotebook pack $configNotebook -fill both -expand yes From fdf00cff0e85bb5f5ce47250364849c51e456438 Mon Sep 17 00:00:00 2001 From: apwiggins Date: Thu, 16 Jul 2020 18:00:12 -0300 Subject: [PATCH 199/210] Update frr.py to add staticd to daemons list Add staticd to the list of possible daemons to be started. http://docs.frrouting.org/en/latest/setup.html#daemons-configuration-file https://github.com/coreemu/core/issues/397 --- daemon/core/services/frr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 9a3443394..e3675bc72 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -271,6 +271,7 @@ def generateFrrDaemons(cls, node): eigrpd=yes babeld=yes sharpd=yes +staticd=yes pbrd=yes bfdd=yes fabricd=yes From 36123e7aa545b9fd04cedfa2169a7ea12612e985 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 14:21:06 -0700 Subject: [PATCH 200/210] updated frr daemons template file for the config service to align with changes to normal service --- daemon/core/configservices/frrservices/templates/daemons | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/core/configservices/frrservices/templates/daemons b/daemon/core/configservices/frrservices/templates/daemons index 0f6bda536..dbd421081 100644 --- a/daemon/core/configservices/frrservices/templates/daemons +++ b/daemon/core/configservices/frrservices/templates/daemons @@ -20,6 +20,7 @@ nhrpd=yes eigrpd=yes babeld=yes sharpd=yes +staticd=yes pbrd=yes bfdd=yes fabricd=yes From 6d4434bc1274c5aa7612ff289a97b646f6b477cc Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 16 Jul 2020 22:51:26 -0700 Subject: [PATCH 201/210] grpc: added set session user call, updated mobility to look for files within new gui as well, fixed pygui issue when start session has a grpc exceptions, showing and empty error window --- daemon/core/api/grpc/client.py | 14 +++++++++ daemon/core/api/grpc/server.py | 15 ++++++++++ daemon/core/gui/coreclient.py | 5 ++++ daemon/core/gui/data/xmls/sample1.xml | 2 +- daemon/core/gui/toolbar.py | 2 +- daemon/core/location/mobility.py | 42 +++++++++++++-------------- daemon/proto/core/api/grpc/core.proto | 11 +++++++ 7 files changed, 67 insertions(+), 24 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 20e193eb2..3e974233d 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -414,6 +414,20 @@ def set_session_state( request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state) return self.stub.SetSessionState(request) + def set_session_user( + self, session_id: int, user: str + ) -> core_pb2.SetSessionUserResponse: + """ + Set session user, used for helping to find files without full paths. + + :param session_id: id of session + :param user: user to set for session + :return: response with result of success or failure + :raises grpc.RpcError: when session doesn't exist + """ + request = core_pb2.SetSessionUserRequest(session_id=session_id, user=user) + return self.stub.SetSessionUser(request) + def add_session_server( self, session_id: int, name: str, host: str ) -> core_pb2.AddSessionServerResponse: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 5bdebac62..da2d53c32 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -448,6 +448,21 @@ def SetSessionState( return core_pb2.SetSessionStateResponse(result=result) + def SetSessionUser( + self, request: core_pb2.SetSessionUserRequest, context: ServicerContext + ) -> core_pb2.SetSessionUserResponse: + """ + Sets the user for a session. + + :param request: set session user request + :param context: context object + :return: set session user response + """ + logging.debug("set session user: %s", request) + session = self.get_session(request.session_id, context) + session.user = request.user + return core_pb2.SetSessionUserResponse(result=True) + def GetSessionOptions( self, request: core_pb2.GetSessionOptionsRequest, context: ServicerContext ) -> core_pb2.GetSessionOptionsResponse: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 255192be9..52023e143 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -1,6 +1,7 @@ """ Incorporate grpc into python tkinter GUI """ +import getpass import json import logging import os @@ -71,6 +72,7 @@ def __init__(self, app: "Application", proxy: bool) -> None: self.default_services: Dict[NodeType, Set[str]] = {} self.emane_models: List[str] = [] self.observer: Optional[str] = None + self.user = getpass.getuser() # loaded configuration data self.servers: Dict[str, CoreServer] = {} @@ -289,6 +291,9 @@ def join_session(self, session_id: int, query_location: bool = True) -> None: self.session_id, self.handle_events ) + # set session user + self.client.set_session_user(self.session_id, self.user) + # get session service defaults response = self.client.get_service_defaults(self.session_id) self.default_services = { diff --git a/daemon/core/gui/data/xmls/sample1.xml b/daemon/core/gui/data/xmls/sample1.xml index afec88742..5055c2254 100644 --- a/daemon/core/gui/data/xmls/sample1.xml +++ b/daemon/core/gui/data/xmls/sample1.xml @@ -188,7 +188,7 @@ - + diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index c3e9067f8..406a88ca1 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -305,7 +305,7 @@ def start_callback(self, response: core_pb2.StartSessionResponse) -> None: self.set_runtime() self.app.core.set_metadata() self.app.core.show_mobility_players() - else: + elif response.exceptions: enable_buttons(self.design_frame, enabled=True) message = "\n".join(response.exceptions) self.app.show_error("Start Session Error", message) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index f2e0f470c..e982c5c1c 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -5,10 +5,10 @@ import heapq import logging import math -import os import threading import time from functools import total_ordering +from pathlib import Path from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils @@ -1030,30 +1030,28 @@ def readscriptfile(self) -> None: def findfile(self, file_name: str) -> str: """ Locate a script file. If the specified file doesn't exist, look in the - same directory as the scenario file, or in the default - configs directory (~/.core/configs). This allows for sample files without - absolute path names. + same directory as the scenario file, or in gui directories. :param file_name: file name to find :return: absolute path to the file - """ - if os.path.exists(file_name): - return file_name - - if self.session.file_name is not None: - d = os.path.dirname(self.session.file_name) - sessfn = os.path.join(d, file_name) - if os.path.exists(sessfn): - return sessfn - - if self.session.user is not None: - userfn = os.path.join( - "/home", self.session.user, ".core", "configs", file_name - ) - if os.path.exists(userfn): - return userfn - - return file_name + :raises CoreError: when file is not found + """ + file_path = Path(file_name).expanduser() + if file_path.exists(): + return str(file_path) + if self.session.file_name: + file_path = Path(self.session.file_name).parent / file_name + if file_path.exists(): + return str(file_path) + if self.session.user: + user_path = Path(f"~{self.session.user}").expanduser() + file_path = user_path / ".core" / "configs" / file_name + if file_path.exists(): + return str(file_path) + file_path = user_path / ".coregui" / "mobility" / file_name + if file_path.exists(): + return str(file_path) + raise CoreError(f"invalid file: {file_name}") def parsemap(self, mapstr: str) -> None: """ diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index f01fca509..5ca4812c4 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -39,6 +39,8 @@ service CoreApi { } rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { } + rpc SetSessionUser (SetSessionUserRequest) returns (SetSessionUserResponse) { + } rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { } @@ -297,6 +299,15 @@ message SetSessionStateResponse { bool result = 1; } +message SetSessionUserRequest { + int32 session_id = 1; + string user = 2; +} + +message SetSessionUserResponse { + bool result = 1; +} + message AddSessionServerRequest { int32 session_id = 1; string name = 2; From 5e2ca0f5497b2b5ed43e104d29860a5a1297af1b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 18 Jul 2020 11:56:48 -0700 Subject: [PATCH 202/210] daemon: refactored how to get required commands, added usage of this func for validating distributed servers when added --- daemon/core/emulator/coreemu.py | 9 ++------- daemon/core/emulator/distributed.py | 12 +++++++++++- daemon/core/executables.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 016f2e5b1..c07d8c953 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -9,7 +9,7 @@ from core import configservices, utils from core.configservice.manager import ConfigServiceManager from core.emulator.session import Session -from core.executables import COMMON_REQUIREMENTS, OVS_REQUIREMENTS, VCMD_REQUIREMENTS +from core.executables import get_requirements from core.services.coreservices import ServiceManager @@ -79,13 +79,8 @@ def _validate_env(self) -> None: :return: nothing :raises core.errors.CoreError: when an executable does not exist on path """ - requirements = COMMON_REQUIREMENTS use_ovs = self.config.get("ovs") == "1" - if use_ovs: - requirements += OVS_REQUIREMENTS - else: - requirements += VCMD_REQUIREMENTS - for requirement in requirements: + for requirement in get_requirements(use_ovs): utils.which(requirement, required=True) def load_services(self) -> None: diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 381eb019d..a5e1009fd 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -14,7 +14,8 @@ from invoke import UnexpectedExit from core import utils -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError +from core.executables import get_requirements from core.nodes.interface import GreTap from core.nodes.network import CoreNetwork, CtrlNet @@ -131,8 +132,17 @@ def add_server(self, name: str, host: str) -> None: :param name: distributed server name :param host: distributed server host address :return: nothing + :raises CoreError: when there is an error validating server """ server = DistributedServer(name, host) + for requirement in get_requirements(self.session.use_ovs()): + try: + server.remote_cmd(f"which {requirement}") + except CoreCommandError: + raise CoreError( + f"server({server.name}) failed validation for " + f"command({requirement})" + ) self.servers[name] = server cmd = f"mkdir -p {self.session.session_dir}" server.remote_cmd(cmd) diff --git a/daemon/core/executables.py b/daemon/core/executables.py index 17aecc1d1..6eb0214a1 100644 --- a/daemon/core/executables.py +++ b/daemon/core/executables.py @@ -14,3 +14,18 @@ COMMON_REQUIREMENTS: List[str] = [SYSCTL, IP, ETHTOOL, TC, EBTABLES, MOUNT, UMOUNT] VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD] OVS_REQUIREMENTS: List[str] = [OVS_VSCTL] + + +def get_requirements(use_ovs: bool) -> List[str]: + """ + Retrieve executable requirements needed to run CORE. + + :param use_ovs: True if OVS is being used, False otherwise + :return: list of executable requirements + """ + requirements = COMMON_REQUIREMENTS + if use_ovs: + requirements += OVS_REQUIREMENTS + else: + requirements += VCMD_REQUIREMENTS + return requirements From d5d5da72560c866e2450640e04cb9e7488d9e9b2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 21 Jul 2020 10:08:12 -0700 Subject: [PATCH 203/210] bumped version to 7.0.0 --- configure.ac | 2 +- daemon/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 10d30c20e..60f6709ec 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 6.5.0) +AC_INIT(core, 7.0.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 1fdc9d1a1..b75f1ee3b 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "6.6.0" +version = "7.0.0" description = "CORE Common Open Research Emulator" authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" From 45bfa9fdadf0b95da3d4755c1a682d34be8319ce Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:52:17 -0700 Subject: [PATCH 204/210] small tweaks to docs --- docs/devguide.md | 2 +- docs/install.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/devguide.md b/docs/devguide.md index 9b9d61c87..ba34a211c 100644 --- a/docs/devguide.md +++ b/docs/devguide.md @@ -62,7 +62,7 @@ core-pygui core-gui # run mocked unit tests -cd $REPO +cd inv test-mock ``` diff --git a/docs/install.md b/docs/install.md index 12d478025..604ac509b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -58,7 +58,7 @@ before proceeding to install. Previous install was built from source: ```shell -cd $REPO +cd sudo make uninstall make clean ./bootstrap.sh clean @@ -138,7 +138,7 @@ installed virtual environment. There is an invoke task to help with this case. ```shell -cd $REPO +cd inv -h run Usage: inv[oke] [--core-opts] run [--options] [other tasks here ...] @@ -153,7 +153,7 @@ Options: Another way would be to enable the core virtual environment shell. Which would allow you to run scripts in a more **normal** way. ```shell -cd $REPO/daemon +cd /daemon poetry shell python run /path/to/script.py ``` @@ -168,7 +168,7 @@ which attempts to build EMANE from source, but has issue on systems with older protobuf-compilers. ```shell -cd $REPO +cd inv install-emane ``` @@ -180,8 +180,8 @@ bindings into the core virtual environment. The following would install the EMANE python bindings after being successfully built. ```shell -cd $REPO/daemon -poetry run pip install $EMANE_REPO/src/python +cd /daemon +poetry run pip install /src/python ``` ## Using Invoke Tasks From 165e404184e92858280b16eb2cd013651ad75abe Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:49:11 -0700 Subject: [PATCH 205/210] added example dockerfile and build command to readme --- daemon/examples/docker/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/daemon/examples/docker/README.md b/daemon/examples/docker/README.md index 3c2b13722..17c6cb90d 100644 --- a/daemon/examples/docker/README.md +++ b/daemon/examples/docker/README.md @@ -44,3 +44,18 @@ newgrp docker This directory provides a few small examples creating Docker nodes and linking them to themselves or with standard CORE nodes. + +Images used by nodes need to have networking tools installed for CORE to automate +setup and configuration of the container. + +Example Dockerfile: +``` +FROM ubuntu:latest +RUN apt-get update +RUN apt-get install -y iproute2 ethtool +``` + +Build image: +```shell +sudo docker build -t . +``` From e34002b851ec51fd74db8e56e6885f98aa8e41d9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 17:18:35 -0700 Subject: [PATCH 206/210] pygui: added option to launch core-pygui into a specific session using an id --- daemon/core/gui/app.py | 4 ++-- daemon/core/gui/coreclient.py | 24 ++++++++++++++++++------ daemon/core/gui/dialogs/error.py | 25 ++++++++++--------------- daemon/examples/grpc/switch.py | 4 ++-- daemon/scripts/core-pygui | 3 ++- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index e0121d14e..176b31e36 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -26,7 +26,7 @@ class Application(ttk.Frame): - def __init__(self, proxy: bool) -> None: + def __init__(self, proxy: bool, session_id: int = None) -> None: super().__init__() # load node icons NodeUtils.setup() @@ -56,7 +56,7 @@ def __init__(self, proxy: bool) -> None: self.core: CoreClient = CoreClient(self, proxy) self.setup_app() self.draw() - self.core.setup() + self.core.setup(session_id) def setup_scaling(self) -> None: self.fonts_size = {name: font.nametofont(name)["size"] for name in font.names()} diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 52023e143..26a5a3907 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -473,7 +473,7 @@ def delete_session(self, session_id: int = None) -> None: except grpc.RpcError as e: self.app.show_grpc_exception("Delete Session Error", e) - def setup(self) -> None: + def setup(self, session_id: int = None) -> None: """ Query sessions, if there exist any, prompt whether to join one """ @@ -494,14 +494,26 @@ def setup(self) -> None: ) group_services.add(service.name) - # if there are no sessions, create a new session, else join a session + # join provided session, create new session, or show dialog to select an + # existing session response = self.client.get_sessions() sessions = response.sessions - if len(sessions) == 0: - self.create_new_session() + if session_id: + session_ids = set(x.id for x in sessions) + if session_id not in session_ids: + dialog = ErrorDialog( + self.app, "Join Session Error", f"{session_id} does not exist" + ) + dialog.show() + self.app.close() + else: + self.join_session(session_id) else: - dialog = SessionsDialog(self.app, True) - dialog.show() + if not sessions: + self.create_new_session() + else: + dialog = SessionsDialog(self.app, True) + dialog.show() except grpc.RpcError as e: logging.exception("core setup error") dialog = ErrorDialog(self.app, "Setup Error", e.details()) diff --git a/daemon/core/gui/dialogs/error.py b/daemon/core/gui/dialogs/error.py index 7fb81077a..9d215e82b 100644 --- a/daemon/core/gui/dialogs/error.py +++ b/daemon/core/gui/dialogs/error.py @@ -1,9 +1,10 @@ +import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images -from core.gui.themes import FRAME_PAD, PADX, PADY +from core.gui.themes import PADY from core.gui.widgets import CodeText if TYPE_CHECKING: @@ -21,21 +22,15 @@ def __init__(self, app: "Application", title: str, details: str) -> None: def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) - - frame = ttk.Frame(self.top, padding=FRAME_PAD) - frame.grid(pady=PADY, sticky="ew") - frame.columnconfigure(1, weight=1) - image = Images.get(ImageEnum.ERROR, 36) - label = ttk.Label(frame, image=image) + image = Images.get(ImageEnum.ERROR, 24) + label = ttk.Label( + self.top, text=self.title, image=image, compound=tk.LEFT, anchor=tk.CENTER + ) label.image = image - label.grid(row=0, column=0, padx=PADX) - label = ttk.Label(frame, text=self.title) - label.grid(row=0, column=1, sticky="ew") - + label.grid(sticky=tk.EW, pady=PADY) self.error_message = CodeText(self.top) self.error_message.text.insert("1.0", self.details) - self.error_message.text.config(state="disabled") - self.error_message.grid(sticky="nsew", pady=PADY) - + self.error_message.text.config(state=tk.DISABLED) + self.error_message.grid(sticky=tk.NSEW, pady=PADY) button = ttk.Button(self.top, text="Close", command=lambda: self.destroy()) - button.grid(sticky="ew") + button.grid(sticky=tk.EW) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 1ed7c684b..79a4e6216 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -40,14 +40,14 @@ def main(): # create node one position = Position(x=100, y=100) - node1 = Node(type=NodeType.DEFAULT, position=position) + node1 = Node(type=NodeType.DEFAULT, position=position, model="PC") response = core.add_node(session_id, node1) logging.info("created node: %s", response) node1_id = response.node_id # create node two position = Position(x=300, y=100) - node2 = Node(type=NodeType.DEFAULT, position=position) + node2 = Node(type=NodeType.DEFAULT, position=position, model="PC") response = core.add_node(session_id, node2) logging.info("created node: %s", response) node2_id = response.node_id diff --git a/daemon/scripts/core-pygui b/daemon/scripts/core-pygui index 46860ce93..888f4171e 100755 --- a/daemon/scripts/core-pygui +++ b/daemon/scripts/core-pygui @@ -13,6 +13,7 @@ if __name__ == "__main__": parser.add_argument("-l", "--level", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO", help="logging level") parser.add_argument("-p", "--proxy", action="store_true", help="enable proxy") + parser.add_argument("-s", "--session", type=int, help="session id to join") args = parser.parse_args() # check home directory exists and create if necessary @@ -28,5 +29,5 @@ if __name__ == "__main__": # start app Images.load_all() - app = Application(args.proxy) + app = Application(args.proxy, args.session) app.mainloop() From f8d862a296d995b242e86a1decb25d90587f5f6e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 19:19:22 -0700 Subject: [PATCH 207/210] grpc/pygui: added grpc alert api, updated pygui to better handle and display alerts --- daemon/core/api/grpc/client.py | 17 +++++++++++++ daemon/core/api/grpc/server.py | 16 +++++++++++- daemon/core/gui/coreclient.py | 2 +- daemon/core/gui/dialogs/alerts.py | 13 ++++++---- daemon/core/gui/statusbar.py | 25 +++++++++++++++++-- daemon/core/gui/themes.py | 35 ++++++++++++--------------- daemon/proto/core/api/grpc/core.proto | 14 +++++++++++ 7 files changed, 94 insertions(+), 28 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 3e974233d..aacfa4f62 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -445,6 +445,23 @@ def add_session_server( ) return self.stub.AddSessionServer(request) + def alert( + self, + session_id: int, + level: core_pb2.ExceptionLevel, + source: str, + text: str, + node_id: int = None, + ) -> core_pb2.SessionAlertResponse: + request = core_pb2.SessionAlertRequest( + session_id=session_id, + level=level, + source=source, + text=text, + node_id=node_id, + ) + return self.stub.SessionAlert(request) + def events( self, session_id: int, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index da2d53c32..4c2048451 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -109,7 +109,12 @@ ) from core.emulator.coreemu import CoreEmu from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions -from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags +from core.emulator.enumerations import ( + EventTypes, + ExceptionLevels, + LinkTypes, + MessageFlags, +) from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility @@ -584,6 +589,15 @@ def AddSessionServer( session.distributed.add_server(request.name, request.host) return core_pb2.AddSessionServerResponse(result=True) + def SessionAlert( + self, request: core_pb2.SessionAlertRequest, context: ServicerContext + ) -> core_pb2.SessionAlertResponse: + session = self.get_session(request.session_id, context) + level = ExceptionLevels(request.level) + node_id = request.node_id if request.node_id else None + session.exception(level, request.source, request.text, node_id) + return core_pb2.SessionAlertResponse(result=True) + def Events(self, request: core_pb2.EventsRequest, context: ServicerContext) -> None: session = self.get_session(request.session_id, context) event_types = set(request.events) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 26a5a3907..8474b3cb3 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -271,7 +271,7 @@ def handle_throughputs(self, event: ThroughputsEvent) -> None: def handle_exception_event(self, event: ExceptionEvent) -> None: logging.info("exception event: %s", event) - self.app.statusbar.core_alarms.append(event) + self.app.statusbar.add_alert(event) def join_session(self, session_id: int, query_location: bool = True) -> None: logging.info("join session(%s)", session_id) diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index 00ef1e8c6..8e0aa02e9 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -52,6 +52,7 @@ def draw(self) -> None: for alarm in self.app.statusbar.core_alarms: exception = alarm.exception_event level_name = ExceptionLevel.Enum.Name(exception.level) + node_id = exception.node_id if exception.node_id else "" insert_id = self.tree.insert( "", tk.END, @@ -60,7 +61,7 @@ def draw(self) -> None: exception.date, level_name, alarm.session_id, - exception.node_id, + node_id, exception.source, ), tags=(level_name,), @@ -98,15 +99,17 @@ def draw(self) -> None: button.grid(row=0, column=1, sticky="ew") def reset_alerts(self) -> None: - self.codetext.text.delete("1.0", tk.END) + self.codetext.text.config(state=tk.NORMAL) + self.codetext.text.delete(1.0, tk.END) + self.codetext.text.config(state=tk.DISABLED) for item in self.tree.get_children(): self.tree.delete(item) - self.app.statusbar.core_alarms.clear() + self.app.statusbar.clear_alerts() def click_select(self, event: tk.Event) -> None: current = self.tree.selection()[0] alarm = self.alarm_map[current] self.codetext.text.config(state=tk.NORMAL) - self.codetext.text.delete("1.0", "end") - self.codetext.text.insert("1.0", alarm.exception_event.text) + self.codetext.text.delete(1.0, tk.END) + self.codetext.text.insert(1.0, alarm.exception_event.text) self.codetext.text.config(state=tk.DISABLED) diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 2b597b63d..67da0efae 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -5,7 +5,7 @@ from tkinter import ttk from typing import TYPE_CHECKING, List, Optional -from core.api.grpc.core_pb2 import ExceptionEvent +from core.api.grpc.core_pb2 import ExceptionEvent, ExceptionLevel from core.gui.dialogs.alerts import AlertsDialog from core.gui.themes import Styles @@ -22,6 +22,7 @@ def __init__(self, master: tk.Widget, app: "Application") -> None: self.zoom: Optional[ttk.Label] = None self.cpu_usage: Optional[ttk.Label] = None self.alerts_button: Optional[ttk.Button] = None + self.alert_style = Styles.no_alert self.running: bool = False self.core_alarms: List[ExceptionEvent] = [] self.draw() @@ -60,10 +61,30 @@ def draw(self) -> None: self.cpu_usage.grid(row=0, column=2, sticky="ew") self.alerts_button = ttk.Button( - self, text="Alerts", command=self.click_alerts, style=Styles.green_alert + self, text="Alerts", command=self.click_alerts, style=self.alert_style ) self.alerts_button.grid(row=0, column=3, sticky="ew") + def add_alert(self, event: ExceptionEvent) -> None: + self.core_alarms.append(event) + level = event.exception_event.level + self._set_alert_style(level) + label = f"Alerts ({len(self.core_alarms)})" + self.alerts_button.config(text=label, style=self.alert_style) + + def _set_alert_style(self, level: ExceptionLevel) -> None: + if level in [ExceptionLevel.FATAL, ExceptionLevel.ERROR]: + self.alert_style = Styles.red_alert + elif level == ExceptionLevel.WARNING and self.alert_style != Styles.red_alert: + self.alert_style = Styles.yellow_alert + elif self.alert_style == Styles.no_alert: + self.alert_style = Styles.green_alert + + def clear_alerts(self): + self.core_alarms.clear() + self.alert_style = Styles.no_alert + self.alerts_button.config(text="Alerts", style=self.alert_style) + def click_alerts(self) -> None: dialog = AlertsDialog(self.app) dialog.show() diff --git a/daemon/core/gui/themes.py b/daemon/core/gui/themes.py index 93a0a5991..45b109f0c 100644 --- a/daemon/core/gui/themes.py +++ b/daemon/core/gui/themes.py @@ -14,6 +14,7 @@ class Styles: tooltip_frame: str = "Tooltip.TFrame" service_checkbutton: str = "Service.TCheckbutton" picker_button: str = "Picker.TButton" + no_alert: str = "NAlert.TButton" green_alert: str = "GAlert.TButton" red_alert: str = "RAlert.TButton" yellow_alert: str = "YAlert.TButton" @@ -175,33 +176,29 @@ def style_listbox(widget: tk.Widget) -> None: ) -def theme_change(event: tk.Event) -> None: - style = ttk.Style() - style.configure(Styles.picker_button, font="TkSmallCaptionFont") - style.configure( - Styles.green_alert, - background="green", - padding=0, - relief=tk.RIDGE, - borderwidth=1, - font="TkDefaultFont", - ) +def _alert_style(style: ttk.Style, name: str, background: str): style.configure( - Styles.yellow_alert, - background="yellow", + name, + background=background, padding=0, relief=tk.RIDGE, borderwidth=1, font="TkDefaultFont", + foreground="black", + highlightbackground="white", ) + style.map(name, background=[("!active", background), ("active", "white")]) + + +def theme_change(event: tk.Event) -> None: + style = ttk.Style() + style.configure(Styles.picker_button, font="TkSmallCaptionFont") style.configure( - Styles.red_alert, - background="red", - padding=0, - relief=tk.RIDGE, - borderwidth=1, - font="TkDefaultFont", + Styles.no_alert, padding=0, relief=tk.RIDGE, borderwidth=1, font="TkDefaultFont" ) + _alert_style(style, Styles.green_alert, "green") + _alert_style(style, Styles.yellow_alert, "yellow") + _alert_style(style, Styles.red_alert, "red") def scale_fonts(fonts_size: Dict[str, int], scale: float) -> None: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 5ca4812c4..eb889d14b 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -43,6 +43,8 @@ service CoreApi { } rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { } + rpc SessionAlert (SessionAlertRequest) returns (SessionAlertResponse) { + } // streams rpc Events (EventsRequest) returns (stream Event) { @@ -318,6 +320,18 @@ message AddSessionServerResponse { bool result = 1; } +message SessionAlertRequest { + int32 session_id = 1; + ExceptionLevel.Enum level = 2; + string source = 3; + string text = 4; + int32 node_id = 5; +} + +message SessionAlertResponse { + bool result = 1; +} + message EventsRequest { int32 session_id = 1; repeated EventType.Enum events = 2; From 3544d004317b5ddbc5053e8f38c09e005fd582e7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 22 Jul 2020 21:57:05 -0700 Subject: [PATCH 208/210] pygui: implemented cpu usage monitor to status bar --- daemon/core/gui/graph/graph.py | 3 +- daemon/core/gui/statusbar.py | 64 ++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 9cb3b1097..56a31c3f6 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -590,8 +590,7 @@ def zoom(self, event: tk.Event, factor: float = None) -> None: ) logging.debug("ratio: %s", self.ratio) logging.debug("offset: %s", self.offset) - zoom_label = f"{self.ratio * 100:.0f}%" - self.app.statusbar.zoom.config(text=zoom_label) + self.app.statusbar.set_zoom(self.ratio) if self.wallpaper: self.redraw_wallpaper() diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 67da0efae..e9fc03b2c 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -1,7 +1,10 @@ """ status bar """ +import sched import tkinter as tk +from pathlib import Path +from threading import Thread from tkinter import ttk from typing import TYPE_CHECKING, List, Optional @@ -13,6 +16,41 @@ from core.gui.app import Application +class CpuUsage: + def __init__(self, statusbar: "StatusBar") -> None: + self.scheduler: sched.scheduler = sched.scheduler() + self.running: bool = False + self.thread: Optional[Thread] = None + self.prev_idle: int = 0 + self.prev_total: int = 0 + self.stat_file: Path = Path("/proc/stat") + self.statusbar: "StatusBar" = statusbar + + def start(self) -> None: + self.running = True + self.thread = Thread(target=self._start, daemon=True) + self.thread.start() + + def _start(self): + self.scheduler.enter(0, 0, self.run) + self.scheduler.run() + + def run(self) -> None: + lines = self.stat_file.read_text().splitlines()[0] + values = [int(x) for x in lines.split()[1:]] + idle = sum(values[3:5]) + non_idle = sum(values[:3] + values[5:8]) + total = idle + non_idle + total_diff = total - self.prev_total + idle_diff = idle - self.prev_idle + cpu_percent = (total_diff - idle_diff) / total_diff + self.statusbar.after(0, self.statusbar.set_cpu, cpu_percent) + self.prev_idle = idle + self.prev_total = total + if self.running: + self.scheduler.enter(3, 0, self.run) + + class StatusBar(ttk.Frame): def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) @@ -20,12 +58,14 @@ def __init__(self, master: tk.Widget, app: "Application") -> None: self.status: Optional[ttk.Label] = None self.statusvar: tk.StringVar = tk.StringVar() self.zoom: Optional[ttk.Label] = None - self.cpu_usage: Optional[ttk.Label] = None + self.cpu_label: Optional[ttk.Label] = None self.alerts_button: Optional[ttk.Button] = None self.alert_style = Styles.no_alert self.running: bool = False self.core_alarms: List[ExceptionEvent] = [] self.draw() + self.cpu_usage: CpuUsage = CpuUsage(self) + self.cpu_usage.start() def draw(self) -> None: self.columnconfigure(0, weight=7) @@ -46,25 +86,27 @@ def draw(self) -> None: ) self.status.grid(row=0, column=0, sticky="ew") - self.zoom = ttk.Label( - self, - text="%s" % (int(self.app.canvas.ratio * 100)) + "%", - anchor=tk.CENTER, - borderwidth=1, - relief=tk.RIDGE, - ) + self.zoom = ttk.Label(self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE) self.zoom.grid(row=0, column=1, sticky="ew") + self.set_zoom(self.app.canvas.ratio) - self.cpu_usage = ttk.Label( - self, text="CPU TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE + self.cpu_label = ttk.Label( + self, anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE ) - self.cpu_usage.grid(row=0, column=2, sticky="ew") + self.cpu_label.grid(row=0, column=2, sticky="ew") + self.set_cpu(0.0) self.alerts_button = ttk.Button( self, text="Alerts", command=self.click_alerts, style=self.alert_style ) self.alerts_button.grid(row=0, column=3, sticky="ew") + def set_cpu(self, usage: float) -> None: + self.cpu_label.config(text=f"CPU {usage * 100:.2f}%") + + def set_zoom(self, zoom: float) -> None: + self.zoom.config(text=f"ZOOM {zoom * 100:.0f}%") + def add_alert(self, event: ExceptionEvent) -> None: self.core_alarms.append(event) level = event.exception_event.level From fff4bd796358797e3b88babc9f005055d035d949 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 23 Jul 2020 09:41:39 -0700 Subject: [PATCH 209/210] moved cpu usage to a grpc call that the gui will listen to, fixed grpc stream typing to be grpc.Future, fixed pygui issue for start callback when a start fails, but there are no exceptions --- daemon/core/api/grpc/client.py | 19 +++++++++++-- daemon/core/api/grpc/grpcutils.py | 20 ++++++++++++++ daemon/core/api/grpc/server.py | 9 ++++++ daemon/core/gui/coreclient.py | 27 ++++++++++++++++-- daemon/core/gui/statusbar.py | 40 --------------------------- daemon/core/gui/toolbar.py | 7 +++-- daemon/proto/core/api/grpc/core.proto | 10 +++++++ 7 files changed, 85 insertions(+), 47 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index aacfa4f62..0674a0ebd 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -467,7 +467,7 @@ def events( session_id: int, handler: Callable[[core_pb2.Event], None], events: List[core_pb2.Event] = None, - ) -> grpc.Channel: + ) -> grpc.Future: """ Listen for session events. @@ -484,7 +484,7 @@ def events( def throughputs( self, session_id: int, handler: Callable[[core_pb2.ThroughputsEvent], None] - ) -> grpc.Channel: + ) -> grpc.Future: """ Listen for throughput events with information for interfaces and bridges. @@ -498,6 +498,21 @@ def throughputs( start_streamer(stream, handler) return stream + def cpu_usage( + self, delay: int, handler: Callable[[core_pb2.CpuUsageEvent], None] + ) -> grpc.Future: + """ + Listen for cpu usage events with the given repeat delay. + + :param delay: delay between receiving events + :param handler: handler for every event + :return: stream processing events, can be used to cancel stream + """ + request = core_pb2.CpuUsageRequest(delay=delay) + stream = self.stub.CpuUsage(request) + start_streamer(stream, handler) + return stream + def add_node( self, session_id: int, node: core_pb2.Node, source: str = None ) -> core_pb2.AddNodeResponse: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index bd3519f77..84b8ee6a0 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -1,5 +1,6 @@ import logging import time +from pathlib import Path from typing import Any, Dict, List, Tuple, Type, Union import grpc @@ -20,6 +21,25 @@ WORKERS = 10 +class CpuUsage: + def __init__(self) -> None: + self.stat_file: Path = Path("/proc/stat") + self.prev_idle: int = 0 + self.prev_total: int = 0 + + def run(self) -> float: + lines = self.stat_file.read_text().splitlines()[0] + values = [int(x) for x in lines.split()[1:]] + idle = sum(values[3:5]) + non_idle = sum(values[:3] + values[5:8]) + total = idle + non_idle + total_diff = total - self.prev_total + idle_diff = idle - self.prev_idle + self.prev_idle = idle + self.prev_total = total + return (total_diff - idle_diff) / total_diff + + def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]: """ Convert node protobuf message to data for creating a node. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4c2048451..38100e055 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -681,6 +681,15 @@ def Throughputs( last_stats = stats time.sleep(delay) + def CpuUsage( + self, request: core_pb2.CpuUsageRequest, context: ServicerContext + ) -> None: + cpu_usage = grpcutils.CpuUsage() + while self._is_running(context): + usage = cpu_usage.run() + yield core_pb2.CpuUsageEvent(usage=usage) + time.sleep(request.delay) + def AddNode( self, request: core_pb2.AddNodeRequest, context: ServicerContext ) -> core_pb2.AddNodeResponse: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8474b3cb3..fc0bd5209 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -16,6 +16,7 @@ from core.api.grpc.common_pb2 import ConfigOption from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig from core.api.grpc.core_pb2 import ( + CpuUsageEvent, Event, ExceptionEvent, Hook, @@ -55,6 +56,7 @@ from core.gui.app import Application GUI_SOURCE = "gui" +CPU_USAGE_DELAY = 3 class CoreClient: @@ -92,8 +94,9 @@ def __init__(self, app: "Application", proxy: bool) -> None: self.hooks: Dict[str, Hook] = {} self.emane_config: Dict[str, ConfigOption] = {} self.mobility_players: Dict[int, MobilityPlayer] = {} - self.handling_throughputs: Optional[grpc.Channel] = None - self.handling_events: Optional[grpc.Channel] = None + self.handling_throughputs: Optional[grpc.Future] = None + self.handling_cpu_usage: Optional[grpc.Future] = None + self.handling_events: Optional[grpc.Future] = None self.xml_dir: Optional[str] = None self.xml_file: Optional[str] = None @@ -111,6 +114,7 @@ def client(self) -> client.CoreGrpcClient: ) if throughputs_enabled: self.enable_throughputs() + self.setup_cpu_usage() return self._client def reset(self) -> None: @@ -258,6 +262,20 @@ def cancel_events(self) -> None: self.handling_events.cancel() self.handling_events = None + def cancel_cpu_usage(self) -> None: + if self.handling_cpu_usage: + self.handling_cpu_usage.cancel() + self.handling_cpu_usage = None + + def setup_cpu_usage(self) -> None: + if self.handling_cpu_usage and self.handling_cpu_usage.running(): + return + if self.handling_cpu_usage: + self.handling_cpu_usage.cancel() + self.handling_cpu_usage = self._client.cpu_usage( + CPU_USAGE_DELAY, self.handle_cpu_event + ) + def handle_throughputs(self, event: ThroughputsEvent) -> None: if event.session_id != self.session_id: logging.warning( @@ -269,6 +287,9 @@ def handle_throughputs(self, event: ThroughputsEvent) -> None: logging.debug("handling throughputs event: %s", event) self.app.after(0, self.app.canvas.set_throughputs, event) + def handle_cpu_event(self, event: CpuUsageEvent) -> None: + self.app.after(0, self.app.statusbar.set_cpu, event.usage) + def handle_exception_event(self, event: ExceptionEvent) -> None: logging.info("exception event: %s", event) self.app.statusbar.add_alert(event) @@ -479,6 +500,8 @@ def setup(self, session_id: int = None) -> None: """ try: self.client.connect() + self.setup_cpu_usage() + # get service information response = self.client.get_services() for service in response.services: diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index e9fc03b2c..6989593e5 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -1,10 +1,7 @@ """ status bar """ -import sched import tkinter as tk -from pathlib import Path -from threading import Thread from tkinter import ttk from typing import TYPE_CHECKING, List, Optional @@ -16,41 +13,6 @@ from core.gui.app import Application -class CpuUsage: - def __init__(self, statusbar: "StatusBar") -> None: - self.scheduler: sched.scheduler = sched.scheduler() - self.running: bool = False - self.thread: Optional[Thread] = None - self.prev_idle: int = 0 - self.prev_total: int = 0 - self.stat_file: Path = Path("/proc/stat") - self.statusbar: "StatusBar" = statusbar - - def start(self) -> None: - self.running = True - self.thread = Thread(target=self._start, daemon=True) - self.thread.start() - - def _start(self): - self.scheduler.enter(0, 0, self.run) - self.scheduler.run() - - def run(self) -> None: - lines = self.stat_file.read_text().splitlines()[0] - values = [int(x) for x in lines.split()[1:]] - idle = sum(values[3:5]) - non_idle = sum(values[:3] + values[5:8]) - total = idle + non_idle - total_diff = total - self.prev_total - idle_diff = idle - self.prev_idle - cpu_percent = (total_diff - idle_diff) / total_diff - self.statusbar.after(0, self.statusbar.set_cpu, cpu_percent) - self.prev_idle = idle - self.prev_total = total - if self.running: - self.scheduler.enter(3, 0, self.run) - - class StatusBar(ttk.Frame): def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) @@ -64,8 +26,6 @@ def __init__(self, master: tk.Widget, app: "Application") -> None: self.running: bool = False self.core_alarms: List[ExceptionEvent] = [] self.draw() - self.cpu_usage: CpuUsage = CpuUsage(self) - self.cpu_usage.start() def draw(self) -> None: self.columnconfigure(0, weight=7) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 406a88ca1..968b447d7 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -305,10 +305,11 @@ def start_callback(self, response: core_pb2.StartSessionResponse) -> None: self.set_runtime() self.app.core.set_metadata() self.app.core.show_mobility_players() - elif response.exceptions: + else: enable_buttons(self.design_frame, enabled=True) - message = "\n".join(response.exceptions) - self.app.show_error("Start Session Error", message) + if response.exceptions: + message = "\n".join(response.exceptions) + self.app.show_error("Start Session Error", message) def set_runtime(self) -> None: enable_buttons(self.runtime_frame, enabled=True) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index eb889d14b..9214ad1b8 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -51,6 +51,8 @@ service CoreApi { } rpc Throughputs (ThroughputsRequest) returns (stream ThroughputsEvent) { } + rpc CpuUsage (CpuUsageRequest) returns (stream CpuUsageEvent) { + } // node rpc rpc AddNode (AddNodeRequest) returns (AddNodeResponse) { @@ -347,6 +349,14 @@ message ThroughputsEvent { repeated InterfaceThroughput iface_throughputs = 3; } +message CpuUsageRequest { + int32 delay = 1; +} + +message CpuUsageEvent { + double usage = 1; +} + message InterfaceThroughput { int32 node_id = 1; int32 iface_id = 2; From ba3a2474957394b196b47e79432daf6fc5e77cf1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 23 Jul 2020 21:21:43 -0700 Subject: [PATCH 210/210] updated changelog for 7.0.0 --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f7b30ac..375a76079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +## 2020-07-23 CORE 7.0.0 + +* Breaking Changes + * core.emudata and core.data combined and cleaned up into core.data + * updates to consistently use mac instead of hwaddr/mac + * \#468 - code related to adding/editing/deleting links cleaned up + * \#469 - usages of per all changed to loss to be consistent + * \#470 - variables with numbered names now use numbers directly + * \#471 - node startup is no longer embedded within its constructor + * \#472 - code updated to refer to interfaces consistently as iface + * \#475 - code updates changing how ip addresses are stored on interfaces + * \#476 - executables to check for moved into own module core.executables + * \#486 - core will now install into its own python virtual environment managed by poetry +* core-daemon + * updates to properly save/load distributed servers to xml + * \#474 - added type hinting to all service files + * \#478 - fixed typo in config service directory + * \#479 - opening an xml file will now cycle through states like a normal session + * \#480 - ovs configuration will now save/load from xml and display in guis + * \#484 - changes to support adding emane links during runtime +* core-pygui + * fixed issue not displaying services for the default group in service dialogs + * fixed issue starting a session when the daemon is not present + * fixed issue attempting to open terminals for invalid nodes + * fixed issue syncing session location + * fixed issue joining a session with mobility, not in runtime + * added cpu usage monitor to status bar + * emane configurations can now be seen during runtime + * rj45 nodes can only have one link + * disabling throughputs will clear labels + * improvements to custom service copy + * link options will now be drawn on as a label + * updates to handle runtime link events + * \#477 - added optional details pane for a quick view of node/link details + * \#485 - pygui fixed observer widget for invalid nodes + * \#496 - improved alert handling +* core-gui + * \#493 - increased frame size to show all emane configuration options +* gRPC API + * added set session user rpc + * added cpu usage stream + * interface objects returned from get_node will now provide node_id, net_id, and net2_id data + * peer to peer nodes will not be included in get_session calls + * pathloss events will now throw an error when nem id not found + * \#481 - link rpc calls will broadcast out + * \#496 - added alert rpc call +* Services + * fixed issue reading files in security services + * \#494 - add staticd to daemons list for frr services + ## 2020-06-11 CORE 6.5.0 * Breaking Changes * CoreNode.newnetif - both parameters are required and now takes an InterfaceData object as its second parameter