diff --git a/.editorconfig b/.editorconfig index f1b09263..d0466b2f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,6 @@ insert_final_newline = true [*.py] indent_style = space indent_size = 4 -max_line_length = 88 [*.am] indent_style = tab diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 6e3ab3c1..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[BUG]" -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. Ubuntu 18.04] - - CORE Version [e.g. 5.2.1] - - EMANE Version [e.g. 1.2.4] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index f4558654..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "[FEATURE]" -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml deleted file mode 100644 index dc169dcf..00000000 --- a/.github/workflows/daemon-checks.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Daemon Checks - -on: [push] - -jobs: - build: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v1 - - name: Set up Python 3.9 - uses: actions/setup-python@v1 - with: - python-version: 3.9 - - name: install poetry - run: | - python -m pip install --upgrade pip - pip install poetry - cd daemon - cp core/constants.py.in core/constants.py - sed -i 's/required=True/required=False/g' core/emulator/coreemu.py - poetry install - - name: isort - run: | - cd daemon - poetry run isort -c -df - - name: black - run: | - cd daemon - poetry run black --check . - - name: flake8 - run: | - cd daemon - poetry run flake8 - - name: grpc - run: | - cd daemon/proto - poetry run python -m grpc_tools.protoc -I . --python_out=.. --grpc_python_out=.. core/api/grpc/*.proto - - name: test - run: | - cd daemon - poetry run pytest --mock tests diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index abbadab3..00000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: documentation -on: - push: - branches: - - master -permissions: - contents: write -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.x - - uses: actions/cache@v2 - with: - key: ${{ github.ref }} - path: .cache - - run: pip install mkdocs-material - - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index ca4c07dd..c59824e9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,22 +14,14 @@ config.h.in config.log config.status configure -configure~ debian stamp-h1 -# python virtual environments -venv - -# generated protobuf files -*_pb2.py -*_pb2_grpc.py - -# python build directory +# python dist - -# vscode -.vscode +*.egg-info +.cache +*.pyc # intellij *.iml @@ -42,26 +34,14 @@ dist coverage.xml # python files -*.egg-info -*.pyc # ignore package files *.rpm *.deb *.tar.gz -# pytest cache files -.cache - # ignore swap files *.swp -# ignore built input files -netns/setup.py -daemon/setup.py - -# python -__pycache__ - -# ignore core player files -*.core +# java files +target diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 425f2ae0..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,977 +0,0 @@ -## 2023-08-01 CORE 9.0.3 - -* Installation - * updated various dependencies -* Documentation - * improved GUI docs to include node interaction and note xhost usage - * \#780 - fixed gRPC examples - * \#787 - complete documentation revamp to leverage mkdocs material - * \#790 - fixed custom emane model example -* core-daemon - * update type hinting to avoid deprecated imports - * updated commands ran within docker based nodes to have proper environment variables - * fixed issue improperly setting session options over gRPC - * \#668 - add fedora sbin path to frr service - * \#774 - fixed pcap configservice - * \#805 - fixed radvd configservice template error -* core-gui - * update type hinting to avoid deprecated imports - * fixed issue allowing duplicate named hook scripts - * fixed issue joining sessions with RJ45 nodes -* utility scripts - * fixed issue in core-cleanup for removing devices - -## 2023-03-02 CORE 9.0.2 - -* Installation - * updated python dependencies, including invoke to resolve python 3.10+ issues - * improved example dockerfiles to use less space for built images -* Documentation - * updated emane install instructions - * added Docker related issues to install instructions -* core-daemon - * fixed issue using invalid device name in sysctl commands - * updated PTP nodes to properly disable mac learning for their linux bridge - * fixed issue for LXC nodes to properly use a configured image name and write it to XML - * \#742 - fixed issue with bad wlan node id being used - * \#744 - fixed issue not properly setting broadcast address -* core-gui - * fixed sample1.xml to remove SSH service - * fixed emane demo examples - * fixed issue displaying emane configs generally configured for a node - -## 2022-11-28 CORE 9.0.1 - -* Installation - * updated protobuf and grpcio-tools versions in pyproject.toml to account for bad version mix - -## 2022-11-18 CORE 9.0.0 - -* Breaking Changes - * removed session nodes file - * removed session state file - * emane now runs in one process per nem with unique control ports - * grpc client has been refactored and updated - * removed tcl/legacy gui, imn file support and the tlv api - * link configuration is now different, but consistent, for wired links -* Installation - * added packaging for single file distribution - * python3.9 is now the minimum required version - * updated Dockerfile examples - * updated various python dependencies - * virtual environment is now installed to /opt/core/venv -* Documentation - * updated emane invoke task examples - * revamped install documentation - * added wireless node notes -* core-gui - * updated config services to display rendered templated and allow editing - * fixed node icon issue when updating preferences - * \#89 - throughput widget now works for hubs/switches - * \#691 - fixed custom nodes to properly use config services -* gRPC API - * add linked call to support linking and unlinking interfaces without destroying them - * fixed issue during start session clearing out session options - * added call to get rendered config service files - * removed get_node_links from links from client - * nem id and nem port have been added to GetNode and AddLink calls -* core-daemon - * wired links always create two veth pairs joined by a bridge - * node interfaces are now configured within the container to apply to outgoing traffic - * session.add_node now uses NodeOptions, allowing for node specific options - * fixed issue with xml reading node canvas values - * removed Session.add_node_file - * fixed get requirements logic - * fixed docker/lxd node support terminal commands on remote servers - * improved docker node command execution time using nsenter - * new wireless node type added to support dynamic loss based on distance - * \#513 - add and deleting distributed links during runtime is now supported - * \#703 - fixed issue not starting emane event listening service - -## 2022-03-21 CORE 8.2.0 - -* core-gui - * improved failed starts to trigger runtime to allow node investigation -* core-daemon - * improved default service loading to use a full import path - * updated session instantiation to always set to a runtime state -* core-cli - * \#672 - fixed xml loading - * \#578 - restored json flag and added geo output to session overview -* Documentation - * updated emane example and documentation - * improved table markdown - -## 2022-02-18 CORE 8.1.0 - -* Installation - * updated dependency versions to account for known vulnerabilities -* GUI - * fixed issue drawing asymmetric link configurations when joining a session -* daemon - * fixed issue getting templates and creating files for config services - * added by directional support for network to network links - * \#647 - fixed issue when creating RJ45 nodes - * \#646 - fixed issue when creating files for Docker nodes - * \#645 - improved wlan change updates to account for all updates with no delay -* services - * fixed file generation for OSPFv2 config service - -## 2022-01-12 CORE 8.0.0 - -*Breaking Changes - * heavily refactored gRPC client, removing some calls, adding others, all using type hinted classes representing their protobuf counterparts - * emane adjustments to run each nem in its own process, includes adjustments to configuration, which may cause issues - * internal daemon cleanup and refactoring, in a script directly driving a scenario is used -* Installation - * added options to allow installation without ospf mdr - * removed tasks that are no longer needed - * updates to properly install/remove example files - * pipx/poetry/invoke versions are now locked to help avoid update related issues - * install.sh is now setup.sh and is a convenience to get tool setup to run invoke -* Documentation - * formally added notes for Docker and LXD based node types - * added config services - * Updated README to have quick notes for installation - * \#563 - update to note how to enable core service -* Examples - * \#598 - update to fix sample1.imn to working order -* core-daemon - * emane global configuration is now configurable per nem - * fixed wlan loss to support float values - * improved default service loading to use full core path - * improved emane model loading to occur one time - * fixed handling rj45 link edits from tlv api - * fixed wlan config getting a default value for the promiscuous setting when not provided - * ebtables usage has now been replaced with nftables - * \#564 - logging is now using module named loggers - * \#573 - emane processes are not created 1 to 1 with nems - * \#608 - update lxml version - * \#609 - update pyyaml version - * \#623 - fixed issue with ovs mode and mac learning -* core-gui - * config services are now the default service type - * legacy services are marked as deprecated - * fix to properly load session options - * logging is now using module named loggers - * save as will not update the current session file name as expected - * fix to properly clear out removed customized services - * adding directories to a service that do not exist, is now valid - * added flag to exit after creating gui directory from command line - * added new options to enable/disable ip4/ip6 assignment - * improved canvas draw order, when joining sessions - * improved node copy/paste to avoid issues when pasting text into service config dialogs - * each canvas will not correctly save and load their size from xml -* gRPC API - * session options are now returned for GetSession - * fixed issue not properly creating the session directory during start session definition state - * updates to separate editing a node and moving a node, new MoveNode call added, EditNode is now used for editing icons -* Services - * fixed default route config service - * config services now have options for shadowing directories, including per node customization - -## 2021-09-17 CORE 7.5.2 - -* Installation - * \#596 - fixes issue related to installing poetry by pinning version to 1.1.7 - * updates pipx installation to pinned version 0.16.4 -* core-daemon - * \#600 - fixes known vulnerability for pillow dependency by updating version - -## 2021-04-15 CORE 7.5.1 - -* core-pygui - * fixed issues creating and drawing custom nodes - -## 2021-03-11 CORE 7.5.0 - -* core-daemon - * fixed issue setting mobility loop value properly - * fixed issue that some states would not properly remove session directories - * \#560 - fixed issues with sdt integration for mobility movement and layer creation -* core-pygui - * added multiple canvas support - * added support to hide nodes and restore them visually - * update to assign full netmasks to wireless connected nodes by default - * update to display services and action controls for nodes during runtime - * fixed issues with custom nodes - * fixed issue auto assigning macs, avoiding duplication - * fixed issue joining session with different netmasks - * fixed issues when deleting a session from the sessions dialog - * \#550 - fixed issue not sending all service customization data -* core-cli - * added delete session command - -## 2021-01-11 CORE 7.4.0 - -* Installation - * fixed issue for automated install assuming ID_LIKE is always present in /etc/os-release -* gRPC API - * fixed issue stopping session and not properly going to data collect state - * fixed issue to have start session properly create a directory before configuration state -* core-pygui - * fixed issue handling deletion of wired link to a switch - * avoid saving edge metadata to xml when values are default - * fixed issue editing node mac addresses - * added support for configuring interface names - * fixed issue with potential node names to allow hyphens and remove under bars - * \#531 - fixed issue changing distributed nodes back to local -* core-daemon - * fixed issue to properly handle deleting links from a network to network node - * updated xml to support writing and reading link buffer configurations - * reverted change and removed mac learning from wlan, due to promiscuous like behavior - * fixed issue creating control interfaces when starting services - * fixed deadlock issue when clearing a session using sdt - * \#116 - fixed issue for wlans handling multiple mobility scripts at once - * \#539 - fixed issue in udp tlv api - -## 2020-12-02 CORE 7.3.0 - -* core-daemon - * fixed issue where emane global configuration was not being sent to core-gui - * updated controlnet names on host to be prefixed with ctrl - * fixed RJ45 link shutdown from core-gui causing an error - * fixed emane external transport xml generation - * \#517 - update to account for radvd required directory - * \#514 - support added for session specific environment files - * \#529 - updated to configure netem limit based on delay or user specified, requires kernel 3.3+ -* core-pygui - * fixed issue drawing wlan/emane link options when it should not have - * edge labels are now placed a set distance from nodes like original gui - * link color/width are now saved to xml files - * added support to configure buffer size for links - * \#525 - added support for multiple wired links between the same nodes - * \#526 - added option to hide/show links with 100% loss -* Documentation - * \#527 - typo in service documentation - * \#515 - added examples to docs for using EMANE features within a CORE context - -## 2020-09-29 CORE 7.2.1 - -* core-daemon - * fixed issue where shutting down sessions may not have removed session directories - * fixed issue with multiple emane interfaces on the same node not getting the right configuration -* Installation - * updated automated install to be a bit more robust for alternative distros - * added force install type to try and leverage a redhat/debian like install - * locked ospf mdr version installed to older commit to avoid issues with multiple interfaces on same node - -## 2020-09-15 CORE 7.2.0 - -* Installation - * locked down version of ospf-mdr installed in automated install - * locked down version of emane to v1.2.5 in automated emane install - * added option to install locally using the -l option -* core-daemon - * improve error when retrieving services that do not exist, or failed to load - * fixed issue with writing/reading emane node interface configurations to xml - * fixed issue with not setting the emane model when creating a node - * added common utility method for getting a emane node interface config id in core.utils - * fixed issue running emane on more than one interface for a node - * fixed issue validating paths when creating emane transport xml for a node - * fixed issue avoiding multiple calls to shutdown, if already in shutdown state -* core-pygui - * fixed issue configuring emane for a node interface -* gRPC API - * added wrapper client that can provide type hinting and a simpler interface at core.api.grpc.clientw - * fixed issue creating sessions that default to having a very large reference scale - * fixed issue with GetSession returning control net nodes - -## 2020-08-21 CORE 7.1.0 - -* Installation - * added core-python script that gets installed to help globally reference the virtual environment -* gRPC API - * GetSession will now return all configuration information for a session and the file it was opened from, if applicable - * node update events will now include icon information - * fixed issue with getting session throughputs for sessions with a high id -* core-daemon - * \#503 - EMANE networks will now work with mobility again - * \#506 - fixed service dependency resolution issue - * fixed issue sending hooks to core-gui when joining session -* core-pygui - * fixed issues editing hooks - * fixed issue with cpu usage when joining a session - * fixed mac field not being disabled during runtime when configuring a node - * removed unlimited button from link config dialog - * fixed issue with copy/paste links and their options - * fixed issue with adding nodes/links and editing links during runtime - * updated open file dialog in config dialogs to open to ~/.coregui home directory - * fixed issue double clicking sessions dialog in invalid areas - * added display of asymmetric link options on links - * fixed emane config dialog display - * fixed issue saving backgrounds in xml files - * added view toggle for wired/wireless links - * node events will now update icons - -## 2020-07-28 CORE 7.0.1 - -* Bugfixes - * \#500 - fixed issue running node commands with shell=True - * fixed issue for poetry based install not properly vetting requirements for dataclasses dependency - -## 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 - * CoreNetworkBase.linkconfig - now takes a LinkOptions parameter instead of a subset of some of the options (ie bandwidth, delay, etc) - * \#453 - Session.add_node and Session.get_node now requires the node class you expect to create/retrieve - * \#458 - rj45 cleanup to only inherit from one class -* Enhancements - * fixed issues with handling bad commands for TLV execute messages - * removed unused boot.sh from CoreNode types - * added linkconfig to CoreNetworkBase and cleaned up function signature - * emane position hook now saves geo position to node - * emane pathloss support - * core.emulator.emudata leveraged dataclass and type hinting - * \#459 - updated transport type usage to an enum - * \#460 - updated network policy type usage to an enum -* Python GUI Enhancements - * fixed throughput events do not work for joined sessions - * fixed exiting app with a toolbar picker showing - * fixed issue with creating interfaces and reusing subnets after deletion - * fixed issue with moving text shapes - * fixed scaling with custom node selected - * fixed toolbar state switching issues - * enable/disable toolbar when running stop/start - * marker config integrated into toolbar - * improved color picker layout - * shapes can now be moved while drawing shapes - * added observers to toolbar in run mode -* gRPC API - * node events will now have geo positional data - * node geo data is now returned in get_session and get_node calls - * \#451 - added wlan link api to allow direct linking/unlinking of wireless links between nodes - * \#462 - added streaming call for sending node position/geo changes - * \#463 - added streaming call for emane pathloss events -* Bugfixes - * \#454 - fixed issue creating docker nodes, but containers are now required to have networking tools - * \#466 - fixed issue in python gui when xml file is loading nodes with no ip4 addresses - -## 2020-05-11 CORE 6.4.0 -* Enhancements - * updates to core-route-monitor, allow specific session, configurable settings, and properly - listen on all interfaces - * install.sh now has a "-r" option to help with reinstalling from current branch and installing - current python dependencies - * \#202 - enable OSPFv2 fast convergence - * \#178 - added comments to OVS service -* Python GUI Enhancements - * added initial documentation to help support usage - * supports drawing multiple links for wireless connections - * supports differentiating wireless networks with different colored links - * implemented unlink in node context menu to delete links to other nodes - * implemented node run tool dialog - * implemented find node dialog - * implemented address configuration dialog - * implemented mac configuration dialog - * updated link address creation to more closely mimic prior behavior - * updated configuration to use yaml class based configs - * implemented auto grid layout for nodes - * fixed drawn wlan ranges during configuration -* Bugfixes - * no longer writes link option data for WLAN/EMANE links in XML - * avoid configuring links for WLAN/EMANE link options in XML, due to them being written to XML prior - * updates to allow building python docs again - * \#431 - peer to peer node uplink link data was not using an enum properly due to code changes - * \#432 - loading XML was not setting EMANE nodes model - * \#435 - loading XML was not maintaining existing session options - * \#448 - fixed issue sorting hooks being saved to XML - -## 2020-04-13 CORE 6.3.0 -* Features - * \#424 - added FRR IS-IS service -* Enhancements - * \#414 - update GUI OSPFv2 adjacency widget to work with FRR - * \#416 - EMANE links can now be drawn for 80211 and RF Pipe models - * \#418 #409 - code cleanup - * \#425 - added route monitor script for SDT3D integration - * a formal error will now be thrown when EMANE binding are not installed, but attempted to be used - * node positions will now default to 0,0 to avoid GUI errors, when one is not provided - * improved SDT3D integration, multiple link support and usage of custom layers -* Python GUI Enhancements - * enabled edit menu delete - * cleaned up node context menu and enabled delete -* Bugfixes - * \#427 - fixed issue in default route service - * \#426 - fixed issue reading ipsec template file - * \#420 - fixed issue with TLV API udp handler - * \#411 - allow wlan to be configured with 0 values - * \#415 - general EMANE configuration was not being saved/loaded from XML - -## 2020-03-16 CORE 6.2.0 -* gRPC API - * Added call to execute python script -* Enhancements - * \#371 - improved coretk gui scaling - * \#374 - display range visually for wlan in coretk gui, when configuring - * \#377 - improved coretk error dialogs - * \#379 - fixed issues with core converting between x,y and lon,lat for values that would cross utm zones - * \#384 - sdt integration moved internally to core code allowing it to work for coretk gui as well - * \#387 - coretk gui will now auto detect potential valid terminal and command to use for interacting with nodes during runtime - * \#389 - coretk gui will now attempt to reconnect to daemon without need to restart - * \#395 - coretk gui now has "save" and "save as" menu options - * \#402 - coretk will now allow terminal preference to be directly edited -* Bugfixes - * \#375 - fixed issues with emane event monitor handling data - * \#381 - executing a python script will now wait until completion before looking to join a new session - * \#391 - fixed configuring node ip addresses in coretk gui - * \#392 - fixed coretk link display when addresses are cleared out - * \#393 - coretk gui will properly clear marker annotations when switching sessions - * \#396 - Docker and LXC nodes will now properly save to XML - * \#406- WLAN bridge initialization was not ran when all nodes are disconnected - -## 2020-02-20 CORE 6.1.0 -* New - * config services - these services leverage a proper template engine and have configurable parameters, given enough time may replace existing services - * core-imn-to-xml - IMN to XML utility script - * replaced internal code for determining ip/mac address with netaddr library -* Enhancements - * added distributed package for built packages - * made use of python type hinting for functions and their return values - * updated Quagga zebra service to remove deprecated warning -* Removed - * removed stale ns3 code -* CORETK GUI - * added logging - * improved error dialog - * properly use global ipv6 addresses for nodes - * disable proxy usage by default, flag available to enable -* gRPC API - * add_link - now returns created interface information - * set_node_service - can now set files and directories to properly replicate previous usage - * get_emane_event_channel - return information related to the currently used emane event channel -* Bugfixes - * fixed session SDT functionality back to working order, due to python3 changes - * avoid shutting down services for nodes that are not up - * EMANE bypass model options will now display properly in GUIs - * XML scenarios will now properly read in custom node icons - * \#372 - fixed mobility waypoint comparisons - * \#370 - fixed radvd service - * \#368 - updated frr services to properly start staticd when needed - * \#358 - fixed systemd service install path - * \#350 - fixed frr babel wireless configuration - * \#354 - updated frr to reset interfaces to properly take configurations - -## 2020-01-01 CORE 6.0.0 -* New - * beta release of the python based tk GUI, use **coretk-gui** to try it out, plan will be to eventually sunset the old GUI once this is good enough - * this GUI will allow us to provide enhancements and a consistent python dev environment for developers -* Major Changes - * python3.6+ support only, due to python2 EOL https://pyfound.blogspot.com/2019/12/python-2-sunset.html - * distributed sessions now leverages the fabric library for sending remote SSH commands -* Enhancements - * changed usage of bridge-utils to using ip based bridge commands due to deprecation - * installation.sh script to help automate a standard make install or dev install - * when sessions are created without an id they will now always start from 1 and return the next unused id - * gRPC is now running by default -* Session API - * removed **create_emane_network** and **create_wlan_network** to help force using **add_node** for all cases - * removed **session.master** as it was only used for previous distributed sessions - * updated **add_node** to allow providing a custom class for node creation -* gRPC API - * added get all services configurations - * added get all wlan configurations - * added start/stop session calls, provides more freedom for startup and shutdown logic - * session events now have a session id to help differentiate which session they are coming from - * throughput events now require a session id and responses include session id for differentiating data - * session events can now be subscribed to with a subset of events or all - * emane model config data now include interface ids properly - * sessions returned from get sessions call may include file names when created from xml - * when opening an xml the session can now be started or not - * edit node will now broadcast the edit for others to listen to - * all config responses will now be in the form of a mapped value of key to ConfigOption, or a list of these when retrieving all, sometimes the config response may be wrapped in a different message to include other metadata -* Bugfixes - * \#311 - initialize ebtables chains for wlan networks only - * \#312 - removed sudo from init script - * \#313 - check if interface exists before flushing, previously would log an exception that didn't matter - * \#314 - node locations stored as floats instead of ints to avoid mobility calculations due to loss of precision - * \#321 - python installation path will be based on distr ibution/python building it - * emane options xml parsing didn't properly take into account the **emane_prefix** configuration - * updates services that checked for ipv4/ipv6 addresses to not fail for valid ipv6 addresses with a decimal -* Documentation - * updated NRL links to new GitHub locations - * updates for distributed session - * updates to dev guide - * updates to examples LXD/Docker setup - * updates to FRR service documentation - * gRPC get node service file will not throw an exception when node doesn't exist - -## 2019-10-12 CORE 5.5.2 -* gRPC - * Added emane_link API for linking/unlinking EMANE nodes within the GUI -* Bugfixes - * Fixed python3 issues when configuring WLAN nodes - * Fixed issue due to refactoring when running distributed - * Fixed issue when running python script from GUI - -## 2019-10-09 CORE 5.5.1 -* Bugfix - * Fixed issue with 5.5.0 refactoring causing issues in python2. - * Fixed python3 issues with NRL services - -## 2019-10-03 CORE 5.5.0 -* Documentation - * updated dependencies for building OSPF MDR on installation page - * added python/pip instruction on installation page - * added ethtool dependency for CORE -* GUI - * removed experimental OVS node to avoid confusion and issues related to using it -* Daemon - * fixed core-daemon --ovs flag back to working order for running CORE using OVS bridges instead of Linux bridges - * updated requirements.txt to refer to configparser 4.0.2, due to 4.0.1 removal by developers - * update to fail fast for dependent executables that are not found within PATH - * update to not load services that fail during service.on_load and move on -* Build - * fixed issue with configure script when using option flags - * python install path will use the native install path for AM_PATH_PYTHON, instead of coercing to python3 -* Issues - * \#271 - OVS node error in GUI - * \#291 - configparser 4.0.1 issue - * \#290 - python3 path issue when building - -## 2019-09-23 CORE 5.4.0 -* Documentation - * Updates to documentation dev guide -* Improvements - * Added support for Pipenv for development - * Added configuration to leverage pre-commit during development - * Added configuration to leverage isort, black, and flake8 during development - * Added Github Actions to help verify pull requests in the same way as pre-commit -* Issues - * \#279 - WLAN configuration does not get set by default - * \#272 - error installing python package futures==3.2.0 -* Pull Requests - * \#275 - Disable MAC learning on WLAN - * \#281 - Bumped jackson version on corefx - -## 2019-07-05 CORE 5.3.1 -* Documentation - * Updates to provide more information regarding several of the included services -* Issues - * \#252 - fixed changing wlan configurations during runtime - * \#256 - fixed mobility waypoint comparison for python3 - * \#174 - turn tx/rx checksums off by default as they will never be valid for virtual interfaces - * \#259 - fixes for distributed EMANE - * \#260 - fixed issue with how execfile was being used due to it not existing within python3 - -## 2019-06-10 CORE 5.3.0 -* Enhancements - * python 2 / 3 support - * added new API using [gRPC](https://grpc.io/) - * --grpc --grpc-port --grpc-address flags added to core-daemon - * core.api.grpc.client.CoreGrpcClient, provides a convenience wrapper for leveraging the API -* Docs - * Updates to installation instructions for latest changes -* Services - * Added FRR service -* EMANE - * Added EMANE prefix configuration when looking for emane model manifest files - * requires configuring **emane_prefix** in /etc/core/core.conf -* Cleanup - * Refactoring of the core python package structure, trying to help provide better organization and - logical groupings -* Issues - * \#246 - Fixed network to network link handling when reading xml files - * \#236 - Fixed storing/reading of link configuration values within xml files - * \#170 - FRR Service - * \#155 - EMANE path configuration - * \#233 - Python 3 support - * \#245 - Fixed bidirectional link configurations when reading from xml files - * \#208 - gRPC API - * Fixed link configuration dup handling when loaded from xml files - -## 2019-06-07 CORE 5.2.2 -* Enhancements: - * adds back in core-daemon udp support for coresendmsg, people may have depended on previously for certain scenarios -* Bug Fixes: - * fixes issue in GUI that would prevent moving nodes during mobility scenarios - -## 2019-03-25 CORE 5.2.1 -* Packaging: - * documentation no longer builds by default, must use configure flag - * added configure flag to allow only building vcmd - * sphinx will no long be required when not building documentation -* Services: - * Added source NAT service - * Fixed DHCP service for Ubuntu 18.04 -* BUGFIXES: - * \#188 - properly remove session on delete TLV API call - * \#192 - updated default gnome terminal command for nodes to be Ubuntu 18.04 compatible - * \#193 - updates to service validation, will retry on failure and better exception logging - * \#195 - TLV link message data fix - * \#196 - fix to avoid clearing out default services - * \#197 - removed wireless_link_all API from EmuSession - * \#216 - updated default WLAN bandwidth to 54Mbps - * \#223 - fix to saving RJ45 to session XML files - -## 2018-05-22 CORE 5.1 -* DAEMON: - * removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB - specific files) - * default nodes are now set in the node map - * moved ns3 and netns directories to the top of the repo - * changes to make use of fpm as the tool for building packages - * removed usage of logzero to avoid dependency issues for built packages - * removed daemon addons directory - * added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting - and creating new external APIs out of - * cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and - informative - * EMANE 1.0.1-1.21 supported - * updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files - * example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py - * EMANE TDMA model now supports an option to start a TDMA schedule when running - * fixed issues with coresendmsg script due to code refactoring - * added make target for generating documentation "make doc" - * Python 2.7+ is now required - * ns3 is no longer bundled by default, but will be produced as a separate package for installation -* GUI: - * updated broken help links in GUI Help->About -* Packaging: - * fixed PYTHON_PATH to PYTHONPATH in sysv script - * added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation - within README.md to try it out -* TEST: - * fixed some broken tests - * new test cases based on CoreEmu usage -* BUGFIXES: - * \#142 - duplication of custom services - * \#136 - sphinx-apidoc command not found - * \#137 - make command fails when using distclean - -## 2017-09-01 CORE 5.0 -* DEVELOPMENT: - * support for editorconfig to help standardize development across IDEs, from the defined configuration file - * support for sonarqube analysis, from the defined configuration file -* DAEMON: - * code cleanup and improvements to adhere to coding standards (SonarQube) - * leverage "logzero" module to make easy usage of the standard logging module - * improvements to documentation across the code base - * initial work to separate the dependence on TCP API messaging from the core library (easier core scripting) - * beta support for running core in Open vSwitch mode, leveraging Open vSwitch bridges, instead of Linux bridges -* SERVICES: - * added Ryu SDN controller service - * added Open vSwitch service -* TEST: - * added unit/integration tests to support validating changes going forward -* BUGFIXES: - * merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96 - -## 2015-06-05 CORE 4.8 -* EMANE: - * support for EMANE 0.9.2 - * run emane in each container when using EMANE 0.9.2 - * support using separate control networks for EMANE OTA and event traffic -* GUI: - * fixed an issue where the adjacency widget lines pointed to old node positions - * fixed an issue where not all EMANE 0.9.x IEEE 802.11 MAC parameter were configurable - * fixed an issue related to running python scripts from the GUI when using tcl/tk version 8.6 - * improved batch mode execution to display the check emulation light status - * improved managing multiple sessions - * improved support for using multiple canvases - * added a reload option to the file menu to revert back to a saved scenario -* DAEMON: - * support exporting scenarios in NRL Network Modeling Framework 1.0 XML format - * support importing scenarios in NRL Network Modeling Framework 1.0 XML format - * support exporting the deployed scenario state in NRL NMF XML 1.0 format - * improved EMANE post-startup processing to better synchronize distributed emulations - * improved how addresses are assigned to tun/tap devices - * added support for python state-change callbacks -* SERVICES: - * added mgen sink and mgen actor services - * added oslrv2 and olsr.org services - * added a docker service -* BUILD: - * improved the install/uninstall process - * improved debian and rpm packaging -* BUGFIXES: - * updated the http service for ubuntu 14.04 - * improved included examples - * shortened the length of network interface names - * improved how the core system service manages running the core daemon - * fixed an issues related to applying session configuration setting - * improved detecting when a distributed emulation is already running - * improved documentation - -## 2014-08-06 CORE 4.7 -* EMANE: - * support for EMANE 0.9.1 - * fix error when using Comm Effect model with loss/duplicate string values - * enable flow control in virtual transport if enabled in the MAC model - * fix bug #150 where EMANE event service/address port were not used -* GUI: - * support Tcl/Tk 8.6 when available - * added --(a)ddress and --(p)ort arguments to core-gui command-line - * added File > Execute XML or Python script... option - * added File > Execute Python script with options... menu item - * when executing Python script from GUI, run in background thread, wait for - RUNTIME state - * enter RUNTIME state when start button pressed with empty canvas - * added support for asymmetric link effects - * support link delays up to 274 seconds (netem maximum) - * allow runtime changes of WLAN link effects -* DAEMON: - * set NODE_NAME, NODE_NUMBER, SESSION_SHORT in default vnoded environment - * changed host device naming to use veth, tap prefixes; b.n.SS for bridges - * allow parsing XML files into live running session - * enable link effects between hub/switch and hub/switch connections - * update MDR service to use broadcast interfaces for non-WLAN links - * allow node class to be specified when initializing XML parser - * save and parse canvas origin (reference point) and scale in MP XML - * up/down control script session option - * fix hash calculation used to determine GRE tunnel keys - * use shell script to detach SMF on startup - * added NRL services for mgen sink and nrlolsrv2 - * use SDT URL session option - * added core-manage tool for addons to add/remove/check services, models, - and custom node types -* API: - * implement local flag in Execute Message for running host commands - * jitter changed to 64-bit value to align with delay in Link Message - * added unidirectional link flag TLV to Link Message - * added reconfigure event type for re-generating service config files - * return errors in API with failed services -* BUGFIXES: - * fix HTTP service running under Ubuntu - * fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244, - 247, 248, 250, 251 - -## 2013-09-25 CORE 4.6 -* NOTE: cored is now core-daemon, and core is now core-gui (for Debian acceptance) -* NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv compatibility) -* EMANE: - * don't start EMANE locally if no local NEMs - * EMANE poststartup() to re-transmit location events during initialization - * added debug port to EMANE options - * added a basic EMANE 802.11 CORE Python script example - * expose transport XML block generation to EmaneModels - * expose NEM entry to the EmaneModel so it can be overridden by a model - * add the control interface bridge prior to starting EMANE, as some models may - * depend on the controlnet functionality - * added EMANE model to CORE converter - * parse lat/long/alt from node messages, for moving nodes using command-line - * fix bug #196 incorrect distance when traversing UTM zones -* GUI: - * added Cut, Copy, and Paste options to the Edit menu - * paste will copy selected services and take care of node and interface - * renumbering - * implement Edit > Find dialog for searching nodes and links - * when copying existing file for a service, perform string replacement of: - * "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%" - * use CORE_DATA_DIR insteadof LIBDIR - * fix Adjacency Widget to work with OSPFv2 only networks -* BUILD: - * build/packaging improvements for inclusion on Debian - * fix error when running scenario with a mobility script in batch mode - * include Linux kernel patches for 3.8 - * renamed core-cleanup.sh to core-cleanup for Debian conformance - * don't always generate man pages from Makefile; new manpages for - coresendmsg and core-daemon -* BUGFIXES: - * don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect) - * fixed lock view - * fix GUI spinbox errors for Tk 8.5.8 (RHEL/CentOS 6.2) - * fix broker node count for distributed session entering the RUNTIME state when - * (non-EMANE) WLANs or GreTapBridges are involved; - * fix "file exists" error message when distributed session number is re-used - * and servers file is written - * fix bug #194 configuration dialog too long, make dialog scrollable/resizable - * allow float values for loss and duplicates percent - * fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202, - 205, 206, 210, 212, 213, 214, 221 - -## 2013-04-13 CORE 4.5 -* GUI: - * improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost - * fix various GUI issues when reconnecting to a session - * support 3D GUI via output to SDT3D - * added "Execute Python script..." entry to the File Menu - * support user-defined terminal program instead of hard-coded xterm - * added session options for "enable RJ45s", "preserve session dir" - * added buttons to the IP Addresses dialog for removing all/selected IPv4/IPv6 - * allow sessions with multiple canvases to enter RUNTIME state - * added "--addons" startup mode to pass control to code included from addons dir - * added "Locked" entry to View menu to prevent moving items - * use currently selected node type when invoking a topology generator - * updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot - configuration with imn file - * improved session dialog -* EMANE: - * EMANE 0.8.1 support with backwards-compatibility for 0.7.4 - * extend CommEffect model to generate CommEffect events upon receipt of Link Messages having link effects -* Services: - * updated FTP service with root directory for anonymous users - * added HTTP, PCAP, BIRD, RADVD, and Babel services - * support copying existing files instead of always generating them - * added "Services..." entry to node right-click menu - * added "View" button for side-by-side comparison when copying customized config files - * updated Quagga daemons to wait for zebra.vty VTY file before starting -* General: - * XML import and export - * renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg" - * code reorganization and clean-up - * updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new - "Save As XML..." File menu entry - * added script_start/pause/stop options to Ns2ScriptedMobility - * "python" source sub-directory renamed to "daemon" - * added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for - GUI connection - * support comma-separated list for custom_services_dir in core.conf file - * updated kernel patches for Linux kernel 3.5 - * support RFC 6164-style IPv6 /127 addressing -* ns-3: - * integrate ns-3 node location between CORE and ns-3 simulation - * added ns-3 random walk mobility example - * updated ns-3 Wifi example to allow GUI connection and moving of nodes -* fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, -184, 190, 193 - -## 2012-09-25 CORE 4.4 -* GUI: - * real-time bandwidth plotting tool - * added Wireshark and tshark right-click menu items - * X,Y coordinates shown in the status bar - * updated GUI attribute option to link messages for changing color/width/dash - * added sample IPsec and VPN scenarios, how many nodes script - * added jitter parameter to WLANs - * renamed Experiment menu to Session menu, added session options - * use 'key=value' configuration for services, EMANE models, WLAN models, etc. - * save only service values that have been customized - * copy service parameters from one customized service to another - * right-click menu to start/stop/restart each service -* EMANE: - * EMANE 0.7.4 support - * added support for EMANE CommEffect model and Comm Effect controller GUI - * added support for EMANE Raw Transport when using RJ45 devices -* Services: - * improved service customization; allow a service to define custom Tcl tab - * added vtysh.conf for Quagga service to support 'write mem' - * support scheduled events and services that start N seconds after runtime - * added UCARP service -* Documentation: - * converted the CORE manual to reStructuredText using Sphinx; added Python docs -* General: - * Python code reorganization - * improved cored.py thread locking - * merged xen branch into trunk - * added an event queue to a session with notion of time zero - * added UDP support to cored.py - * use UDP by default in coresendmsg.py; added '-H' option to print examples - * enter a bash shell by default when running vcmd with no arguments - * fixes to distributed emulation entering runtime state - * write 'nodes' file upon session startup - * make session number and other attributes available in environment - * support /etc/core/environment and ~/.core/environment files - * added Ns2ScriptedMobility model to Python, removed from the GUI - * namespace nodes mount a private /sys - * fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, - 147, 151, 154, 155 - -## 2012-03-07 CORE 4.3 -* EMANE 0.7.2 and 0.7.3 support -* hook scripts: customize actions at any of six different session states -* Check Emulation Light (CEL) exception feedback system -* added FTP and XORP services, and service validate commands -* services can flag when customization is required -* Python classes to support ns-3 simulation experiments -* write state, node X,Y position, and servers to pycore session dir -* removed over 9,000 lines of unused GUI code -* performance monitoring script -* batch mode improvements and --closebatch option -* export session to EmulationScript XML files -* basic range model moved from GUI to Python, supports 3D coordinates -* improved WLAN dialog with tabs -* added PhysicalNode class for joining real nodes with emulated networks -* fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120 - -## 2011-08-19 CORE 4.2 -* EMANE 0.7.1 support - * support for Bypass model, Universal PHY, logging, realtime -* configurable MAC addresses -* control interfaces (backchannel between node and host) -* service customization dialog improved (tabbed) -* new testing scripts for MDR and EMANE performance testing -* improved upgrading of old imn files -* new coresendmsg.py utility (deprecates libcoreapi and coreapisend) -* new security services, custom service becomes UserDefined -* new services and Python scripting chapters in manual -* fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches -* fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, -65, 66, 68, 71, 72, 74 - -## 2011-01-05 CORE 4.1 -* new icons for toolbars and nodes -* node services introduced, node models deprecated -* customizable node types -* traffic flow editor with MGEN support -* user configs moved from /etc/core/`*` to ~/.core/ -* allocate addresses from custom IPv4/IPv6 prefixes -* distributed emulation using GRE tunnels -* FreeBSD 8.1 now uses cored.py -* EMANE 0.6.4 support -* numerous bugfixes - -## 2010-08-17 CORE 4.0 -* Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform) -* ability to close the GUI and later reconnect to a running session (netns only) -* EMANE integration (netns only) -* new topology generators, host file generator -* user-editable Observer Widgets -* use of /etc/core instead of /usr/local/etc/core -* various bugfixes - -## 2009-09-15 CORE 3.5 - -## 2009-06-23 CORE 3.4 - -## 2009-03-11 CORE 3.3 diff --git a/Changelog b/Changelog new file mode 100644 index 00000000..dd034ab6 --- /dev/null +++ b/Changelog @@ -0,0 +1,324 @@ +2018-05-22 CORE 5.1 + * DAEMON: + - removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB specific files) + - default nodes are now set in the node map + - moved ns3 and netns directories to the top of the repo + - changes to make use of fpm as the tool for building packages + - removed usage of logzero to avoid dependency issues for built packages + - removed daemon addons directory + - added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting and creating new external APIs out of + - cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and informative + - EMANE 1.0.1-1.21 supported + - updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files + - example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py + - EMANE TDMA model now supports an option to start a TDMA schedule when running + - fixed issues with coresendmsg script due to code refactoring + - added make target for generating documentation "make doc" + - Python 2.7+ is now required + - ns3 is no longer bundled by default, but will be produced as a separate package for installation + * GUI + - updated broken help links in GUI Help->About + * Packaging + - fixed PYTHON_PATH to PYTHONPATH in sysv script + - added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation within README.md to try it out + * TEST: + - fixed some broken tests + - new test cases based on CoreEmu usage + * BUGFIXES: + - #142 - duplication of custom services + - #136 - sphinx-apidoc command not found + - #137 - make command fails when using distclean + +2017-09-01 CORE 5.0 + * DEVELOPMENT: + - support for editorconfig to help standardize development across IDEs, from the defined configuration file + - support for sonarqube analysis, from the defined configuration file + * DAEMON: + - code cleanup and improvements to adhere to coding standards (SonarQube) + - leverage "logzero" module to make easy usage of the standard logging module + - improvements to documentation across the code base + - initial work to separate the dependence on TCP API messaging from the core library (easier core scripting) + - beta support for running core in Open vSwitch mode, leveraging Open vSwitch bridges, instead of Linux bridges + * SERVICES: + - added Ryu SDN controller service + - added Open vSwitch service + * TEST: + - added unit/integration tests to support validating changes going forward + * BUGFIXES: + - merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96 + +2015-06-05 CORE 4.8 + * EMANE: + - support for EMANE 0.9.2 + - run emane in each container when using EMANE 0.9.2 + - support using separate control networks for EMANE OTA and event traffic + * GUI: + - fixed an issue where the adjacency widget lines pointed to old node positions + - fixed an issue where not all EMANE 0.9.x IEEE 802.11 MAC parameter were configurable + - fixed an issue related to running python scripts from the GUI when using tcl/tk version 8.6 + - improved batch mode execution to display the check emulation light status + - improved managing multiple sessions + - improved support for using multiple canvases + - added a reload option to the file menu to revert back to a saved scenario + * DAEMON: + - support exporting scenarios in NRL Network Modeling Framework 1.0 XML format + - support importing scenarios in NRL Network Modeling Framework 1.0 XML format + - support exporting the deployed scenario state in NRL NMF XML 1.0 format + - improved EMANE post-startup processing to better synchronize distributed emulations + - improved how addresses are assigned to tun/tap devices + - added support for python state-change callbacks + * SERVICES: + - added mgen sink and mgen actor services + - added oslrv2 and olsr.org services + - added a docker service + * BUILD: + - improved the install/uninstall process + - improved debian and rpm packaging + * BUGFIXES: + - updated the http service for ubuntu 14.04 + - improved included examples + - shortened the length of network interface names + - improved how the core system service manages running the core daemon + - fixed an issues related to applying session configuration setting + - improved detecting when a distributed emulation is already running + - improved documentation + +2014-08-06 CORE 4.7 + + * EMANE: + - support for EMANE 0.9.1 + - fix error when using Comm Effect model with loss/duplicate string values + - enable flow control in virtual transport if enabled in the MAC model + - fix bug #150 where EMANE event service/address port were not used + * GUI: + - support Tcl/Tk 8.6 when available + - added --(a)ddress and --(p)ort arguments to core-gui command-line + - added File > Execute XML or Python script... option + - added File > Execute Python script with options... menu item + - when executing Python script from GUI, run in background thread, wait for + RUNTIME state + - enter RUNTIME state when start button pressed with empty canvas + - added support for asymmetric link effects + - support link delays up to 274 seconds (netem maximum) + - allow runtime changes of WLAN link effects + * DAEMON: + - set NODE_NAME, NODE_NUMBER, SESSION_SHORT in default vnoded environment + - changed host device naming to use veth, tap prefixes; b.n.SS for bridges + - allow parsing XML files into live running session + - enable link effects between hub/switch and hub/switch connections + - update MDR service to use broadcast interfaces for non-WLAN links + - allow node class to be specified when initializing XML parser + - save and parse canvas origin (reference point) and scale in MP XML + - up/down control script session option + - fix hash calculation used to determine GRE tunnel keys + - use shell script to detach SMF on startup + - added NRL services for mgen sink and nrlolsrv2 + - use SDT URL session option + - added core-manage tool for addons to add/remove/check services, models, + and custom node types + * API: + - implement local flag in Execute Message for running host commands + - jitter changed to 64-bit value to align with delay in Link Message + - added unidirectional link flag TLV to Link Message + - added reconfigure event type for re-generating service config files + - return errors in API with failed services + * BUGFIXES: + - fix HTTP service running under Ubuntu + - fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244, + 247, 248, 250, 251 + +2013-09-25 CORE 4.6 + + * NOTE: cored is now core-daemon, and core is now core-gui (for Debian + acceptance) + * NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv + compatibility) + * EMANE: + - don't start EMANE locally if no local NEMs + - EMANE poststartup() to re-transmit location events during initialization + - added debug port to EMANE options + - added a basic EMANE 802.11 CORE Python script example + - expose transport XML block generation to EmaneModels + - expose NEM entry to the EmaneModel so it can be overridden by a model + - add the control interface bridge prior to starting EMANE, as some models may + - depend on the controlnet functionality + - added EMANE model to CORE converter + - parse lat/long/alt from node messages, for moving nodes using command-line + - fix bug #196 incorrect distance when traversing UTM zones + + * GUI: + - added Cut, Copy, and Paste options to the Edit menu + - paste will copy selected services and take care of node and interface + - renumbering + - implement Edit > Find dialog for searching nodes and links + - when copying existing file for a service, perform string replacement of: + - "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%" + - use CORE_DATA_DIR insteadof LIBDIR + - fix Adjacency Widget to work with OSPFv2 only networks + + * BUILD: + - build/packaging improvements for inclusion on Debian + - fix error when running scenario with a mobility script in batch mode + - include Linux kernel patches for 3.8 + - renamed core-cleanup.sh to core-cleanup for Debian conformance + - don't always generate man pages from Makefile; new manpages for + coresendmsg and core-daemon + + * BUGFIXES: + - don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect) + - fixed lock view + - fix GUI spinbox errors for Tk 8.5.8 (RHEL/CentOS 6.2) + - fix broker node count for distributed session entering the RUNTIME state when + - (non-EMANE) WLANs or GreTapBridges are involved; + - fix "file exists" error message when distributed session number is re-used + - and servers file is written + - fix bug #194 configuration dialog too long, make dialog scrollable/resizable + - allow float values for loss and duplicates percent + - fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202, + 205, 206, 210, 212, 213, 214, 221 + +2013-04-13 CORE 4.5 + + * GUI: + - improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost + - fix various GUI issues when reconnecting to a session + - support 3D GUI via output to SDT3D + - added "Execute Python script..." entry to the File Menu + - support user-defined terminal program instead of hard-coded xterm + - added session options for "enable RJ45s", "preserve session dir" + - added buttons to the IP Addresses dialog for removing all/selected IPv4/IPv6 + - allow sessions with multiple canvases to enter RUNTIME state + - added "--addons" startup mode to pass control to code included from addons dir + - added "Locked" entry to View menu to prevent moving items + - use currently selected node type when invoking a topology generator + - updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot configuration with imn file + - improved session dialog + * EMANE: + - EMANE 0.8.1 support with backwards-compatibility for 0.7.4 + - extend CommEffect model to generate CommEffect events upon receipt of Link Messages having link effects + * Services: + - updated FTP service with root directory for anonymous users + - added HTTP, PCAP, BIRD, RADVD, and Babel services + - support copying existing files instead of always generating them + - added "Services..." entry to node right-click menu + - added "View" button for side-by-side comparison when copying customized config files + - updated Quagga daemons to wait for zebra.vty VTY file before starting + * General: + - XML import and export + - renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg" + - code reorganization and clean-up + - updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new "Save As XML..." File menu entry + - added script_start/pause/stop options to Ns2ScriptedMobility + - "python" source sub-directory renamed to "daemon" + - added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for GUI connection + - support comma-separated list for custom_services_dir in core.conf file + - updated kernel patches for Linux kernel 3.5 + - support RFC 6164-style IPv6 /127 addressing + * ns-3: + - integrate ns-3 node location between CORE and ns-3 simulation + - added ns-3 random walk mobility example + - updated ns-3 Wifi example to allow GUI connection and moving of nodes + * fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, 184, 190, 193 + +2012-09-25 CORE 4.4 + + * GUI: + - real-time bandwidth plotting tool + - added Wireshark and tshark right-click menu items + - X,Y coordinates shown in the status bar + - updated GUI attribute option to link messages for changing color/width/dash + - added sample IPsec and VPN scenarios, how many nodes script + - added jitter parameter to WLANs + - renamed Experiment menu to Session menu, added session options + - use 'key=value' configuration for services, EMANE models, WLAN models, etc. + - save only service values that have been customized + - copy service parameters from one customized service to another + - right-click menu to start/stop/restart each service + * EMANE: + - EMANE 0.7.4 support + - added support for EMANE CommEffect model and Comm Effect controller GUI + - added support for EMANE Raw Transport when using RJ45 devices + * Services: + - improved service customization; allow a service to define custom Tcl tab + - added vtysh.conf for Quagga service to support 'write mem' + - support scheduled events and services that start N seconds after runtime + - added UCARP service + * Documentation: + - converted the CORE manual to reStructuredText using Sphinx; added Python docs + * General: + - Python code reorganization + - improved cored.py thread locking + - merged xen branch into trunk + - added an event queue to a session with notion of time zero + - added UDP support to cored.py + - use UDP by default in coresendmsg.py; added '-H' option to print examples + - enter a bash shell by default when running vcmd with no arguments + - fixes to distributed emulation entering runtime state + - write 'nodes' file upon session startup + - make session number and other attributes available in environment + - support /etc/core/environment and ~/.core/environment files + - added Ns2ScriptedMobility model to Python, removed from the GUI + - namespace nodes mount a private /sys + + - fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, 147, 151, 154, 155 + +2012-03-07 CORE 4.3 + + * EMANE 0.7.2 and 0.7.3 support + * hook scripts: customize actions at any of six different session states + * Check Emulation Light (CEL) exception feedback system + * added FTP and XORP services, and service validate commands + * services can flag when customization is required + * Python classes to support ns-3 simulation experiments + * write state, node X,Y position, and servers to pycore session dir + * removed over 9,000 lines of unused GUI code + * performance monitoring script + * batch mode improvements and --closebatch option + * export session to EmulationScript XML files + * basic range model moved from GUI to Python, supports 3D coordinates + * improved WLAN dialog with tabs + * added PhysicalNode class for joining real nodes with emulated networks + * fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120 + +2011-08-19 CORE 4.2 + + * EMANE 0.7.1 support + - support for Bypass model, Universal PHY, logging, realtime + * configurable MAC addresses + * control interfaces (backchannel between node and host) + * service customization dialog improved (tabbed) + * new testing scripts for MDR and EMANE performance testing + * improved upgrading of old imn files + * new coresendmsg.py utility (deprecates libcoreapi and coreapisend) + * new security services, custom service becomes UserDefined + * new services and Python scripting chapters in manual + * fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches + * fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, 65, 66, 68, 71, 72, 74 + +2011-01-05 CORE 4.1 + * new icons for toolbars and nodes + * node services introduced, node models deprecated + * customizable node types + * traffic flow editor with MGEN support + * user configs moved from /etc/core/`*` to ~/.core/ + * allocate addresses from custom IPv4/IPv6 prefixes + * distributed emulation using GRE tunnels + * FreeBSD 8.1 now uses cored.py + * EMANE 0.6.4 support + * numerous bugfixes + +2010-08-17 CORE 4.0 + * Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform) + * ability to close the GUI and later reconnect to a running session (netns only) + * EMANE integration (netns only) + * new topology generators, host file generator + * user-editable Observer Widgets + * use of /etc/core instead of /usr/local/etc/core + * various bugfixes + +2009-09-15 CORE 3.5 + +2009-06-23 CORE 3.4 + +2009-03-11 CORE 3.3 + diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 155cacc0..00000000 --- a/Dockerfile +++ /dev/null @@ -1,126 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM ubuntu:22.04 -LABEL Description="CORE Docker Ubuntu Image" - -ARG PREFIX=/usr/local -ARG BRANCH=master -ARG PROTOC_VERSION=3.19.6 -ARG VENV_PATH=/opt/core/venv -ENV DEBIAN_FRONTEND=noninteractive -ENV PATH="$PATH:${VENV_PATH}/bin" -WORKDIR /opt - -# install system dependencies - -RUN apt-get update -y && \ - apt-get install -y software-properties-common - -RUN add-apt-repository "deb http://archive.ubuntu.com/ubuntu jammy universe" - -RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - automake \ - bash \ - ca-certificates \ - ethtool \ - gawk \ - gcc \ - g++ \ - iproute2 \ - iputils-ping \ - libc-dev \ - libev-dev \ - libreadline-dev \ - libtool \ - nftables \ - python3 \ - python3-pip \ - python3-tk \ - pkg-config \ - tk \ - xauth \ - xterm \ - wireshark \ - vim \ - build-essential \ - nano \ - firefox \ - net-tools \ - rsync \ - openssh-server \ - openssh-client \ - vsftpd \ - atftpd \ - atftp \ - mini-httpd \ - lynx \ - tcpdump \ - iperf \ - iperf3 \ - tshark \ - openssh-sftp-server \ - bind9 \ - bind9-utils \ - openvpn \ - isc-dhcp-server \ - isc-dhcp-client \ - whois \ - ipcalc \ - socat \ - hping3 \ - libgtk-3-0 \ - librest-0.7-0 \ - libgtk-3-common \ - dconf-gsettings-backend \ - libsoup-gnome2.4-1 \ - libsoup2.4-1 \ - dconf-service \ - x11-xserver-utils \ - ftp \ - git \ - sudo \ - wget \ - tzdata \ - libpcap-dev \ - libpcre3-dev \ - libprotobuf-dev \ - libxml2-dev \ - protobuf-compiler \ - unzip \ - uuid-dev \ - iproute2 \ - vlc \ - iputils-ping && \ - apt-get autoremove -y - -# install core -RUN git clone https://github.com/coreemu/core && \ - cd core && \ - git checkout ${BRANCH} && \ - ./setup.sh && \ - PATH=/root/.local/bin:$PATH inv install -v -p ${PREFIX} && \ - cd /opt && \ - rm -rf ospf-mdr - -# install emane -RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip && \ - mkdir protoc && \ - unzip protoc-${PROTOC_VERSION}-linux-x86_64.zip -d protoc && \ - git clone https://github.com/adjacentlink/emane.git && \ - cd emane && \ - ./autogen.sh && \ - ./configure --prefix=/usr && \ - make -j$(nproc) && \ - make install && \ - cd src/python && \ - make clean && \ - PATH=/opt/protoc/bin:$PATH make && \ - ${VENV_PATH}/bin/python -m pip install . && \ - cd /opt && \ - rm -rf protoc && \ - rm -rf emane && \ - rm -f protoc-${PROTOC_VERSION}-linux-x86_64.zip - -WORKDIR /root - -CMD /opt/core/venv/bin/core-daemon diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..e70e3dc6 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,20 @@ +pipeline { + agent any + stages { + stage('build core') { + steps { + sh './bootstrap.sh' + sh './configure' + sh 'make' + sh 'sudo make install' + } + } + stage('test core') { + steps { + sh 'pytest daemon/tests/test_core.py' + sh 'pytest daemon/tests/test_gui.py' + sh 'pytest daemon/tests/test_emane.py' + } + } + } +} \ No newline at end of file diff --git a/Makefile.am b/Makefile.am index 2b5f29e2..ee26b643 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,26 +6,29 @@ if WANT_DOCS DOCS = docs man endif +if WANT_GUI + GUI = gui +endif + if WANT_DAEMON - DAEMON = daemon + DAEMON = scripts daemon endif if WANT_NETNS - NETNS = netns + NETNS = netns ns3 endif # keep docs last due to dependencies on binaries -SUBDIRS = $(DAEMON) $(NETNS) $(DOCS) +SUBDIRS = $(GUI) $(DAEMON) $(NETNS) $(DOCS) ACLOCAL_AMFLAGS = -I config # extra files to include with distribution tarball EXTRA_DIST = bootstrap.sh \ - package \ LICENSE \ README.md \ ASSIGNMENT_OF_COPYRIGHT.pdf \ - CHANGELOG.md \ + Changelog \ .version \ .version.date @@ -41,117 +44,80 @@ DISTCLEANFILES = aclocal.m4 \ MAINTAINERCLEANFILES = .version \ .version.date -define fpm-distributed-deb = -fpm -s dir -t deb -n core-distributed \ +define fpm-python = +fpm -s python -t $1 \ -m "$(PACKAGE_MAINTAINERS)" \ - --license "BSD" \ - --description "Common Open Research Emulator Distributed Package" \ - --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core-distributed_VERSION_ARCH.deb \ - -v $(PACKAGE_VERSION) \ - -d "ethtool" \ - -d "procps" \ - -d "libc6 >= 2.14" \ - -d "bash >= 3.0" \ - -d "nftables" \ - -d "iproute2" \ - -d "libev4" \ - -d "openssh-server" \ - -d "xterm" \ - netns/vnoded=/usr/bin/ \ - netns/vcmd=/usr/bin/ + $2 endef -define fpm-distributed-rpm = -fpm -s dir -t rpm -n core-distributed \ +define fpm-gui = +fpm -s dir -t $1 -n core-gui \ -m "$(PACKAGE_MAINTAINERS)" \ --license "BSD" \ - --description "Common Open Research Emulator Distributed Package" \ - --url https://github.com/coreemu/core \ + --description "Common Open Research Emulator GUI front-end" \ + --url http://www.nrl.navy.mil/itd/ncs/products/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core-distributed_VERSION_ARCH.rpm \ + -p core-gui_VERSION_ARCH.$1 \ -v $(PACKAGE_VERSION) \ - -d "ethtool" \ - -d "procps-ng" \ - -d "bash >= 3.0" \ - -d "nftables" \ - -d "iproute" \ - -d "libev" \ - -d "net-tools" \ - -d "openssh-server" \ - -d "xterm" \ - netns/vnoded=/usr/bin/ \ - netns/vcmd=/usr/bin/ -endef - -define fpm-rpm = -fpm -s dir -t rpm -n core \ - -m "$(PACKAGE_MAINTAINERS)" \ - --license "BSD" \ - --description "core vnoded/vcmd and system dependencies" \ - --url https://github.com/coreemu/core \ - --vendor "$(PACKAGE_VENDOR)" \ - -p core_VERSION_ARCH.rpm \ - -v $(PACKAGE_VERSION) \ - --rpm-init package/core-daemon \ - --after-install package/after-install.sh \ - --after-remove package/after-remove.sh \ - -d "ethtool" \ + -d "bash" \ + -d "tcl" \ -d "tk" \ + $2 \ + -C $(DESTDIR) +endef + +define fpm-daemon-rpm = +fpm -s python -t rpm \ + -p NAME_sysv_VERSION_ARCH.rpm \ + --rpm-init scripts/core-daemon \ + --python-install-bin $(bindir) \ + --python-install-data $(prefix) \ + --python-install-lib $(pythondir) \ + -m "$(PACKAGE_MAINTAINERS)" \ + --vendor "$(PACKAGE_VENDOR)" \ -d "procps-ng" \ -d "bash >= 3.0" \ + -d "bridge-utils" \ -d "ebtables" \ -d "iproute" \ -d "libev" \ -d "net-tools" \ - -d "nftables" \ - netns/vnoded=/usr/bin/ \ - netns/vcmd=/usr/bin/ \ - package/etc/core.conf=/etc/core/ \ - package/etc/logging.conf=/etc/core/ \ - package/examples=/opt/core/ \ - daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/ + -d "python >= 2.7, python < 3.0" \ + netns/setup.py daemon/setup.py endef -define fpm-deb = -fpm -s dir -t deb -n core \ +define fpm-daemon-deb = +fpm -s python -t deb \ + -p NAME_$1_VERSION_ARCH.deb \ + --python-install-bin $(bindir) \ + --python-install-data $(prefix) \ + --python-install-lib $(pythondir) \ + $2 $3 \ -m "$(PACKAGE_MAINTAINERS)" \ - --license "BSD" \ - --description "core vnoded/vcmd and system dependencies" \ - --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_VERSION_ARCH.deb \ - -v $(PACKAGE_VERSION) \ - --deb-systemd package/core-daemon.service \ - --deb-no-default-config-files \ - --after-install package/after-install.sh \ - --after-remove package/after-remove.sh \ - -d "ethtool" \ - -d "tk" \ - -d "libtk-img" \ -d "procps" \ -d "libc6 >= 2.14" \ -d "bash >= 3.0" \ + -d "bridge-utils" \ -d "ebtables" \ -d "iproute2" \ -d "libev4" \ - -d "nftables" \ - netns/vnoded=/usr/bin/ \ - netns/vcmd=/usr/bin/ \ - package/etc/core.conf=/etc/core/ \ - package/etc/logging.conf=/etc/core/ \ - package/examples=/opt/core/ \ - daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/ + -d "python (>= 2.7), python (<< 3.0)" \ + --deb-recommends quagga \ + netns/setup.py daemon/setup.py endef .PHONY: fpm fpm: clean-local-fpm - cd daemon && poetry build -f wheel - $(call fpm-deb) - $(call fpm-rpm) - $(call fpm-distributed-deb) - $(call fpm-distributed-rpm) + $(MAKE) -C gui install DESTDIR=$(DESTDIR) + $(call fpm-gui,rpm) + $(call fpm-gui,deb,-d "libtk-img") + $(call fpm-python,rpm,ns3/setup.py) + $(call fpm-python,deb,ns3/setup.py) + $(call fpm-daemon-rpm) + $(call fpm-daemon-deb,sysv,--deb-init,scripts/core-daemon) + $(call fpm-daemon-deb,systemd,--deb-systemd,scripts/core-daemon.service) .PHONY: clean-local-fpm clean-local-fpm: @@ -170,12 +136,24 @@ define change-files = $(info creating file $1 from $1.in) @$(SED) -e 's,[@]sbindir[@],$(sbindir),g' \ -e 's,[@]bindir[@],$(bindir),g' \ + -e 's,[@]pythondir[@],$(pythondir),g' \ + -e 's,[@]PYTHON[@],$(PYTHON),g' \ -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \ -e 's,[@]PACKAGE_DATE[@],$(PACKAGE_DATE),g' \ -e 's,[@]CORE_LIB_DIR[@],$(CORE_LIB_DIR),g' \ -e 's,[@]CORE_STATE_DIR[@],$(CORE_STATE_DIR),g' \ -e 's,[@]CORE_DATA_DIR[@],$(CORE_DATA_DIR),g' \ -e 's,[@]CORE_CONF_DIR[@],$(CORE_CONF_DIR),g' \ + -e 's,[@]CORE_GUI_CONF_DIR[@],$(CORE_GUI_CONF_DIR),g' \ + -e 's,[@]brctl_path[@],$(brctl_path),g' \ + -e 's,[@]sysctl_path[@],$(sysctl_path),g' \ + -e 's,[@]ip_path[@],$(ip_path),g' \ + -e 's,[@]tc_path[@],$(tc_path),g' \ + -e 's,[@]ebtables_path[@],$(ebtables_path),g' \ + -e 's,[@]mount_path[@],$(mount_path),g' \ + -e 's,[@]umount_path[@],$(umount_path),g' \ + -e 's,[@]ovs_vs_path[@],$(ovs_vs_path),g' \ + -e 's,[@]ovs_of_path[@],$(ovs_of_path),g' \ < $1.in > $1 endef @@ -183,8 +161,10 @@ 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) CORE_DOC_SRC = core-python-$(PACKAGE_VERSION) .PHONY: doc diff --git a/README.md b/README.md index efab2e70..25ffc6d3 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,103 @@ -# Index -- CORE -- Docker Setup - - Precompiled container image - - Build container image from source - - Adding extra packages - -- Useful commands -- License - # CORE CORE: Common Open Research Emulator -Copyright (c)2005-2022 the Boeing Company. +Copyright (c)2005-2018 the Boeing Company. See the LICENSE file included in this distribution. -# Docker Setup +## About -Here you have 2 choices +The Common Open Research Emulator (CORE) is a tool for emulating +networks on one or more machines. You can connect these emulated +networks to live networks. CORE consists of a GUI for drawing +topologies of lightweight virtual machines, and Python modules for +scripting network emulation. -## Precompiled container image +## Documentation and Examples -```bash +* Documentation hosted on GitHub + * http://coreemu.github.io/core/ +* Basic Script Examples + * [Examples](daemon/examples/api) +* Custom Service Example + * [sample.py](daemon/examples/myservices/sample.py) +* Custom Emane Model Example + * [examplemodel.py](daemon/examples/myemane/examplemodel.py) -# Start container -sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped git.olympuslab.net/afonso/core-extra:latest +## Support -``` -## Build container image from source +We are leveraging Discord for persistent chat rooms, voice chat, and +GitHub integration. This allows for more dynamic conversations and the +capability to respond faster. Feel free to join us at the link below. +https://discord.gg/AKd7kmP -```bash -# Clone the repo -git clone https://gitea.olympuslab.net/afonso/core-extra.git +You can also get help with questions, comments, or trouble, by using +the CORE mailing lists: -# cd into the directory -cd core-extra +* [core-users](https://pf.itd.nrl.navy.mil/mailman/listinfo/core-users) for general comments and questions +* [core-dev](https://pf.itd.nrl.navy.mil/mailman/listinfo/core-dev) for bugs, compile errors, and other development issues -# build the docker image -sudo docker build -t core-extra . - -# start container -sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped core-extra +## Building CORE +```shell +./bootstrap.sh +./configure +make +sudo make install ``` -### Adding extra packages - -To add extra packages you must modify the Dockerfile and then compile the docker image. -If you install it after starting the container it will, by docker nature, be reverted on the next boot of the container. - -# Useful commands - -I have the following functions on my fish shell -to help me better use core - -THIS ONLY WORKS ON FISH, MODIFY FOR BASH OR ZSH - -```fish - -# RUN CORE GUI -function core - xhost +local:root - sudo docker exec -it core core-gui -end - -# RUN BASH INSIDE THE CONTAINER -function core-bash - sudo docker exec -it core /bin/bash -end - - -# LAUNCH NODE BASH ON THE HOST MACHINE -function launch-term --argument nodename - sudo docker exec -it core xterm -bg black -fg white -fa 'DejaVu Sans Mono' -fs 16 -e vcmd -c /tmp/pycore.1/$nodename -- /bin/bash -end - -#TO RUN ANY OTHER COMMAND -sudo docker exec -it core COMAND_GOES_HERE +Building Documentation +---------------------- +```shell +./bootstrap.sh +./configure +make doc ``` -## LICENSE +Building Packages +----------------- -Copyright (c) 2005-2018, the Boeing Company. +Install fpm: http://fpm.readthedocs.io/en/latest/installing.html -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Build package commands, DESTDIR is used for gui packaging only -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +```shell +./bootstrap.sh +./configure +make +mkdir /tmp/core-gui +make fpm DESTDIR=/tmp/core-gui +``` -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -THE POSSIBILITY OF SUCH DAMAGE. +This will produce: + +* CORE GUI rpm/deb files + * core-gui_$VERSION_$ARCH +* CORE ns3 rpm/deb files + * python-core-ns3_$VERSION_$ARCH +* CORE python rpm/deb files for SysV and systemd service types + * python-core-sysv_$VERSION_$ARCH + * python-core-systemd_$VERSION_$ARCH + +Running CORE +------------ + +First start the CORE services: + +```shell +# sysv +sudo service core-daemon start +# systemd +sudo systemctl start core-daemon +``` + +This automatically runs the core-daemon program. +Assuming the GUI is in your PATH, run the CORE GUI by typing the following: + +```shell +core-gui +``` + +This launches the CORE GUI. You do not need to run the GUI as root. diff --git a/bootstrap.sh b/bootstrap.sh index 25fdecfd..ab3d741c 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,5 +1,9 @@ #!/bin/sh # +# (c)2010-2012 the Boeing Company +# +# author: Jeff Ahrenholz +# # Bootstrap the autoconf system. # diff --git a/configure.ac b/configure.ac index 4e56507a..80e45840 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, 9.0.3) +AC_INIT(core, 5.2, core-dev@nrl.navy.mil) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) @@ -14,7 +14,7 @@ AM_INIT_AUTOMAKE([tar-ustar]) # define variables used for packaging and date display PACKAGE_DATE=m4_esyscmd_s([date +%Y%m%d]) PACKAGE_VENDOR="CORE Developers" -PACKAGE_MAINTAINERS="$PACKAGE_VENDOR" +PACKAGE_MAINTAINERS="$PACKAGE_VENDOR <$PACKAGE_BUGREPORT>" # core specific variables CORE_LIB_DIR="\${prefix}/lib/core" @@ -30,25 +30,19 @@ AC_SUBST(CORE_CONF_DIR) AC_SUBST(CORE_DATA_DIR) AC_SUBST(CORE_STATE_DIR) -# documentation option -AC_ARG_ENABLE([docs], - [AS_HELP_STRING([--enable-docs[=ARG]], - [build python documentation (default is no)])], - [], [enable_docs=no]) -AC_SUBST(enable_docs) - -# python option -AC_ARG_ENABLE([python], - [AS_HELP_STRING([--enable-python[=ARG]], - [build and install the python bindings (default is yes)])], - [], [enable_python=yes]) -AC_SUBST(enable_python) -if test "x$enable_python" = "xyes" ; then - want_python=yes -else - want_python=no -fi - +# CORE GUI configuration files and preferences in CORE_GUI_CONF_DIR +# scenario files in ~/.core/configs/ +AC_ARG_WITH([guiconfdir], + [AS_HELP_STRING([--with-guiconfdir=dir], + [specify GUI configuration directory])], + [CORE_GUI_CONF_DIR="$with_guiconfdir"], + [CORE_GUI_CONF_DIR="\$\${HOME}/.core"]) +AC_SUBST(CORE_GUI_CONF_DIR) +AC_ARG_ENABLE([gui], + [AS_HELP_STRING([--enable-gui[=ARG]], + [build and install the GUI (default is yes)])], + [], [enable_gui=yes]) +AC_SUBST(enable_gui) AC_ARG_ENABLE([daemon], [AS_HELP_STRING([--enable-daemon[=ARG]], [build and install the daemon with Python modules @@ -56,13 +50,6 @@ AC_ARG_ENABLE([daemon], [], [enable_daemon=yes]) AC_SUBST(enable_daemon) -AC_ARG_ENABLE([vnodedonly], - [AS_HELP_STRING([--enable-vnodedonly[=ARG]], - [only try to build vnoded and vcmd container utils - (default is no)])], - [enable_vnodedonly=yes], [enable_vnodedonly=no]) -AC_SUBST(enable_vnodedonly) - SEARCHPATH="/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin" # default compiler flags @@ -83,62 +70,8 @@ if test "x$enable_daemon" = "xyes"; then want_python=yes want_linux_netns=yes - AM_PATH_PYTHON(3.9) - AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])]) - - AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH) - if test "x$sysctl_path" = "xno" ; then - AC_MSG_ERROR([Could not locate sysctl (from procps package).]) - fi - - AC_CHECK_PROG(nftables_path, nft, $as_dir, no, $SEARCHPATH) - if test "x$nftables_path" = "xno" ; then - AC_MSG_ERROR([Could not locate nftables (from nftables package).]) - fi - - AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH) - if test "x$ip_path" = "xno" ; then - AC_MSG_ERROR([Could not locate ip (from iproute package).]) - fi - - AC_CHECK_PROG(tc_path, tc, $as_dir, no, $SEARCHPATH) - if test "x$tc_path" = "xno" ; then - AC_MSG_ERROR([Could not locate tc (from iproute package).]) - fi - - AC_CHECK_PROG(ethtool_path, ethtool, $as_dir, no, $SEARCHPATH) - if test "x$ethtool_path" = "xno" ; then - AC_MSG_ERROR([Could not locate ethtool (from package ethtool)]) - fi - - AC_CHECK_PROG(mount_path, mount, $as_dir, no, $SEARCHPATH) - if test "x$mount_path" = "xno" ; then - AC_MSG_ERROR([Could not locate mount (from package mount)]) - fi - - AC_CHECK_PROG(umount_path, umount, $as_dir, no, $SEARCHPATH) - if test "x$umount_path" = "xno" ; then - AC_MSG_ERROR([Could not locate umount (from package mount)]) - fi - - AC_CHECK_PROG(convert, convert, yes, no, $SEARCHPATH) - if test "x$convert" = "xno" ; then - AC_MSG_WARN([Could not locate ImageMagick convert.]) - fi - - AC_CHECK_PROG(ovs_vs_path, ovs-vsctl, $as_dir, no, $SEARCHPATH) - if test "x$ovs_vs_path" = "xno" ; then - AC_MSG_WARN([Could not locate ovs-vsctl cannot use OVS mode]) - fi - - AC_CHECK_PROG(ovs_of_path, ovs-ofctl, $as_dir, no, $SEARCHPATH) - if test "x$ovs_of_path" = "xno" ; then - AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS mode]) - fi -fi - -if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then - want_linux_netns=yes + # Checks for libraries. + AC_CHECK_LIB([netgraph], [NgMkSockNode]) # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h]) @@ -158,6 +91,51 @@ if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; AC_FUNC_REALLOC AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname]) + AM_PATH_PYTHON(2.7) + + AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH) + if test "x$brctl_path" = "xno" ; then + AC_MSG_ERROR([Could not locate brctl (from bridge-utils package).]) + fi + AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH) + AC_CHECK_PROG(ebtables_path, ebtables, $as_dir, no, $SEARCHPATH) + if test "x$ebtables_path" = "xno" ; then + AC_MSG_ERROR([Could not locate ebtables (from ebtables package).]) + fi + AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH) + if test "x$ip_path" = "xno" ; then + AC_MSG_ERROR([Could not locate ip (from iproute package).]) + fi + AC_CHECK_PROG(tc_path, tc, $as_dir, no, $SEARCHPATH) + if test "x$tc_path" = "xno" ; then + AC_MSG_ERROR([Could not locate tc (from iproute package).]) + fi + AC_CHECK_PROG(mount_path, mount, $as_dir, no, $SEARCHPATH) + AC_CHECK_PROG(umount_path, umount, $as_dir, no, $SEARCHPATH) + AC_CHECK_PROG(convert, convert, yes, no, $SEARCHPATH) + if test "x$convert" = "xno" ; then + AC_MSG_WARN([Could not locate ImageMagick convert.]) + fi + AC_CHECK_PROG(ovs_vs_path, ovs-vsctl, $as_dir, no, $SEARCHPATH) + if test "x$ovs_vs_path" = "xno" ; then + AC_MSG_WARN([Could not locate ovs-vsctl cannot use OVS nodes]) + fi + AC_CHECK_PROG(ovs_of_path, ovs-ofctl, $as_dir, no, $SEARCHPATH) + if test "x$ovs_of_path" = "xno" ; then + AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS nodes]) + 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 PKG_CHECK_MODULES(libev, libev, AC_MSG_RESULT([found libev using pkgconfig OK]) AC_SUBST(libev_CFLAGS) @@ -170,37 +148,52 @@ if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; AC_MSG_ERROR([Python bindings require libev (try installing your 'libev-devel' or 'libev-dev' package)]))) fi -want_docs=no -if [test "x$want_python" = "xyes" && test "x$enable_docs" = "xyes"] ; then - AC_CHECK_PROG(help2man, help2man, yes, no, $SEARCHPATH) +AC_CHECK_PROG(help2man, help2man, yes, no, $SEARCHPATH) - if test "x$help2man" = "xno" ; then +if test "x$help2man" = "xno" ; then AC_MSG_WARN([Could not locate help2man.]) want_docs_missing="$want_docs_missing help2man" - fi - - if test "x$want_docs_missing" = "x" ; then - want_docs=yes - else - AC_MSG_ERROR([Could not find required helper utilities (${want_docs_missing}) so the CORE documentation will not be built.]) - want_docs=no - fi - - # check for sphinx required during make - AC_CHECK_PROG(sphinxapi_path, sphinx-apidoc, $as_dir, no, $SEARCHPATH) - if test "x$sphinxapi_path" = "xno" ; then - AC_MSG_ERROR(["Could not locate sphinx-apidoc, install python3 -m pip install sphinx"]) - want_docs=no - fi - 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 +if test "x$want_docs_missing" = "x" ; then + want_docs=yes +else + AC_MSG_WARN([Could not find required helper utilities (${want_docs_missing}) so the CORE documentation will not be built.]) + want_docs=no +fi + +# check for sphinx required during make +AC_CHECK_PROG(sphinxapi_path, sphinx-apidoc, $as_dir, no, $SEARCHPATH) +if test "x$sphinxapi_path" = "xno" ; then + AC_MSG_ERROR(["Could not location sphinx-apidoc, from the python-sphinx package"]) +fi + +#AC_PATH_PROGS(tcl_path, [tclsh tclsh8.5 tclsh8.4], no) +#if test "x$tcl_path" = "xno" ; then +# AC_MSG_ERROR([Could not locate tclsh. Please install Tcl/Tk.]) +#fi + +#AC_PATH_PROGS(wish_path, [wish wish8.5 wish8.4], no) +#if test "x$wish_path" = "xno" ; then +# AC_MSG_ERROR([Could not locate wish. Please install Tcl/Tk.]) +#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_VNODEDONLY, test x$enable_vnodedonly = xyes) +AM_CONDITIONAL(WANT_INITD, test x$with_startup = xinitd) +AM_CONDITIONAL(WANT_SYSTEMD, test x$with_startup = xsystemd) if test $cross_compiling = no; then AM_MISSING_PROG(HELP2MAN, help2man) @@ -210,14 +203,19 @@ fi # Output files AC_CONFIG_FILES([Makefile + gui/version.tcl + gui/Makefile + gui/icons/Makefile + scripts/Makefile + scripts/perf/Makefile man/Makefile docs/Makefile daemon/Makefile daemon/doc/Makefile daemon/doc/conf.py - daemon/proto/Makefile netns/Makefile - netns/version.h],) + netns/version.h + ns3/Makefile],) AC_OUTPUT # Summary text @@ -231,12 +229,20 @@ Build: Prefix: ${prefix} Exec Prefix: ${exec_prefix} +GUI: + GUI path: ${CORE_LIB_DIR} + GUI config: ${CORE_GUI_CONF_DIR} + Daemon: Daemon path: ${bindir} Daemon config: ${CORE_CONF_DIR} - Python: ${PYTHON} + Python modules: ${pythondir} + Logs: ${CORE_STATE_DIR}/log + +Startup: ${with_startup} Features to build: + Build GUI: ${enable_gui} Build Daemon: ${enable_daemon} Documentation: ${want_docs} diff --git a/corefx/pom.xml b/corefx/pom.xml new file mode 100644 index 00000000..4b6653a1 --- /dev/null +++ b/corefx/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + com.core + corefx + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + 2.1.1 + 2.9.6 + + + + + net.sf.jung + jung-api + ${jung.version} + + + net.sf.jung + jung-graph-impl + ${jung.version} + + + net.sf.jung + jung-algorithms + ${jung.version} + + + net.sf.jung + jung-io + ${jung.version} + + + net.sf.jung + jung-visualization + ${jung.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.squareup.okhttp3 + okhttp + 3.11.0 + + + org.apache.logging.log4j + log4j-api + 2.9.0 + + + org.apache.logging.log4j + log4j-core + 2.9.0 + + + io.socket + socket.io-client + 0.8.3 + + + org.projectlombok + lombok + 1.18.0 + provided + + + commons-net + commons-net + 3.6 + + + com.jfoenix + jfoenix + 8.0.7 + + + + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + com.core.Main + + + + + \ No newline at end of file diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java new file mode 100644 index 00000000..78e7791e --- /dev/null +++ b/corefx/src/main/java/com/core/Controller.java @@ -0,0 +1,543 @@ +package com.core; + +import com.core.client.ICoreClient; +import com.core.client.rest.CoreRestClient; +import com.core.data.*; +import com.core.graph.NetworkGraph; +import com.core.ui.*; +import com.core.ui.dialogs.*; +import com.core.utils.ConfigUtils; +import com.core.utils.Configuration; +import com.core.utils.NodeTypeConfig; +import com.core.websocket.CoreWebSocket; +import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.controls.JFXProgressBar; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingNode; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.MenuItem; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.ItemEvent; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@Data +public class Controller implements Initializable { + private static final Logger logger = LogManager.getLogger(); + @FXML private StackPane stackPane; + @FXML private BorderPane borderPane; + @FXML private VBox top; + @FXML private VBox bottom; + @FXML private SwingNode swingNode; + @FXML private MenuItem saveXmlMenuItem; + @FXML private JFXProgressBar progressBar; + @FXML private CheckMenuItem throughputMenuItem; + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final Map mobilityScripts = new HashMap<>(); + private final Map mobilityPlayerDialogs = new HashMap<>(); + private Application application; + private JFXDecorator decorator; + private Stage window; + private Configuration configuration; + private Map> defaultServices = new HashMap<>(); + + // core client utilities + private ICoreClient coreClient = new CoreRestClient(); + private CoreWebSocket coreWebSocket; + + // ui elements + private NetworkGraph networkGraph = new NetworkGraph(this); + private AnnotationToolbar annotationToolbar = new AnnotationToolbar(networkGraph); + private NodeDetails nodeDetails = new NodeDetails(this); + private LinkDetails linkDetails = new LinkDetails(this); + private GraphToolbar graphToolbar = new GraphToolbar(this); + + // dialogs + private SessionsDialog sessionsDialog = new SessionsDialog(this); + private ServiceDialog serviceDialog = new ServiceDialog(this); + private NodeServicesDialog nodeServicesDialog = new NodeServicesDialog(this); + private NodeEmaneDialog nodeEmaneDialog = new NodeEmaneDialog(this); + private NodeWlanDialog nodeWlanDialog = new NodeWlanDialog(this); + private ConfigDialog configDialog = new ConfigDialog(this); + private HooksDialog hooksDialog = new HooksDialog(this); + private MobilityDialog mobilityDialog = new MobilityDialog(this); + private ChartDialog chartDialog = new ChartDialog(this); + private NodeTypesDialog nodeTypesDialog = new NodeTypesDialog(this); + private BackgroundDialog backgroundDialog = new BackgroundDialog(this); + private LocationDialog locationDialog = new LocationDialog(this); + private GeoDialog geoDialog = new GeoDialog(this); + private ConnectDialog connectDialog = new ConnectDialog(this); + private GuiPreferencesDialog guiPreferencesDialog = new GuiPreferencesDialog(this); + private NodeTypeCreateDialog nodeTypeCreateDialog = new NodeTypeCreateDialog(this); + + public void connectToCore(String address, int port) { + coreWebSocket.stop(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.submit(() -> { + try { + coreWebSocket.start(address, port); + coreClient.setConnection(address, port); + initialJoin(); + } catch (IOException | URISyntaxException ex) { + Toast.error(String.format("Connection failure: %s", ex.getMessage()), ex); + Platform.runLater(() -> connectDialog.showDialog()); + } + }); + } + + private void initialJoin() throws IOException { + Map> serviceGroups = coreClient.getServices(); + logger.info("core services: {}", serviceGroups); + nodeServicesDialog.setServices(serviceGroups); + nodeTypeCreateDialog.setServices(serviceGroups); + + logger.info("initial core session join"); + List sessions = coreClient.getSessions(); + + logger.info("existing sessions: {}", sessions); + Integer sessionId; + if (sessions.isEmpty()) { + logger.info("creating initial session"); + SessionOverview sessionOverview = coreClient.createSession(); + sessionId = sessionOverview.getId(); + Toast.info(String.format("Created Session %s", sessionId)); + } else { + SessionOverview sessionOverview = sessions.get(0); + sessionId = sessionOverview.getId(); + Toast.info(String.format("Joined Session %s", sessionId)); + } + + joinSession(sessionId); + + // set emane models + List emaneModels = coreClient.getEmaneModels(); + nodeEmaneDialog.setModels(emaneModels); + } + + public void joinSession(Integer sessionId) throws IOException { + // clear graph + networkGraph.reset(); + + // clear out any previously set information + mobilityPlayerDialogs.clear(); + mobilityScripts.clear(); + mobilityDialog.setNode(null); + Platform.runLater(() -> borderPane.setRight(null)); + + // get session to join + Session session = coreClient.getSession(sessionId); + SessionState sessionState = SessionState.get(session.getState()); + + // update client to use this session + coreClient.updateSession(sessionId); + coreClient.updateState(sessionState); + + // display all nodes + logger.info("joining core session({}) state({}): {}", sessionId, sessionState, session); + for (CoreNode node : session.getNodes()) { + NodeType nodeType = NodeType.find(node.getType(), node.getModel()); + if (nodeType == null) { + logger.info(String.format("failed to find node type(%s) model(%s): %s", + node.getType(), node.getModel(), node.getName())); + continue; + } + + node.setNodeType(nodeType); + networkGraph.addNode(node); + } + + // display all links + for (CoreLink link : session.getLinks()) { + if (link.getInterfaceOne() != null || link.getInterfaceTwo() != null) { + link.setType(LinkTypes.WIRED.getValue()); + } + + networkGraph.addLink(link); + } + + // refresh graph + networkGraph.getGraphViewer().repaint(); + + // update other components for new session + graphToolbar.setRunButton(coreClient.isRunning()); + hooksDialog.updateHooks(); + + // update session default services + setCoreDefaultServices(); + + // retrieve current mobility script configurations and show dialogs + Map mobilityConfigMap = coreClient.getMobilityConfigs(); + mobilityScripts.putAll(mobilityConfigMap); + showMobilityScriptDialogs(); + + Platform.runLater(() -> decorator.setTitle(String.format("CORE (Session %s)", sessionId))); + } + + public boolean startSession() throws IOException { + // force nodes to get latest positions + networkGraph.updatePositions(); + + // retrieve items for creation/start + Collection nodes = networkGraph.getGraph().getVertices(); + Collection links = networkGraph.getGraph().getEdges(); + List hooks = hooksDialog.getHooks(); + + // start/create session + progressBar.setVisible(true); + boolean result = coreClient.start(nodes, links, hooks); + progressBar.setVisible(false); + if (result) { + showMobilityScriptDialogs(); + saveXmlMenuItem.setDisable(false); + } + return result; + } + + public boolean stopSession() throws IOException { + // clear out any drawn wireless links + List wirelessLinks = networkGraph.getGraph().getEdges().stream() + .filter(CoreLink::isWireless) + .collect(Collectors.toList()); + wirelessLinks.forEach(networkGraph::removeWirelessLink); + networkGraph.getGraphViewer().repaint(); + + // stop session + progressBar.setVisible(true); + boolean result = coreClient.stop(); + progressBar.setVisible(false); + if (result) { + saveXmlMenuItem.setDisable(true); + } + return result; + } + + public void handleThroughputs(Throughputs throughputs) { + for (InterfaceThroughput interfaceThroughput : throughputs.getInterfaces()) { + int nodeId = interfaceThroughput.getNode(); + CoreNode node = networkGraph.getVertex(nodeId); + Collection links = networkGraph.getGraph().getIncidentEdges(node); + int interfaceId = interfaceThroughput.getNodeInterface(); + for (CoreLink link : links) { + if (nodeId == link.getNodeOne()) { + if (interfaceId == link.getInterfaceOne().getId()) { + link.setThroughput(interfaceThroughput.getThroughput()); + } + } else { + if (interfaceId == link.getInterfaceTwo().getId()) { + link.setThroughput(interfaceThroughput.getThroughput()); + } + } + } + } + networkGraph.getGraphViewer().repaint(); + } + + private void setCoreDefaultServices() { + try { + coreClient.setDefaultServices(defaultServices); + } catch (IOException ex) { + Toast.error("Error updating core default services", ex); + } + } + + public void updateNodeTypes() { + graphToolbar.setupNodeTypes(); + setCoreDefaultServices(); + try { + ConfigUtils.save(configuration); + } catch (IOException ex) { + Toast.error("Error saving configuration", ex); + } + } + + public void deleteNode(CoreNode node) { + networkGraph.removeNode(node); + CoreNode mobilityNode = mobilityDialog.getNode(); + if (mobilityNode != null && mobilityNode.getId().equals(node.getId())) { + mobilityDialog.setNode(null); + } + } + + void setWindow(Stage window) { + this.window = window; + sessionsDialog.setOwner(window); + hooksDialog.setOwner(window); + nodeServicesDialog.setOwner(window); + serviceDialog.setOwner(window); + nodeWlanDialog.setOwner(window); + nodeEmaneDialog.setOwner(window); + configDialog.setOwner(window); + mobilityDialog.setOwner(window); + nodeTypesDialog.setOwner(window); + backgroundDialog.setOwner(window); + locationDialog.setOwner(window); + connectDialog.setOwner(window); + guiPreferencesDialog.setOwner(window); + nodeTypeCreateDialog.setOwner(window); + } + + private void showMobilityScriptDialogs() { + for (Map.Entry entry : mobilityScripts.entrySet()) { + Integer nodeId = entry.getKey(); + CoreNode node = networkGraph.getVertex(nodeId); + MobilityConfig mobilityConfig = entry.getValue(); + Platform.runLater(() -> { + MobilityPlayerDialog mobilityPlayerDialog = new MobilityPlayerDialog(this, node); + mobilityPlayerDialog.setOwner(window); + mobilityPlayerDialogs.put(nodeId, mobilityPlayerDialog); + mobilityPlayerDialog.showDialog(mobilityConfig); + }); + } + } + + @FXML + private void onCoreMenuConnect(ActionEvent event) { + logger.info("showing connect!"); + connectDialog.showDialog(); + } + + @FXML + private void onOptionsMenuNodeTypes(ActionEvent event) { + nodeTypesDialog.showDialog(); + } + + @FXML + private void onOptionsMenuBackground(ActionEvent event) { + backgroundDialog.showDialog(); + } + + @FXML + private void onOptionsMenuLocation(ActionEvent event) { + locationDialog.showDialog(); + } + + @FXML + private void onOptionsMenuPreferences(ActionEvent event) { + guiPreferencesDialog.showDialog(); + } + + @FXML + private void onHelpMenuWebsite(ActionEvent event) { + application.getHostServices().showDocument("https://github.com/coreemu/core"); + } + + @FXML + private void onHelpMenuDocumentation(ActionEvent event) { + application.getHostServices().showDocument("http://coreemu.github.io/core/"); + } + + @FXML + private void onHelpMenuMailingList(ActionEvent event) { + application.getHostServices().showDocument("https://publists.nrl.navy.mil/mailman/listinfo/core-users"); + } + + @FXML + private void onOpenXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Session"); + fileChooser.setInitialDirectory(new File(configuration.getXmlPath())); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + try { + File file = fileChooser.showOpenDialog(window); + if (file != null) { + openXml(file); + } + } catch (IllegalArgumentException ex) { + Toast.error(String.format("Invalid XML directory: %s", configuration.getXmlPath())); + } + } + + private void openXml(File file) { + logger.info("opening session xml: {}", file.getPath()); + try { + SessionOverview sessionOverview = coreClient.openSession(file); + Integer sessionId = sessionOverview.getId(); + joinSession(sessionId); + Toast.info(String.format("Joined Session %s", sessionId)); + } catch (IOException ex) { + Toast.error("Error opening session xml", ex); + } + } + + @FXML + private void onSaveXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save Session"); + fileChooser.setInitialFileName("session.xml"); + fileChooser.setInitialDirectory(new File(configuration.getXmlPath())); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + File file = fileChooser.showSaveDialog(window); + if (file != null) { + logger.info("saving session xml: {}", file.getPath()); + try { + coreClient.saveSession(file); + } catch (IOException ex) { + Toast.error("Error saving session xml", ex); + } + } + } + + @FXML + private void onSessionMenu(ActionEvent event) { + logger.info("sessions menu clicked"); + try { + sessionsDialog.showDialog(); + } catch (IOException ex) { + Toast.error("Error retrieving sessions", ex); + } + } + + @FXML + private void onSessionNodesMenu(ActionEvent event) { + + } + + @FXML + private void onSessionHooksMenu(ActionEvent event) { + hooksDialog.showDialog(); + } + + @FXML + private void onSessionOptionsMenu(ActionEvent event) { + try { + List configGroups = coreClient.getSessionConfig(); + configDialog.showDialog("Session Options", configGroups, () -> { + List options = configDialog.getOptions(); + try { + boolean result = coreClient.setSessionConfig(options); + if (result) { + Toast.info("Session options saved"); + } else { + Toast.error("Failure to set session config"); + } + } catch (IOException ex) { + logger.error("error getting session config"); + } + }); + } catch (IOException ex) { + logger.error("error getting session config"); + } + } + + @FXML + private void onTestMenuCharts(ActionEvent event) { + chartDialog.show(); + } + + @FXML + private void onTestMenuGeo(ActionEvent event) { + geoDialog.showDialog(); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + coreWebSocket = new CoreWebSocket(this); + configuration = ConfigUtils.load(); + String address = configuration.getCoreAddress(); + int port = configuration.getCorePort(); + logger.info("core connection: {}:{}", address, port); + connectDialog.setAddress(address); + connectDialog.setPort(port); + connectToCore(address, port); + + logger.info("controller initialize"); + swingNode.setContent(networkGraph.getGraphViewer()); + + // update graph preferences + networkGraph.updatePreferences(configuration); + + // set node types / default services + graphToolbar.setupNodeTypes(); + defaultServices = configuration.getNodeTypeConfigs().stream() + .collect(Collectors.toMap(NodeTypeConfig::getModel, NodeTypeConfig::getServices)); + + // set graph toolbar + borderPane.setLeft(graphToolbar); + + // setup snackbar + Toast.setSnackbarRoot(stackPane); + + // setup throughput menu item + throughputMenuItem.setOnAction(event -> executorService.submit(new ChangeThroughputTask())); + + // node details + networkGraph.getGraphViewer().getPickedVertexState().addItemListener(event -> { + CoreNode node = (CoreNode) event.getItem(); + logger.info("picked: {}", node.getName()); + if (event.getStateChange() == ItemEvent.SELECTED) { + Platform.runLater(() -> { + nodeDetails.setNode(node); + borderPane.setRight(nodeDetails); + }); + } else { + Platform.runLater(() -> borderPane.setRight(null)); + } + }); + + // edge details + networkGraph.getGraphViewer().getPickedEdgeState().addItemListener(event -> { + CoreLink link = (CoreLink) event.getItem(); + logger.info("picked: {} - {}", link.getNodeOne(), link.getNodeTwo()); + if (event.getStateChange() == ItemEvent.SELECTED) { + Platform.runLater(() -> { + linkDetails.setLink(link); + borderPane.setRight(linkDetails); + }); + } else { + Platform.runLater(() -> borderPane.setRight(null)); + } + }); + } + + private class ChangeThroughputTask extends Task { + @Override + protected Boolean call() throws Exception { + if (throughputMenuItem.isSelected()) { + return coreClient.startThroughput(); + } else { + return coreClient.stopThroughput(); + } + } + + @Override + protected void succeeded() { + if (getValue()) { + if (throughputMenuItem.isSelected()) { + networkGraph.setShowThroughput(true); + } else { + networkGraph.setShowThroughput(false); + networkGraph.getGraph().getEdges().forEach(edge -> edge.setThroughput(0)); + networkGraph.getGraphViewer().repaint(); + } + } else { + Toast.error("Failure changing throughput"); + } + } + + @Override + protected void failed() { + Toast.error("Error changing throughput", new RuntimeException(getException())); + } + } +} diff --git a/corefx/src/main/java/com/core/Main.java b/corefx/src/main/java/com/core/Main.java new file mode 100644 index 00000000..4834278f --- /dev/null +++ b/corefx/src/main/java/com/core/Main.java @@ -0,0 +1,72 @@ +package com.core; + +import com.core.utils.ConfigUtils; +import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.svg.SVGGlyphLoader; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Font; +import javafx.stage.Stage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class Main extends Application { + private static final Path LOG_FILE = Paths.get(System.getProperty("user.home"), ".core", "core.log"); + + @Override + public void start(Stage window) throws Exception { + // set core dir property for logging + System.setProperty("core_log", LOG_FILE.toString()); + + // check for and create gui home directory + ConfigUtils.checkHomeDirectory(); + + // load svg icons + SVGGlyphLoader.loadGlyphsFont(getClass().getResourceAsStream("/icons/icomoon_material.svg"), + "icomoon.svg"); + + // load font + Font.loadFont(getClass().getResourceAsStream("/font/roboto/Roboto-Regular.ttf"), 10); + + // load main fxml + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml")); + Parent root = loader.load(); + + // window decorator + JFXDecorator decorator = new JFXDecorator(window, root); + decorator.setCustomMaximize(true); + decorator.setMaximized(true); + decorator.setTitle("CORE"); + Image coreIcon = new Image(getClass().getResourceAsStream("/core-icon.png")); + decorator.setGraphic(new ImageView(coreIcon)); + window.getIcons().add(coreIcon); + + // create scene and set as current scene within window + Scene scene = new Scene(decorator); + scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm()); + window.setScene(scene); + + // update controller + Controller controller = loader.getController(); + controller.setApplication(this); + controller.setWindow(window); + controller.setDecorator(decorator); + + // configure window + window.setOnCloseRequest(event -> { + Platform.exit(); + System.exit(0); + }); + window.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/corefx/src/main/java/com/core/client/ICoreClient.java b/corefx/src/main/java/com/core/client/ICoreClient.java new file mode 100644 index 00000000..fa06395d --- /dev/null +++ b/corefx/src/main/java/com/core/client/ICoreClient.java @@ -0,0 +1,118 @@ +package com.core.client; + +import com.core.client.rest.ServiceFile; +import com.core.client.rest.WlanConfig; +import com.core.data.*; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface ICoreClient { + void setConnection(String address, int port); + + boolean isLocalConnection(); + + Integer currentSession(); + + boolean startThroughput() throws IOException; + + boolean stopThroughput() throws IOException; + + void updateSession(Integer sessionId); + + void updateState(SessionState state); + + SessionOverview createSession() throws IOException; + + boolean deleteSession(Integer sessionId) throws IOException; + + List getSessions() throws IOException; + + Session getSession(Integer sessionId) throws IOException; + + boolean start(Collection nodes, Collection links, List hooks) throws IOException; + + boolean stop() throws IOException; + + boolean setState(SessionState state) throws IOException; + + Map> getServices() throws IOException; + + Map> getDefaultServices() throws IOException; + + boolean setDefaultServices(Map> defaults) throws IOException; + + CoreService getService(CoreNode node, String serviceName) throws IOException; + + boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException; + + String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException; + + boolean startService(CoreNode node, String serviceName) throws IOException; + + boolean stopService(CoreNode node, String serviceName) throws IOException; + + boolean restartService(CoreNode node, String serviceName) throws IOException; + + boolean validateService(CoreNode node, String serviceName) throws IOException; + + boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException; + + List getEmaneConfig(CoreNode node) throws IOException; + + List getEmaneModels() throws IOException; + + boolean setEmaneConfig(CoreNode node, List options) throws IOException; + + List getEmaneModelConfig(Integer id, String model) throws IOException; + + boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException; + + boolean isRunning(); + + void saveSession(File file) throws IOException; + + SessionOverview openSession(File file) throws IOException; + + List getSessionConfig() throws IOException; + + boolean setSessionConfig(List configOptions) throws IOException; + + boolean createNode(CoreNode node) throws IOException; + + String nodeCommand(CoreNode node, String command) throws IOException; + + boolean editNode(CoreNode node) throws IOException; + + boolean deleteNode(CoreNode node) throws IOException; + + boolean createLink(CoreLink link) throws IOException; + + boolean editLink(CoreLink link) throws IOException; + + boolean createHook(Hook hook) throws IOException; + + List getHooks() throws IOException; + + WlanConfig getWlanConfig(CoreNode node) throws IOException; + + boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException; + + String getTerminalCommand(CoreNode node) throws IOException; + + Map getMobilityConfigs() throws IOException; + + boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException; + + MobilityConfig getMobilityConfig(CoreNode node) throws IOException; + + boolean mobilityAction(CoreNode node, String action) throws IOException; + + LocationConfig getLocationConfig() throws IOException; + + boolean setLocationConfig(LocationConfig config) throws IOException; +} diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java new file mode 100644 index 00000000..3e963583 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java @@ -0,0 +1,415 @@ +package com.core.client.rest; + +import com.core.client.ICoreClient; +import com.core.data.*; +import com.core.utils.WebUtils; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +@Data +public class CoreRestClient implements ICoreClient { + private static final Logger logger = LogManager.getLogger(); + private String address; + private int port; + private Integer sessionId; + private SessionState sessionState; + + @Override + public void setConnection(String address, int port) { + this.address = address; + this.port = port; + } + + @Override + public boolean isLocalConnection() { + return address.equals("127.0.0.1") || address.equals("localhost"); + } + + @Override + public Integer currentSession() { + return sessionId; + } + + @Override + public void updateState(SessionState state) { + sessionState = state; + } + + @Override + public void updateSession(Integer sessionId) { + this.sessionId = sessionId; + } + + private String getUrl(String path) { + return String.format("http://%s:%s/%s", address, port, path); + } + + @Override + public SessionOverview createSession() throws IOException { + String url = getUrl("sessions"); + return WebUtils.post(url, SessionOverview.class); + } + + @Override + public boolean deleteSession(Integer sessionId) throws IOException { + String path = String.format("sessions/%s", sessionId); + String url = getUrl(path); + return WebUtils.delete(url); + } + + public Map> getServices() throws IOException { + String url = getUrl("services"); + GetServices getServices = WebUtils.getJson(url, GetServices.class); + return getServices.getGroups(); + } + + @Override + public Session getSession(Integer sessionId) throws IOException { + String path = String.format("sessions/%s", sessionId); + String url = getUrl(path); + return WebUtils.getJson(url, Session.class); + } + + @Override + public List getSessions() throws IOException { + String url = getUrl("sessions"); + GetSessions getSessions = WebUtils.getJson(url, GetSessions.class); + return getSessions.getSessions(); + } + + @Override + public boolean start(Collection nodes, Collection links, List hooks) throws IOException { + boolean result = setState(SessionState.DEFINITION); + if (!result) { + return false; + } + + result = setState(SessionState.CONFIGURATION); + if (!result) { + return false; + } + + for (Hook hook : hooks) { + if (!createHook(hook)) { + return false; + } + } + + for (CoreNode node : nodes) { + // must pre-configure wlan nodes, if not already + if (node.getNodeType().getValue() == NodeType.WLAN) { + WlanConfig config = getWlanConfig(node); + setWlanConfig(node, config); + } + + if (!createNode(node)) { + return false; + } + } + + for (CoreLink link : links) { + if (!createLink(link)) { + return false; + } + } + + return setState(SessionState.INSTANTIATION); + } + + @Override + public boolean stop() throws IOException { + return setState(SessionState.SHUTDOWN); + } + + @Override + public boolean setState(SessionState state) throws IOException { + String url = getUrl(String.format("sessions/%s/state", sessionId)); + Map data = new HashMap<>(); + data.put("state", state.getValue()); + boolean result = WebUtils.putJson(url, data); + + if (result) { + sessionState = state; + } + return result; + } + + private boolean uploadFile(File file) throws IOException { + String url = getUrl("upload"); + return WebUtils.postFile(url, file); + } + + @Override + public boolean startThroughput() throws IOException { + String url = getUrl("throughput/start"); + return WebUtils.putJson(url); + } + + @Override + public boolean stopThroughput() throws IOException { + String url = getUrl("throughput/stop"); + return WebUtils.putJson(url); + } + + @Override + public Map> getDefaultServices() throws IOException { + String url = getUrl(String.format("sessions/%s/services/default", sessionId)); + GetDefaultServices getDefaultServices = WebUtils.getJson(url, GetDefaultServices.class); + return getDefaultServices.getDefaults(); + } + + @Override + public boolean setDefaultServices(Map> defaults) throws IOException { + String url = getUrl(String.format("sessions/%s/services/default", sessionId)); + return WebUtils.postJson(url, defaults); + } + + @Override + public CoreService getService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); + return WebUtils.getJson(url, CoreService.class); + } + + @Override + public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); + return WebUtils.putJson(url, service); + } + + @Override + public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), + serviceName)); + Map args = new HashMap<>(); + args.put("file", fileName); + return WebUtils.getJson(url, String.class, args); + } + + @Override + public boolean startService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/start", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean stopService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/stop", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean restartService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/restart", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean validateService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/validate", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url, serviceFile); + } + + @Override + public List getEmaneModels() throws IOException { + String url = getUrl(String.format("sessions/%s/emane/models", sessionId)); + GetEmaneModels getEmaneModels = WebUtils.getJson(url, GetEmaneModels.class); + return getEmaneModels.getModels(); + } + + @Override + public List getEmaneModelConfig(Integer id, String model) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); + Map args = new HashMap<>(); + args.put("node", id.toString()); + args.put("name", model); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); + return getConfig.getGroups(); + } + + @Override + public List getEmaneConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); + Map args = new HashMap<>(); + args.put("node", node.getId().toString()); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); + return getConfig.getGroups(); + } + + @Override + public boolean setEmaneConfig(CoreNode node, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); + SetEmaneConfig setEmaneConfig = new SetEmaneConfig(); + setEmaneConfig.setNode(node.getId()); + setEmaneConfig.setValues(options); + return WebUtils.putJson(url, setEmaneConfig); + } + + @Override + public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); + SetEmaneModelConfig setEmaneModelConfig = new SetEmaneModelConfig(); + setEmaneModelConfig.setNode(id); + setEmaneModelConfig.setName(model); + setEmaneModelConfig.setValues(options); + return WebUtils.putJson(url, setEmaneModelConfig); + } + + @Override + public boolean isRunning() { + return sessionState == SessionState.RUNTIME; + } + + @Override + public void saveSession(File file) throws IOException { + String path = String.format("sessions/%s/xml", sessionId); + String url = getUrl(path); + WebUtils.getFile(url, file); + } + + @Override + public SessionOverview openSession(File file) throws IOException { + String url = getUrl("sessions/xml"); + return WebUtils.postFile(url, file, SessionOverview.class); + } + + @Override + public List getSessionConfig() throws IOException { + String url = getUrl(String.format("sessions/%s/options", sessionId)); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class); + return getConfig.getGroups(); + } + + @Override + public boolean setSessionConfig(List configOptions) throws IOException { + String url = getUrl(String.format("sessions/%s/options", sessionId)); + SetConfig setConfig = new SetConfig(configOptions); + return WebUtils.putJson(url, setConfig); + } + + @Override + public LocationConfig getLocationConfig() throws IOException { + String url = getUrl(String.format("sessions/%s/location", sessionId)); + return WebUtils.getJson(url, LocationConfig.class); + } + + @Override + public boolean setLocationConfig(LocationConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/location", sessionId)); + return WebUtils.putJson(url, config); + } + + @Override + public String nodeCommand(CoreNode node, String command) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/command", sessionId, node.getId())); + return WebUtils.putJson(url, command, String.class); + } + + @Override + public boolean createNode(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes", sessionId)); + return WebUtils.postJson(url, node); + } + + + @Override + public boolean editNode(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s", sessionId, node.getId())); + return WebUtils.putJson(url, node); + } + + @Override + public boolean deleteNode(CoreNode node) throws IOException { + String url = getUrl(String.format("/sessions/%s/nodes/%s", sessionId, node.getId())); + return WebUtils.delete(url); + } + + @Override + public boolean createLink(CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", sessionId)); + return WebUtils.postJson(url, link); + } + + @Override + public boolean editLink(CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", sessionId)); + return WebUtils.putJson(url, link); + } + + @Override + public boolean createHook(Hook hook) throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", sessionId)); + return WebUtils.postJson(url, hook); + } + + @Override + public List getHooks() throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", sessionId)); + GetHooks getHooks = WebUtils.getJson(url, GetHooks.class); + return getHooks.getHooks(); + } + + @Override + public WlanConfig getWlanConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); + return WebUtils.getJson(url, WlanConfig.class); + } + + @Override + public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); + return WebUtils.putJson(url, config); + } + + @Override + public String getTerminalCommand(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/terminal", sessionId, node.getId())); + return WebUtils.getJson(url, String.class); + } + + @Override + public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { + boolean uploaded = uploadFile(config.getScriptFile()); + if (!uploaded) { + throw new IOException("failed to upload mobility script"); + } + + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); + config.setFile(config.getScriptFile().getName()); + return WebUtils.postJson(url, config); + } + + @Override + public Map getMobilityConfigs() throws IOException { + String url = getUrl(String.format("sessions/%s/mobility/configs", sessionId)); + GetMobilityConfigs getMobilityConfigs = WebUtils.getJson(url, GetMobilityConfigs.class); + return getMobilityConfigs.getConfigurations(); + } + + @Override + public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); + return WebUtils.getJson(url, MobilityConfig.class); + } + + @Override + public boolean mobilityAction(CoreNode node, String action) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility/%s", sessionId, node.getId(), action)); + return WebUtils.putJson(url); + } +} diff --git a/corefx/src/main/java/com/core/client/rest/GetConfig.java b/corefx/src/main/java/com/core/client/rest/GetConfig.java new file mode 100644 index 00000000..61a843b6 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetConfig.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.ConfigGroup; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetConfig { + private List groups = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java new file mode 100644 index 00000000..b134cbc0 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java @@ -0,0 +1,16 @@ +package com.core.client.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetDefaultServices { + private Map> defaults = new HashMap<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java new file mode 100644 index 00000000..43cef1c6 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java @@ -0,0 +1,11 @@ +package com.core.client.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetEmaneModels { + private List models = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetHooks.java b/corefx/src/main/java/com/core/client/rest/GetHooks.java new file mode 100644 index 00000000..fe26e828 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetHooks.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.Hook; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetHooks { + private List hooks = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java new file mode 100644 index 00000000..e5905b7e --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.MobilityConfig; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class GetMobilityConfigs { + private Map configurations = new HashMap<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetServices.java b/corefx/src/main/java/com/core/client/rest/GetServices.java new file mode 100644 index 00000000..17e04ddc --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetServices.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +public class GetServices { + private Map> groups; +} diff --git a/corefx/src/main/java/com/core/client/rest/GetSessions.java b/corefx/src/main/java/com/core/client/rest/GetSessions.java new file mode 100644 index 00000000..674fc83a --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetSessions.java @@ -0,0 +1,14 @@ +package com.core.client.rest; + +import com.core.data.SessionOverview; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class GetSessions { + private List sessions = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/ServiceFile.java b/corefx/src/main/java/com/core/client/rest/ServiceFile.java new file mode 100644 index 00000000..977ed984 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/ServiceFile.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServiceFile { + private String name; + private String data; +} diff --git a/corefx/src/main/java/com/core/client/rest/SetConfig.java b/corefx/src/main/java/com/core/client/rest/SetConfig.java new file mode 100644 index 00000000..0d3f11ea --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetConfig.java @@ -0,0 +1,16 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SetConfig { + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java new file mode 100644 index 00000000..ee947c51 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneConfig { + private Integer node; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java new file mode 100644 index 00000000..e5c37092 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java @@ -0,0 +1,14 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneModelConfig { + private Integer node; + private String name; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/WlanConfig.java b/corefx/src/main/java/com/core/client/rest/WlanConfig.java new file mode 100644 index 00000000..45b9805f --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/WlanConfig.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import lombok.Data; + +@Data +public class WlanConfig { + private String range; + private String bandwidth; + private String jitter; + private String delay; + private String error; +} diff --git a/corefx/src/main/java/com/core/data/BridgeThroughput.java b/corefx/src/main/java/com/core/data/BridgeThroughput.java new file mode 100644 index 00000000..167fb402 --- /dev/null +++ b/corefx/src/main/java/com/core/data/BridgeThroughput.java @@ -0,0 +1,9 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class BridgeThroughput { + private int node; + private Double throughput; +} diff --git a/corefx/src/main/java/com/core/data/ConfigDataType.java b/corefx/src/main/java/com/core/data/ConfigDataType.java new file mode 100644 index 00000000..3dd44864 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigDataType.java @@ -0,0 +1,40 @@ +package com.core.data; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum ConfigDataType { + UINT8(1), + UINT16(2), + UINT32(3), + UINT64(4), + INT8(5), + INT16(6), + INT32(7), + INT64(8), + FLOAT(9), + STRING(10), + BOOL(11); + + private static final Map LOOKUP = new HashMap<>(); + + static { + Arrays.stream(ConfigDataType.values()).forEach(x -> LOOKUP.put(x.getValue(), x)); + } + + private final int value; + + ConfigDataType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + + public static ConfigDataType get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/ConfigGroup.java b/corefx/src/main/java/com/core/data/ConfigGroup.java new file mode 100644 index 00000000..9b45bdb7 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigGroup.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigGroup { + private String name; + private List options = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/ConfigOption.java b/corefx/src/main/java/com/core/data/ConfigOption.java new file mode 100644 index 00000000..77af265e --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigOption.java @@ -0,0 +1,15 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigOption { + private String label; + private String name; + private String value; + private Integer type; + private List select = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/CoreEvent.java b/corefx/src/main/java/com/core/data/CoreEvent.java new file mode 100644 index 00000000..9ac8ed03 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreEvent.java @@ -0,0 +1,19 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.Data; + +@Data +public class CoreEvent { + private Integer session; + private Integer node; + private String name; + private Double time; + private EventType eventType; + private String data; + + @JsonSetter("event_type") + public void setEventType(int value) { + eventType = EventType.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/CoreInterface.java b/corefx/src/main/java/com/core/data/CoreInterface.java new file mode 100644 index 00000000..f159f10e --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreInterface.java @@ -0,0 +1,19 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CoreInterface { + private Integer id; + private String name; + private String mac; + private String ip4; + @JsonProperty("ip4mask") + private Integer ip4Mask; + private String ip6; + @JsonProperty("ip6mask") + private String ip6Mask; +} diff --git a/corefx/src/main/java/com/core/data/CoreLink.java b/corefx/src/main/java/com/core/data/CoreLink.java new file mode 100644 index 00000000..f3d4a215 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLink.java @@ -0,0 +1,56 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class CoreLink { + @EqualsAndHashCode.Include + private Integer id; + + @JsonIgnore + private Float weight = 1.0f; + + @JsonIgnore + private boolean loaded = true; + + @JsonIgnore + private double throughput; + + @JsonIgnore + private boolean visible = true; + + @JsonProperty("message_type") + private Integer messageType; + + private Integer type = 1; + + @JsonProperty("node_one") + private Integer nodeOne; + + @JsonProperty("node_two") + private Integer nodeTwo; + + @JsonProperty("interface_one") + private CoreInterface interfaceOne; + + @JsonProperty("interface_two") + private CoreInterface interfaceTwo; + + private CoreLinkOptions options = new CoreLinkOptions(); + + public CoreLink(Integer id) { + this.id = id; + this.weight = (float) id; + this.loaded = false; + } + + public boolean isWireless() { + return interfaceOne == null && interfaceTwo == null; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreLinkOptions.java b/corefx/src/main/java/com/core/data/CoreLinkOptions.java new file mode 100644 index 00000000..6556d3c0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLinkOptions.java @@ -0,0 +1,21 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CoreLinkOptions { + private String opaque; + private Integer session; + private Double jitter; + private Integer key; + private Double mburst; + private Double mer; + private Double per; + private Double bandwidth; + private Double burst; + private Double delay; + private Double dup; + private Integer unidirectional; +} diff --git a/corefx/src/main/java/com/core/data/CoreNode.java b/corefx/src/main/java/com/core/data/CoreNode.java new file mode 100644 index 00000000..f441bd69 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreNode.java @@ -0,0 +1,59 @@ +package com.core.data; + +import com.core.graph.RadioIcon; +import com.core.utils.IconUtils; +import com.fasterxml.jackson.annotation.JsonIgnore; +import edu.uci.ics.jung.visualization.LayeredIcon; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashSet; +import java.util.Set; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class CoreNode { + private static final Logger logger = LogManager.getLogger(); + @EqualsAndHashCode.Include + private Integer id; + private String name; + private Integer type; + private String model; + private Position position = new Position(); + private Set services = new HashSet<>(); + private String emane; + private String url; + @JsonIgnore + private NodeType nodeType; + @JsonIgnore + private String icon; + @JsonIgnore + private boolean loaded = true; + @JsonIgnore + private LayeredIcon graphIcon; + @JsonIgnore + private RadioIcon radioIcon = new RadioIcon(); + + public CoreNode(Integer id) { + this.id = id; + this.name = String.format("Node%s", this.id); + this.loaded = false; + } + + public void setNodeType(NodeType nodeType) { + type = nodeType.getValue(); + model = nodeType.getModel(); + icon = nodeType.getIcon(); + if (icon.startsWith("file:")) { + graphIcon = IconUtils.getExternalLayeredIcon(icon); + } else { + graphIcon = IconUtils.getLayeredIcon(icon); + } + graphIcon.add(radioIcon); + this.nodeType = nodeType; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreService.java b/corefx/src/main/java/com/core/data/CoreService.java new file mode 100644 index 00000000..ed9dd230 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreService.java @@ -0,0 +1,23 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class CoreService { + private List executables = new ArrayList<>(); + private List dependencies = new ArrayList<>(); + private List dirs = new ArrayList<>(); + private List configs = new ArrayList<>(); + private List startup = new ArrayList<>(); + private List validate = new ArrayList<>(); + @JsonProperty("validation_mode") + private String validationMode; + @JsonProperty("validation_timer") + private String validationTimer; + private List shutdown = new ArrayList<>(); + private String meta; +} diff --git a/corefx/src/main/java/com/core/data/EventType.java b/corefx/src/main/java/com/core/data/EventType.java new file mode 100644 index 00000000..487de4c1 --- /dev/null +++ b/corefx/src/main/java/com/core/data/EventType.java @@ -0,0 +1,45 @@ +package com.core.data; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum EventType { + NONE(0), + DEFINITION_STATE(1), + CONFIGURATION_STATE(2), + INSTANTIATION_STATE(3), + RUNTIME_STATE(4), + DATACOLLECT_STATE(5), + SHUTDOWN_STATE(6), + START(7), + STOP(8), + PAUSE(9), + RESTART(10), + FILE_OPEN(11), + FILE_SAVE(12), + SCHEDULED(13), + RECONFIGURE(14), + INSTANTIATION_COMPLETE(15); + + private static final Map LOOKUP = new HashMap<>(); + + static { + Arrays.stream(EventType.values()).forEach(x -> LOOKUP.put(x.getValue(), x)); + } + + private final int value; + + EventType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + + public static EventType get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Hook.java b/corefx/src/main/java/com/core/data/Hook.java new file mode 100644 index 00000000..35b1a934 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Hook.java @@ -0,0 +1,13 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +@Data +public class Hook { + private String file; + private Integer state; + @JsonIgnore + private String stateDisplay; + private String data; +} diff --git a/corefx/src/main/java/com/core/data/InterfaceThroughput.java b/corefx/src/main/java/com/core/data/InterfaceThroughput.java new file mode 100644 index 00000000..1c6303e0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/InterfaceThroughput.java @@ -0,0 +1,12 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class InterfaceThroughput { + private int node; + @JsonProperty("interface") + private int nodeInterface; + private double throughput; +} diff --git a/corefx/src/main/java/com/core/data/LinkTypes.java b/corefx/src/main/java/com/core/data/LinkTypes.java new file mode 100644 index 00000000..4bfaf1dc --- /dev/null +++ b/corefx/src/main/java/com/core/data/LinkTypes.java @@ -0,0 +1,31 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum LinkTypes { + WIRELESS(0), + WIRED(1); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (LinkTypes state : LinkTypes.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + LinkTypes(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static LinkTypes get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Location.java b/corefx/src/main/java/com/core/data/Location.java new file mode 100644 index 00000000..f5c399c4 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Location.java @@ -0,0 +1,10 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class Location { + private Double latitude = 0.0; + private Double longitude = 0.0; + private Double altitude = 0.0; +} diff --git a/corefx/src/main/java/com/core/data/LocationConfig.java b/corefx/src/main/java/com/core/data/LocationConfig.java new file mode 100644 index 00000000..10ba11ac --- /dev/null +++ b/corefx/src/main/java/com/core/data/LocationConfig.java @@ -0,0 +1,10 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class LocationConfig { + private Position position = new Position(); + private Location location = new Location(); + private Double scale; +} diff --git a/corefx/src/main/java/com/core/data/MessageFlags.java b/corefx/src/main/java/com/core/data/MessageFlags.java new file mode 100644 index 00000000..6272099b --- /dev/null +++ b/corefx/src/main/java/com/core/data/MessageFlags.java @@ -0,0 +1,36 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum MessageFlags { + ADD(1), + DELETE(2), + CRI(4), + LOCAL(8), + STRING(16), + TEXT(32), + TTY(64); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (MessageFlags state : MessageFlags.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + MessageFlags(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MessageFlags get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/MobilityConfig.java b/corefx/src/main/java/com/core/data/MobilityConfig.java new file mode 100644 index 00000000..67b5d769 --- /dev/null +++ b/corefx/src/main/java/com/core/data/MobilityConfig.java @@ -0,0 +1,25 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.File; + +@Data +public class MobilityConfig { + private String file; + @JsonIgnore + private File scriptFile; + @JsonProperty("refresh_ms") + private Integer refresh; + private String loop; + private String autostart; + private String map; + @JsonProperty("script_start") + private String startScript; + @JsonProperty("script_pause") + private String pauseScript; + @JsonProperty("script_stop") + private String stopScript; +} diff --git a/corefx/src/main/java/com/core/data/NodeType.java b/corefx/src/main/java/com/core/data/NodeType.java new file mode 100644 index 00000000..4af7dc3d --- /dev/null +++ b/corefx/src/main/java/com/core/data/NodeType.java @@ -0,0 +1,86 @@ +package com.core.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Data +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class NodeType { + private static final Logger logger = LogManager.getLogger(); + private static final AtomicInteger idGenerator = new AtomicInteger(0); + private static final Map ID_LOOKUP = new HashMap<>(); + public static final int DEFAULT = 0; + public static final int SWITCH = 4; + public static final int HUB = 5; + public static final int WLAN = 6; + public static final int EMANE = 10; + @EqualsAndHashCode.Include + private final int id; + private final int value; + private final Set services = new TreeSet<>(); + private String display; + private String model; + private String icon; + + // PHYSICAL = 1 +// RJ45 = 7 +// TUNNEL = 8 +// KTUNNEL = 9 +// EMANE = 10 +// TAP_BRIDGE = 11 +// PEER_TO_PEER = 12 +// CONTROL_NET = 13 +// EMANE_NET = 14; + + static { + add(new NodeType(SWITCH, "lanswitch", "Switch", "/icons/switch-100.png")); + add(new NodeType(HUB, "hub", "Hub", "/icons/hub-100.png")); + add(new NodeType(WLAN, "wlan", "WLAN", "/icons/wlan-100.png")); + add(new NodeType(EMANE, "wlan", "EMANE", "/icons/emane-100.png")); + } + + + public NodeType(int value, String model, String display, String icon) { + this.id = idGenerator.incrementAndGet(); + this.value = value; + this.model = model; + this.display = display; + this.icon = icon; + } + + public static void add(NodeType nodeType) { + ID_LOOKUP.put(nodeType.getId(), nodeType); + } + + public static void remove(NodeType nodeType) { + ID_LOOKUP.remove(nodeType.getId()); + } + + public static NodeType get(Integer id) { + return ID_LOOKUP.get(id); + } + + public static Collection getAll() { + return ID_LOOKUP.values(); + } + + public static NodeType find(Integer type, String model) { + return ID_LOOKUP.values().stream() + .filter(nodeType -> { + boolean sameType = nodeType.getValue() == type; + boolean sameModel; + if (model != null) { + sameModel = model.equals(nodeType.getModel()); + } else { + sameModel = nodeType.getModel() == null; + } + return sameType && sameModel; + }) + .findFirst().orElse(null); + } +} diff --git a/corefx/src/main/java/com/core/data/Position.java b/corefx/src/main/java/com/core/data/Position.java new file mode 100644 index 00000000..c5bfa728 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Position.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class Position { + private Double x; + private Double y; + private Double z; +} diff --git a/corefx/src/main/java/com/core/data/Session.java b/corefx/src/main/java/com/core/data/Session.java new file mode 100644 index 00000000..374e379d --- /dev/null +++ b/corefx/src/main/java/com/core/data/Session.java @@ -0,0 +1,15 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class Session { + private Integer state; + private List nodes = new ArrayList<>(); + private List links = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/SessionOverview.java b/corefx/src/main/java/com/core/data/SessionOverview.java new file mode 100644 index 00000000..47773338 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionOverview.java @@ -0,0 +1,13 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class SessionOverview { + private Integer id; + private Integer state; + private Integer nodes = 0; + private String url; +} diff --git a/corefx/src/main/java/com/core/data/SessionState.java b/corefx/src/main/java/com/core/data/SessionState.java new file mode 100644 index 00000000..c9e92904 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionState.java @@ -0,0 +1,38 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum SessionState { + DEFINITION(1), + CONFIGURATION(2), + INSTANTIATION(3), + RUNTIME(4), + DATA_COLLECT(5), + SHUTDOWN(6), + START(7), + STOP(8), + PAUSE(9); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (SessionState state : SessionState.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + SessionState(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SessionState get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Throughputs.java b/corefx/src/main/java/com/core/data/Throughputs.java new file mode 100644 index 00000000..c02a7f6f --- /dev/null +++ b/corefx/src/main/java/com/core/data/Throughputs.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Throughputs { + private List interfaces = new ArrayList<>(); + private List bridges = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraph.java b/corefx/src/main/java/com/core/datavis/CoreGraph.java new file mode 100644 index 00000000..f5a0c0fd --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraph.java @@ -0,0 +1,11 @@ +package com.core.datavis; + +import lombok.Data; + +@Data +public class CoreGraph { + private String title; + private CoreGraphAxis xAxis; + private CoreGraphAxis yAxis; + private GraphType graphType; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java b/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java new file mode 100644 index 00000000..8631f3a1 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java @@ -0,0 +1,15 @@ +package com.core.datavis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CoreGraphAxis { + private String label; + private Double lower; + private Double upper; + private Double tick; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphData.java b/corefx/src/main/java/com/core/datavis/CoreGraphData.java new file mode 100644 index 00000000..ff0d4c77 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphData.java @@ -0,0 +1,15 @@ +package com.core.datavis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CoreGraphData { + private String name; + private Double x; + private Double y; + private Double weight; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java b/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java new file mode 100644 index 00000000..eae92e8f --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java @@ -0,0 +1,157 @@ +package com.core.datavis; + +import javafx.scene.chart.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class CoreGraphWrapper { + private static final Logger logger = LogManager.getLogger(); + private final GraphType graphType; + private PieChart pieChart; + private final Map pieData = new HashMap<>(); + private BarChart barChart; + private final Map> barMap = new HashMap<>(); + private XYChart xyChart; + private final XYChart.Series series = new XYChart.Series<>(); + private final XYChart.Series barSeries = new XYChart.Series<>(); + private AtomicInteger timeValue = new AtomicInteger(0); + + public CoreGraphWrapper(CoreGraph coreGraph) { + graphType = coreGraph.getGraphType(); + createChart(coreGraph); + } + + public Chart getChart() { + switch (graphType) { + case PIE: + return pieChart; + case BAR: + return barChart; + default: + return xyChart; + } + } + + public void add(CoreGraphData coreGraphData) { + switch (graphType) { + case PIE: + case BAR: + add(coreGraphData.getName(), coreGraphData.getY()); + break; + case TIME: + add(coreGraphData.getY()); + break; + case BUBBLE: + add(coreGraphData.getX(), coreGraphData.getY(), coreGraphData.getWeight()); + break; + default: + add(coreGraphData.getX(), coreGraphData.getY()); + } + } + + public void add(String name, double value) { + if (GraphType.PIE == graphType) { + PieChart.Data data = pieData.computeIfAbsent(name, x -> { + PieChart.Data newData = new PieChart.Data(x, value); + pieChart.getData().add(newData); + return newData; + }); + data.setPieValue(value); + } else { + XYChart.Data data = barMap.computeIfAbsent(name, x -> { + XYChart.Data newData = new XYChart.Data<>(name, value); + barSeries.getData().add(newData); + return newData; + }); + data.setYValue(value); + } + } + + public void add(Number y) { + series.getData().add(new XYChart.Data<>(timeValue.getAndIncrement(), y)); + } + + public void add(Number x, Number y) { + series.getData().add(new XYChart.Data<>(x, y)); + } + + public void add(Number x, Number y, Number weight) { + series.getData().add(new XYChart.Data<>(x, y, weight)); + } + + private NumberAxis getAxis(CoreGraphAxis graphAxis) { + return new NumberAxis(graphAxis.getLabel(), graphAxis.getLower(), + graphAxis.getUpper(), graphAxis.getTick()); + } + + private void createChart(CoreGraph coreGraph) { + NumberAxis xAxis; + NumberAxis yAxis; + + switch (coreGraph.getGraphType()) { + case AREA: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new AreaChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case TIME: + xAxis = new NumberAxis(); + xAxis.setLabel(coreGraph.getXAxis().getLabel()); + xAxis.setTickUnit(1); + xAxis.setLowerBound(0); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new LineChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case LINE: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new LineChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case BUBBLE: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new BubbleChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case SCATTER: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new ScatterChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case PIE: + pieChart = new PieChart(); + pieChart.setTitle(coreGraph.getTitle()); + break; + case BAR: + CategoryAxis categoryAxis = new CategoryAxis(); + categoryAxis.setLabel(coreGraph.getXAxis().getLabel()); + yAxis = getAxis(coreGraph.getYAxis()); + barChart = new BarChart<>(categoryAxis, yAxis); + barChart.setLegendVisible(false); + barChart.setTitle(coreGraph.getTitle()); + barChart.getData().add(barSeries); + break; + default: + throw new IllegalArgumentException(String.format("unknown graph type: %s", + coreGraph.getGraphType())); + } + } +} diff --git a/corefx/src/main/java/com/core/datavis/GraphType.java b/corefx/src/main/java/com/core/datavis/GraphType.java new file mode 100644 index 00000000..7609eee3 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/GraphType.java @@ -0,0 +1,11 @@ +package com.core.datavis; + +public enum GraphType { + PIE, + LINE, + TIME, + AREA, + BAR, + SCATTER, + BUBBLE +} diff --git a/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java b/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java new file mode 100644 index 00000000..d04097c2 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java @@ -0,0 +1,22 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.MenuItem; + +abstract class AbstractNodeContextMenu extends GraphContextMenu { + final CoreNode coreNode; + + AbstractNodeContextMenu(Controller controller, CoreNode coreNode) { + super(controller); + this.coreNode = coreNode; + } + + void addMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + getItems().add(menuItem); + } +} diff --git a/corefx/src/main/java/com/core/graph/BackgroundPaintable.java b/corefx/src/main/java/com/core/graph/BackgroundPaintable.java new file mode 100644 index 00000000..b0cfe6a3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/BackgroundPaintable.java @@ -0,0 +1,51 @@ +package com.core.graph; + +import edu.uci.ics.jung.visualization.Layer; +import edu.uci.ics.jung.visualization.VisualizationViewer; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.nio.file.Paths; + +public class BackgroundPaintable implements VisualizationViewer.Paintable { + private final ImageIcon imageIcon; + private final VisualizationViewer vv; + private final String imagePath; + + public BackgroundPaintable(String imagePath, VisualizationViewer vv) throws IOException { + this.imagePath = imagePath; + Image image = ImageIO.read(Paths.get(imagePath).toFile()); + imageIcon = new ImageIcon(image); + this.vv = vv; + } + + public String getImage() { + return imagePath; + } + + @Override + public void paint(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + AffineTransform oldXform = g2d.getTransform(); + AffineTransform lat = + vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform(); + AffineTransform vat = + vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform(); + AffineTransform at = new AffineTransform(); + at.concatenate(g2d.getTransform()); + at.concatenate(vat); + at.concatenate(lat); + g2d.setTransform(at); + g.drawImage(imageIcon.getImage(), 0, 0, + imageIcon.getIconWidth(), imageIcon.getIconHeight(), vv); + g2d.setTransform(oldXform); + } + + @Override + public boolean useTransform() { + return false; + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java new file mode 100644 index 00000000..5db724b6 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -0,0 +1,41 @@ +package com.core.graph; + +import com.core.data.CoreInterface; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collection; + +public class CoreAddresses { + private static final Logger logger = LogManager.getLogger(); + public static final int IP4_MASK = 24; + public static final int IP4_INDEX = (IP4_MASK / 8) - 1; + + private String ip4Base; + + public CoreAddresses(String ip4Base) { + this.ip4Base = ip4Base; + } + + public int getSubnet(Collection nodeOneInterfaces, Collection nodeTwoInterfaces) { + int subOne = getMaxSubnet(nodeOneInterfaces); + int subTwo = getMaxSubnet(nodeTwoInterfaces); + logger.info("next subnet: {} - {}", subOne, subTwo); + return Math.max(subOne, subTwo) + 1; + } + + private int getMaxSubnet(Collection coreInterfaces) { + int sub = 0; + for (CoreInterface coreInterface : coreInterfaces) { + String[] values = coreInterface.getIp4().split("\\."); + int currentSub = Integer.parseInt(values[IP4_INDEX]); + logger.info("checking {} value {}", coreInterface.getIp4(), currentSub); + sub = Math.max(currentSub, sub); + } + return sub; + } + + public String getIp4Address(int sub, int id) { + return String.format("%s.%s.%s", ip4Base, sub, id); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java new file mode 100644 index 00000000..6cbd1bf8 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java @@ -0,0 +1,57 @@ +package com.core.graph; + +import com.core.Controller; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin; +import edu.uci.ics.jung.visualization.annotations.Annotation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.RectangularShape; + +public class CoreAnnotatingGraphMousePlugin extends AnnotatingGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private JFrame frame = new JFrame(); + + public CoreAnnotatingGraphMousePlugin(Controller controller, RenderContext renderContext) { + super(renderContext); + this.controller = controller; + frame.setVisible(false); + frame.setAlwaysOnTop(true); + } + + @Override + public void mouseReleased(MouseEvent e) { + VisualizationViewer vv = (VisualizationViewer) e.getSource(); + if (e.isPopupTrigger()) { + frame.setLocationRelativeTo(vv); + String annotationString = JOptionPane.showInputDialog(frame, "Annotation:", + "Annotation Label", JOptionPane.PLAIN_MESSAGE); + if (annotationString != null && annotationString.length() > 0) { + Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(this.down); + Annotation annotation = new Annotation(annotationString, this.layer, + this.annotationColor, this.fill, p); + this.annotationManager.add(this.layer, annotation); + } + } else if (e.getModifiers() == this.modifiers && this.down != null) { + Point2D out = e.getPoint(); + RectangularShape arect = (RectangularShape) this.rectangularShape.clone(); + arect.setFrameFromDiagonal(this.down, out); + Shape s = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(arect); + Annotation annotation = new Annotation(s, this.layer, this.annotationColor, this.fill, out); + this.annotationManager.add(this.layer, annotation); + } + + this.down = null; + vv.removePostRenderPaintable(this.lensPaintable); + vv.repaint(); + } + + +} diff --git a/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java new file mode 100644 index 00000000..9d80183e --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java @@ -0,0 +1,17 @@ +package com.core.graph; + +import com.core.Controller; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; + +public class CoreEditingModalGraphMouse extends EditingModalGraphMouse { + public CoreEditingModalGraphMouse(Controller controller, NetworkGraph networkGraph, + RenderContext rc, Supplier vertexFactory, Supplier edgeFactory) { + super(rc, vertexFactory, edgeFactory); + remove(annotatingPlugin); + remove(popupEditingPlugin); + annotatingPlugin = new CoreAnnotatingGraphMousePlugin<>(controller, rc); + popupEditingPlugin = new CorePopupGraphMousePlugin<>(controller, networkGraph, vertexFactory, edgeFactory); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreObservableGraph.java b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java new file mode 100644 index 00000000..cbf678f0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java @@ -0,0 +1,31 @@ +package com.core.graph; + +import edu.uci.ics.jung.graph.Graph; +import edu.uci.ics.jung.graph.ObservableGraph; +import edu.uci.ics.jung.graph.util.EdgeType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CoreObservableGraph extends ObservableGraph { + private static final Logger logger = LogManager.getLogger(); + + public CoreObservableGraph(Graph graph) { + super(graph); + } + + @Override + public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2, edgeType); + } + + @Override + public boolean addEdge(E e, V v1, V v2) { + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2); + } +} diff --git a/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java new file mode 100644 index 00000000..3b96c9fa --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java @@ -0,0 +1,78 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreLink; +import com.core.data.CoreNode; +import com.core.data.NodeType; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; +import edu.uci.ics.jung.algorithms.layout.Layout; +import edu.uci.ics.jung.visualization.control.EditingPopupGraphMousePlugin; +import javafx.application.Platform; +import javafx.scene.control.ContextMenu; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +public class CorePopupGraphMousePlugin extends EditingPopupGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private final NetworkGraph networkGraph; + private final Layout graphLayout; + private final GraphElementAccessor pickSupport; + + public CorePopupGraphMousePlugin(Controller controller, NetworkGraph networkGraph, + Supplier vertexFactory, Supplier edgeFactory) { + super(vertexFactory, edgeFactory); + this.controller = controller; + this.networkGraph = networkGraph; + graphLayout = this.networkGraph.getGraphLayout(); + pickSupport = this.networkGraph.getGraphViewer().getPickSupport(); + } + + @Override + protected void handlePopup(MouseEvent e) { + logger.info("showing popup!"); + final Point2D p = e.getPoint(); + + final CoreNode node = pickSupport.getVertex(graphLayout, p.getX(), p.getY()); + final CoreLink link = pickSupport.getEdge(graphLayout, p.getX(), p.getY()); + + final ContextMenu contextMenu; + if (node != null) { + contextMenu = handleNodeContext(node); + } else if (link != null) { + contextMenu = new LinkContextMenu(controller, link); + } else { + contextMenu = new ContextMenu(); + } + + if (!contextMenu.getItems().isEmpty()) { + logger.info("showing context menu"); + Platform.runLater(() -> contextMenu.show(controller.getWindow(), + e.getXOnScreen(), e.getYOnScreen())); + } + } + + private ContextMenu handleNodeContext(final CoreNode node) { + ContextMenu contextMenu = new ContextMenu(); + switch (node.getType()) { + case NodeType.DEFAULT: + contextMenu = new NodeContextMenu(controller, node); + break; + case NodeType.WLAN: + contextMenu = new WlanContextMenu(controller, node); + break; + case NodeType.EMANE: + contextMenu = new EmaneContextMenu(controller, node); + break; + default: + logger.warn("no context menu for node: {}", node.getType()); + break; + } + + return contextMenu; + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java b/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java new file mode 100644 index 00000000..25c49fa3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java @@ -0,0 +1,43 @@ +package com.core.graph; + +import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; + +public class CoreVertexLabelRenderer extends DefaultVertexLabelRenderer { + private Color foregroundColor = Color.WHITE; + private Color backgroundColor = Color.BLACK; + + CoreVertexLabelRenderer() { + super(Color.YELLOW); + } + + public void setColors(Color foregroundColor, Color backgroundColor) { + this.foregroundColor = foregroundColor; + this.backgroundColor = backgroundColor; + } + + @Override + public Component getVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, V vertex) { + super.setForeground(foregroundColor); + if (isSelected) { + this.setForeground(this.pickedVertexLabelColor); + } + + super.setBackground(backgroundColor); + if (font != null) { + this.setFont(font); + } else { + this.setFont(vv.getFont()); + } + + this.setIcon(null); + EmptyBorder padding = new EmptyBorder(5, 5, 5, 5); + this.setBorder(padding); + this.setValue(value); + setFont(getFont().deriveFont(Font.BOLD)); + return this; + } +} diff --git a/corefx/src/main/java/com/core/graph/EmaneContextMenu.java b/corefx/src/main/java/com/core/graph/EmaneContextMenu.java new file mode 100644 index 00000000..848bbfb3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/EmaneContextMenu.java @@ -0,0 +1,26 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.dialogs.MobilityPlayerDialog; + +class EmaneContextMenu extends AbstractNodeContextMenu { + EmaneContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private void setup() { + addMenuItem("EMANE Settings", event -> controller.getNodeEmaneDialog().showDialog(coreNode)); + if (controller.getCoreClient().isRunning()) { + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId()); + if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) { + addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show()); + } + } else { + addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode)); + addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/GraphContextMenu.java b/corefx/src/main/java/com/core/graph/GraphContextMenu.java new file mode 100644 index 00000000..c93ecba9 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/GraphContextMenu.java @@ -0,0 +1,22 @@ +package com.core.graph; + +import com.core.Controller; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; + +abstract class GraphContextMenu extends ContextMenu { + final Controller controller; + + GraphContextMenu(Controller controller) { + super(); + this.controller = controller; + } + + void addMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + getItems().add(menuItem); + } +} diff --git a/corefx/src/main/java/com/core/graph/LinkContextMenu.java b/corefx/src/main/java/com/core/graph/LinkContextMenu.java new file mode 100644 index 00000000..d9b11115 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/LinkContextMenu.java @@ -0,0 +1,21 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreLink; + +class LinkContextMenu extends GraphContextMenu { + final CoreLink coreLink; + + LinkContextMenu(Controller controller, CoreLink coreLink) { + super(controller); + this.coreLink = coreLink; + setup(); + } + + private void setup() { + if (!controller.getCoreClient().isRunning()) { + addMenuItem("Delete Link", + event -> controller.getNetworkGraph().removeLink(coreLink)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java new file mode 100644 index 00000000..7d55cdb2 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -0,0 +1,492 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.*; +import com.core.ui.Toast; +import com.core.ui.dialogs.TerminalDialog; +import com.core.utils.Configuration; +import com.core.utils.IconUtils; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.algorithms.layout.StaticLayout; +import edu.uci.ics.jung.graph.ObservableGraph; +import edu.uci.ics.jung.graph.event.GraphEvent; +import edu.uci.ics.jung.graph.event.GraphEventListener; +import edu.uci.ics.jung.graph.util.Pair; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.annotations.AnnotationControls; +import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; +import edu.uci.ics.jung.visualization.control.GraphMouseListener; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import edu.uci.ics.jung.visualization.decorators.EdgeShape; +import edu.uci.ics.jung.visualization.renderers.Renderer; +import javafx.application.Platform; +import lombok.Data; +import org.apache.commons.net.util.SubnetUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Ellipse2D; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +@Data +public class NetworkGraph { + private static final Logger logger = LogManager.getLogger(); + private static final int EDGE_LABEL_OFFSET = -5; + private static final int EDGE_WIDTH = 5; + private Controller controller; + private ObservableGraph graph; + private StaticLayout graphLayout; + private VisualizationViewer graphViewer; + private EditingModalGraphMouse graphMouse; + private AnnotationControls annotationControls; + + private SubnetUtils subnetUtils = new SubnetUtils("10.0.0.0/24"); + private CoreAddresses coreAddresses = new CoreAddresses("10.0"); + private NodeType nodeType; + private Map nodeMap = new ConcurrentHashMap<>(); + private int vertexId = 1; + private int linkId = 1; + private Supplier vertexFactory = () -> new CoreNode(vertexId++); + private Supplier linkFactory = () -> new CoreLink(linkId++); + private CorePopupGraphMousePlugin customPopupPlugin; + private CoreAnnotatingGraphMousePlugin customAnnotatingPlugin; + private BackgroundPaintable backgroundPaintable; + private CoreVertexLabelRenderer nodeLabelRenderer = new CoreVertexLabelRenderer(); + + // display options + private boolean showThroughput = false; + private Double throughputLimit = null; + private int throughputWidth = 10; + + public NetworkGraph(Controller controller) { + this.controller = controller; + graph = new CoreObservableGraph<>(new UndirectedSimpleGraph<>()); + graph.addGraphEventListener(graphEventListener); + graphLayout = new StaticLayout<>(graph); + graphViewer = new VisualizationViewer<>(graphLayout); + graphViewer.setBackground(Color.WHITE); + graphViewer.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S); + + RenderContext renderContext = graphViewer.getRenderContext(); + + // node render properties + renderContext.setVertexLabelTransformer(CoreNode::getName); + renderContext.setVertexLabelRenderer(nodeLabelRenderer); + renderContext.setVertexShapeTransformer(node -> { + double offset = -(IconUtils.ICON_SIZE / 2.0); + return new Ellipse2D.Double(offset, offset, IconUtils.ICON_SIZE, IconUtils.ICON_SIZE); + }); + renderContext.setVertexIconTransformer(vertex -> { + long wirelessLinks = wirelessLinkCount(vertex); + vertex.getRadioIcon().setWiressLinks(wirelessLinks); + return vertex.getGraphIcon(); + }); + + // link render properties + renderContext.setEdgeLabelTransformer(edge -> { + if (!showThroughput || edge == null) { + return null; + } + double kbps = edge.getThroughput() / 1000.0; + return String.format("%.2f kbps", kbps); + }); + renderContext.setLabelOffset(EDGE_LABEL_OFFSET); + renderContext.setEdgeStrokeTransformer(edge -> { + // determine edge width + int width = EDGE_WIDTH; + if (throughputLimit != null && edge.getThroughput() > throughputLimit) { + width = throughputWidth; + } + + LinkTypes linkType = LinkTypes.get(edge.getType()); + if (LinkTypes.WIRELESS == linkType) { + float[] dash = {15.0f}; + return new BasicStroke(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, + 0, dash, 0); + } else { + return new BasicStroke(width); + } + }); + renderContext.setEdgeShapeTransformer(EdgeShape.line(graph)); + renderContext.setEdgeDrawPaintTransformer(edge -> { + LinkTypes linkType = LinkTypes.get(edge.getType()); + if (LinkTypes.WIRELESS == linkType) { + return Color.BLUE; + } else { + return Color.BLACK; + } + }); + renderContext.setEdgeIncludePredicate(predicate -> predicate.element.isVisible()); + + graphViewer.setVertexToolTipTransformer(renderContext.getVertexLabelTransformer()); + graphMouse = new CoreEditingModalGraphMouse<>(controller, this, renderContext, + vertexFactory, linkFactory); + graphViewer.setGraphMouse(graphMouse); + + // mouse events + graphViewer.addGraphMouseListener(new GraphMouseListener() { + @Override + public void graphClicked(CoreNode node, MouseEvent mouseEvent) { + // double click + logger.info("click count: {}, running?: {}", mouseEvent.getClickCount(), + controller.getCoreClient().isRunning()); + + if (mouseEvent.getClickCount() == 2 && controller.getCoreClient().isRunning()) { + if (controller.getCoreClient().isLocalConnection()) { + try { + String shellCommand = controller.getConfiguration().getShellCommand(); + String terminalCommand = controller.getCoreClient().getTerminalCommand(node); + terminalCommand = String.format("%s %s", shellCommand, terminalCommand); + logger.info("launching node terminal: {}", terminalCommand); + String[] commands = terminalCommand.split("\\s+"); + logger.info("launching node terminal: {}", Arrays.toString(commands)); + Process p = new ProcessBuilder(commands).start(); + try { + if (!p.waitFor(5, TimeUnit.SECONDS)) { + Toast.error("Node terminal command failed"); + } + } catch (InterruptedException ex) { + logger.error("error waiting for terminal to start", ex); + } + } catch (IOException ex) { + logger.error("error launching terminal", ex); + Toast.error("Node terminal failed to start"); + } + } else { + Platform.runLater(() -> { + TerminalDialog terminalDialog = new TerminalDialog(controller); + terminalDialog.setOwner(controller.getWindow()); + terminalDialog.showDialog(node); + }); + } + } + } + + @Override + public void graphPressed(CoreNode node, MouseEvent mouseEvent) { + logger.debug("graph pressed: {} - {}", node, mouseEvent); + } + + @Override + public void graphReleased(CoreNode node, MouseEvent mouseEvent) { + if (SwingUtilities.isLeftMouseButton(mouseEvent)) { + Double newX = graphLayout.getX(node); + Double newY = graphLayout.getY(node); + Double oldX = node.getPosition().getX(); + Double oldY = node.getPosition().getY(); + if (newX.equals(oldX) && newY.equals(oldY)) { + return; + } + logger.debug("graph moved node({}): {},{}", node.getName(), newX, newY); + node.getPosition().setX(newX); + node.getPosition().setY(newY); + + // upate node when session is active + if (controller.getCoreClient().isRunning()) { + try { + controller.getCoreClient().editNode(node); + } catch (IOException ex) { + Toast.error("failed to update node location"); + } + } + } + } + }); + } + + private Color convertJfxColor(String hexValue) { + javafx.scene.paint.Color color = javafx.scene.paint.Color.web(hexValue); + return new Color((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue()); + } + + public void updatePreferences(Configuration configuration) { + Color nodeLabelColor = convertJfxColor(configuration.getNodeLabelColor()); + Color nodeLabelBackgroundColor = convertJfxColor(configuration.getNodeLabelBackgroundColor()); + nodeLabelRenderer.setColors(nodeLabelColor, nodeLabelBackgroundColor); + throughputLimit = configuration.getThroughputLimit(); + if (configuration.getThroughputWidth() != null) { + throughputWidth = configuration.getThroughputWidth(); + } + graphViewer.repaint(); + } + + public void setBackground(String imagePath) { + try { + backgroundPaintable = new BackgroundPaintable<>(imagePath, graphViewer); + graphViewer.addPreRenderPaintable(backgroundPaintable); + graphViewer.repaint(); + } catch (IOException ex) { + logger.error("error setting background", ex); + } + } + + public void removeBackground() { + if (backgroundPaintable != null) { + graphViewer.removePreRenderPaintable(backgroundPaintable); + graphViewer.repaint(); + backgroundPaintable = null; + } + } + + public void setMode(ModalGraphMouse.Mode mode) { + graphMouse.setMode(mode); + } + + public void reset() { + logger.info("network graph reset"); + vertexId = 1; + linkId = 1; + for (CoreNode node : nodeMap.values()) { + graph.removeVertex(node); + } + nodeMap.clear(); + graphViewer.repaint(); + } + + public void updatePositions() { + for (CoreNode node : graph.getVertices()) { + Double x = graphLayout.getX(node); + Double y = graphLayout.getY(node); + node.getPosition().setX(x); + node.getPosition().setY(y); + logger.debug("updating node position node({}): {},{}", node, x, y); + } + } + + public CoreNode getVertex(int id) { + return nodeMap.get(id); + } + + private GraphEventListener graphEventListener = graphEvent -> { + logger.info("graph event: {}", graphEvent.getType()); + switch (graphEvent.getType()) { + case EDGE_ADDED: + handleEdgeAdded((GraphEvent.Edge) graphEvent); + break; + case EDGE_REMOVED: + handleEdgeRemoved((GraphEvent.Edge) graphEvent); + break; + case VERTEX_ADDED: + handleVertexAdded((GraphEvent.Vertex) graphEvent); + break; + case VERTEX_REMOVED: + handleVertexRemoved((GraphEvent.Vertex) graphEvent); + break; + } + }; + + private void handleEdgeAdded(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + if (!link.isLoaded()) { + Pair endpoints = graph.getEndpoints(link); + + CoreNode nodeOne = endpoints.getFirst(); + CoreNode nodeTwo = endpoints.getSecond(); + + // create interfaces for nodes + int sub = coreAddresses.getSubnet( + getInterfaces(nodeOne), + getInterfaces(nodeTwo) + ); + + link.setNodeOne(nodeOne.getId()); + if (isNode(nodeOne)) { + int interfaceOneId = nextInterfaceId(nodeOne); + CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId); + link.setInterfaceOne(interfaceOne); + } + + link.setNodeTwo(nodeTwo.getId()); + if (isNode(nodeTwo)) { + int interfaceTwoId = nextInterfaceId(nodeTwo); + CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId); + link.setInterfaceTwo(interfaceTwo); + } + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + + logger.info("adding user created edge: {}", link); + } + } + + public List getInterfaces(CoreNode node) { + return graph.getIncidentEdges(node).stream() + .map(link -> { + if (node.getId().equals(link.getNodeOne())) { + return link.getInterfaceOne(); + } else { + return link.getInterfaceTwo(); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private int nextInterfaceId(CoreNode node) { + Set interfaceIds = graph.getIncidentEdges(node).stream() + .map(link -> { + if (node.getId().equals(link.getNodeOne())) { + return link.getInterfaceOne(); + } else { + return link.getInterfaceTwo(); + } + }) + .filter(Objects::nonNull) + .map(CoreInterface::getId) + .collect(Collectors.toSet()); + + int i = 0; + while (true) { + if (!interfaceIds.contains(i)) { + return i; + } + + i += 1; + } + } + + private boolean isNode(CoreNode node) { + return node.getType() == NodeType.DEFAULT; + } + + private CoreInterface createInterface(CoreNode node, int sub, int interfaceId) { + CoreInterface coreInterface = new CoreInterface(); + coreInterface.setId(interfaceId); + coreInterface.setName(String.format("eth%s", interfaceId)); + String nodeOneIp4 = coreAddresses.getIp4Address(sub, node.getId()); + coreInterface.setIp4(nodeOneIp4); + coreInterface.setIp4Mask(CoreAddresses.IP4_MASK); + return coreInterface; + } + + private void handleEdgeRemoved(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + logger.info("removed edge: {}", link); + } + + private void handleVertexAdded(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + if (!node.isLoaded()) { + node.setNodeType(nodeType); + if (node.getType() == NodeType.EMANE) { + String emaneModel = controller.getNodeEmaneDialog().getModels().get(0); + node.setEmane(emaneModel); + } + + logger.info("adding user created node: {}", node); + nodeMap.put(node.getId(), node); + } + } + + private void handleVertexRemoved(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + logger.info("removed vertex: {}", node); + nodeMap.remove(node.getId()); + } + + public void addNode(CoreNode node) { + vertexId = Math.max(node.getId() + 1, node.getId()); + double x = Math.abs(node.getPosition().getX()); + double y = Math.abs(node.getPosition().getY()); + logger.info("adding session node: {}", node); + graph.addVertex(node); + graphLayout.setLocation(node, x, y); + nodeMap.put(node.getId(), node); + } + + public void setNodeLocation(CoreNode nodeData) { + // update actual graph node + CoreNode node = nodeMap.get(nodeData.getId()); + node.getPosition().setX(nodeData.getPosition().getX()); + node.getPosition().setY(nodeData.getPosition().getY()); + + // set graph node location + double x = Math.abs(node.getPosition().getX()); + double y = Math.abs(node.getPosition().getY()); + graphLayout.setLocation(node, x, y); + graphViewer.repaint(); + } + + public void removeNode(CoreNode node) { + try { + controller.getCoreClient().deleteNode(node); + } catch (IOException ex) { + logger.error("error deleting node"); + Toast.error(String.format("Error deleting node: %s", node.getName())); + } + graphViewer.getPickedVertexState().pick(node, false); + graph.removeVertex(node); + graphViewer.repaint(); + } + + private boolean isWirelessNode(CoreNode node) { + return node.getType() == NodeType.EMANE || node.getType() == NodeType.WLAN; + } + + private boolean checkForWirelessNode(CoreNode nodeOne, CoreNode nodeTwo) { + boolean result = isWirelessNode(nodeOne); + return result || isWirelessNode(nodeTwo); + } + + private long wirelessLinkCount(CoreNode node) { + return graph.getNeighbors(node).stream() + .filter(this::isWirelessNode) + .count(); + } + + public void addLink(CoreLink link) { + logger.info("adding session link: {}", link); + link.setId(linkId++); + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + + graph.addEdge(link, nodeOne, nodeTwo); + } + + public void removeWirelessLink(CoreLink link) { + logger.info("deleting link: {}", link); + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + + CoreLink existingLink = graph.findEdge(nodeOne, nodeTwo); + if (existingLink != null) { + graph.removeEdge(existingLink); + } + } + + public void removeLink(CoreLink link) { + graphViewer.getPickedEdgeState().pick(link, false); + graph.removeEdge(link); + graphViewer.repaint(); + } + + public void linkMdrs(CoreNode node) { + for (CoreNode currentNode : graph.getVertices()) { + if (!"mdr".equals(currentNode.getModel())) { + continue; + } + + // only links mdrs we have not already linked + Collection links = graph.findEdgeSet(node, currentNode); + if (links.isEmpty()) { + CoreLink link = linkFactory.get(); + graph.addEdge(link, currentNode, node); + graphViewer.repaint(); + } + } + } +} diff --git a/corefx/src/main/java/com/core/graph/NodeContextMenu.java b/corefx/src/main/java/com/core/graph/NodeContextMenu.java new file mode 100644 index 00000000..d50cd58a --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NodeContextMenu.java @@ -0,0 +1,116 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.Toast; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +class NodeContextMenu extends AbstractNodeContextMenu { + private static final Logger logger = LogManager.getLogger(); + + NodeContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private MenuItem createStartItem(String service) { + MenuItem menuItem = new MenuItem("Start"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().startService(coreNode, service); + if (result) { + Toast.success("Started " + service); + } else { + Toast.error("Failure to start " + service); + } + } catch (IOException ex) { + Toast.error("Error starting " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createStopItem(String service) { + MenuItem menuItem = new MenuItem("Stop"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().stopService(coreNode, service); + if (result) { + Toast.success("Stopped " + service); + } else { + Toast.error("Failure to stop " + service); + } + } catch (IOException ex) { + Toast.error("Error stopping " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createRestartItem(String service) { + MenuItem menuItem = new MenuItem("Restart"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().restartService(coreNode, service); + if (result) { + Toast.success("Restarted " + service); + } else { + Toast.error("Failure to restart " + service); + } + } catch (IOException ex) { + Toast.error("Error restarting " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createValidateItem(String service) { + MenuItem menuItem = new MenuItem("Validate"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().validateService(coreNode, service); + if (result) { + Toast.success("Validated " + service); + } else { + Toast.error("Validation failed for " + service); + } + } catch (IOException ex) { + Toast.error("Error validating " + service, ex); + } + }); + return menuItem; + } + + private void setup() { + if (controller.getCoreClient().isRunning()) { + Set services = coreNode.getServices(); + if (services.isEmpty()) { + services = controller.getDefaultServices().getOrDefault(coreNode.getModel(), Collections.emptySet()); + } + + if (!services.isEmpty()) { + Menu menu = new Menu("Manage Services"); + for (String service : services) { + Menu serviceMenu = new Menu(service); + MenuItem startItem = createStartItem(service); + MenuItem stopItem = createStopItem(service); + MenuItem restartItem = createRestartItem(service); + MenuItem validateItem = createValidateItem(service); + serviceMenu.getItems().addAll(startItem, stopItem, restartItem, validateItem); + menu.getItems().add(serviceMenu); + } + getItems().add(menu); + } + } else { + addMenuItem("Services", event -> controller.getNodeServicesDialog().showDialog(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/RadioIcon.java b/corefx/src/main/java/com/core/graph/RadioIcon.java new file mode 100644 index 00000000..e3139eaf --- /dev/null +++ b/corefx/src/main/java/com/core/graph/RadioIcon.java @@ -0,0 +1,31 @@ +package com.core.graph; + +import com.core.utils.IconUtils; +import lombok.Data; + +import javax.swing.*; +import java.awt.*; + +@Data +public class RadioIcon implements Icon { + private long wiressLinks = 0; + + @Override + public int getIconHeight() { + return IconUtils.ICON_SIZE; + } + + @Override + public int getIconWidth() { + return IconUtils.ICON_SIZE; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(Color.black); + for (int i = 0; i < wiressLinks; i++) { + g.fillOval(x, y, 10, 10); + x += 15; + } + } +} diff --git a/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java new file mode 100644 index 00000000..355b44a0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java @@ -0,0 +1,24 @@ +package com.core.graph; + +import edu.uci.ics.jung.graph.UndirectedSparseGraph; +import edu.uci.ics.jung.graph.util.EdgeType; +import edu.uci.ics.jung.graph.util.Pair; + +public class UndirectedSimpleGraph extends UndirectedSparseGraph { + @Override + public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { + Pair newEndpoints = getValidatedEndpoints(edge, endpoints); + if (newEndpoints == null) { + return false; + } + + V first = newEndpoints.getFirst(); + V second = newEndpoints.getSecond(); + + if (first.equals(second)) { + return false; + } else { + return super.addEdge(edge, endpoints, edgeType); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/WlanContextMenu.java b/corefx/src/main/java/com/core/graph/WlanContextMenu.java new file mode 100644 index 00000000..937eef15 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/WlanContextMenu.java @@ -0,0 +1,26 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.dialogs.MobilityPlayerDialog; + +class WlanContextMenu extends AbstractNodeContextMenu { + WlanContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private void setup() { + addMenuItem("WLAN Settings", event -> controller.getNodeWlanDialog().showDialog(coreNode)); + if (controller.getCoreClient().isRunning()) { + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId()); + if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) { + addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show()); + } + } else { + addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode)); + addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/ui/AnnotationToolbar.java b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java new file mode 100644 index 00000000..b32d3c8b --- /dev/null +++ b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java @@ -0,0 +1,104 @@ +package com.core.ui; + +import com.core.graph.NetworkGraph; +import com.core.utils.FxmlUtils; +import com.jfoenix.controls.JFXColorPicker; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXToggleButton; +import edu.uci.ics.jung.visualization.annotations.Annotation; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.layout.GridPane; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.RectangularShape; +import java.awt.geom.RoundRectangle2D; + +public class AnnotationToolbar extends GridPane { + private static final Logger logger = LogManager.getLogger(); + private static final String RECTANGLE = "Rectangle"; + private static final String ROUND_RECTANGLE = "RoundRectangle"; + private static final String ELLIPSE = "Ellipse"; + private static final String UPPER_LAYER = "Upper"; + private static final String LOWER_LAYER = "Lower"; + private NetworkGraph graph; + @FXML private JFXComboBox shapeCombo; + @FXML private JFXColorPicker colorPicker; + @FXML private JFXComboBox layerCombo; + @FXML private JFXToggleButton fillToggle; + + public AnnotationToolbar(NetworkGraph graph) { + this.graph = graph; + FxmlUtils.loadRootController(this, "/fxml/annotation_toolbar.fxml"); + + // setup annotation shape combo + shapeCombo.getItems().addAll(RECTANGLE, ROUND_RECTANGLE, ELLIPSE); + shapeCombo.setOnAction(this::shapeChange); + shapeCombo.getSelectionModel().selectFirst(); + + // setup annotation layer combo + layerCombo.getItems().addAll(LOWER_LAYER, UPPER_LAYER); + layerCombo.setOnAction(this::layerChange); + layerCombo.getSelectionModel().selectFirst(); + + // setup annotation color picker + colorPicker.setOnAction(this::colorChange); + colorPicker.setValue(javafx.scene.paint.Color.AQUA); + colorPicker.fireEvent(new ActionEvent()); + + // setup annotation toggle fill + fillToggle.setOnAction(this::fillChange); + } + + private void fillChange(ActionEvent event) { + boolean selected = fillToggle.isSelected(); + graph.getGraphMouse().getAnnotatingPlugin().setFill(selected); + } + + private void colorChange(ActionEvent event) { + javafx.scene.paint.Color fxColor = colorPicker.getValue(); + java.awt.Color color = new java.awt.Color( + (float) fxColor.getRed(), + (float) fxColor.getGreen(), + (float) fxColor.getBlue(), + (float) fxColor.getOpacity() + ); + logger.info("color selected: {}", fxColor); + graph.getGraphMouse().getAnnotatingPlugin().setAnnotationColor(color); + } + + private void layerChange(ActionEvent event) { + String selected = layerCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation layer selected: {}", selected); + Annotation.Layer layer; + if (LOWER_LAYER.equals(selected)) { + layer = Annotation.Layer.LOWER; + } else { + layer = Annotation.Layer.UPPER; + } + graph.getGraphMouse().getAnnotatingPlugin().setLayer(layer); + } + + private void shapeChange(ActionEvent event) { + String selected = shapeCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation shape selected: {}", selected); + RectangularShape shape = new Rectangle(); + switch (selected) { + case RECTANGLE: + shape = new Rectangle(); + break; + case ROUND_RECTANGLE: + shape = new RoundRectangle2D.Double(0, 0, 0, 0, 50.0, 50.0); + break; + case ELLIPSE: + shape = new Ellipse2D.Double(); + break; + default: + Toast.error("Unknown annotation shape " + selected); + } + graph.getGraphMouse().getAnnotatingPlugin().setRectangularShape(shape); + } +} diff --git a/corefx/src/main/java/com/core/ui/GraphToolbar.java b/corefx/src/main/java/com/core/ui/GraphToolbar.java new file mode 100644 index 00000000..f64ca73a --- /dev/null +++ b/corefx/src/main/java/com/core/ui/GraphToolbar.java @@ -0,0 +1,297 @@ +package com.core.ui; + +import com.core.Controller; +import com.core.data.NodeType; +import com.core.utils.FxmlUtils; +import com.core.utils.IconUtils; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.svg.SVGGlyph; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import javafx.application.Platform; +import javafx.css.PseudoClass; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +public class GraphToolbar extends VBox { + private static final Logger logger = LogManager.getLogger(); + private static final int ICON_SIZE = 40; + private static final int NODES_ICON_SIZE = 20; + private static final PseudoClass START_CLASS = PseudoClass.getPseudoClass("start"); + private static final PseudoClass STOP_CLASS = PseudoClass.getPseudoClass("stop"); + private static final PseudoClass SELECTED_CLASS = PseudoClass.getPseudoClass("selected"); + private final Controller controller; + private final Map labelMap = new HashMap<>(); + private SVGGlyph startIcon; + private SVGGlyph stopIcon; + private JFXListView