summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dcaeapplib/ChangeLog.md6
-rw-r--r--dcaeapplib/LICENSE.txt17
-rw-r--r--dcaeapplib/README.md64
-rw-r--r--dcaeapplib/dcaeapplib/__init__.py141
-rw-r--r--dcaeapplib/pom.xml241
-rw-r--r--dcaeapplib/setup.py39
-rw-r--r--dcaeapplib/tests/test_dcaeapplib.py107
-rw-r--r--dcaeapplib/tox-local.ini11
-rw-r--r--dcaeapplib/tox.ini11
-rw-r--r--pom.xml1
10 files changed, 638 insertions, 0 deletions
diff --git a/dcaeapplib/ChangeLog.md b/dcaeapplib/ChangeLog.md
new file mode 100644
index 0000000..0942537
--- /dev/null
+++ b/dcaeapplib/ChangeLog.md
@@ -0,0 +1,6 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
+and this project adheres to [Semantic Versioning](http://semver.org/).
diff --git a/dcaeapplib/LICENSE.txt b/dcaeapplib/LICENSE.txt
new file mode 100644
index 0000000..8bdab89
--- /dev/null
+++ b/dcaeapplib/LICENSE.txt
@@ -0,0 +1,17 @@
+============LICENSE_START=======================================================
+Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+================================================================================
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+============LICENSE_END=========================================================
+
+ECOMP is a trademark and service mark of AT&T Intellectual Property.
diff --git a/dcaeapplib/README.md b/dcaeapplib/README.md
new file mode 100644
index 0000000..81fd825
--- /dev/null
+++ b/dcaeapplib/README.md
@@ -0,0 +1,64 @@
+# dcaeapplib
+
+Library for building DCAE analytics applications
+
+# Example use:
+
+```
+
+class myapp:
+ def __init__(self):
+ # get the environment, and register callbacks for health checks and
+ # configuration changes
+ self.dcaeenv = dcaeapplib.DcaeEnv(healthCB=self.isHealthy, reconfigCB=self.reconfigure)
+ # simulate a configuration change - we want the initial configuration
+ self.reconfigure()
+ # start the environment (to wait for health checks and config changes)
+ self.dcaeenv.start()
+ # begin processing loop
+ while True:
+ if self.configchanged:
+ # fetch the updated configuration
+ self.conig = self.dcaeenv.getconfig()
+ self.configchanged = False
+ # do any setup relevant to the configuration
+ data = self.dcaeenv.getdata('myinputstream')
+ # Data is a UTF-8 string, 'myinputstream' is this application's logical
+ # name for this (one of potentially several) data sources.
+ # Can also specify timeout_ms and limit as arguments.
+ # timeout_ms (default 15,000) is the maximum time getdata() will block
+ # limit is the maximum number of records retrieved at a time.
+ # If no data is retrieved (timeout) the return will be None.
+ if data is not None:
+ # do something to process the data
+ self.dcaeenv.senddata('myoutputstream', 'somepartitionkey', data)
+ # data is a string, 'myoutputstream' is the application's logical
+ # name for this (one of potentially several) data sinks.
+ # somepartitionkey is used, by Kafka, to assign the data to a partition.
+
+ def isHealthy(self):
+ # return the health of the application (True/False)
+ return True
+
+ def reconfigure(self):
+ # Do whatever needs to be done to handle a configuration change
+ self.configchanged = True
+```
+
+# Environment Variables
+
+This library uses the onap-dcae-cbs-docker-client library to fetch
+configuration. That library relies on the HOSTNAME and CONSUL_HOST
+environment variables to find the configuration.
+
+This library provides an HTTP interface for health checks. By default this
+listens on port 80 but can be overridden to use another port by setting the
+DCAEPORT environment variable. The HTTP interface supports getting 2 URLs:
+/healthcheck, which will return a status of 202 (Accepted) for healthy,
+and 503 (Service Unavailable) for unhealthy, and /reconfigure, which triggers
+the library to check for updated configuration.
+
+# Console Commands
+
+This library provides a single console command "reconfigure.sh" which
+performs an HTTP get of the /reconfigure URL
diff --git a/dcaeapplib/dcaeapplib/__init__.py b/dcaeapplib/dcaeapplib/__init__.py
new file mode 100644
index 0000000..e81b6fc
--- /dev/null
+++ b/dcaeapplib/dcaeapplib/__init__.py
@@ -0,0 +1,141 @@
+# org.onap.dcae
+# ============LICENSE_START====================================================
+# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+# =============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END======================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+import base64
+import json
+import os
+import requests
+from threading import Thread
+import uuid
+from onap_dcae_cbs_docker_client.client import get_config
+
+try:
+ from http.server import BaseHTTPRequestHandler, HTTPServer
+except ImportError:
+ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+
+_httpport = int(os.environ['DCAEPORT']) if 'DCAEPORT' in os.environ else 80
+_clientid = uuid.uuid4().hex
+_groupid = uuid.uuid4().hex
+
+class _handler(BaseHTTPRequestHandler):
+ def do_GET(self):
+ if '/healthcheck' == self.path:
+ if self.server.env._health():
+ self.send_response(202)
+ self.end_headers()
+ else:
+ self.send_error(503)
+ elif '/reconfigure' == self.path:
+ self.server.env._loadconfig()
+ self.server.env._reconf()
+ self.send_response(202)
+ self.end_headers()
+ else:
+ self.send_error(404)
+
+class DcaeEnv:
+ def __init__(self, healthCB = lambda:True, reconfigCB = lambda:None):
+ """
+ Initialize environment, but don't start web server or invoke any callbacks.
+ """
+ self._health = healthCB
+ self._reconf = reconfigCB
+ self._unread = {}
+ self._server = None
+ self._loadconfig()
+
+ def start(self):
+ """
+ Start web server to receive health checks and reconfigure requests
+ """
+ if self._server is not None:
+ return
+ self._server = HTTPServer(('', _httpport), _handler)
+ self._server.env = self
+ th = Thread(target=self._server.serve_forever, name='webserver')
+ th.daemon = True
+ th.start()
+
+ def stop(self):
+ """
+ Stop web server
+ """
+ if self._server is None:
+ return
+ self._server.shutdown()
+ self._server.env = None
+ self._server = None
+
+ def _loadconfig(self):
+ self._config = get_config()
+
+ def hasdata(self, stream):
+ """
+ Return whether there is any unprocessed received data for the specified
+ data stream. That is, if an earlier getdata() call returned more than
+ one record, and the additional records have not yet been retrieved.
+ """
+ return stream in self._unread
+
+ def getdata(self, stream, timeout_ms = 15000, limit = 10):
+ """
+ Try to retrieve data from Message Router for the specified data stream.
+ If no data is retrieved, within the specified timeout, return None.
+ """
+ sinfo = self._config['streams_subscribes'][stream]
+ if stream in self._unread:
+ x = self._unread[stream]
+ ret = x.pop()
+ if len(x) == 0:
+ del self._unread[stream]
+ return ret
+ gid = sinfo['client_id'] if 'client_id' in sinfo and sinfo['client_id'] else _groupid
+ resp = requests.get('{0}/{1}/{2}?timeout={3}&limit={4}'.format(sinfo['dmaap_info']['topic_url'], gid, _clientid, timeout_ms, limit), auth=(sinfo['aaf_username'], sinfo['aaf_password']))
+ resp.raise_for_status()
+ x = resp.json()
+ if len(x) == 0:
+ return None
+ if len(x) == 1:
+ return x[0]
+ x.reverse()
+ ret = x.pop()
+ self._unread[stream] = x
+ return ret
+
+ def senddata(self, stream, partition, data):
+ """
+ Publish data to the specified stream.
+ """
+ sinfo = self._config['streams_publishes'][stream]
+ body = '{0}.{1}.{2}{3}'.format(len(partition), len(data), partition, data)
+ resp = requests.post('{0}'.format(sinfo['dmaap_info']['topic_url']), auth=(sinfo['aaf_username'], sinfo['aaf_password']), headers={'Content-Type': 'application/cambria'}, data=body)
+ resp.raise_for_status()
+
+ def getconfig(self):
+ """
+ Get the latest version of the configuration data.
+ """
+ return self._config
+
+def reconfigure():
+ """
+ Make the web request to reconfigure (locally)
+ """
+ requests.get('http://localhost:{0}/reconfigure'.format(_httpport))
diff --git a/dcaeapplib/pom.xml b/dcaeapplib/pom.xml
new file mode 100644
index 0000000..878e7c6
--- /dev/null
+++ b/dcaeapplib/pom.xml
@@ -0,0 +1,241 @@
+<?xml version="1.0"?>
+<!--
+================================================================================
+Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+================================================================================
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+============LICENSE_END=========================================================
+
+ECOMP is a trademark and service mark of AT&T Intellectual Property.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.onap.dcaegen2.utils</groupId>
+ <artifactId>utils</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ </parent>
+ <groupId>org.onap.dcaegen2.utils</groupId>
+ <artifactId>dcaeapplib</artifactId>
+ <name>dcaeapplib</name>
+ <version>0.0.3-SNAPSHOT</version>
+ <url>http://maven.apache.org</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+ <sonar.sources>.</sonar.sources>
+ <sonar.junit.reportsPath>xunit-results.xml</sonar.junit.reportsPath>
+ <sonar.python.coverage.reportPath>coverage.xml</sonar.python.coverage.reportPath>
+ <sonar.language>py</sonar.language>
+ <sonar.pluginName>Python</sonar.pluginName>
+ <sonar.inclusions>**/*.py</sonar.inclusions>
+ <sonar.exclusions>tests/*</sonar.exclusions>
+ </properties>
+ <build>
+ <finalName>${project.artifactId}-${project.version}</finalName>
+ <pluginManagement>
+ <plugins>
+ <!-- the following plugins are invoked from oparent, we do not need them -->
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <version>1.6.7</version>
+ <configuration>
+ <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <!-- This version supports the "deployAtEnd" parameter -->
+ <version>2.8</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- first disable the default Java plugins at various stages -->
+ <!-- maven-resources-plugin is called during "*resource" phases by default behavior. it prepares
+ the resources dir. we do not need it -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- maven-compiler-plugin is called during "compile" phases by default behavior. we do not need it -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- maven-jar-plugin is called during "compile" phase by default behavior. we do not need it -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <executions>
+ <execution>
+ <id>default-jar</id>
+ <phase/>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- maven-install-plugin is called during "install" phase by default behavior. it tries to copy stuff under
+ target dir to ~/.m2. we do not need it -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <!-- maven-surefire-plugin is called during "test" phase by default behavior. it triggers junit test.
+ we do not need it -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.12.4</version>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <!-- plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.4.1</version>
+ <configuration>
+ <descriptors>
+ <descriptor>assembly/dep.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin -->
+ <!-- now we configure custom action (calling a script) at various lifecycle phases -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <executions>
+ <execution>
+ <id>clean phase script</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>clean</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>generate-sources script</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>generate-sources</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>compile script</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>compile</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>package script</id>
+ <phase>package</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>package</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>test script</id>
+ <phase>test</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>test</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>install script</id>
+ <phase>install</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>install</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>deploy script</id>
+ <phase>deploy</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>${project.artifactId}</argument>
+ <argument>deploy</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/dcaeapplib/setup.py b/dcaeapplib/setup.py
new file mode 100644
index 0000000..a19fa7c
--- /dev/null
+++ b/dcaeapplib/setup.py
@@ -0,0 +1,39 @@
+# org.onap.dcae
+# ============LICENSE_START====================================================
+# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+# =============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END======================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+from setuptools import setup, find_packages
+
+setup(
+ name='dcaeapplib',
+ version='0.0.3',
+ packages=find_packages(),
+ author = 'Andrew Gauld',
+ author_email = 'ag1282@att.com',
+ description = ('Library for building DCAE analytics applications'),
+ license = 'Apache 2.0',
+ keywords = '',
+ url = '',
+ zip_safe = True,
+ install_requires=[ 'onap-dcae-cbs-docker-client>=0.0.2' ],
+ entry_points = {
+ 'console_scripts': [
+ 'reconfigure.sh=dcaeapplib:reconfigure'
+ ]
+ }
+)
diff --git a/dcaeapplib/tests/test_dcaeapplib.py b/dcaeapplib/tests/test_dcaeapplib.py
new file mode 100644
index 0000000..fabec41
--- /dev/null
+++ b/dcaeapplib/tests/test_dcaeapplib.py
@@ -0,0 +1,107 @@
+# org.onap.dcae
+# ============LICENSE_START====================================================
+# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+# =============================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END======================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+import dcaeapplib
+import requests
+import json
+
+class Stubs:
+ def __init__(self):
+ self.toreturn = [ 's1', 's2', 's3' ]
+ self.config = {
+ "anotherparameter": 1,
+ "streams_subscribes": {
+ "myinputstream": {
+ "aaf_username": "user1",
+ "aaf_password": "pass1",
+ "dmaap_info": {
+ "topic_url": "http://messagerouter.example.com:3904/events/topic1"
+ }
+ }
+ },
+ "streams_publishes": {
+ "myoutputstream": {
+ "aaf_username": "user2",
+ "aaf_password": "pass2",
+ "dmaap_info": {
+ "topic_url": "http://messagerouter.example.com:3904/events/topic2"
+ }
+ }
+ }
+ }
+ self.health = True
+ self.cc = False
+
+ def raise_for_status(self):
+ pass
+
+ def json(self):
+ return self.toreturn
+
+def test_todo(monkeypatch):
+ stuff = Stubs()
+ def stub_config():
+ return json.loads(json.dumps(stuff.config))
+ def stub_hc():
+ return stuff.health
+ def stub_cc():
+ stuff.cc = True
+ monkeypatch.setattr(dcaeapplib, 'get_config', stub_config)
+ monkeypatch.setattr(dcaeapplib, '_httpport', 0)
+ env = dcaeapplib.DcaeEnv(healthCB=stub_hc, reconfigCB=stub_cc)
+ env.stop() # exercise stop when not running
+ env.start()
+ print('Port is {0}'.format(env._server.server_port))
+ monkeypatch.setattr(dcaeapplib, '_httpport', env._server.server_port)
+ stuff.config['anotherparameter'] = 2
+ assert env.getconfig()['anotherparameter'] == 1
+ dcaeapplib.reconfigure()
+ assert stuff.cc is True
+ assert env.getconfig()['anotherparameter'] == 2
+ resp = requests.get('http://localhost:{0}/healthcheck'.format(dcaeapplib._httpport))
+ assert resp.status_code < 300
+ stuff.health = False
+ resp = requests.get('http://localhost:{0}/healthcheck'.format(dcaeapplib._httpport))
+ assert resp.status_code >= 500
+ resp = requests.get('http://localhost:{0}/invalid'.format(dcaeapplib._httpport))
+ assert resp.status_code == 404
+ env.start() # exercise start when already running
+ env.stop()
+ def stub_get(*args, **kwargs):
+ return stuff
+ def stub_post(url, data, *args, **kwargs):
+ assert data == '4.11.asdfhello world'
+ stuff.posted = True
+ return stuff
+ monkeypatch.setattr(requests, 'post', stub_post)
+ stuff.posted = False
+ env.senddata('myoutputstream', 'asdf', 'hello world')
+ assert stuff.posted == True
+ monkeypatch.setattr(requests, 'get', stub_get)
+ assert env.hasdata('myinputstream') is False
+ assert env.getdata('myinputstream') == 's1'
+ stuff.toreturn = [ 'a1' ]
+ assert env.getdata('myinputstream') == 's2'
+ assert env.hasdata('myinputstream') is True
+ assert env.getdata('myinputstream') == 's3'
+ assert env.hasdata('myinputstream') is False
+ assert env.getdata('myinputstream') == 'a1'
+ stuff.toreturn = []
+ assert env.hasdata('myinputstream') is False
+ assert env.getdata('myinputstream') is None
diff --git a/dcaeapplib/tox-local.ini b/dcaeapplib/tox-local.ini
new file mode 100644
index 0000000..935a5d6
--- /dev/null
+++ b/dcaeapplib/tox-local.ini
@@ -0,0 +1,11 @@
+[tox]
+envlist = py27
+[testenv]
+deps=
+ onap-dcae-cbs-docker-client>=0.0.2
+ pytest
+ coverage
+ pytest-cov
+setenv =
+ PYTHONPATH={toxinidir}
+commands=pytest --cov dcaeapplib --cov-report html
diff --git a/dcaeapplib/tox.ini b/dcaeapplib/tox.ini
new file mode 100644
index 0000000..9eb453c
--- /dev/null
+++ b/dcaeapplib/tox.ini
@@ -0,0 +1,11 @@
+[tox]
+envlist = py27,py35
+[testenv]
+deps=
+ onap-dcae-cbs-docker-client>=0.0.2
+ pytest
+ coverage
+ pytest-cov
+setenv =
+ PYTHONPATH={toxinidir}
+commands=pytest --junitxml xunit-results.xml --cov dcaeapplib --cov-report xml
diff --git a/pom.xml b/pom.xml
index c03e65a..6af5fc4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property.
<module>onap-dcae-dcaepolicy-lib</module>
<module>python-discovery-client</module>
<module>python-dockering</module>
+ <module>dcaeapplib</module>
</modules>
<properties>