Merge ~xavpaice/charm-openstack-service-checks/+git/charm-openstack-service-checks:ssl-nagios-checks into ~canonical-bootstack/charm-openstack-service-checks:master

Proposed by Xav Paice
Status: Merged
Merge reported by: David O Neill
Merged at revision: f6a6d6028e8675a05750720215ec58dbbb4757ab
Proposed branch: ~xavpaice/charm-openstack-service-checks/+git/charm-openstack-service-checks:ssl-nagios-checks
Merge into: ~canonical-bootstack/charm-openstack-service-checks:master
Diff against target: 586 lines (+282/-91)
10 files modified
.gitignore (+9/-0)
Makefile (+7/-5)
README.md (+9/-5)
config.yaml (+25/-0)
files/plugins/check_nova_services.py (+7/-7)
layer.yaml (+11/-1)
reactive/service_checks.py (+158/-64)
templates/nagios.novarc (+6/-6)
test-requirements.txt (+9/-0)
tox.ini (+41/-3)
Reviewer Review Type Date Requested Status
Joel Sing (community) +1 Approve
Andrea Ieri (community) Needs Fixing
Alvaro Uria (community) Needs Fixing
Review via email: mp+361325@code.launchpad.net

This proposal has been superseded by a proposal from 2019-02-25.

Commit message

Add NRPE checks for all API endpoints in the Keystone catalog

This change implements the first stage of spec https://code.launchpad.net/~xavpaice/bootstack-charm-specs/+git/bootstack-charm-specs/+ref/ssl-nagios-checks

The intent is to add an NRPE check to the openstack-services-checks unit, checking each endpoint found in the Keystone catalog using a healthcheck URL if known, otherwise the root URL found in the catalog. If the service is using TLS, then the expiry date of the certificate is also checked.

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Alvaro Uria (aluria) wrote :

Please find comments inline. Other than that, I tested it in the charmlab and it looks good to me.

review: Needs Fixing
Revision history for this message
Xav Paice (xavpaice) wrote :

Thanks for the review - couple of responses inline. Will update the actual change shortly.

7bac062... by Xav Paice

Address review points for additional service checks

90ee556... by Xav Paice

Add PEP8 test and fix failures

Revision history for this message
Joel Sing (jsing) wrote :

Please add an actual commit message to this merge proposal - what is this change doing and why?

Readability/standards review comments inline (mostly consistency related).

Revision history for this message
Andrea Ieri (aieri) wrote :

A bunch of inline comments :)

review: Needs Fixing
1d372dd... by Xav Paice

Address review comments

Revision history for this message
Xav Paice (xavpaice) wrote :

Thanks for the detailed review(s). Fresh commit coming.

Couple of inline responses.

Revision history for this message
Joel Sing (jsing) :
Revision history for this message
Joel Sing (jsing) wrote :

LGTM for a readability/standards review - couple of minor comments inline.

Please ensure you get a peer approval before merging.

review: Approve (+1)
f6a6d60... by Xav Paice

address review nits

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000..5835925
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1,9 @@
7+*~
8+/bin
9+debian/files
10+/pkg
11+*.pyc
12+__pycache__
13+*.swp
14+.tox
15+.venv
16diff --git a/Makefile b/Makefile
17index a1ad3a5..27eeff6 100644
18--- a/Makefile
19+++ b/Makefile
20@@ -1,6 +1,6 @@
21 #!/usr/bin/make
22
23-all: lint unit_test
24+all: lint test
25
26
27 .PHONY: clean
28@@ -10,15 +10,17 @@ clean:
29 .PHONY: apt_prereqs
30 apt_prereqs:
31 @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
32- @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
33+ @which tox >/dev/null || (sudo apt-get install -y python3-pip && sudo pip3 install tox)
34
35 .PHONY: lint
36 lint: apt_prereqs
37- @tox --notest
38- @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
39+ @tox -e pep8
40 @charm proof
41
42-.PHONY: unit_test
43+.PHONY: test
44 unit_test: apt_prereqs
45 @echo Starting tests...
46 tox
47+
48+build:
49+ charm build
50diff --git a/README.md b/README.md
51index 24a4b54..b3159af 100644
52--- a/README.md
53+++ b/README.md
54@@ -2,7 +2,7 @@
55
56 This charm provides OpenStack services checks for Nagios
57
58- # Build
59+# Build
60 The fully built charm needs the following source branch
61 * https://git.launchpad.net/~canonical-bootstack/bootstack-ops/+git/charm-openstack-services-checks
62
63@@ -29,16 +29,20 @@ Build the charm, and symlink for juju-1 compatibility
64
65 juju deploy local:xenial/openstack-services-checks
66
67-This charm supports relating to keystone, but keystone-credentials interface
68-seems to be flaky, and hard to remove-relation, so the charm works around this
69-by adding 'os-credentials' setting (see setting description hints)
70+This charm supports relating to keystone via the keystone-credentials
71+interface. If you do not wish to use this, you can supply your own credential
72+set for Openstack by adding 'os-credentials' setting (see setting description
73+hints)
74
75 juju set openstack-services-checks os-credentials=" ... "
76-
77 juju add-relation openstack-services-checks nagios
78
79 With Keystone
80
81 juju add-relation openstack-services-checks:identity-credentials keystone:identity-credentials
82
83+If your OpenStack API endpoints have a common URL for the Admin, Public and
84+Internal addresses, you should consider disabling some endpoints which would be
85+duplicated otherwise, e.g.
86
87+ juju config openstack-service-checks check_internal_urls=False check_admin_urls=False
88diff --git a/config.yaml b/config.yaml
89index e4e8783..b71208c 100644
90--- a/config.yaml
91+++ b/config.yaml
92@@ -48,3 +48,28 @@ options:
93 default: false
94 description: |
95 An option to specify whether you want Warning alerts in nagios for disabled nova-compute hosts.
96+ tls_warn_days:
97+ type: int
98+ default: 30
99+ description: |
100+ Number of days left for the TLS certificate to expire before warning.
101+ tls_crit_days:
102+ type: int
103+ default: 14
104+ description: |
105+ Number of days left for the TLS certificate to expire before alerting Critical.
106+ check_public_urls:
107+ type: boolean
108+ default: True
109+ description: |
110+ If true, create NRPE checks matching all 'public' URLs in the Keystone catalog.
111+ check_internal_urls:
112+ type: boolean
113+ default: True
114+ description: |
115+ If true, create NRPE checks matching all 'internal' URLs in the Keystone catalog.
116+ check_admin_urls:
117+ type: boolean
118+ default: True
119+ description: |
120+ If true, create NRPE checks matching all 'admin' URLs in the Keystone catalog.
121diff --git a/files/plugins/check_nova_services.py b/files/plugins/check_nova_services.py
122index 1279c6a..6498542 100755
123--- a/files/plugins/check_nova_services.py
124+++ b/files/plugins/check_nova_services.py
125@@ -43,11 +43,11 @@ def check_hosts_up(args, aggregate, hosts, services_compute):
126 local_msg.append("Host Aggregate {} has {} hosts alive".format(
127 aggregate, counts['ok']))
128 nova_status = {
129- 'agg_name': aggregate,
130- 'msg_text': ", ".join(local_msg),
131- 'critical': status_crit,
132- 'warning': status_warn,
133- }
134+ 'agg_name': aggregate,
135+ 'msg_text': ", ".join(local_msg),
136+ 'critical': status_crit,
137+ 'warning': status_warn,
138+ }
139 return nova_status
140
141
142@@ -103,8 +103,8 @@ if __name__ == '__main__':
143 command = ['/bin/bash', '-c', "source {} && env".format(args.env)]
144 proc = subprocess.Popen(command, stdout=subprocess.PIPE)
145 for line in proc.stdout:
146- (key, _, value) = line.partition("=")
147- os.environ[key] = value.rstrip()
148+ (key, _, value) = line.partition(b'=')
149+ os.environ[key.decode('utf-8')] = value.rstrip().decode('utf-8')
150 proc.communicate()
151 nova = os_client_config.session_client('compute', cloud='envvars')
152 nagios_plugin.try_check(check_nova_services, args, nova)
153diff --git a/layer.yaml b/layer.yaml
154index f364d90..72f0af5 100644
155--- a/layer.yaml
156+++ b/layer.yaml
157@@ -1,6 +1,16 @@
158 includes:
159+ - 'layer:apt'
160 - 'layer:basic'
161- - 'interface:nrpe-external-master'
162 - 'interface:keystone-credentials'
163+ - 'interface:nrpe-external-master'
164 ignore: ['.*.swp' ]
165 repo: 'lp:~canonical-bootstack/+git/charm-openstack-service-checks'
166+options:
167+ basic:
168+ use_venv: true
169+ apt:
170+ packages:
171+ - nagios-nrpe-server
172+ - python3-keystoneclient
173+ - python3-openstackclient
174+ - python-openstackclient
175diff --git a/reactive/service_checks.py b/reactive/service_checks.py
176index 8341324..982b1a5 100644
177--- a/reactive/service_checks.py
178+++ b/reactive/service_checks.py
179@@ -17,112 +17,109 @@ from charmhelpers.core import (
180 unitdata,
181 )
182
183-from charmhelpers.fetch import (
184- apt_install,
185- apt_update,
186-)
187-
188 from charmhelpers.contrib.charmsupport.nrpe import NRPE
189+from urllib.parse import urlparse
190
191 config = hookenv.config()
192-install_packages = ['nagios-nrpe-server', 'python-openstackclient']
193+NOVARC = '/var/lib/nagios/nagios.novarc'
194+PLUGINS_DIR = '/usr/local/lib/nagios/plugins/'
195
196
197 @when_not('os-service-checks.installed')
198 def install_service_checks():
199 hookenv.status_set('maintenance', 'Installing software')
200- apt_update()
201- apt_install(install_packages)
202+ # moved to apt layer
203 set_state('os-service-checks.installed')
204 remove_state('os-service-checks.configured')
205 hookenv.status_set('active', 'Ready')
206-# setup openstack user
207
208
209 @when('identity-credentials.connected')
210-def configure_keystone_username(keystone):
211+def configure_ident_username(keystone):
212 username = 'nagios'
213 keystone.request_credentials(username)
214
215
216 @when('identity-credentials.available')
217 def save_creds(keystone):
218- creds = get_creds(keystone)
219- unitdata.kv().set('keystone-relation-creds', creds)
220- remove_state('os-service-checks.configured')
221-
222-
223-def get_creds(keystone):
224-
225+ """
226+ Get credentials from the Keystone relation,
227+ reformat them into something the Keystone client
228+ can use, and save them into the unitdata.
229+ """
230+ creds = {
231+ 'username': keystone.credentials_username(),
232+ 'password': keystone.credentials_password(),
233+ 'region': keystone.region(),
234+ }
235 if keystone.api_version() == '3':
236 api_url = "v3"
237 try:
238 domain = keystone.domain()
239 except AttributeError:
240 domain = 'service_domain'
241- creds = {
242- 'credentials_username': keystone.credentials_username(),
243- 'credentials_password': keystone.credentials_password(),
244- 'credentials_project': keystone.credentials_project(),
245+ # keystone relation sends info back with funny names, fix here
246+ creds.update({
247+ 'project_name': keystone.credentials_project(),
248 'auth_version': '3',
249- 'region': keystone.region(),
250- 'credentials_user_domain': domain,
251- 'credentials_project_domain': domain
252- }
253+ 'user_domain_name': domain,
254+ 'project_domain_name': domain
255+ })
256 else:
257 api_url = "v2.0"
258- creds = {
259- 'credentials_username': keystone.credentials_username(),
260- 'credentials_password': keystone.credentials_password(),
261- 'credentials_project': keystone.credentials_project(),
262- 'region': keystone.region(),
263- }
264+ creds['tenant_name'] = keystone.credentials_project()
265
266 auth_url = "%s://%s:%s/%s" % (keystone.auth_protocol(),
267 keystone.auth_host(), keystone.auth_port(),
268 api_url)
269 creds['auth_url'] = auth_url
270- return creds
271+ unitdata.kv().set('keystonecreds', creds)
272+ remove_state('os-service-checks.configured')
273
274
275-# allow user to override credentials (and the need to be related to keystone)
276+# allow user to override credentials (and the need to be related to ident)
277 # with 'os-credentials'
278 def get_credentials():
279- keystone_creds = config_flags_parser(config.get('os-credentials'))
280- if keystone_creds:
281- if '/v3' in keystone_creds['auth_url']:
282- creds = {
283- 'credentials_username': keystone_creds['username'],
284- 'credentials_password': keystone_creds['password'],
285- 'credentials_project': keystone_creds['credentials_project'],
286- 'region': keystone_creds['region_name'],
287- 'auth_url': keystone_creds['auth_url'],
288+ ident_creds = config_flags_parser(config.get('os-credentials'))
289+ if ident_creds:
290+ creds = {
291+ 'username': ident_creds['username'],
292+ 'password': ident_creds['password'],
293+ 'region': ident_creds['region_name'],
294+ 'auth_url': ident_creds['auth_url'],
295+ }
296+ if '/v3' in ident_creds['auth_url']:
297+ creds.update({
298+ 'project_name': ident_creds['credentials_project'],
299 'auth_version': '3',
300- 'credentials_user_domain': keystone_creds['domain'],
301- 'credentials_project_domain': keystone_creds['domain'],
302- }
303+ 'user_domain_name': ident_creds['domain'],
304+ 'project_domain_name': ident_creds['domain'],
305+ })
306 else:
307- creds = {
308- 'credentials_username': keystone_creds['username'],
309- 'credentials_password': keystone_creds['password'],
310- 'credentials_project': keystone_creds['credentials_project'],
311- 'region': keystone_creds['region_name'],
312- 'auth_url': keystone_creds['auth_url'],
313- }
314+ creds['tenant_name'] = ident_creds['credentials_project']
315 else:
316 kv = unitdata.kv()
317- creds = kv.get('keystone-relation-creds')
318+ creds = kv.get('keystonecreds')
319+ old_creds = kv.get('keystone-relation-creds')
320+ if old_creds and not creds:
321+ # This set of creds needs an update to a newer format
322+ creds['username'] = old_creds.pop('credentials_username')
323+ creds['password'] = old_creds.pop('credentials_password')
324+ creds['project_name'] = old_creds.pop('credentials_project')
325+ creds['tenant_name'] = old_creds['project_name']
326+ creds['user_domain_name'] = old_creds.pop('credentials_user_domain', None)
327+ creds['project_domain_name'] = old_creds.pop('credentials_project_domain', None)
328+ kv.set('keystonecreds', creds)
329+ kv.update(creds, 'keystonecreds')
330 return creds
331
332
333 def render_checks():
334 nrpe = NRPE()
335- plugins_dir = '/usr/local/lib/nagios/plugins/'
336- if not os.path.exists(plugins_dir):
337- os.makedirs(plugins_dir)
338+ if not os.path.exists(PLUGINS_DIR):
339+ os.makedirs(PLUGINS_DIR)
340 charm_file_dir = os.path.join(hookenv.charm_dir(), 'files')
341 charm_plugin_dir = os.path.join(charm_file_dir, 'plugins')
342-
343 host.rsync(
344 charm_plugin_dir,
345 '/usr/local/lib/nagios/',
346@@ -133,9 +130,8 @@ def render_checks():
347 crit = config.get("nova_crit")
348 skip_disabled = config.get("skip-disabled")
349 check_dns = config.get("check-dns")
350-
351- check_command = plugins_dir + 'check_nova_services.py --warn ' \
352- + str(warn) + ' --crit ' + str(crit)
353+ nova_check_command = os.path.join(PLUGINS_DIR, 'check_nova_services.py')
354+ check_command = '{} --warn {} --crit {}'.format(nova_check_command, warn, crit)
355
356 if skip_disabled:
357 check_command = check_command + ' --skip-disabled'
358@@ -146,15 +142,22 @@ def render_checks():
359
360 nrpe.add_check(shortname='neutron_agents',
361 description='Check that enabled Neutron agents are up',
362- check_cmd=plugins_dir + 'check_neutron_agents.sh')
363+ check_cmd=os.path.join(PLUGINS_DIR, 'check_neutron_agents.sh'))
364
365 if len(check_dns):
366+ dns_check_cmd = "{} {}".format(
367+ os.path.join(PLUGINS_DIR, 'check_dns_multi.sh'),
368+ ' '.join(check_dns.split())
369+ )
370 nrpe.add_check(shortname='dns_multi',
371 description='Check DNS names are resolvable',
372- check_cmd=plugins_dir + 'check_dns_multi.sh ' + ' '.join(check_dns.split()))
373+ check_cmd=dns_check_cmd)
374 else:
375 nrpe.remove_check(shortname='dns_multi')
376
377+ endpoint_checks = create_endpoint_checks()
378+ for check in endpoint_checks:
379+ nrpe.add_check(**check)
380 nrpe.write()
381
382
383@@ -171,8 +174,8 @@ def render_config():
384 hookenv.log('render_config: No credentials yet, skipping')
385 return
386 hookenv.log('render_config: Got credentials for username={}'.format(
387- creds['credentials_username']))
388- render('nagios.novarc', '/var/lib/nagios/nagios.novarc', creds,
389+ creds['username']))
390+ render('nagios.novarc', NOVARC, creds,
391 owner='nagios', group='nagios')
392 render_checks()
393 if config.get('trusted_ssl_ca', None):
394@@ -198,3 +201,94 @@ def fix_ssl():
395 with open(cert_file, 'w') as f:
396 print(cert_content, file=f)
397 subprocess.call(["/usr/sbin/update-ca-certificates"])
398+
399+
400+def create_endpoint_checks():
401+ """
402+ Read the Keystone catalog, and create a check for each endpoint listed.
403+ If there is a healthcheck endpoint for the API, use that URL, otherwise check
404+ the url '/'.
405+ If SSL, add a check for the cert.
406+ """
407+ # provide URLs that can be used for healthcheck for some services
408+ # This also provides a nasty hack-ish way to add switches if we need
409+ # for some services.
410+ health_urls = {
411+ 'keystone': '/healthcheck',
412+ 's3': '/healthcheck',
413+ 'swift': '/healthcheck',
414+ 'aodh': '/ -e Unauthorized -d x-openstack-request-id',
415+ 'cinderv3': '/v3 -e Unauthorized -d x-openstack-request-id',
416+ 'cinderv2': '/v2 -e Unauthorized -d x-openstack-request-id',
417+ 'cinderv1': '/v1 -e Unauthorized -d x-openstack-request-id',
418+ 'glance': '/healthcheck',
419+ 'nova': '/healthcheck',
420+ }
421+
422+ creds = get_credentials()
423+ keystone_client = get_keystone_client(creds)
424+ endpoints = keystone_client.endpoints.list()
425+ services = [x for x in keystone_client.services.list() if x.enabled]
426+ nrpe_checks = []
427+ for endpoint in endpoints:
428+ endpoint.service_names = [x.name for x in services if x.id == endpoint.service_id]
429+ service_name = endpoint.service_names[0]
430+ endpoint.healthcheck_url = health_urls.get(service_name, '/')
431+ if config.get('check_{}_urls'.format(endpoint.interface)):
432+ cmd_params = ['/usr/lib/nagios/plugins/check_http ']
433+ check_url = urlparse(endpoint.url)
434+ host_port = check_url.netloc.split(':')
435+ cmd_params.append('-H {} -p {}'.format(host_port[0], host_port[1]))
436+ cmd_params.append('-u {}'.format(endpoint.healthcheck_url))
437+ # if this is https, we want to add a check for cert expiry
438+ # also need to tell check_http use use TLS
439+ if check_url.scheme == 'https':
440+ cmd_params.append('-S')
441+ # Add an extra check for TLS cert expiry
442+ cert_check = ['-C {},{}'.format(config.get('tls_warn_days'),
443+ config.get('tls_crit_days'))]
444+ nrpe_checks.append({
445+ 'shortname': "{}_{}_cert".format(service_name,
446+ endpoint.interface),
447+ 'description': 'Certificate expiry check for {} {}'.format(
448+ service_name,
449+ endpoint.interface),
450+ 'check_cmd': ' '.join(cmd_params + cert_check)})
451+ # Add the actual health check for the URL
452+ nrpe_checks.append({
453+ 'shortname': "{}_{}".format(service_name,
454+ endpoint.interface),
455+ 'description': 'Endpoint url check for {} {}'.format(
456+ service_name,
457+ endpoint.interface),
458+ 'check_cmd': (' '.join(cmd_params))})
459+ return nrpe_checks
460+
461+
462+def get_keystone_client(creds):
463+ '''
464+ Import the appropriate Keystone client depending on API version.
465+ Return a client object.
466+ '''
467+ from keystoneclient import session
468+ if int(creds['auth_version']) >= 3:
469+ from keystoneclient.v3 import client
470+ from keystoneclient.auth.identity import v3
471+ auth_fields = ['auth_url', 'password', 'username', 'user_domain_name',
472+ 'project_domain_name', 'project_name']
473+ auth_creds = {}
474+ for key in auth_fields:
475+ auth_creds[key] = creds[key]
476+ auth = v3.Password(**auth_creds)
477+
478+ else:
479+ from keystoneclient.v2_0 import client
480+ from keystoneclient.auth.identity import v2
481+ auth_fields = ['auth_url', 'password', 'username', 'tenant_name']
482+ auth_creds = {}
483+ for key in auth_fields:
484+ auth_creds[key] = creds[key]
485+ auth = v2.Password(**auth_creds)
486+
487+ sess = session.Session(auth=auth)
488+ return client.Client(session=sess)
489diff --git a/templates/nagios.novarc b/templates/nagios.novarc
490index e4c7e68..27f1c2a 100644
491--- a/templates/nagios.novarc
492+++ b/templates/nagios.novarc
493@@ -1,7 +1,7 @@
494-export OS_USERNAME={{ credentials_username }}
495-export OS_TENANT_NAME={{ credentials_project }}
496-export OS_PROJECT_NAME={{ credentials_project }}
497-export OS_PASSWORD={{ credentials_password }}
498+export OS_USERNAME={{ username }}
499+export OS_TENANT_NAME={{ tenant_name }}
500+export OS_PROJECT_NAME={{ project_name }}
501+export OS_PASSWORD={{ password }}
502 export OS_REGION_NAME={{ region }}
503 export OS_AUTH_URL={{ auth_url }}
504 {%- if cacert %}
505@@ -13,6 +13,6 @@ export HOME=${SNAP_COMMON}
506 {%- if auth_version %}
507 export OS_IDENTITY_API_VERSION={{ auth_version }}
508 export OS_AUTH_VERSION={{ auth_version }}
509-export OS_USER_DOMAIN_NAME={{ credentials_user_domain }}
510-export OS_PROJECT_DOMAIN_NAME={{ credentials_project_domain }}
511+export OS_USER_DOMAIN_NAME={{ user_domain_name }}
512+export OS_PROJECT_DOMAIN_NAME={{ project_domain_name }}
513 {%- endif %}
514diff --git a/test-requirements.txt b/test-requirements.txt
515new file mode 100644
516index 0000000..77ec3f5
517--- /dev/null
518+++ b/test-requirements.txt
519@@ -0,0 +1,9 @@
520+# Lint and unit test requirements
521+flake8>=2.2.4,<=2.4.1
522+os-testr>=0.4.1
523+requests>=2.18.4
524+charms.reactive
525+mock>=1.2
526+nose>=1.3.7
527+coverage>=3.6
528+netifaces
529diff --git a/tox.ini b/tox.ini
530index 0b8b27a..ba58ab5 100644
531--- a/tox.ini
532+++ b/tox.ini
533@@ -1,12 +1,50 @@
534 [tox]
535 skipsdist=True
536-envlist = py34, py35
537+envlist = pep8, py34, py35, py36
538 skip_missing_interpreters = True
539
540 [testenv]
541-commands = py.test -v
542+setenv = VIRTUAL_ENV={envdir}
543+ PYTHONHASHSEED=0
544+ TERM=linux
545+ LAYER_PATH={toxinidir}/layers
546+ INTERFACE_PATH={toxinidir}/interfaces
547+ JUJU_REPOSITORY={toxinidir}/build
548+passenv = http_proxy https_proxy
549+install_command =
550+ pip install {opts} {packages}
551 deps =
552 -r{toxinidir}/requirements.txt
553
554+[testenv:build]
555+basepython = python3
556+commands =
557+ charm-build --log-level DEBUG -o {toxinidir}/build src {posargs}
558+
559+[testenv:py34]
560+basepython = python3.4
561+deps = -r{toxinidir}/test-requirements.txt
562+commands = ostestr {posargs}
563+
564+[testenv:py35]
565+basepython = python3.5
566+deps = -r{toxinidir}/test-requirements.txt
567+commands = ostestr {posargs}
568+
569+[testenv:py36]
570+basepython = python3.6
571+deps = -r{toxinidir}/test-requirements.txt
572+commands = ostestr {posargs}
573+
574+[testenv:pep8]
575+basepython = python3.6
576+deps = -r{toxinidir}/test-requirements.txt
577+commands = flake8 {posargs} --max-complexity=10 --max-line-length=120 reactive files unit_tests
578+
579+[testenv:venv]
580+basepython = python3
581+commands = {posargs}
582+
583 [flake8]
584-exclude=docs
585+# E402 ignore necessary for path append before sys module import in actions
586+ignore = E402

Subscribers

People subscribed via source and target branches