diff --git a/LICENSE b/LICENSE index 60debc6..89e556a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 SPOpenSource +(C) Copyright 2023 Hewlett Packard Enterprise Development LP. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1effa0f..467415a 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ To install the most recent version of pyedgeconnect, open an interactive shell and run: ```bash - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python + $ pip install git+https://github.com/aruba/pyedgeconnect ... $ pip list Package Version @@ -69,10 +69,10 @@ interactive shell and run: To install a specific branch use the @branch syntax ```bash - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python@ + $ pip install git+https://github.com/aruba/pyedgeconnect@ ... # Install the Development branch - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python@development + $ pip install git+https://github.com/aruba/pyedgeconnect@development ... ``` @@ -88,7 +88,7 @@ following syntax: ```bash $ pip install pyedgeconnect[dev] or - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python#egg=pyedgeconnect[dev] + $ pip install git+https://github.com/aruba/pyedgeconnect#egg=pyedgeconnect[dev] ``` ## Docs @@ -101,7 +101,7 @@ To build the documentation locally, clone the repository, install with ``[dev]`` to include sphinx and related packages, then in the docs directory run ``make html`` ```bash - git clone https://github.com/SPOpenSource/edgeconnect-python.git + git clone https://github.com/aruba/pyedgeconnect.git cd edgeconnect-python pip install .[dev] cd docs diff --git a/_version.py b/_version.py index 36d484f..d1c7ad4 100644 --- a/_version.py +++ b/_version.py @@ -1 +1 @@ -version = "0.15.3a1.dev0" +version = "0.15.4a1.dev0" diff --git a/docs/source/_static/hpe_aruba_black_pos_rgb.png b/docs/source/_static/hpe_aruba_black_pos_rgb.png new file mode 100644 index 0000000..c6a616c Binary files /dev/null and b/docs/source/_static/hpe_aruba_black_pos_rgb.png differ diff --git a/docs/source/conf.py b/docs/source/conf.py index 5d0f661..49d3351 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,7 +16,7 @@ # Main version number version = "0.15" # The full version, including alpha/beta/rc tags -release = "0.15.3-a1" +release = "0.15.4-a1" # -- General configuration --------------------------------------------------- @@ -46,7 +46,7 @@ # a list of builtin themes. # html_theme = "sphinx_rtd_theme" -html_logo = "_static/aruba_small_use_logo.png" +html_logo = "_static/hpe_aruba_black_pos_rgb.png" # Add any paths that contain custom static files (such as style sheets) here, diff --git a/docs/source/examples/auth_example.rst b/docs/source/examples/auth_example.rst index 88f6570..397a998 100644 --- a/docs/source/examples/auth_example.rst +++ b/docs/source/examples/auth_example.rst @@ -14,7 +14,7 @@ addition to any other required logic for a particular use-case. The code referenced in this document and all published examples with pyedgeconnect are available from the GitHub repository within the - `examples `_ + `examples `_ folder. Each example script contains logic to authenticate to the Orchestrator as documented in the authentication example. @@ -22,7 +22,7 @@ addition to any other required logic for a particular use-case. .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Environment variables diff --git a/docs/source/examples/basic_examples.rst b/docs/source/examples/basic_examples.rst index a3b2fcc..247be21 100644 --- a/docs/source/examples/basic_examples.rst +++ b/docs/source/examples/basic_examples.rst @@ -5,7 +5,7 @@ The code referenced in this document and all published examples with pyedgeconnect are available from the GitHub repository within the - `examples `_ + `examples `_ folder. Each example script contains logic to authenticate to the Orchestrator as documented in the authentication example. @@ -13,7 +13,7 @@ .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Print Appliance Information diff --git a/docs/source/examples/ec_telemetry_demo.rst b/docs/source/examples/ec_telemetry_demo.rst index 39d20df..3e96776 100644 --- a/docs/source/examples/ec_telemetry_demo.rst +++ b/docs/source/examples/ec_telemetry_demo.rst @@ -25,7 +25,7 @@ EdgeConnect Telemetry API Demo The code referenced in this document and all published examples with pyedgeconnect are available from the GitHub repository within the - `examples `_ + `examples `_ folder. Each example script contains logic to authenticate to the Orchestrator as documented in the authentication example. @@ -33,7 +33,7 @@ EdgeConnect Telemetry API Demo .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Overview and System Prerequisites ----------------------------------- @@ -433,6 +433,13 @@ Visualize Data in Grafana Required Configuration To Run Demo ----------------------------------- +.. note:: + + If data is not populating in the Dashboards, check the recent log + file ``ec-telemetry.log`` in ``app/logging`` on the demo host. Here + you can find log messages related to the data collection worker + containers. + Environment Variables ========================== @@ -473,6 +480,7 @@ up -d`` # DB_USER and DB_PW used to login to both Grafana/InfluxDB DB_TOKEN= DB_USER=admin + # DB_PW must be > 8 characters to meet minimum requirements DB_PW= # Number of replica containers for ec-telemetry-worker diff --git a/docs/source/examples/ec_telemetry_img/ec_telemetry_demo_arch.png b/docs/source/examples/ec_telemetry_img/ec_telemetry_demo_arch.png index 23ebbc7..3db58d8 100644 Binary files a/docs/source/examples/ec_telemetry_img/ec_telemetry_demo_arch.png and b/docs/source/examples/ec_telemetry_img/ec_telemetry_demo_arch.png differ diff --git a/docs/source/examples/generate_preconfig.rst b/docs/source/examples/generate_preconfig.rst index 740858d..a783ff0 100644 --- a/docs/source/examples/generate_preconfig.rst +++ b/docs/source/examples/generate_preconfig.rst @@ -13,7 +13,7 @@ The code referenced in this document and all published examples with pyedgeconnect are available from the GitHub repository within the - `examples `_ + `examples `_ folder. Each example script contains logic to authenticate to the Orchestrator as documented in the authentication example. @@ -21,7 +21,7 @@ .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Generate EdgeConnect Preconfig ******************************** diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index 316744f..413de98 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -4,14 +4,14 @@ Example Code from Repository All examples documented in this section are available from the GitHub repository within the -`examples `_ +`examples `_ folder: Clone the repository and download the examples with: .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Example script documentation and instructions @@ -23,4 +23,5 @@ Example script documentation and instructions basic_examples generate_preconfig upload_security_policy + update_port_forwarding ec_telemetry_demo \ No newline at end of file diff --git a/docs/source/examples/update_port_forwarding.rst b/docs/source/examples/update_port_forwarding.rst new file mode 100644 index 0000000..13cfc4a --- /dev/null +++ b/docs/source/examples/update_port_forwarding.rst @@ -0,0 +1,220 @@ +.. update_port_forwarding: + + +.. important:: + + The following example is more complex code than the general examples, + automating the configuration change of existing inbound port + forwarding rules. Using and modifying these examples requires a + greater understanding of python functions, handling variables, and + understanding of the data structures involved. + + Updating the inbound port forwarding rules of an EdgeConnect, + especially with automation, should be treated with caution as a + misconfiguration could allow unwanted traffic or block unintended + production traffic. This code example is not meant to check the + intent of any rules, simply show how policy can be updated in an + automated fashion. + +.. note:: + + The code referenced in this document and all published examples + with pyedgeconnect are available from the GitHub repository within the + `examples `_ + folder. Each example script contains logic to authenticate to the + Orchestrator as documented in the authentication example. + + Clone the repository and download the examples with: + + .. code:: bash + + $ git clone https://github.com/aruba/pyedgeconnect.git + +Update Port Forwarding From DHCP +************************************ + +This example takes an EdgeConnect appliance with existing Inbound Port +Forwarding rules corresponding to a WAN interface with a label specified +by the user to update the destination IP address assuming it has +recently changed due to the interface being addressed via DHCP. + +The client running the script communicates directly with the EdgeConnect +appliance and as such requires direct IP connectivity to the appliance. + +Python Script & EdgeConnect API calls +====================================== + +The script will first login to the appliance, looking for +environment variables ``EC_USER`` and ``EC_PW`` for credentials, if +either are not set it will prompt the user to enter valid admin +credentials. + + .. code-block::python + + # Set EdgeConnect login via environment variable or user input + if os.getenv("EC_USER") is not None and os.getenv("EC_PW"): + ec_user = os.getenv("EC_USER") + ec_pw = os.getenv("EC_PW") + else: + ec_user = input("EdgeConnect admin user: ") + ec_pw = getpass("EdgeConnect admin password: ") + + # Instantiate EdgeConnect with ``log_console`` enabled for + # printing log messages to terminal + ec = EdgeConnect( + ec_url, + log_console=True, + verify_ssl=False, + ) + + ec.login(ec_user, ec_pw) + +Assuming the login is successful, the appliance Deployment is retrieved +and parsed for a WAN interface with a matching label specified by the +user and that has an IP address that has been assigned via DHCP. + + .. code-block::python + + # Get deployment detail from appliance + deployment = ec.get_appliance_deployment() + + # Identify WAN label ID from label name provided by user + for wan_label in deployment["sysConfig"]["ifLabels"]["wan"]: + if wan_label["name"] == target_label: + label_id = wan_label["id"] + break + else: + label_id = None + + if label_id is None: + print(f"WAN label {target_label} not present on {hostname}") + exit() + + # Find WAN IP address of corresponding interface with WAN label that is + # configured with DHCP + for interface in deployment["modeIfs"]: + for subinterface in interface["applianceIPs"]: + if subinterface["label"] == label_id and subinterface["dhcp"] is True: + wan_ip = subinterface["ip"] + break + else: + wan_ip = None + +Finally, the destination address in the existing port forwarding rules +are compared with the current WAN IP address, and if different, are +corrected and updated to the appliance + + + .. code-block::python + + # If WAN IP found, get existing inbound port forwarding rules and update + if wan_ip is not None: + + pfw_rules = ec.get_port_forwarding_rules() + + # If WAN IP is different than the destination subnet in existing + # port forwarded rules, update with the current WAN IP from + # deployment + rule_update = False + for rule in pfw_rules: + if rule["destSubnet"].split("/")[0] == wan_ip: + print("WAN IP is correct in PFW rule, no change") + else: + rule["destSubnet"] = wan_ip + "/32" + print("WAN IP is different in PFW rule, updating") + rule_update = True + + if rule_update: + + ec.set_port_forwarding_rules(pfw_rules=pfw_rules) + +.. warning:: + + In it's current form, this script is not written to handle an + appliance with multiple dhcp addressed WAN interfaces with related + inbound port forwarding rules. The current logic would find the + first matching interface with the specified label and update + all port forwarding rules for that destination IP. + +Runtime arguments +^^^^^^^^^^^^^^^^^ + +The python script has multiple runtime arguments defined. The two +required arguments are below: + +* Use ``-a`` or ``--appliance`` to specify the appliance hostname or ip + address to connect to +* Use ``-l`` or ``--label`` to specify WAN label (e.g. ``INET1``) to + retrieve IP information for and update destination ip addresses in + inbound port forwarding rules + +Example details +^^^^^^^^^^^^^^^^^ + +Prior to any changes the appliance has multiple existing inbound port +forwarding rules configured and a previous WAN IP address of +192.0.2.2/24 from dhcp as below: + + .. list-table:: + :header-rows: 1 + + * - Source IP + - Destination IP + - Destination Port/Range + - Protocol + - Translated IP + * - 0.0.0.0/0 + - 192.0.2.2/32 + - 443 + - TCP + - 198.51.100.2 + * - 0.0.0.0/0 + - 192.0.2.2/32 + - 8443 + - TCP + - 198.51.100.2 + +Assuming the WAN interface with a label of INET1 has it's WAN +address updated from dhcp, and is currently 192.0.2.40/24. Run the +script specifying the appliance's management IP address of +198.51.100.254 and the WAN label of INET1 + +.. code-block:: bash + + $ python update_port_forwarding_from_dhcp.py -a 198.51.100.254 -l INET1 + +Because the WAN ip has changed, is assigned via DHCP, and has a WAN +label of INET1, the port forwarding rules will be updated as follows: + + .. list-table:: + :header-rows: 1 + + * - Source IP + - Destination IP + - Destination Port/Range + - Protocol + - Translated IP + * - 0.0.0.0/0 + - **192.0.2.40/32** + - 443 + - TCP + - 198.51.100.2 + * - 0.0.0.0/0 + - **192.0.2.40/32** + - 8443 + - TCP + - 198.51.100.2 + +EdgeConnect API calls +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The API calls to Orchestrator (outside of authentication) used in this +example are: + +* :func:`pyedgeconnect.EdgeConnect.get_appliance_deployment` + * Retrieves appliance Deployment configuration to find interface IP + ip addresses and interface labels +* :func:`pyedgeconnect.EdgeConnect.get_port_forwarding_rules` + * Retrieves existing port forwarding rules on appliance +* :func:`pyedgeconnect.EdgeConnect.set_port_forwarding_rules` + * Updates port forwarding rules on appliance \ No newline at end of file diff --git a/docs/source/examples/upload_security_policy.rst b/docs/source/examples/upload_security_policy.rst index 17f8f47..98ded23 100644 --- a/docs/source/examples/upload_security_policy.rst +++ b/docs/source/examples/upload_security_policy.rst @@ -19,7 +19,7 @@ The code referenced in this document and all published examples with pyedgeconnect are available from the GitHub repository within the - `examples `_ + `examples `_ folder. Each example script contains logic to authenticate to the Orchestrator as documented in the authentication example. @@ -27,7 +27,7 @@ .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Upload EdgeConnect Security Policy ************************************ diff --git a/docs/source/install.rst b/docs/source/install.rst index 3fee3dc..08f06dd 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -57,7 +57,7 @@ interactive shell and run: .. code:: bash - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python + $ pip install git+https://github.com/aruba/pyedgeconnect ... $ pip list Package Version @@ -70,9 +70,9 @@ To install a specific branch use the @branch syntax .. code:: bash - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python@ + $ pip install git+https://github.com/aruba/pyedgeconnect@ # Install the Development branch - $ pip install git+https://github.com/SPOpenSource/edgeconnect-python@Development + $ pip install git+https://github.com/aruba/pyedgeconnect@Development Build Documentation Locally --------------------------- @@ -82,7 +82,7 @@ to include sphinx and related packages, then in the docs directory run ``make ht .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git $ cd edgeconnect-python $ pip install .[dev] $ cd docs diff --git a/docs/source/release-notes/0.13.0-a1.rst b/docs/source/release-notes/0.13.0-a1.rst index 276f276..6ff4a9a 100644 --- a/docs/source/release-notes/0.13.0-a1.rst +++ b/docs/source/release-notes/0.13.0-a1.rst @@ -6,4 +6,4 @@ ~~~~~~~~~~~~~ Prepared for first public release on GitHub: -github.com/SPOpenSource/edgeconnect-python \ No newline at end of file +github.com/aruba/pyedgeconnect \ No newline at end of file diff --git a/docs/source/release-notes/0.15.1-a1.rst b/docs/source/release-notes/0.15.1-a1.rst index ef093a9..0141e11 100644 --- a/docs/source/release-notes/0.15.1-a1.rst +++ b/docs/source/release-notes/0.15.1-a1.rst @@ -31,13 +31,13 @@ from .ecos._time 🐛 Bug Fixes ~~~~~~~~~~~~~~ -- `#4 `_ - +- `#4 `_ - :func:`~pyedgeconnect.Orchestrator.get_all_users` was using POST instead of GET -- `#6 `_ - +- `#6 `_ - :func:`~pyedgeconnect.Orchestrator.update_appliance_access_group` remove default values for appliance_groups and appliance_regions -- `#7 `_ - +- `#7 `_ - :func:`~pyedgeconnect.Orchestrator.get_timeseries_stats_tunnel_single_appliance` update tunnel_name query in URL to tunnelName diff --git a/docs/source/release-notes/0.15.2-a1.rst b/docs/source/release-notes/0.15.2-a1.rst index 16aa58c..e2a2b4b 100644 --- a/docs/source/release-notes/0.15.2-a1.rst +++ b/docs/source/release-notes/0.15.2-a1.rst @@ -10,7 +10,7 @@ be assigned to appliances - Overview: :doc:`/examples/upload_security_policy` - - Code: `upload_security_policy `_ + - Code: `upload_security_policy `_ - Updated logging messages (when using ``log_console`` and ``log_file`` parameters for EdgeConnect and Orchestrator) to include base Orch FQDN @@ -111,20 +111,20 @@ from .ecos._tunnel 🐛 Bug Fixes ~~~~~~~~~~~~~~ -- `#10 `_ - +- `#10 `_ - :func:`~pyedgeconnect.Orchestrator.login` and :func:`~pyedgeconnect.EdgeConnect.login` returned ``True`` even when login failed -- `#11 `_ - +- `#11 `_ - :func:`~pyedgeconnect.Orchestrator.appliance_resync` had incorrect endpoint of ``/applianceResyncSynchronize``, corrected to ``/applianceResync`` -- `#12 `_ - +- `#12 `_ - The preconfig generator example code had the option to look for a column for appliance serial number to match preconfig to, however, didn't include that information when creating the preconfig on Orchestrator as the parameter wasn't specified. -- `#13 `_ - +- `#13 `_ - Corrected return type-hint for methods that use `return_type="full_response"` with hint of requests.Response object rather than `dict` diff --git a/docs/source/release-notes/0.15.3-a1.rst b/docs/source/release-notes/0.15.3-a1.rst index bf8bb28..264b3e8 100644 --- a/docs/source/release-notes/0.15.3-a1.rst +++ b/docs/source/release-notes/0.15.3-a1.rst @@ -21,14 +21,14 @@ dashboards. Detailed documentation in the Examples section :ref:`ec_telemetry_demo` As with all examples, the code referenced is available from the GitHub -repository within the `examples `_ +repository within the `examples `_ folder. Clone the repository and download the examples with: .. code:: bash - $ git clone https://github.com/SPOpenSource/edgeconnect-python.git + $ git clone https://github.com/aruba/pyedgeconnect.git Init Requests Timeout for Orchestrator & EdgeConnect ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/release-notes/0.15.4-a1.rst b/docs/source/release-notes/0.15.4-a1.rst new file mode 100644 index 0000000..2334326 --- /dev/null +++ b/docs/source/release-notes/0.15.4-a1.rst @@ -0,0 +1,81 @@ +0.15.4-a1 -- 2023-01-30 +----------------------- + + +🚀 Features +~~~~~~~~~~~~~ + +✨ **A new home!**: Pyedgeconnect will now be maintained going forward +on the Aruba GitHub: https://github.com/aruba/pyedgeconnect + +✨ **A new video!**: Video walkthrough explaining and demonstrating the +EdgeConnect Telemetry code demo has been published, +`check it out! `_. + +✨ **A new code example!**: Automatically update port forwarding +rules on an appliance with a DHCP-addressed WAN interface to true up +discrepencies when the WAN IP address may have changed and requires +the destination address in the inbound port forwarding rules to be +updated. + + +Updated the following Orchestrator functions from Swagger: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :func:`~pyedgeconnect.Orchestrator.broadcast_cli` Updated return type + from ``bool`` to ``text`` forso that result can be retrieved using + returned key string with + :func:`~pyedgeconnect.Orchestrator.get_audit_log_task_status` +- :func:`~pyedgeconnect.Orchestrator.get_appliance_template_history` + Updated type hint for parameter ``latest`` to ``str`` from ``bool`` + + +Added the following EdgeConnect functions from Swagger: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :func:`~pyedgeconnect.EdgeConnect.get_appliance_flow_bandwidth_stats` +- :func:`~pyedgeconnect.EdgeConnect.get_appliance_flow_details` +- :func:`~pyedgeconnect.EdgeConnect.get_appliance_flow_details_verbose` +- :func:`~pyedgeconnect.EdgeConnect.get_appliance_flows` +- :func:`~pyedgeconnect.EdgeConnect.reclassify_flows` +- :func:`~pyedgeconnect.EdgeConnect.reset_flows` + +- :func:`~pyedgeconnect.EdgeConnect.get_port_forwarding_rules` +- :func:`~pyedgeconnect.EdgeConnect.set_port_forwarding_rules` +- :func:`~pyedgeconnect.EdgeConnect.set_gms_marked_port_forwarding_rules` + +- :func:`~pyedgeconnect.EdgeConnect.run_ping_or_traceroute` +- :func:`~pyedgeconnect.EdgeConnect.get_ping_or_traceroute` +- :func:`~pyedgeconnect.EdgeConnect.stop_ping_or_traceroute` + +- :func:`pyedgeconnect.EdgeConnect.get_appliance_nexthops` + + +🐛 Bug Fixes +~~~~~~~~~~~~~~ + + +💥 Breaking Changes: +~~~~~~~~~~~~~~~~~~~~~~~ + +- Corrected function name for :func:`~pyedgeconnect.EdgeConnect.get_vrrp_interfaces` + which previously was missing `c` in `interfaces` + + +🧰 Maintenance / Other +~~~~~~~~~~~~~~~~~~~~~~~ + +- Within the EdgeConnect Telemetry Demo code example: fix ``Total Flows`` + calculation for tunnel at bottom of ``EdgeConnect Telemetry`` + dashboard + + +🐛 Known Issues +~~~~~~~~~~~~~~~ + +.. warning:: + + The following two functions for the _ip_objects submodule exprience + errors at this time. These function do work in the Orchestrator UI: + :func:`~pyedgeconnect.Orchestrator.bulk_upload_address_group` and + :func:`~pyedgeconnect.Orchestrator.bulk_upload_service_group` \ No newline at end of file diff --git a/docs/source/release-notes/index.rst b/docs/source/release-notes/index.rst index fc98b58..e787562 100644 --- a/docs/source/release-notes/index.rst +++ b/docs/source/release-notes/index.rst @@ -8,6 +8,7 @@ All of the release notes for pyedgeconnect are organized below ================== .. toctree:: + 0.15.4-a1 0.15.3-a1 0.15.2-a1 0.15.1-a1 diff --git a/examples/edgeconnect-telemetry-demo/Dockerfile b/examples/edgeconnect-telemetry-demo/Dockerfile index 7532be6..638903a 100644 --- a/examples/edgeconnect-telemetry-demo/Dockerfile +++ b/examples/edgeconnect-telemetry-demo/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:latest RUN mkdir /app COPY ./app/ec-telemetry /app/ec-telemetry RUN chmod +x /app/ec-telemetry diff --git a/examples/edgeconnect-telemetry-demo/app/grafana/dashboards/ec_appliance.json b/examples/edgeconnect-telemetry-demo/app/grafana/dashboards/ec_appliance.json index 7f2e14c..9ab92ba 100644 --- a/examples/edgeconnect-telemetry-demo/app/grafana/dashboards/ec_appliance.json +++ b/examples/edgeconnect-telemetry-demo/app/grafana/dashboards/ec_appliance.json @@ -5788,10 +5788,6 @@ "alias": "Total Flows", "mode": "reduceRow", "reduce": { - "include": [ - "non-TCP Flows {_field=\"non-TCP_flow_count\", _start=\"2022-10-07 18:13:51.021 +0000 UTC\", _stop=\"2022-10-07 19:13:51.021 +0000 UTC\", appliance_id=\"1326.NE\", hostname=\"Kennesaw3-Powers\", name=\"tunnel_stats\", overlay=\"DEFAULT\", tunnel_alias=\"to_EAST8-AWS_DEFAULT\", tunnel_id=\"bondedTunnel_107436\"}", - "TCP Flows {_field=\"TCP_flow_count\", _start=\"2022-10-07 18:13:51.021 +0000 UTC\", _stop=\"2022-10-07 19:13:51.021 +0000 UTC\", appliance_id=\"1326.NE\", hostname=\"Kennesaw3-Powers\", name=\"tunnel_stats\", overlay=\"DEFAULT\", tunnel_alias=\"to_EAST8-AWS_DEFAULT\", tunnel_id=\"bondedTunnel_107436\"}" - ], "reducer": "sum" } } diff --git a/examples/edgeconnect-telemetry-demo/docker-compose.yml b/examples/edgeconnect-telemetry-demo/docker-compose.yml index a61056b..3538317 100644 --- a/examples/edgeconnect-telemetry-demo/docker-compose.yml +++ b/examples/edgeconnect-telemetry-demo/docker-compose.yml @@ -19,7 +19,7 @@ services: influxdb: container_name: influxdb - image: influxdb:2.4 + image: influxdb:latest environment: - DOCKER_INFLUXDB_INIT_MODE=setup - DOCKER_INFLUXDB_INIT_USERNAME=${DB_USER} @@ -37,7 +37,7 @@ services: redis: container_name: redis - image: redis:4.0.6-alpine + image: redis:latest volumes: - ./app/redis:/redis restart: unless-stopped @@ -97,7 +97,7 @@ services: restart: unless-stopped grafana: - image: grafana/grafana-enterprise:9.1.6 + image: grafana/grafana-enterprise:latest container_name: grafana entrypoint: - /run.sh diff --git a/examples/edgeconnect-telemetry-demo/example.env b/examples/edgeconnect-telemetry-demo/example.env index 7547b51..5958839 100644 --- a/examples/edgeconnect-telemetry-demo/example.env +++ b/examples/edgeconnect-telemetry-demo/example.env @@ -13,6 +13,7 @@ EC_PW= # DB_USER and DB_PW used to login to both Grafana/InfluxDB DB_TOKEN= DB_USER=admin +# DB_PW must be > 8 characters to meet minimum requirements DB_PW= # Number of replica containers for ec-telemetry-worker diff --git a/examples/update_port_forwarding_from_dhcp.py b/examples/update_port_forwarding_from_dhcp.py new file mode 100644 index 0000000..697f4f9 --- /dev/null +++ b/examples/update_port_forwarding_from_dhcp.py @@ -0,0 +1,103 @@ +import argparse +import getpass +import os + +from pyedgeconnect import EdgeConnect + +# Parse runtime arguments +parser = argparse.ArgumentParser() +parser.add_argument( + "-a", + "--appliance", + help="Appliance ip address", + type=str, +) +parser.add_argument( + "-l", + "--label", + help="Interface WAN label to check", + type=str, +) +args = parser.parse_args() + +# Set EdgeConnect FQDN/IP via arguments or user input +if vars(args)["appliance"] is not None: + ec_url = vars(args)["appliance"] +else: + ec_url = input("EdgeConnect IP or FQDN: ") + +# Set EdgeConnect login via environment variable or user input +if os.getenv("EC_USER") is not None and os.getenv("EC_PW"): + ec_user = os.getenv("EC_USER") + ec_pw = os.getenv("EC_PW") +else: + ec_user = input("EdgeConnect admin user: ") + ec_pw = getpass.getpass("EdgeConnect admin password: ") + +# Instantiate EdgeConnect with ``log_console`` enabled for +# printing log messages to terminal +ec = EdgeConnect( + ec_url, + log_console=True, + verify_ssl=False, +) + +ec.login(ec_user, ec_pw) + +target_label = vars(args)["label"] +if target_label is None: + target_label = input("WAN label to check (e.g. INET1): ") + +# Check that a valid WAN label was specified to match for inbound port +# forwarding rules +if target_label is None: + print("No WAN label provided to check inbound port forwarding for") + exit() + +# Get deployment detail from appliance +deployment = ec.get_appliance_deployment() + +# Identify WAN label ID from label name provided by user +for wan_label in deployment["sysConfig"]["ifLabels"]["wan"]: + if wan_label["name"] == target_label: + label_id = wan_label["id"] + break + else: + label_id = None + +if label_id is None: + print(f"WAN label {target_label} not present on {hostname}") + exit() + +# Find WAN IP address of corresponding interface with WAN label that is +# configured with DHCP +for interface in deployment["modeIfs"]: + for subinterface in interface["applianceIPs"]: + if subinterface["label"] == label_id and subinterface["dhcp"] is True: + wan_ip = subinterface["ip"] + break + else: + wan_ip = None + +# If WAN IP found, get existing inbound port forwarding rules and update +if wan_ip is not None: + + pfw_rules = ec.get_port_forwarding_rules() + + # If WAN IP is different than the destination subnet in existing + # port forwarded rules, update with the current WAN IP from + # deployment + rule_update = False + for rule in pfw_rules: + if rule["destSubnet"].split("/")[0] == wan_ip: + print("WAN IP is correct in PFW rule, no change") + else: + rule["destSubnet"] = wan_ip + "/32" + print("WAN IP is different in PFW rule, updating") + rule_update = True + + if rule_update: + + ec.set_port_forwarding_rules(pfw_rules=pfw_rules) + +ec.logout() diff --git a/pyedgeconnect/__init__.py b/pyedgeconnect/__init__.py index 3a87283..1283cf6 100644 --- a/pyedgeconnect/__init__.py +++ b/pyedgeconnect/__init__.py @@ -1224,7 +1224,7 @@ def __init__( ) from .orch._vrf_dnat_maps import get_dnat_maps from .orch._vrf_snat_maps import get_snat_maps - from .orch._vrrp import get_vrrp_interfaes + from .orch._vrrp import get_vrrp_interfaces from .orch._vti import get_vti_interfaes from .orch._vxoa_hostname import update_appliance_hostname from .orch._wan_next_hop_health import get_wan_next_hop_health_config @@ -1382,6 +1382,14 @@ def __init__( from .ecos._deployment import get_appliance_deployment from .ecos._disk_usage import get_appliance_disk_usage from .ecos._dns import get_appliance_dns_config, set_appliance_dns_config + from .ecos._flows import ( + get_appliance_flow_bandwidth_stats, + get_appliance_flow_details, + get_appliance_flow_details_verbose, + get_appliance_flows, + reclassify_flows, + reset_flows, + ) from .ecos._gms import assign_orchestrator, get_orchestrator from .ecos._interfaces import get_appliance_interfaces from .ecos._license import is_reboot_required @@ -1404,7 +1412,18 @@ def __init__( get_appliance_network_interfaces, modify_network_interfaces, ) + from .ecos._nexthops import get_appliance_nexthops from .ecos._peers import get_appliance_peers, get_appliance_peers_ec_only + from .ecos._ping_trace import ( + get_ping_or_traceroute, + run_ping_or_traceroute, + stop_ping_or_traceroute, + ) + from .ecos._port_forwarding import ( + get_port_forwarding_rules, + set_gms_marked_port_forwarding_rules, + set_port_forwarding_rules, + ) from .ecos._reboot import request_reboot from .ecos._save_changes import save_changes from .ecos._security_maps import ( diff --git a/pyedgeconnect/ecos/_flows.py b/pyedgeconnect/ecos/_flows.py new file mode 100644 index 0000000..4e8a1af --- /dev/null +++ b/pyedgeconnect/ecos/_flows.py @@ -0,0 +1,556 @@ +# MIT License +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP. +# +# flows : ECOS current flows + + +def get_appliance_flows( # noqa: C901, silences flake8 complexity + self, + ip1: str = None, + mask1: str = None, + port1: int = None, + ip2: str = None, + mask2: str = None, + port2: int = None, + ip_either_flag: bool = True, + port_either_flag: bool = True, + vrf1: str = None, + vrf2: str = None, + vrf_either: str = None, + application: str = None, + application_group: str = None, + protocol: str = None, + vlan: int = None, + dscp: str = None, + overlays: str = None, + transport: str = None, + services: str = None, + zone1: str = None, + zone2: str = None, + zone_either: str = None, + flow_category: str = "all", + edge_ha: bool = False, + built_in: bool = False, + uptime: str = None, + active_uptime_start: int = None, + active_uptime_end: int = None, + term_uptime_start: int = None, + term_uptime_end: int = None, + bytes_transferred: str = "total", + duration: str = None, + anytime_slow_flows: str = None, +) -> dict: + """Get active, inactive, or both types of flows from an appliance + based on supplied query parameters + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - flows + - GET + - /flows + + :param ip1: First IP endpoint, defaults to None + :type ip1: str, optional + :param mask1: Mask for ip1 ip address, defaults to None + :type mask1: str, optional + :param port1: Port for ip1, defaults to None + :type port1: int, optional + :param ip2: Second IP endpoint, defaults to None + :type ip2: str, optional + :param mask2: Mask for ip2 ip address, defaults to None + :type mask2: str, optional + :param port2: Port for ip2, defaults to None + :type port2: int, optional + :param ip_either_flag: Enable directionality for IP parameters. + ``True`` will treat ip1 as source ip and ip2 as destination ip, + defaults to True. + :type ip_either_flag: bool, optional + :param port_either_flag: Enable directionality for port parameters. + ``True`` will treat port1 as source port and port2 as + destination port, defaults to True. + :type port_either_flag: bool, optional + :param vrf1: VRF ID for ip1, ``0`` is the default VRF ID, + defaults to None + :type vrf1: str, optional + :param vrf2: VRF ID for ip2, ``0`` is the default VRF ID, + defaults to None + :type vrf2: str, optional + :param vrf_either: VRF ID for flows sourced from or destinted to. + Accepted values are ``any``, ``0`` for default VRF, or a + specific VRF ID, defaults to None + :type vrf_either: str, optional + :param application: Filter for application-specific flows. Allows + both built-in and user-defined applications as values, + defaults to None + :type application: str, optional + :param application_group: Filter for application-group flows, + defaults to None + :type application_group: str, optional + :param protocol: Filter by protocol, e.g. ``ip``, ``icmp``, ``bgp``, + etc., defaults to None + :type protocol: str, optional + :param vlan: Filter for VLAN ID, defaults to None + :type vlan: int, optional + :param dscp: Filter for DSCP marking, defaults to None + :type dscp: str, optional + :param overlays: Filter for Overlay ID, multiple values can be + passed separated by | e.g. ``1|2``, defaults to None + :type overlays: str, optional + :param transport: Transport type, accepted values include + ``fabric``, ``underlay``, ``breakout``. Multiple values can be + used together separated by | e.g. ``fabric|underlay``, + defaults to None + :type transport: str, optional + :param services: Service name, third-party services should be + formatted with an asterisk at the end to filter by prefix + instead of complete match, multiple services separated by | + e.g. ``Zscaler_*|PaloAlto``, defaults to None + :type services: str, optional + :param zone1: Flows to zone1, accepted values include ``any``, ``0`` + for default zone, or specific zone id, defaults to None + :type zone1: str, optional + :param zone2: Flows from zone2, accepted values include ``any``, + ``0`` for default zone, or specific zone id, defaults to None + :type zone2: str, optional + :param zone_either: Flows to or from specified zone, accepted values + include ``any``, ``0`` for default zone, or specific zone id, + defaults to None + :type zone_either: str, optional + :param flow_category: Filter flow category, accepted values are + ``all``, ``asymmetric``, ``stale``, ``passThrough``, + ``boosted``, ``routeDropped``, ``firewallDropped``, + ``directlyAttached``, defaults to None + :type flow_category: str, optional + :param edge_ha: ``True`` to include EdgeHA flows, False to exclude, + defaults to False + :type edge_ha: bool, optional + :param built_in: ``True`` to include built-in policy flows, False to + exclude, defaults to False + :type built_in: bool, optional + :param uptime: Filter for uptime of flow. Term for ended within. + Accepted values include ``anytime``, ``last5m``, ``term5m``, + ``term``, ``last1hr``, ``term1hr``, ``last4hr``, ``term4h``, + ``last24hr``, ``term24hr``. ``last`` implies started, ``term`` + implies ended. Values can be combined with ``%7C``, e.g., + value of ``anytime%7Cterm24hr`` would imply started anytime or + ended within past 24 hours. Defaults to None + :type uptime: str, optional + :param active_uptime_start: Custom start time filter for active + flows, units in epoch milliseconds, must be used along with + ``active_uptime_end``, otherwise ignored. Defaults to None + :type active_uptime_start: int, optional + :param active_uptime_end: Custom end time filter for active + flows, units in epoch milliseconds, must be used along with + ``active_uptime_start``, otherwise ignored. Defaults to None + :type active_uptime_end: int, optional + :param term_uptime_start: Custom start time filter for ended + flows, units in epoch milliseconds, must be used along with + ``term_uptime_end``, otherwise ignored. Defaults to None + :type term_uptime_start: int, optional + :param term_uptime_end: Custom end time filter for ended + flows, units in epoch milliseconds, must be used along with + ``term_uptime_start``, otherwise ignored. Defaults to None + :type term_uptime_end: int, optional + :param bytes_transferred: Bytes transfered, accepted values are + ``total`` and ``last5m``, defaults to "total" + :type bytes_transferred: str, optional + :param duration: Flows that have lasted less than ("<") or greater + than (">") the specified duration (in minutes). Value should be + ``any`` or a number formatted with a "<" or ">" in front of a + value between ``0`` and ``18446744073709551615``. + e.g. ``>5000``, defaults to None + :type duration: str, optional + :param anytime_slow_flows: Slow Flows flag. If this flag is present, + it will show slow flows only, defaults to None + :type anytime_slow_flows: str, optional + :return: Returns dictionary of flows based on supplied query + details \n + * keyword **total_flows** (`int`): Total number of flows + * keyword **matched_flows** (`int`): Total number of matched + flows + * keyword **now** (`int`): current epoch time in seconds + * keyword **returned_flows** (`int`): Number of returned flows + * keyword **stale_flows** (`int`): Number of stale flows + * keyword **inconsistent_flows** (`int`): Number of inconsistent + flows + * keyword **flows_with_issues** (`int`): Number of flows with + issues + * keyword **flows_optimized** (`int`): Number of optimized flows + * keyword **flows_with_ignores** (`int`): No description in + Swagger + * keyword **flows_passthrough** (`int`): Number of passthrough + flows + * keyword **flows_management** (`int`): Number of management + flows + * keyword **active** (`dict`): Active flows object \n + * keyword **total_flows** (`int`): Number of active flows + * keyword **stale_flows** (`int`): Number of active stale + flows + * keyword **inconsistent_flows** (`int`): Number of active + inconsistent flows + * keyword **flows_with_issues** (`int`): Number of active + flows with issues + * keyword **flows_optimized** (`int`): Number of active + optimized flows + * keyword **flows_with_ignores** (`int`): No description in + Swagger + * keyword **flows_passthrough** (`int`): Number of active + passthrough flows + * keyword **flows_management** (`int`): Number of active + management flows + * keyword **flows_asymmetric** (`int`): Number of active + asymmetric flows + * keyword **flows_route_dropped** (`int`): Number of active + flows dropped due to route + * keyword **flows_firewall_dropped** (`int`): Number of + active flows dropped due to firewall + * keyword **inactive** (`dict`): Inactive flows object \n + * keyword **total_flows** (`int`): Number of inactive flows + * keyword **stale_flows** (`int`): Number of inactive stale + flows + * keyword **inconsistent_flows** (`int`): Number of inactive + inconsistent flows + * keyword **flows_with_issues** (`int`): Number of inactive + flows with issues + * keyword **flows_optimized** (`int`): Number of inactive + optimized flows + * keyword **flows_with_ignores** (`int`): No description in + Swagger + * keyword **flows_passthrough** (`int`): Number of inactive + passthrough flows + * keyword **flows_management** (`int`): Number of inactive + management flows + * keyword **flows_asymmetric** (`int`): Number of inactive + asymmetric flows + * keyword **flows_route_dropped** (`int`): Number of + inactive flows dropped due to route + * keyword **flows_firewall_dropped** (`int`): Number of + inactive flows dropped due to firewall + * keyword **ret_code** (`int`): No description in Swagger + * keyword **err_msg** (`str`): Error message + * keyword **flows** (`list[list]`): List of flow details \n + * [0] (`int`): Flow Id + * [1] (`int`): Flow Sequence Id + * [2] (`int`): SilverPeak Flow Id + * [3] (`str`): Application Name, e.g. ``https`` + * [4] (`int`): IP1_1, IPv6 128-bit source IP address spread + over four fields (IP1_1-4). If IPv4, 32-bit integer format + and other fields are ``0``. + * [5] (`int`): IP1_2, in 32-bit integer format + * [6] (`int`): IP1_3, in 32-bit integer format + * [7] (`int`): IP1_4, in 32-bit integer format + * [8] (`int`): Ip1 Version e.g. ``4`` or ``6`` + * [9] (`str`): IP1 String + * [10] (`int`): Port1, port number of source address + * [11] (`int`): IP2_1, IPv6 128-bit destination IP address + spread over four fields (IP2_1-4). If IPv4, 32-bit integer + format and other fields are ``0``. + * [12] (`int`): IP2_2, in 32-bit integer format + * [13] (`int`): IP2_3, in 32-bit integer format + * [14] (`int`): IP2_4, in 32-bit integer format + * [15] (`int`): Ip2 Version e.g. ``4`` or ``6`` + * [16] (`str`): IP2 String + * [17] (`int`): Port2, port number of destination address + * [18] (`int`): Flow Optimization Status + * [19] (`int`): Percentage Reduction Inbound + * [20] (`int`): Inbound TX Bytes + * [21] (`int`): Inbound RX Bytes + * [22] (`int`): Outbound RX Bytes + * [23] (`int`): Outbound TX Bytes + * [24] (`int`): Percentage Reduction OutBound + * [25] (`int`): Uptime in ms + * [26] (`str`): Protocol, e.g. ``tcp`` + * [27] (`str`): Outbound Tunnel Id + * [28] (`str`): Inbound Tunnel Id + * [29] (`str`): Configured Outbound Tunnel Id + * [30] (`int`): LAN Side VLAN associated with flow + * [31] (`int`): QoS Traffic Class (1-10) + * [32] (`str`): LAN DSCP treatment behavior, + e.g. ``trust-lan`` + * [33] (`str`): WAN DSCP treatment behavior, + e.g. ``trust-lan`` + * [34] (`int`): IP address of Peer flow redirected from + * [35] (`str`): ISCI vendor Flow Info + * [36] (`int`): NAT address of IP1 (source) + * [37] (`int`): NAT address of IP1 (destination) + * [38] (`int`): Flow Start Time, unix epoch seconds + * [39] (`int`): Flow End Time, unix epoch seconds + * [40] (`int`): Service Id, identifying owner and location + * [41] (`int`): SaaS Id + * [42] (`int`): TCP UTC Slow Start Time, unix epoch seconds + * [43] (`int`): TCP Slow Duration + * [44] (`str`): IP1 Domain Name + * [45] (`str`): IP2 Domain Name + * [46] (`int`): From Zone, integer zone id + * [47] (`int`): To Zone, integer zone id + * [48] (`int`): If flow was dropped by security policy (ZBF) + e.g. ``0`` if not dropped. + * [49] (`str`): EdgeHA, ``"true"`` or ``"false"`` if belongs + to HA interface subnet + * [50] (`str`): LAN TX DSCP + * [51] (`str`): LAN RX DSCP + * [52] (`str`): WAN TX DSCP + * [53] (`str`): WAN RX DSCP + * [54] (`str`): NAT IP (if NAT applied) + * [55] (`str`): NAT Port + * [56] (`str`): Original Port + * [57] (`str`): NAT Type, e.g. ``LAN_SNAT``, ``LAN_DNAT``, + ``WAN_SNAT`` or ``WAN_DNAT`` + * [58] (`str`): Application Type, e.g. ``Idle``, ``Voice``, + ``Video_Conferencing``, ``Video_Streaming``, + ``Bulk_Data_transfer``, or ``Interactive`` + * [59] (`int`): Drop reason, ``0`` for not dropped, ``1`` + indicates dropped by ZBF, ``2`` dropped due to routing + decision + * [60] (`str`): Business Intent Overlay, + e.g. ``DefaultOverlay`` + * [61] (`int`): Source VRF ID + * [62] (`int`): Destination VRF ID + :rtype: dict + """ + path = "/flows?" + + if ip1 is not None: + path = path + f"&ip1={ip1}" + if mask1 is not None: + path = path + f"&mask1={mask1}" + if port1 is not None: + path = path + f"&port1={port1}" + if ip2 is not None: + path = path + f"&ip2={ip2}" + if mask2 is not None: + path = path + f"&mask2={mask2}" + if port2 is not None: + path = path + f"&port2={port2}" + + path = path + f"&ipEitherFlag={ip_either_flag}" + path = path + f"&portEitherFlag={port_either_flag}" + + if vrf1 is not None: + path = path + f"&vrf1={vrf1}" + if vrf2 is not None: + path = path + f"&vrf2={vrf2}" + if vrf_either is not None: + path = path + f"&vrfEither={vrf_either}" + if application is not None: + path = path + f"&application={application}" + if application_group is not None: + path = path + f"&applicationGroup={application_group}" + if protocol is not None: + path = path + f"&protocol={protocol}" + if vlan is not None: + path = path + f"&vlan={vlan}" + if dscp is not None: + path = path + f"&dscp={dscp}" + if overlays is not None: + path = path + f"&overlays={overlays}" + if transport is not None: + path = path + f"&transport={transport}" + if services is not None: + path = path + f"&services={services}" + if zone1 is not None: + path = path + f"&zone1={zone1}" + if zone2 is not None: + path = path + f"&zone2={zone2}" + if zone_either is not None: + path = path + f"&zoneEither={zone_either}" + + path = path + f"&filter={flow_category}" + path = path + f"&edgeHa={edge_ha}" + path = path + f"&builtIn={built_in}" + + if uptime is not None: + path = path + f"&uptime={uptime}" + if active_uptime_start is not None and active_uptime_end is not None: + path += f"&anyStartTime={active_uptime_start}&anyEndTime={active_uptime_end}" + if term_uptime_start is not None and term_uptime_end is not None: + path += ( + f"&termStartTime={term_uptime_start}&termEndTime={term_uptime_end}" + ) + + path = path + f"&bytes={bytes_transferred}" + path = path + f"&duration={duration}" + + if duration is not None: + path = path + f"&duration={duration}" + if anytime_slow_flows is not None: + path = path + f"&anytimeSlowFlows={anytime_slow_flows}" + + return self._get(path) + + +def reset_flows( + self, + flow_id_list: list, +) -> dict: + """Reset specified flows on an appliance. + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - flows + - POST + - /flowsReset + + :param flow_id_list: List of flow id's to be reset on appliance, + e.g. ``[0,1,2]`` + :type flow_id_list: list + :return: If specified flows were present, returns results, otherwise + will respond with a 204, empty content + :rtype: dict + """ + data = {"spIds": flow_id_list} + + return self._post( + "/flowsReset", + data=data, + expected_status=[200, 204], + ) + + +def reclassify_flows( + self, + flow_id_list: list = [], +) -> dict: + """Reclassify specified flows on an appliance. + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - flows + - POST + - /flowsReClassification + + :param flow_id_list: List of flow id's to be reset on appliance, + e.g. ``[0,1,2]``, defaults to empty list to reclassify all flows + :type flow_id_list: list + :return: Responds with dictionary with action success message \n + * keyword **results** (`list`): No description in Swagger + * keyword **rc** (`int`): ``0`` indicates success, ``-1`` + indicates failure + * keyword **value** (`str`): Action message, + e.g. ``Action set successful.`` + :rtype: dict + """ + data = {"spIds": flow_id_list} + + return self._post("/flowsReClassification", data=data) + + +def get_appliance_flow_bandwidth_stats( + self, + flow_id: int, + flow_seq_num: int, +) -> list: + """Get the so far accumulated bandwidth stats about the flow + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - flows + - GET + - /flowBandwidthStats + + :param flow_id: Flow ID + :type flow_id: int + :param flow_seq_num: Flow sequence number + :type flow_seq_num: int + :return: Returns list of dictionaries for so far accumulated + bandwidth stats about the flow + :rtype: list[dict] + """ + return self._get(f"/flowBandwidthStats?id={flow_id}&seq={flow_seq_num}") + + +def get_appliance_flow_details( + self, + flow_id: int, + flow_seq_num: int, +) -> list: + """Get specific flow details from appliance + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - flows + - GET + - /flowDetails + + :param flow_id: Flow ID + :type flow_id: int + :param flow_seq_num: Flow sequence number + :type flow_seq_num: int + :return: Returns nested dictionary/lists of flow details \n + [`dict`]: Response object \n + * keyword **Route** (`list[dict]`): No description in + Swagger + * keyword **Optimization** (`list[dict]`): No description in + Swagger + * keyword **Stats** (`list[dict]`): No description in + Swagger + * keyword **QoS** (`list[dict]`): No description in Swagger + * keyword **Application Performance** (`list[dict]`): No + description in Swagger + :rtype: list + """ + return self._get(f"flowDetails?id={flow_id}&seq={flow_seq_num}") + + +def get_appliance_flow_details_verbose( + self, + flow_id: int, + flow_seq_num: int, +) -> list: + """Get verbose specific flow details from appliance + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - flows + - GET + - /flowDetails2 + + :param flow_id: Flow ID + :type flow_id: int + :param flow_seq_num: Flow sequence number + :type flow_seq_num: int + :return: Returns list of dictionaries flow details \n + [`dict`]: Response object \n + * keyword **General** (`dict`): General stats object \n + * keyword **Stats** (`list[dict]`): No description in + Swagger + * keyword **Route** (`list[dict]`): No description in + Swagger + * keyword **Optimization** (`list[dict]`): No + description in Swagger + * keyword **QoS** (`list[dict]`): No description in + Swagger + * keyword **TCP Info** (`dict`): TCP info object \n + * keyword **TCP-Sats** (`list[dict]`): No description + in Swagger + * keyword **Nat Info** (`dict`): NAT info object \n + * keyword **NAT** (`list[dict]`): No description in + Swagger + + :rtype: list + """ + return self._get(f"/flowDetails2?id={flow_id}&seq={flow_seq_num}") diff --git a/pyedgeconnect/ecos/_nexthops.py b/pyedgeconnect/ecos/_nexthops.py new file mode 100644 index 0000000..53788eb --- /dev/null +++ b/pyedgeconnect/ecos/_nexthops.py @@ -0,0 +1,38 @@ +# MIT License +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP. +# +# nexthops : Next hops +from __future__ import annotations + + +def get_appliance_nexthops( + self, +) -> list[dict]: + """Get appliance's data path next hops information + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - nexthops + - GET + - /nexthops + + :return: Returns list of dictionaries of next hop information \n + * [`dict`]: list of next hop objects \n + * keyword **nhop_ipver** (`str`): IP version for next hop, + ``ipv4`` or ``ipv6`` + * keyword **source** (`str`): Next hop for data path WAN or + LAN side + * keyword **nhop_ip** (`str`): Next hop IP address + * keyword **nhop_state** (`str`): Next hop reachable state, + ``reachable`` or ``unreachable`` + * keyword **nhop_uptime** (`int`): Next hop uptime in + milliseconds + * keyword **nhop_ifname** (`str`): Data path interface this + Next hop belong to, e.g. ``wan0`` + :rtype: list[dict] + """ + return self._get("/nexthops") diff --git a/pyedgeconnect/ecos/_ping_trace.py b/pyedgeconnect/ecos/_ping_trace.py new file mode 100644 index 0000000..c4d5a46 --- /dev/null +++ b/pyedgeconnect/ecos/_ping_trace.py @@ -0,0 +1,107 @@ +# MIT License +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP. +# +# pingTrace : Ping and Traceroute + + +def run_ping_or_traceroute( + self, + command: str, + destination_ip_hostname: str, + options: str, +) -> dict: + """Run a ping or traceroute from appliance + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - pingTrace + - POST + - /pingTrace + + :param command: Accepted values of ``ping`` or ``traceroute`` + :type command: str + :param destination_ip_hostname: Destination IP or hostname to try + to ping or traceroute to, e.g. ``8.8.8.8`` + :type destination_ip_hostname: str + :param options: Ping or traceroute command options as you would use + in the appliance UI. e.g., For ping specify an interface with + ``-I wan0`` + :type options: str + :return: Returns dictionary of process id, data, and status \n + * keyword **pid** (`int`): Process ID to reference + * keyword **data** (`str`): Current data from command, generally + blank when just run + * keyword **status** (`str`): Status of process, if currently + running, ``True``, otherwise ``False`` + :rtype: dict + """ + data = { + "cmd": command, + "ip_hostname": destination_ip_hostname, + "options": options, + } + + return self._post( + "/pingTrace", + data=data, + ) + + +def get_ping_or_traceroute( + self, + pid: int, +) -> dict: + """Get the result of running the ping or traceroute commands on + the appliance + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - pingTrace + - POST + - /pingTrace/{pid} + + :param pid: Integer process ID of running ping or traceroute + :type pid: int + :return: Returns dictionary of process id, data, and status \n + * keyword **pid** (`int`): Process ID to reference + * keyword **data** (`str`): Current data from command + * keyword **status** (`str`): Status of process, if currently + running, ``True``, otherwise ``False`` + :rtype: dict + """ + return self._get(f"/pingTrace/{pid}") + + +def stop_ping_or_traceroute( + self, + pid: int, +) -> str: + """Stop the a current ping or traceroute commands on the appliance + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - pingTrace + - POST + - /pingTrace/{pid} + + :param pid: Integer process ID of running ping or traceroute + :type pid: int + :return: Returns string of ``OK`` if successfull + :rtype: dict + """ + return self._delete( + f"/pingTrace/{pid}", + return_type="text", + ) diff --git a/pyedgeconnect/ecos/_port_forwarding.py b/pyedgeconnect/ecos/_port_forwarding.py new file mode 100644 index 0000000..3ed5842 --- /dev/null +++ b/pyedgeconnect/ecos/_port_forwarding.py @@ -0,0 +1,168 @@ +# MIT License +# (C) Copyright 2023 Hewlett Packard Enterprise Development LP. +# +# portForwarding : Port Forwarding Settings +from __future__ import annotations + + +def get_port_forwarding_rules( + self, +) -> list[dict]: + """Get port forwarding rules on this appliance + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - portForwarding + - GET + - /portForwarding + + :return: Returns list of dictionaries of each port forwarding rule + configured on appliance \n + * [`dict`]: List of port forwarding rule objects \n + * keyword **srcSubnet** (`str`): Source subnet of the rule + * keyword **destSubnet** (`str`): Destination subnet of the + rule + * keyword **destPort** (`str`): Destination port or port range + of the rule, e.g., ``123`` or ``123-234``, and should be + ``0`` for ICMP protocol, and should be ``0-65535`` for ANY + protocol + * keyword **protocol** (`str`): Protocol of the rule, could be + any of ``UDP``, ``TCP``, ``ICMP`` and ``ANY`` + * keyword **targetIp** (`str`): If a packet header matches + this rule, the packet would be translated to this new IP + address + * keyword **targetPort** (`str`): If a packet header matches + this rule, the packet would be translated to this new port + or port range + * keyword **gms_marked** (`bool`): To indicate whether this + rule was generated by GMS or by user + * keyword **comment** (`str`): Comment + * keyword **srcIf** (`str`): Source interface name + * keyword **vrf_id** (`int`): vrf segment id + :rtype: list[dict] + """ + return self._get("/portForwarding") + + +def set_port_forwarding_rules( + self, + pfw_rules: list, +) -> bool: + """Set port forwarding rules on this appliance + + .. warning:: + + This will overwrite all existing port forwarding rules on + appliance. If you're trying to append new rules to existing + rules, first use + :func:`~pyedgeconnect.EdgeConnect.get_port_forwarding_rules` + to get existing rules, append new rules to the list, then post + all the rules together with this function. + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - portForwarding + - POST + - /portForwarding2 + + :param pfw_rules: List of port forwarding rules to be configured + on the appliance. \n + * [`dict`]: Port forwarding rule list object \n + * keyword **srcSubnet** (`str`): Source subnet of the rule + * keyword **destSubnet** (`str`): Destination subnet of the + rule + * keyword **destPort** (`str`): Destination port or port range + of the rule, e.g., ``123`` or ``123-234``, and should be + ``0`` for ICMP protocol, and should be ``0-65535`` for ANY + protocol + * keyword **protocol** (`str`): Protocol of the rule, could be + any of ``UDP``, ``TCP``, ``ICMP`` and ``ANY`` + * keyword **targetIp** (`str`): If a packet header matches + this rule, the packet would be translated to this new IP + address + * keyword **targetPort** (`str`): If a packet header matches + this rule, the packet would be translated to this new port + or port range + * keyword **gms_marked** (`bool`): To indicate whether this + rule was generated by GMS or by user + * keyword **comment** (`str`): Comment + * keyword **srcIf** (`str`): Source interface name + * keyword **vrf_id** (`int`): vrf segment id + :type pfw_rules: list[dict] + :return: If successful returns string of ``Successfully saved port + forwarding rules.`` + :rtype: str + """ + return self._post( + "/portForwarding2", + data=pfw_rules, + return_type="text", + ) + + +def set_gms_marked_port_forwarding_rules( + self, + pfw_rules: list, +) -> bool: + """Set GMS marked port forwarding rules on this appliance (as if + they were configured by Orchestrator, greyed out in the UI until + edited). + + .. warning:: + + Rules added via this function will not persist if + :func:`~pyedgeconnect.EdgeConnect.set_port_forwarding_rules` is + used or if the rules are edited in the UI. The rules will not + overwrite non-gms marked rules. + + .. list-table:: + :header-rows: 1 + + * - Swagger Section + - Method + - Endpoint + * - portForwarding + - POST + - /portForwarding2/gms + + :param pfw_rules: List of port forwarding rules to be configured + on the appliance. \n + * [`dict`]: Port forwarding rule list object \n + * keyword **srcSubnet** (`str`): Source subnet of the rule + * keyword **destSubnet** (`str`): Destination subnet of the + rule + * keyword **destPort** (`str`): Destination port or port range + of the rule, e.g., ``123`` or ``123-234``, and should be + ``0`` for ICMP protocol, and should be ``0-65535`` for ANY + protocol + * keyword **protocol** (`str`): Protocol of the rule, could be + any of ``UDP``, ``TCP``, ``ICMP`` and ``ANY`` + * keyword **targetIp** (`str`): If a packet header matches + this rule, the packet would be translated to this new IP + address + * keyword **targetPort** (`str`): If a packet header matches + this rule, the packet would be translated to this new port + or port range + * keyword **gms_marked** (`bool`): To indicate whether this + rule was generated by GMS or by user + * keyword **comment** (`str`): Comment + * keyword **srcIf** (`str`): Source interface name + * keyword **vrf_id** (`int`): vrf segment id + :type pfw_rules: list[dict] + :return: If successful returns string of ``Successfully saved port + forwarding rules.`` + :rtype: str + """ + return self._post( + "/portForwarding2", + data=pfw_rules, + return_type="text", + ) diff --git a/pyedgeconnect/orch/_appliance_preconfig.py b/pyedgeconnect/orch/_appliance_preconfig.py index 6e9e461..7551fe0 100644 --- a/pyedgeconnect/orch/_appliance_preconfig.py +++ b/pyedgeconnect/orch/_appliance_preconfig.py @@ -10,6 +10,7 @@ def get_all_preconfig( self, data_filter: str = None, + preconfig_id: int = None, ) -> list: """Get preconfigs from Orchestrator @@ -23,12 +24,17 @@ def get_all_preconfig( - GET - /gms/appliance/preconfiguration - :param filter: Filter of returned results. Value of "names" will - return list of dictionaries of just preconfig names. Value of - "metadata" will include all associated metatdata without + :param data_filter: Filter of returned results. Value of ``names`` + will return list of dictionaries of just preconfig names. Value + of ``metadata`` will include all associated metatdata without configuration data. No filter will return all metadata and - base64 of the preconfig configuration, defaults to None - :type filter: str, optional + base64 of the preconfig configuration. Cannot be used with + preconfig_id parameter, defaults to None + :type data_filter: str, optional + :param preconfig_id: Only retrieve details of preconfig with + matching id value. Cannot be used with data_filter parameter, + defaults to None + :type preconfig_id: int, optional :return: Returns list of dictionaries of preconfigs and/or associated metadata and configuration \n [`dict`]: preconfig object \n @@ -88,10 +94,18 @@ def get_all_preconfig( for the data that was used by this task :rtype: list """ - if data_filter == "names": + if data_filter is not None and preconfig_id is not None: + raise ValueError( + "Cannot combine preconfig_id and data_filter paramters" + ) + elif data_filter == "names": return self._get("/gms/appliance/preconfiguration?filter=names") elif data_filter == "metadata": return self._get("/gms/appliance/preconfiguration?filter=metadata") + elif preconfig_id is not None: + return self._get( + f"/gms/appliance/preconfiguration?preconfigId={preconfig_id}" + ) else: return self._get("/gms/appliance/preconfiguration") @@ -234,6 +248,8 @@ def get_preconfig( ) -> dict: """Get specific preconfig from Orchestrator + *** DEPRECATED IN Orchestrator 9.3+ *** + .. list-table:: :header-rows: 1 diff --git a/pyedgeconnect/orch/_broadcast_cli.py b/pyedgeconnect/orch/_broadcast_cli.py index 0871fa2..621f3c1 100644 --- a/pyedgeconnect/orch/_broadcast_cli.py +++ b/pyedgeconnect/orch/_broadcast_cli.py @@ -33,5 +33,5 @@ def broadcast_cli( return self._post( "/broadcastCli", data={"neList": appliance_list, "cmdList": cli_commands}, - return_type="bool", + return_type="text", ) diff --git a/pyedgeconnect/orch/_flow.py b/pyedgeconnect/orch/_flow.py index 8247793..ba28dc7 100644 --- a/pyedgeconnect/orch/_flow.py +++ b/pyedgeconnect/orch/_flow.py @@ -139,7 +139,7 @@ def get_appliance_flows( # noqa: C901, silences flake8 complexity ``term``, ``last1hr``, ``term1hr``, ``last4hr``, ``term4h``, ``last24hr``, ``term24hr``. ``last`` implies started, ``term`` implies ended. Values can be combined with ``%7C``, e.g., - value of ``anytime%7Cterm24hr`` would implie started anytime or + value of ``anytime%7Cterm24hr`` would imply started anytime or ended within past 24 hours. Defaults to None :type uptime: str, optional :param active_uptime_start: Custom start time filter for active diff --git a/pyedgeconnect/orch/_template.py b/pyedgeconnect/orch/_template.py index c27bbd3..9fa731e 100644 --- a/pyedgeconnect/orch/_template.py +++ b/pyedgeconnect/orch/_template.py @@ -251,7 +251,7 @@ def select_templates_for_template_group( def get_appliance_template_history( self, ne_pk: str, - latest: bool, + latest: str, ) -> list: """Get history of applied templates to the specified appliance. Will return a HTTP 204 if no data available. @@ -268,7 +268,7 @@ def get_appliance_template_history( :param ne_pk: Network Primary Key (nePk) of appliance, e.g. ``3.NE`` :type ne_pk: str - :param latest: ``True`` for latest applied templates only, or + :param latest: String ``True`` for latest applied templates only, or ``False`` for all :type latest: str :return: Returns list of applied templates diff --git a/pyedgeconnect/orch/_vrrp.py b/pyedgeconnect/orch/_vrrp.py index 94e8cc3..ce82fa9 100644 --- a/pyedgeconnect/orch/_vrrp.py +++ b/pyedgeconnect/orch/_vrrp.py @@ -4,7 +4,7 @@ # vrrp : ECOS VRRP configuration -def get_vrrp_interfaes( +def get_vrrp_interfaces( self, ne_id: str, cached: bool, diff --git a/setup.py b/setup.py index b064cbf..e88a971 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ description="A Python wrapper for Aruba Orchestrator and Edge Connect API", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/SPOpenSource/edgeconnect-python", + url="https://github.com/aruba/pyedgeconnect", author="Zach Camara", author_email="zachary.camara@hpe.com", license="MIT", @@ -58,7 +58,7 @@ ], }, project_urls={ - "Bug Reports": "https://github.com/SPOpenSource/edgeconnect-python/issues", # noqa E501 - "Source": "https://github.com/SPOpenSource/edgeconnect-python/", + "Bug Reports": "https://github.com/aruba/pyedgeconnect/issues", # noqa E501 + "Source": "https://github.com/aruba/pyedgeconnect/", }, )