MySQL Router Charm
The charm intelligently proxies database requests from clients to MySQL InnoDB Clusters.
This commit is contained in:
commit
67d9b4483c
|
@ -0,0 +1,14 @@
|
||||||
|
.tox
|
||||||
|
.stestr
|
||||||
|
*__pycache__*
|
||||||
|
*.pyc
|
||||||
|
build
|
||||||
|
interfaces
|
||||||
|
layers
|
||||||
|
README.ex
|
||||||
|
|
||||||
|
# Remove these
|
||||||
|
src/tests/mysqlsh.snap
|
||||||
|
src/tests/bundles/overlays/local-charm-overlay.yaml.j2
|
||||||
|
src/files
|
||||||
|
manual-attach.sh
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is managed centrally. If you find the need to modify this as a
|
||||||
|
# one-off, please don't. Intead, consult #openstack-charms and ask about
|
||||||
|
# requirements management in charms via bot-control. Thank you.
|
||||||
|
#
|
||||||
|
# Build requirements
|
||||||
|
charm-tools>=2.4.4
|
||||||
|
simplejson
|
|
@ -0,0 +1,27 @@
|
||||||
|
options:
|
||||||
|
source:
|
||||||
|
type: string
|
||||||
|
default: distro
|
||||||
|
description: |
|
||||||
|
Repository from which to install. May be one of the following:
|
||||||
|
distro (default), ppa:somecustom/ppa, a deb url sources entry,
|
||||||
|
or a supported Ubuntu Cloud Archive e.g.
|
||||||
|
.
|
||||||
|
cloud:<series>-<openstack-release>
|
||||||
|
cloud:<series>-<openstack-release>/updates
|
||||||
|
cloud:<series>-<openstack-release>/staging
|
||||||
|
cloud:<series>-<openstack-release>/proposed
|
||||||
|
.
|
||||||
|
See https://wiki.ubuntu.com/OpenStack/CloudArchive for info on which
|
||||||
|
cloud archives are available and supported.
|
||||||
|
system-user:
|
||||||
|
# TODO What user? No mysql user exists. Create one?
|
||||||
|
type: string
|
||||||
|
description: System user to run mysqlrouter
|
||||||
|
default: ubuntu
|
||||||
|
base-port:
|
||||||
|
type: int
|
||||||
|
default: 3306
|
||||||
|
description: |
|
||||||
|
Base port number for RW interface. RO, xRW and xRO will
|
||||||
|
increment from base_port.
|
|
@ -0,0 +1,15 @@
|
||||||
|
includes:
|
||||||
|
- layer:openstack
|
||||||
|
- interface:mysql-shared
|
||||||
|
- interface:mysql-router
|
||||||
|
options:
|
||||||
|
basic:
|
||||||
|
use_venv: True
|
||||||
|
packages: [ 'libmysqlclient-dev']
|
||||||
|
repo: https://github.com/openstack-charmers/charm-mysql-router
|
||||||
|
config:
|
||||||
|
deletes:
|
||||||
|
- verbose
|
||||||
|
- openstack-origin
|
||||||
|
- use-internal-endpoints
|
||||||
|
- debug
|
|
@ -0,0 +1,290 @@
|
||||||
|
# Copyright 2019 Canonicauh Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import charms_openstack.charm
|
||||||
|
import charms_openstack.adapters
|
||||||
|
|
||||||
|
import charms.reactive as reactive
|
||||||
|
|
||||||
|
import charmhelpers.core as ch_core
|
||||||
|
import charmhelpers.contrib.network.ip as ch_net_ip
|
||||||
|
|
||||||
|
import charmhelpers.contrib.database.mysql as mysql
|
||||||
|
|
||||||
|
|
||||||
|
MYSQLD_CNF = "/etc/mysql/mysql.conf.d/mysqld.cnf"
|
||||||
|
|
||||||
|
# Flag Strings
|
||||||
|
MYSQL_ROUTER_BOOTSTRAPPED = "charm.mysqlrouter.bootstrapped"
|
||||||
|
MYSQL_ROUTER_STARTED = "charm.mysqlrouter.started"
|
||||||
|
DB_ROUTER_AVAILABLE = "db-router.available"
|
||||||
|
DB_ROUTER_PROXY_AVAILABLE = "db-router.available.proxy"
|
||||||
|
|
||||||
|
|
||||||
|
@charms_openstack.adapters.config_property
|
||||||
|
def db_router_address(cls):
|
||||||
|
return ch_net_ip.get_relation_ip("db-router")
|
||||||
|
|
||||||
|
|
||||||
|
@charms_openstack.adapters.config_property
|
||||||
|
def shared_db_address(cls):
|
||||||
|
# This is is a subordinate relation, we want mysql communication
|
||||||
|
# to run over localhost
|
||||||
|
# Alternatively: ch_net_ip.get_relation_ip("shared-db")
|
||||||
|
return "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
|
||||||
|
"""Charm class for the MySQLRouter charm."""
|
||||||
|
name = "mysqlrouter"
|
||||||
|
packages = ["mysql-router"]
|
||||||
|
release = "stein"
|
||||||
|
release_pkg = "mysql-router"
|
||||||
|
required_relations = ["db-router", "shared-db"]
|
||||||
|
source_config_key = "source"
|
||||||
|
|
||||||
|
# FIXME Can we have a non-systemd services?
|
||||||
|
# Create a systemd shim?
|
||||||
|
# services = ["mysqlrouter"]
|
||||||
|
services = []
|
||||||
|
# TODO Post bootstrap config management and restarts
|
||||||
|
restart_map = {}
|
||||||
|
# TODO Pick group owner
|
||||||
|
group = "mysql"
|
||||||
|
|
||||||
|
# For internal use with mysql.get_db_data
|
||||||
|
_unprefixed = "MRUP"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mysqlrouter_bin(self):
|
||||||
|
return "/usr/bin/mysqlrouter"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_router_endpoint(self):
|
||||||
|
return reactive.relations.endpoint_from_flag("db-router.available")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_prefix(self):
|
||||||
|
return "mysqlrouter"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_router_user(self):
|
||||||
|
# The prefix will be prepended
|
||||||
|
return "{}user".format(self.db_prefix)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_router_password(self):
|
||||||
|
return json.loads(
|
||||||
|
self.db_router_endpoint.password(prefix=self.db_prefix))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_router_address(self):
|
||||||
|
""" My address """
|
||||||
|
return self.options.db_router_address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cluster_address(self):
|
||||||
|
""" Database Cluster Addresss """
|
||||||
|
return json.loads(self.db_router_endpoint.db_host())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shared_db_address(self):
|
||||||
|
""" My address """
|
||||||
|
return self.options.shared_db_address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mysqlrouter_dir(self):
|
||||||
|
return "/home/{}/mysqlrouter".format(self.options.system_user)
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
"""Custom install function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: charms.openstack should probably do this
|
||||||
|
# Need to configure source first
|
||||||
|
self.configure_source()
|
||||||
|
super().install()
|
||||||
|
|
||||||
|
def get_db_helper(self):
|
||||||
|
|
||||||
|
db_helper = mysql.MySQL8Helper(
|
||||||
|
rpasswdf_template="/var/lib/charm/{}/mysql.passwd"
|
||||||
|
.format(ch_core.hookenv.service_name()),
|
||||||
|
upasswdf_template="/var/lib/charm/{}/mysql-{{}}.passwd"
|
||||||
|
.format(ch_core.hookenv.service_name()),
|
||||||
|
user=self.db_router_user,
|
||||||
|
password=self.db_router_password,
|
||||||
|
host=self.cluster_address)
|
||||||
|
return db_helper
|
||||||
|
|
||||||
|
def states_to_check(self, required_relations=None):
|
||||||
|
"""Custom state check function for charm specific state check needs.
|
||||||
|
|
||||||
|
"""
|
||||||
|
states_to_check = super().states_to_check(required_relations)
|
||||||
|
states_to_check["charm"] = [
|
||||||
|
(MYSQL_ROUTER_BOOTSTRAPPED,
|
||||||
|
"waiting",
|
||||||
|
"MySQL-Router not yet bootstrapped"),
|
||||||
|
(MYSQL_ROUTER_STARTED,
|
||||||
|
"waiting",
|
||||||
|
"MySQL-Router not yet started"),
|
||||||
|
(DB_ROUTER_PROXY_AVAILABLE,
|
||||||
|
"waiting",
|
||||||
|
"Waiting for proxied DB creation from cluster")]
|
||||||
|
|
||||||
|
return states_to_check
|
||||||
|
|
||||||
|
def check_mysql_connection(self):
|
||||||
|
"""Check if local instance of mysql is accessible.
|
||||||
|
|
||||||
|
Attempt a connection to the local instance of mysql to determine
|
||||||
|
if it is running and accessible.
|
||||||
|
|
||||||
|
:side effect: Uses get_db_helper to execute a connection to the DB.
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
|
m_helper = self.get_db_helper()
|
||||||
|
try:
|
||||||
|
m_helper.connect(self.db_router_user,
|
||||||
|
self.db_router_password,
|
||||||
|
self.shared_db_address)
|
||||||
|
return True
|
||||||
|
except mysql.MySQLdb._exceptions.OperationalError:
|
||||||
|
ch_core.hookenv.log("Could not connect to db", "DEBUG")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def custom_assess_status_check(self):
|
||||||
|
|
||||||
|
# Start with default checks
|
||||||
|
for f in [self.check_if_paused,
|
||||||
|
self.check_interfaces,
|
||||||
|
self.check_mandatory_config]:
|
||||||
|
state, message = f()
|
||||||
|
if state is not None:
|
||||||
|
ch_core.hookenv.status_set(state, message)
|
||||||
|
return state, message
|
||||||
|
|
||||||
|
# We should not get here until there is a connection to the
|
||||||
|
# cluster (db-router available)
|
||||||
|
if not self.check_mysql_connection():
|
||||||
|
return "blocked", "Failed to connect to MySQL"
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def bootstrap_mysqlrouter(self):
|
||||||
|
|
||||||
|
cmd = [self.mysqlrouter_bin,
|
||||||
|
"--user", self.options.system_user,
|
||||||
|
"--bootstrap",
|
||||||
|
"{}:{}@{}".format(self.db_router_user,
|
||||||
|
self.db_router_password,
|
||||||
|
self.cluster_address),
|
||||||
|
"--directory", self.mysqlrouter_dir,
|
||||||
|
"--conf-use-sockets",
|
||||||
|
"--conf-base-port", str(self.options.base_port)]
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||||
|
ch_core.hookenv.log(output, "DEBUG")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
ch_core.hookenv.log(
|
||||||
|
"Failed to bootstrap mysqlrouter: {}"
|
||||||
|
.format(e.output.decode("UTF-8")), "ERROR")
|
||||||
|
return
|
||||||
|
reactive.flags.set_flag(MYSQL_ROUTER_BOOTSTRAPPED)
|
||||||
|
|
||||||
|
def start_mysqlrouter(self):
|
||||||
|
cmd = ["{}/start.sh".format(self.mysqlrouter_dir)]
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
cmd, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, bufsize=1,
|
||||||
|
universal_newlines=True)
|
||||||
|
proc.wait()
|
||||||
|
ch_core.hookenv.log("MySQL router started", "DEBUG")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
ch_core.hookenv.log(
|
||||||
|
"Failed to start mysqlrouter: {}"
|
||||||
|
.format(e.output.decode("UTF-8")), "ERROR")
|
||||||
|
return
|
||||||
|
reactive.flags.set_flag(MYSQL_ROUTER_STARTED)
|
||||||
|
|
||||||
|
def stop_mysqlrouter(self):
|
||||||
|
cmd = ["{}/stop.sh".format(self.mysqlrouter_dir)]
|
||||||
|
try:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
cmd, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, bufsize=1,
|
||||||
|
universal_newlines=True)
|
||||||
|
proc.wait()
|
||||||
|
ch_core.hookenv.log("MySQL router stopped", "DEBUG")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
ch_core.hookenv.log(
|
||||||
|
"Failed to start mysqlrouter: {}"
|
||||||
|
.format(e.output.decode("UTF-8")), "ERROR")
|
||||||
|
return
|
||||||
|
reactive.flags.clear_flag(MYSQL_ROUTER_STARTED)
|
||||||
|
|
||||||
|
def restart_mysqlrouter(self):
|
||||||
|
self.stop_mysqlrouter()
|
||||||
|
self.start_mysqlrouter()
|
||||||
|
|
||||||
|
def proxy_db_and_user_requests(
|
||||||
|
self, receiving_interface, sending_interface):
|
||||||
|
|
||||||
|
# We can use receiving_interface.all_joined_units.received
|
||||||
|
# as this is a subordiante and there is only one unit related.
|
||||||
|
db_data = mysql.get_db_data(
|
||||||
|
dict(receiving_interface.all_joined_units.received),
|
||||||
|
unprefixed=self._unprefixed)
|
||||||
|
|
||||||
|
for prefix in db_data:
|
||||||
|
sending_interface.configure_proxy_db(
|
||||||
|
db_data[prefix].get("database"),
|
||||||
|
db_data[prefix].get("username"),
|
||||||
|
db_data[prefix].get("hostname"),
|
||||||
|
prefix=prefix)
|
||||||
|
|
||||||
|
def proxy_db_and_user_responses(
|
||||||
|
self, receiving_interface, sending_interface):
|
||||||
|
|
||||||
|
# This is a suborndinate relationship there is only ever one
|
||||||
|
unit = sending_interface.all_joined_units[0]
|
||||||
|
|
||||||
|
for prefix in receiving_interface.get_prefixes():
|
||||||
|
|
||||||
|
if prefix in self.db_prefix:
|
||||||
|
# Do not send the mysqlrouter credentials to the client
|
||||||
|
continue
|
||||||
|
|
||||||
|
_password = json.loads(
|
||||||
|
receiving_interface.password(prefix=prefix))
|
||||||
|
if ch_core.hookenv.local_unit() in (json.loads(
|
||||||
|
receiving_interface.allowed_units(prefix=prefix))):
|
||||||
|
_allowed_hosts = unit.unit_name
|
||||||
|
else:
|
||||||
|
_allowed_hosts = None
|
||||||
|
if prefix in self._unprefixed:
|
||||||
|
prefix = None
|
||||||
|
|
||||||
|
sending_interface.set_db_connection_info(
|
||||||
|
unit.relation.relation_id,
|
||||||
|
self.shared_db_address,
|
||||||
|
_password,
|
||||||
|
_allowed_hosts,
|
||||||
|
prefix=prefix)
|
|
@ -0,0 +1,20 @@
|
||||||
|
name: mysql-router
|
||||||
|
summary: MySQL Router
|
||||||
|
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
|
||||||
|
description: |
|
||||||
|
MySQL Router proxying communication between application clients and MySQL InnoDB Clusters.
|
||||||
|
tags:
|
||||||
|
- databases
|
||||||
|
subordinate: true
|
||||||
|
series:
|
||||||
|
- eoan
|
||||||
|
provides:
|
||||||
|
shared-db:
|
||||||
|
interface: mysql-shared
|
||||||
|
scope: container
|
||||||
|
requires:
|
||||||
|
juju-info:
|
||||||
|
interface: juju-info
|
||||||
|
scope: container
|
||||||
|
db-router:
|
||||||
|
interface: mysql-router
|
|
@ -0,0 +1,64 @@
|
||||||
|
import charms.reactive as reactive
|
||||||
|
|
||||||
|
import charms_openstack.bus
|
||||||
|
import charms_openstack.charm as charm
|
||||||
|
|
||||||
|
import charm.mysql_router as mysql_router # noqa
|
||||||
|
|
||||||
|
charms_openstack.bus.discover()
|
||||||
|
|
||||||
|
|
||||||
|
charm.use_defaults(
|
||||||
|
'charm.installed',
|
||||||
|
'config.changed',
|
||||||
|
'update-status',
|
||||||
|
'upgrade-charm')
|
||||||
|
|
||||||
|
|
||||||
|
@reactive.when('charm.installed')
|
||||||
|
@reactive.when('db-router.connected')
|
||||||
|
def db_router_request(db_router):
|
||||||
|
with charm.provide_charm_instance() as instance:
|
||||||
|
db_router.set_prefix(instance.db_prefix)
|
||||||
|
db_router.configure_db_router(
|
||||||
|
instance.db_router_user,
|
||||||
|
instance.db_router_address,
|
||||||
|
prefix=instance.db_prefix)
|
||||||
|
instance.assess_status()
|
||||||
|
|
||||||
|
|
||||||
|
@reactive.when('charm.installed')
|
||||||
|
@reactive.when(mysql_router.DB_ROUTER_AVAILABLE)
|
||||||
|
@reactive.when_not(mysql_router.MYSQL_ROUTER_BOOTSTRAPPED)
|
||||||
|
def bootstrap_mysqlrouter(db_router):
|
||||||
|
with charm.provide_charm_instance() as instance:
|
||||||
|
instance.bootstrap_mysqlrouter()
|
||||||
|
instance.assess_status()
|
||||||
|
|
||||||
|
|
||||||
|
@reactive.when('charm.installed')
|
||||||
|
@reactive.when(mysql_router.DB_ROUTER_AVAILABLE)
|
||||||
|
@reactive.when(mysql_router.MYSQL_ROUTER_BOOTSTRAPPED)
|
||||||
|
@reactive.when_not(mysql_router.MYSQL_ROUTER_STARTED)
|
||||||
|
def start_mysqlrouter(db_router):
|
||||||
|
with charm.provide_charm_instance() as instance:
|
||||||
|
instance.start_mysqlrouter()
|
||||||
|
instance.assess_status()
|
||||||
|
|
||||||
|
|
||||||
|
@reactive.when(mysql_router.MYSQL_ROUTER_STARTED)
|
||||||
|
@reactive.when(mysql_router.DB_ROUTER_AVAILABLE)
|
||||||
|
@reactive.when('shared-db.available')
|
||||||
|
def proxy_shared_db_requests(shared_db, db_router):
|
||||||
|
with charm.provide_charm_instance() as instance:
|
||||||
|
instance.proxy_db_and_user_requests(shared_db, db_router)
|
||||||
|
instance.assess_status()
|
||||||
|
|
||||||
|
|
||||||
|
@reactive.when(mysql_router.MYSQL_ROUTER_STARTED)
|
||||||
|
@reactive.when(mysql_router.DB_ROUTER_PROXY_AVAILABLE)
|
||||||
|
@reactive.when('shared-db.available')
|
||||||
|
def proxy_shared_db_responses(shared_db, db_router):
|
||||||
|
with charm.provide_charm_instance() as instance:
|
||||||
|
instance.proxy_db_and_user_responses(db_router, shared_db)
|
||||||
|
instance.assess_status()
|
|
@ -0,0 +1,3 @@
|
||||||
|
# zaza
|
||||||
|
git+https://github.com/openstack-charmers/zaza.git#egg=zaza
|
||||||
|
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
|
|
@ -0,0 +1,17 @@
|
||||||
|
series: eoan
|
||||||
|
relations:
|
||||||
|
- ["keystone:shared-db", "mysql-router:shared-db"]
|
||||||
|
- ["mysql-router:db-router", "mysql-innodb-cluster:db-router"]
|
||||||
|
applications:
|
||||||
|
mysql-router:
|
||||||
|
charm: ../../../mysql-router
|
||||||
|
mysql-innodb-cluster:
|
||||||
|
series: eoan
|
||||||
|
charm: cs:~thedac/mysql-innodb-cluster
|
||||||
|
num_units: 3
|
||||||
|
options:
|
||||||
|
source: distro-proposed
|
||||||
|
keystone:
|
||||||
|
series: eoan
|
||||||
|
charm: cs:~openstack-charmers-next/keystone
|
||||||
|
num_units: 1
|
|
@ -0,0 +1,11 @@
|
||||||
|
charm_name: mysql-router
|
||||||
|
configure:
|
||||||
|
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
|
||||||
|
tests:
|
||||||
|
# Validates DB connectivity
|
||||||
|
- zaza.openstack.charm_tests.keystone.tests.AuthenticationAuthorizationTest
|
||||||
|
dev_bundles:
|
||||||
|
gate_bundles:
|
||||||
|
- eoan
|
||||||
|
smoke_bundles:
|
||||||
|
- eoan
|
|
@ -0,0 +1,35 @@
|
||||||
|
[tox]
|
||||||
|
envlist = pep8
|
||||||
|
skipsdist = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
PYTHONHASHSEED=0
|
||||||
|
whitelist_externals = juju
|
||||||
|
passenv = HOME TERM CS_API_* OS_* AMULET_*
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
install_command =
|
||||||
|
pip install {opts} {packages}
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
basepython = python3
|
||||||
|
deps=charm-tools
|
||||||
|
commands = charm-proof
|
||||||
|
|
||||||
|
[testenv:func-noop]
|
||||||
|
basepython = python3
|
||||||
|
commands =
|
||||||
|
true
|
||||||
|
|
||||||
|
[testenv:func]
|
||||||
|
basepython = python3
|
||||||
|
commands =
|
||||||
|
functest-run-suite --keep-model
|
||||||
|
|
||||||
|
[testenv:func-smoke]
|
||||||
|
basepython = python3
|
||||||
|
commands =
|
||||||
|
functest-run-suite --keep-model --smoke
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
|
@ -0,0 +1,3 @@
|
||||||
|
jinja2
|
||||||
|
psutil
|
||||||
|
mysqlclient
|
|
@ -0,0 +1,13 @@
|
||||||
|
# This file is managed centrally. If you find the need to modify this as a
|
||||||
|
# one-off, please don't. Intead, consult #openstack-charms and ask about
|
||||||
|
# requirements management in charms via bot-control. Thank you.
|
||||||
|
#
|
||||||
|
# Lint and unit test requirements
|
||||||
|
flake8>=2.2.4,<=2.4.1
|
||||||
|
stestr>=2.2.0
|
||||||
|
requests>=2.18.4
|
||||||
|
charms.reactive
|
||||||
|
mock>=1.2
|
||||||
|
nose>=1.3.7
|
||||||
|
coverage>=3.6
|
||||||
|
git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Source charm: ./tox.ini
|
||||||
|
# This file is managed centrally by release-tools and should not be modified
|
||||||
|
# within individual charm repos.
|
||||||
|
[tox]
|
||||||
|
skipsdist = True
|
||||||
|
envlist = pep8,py3
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
PYTHONHASHSEED=0
|
||||||
|
TERM=linux
|
||||||
|
CHARM_LAYER_PATH={toxinidir}/layers
|
||||||
|
CHARM_INTERFACES_DIR={toxinidir}/interfaces
|
||||||
|
JUJU_REPOSITORY={toxinidir}/build
|
||||||
|
passenv = http_proxy https_proxy OS_*
|
||||||
|
install_command =
|
||||||
|
pip install {opts} {packages}
|
||||||
|
deps =
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
|
||||||
|
[testenv:build]
|
||||||
|
basepython = python3
|
||||||
|
commands =
|
||||||
|
charm-build --log-level DEBUG -o {toxinidir}/build src {posargs}
|
||||||
|
|
||||||
|
[testenv:py3]
|
||||||
|
basepython = python3
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = stestr run {posargs}
|
||||||
|
|
||||||
|
[testenv:py35]
|
||||||
|
basepython = python3.5
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = stestr run {posargs}
|
||||||
|
|
||||||
|
[testenv:py36]
|
||||||
|
basepython = python3.6
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = stestr run {posargs}
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
basepython = python3
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = flake8 {posargs} src unit_tests
|
||||||
|
|
||||||
|
[testenv:cover]
|
||||||
|
# Technique based heavily upon
|
||||||
|
# https://github.com/openstack/nova/blob/master/tox.ini
|
||||||
|
basepython = python3
|
||||||
|
deps = -r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
setenv =
|
||||||
|
{[testenv]setenv}
|
||||||
|
PYTHON=coverage run
|
||||||
|
commands =
|
||||||
|
coverage erase
|
||||||
|
stestr run {posargs}
|
||||||
|
coverage combine
|
||||||
|
coverage html -d cover
|
||||||
|
coverage xml -o cover/coverage.xml
|
||||||
|
coverage report
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
branch = True
|
||||||
|
concurrency = multiprocessing
|
||||||
|
parallel = True
|
||||||
|
source =
|
||||||
|
.
|
||||||
|
omit =
|
||||||
|
.tox/*
|
||||||
|
*/charmhelpers/*
|
||||||
|
unit_tests/*
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
basepython = python3
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
# E402 ignore necessary for path append before sys module import in actions
|
||||||
|
ignore = E402
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Copyright 2019 Canonical Ltd
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Mock out charmhelpers so that we can test without it.
|
||||||
|
import charms_openstack.test_mocks # noqa
|
||||||
|
charms_openstack.test_mocks.mock_charmhelpers()
|
||||||
|
|
||||||
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_src = os.path.abspath(os.path.join(_path, 'src'))
|
||||||
|
_lib = os.path.abspath(os.path.join(_path, 'src/lib'))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
_add_path(_src)
|
||||||
|
_add_path(_lib)
|
Loading…
Reference in New Issue