Added documentation directory compliant with LF guidelines 15/1515/1
authorAshwin Sridharan <asridharan@research.att.com>
Tue, 12 Nov 2019 18:46:57 +0000 (13:46 -0500)
committerAshwin Sridharan <asridharan@research.att.com>
Tue, 12 Nov 2019 18:46:57 +0000 (13:46 -0500)
Removed latency computation from message processor (more appropriate for off-line testing)
Fixed issue in admission wrapper

Signed-off-by: Ashwin Sridharan <asridharan@research.att.com>
Change-Id: I7ad4893bd02b8348679ccdc35340f3896845b96c

14 files changed:
docs/_static/logo.png [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/conf.yaml [new file with mode: 0644]
docs/favicon.ico [new file with mode: 0644]
docs/index.rst [new file with mode: 0644]
docs/installation-guide.rst [new file with mode: 0755]
docs/overview.rst [new file with mode: 0644]
docs/release-notes.rst [new file with mode: 0644]
docs/user-guide.rst [new file with mode: 0755]
src/README.md
src/message_processor_class.cc
src/message_processor_class.hpp
src/protector-plugin/admission_policy.cc
tox.ini [new file with mode: 0644]

diff --git a/docs/_static/logo.png b/docs/_static/logo.png
new file mode 100644 (file)
index 0000000..c3b6ce5
Binary files /dev/null and b/docs/_static/logo.png differ
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..ef74119
--- /dev/null
@@ -0,0 +1,7 @@
+from docs_conf.conf import *
+linkcheck_ignore = [
+    'http://localhost.*',
+    'http://127.0.0.1.*',
+    'https://gerrit.o-ran-sc.org.*'
+]
+extensions = ['sphinx.ext.autosectionlabel']
diff --git a/docs/conf.yaml b/docs/conf.yaml
new file mode 100644 (file)
index 0000000..9c33fdd
--- /dev/null
@@ -0,0 +1,6 @@
+---
+project_cfg: oran
+project: admin
+
+
+
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644 (file)
index 0000000..00b0fd0
Binary files /dev/null and b/docs/favicon.ico differ
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..bc0fee0
--- /dev/null
@@ -0,0 +1,22 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2019 AT&T
+
+
+Welcome to O-RAN SC Admission Control xAPP Documentation
+========================================================
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   overview.rst
+   release-notes.rst
+   installation-guide.rst
+   user-guide.rst
+
+
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/installation-guide.rst b/docs/installation-guide.rst
new file mode 100755 (executable)
index 0000000..2502d0e
--- /dev/null
@@ -0,0 +1,139 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2019 AT&T
+
+
+Installation Guide
+==================
+
+.. contents::
+   :depth: 3
+   :local:
+
+Abstract
+--------
+
+This document describes how to install the Admission Control (AC) xAPP. 
+
+Version history
+
++--------------------+--------------------+--------------------+--------------------+
+| **Date**           | **Ver.**           | **Author**         | **Comment**        |
+|                    |                    |                    |                    |
++--------------------+--------------------+--------------------+--------------------+
+| 2019-11-14         |1.0.8               |Ashwin Sridharan    | Amber Release      |
+|                    |                    |                    |                    |
++--------------------+--------------------+--------------------+--------------------+
+
+
+Introduction
+------------
+
+This document provides guidelines on how to install and configure the AC xAPP in various environments/operating modes.
+The audience of this document is assumed to have good knowledge in RAN network nd Linux system.
+
+
+Preface
+-------
+The AC xAPP can be run directly as a Linux binary, as a docker image, or in a pod in a Kubernetes environment.  The first
+two can be used for testing/evaluation. The last option is how an xAPP is deployed in the RAN Intelligent Controller environment.
+This document covers all three methods.  
+
+
+
+
+Software Installation and Deployment
+------------------------------------
+The build process assumes a Linux environment with a gcc (>= 4.0)  compatible compiler and  has been tested on Ubuntu and Fedora. For building docker images,
+the Docker environment must be present in the system.
+
+
+Build Process
+~~~~~~~~~~~~~
+The AC xAPP can be either tested as a Linux binary or as a docker image.
+   1. **Docker Image**:    From the root of the repository, run   *docker --no-cache build -t <image-name> ./* .
+      **IMPORTANT** : docker user must have credentials to access to the LF O-RAN docker repository : *nexus3.o-ran-sc.org:10004*,  which can be done with docker login **prior** to running the build
+
+   2. **Linux binary**: 
+      The AC xAPP may be compiled and invoked directly. Pre-requisite software packages that must be installed prior to compiling are documented in the Dockerfile (and also in README under src/) in the repository. From within the *src* directory,  run *make adm-ctrl-xapp*.   
+
+
+Deployment
+~~~~~~~~~~
+For simple unit tests, integration tests etc., the Linux binary or docker image may be used directly. When tested E2E in a RIC, the AC xAPP must be deployed as a K8 pod using the xAPP manager (or helm for simple tests). In all
+scenarios, an important pre-requisite for deployment is the availability of routes (see RMR documentation) to the xAPP indicating where to send E2AP subscription requests and control messages. In production, this set of routes
+will be provided by the *Route Manager*. For local testing, static routes can be provided to the RMR library by setting the environment variable *RMR_SEED_RT* to point to the complete path of a  file with routes.  An example RMR route file is provided under *test/uta_rtg.rt*.
+
+1. **Invoking the AC xAPP directly as a Linux binary** :
+   
+   - Set the environment variable *RMR_SEED_RT* to point to the complete path of a file with static routes.
+     
+   - Use the provided  sample script src/run_xapp.sh to invoke the AC xAPP. The script lists the  various options which may be changed depending on environment.
+     
+
+2. **Invoking  xAPP docker container directly** (not in RIC Kubernetes env.):
+
+   - The xAPP docker run time **must** be configured with a json configuration file appropriate to the test environment which injects various environment variables including the RMR routes   (an example is provided under init/config-file.json).
+
+   - Once such a  file is available (say under directory /home/user/test-config),  the docker image can be invoked as *docker run --net host -it --rm -v "/home/user/test-config:/opt/ric/config" --name  "AC-xAPP" <image>*.  See REAMDE.md under the init directory for more details.
+
+
+3. **Invoking docker xAPP container in RIC Kubernetes environment** :
+   In an actual deployment, xAPPs are deployed as K8 pods via the
+   xapp-manager in the RIC with their configuration mounted as
+   *configmaps*. In order to be deployable by the xapp-manager, a helm
+   chart of the xAPP must be created and uploaded to the helm
+   repository. Generation of helm chart falls under the purview of the
+   RIC integration team rather than the xAPP owner since the helm
+   chart will contain several environment specific
+   parameters. However, the xAPP owner may provide any xAPP specific
+   configuration via a JSON configuration file and associated schema.
+   A sample configuration json file (which **MUST** be named
+   config-file.json) and schema are provided under init/. Parameters
+   of the JSON are documented in README under the init/ directory.
+
+   As an alternative to the xapp-manager, in a test K8 environment,
+   the AC xAPP may also be directly installed via Helm (thought it may
+   not be controllable via the RIC dashboard).
+
+Testing 
+--------
+
+Unit tests for various modules of the AC xAPP are under the *test/* repository. Currently, the unit tests must be compiled and executed  in a Linux environment. All software packages  required for compiling the AC xAPP must be installed (as listed in the Dockerfile). In addition   a pre-requisite for the unit tests is installation of the *Catch2 C++* header file  since the unit tests use this framework.  This can be easily done by running
+
+*wget -nv  --directory-prefix=/usr/local/include/Catch2 https://github.com/catchorg/Catch2/releases/download/v2.9.1/catch.hpp*.
+
+After that, the unit tests can be compiled and run by executing the following commands from the *test/* directory :
+
+- *make all_tests*
+- ./run_tests.sh
+- If gcovr is installed (https://github.com/gcovr/gcovr) the script  will  also generates a coverage report (as ../coverage_report.html)
+
+In order to run integration tests, the AC-xAPP requires *three* components : an *E2 Termination point* to send and receive RAN messages, an *A1 mediator* to send policy updatesd and a *VES collector* to receive metrics. The *test/*
+directory contains mock-ups for these three components which can be build and executed from the *test/* directory as follows  :
+
+1.  **E2 Termination** :  The E2 termination is responsible for forwarding  messages to and fro between the RAN and RIC. A mock-up of the E2 termination is provided in *test/* that
+
+    - listens and responds to E2AP subscribption requests.
+    - upon receiving an E2AP subscription request, starts sending E2AP Indication messages that contain the X2AP SgNB Addition Request Message.
+    - monitors E2AP control messages from the AC xAPP.
+      
+    The E2 term executable can be build and executed as follows :
+
+    - *make mock-e2term-server* compiles the executable
+    - To invoke it first ensure the *RMR_SEED_RT* environment variable is set to point to complete path of a route file. Then run * ./mock-e2term-server -p <E2 term port number> -r <rate to send E2AP indication messages>
+    - *NOTE* : The E2 term port number must be set to the port number listed in the route table that can receive E2AP subscription requests, E2AP indications. Default port that is used is 38000.
+
+2.  **A1 Mediator** : The A1 mediator is responsible for sending policies to the xAPPs over RMR to configure their behaviour. A mock-up of the A1 mediator can be built and executed as follows :
+    
+    - *make mock-a1-server* builds the executable.
+    - The executable can be run as *./mock-a1-server -p <port number>*  where port number can be any port not conflicting with the xAPP and E2 Term.
+    - Note that the A1 mediator also uses RMR and hence the environment variable *RMR_SEED_RT* must also be set when executing *mock-a1-server* (if static routes are being used).
+    - On start up, the *mock-a1-mediator* will send a stream of valid/invalid JSON messages containing policies to test the xAPP.
+        
+3.  **VES Collector** : This component is responsible for receiving metrics from xAPPs as JSON payloads. A simple mock-up is available under *test/* which is basically a *cherrypy* web-server that receives JSON and prints out relevant messages. It can be invoked as *python ./mock_ves_collector.py*.
+
+    - Pre-requisites for the VES collector are the *cherrypy* and *requests* Python modules. They can be installed via pip :  *pip install cherrypy requests*.
+      
+
+
diff --git a/docs/overview.rst b/docs/overview.rst
new file mode 100644 (file)
index 0000000..d0a6ec8
--- /dev/null
@@ -0,0 +1,24 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2019 AT&T
+
+
+
+
+Admission Control xAPP  Overview
+================================
+
+The Admission Control (AC) xAPP repository contains open-source code for a prototype xAPP that 
+can execute the full control loop  to regulate the number of 5G connection requests at a gNodeB.
+
+The AC  xAPP subscribes and listens for SgNB Addition Request X2AP messages. Upon
+receiving one, it makes a decision on whether to accept or reject the request based on a simple
+sliding window. If the decision is to accept the request, it sends an SgNB Addition Acknowledge, else
+it sends an SgNB Addition Reject message.
+
+The AC xAPP repository contains code to handle the E2 subscription process, encode/decode E2AP Indication/Control messages, X2AP SgNB Addition Request/Response
+and supports the A1-interface which can be used to send policies to configure the admission control behaviour.
+
+
+
+
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
new file mode 100644 (file)
index 0000000..ebd3bba
--- /dev/null
@@ -0,0 +1,83 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2019 AT&T
+
+
+Release Notes
+=============
+
+
+This document provides the release notes for the Amber Release of the Admission Control xAPP.
+
+.. contents::
+   :depth: 3
+   :local:
+
+
+Version history
+---------------
+
++--------------------+--------------------+--------------------+--------------------+
+| **Date**           | **Ver.**           | **Author**         | **Comment**        |
+|                    |                    |                    |                    |
++--------------------+--------------------+--------------------+--------------------+
+| 2019-11-04         | 1.0.0              |   Ashwin Sridharan | First draft        |
+|                    |                    |                    |                    |
++--------------------+--------------------+--------------------+--------------------+
+
+
+
+Summary
+-------
+
+The Amber release of the AC xAPP supports  full closed loop control as well as report mode operation
+for admission control of SgNB Addition requests, reporting of metrics over VES,
+and configuration of single instance policies via the A1-Interface.
+
+
+
+Release Data
+------------
+
++--------------------------------------+--------------------------------------+
+| **Project**                          | RAN Intelligent Controller           |
+|                                      |                                      |
++--------------------------------------+--------------------------------------+
+| **Repo/commit-ID**                   |        ric-app/admin                 |
+|                                      |                                      |
++--------------------------------------+--------------------------------------+
+| **Release designation**              |              Amber                   |
+|                                      |                                      |
++--------------------------------------+--------------------------------------+
+| **Release date**                     |      2019-11-14                      |
+|                                      |                                      |
++--------------------------------------+--------------------------------------+
+| **Purpose of the delivery**          | open-source xAPP for admission       |
+|                                      | control.                             |
+|                                      |                                      |
++--------------------------------------+--------------------------------------+
+
+Components
+----------
+
+- *src/* contains the main source code. Under that directory :
+  
+  + *xapp_utils.hpp, xapp_utls.cc* is generic multi-threaded framework for receiving and sending RMR events.
+  + *E2AP-c/subscription/* contains generic classes to send/process ASN1 subscription requests, responses, deletes and failures as well as thread-safe subscription handler for managing the subscription process.
+  + *E2AP-c/* contains generic classes for generating/processing ASN1  E2AP Indication and Control messages.
+  + *E2SM/* contains generic classes for handling generating/processing ASN1 E2SM service model (e.g event trigger etc).
+  + *curl/* contains a simple *libcurl* based class for POSTing JSON messages.
+  + *json/* contains a generic class for simple JSON key retreival and modification (based on rapidjson)
+  + *protector-plugin/* contains code specific to the admission control algorithm and interfaces for setting/getting policy.
+
+- *test/* contains unit tests showing how to use various components as well as mock-ups for integration testing.
+
+- *schemas/* contains the JSON schemas for A1 policy, VES metrics as well as sample payloads.
+  
+    
+
+Limitations
+-----------
+- While the xAPP framework used in the AC xAPP supports multi-threading, the admission plugin currently only supports a single thread.
+- The admission plugin supports only a single class of service in current release.
+- The subscription request parameters (RAN Function ID etc) cannot be changed.
diff --git a/docs/user-guide.rst b/docs/user-guide.rst
new file mode 100755 (executable)
index 0000000..2996a8a
--- /dev/null
@@ -0,0 +1,90 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+.. Copyright (C) 2019 AT&T
+
+
+User Guide
+==========
+
+This is the user guide of AC xAPP, describing its various features and how to configure them .
+
+.. contents::
+   :depth: 3
+   :local:
+
+..  a user guide should be how to use the component or system; it should not be a requirements document
+..  delete this content after edittng it
+
+
+Description
+-----------
+.. Describe the target users of the projcet, for example, modeler/data scientist, ORAN-OSC platform admin, marketplace user, design studio end user, etc
+.. Descirbe how the target users can get use of a O-RAN SC component.
+.. If the guide contains sections on third-party tools, is it clearly stated why the O-RAN-OSC platform is using those tools? Are there instructions on how to install and configure each tool/toolset?
+
+The AC xAPP provides rate control of SgNB Addition Requests via a standard sliding window control algorithm which is configurable at run-time. Please see :ref:`Installation Guide` for instructions on compiling and executing the AC xAPP. This document explains the various configurable parameters of the AC xAPP executable, policies and metrics.
+
+Upon start up, the AC xAPP will spin up a thread on which to listen for RMR events, another thread to post metrics and then start sending subscription requests for the SgNB Addition Request on the main thread. When an E2AP indication
+message with the X2AP SgNB Addition Request message is received, the AC xAPP will run the sliding window algorithm, decide a response (see below for specifics) and return the response. The next section shows how to configure
+various facets of the AC xAPP that cover subscription, policy for configuration, metric reporting and behaviour of the core algorithm itself.
+
+
+Run Time options
+----------------
+The AC xAPP takes the following parameters (either on the command line) or as environment variables on invocation (see *src/run_xapp.sh* for an example of providing arguments on command line and *init/config-file.json* for environment variables)  :
+
+1. List of comma separated gNodeBs to send subscription requests to.
+   - Use *-g or --gNodeB* on command line.
+   - Set the "GNODEB" environment variable
+
+2. The A1 policy schema file which specifies the schema and parameters of acceptable policy. The AC xAPP sliding window algorithm can be configured with the following 5 parameters via the policy (see *schemas/sample.json* for an example).  The AC xAPP policy schema file is outlined in *schemas/adm-ctrl-xapp-policy-schema.json*.   The policy parameters are :
+   
+   - "enforce": A boolean flag on whether to enforce policy or not.
+   - "window_length" : Length of the sliding window in minutes.
+   - "trigger_threshold: Threshold of events in sliding window above which to start blocking.
+   - "blocking_rate": A percentage between [0-100] of connections to block if above *trigger_threshold*.
+
+     For example, a window length of 1, trigger threshold of 5000 and blocking rate of 50 would mean that if in a window of 1 minute (60 seconds), there are more than 5000 events, then start blocking half of the incoming connections.
+     Note that the window is always updated *prior* to blocking. In the above example, if arrival rate exceeds 84 events/sec ) over a period exceeding 1 minute ((84 * 60 = 5040 > 5000), then blocking will be triggered.
+     
+     The A1 policy file location can be specified to the  AC xAPP with 
+     -  *-a* on command line.
+     -  "A1_SCHEMA_FILE" environment variable.
+
+
+3. The VES schema to which all reported metrics must comply. The VES schema file can be specified with
+
+   - *-v* on command line.
+   - "VES_SCHEMA_FILE" environment variable.
+
+4. Set of sample JSON payloads for policy and metrics that the AC xAPP uses as templates to generate payloads. Values in the template payload are modified/retreived rather than construct the entire payload from scratch. The JSON file
+   containing the payloads can be specified with :
+   - *-s* on command line.
+   - "SAMPLE_FILE" environment variable.
+
+    
+5. URL of the VES collector to which to send metrics. Can be specified with :
+
+   - *-u* on command line.
+   - "VES_COLLECTOR_URL" environment variable.
+
+6. Reporting interval for metrics in seconds (i.e., how often to post metrics to VES). Can be specified with :
+
+   - *-i* on command line.
+   - "VES_MEASUREMENT_INTERVAL" environment variable.
+
+7. Log level. Can be specified with :
+
+   - *--verbose** flag on command line which will set the log level to most verbose (DEBUG).
+   - "LOG_LEVEL" environment variable. This allows finer grained control. Options are MDCLOG_ERR, MDCLOG_INFO, MDCLOG_DEBUG.
+
+8. The operating mode of the AC xAPP.  The AC xAPP can be run in three modes which can be specified with :
+
+   - *-c* option on command line.
+   - "OPERATING_MODE" environment variable.
+
+   The three operating modes supported are :
+
+   - "E2AP_PROC_ONLY" . In this mode, the AC xAPP simply decodes and records the E2AP message.
+   - "REPORT" . In this mode, the AC xAPP decodes the E2AP and the underlying X2AP message. If the message is an SgNB Addition Request Message, it executes the admission control algorithm and records the result. It does **not** however send a response back to RAN. This is useful when testing passively without interfering with RAN operation.
+   - "CONTROL". In this mode, the AC xAPP executes the full control loop where it also sends back an SgNB Addition Response or Reject message.
index 76429e9..3630d6f 100644 (file)
@@ -162,4 +162,4 @@ Other Components
 
        - get_config.cc  : processing command line and environment variables at start up.
 
-       - message_processor_class.cc  : The RMR message processing engine. It listens for messages on RMR and invokes appropriate handler (e.g subscriptions, E2AP, Policy).  It also periodically posts processing latency metrics to stdout 
+       - message_processor_class.cc  : The RMR message processing engine. It listens for messages on RMR and invokes appropriate handler (e.g subscriptions, E2AP, Policy). 
index 1601daa..a5a9685 100644 (file)
@@ -30,11 +30,6 @@ message_processor::message_processor(int mode, bool report_mode, size_t buffer_l
   scratch_buffer = 0;
   scratch_buffer = (unsigned char *)calloc(_buffer_size, sizeof(unsigned char));
   assert(scratch_buffer != 0);
-  _reporting_interval = reporting_interval;
-  num_proc_packets = 0;
-  processing_duration = 0;
-  processing_dev = 0;
-  max_proc_duration = 0;
 
 };
 
@@ -80,8 +75,6 @@ bool message_processor::operator()(rmr_mbuf_t *message){
     return false;
   }
   
-  // start measurement 
-  auto start = std::chrono::high_resolution_clock::now();
   
   // main message processing code
   switch(message->mtype){
@@ -303,35 +296,6 @@ bool message_processor::operator()(rmr_mbuf_t *message){
 
   };
 
-  auto end = std::chrono::high_resolution_clock::now();
-  // Ad hoc metric reporting for now ...
-  // every reporting interval, prints to stdou
-  // 1, reporting interval (# of packets)
-  // 2. average  processing time (in micro seconds) across each packet
-  // 3. standard deviation (micro seconds ^2) a
-  // 4. maximum processing time (in micro seconds)
-  
-  if(num_proc_packets == _reporting_interval){
-    auto epoch = std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now()).time_since_epoch();
-    double avg_latency =  processing_duration/num_proc_packets;
-    double std_dev_latency = processing_dev/num_proc_packets - avg_latency * avg_latency;
-    std::cout << "Processing Metrics : " << epoch.count() << "," << num_proc_packets << "," << avg_latency << "," << std_dev_latency << "," << max_proc_duration << std::endl;  
-    processing_duration = 0;
-    processing_dev = 0;
-    max_proc_duration = 0;
-    num_proc_packets = 0;
-  }
-
-  double  elapsed = std::chrono::duration<double, std::micro>(end - start).count();
-  if(elapsed > max_proc_duration){
-    max_proc_duration = elapsed;
-  }
-  
-  processing_duration += elapsed;
-  processing_dev += elapsed * elapsed;
-  num_proc_packets ++;
-
-  
   if(send_msg){
     return true;
   }
index f289711..247c594 100644 (file)
@@ -111,16 +111,6 @@ private:
   bool report_mode_only; // suppress e2ap control
   size_t _buffer_size; // for storing encoding
 
-  // these two parameters are used to report average processing latency.
-  // processing latency is accumalated over every num_proc_packets
-  // and both values are reported out on std log. After each report
-  // counters are reset
-
-  size_t _reporting_interval; // number of packets in a measurement interval
-  size_t num_proc_packets;
-  double processing_duration; // for storing time to process
-  double processing_dev; // for storing standard deviation
-  double max_proc_duration ;
 };
 
 
index 783d75e..6278197 100644 (file)
@@ -129,7 +129,7 @@ admission::admission (std::string policy_schema_file, std::string samples_file,
 };
 
 void admission::instantiate_protector_plugin(bool mode){
-  _plugin_instances.emplace_back(bool(current_config["enforce"]), current_config["window_length"], current_config["blocking_rate"], current_config["trigger_threshold"], mode);
+  _plugin_instances.emplace_back(bool(current_config["enforce"]), current_config["window_length"], current_config["trigger_threshold"], current_config["blocking_rate"], mode);
 }
 
 admission::~admission(void){
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..db50ddf
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,32 @@
+[tox]
+minversion = 2.0
+envlist =
+    docs,
+    docs-linkcheck,
+
+skipsdist = true
+
+[testenv:docs]
+basepython = python3
+deps = 
+    sphinx
+    sphinx-rtd-theme
+    sphinxcontrib-httpdomain
+    recommonmark
+    lfdocs-conf
+    
+commands =
+    sphinx-build -W -b html -n -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/html
+
+    echo "Generated docs available in {toxinidir}/docs/_build/html"
+
+whitelist_externals = echo
+
+[testenv:docs-linkcheck]
+basepython = python3
+deps = sphinx
+       sphinx-rtd-theme
+       sphinxcontrib-httpdomain
+       recommonmark
+       lfdocs-conf
+commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck