Misc fixes and additional testing
* Add functional tests. This is currently limited to checking the charm deploys and relates to mandatory relations. * Add heat-engine container * Add management of all 3 containers to charm. Previously only the heat-api container was managed and this was incorrectly done on the assumption is was a wsgi app * Add management for auth_encryption_key * Add ops.testing unit tests Change-Id: I57b24a01ed473c96648f78095dc5e4e87d240e66
This commit is contained in:
parent
d84c620e0f
commit
2ee5a5779a
8
charms/heat-k8s/.gitignore
vendored
8
charms/heat-k8s/.gitignore
vendored
@ -1,9 +1,11 @@
|
|||||||
venv/
|
venv/
|
||||||
build/
|
build/
|
||||||
*.charm
|
*.charm
|
||||||
.tox/
|
*.swp
|
||||||
|
|
||||||
.coverage
|
.coverage
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
.idea
|
.tox
|
||||||
.vscode/
|
.stestr/
|
||||||
|
tempest.log
|
||||||
|
5
charms/heat-k8s/.gitreview
Normal file
5
charms/heat-k8s/.gitreview
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[gerrit]
|
||||||
|
host=review.opendev.org
|
||||||
|
port=29418
|
||||||
|
project=openstack/charm-heat-k8s.git
|
||||||
|
defaultbranch=main
|
3
charms/heat-k8s/.stestr.conf
Normal file
3
charms/heat-k8s/.stestr.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_path=./tests/unit
|
||||||
|
top_dir=./tests
|
11
charms/heat-k8s/.zuul.yaml
Normal file
11
charms/heat-k8s/.zuul.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
- project:
|
||||||
|
templates:
|
||||||
|
- openstack-python3-charm-yoga-jobs
|
||||||
|
- openstack-cover-jobs
|
||||||
|
- microk8s-func-test
|
||||||
|
vars:
|
||||||
|
charm_build_name: heat-k8s
|
||||||
|
juju_channel: 3.2/stable
|
||||||
|
juju_classic_mode: false
|
||||||
|
microk8s_channel: 1.26-strict/stable
|
||||||
|
microk8s_classic_mode: false
|
3
charms/heat-k8s/TODO.txt
Normal file
3
charms/heat-k8s/TODO.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
* Register CFN endpoint with keystone traefik
|
||||||
|
* Tempest tests
|
||||||
|
* Switch to Antelope rocks
|
@ -8,15 +8,22 @@ description: |
|
|||||||
version: 3
|
version: 3
|
||||||
bases:
|
bases:
|
||||||
- name: ubuntu
|
- name: ubuntu
|
||||||
channel: 20.04/stable
|
channel: 22.04/stable
|
||||||
|
assumes:
|
||||||
|
- k8s-api
|
||||||
|
- juju >= 3.1
|
||||||
tags:
|
tags:
|
||||||
- openstack
|
- openstack
|
||||||
|
source: https://opendev.org/openstack/charm-heat-k8s
|
||||||
|
issues: https://bugs.launchpad.net/charm-heat-k8s
|
||||||
|
|
||||||
containers:
|
containers:
|
||||||
heat-api:
|
heat-api:
|
||||||
resource: heat-api-image
|
resource: heat-api-image
|
||||||
heat-api-cfn:
|
heat-api-cfn:
|
||||||
resource: heat-api-cfn-image
|
resource: heat-api-cfn-image
|
||||||
|
heat-engine:
|
||||||
|
resource: heat-engine-image
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
heat-api-image:
|
heat-api-image:
|
||||||
@ -29,6 +36,10 @@ resources:
|
|||||||
description: OCI image for OpenStack Heat CFN
|
description: OCI image for OpenStack Heat CFN
|
||||||
# docker.io/kolla/ubuntu-binary-heat-api-cfn:yoga
|
# docker.io/kolla/ubuntu-binary-heat-api-cfn:yoga
|
||||||
upstream-source: docker.io/kolla/ubuntu-binary-heat-api-cfn@sha256:6eec5915066b55696414022c86c42360cdbd4b8b1250e06b470fee25af394b66
|
upstream-source: docker.io/kolla/ubuntu-binary-heat-api-cfn@sha256:6eec5915066b55696414022c86c42360cdbd4b8b1250e06b470fee25af394b66
|
||||||
|
heat-engine-image:
|
||||||
|
type: oci-image
|
||||||
|
description: OCI image for OpenStack Heat Engine
|
||||||
|
upstream-source: docker.io/kolla/ubuntu-binary-heat-engine@sha256:a54491f7e09eedeaa42c046cedc478f8ba78fc455a6ba285a52a5d0f8ae1df84
|
||||||
|
|
||||||
requires:
|
requires:
|
||||||
database:
|
database:
|
||||||
|
10
charms/heat-k8s/osci.yaml
Normal file
10
charms/heat-k8s/osci.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
- project:
|
||||||
|
templates:
|
||||||
|
- charm-publish-jobs
|
||||||
|
vars:
|
||||||
|
needs_charm_build: true
|
||||||
|
charm_build_name: heat-k8s
|
||||||
|
build_type: charmcraft
|
||||||
|
publish_charm: true
|
||||||
|
charmcraft_channel: 2.0/stable
|
||||||
|
publish_channel: 2023.1/edge
|
@ -1,3 +1,6 @@
|
|||||||
|
# Copyright 2022 Canonical Ltd.
|
||||||
|
# See LICENSE file for licensing details.
|
||||||
|
|
||||||
# Testing tools configuration
|
# Testing tools configuration
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
branch = true
|
branch = true
|
||||||
@ -11,29 +14,26 @@ log_cli_level = "INFO"
|
|||||||
|
|
||||||
# Formatting tools configuration
|
# Formatting tools configuration
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 99
|
line-length = 79
|
||||||
target-version = ["py38"]
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
multi_line_output = 3
|
||||||
|
force_grid_wrap = true
|
||||||
|
|
||||||
# Linting tools configuration
|
# Linting tools configuration
|
||||||
[tool.ruff]
|
[tool.flake8]
|
||||||
line-length = 99
|
max-line-length = 79
|
||||||
select = ["E", "W", "F", "C", "N", "D", "I001"]
|
max-doc-length = 99
|
||||||
extend-ignore = [
|
|
||||||
"D203",
|
|
||||||
"D204",
|
|
||||||
"D213",
|
|
||||||
"D215",
|
|
||||||
"D400",
|
|
||||||
"D404",
|
|
||||||
"D406",
|
|
||||||
"D407",
|
|
||||||
"D408",
|
|
||||||
"D409",
|
|
||||||
"D413",
|
|
||||||
]
|
|
||||||
ignore = ["E501", "D107"]
|
|
||||||
extend-exclude = ["__pycache__", "*.egg_info"]
|
|
||||||
per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]}
|
|
||||||
|
|
||||||
[tool.ruff.mccabe]
|
|
||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
|
exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
|
||||||
|
select = ["E", "W", "F", "C", "N", "R", "D", "H"]
|
||||||
|
# Ignore W503, E501 because using black creates errors with this
|
||||||
|
# Ignore D107 Missing docstring in __init__
|
||||||
|
ignore = ["W503", "E501", "D107", "E402"]
|
||||||
|
per-file-ignores = []
|
||||||
|
docstring-convention = "google"
|
||||||
|
# Check for properly formatted copyright header in each file
|
||||||
|
copyright-check = "True"
|
||||||
|
copyright-author = "Canonical Ltd."
|
||||||
|
copyright-regexp = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+%(author)s"
|
||||||
|
13
charms/heat-k8s/rename.sh
Executable file
13
charms/heat-k8s/rename.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
charm=$(grep "charm_build_name" osci.yaml | awk '{print $2}')
|
||||||
|
echo "renaming ${charm}_*.charm to ${charm}.charm"
|
||||||
|
echo -n "pwd: "
|
||||||
|
pwd
|
||||||
|
ls -al
|
||||||
|
echo "Removing bad downloaded charm maybe?"
|
||||||
|
if [[ -e "${charm}.charm" ]];
|
||||||
|
then
|
||||||
|
rm "${charm}.charm"
|
||||||
|
fi
|
||||||
|
echo "Renaming charm here."
|
||||||
|
mv ${charm}_*.charm ${charm}.charm
|
@ -1,56 +1,225 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2023 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.
|
||||||
|
|
||||||
"""Heat Operator Charm.
|
"""Heat Operator Charm.
|
||||||
|
|
||||||
This charm provide Heat services as part of an OpenStack deployment
|
This charm provide heat services as part of an OpenStack deployment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import secrets
|
||||||
from ops.framework import StoredState
|
import string
|
||||||
from ops.main import main
|
from typing import (
|
||||||
|
List,
|
||||||
|
)
|
||||||
|
|
||||||
import ops_sunbeam.charm as sunbeam_charm
|
import ops_sunbeam.charm as sunbeam_charm
|
||||||
|
import ops_sunbeam.container_handlers as sunbeam_chandlers
|
||||||
|
import ops_sunbeam.core as sunbeam_core
|
||||||
|
from ops.framework import (
|
||||||
|
StoredState,
|
||||||
|
)
|
||||||
|
from ops.main import (
|
||||||
|
main,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
HEAT_API_CONTAINER = "heat-api"
|
||||||
|
HEAT_API_CNF_CONTAINER = "heat-api-cfn"
|
||||||
|
HEAT_ENGINE_CONTAINER = "heat-engine"
|
||||||
|
|
||||||
|
|
||||||
|
class HeatAPIPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||||
|
"""Pebble handler for Heat API container."""
|
||||||
|
|
||||||
|
def get_layer(self):
|
||||||
|
"""Heat API service.
|
||||||
|
|
||||||
|
:returns: pebble service layer configuration for heat api service
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"summary": "heat api layer",
|
||||||
|
"description": "pebble configuration for heat api service",
|
||||||
|
"services": {
|
||||||
|
"heat-api": {
|
||||||
|
"override": "replace",
|
||||||
|
"summary": "Heat API",
|
||||||
|
"command": "heat-api",
|
||||||
|
"startup": "enabled",
|
||||||
|
"user": "heat",
|
||||||
|
"group": "heat",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HeatAPICFNPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||||
|
"""Pebble handler for Heat API CNF container."""
|
||||||
|
|
||||||
|
def get_layer(self):
|
||||||
|
"""Heat API CNF service.
|
||||||
|
|
||||||
|
:returns: pebble service layer configuration for API CNF service
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"summary": "heat api cfn layer",
|
||||||
|
"description": "pebble configuration for heat api cfn service",
|
||||||
|
"services": {
|
||||||
|
"heat-api-cfn": {
|
||||||
|
"override": "replace",
|
||||||
|
"summary": "Heat API CNF",
|
||||||
|
"command": "heat-api-cfn",
|
||||||
|
"startup": "enabled",
|
||||||
|
"user": "heat",
|
||||||
|
"group": "heat",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HeatEnginePebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||||
|
"""Pebble handler for Heat engine container."""
|
||||||
|
|
||||||
|
def get_layer(self):
|
||||||
|
"""Heat Engine service.
|
||||||
|
|
||||||
|
:returns: pebble service layer configuration for heat engine service
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"summary": "heat engine layer",
|
||||||
|
"description": "pebble configuration for heat engine service",
|
||||||
|
"services": {
|
||||||
|
"heat-engine": {
|
||||||
|
"override": "replace",
|
||||||
|
"summary": "Heat Engine",
|
||||||
|
"command": "heat-engine",
|
||||||
|
"startup": "enabled",
|
||||||
|
"user": "heat",
|
||||||
|
"group": "heat",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||||
"""Charm the service."""
|
"""Charm the service."""
|
||||||
|
|
||||||
_state = StoredState()
|
_state = StoredState()
|
||||||
service_name = "heat-api"
|
service_name = "heat-api"
|
||||||
wsgi_admin_script = '/usr/bin/heat-wsgi-api'
|
wsgi_admin_script = "/usr/bin/heat-wsgi-api"
|
||||||
wsgi_public_script = '/usr/bin/heat-wsgi-api'
|
wsgi_public_script = "/usr/bin/heat-wsgi-api"
|
||||||
|
heat_auth_encryption_key = "auth_encryption_key"
|
||||||
|
|
||||||
db_sync_cmds = [
|
db_sync_cmds = [["heat-manage", "db_sync"]]
|
||||||
['heat-manage', 'db_sync']
|
|
||||||
]
|
mandatory_relations = {
|
||||||
|
"database",
|
||||||
|
"amqp",
|
||||||
|
"identity-service",
|
||||||
|
"ingress-public",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_pebble_handlers(
|
||||||
|
self,
|
||||||
|
) -> List[sunbeam_chandlers.ServicePebbleHandler]:
|
||||||
|
"""Pebble handlers for operator."""
|
||||||
|
pebble_handlers = [
|
||||||
|
HeatAPIPebbleHandler(
|
||||||
|
self,
|
||||||
|
HEAT_API_CONTAINER,
|
||||||
|
"heat-api",
|
||||||
|
self.default_container_configs(),
|
||||||
|
self.template_dir,
|
||||||
|
self.configure_charm,
|
||||||
|
),
|
||||||
|
HeatAPICFNPebbleHandler(
|
||||||
|
self,
|
||||||
|
HEAT_API_CNF_CONTAINER,
|
||||||
|
"heat-api-cfn",
|
||||||
|
self.default_container_configs(),
|
||||||
|
self.template_dir,
|
||||||
|
self.configure_charm,
|
||||||
|
),
|
||||||
|
HeatEnginePebbleHandler(
|
||||||
|
self,
|
||||||
|
HEAT_ENGINE_CONTAINER,
|
||||||
|
"heat-engine",
|
||||||
|
self.default_container_configs(),
|
||||||
|
self.template_dir,
|
||||||
|
self.configure_charm,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return pebble_handlers
|
||||||
|
|
||||||
|
def get_heat_auth_encryption_key(self):
|
||||||
|
"""Return the shared metadata secret."""
|
||||||
|
return self.leader_get(self.heat_auth_encryption_key)
|
||||||
|
|
||||||
|
def set_heat_auth_encryption_key(self):
|
||||||
|
"""Store the shared metadata secret."""
|
||||||
|
alphabet = string.ascii_letters + string.digits
|
||||||
|
key = "".join(secrets.choice(alphabet) for i in range(32))
|
||||||
|
self.leader_set({self.heat_auth_encryption_key: key})
|
||||||
|
|
||||||
|
def configure_charm(self, event):
|
||||||
|
"""Configure charm.
|
||||||
|
|
||||||
|
Ensure setting the auth key is first as services in container need it
|
||||||
|
to start.
|
||||||
|
"""
|
||||||
|
if self.unit.is_leader():
|
||||||
|
auth_key = self.get_heat_auth_encryption_key()
|
||||||
|
if auth_key:
|
||||||
|
logger.debug("Found auth key in leader DB")
|
||||||
|
else:
|
||||||
|
logger.debug("Creating auth key")
|
||||||
|
self.set_heat_auth_encryption_key()
|
||||||
|
super().configure_charm(event)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_conf(self) -> str:
|
def service_conf(self) -> str:
|
||||||
"""Service default configuration file."""
|
"""Service default configuration file."""
|
||||||
return f"/etc/heat/heat.conf"
|
return "/etc/heat/heat.conf"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_user(self) -> str:
|
def service_user(self) -> str:
|
||||||
"""Service user file and directory ownership."""
|
"""Service user file and directory ownership."""
|
||||||
return 'heat'
|
return "heat"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_group(self) -> str:
|
def service_group(self) -> str:
|
||||||
"""Service group file and directory ownership."""
|
"""Service group file and directory ownership."""
|
||||||
return 'heat'
|
return "heat"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_endpoints(self):
|
def service_endpoints(self):
|
||||||
|
"""Return heat service endpoints."""
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'service_name': 'heat',
|
"service_name": "heat",
|
||||||
'type': 'heat',
|
"type": "heat",
|
||||||
'description': "OpenStack Heat API",
|
"description": "OpenStack Heat API",
|
||||||
'internal_url': f'{self.internal_url}',
|
"internal_url": f"{self.internal_url}",
|
||||||
'public_url': f'{self.public_url}',
|
"public_url": f"{self.public_url}",
|
||||||
'admin_url': f'{self.admin_url}'}]
|
"admin_url": f"{self.admin_url}",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
def get_healthcheck_layer(self) -> dict:
|
def get_healthcheck_layer(self) -> dict:
|
||||||
"""Health check pebble layer.
|
"""Health check pebble layer.
|
||||||
@ -68,18 +237,19 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def default_container_configs(self):
|
def default_container_configs(self):
|
||||||
"""Base container configs."""
|
"""Return base container configs."""
|
||||||
return [
|
return [
|
||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/heat/heat-api.conf", "heat", "heat"
|
"/etc/heat/heat.conf", "root", "heat"
|
||||||
),
|
),
|
||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/heat/api-paste.ini", "heat", "heat"
|
"/etc/heat/api-paste.ini", "root", "heat"
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_public_ingress_port(self):
|
def default_public_ingress_port(self):
|
||||||
|
"""Port for Heat AI service."""
|
||||||
return 8004
|
return 8004
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
# heat-api composite
|
|
||||||
[composite:heat-api]
|
|
||||||
paste.composite_factory = heat.api:root_app_factory
|
|
||||||
/: api
|
|
||||||
/healthcheck: healthcheck
|
|
||||||
|
|
||||||
# heat-api composite for standalone heat
|
# heat-api pipeline
|
||||||
|
[pipeline:heat-api]
|
||||||
|
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authtoken context osprofiler apiv1app
|
||||||
|
|
||||||
|
# heat-api pipeline for standalone heat
|
||||||
# ie. uses alternative auth backend that authenticates users against keystone
|
# ie. uses alternative auth backend that authenticates users against keystone
|
||||||
# using username and password instead of validating token (which requires
|
# using username and password instead of validating token (which requires
|
||||||
# an admin/service token).
|
# an admin/service token).
|
||||||
@ -12,54 +11,32 @@ paste.composite_factory = heat.api:root_app_factory
|
|||||||
# [paste_deploy]
|
# [paste_deploy]
|
||||||
# flavor = standalone
|
# flavor = standalone
|
||||||
#
|
#
|
||||||
[composite:heat-api-standalone]
|
[pipeline:heat-api-standalone]
|
||||||
paste.composite_factory = heat.api:root_app_factory
|
pipeline = healthcheck cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword context apiv1app
|
||||||
/: api
|
|
||||||
/healthcheck: healthcheck
|
|
||||||
|
|
||||||
# heat-api composite for custom cloud backends
|
# heat-api pipeline for custom cloud backends
|
||||||
# i.e. in heat.conf:
|
# i.e. in heat.conf:
|
||||||
# [paste_deploy]
|
# [paste_deploy]
|
||||||
# flavor = custombackend
|
# flavor = custombackend
|
||||||
#
|
#
|
||||||
[composite:heat-api-custombackend]
|
[pipeline:heat-api-custombackend]
|
||||||
paste.composite_factory = heat.api:root_app_factory
|
pipeline = healthcheck cors request_id context faultwrap versionnegotiation custombackendauth apiv1app
|
||||||
/: api
|
|
||||||
/healthcheck: healthcheck
|
|
||||||
|
|
||||||
# To enable, in heat.conf:
|
# To enable, in heat.conf:
|
||||||
# [paste_deploy]
|
# [paste_deploy]
|
||||||
# flavor = noauth
|
# flavor = noauth
|
||||||
#
|
#
|
||||||
[composite:heat-api-noauth]
|
[pipeline:heat-api-noauth]
|
||||||
paste.composite_factory = heat.api:root_app_factory
|
pipeline = healthcheck cors request_id faultwrap noauth context http_proxy_to_wsgi versionnegotiation apiv1app
|
||||||
/: api
|
|
||||||
/healthcheck: healthcheck
|
|
||||||
|
|
||||||
# heat-api-cfn composite
|
# heat-api-cfn pipeline
|
||||||
[composite:heat-api-cfn]
|
[pipeline:heat-api-cfn]
|
||||||
paste.composite_factory = heat.api:root_app_factory
|
pipeline = healthcheck cors request_id http_proxy_to_wsgi cfnversionnegotiation ec2authtoken authtoken context osprofiler apicfnv1app
|
||||||
/: api-cfn
|
|
||||||
/healthcheck: healthcheck
|
|
||||||
|
|
||||||
# heat-api-cfn composite for standalone heat
|
# heat-api-cfn pipeline for standalone heat
|
||||||
# relies exclusively on authenticating with ec2 signed requests
|
# relies exclusively on authenticating with ec2 signed requests
|
||||||
[composite:heat-api-cfn-standalone]
|
[pipeline:heat-api-cfn-standalone]
|
||||||
paste.composite_factory = heat.api:root_app_factory
|
pipeline = healthcheck cors request_id http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app
|
||||||
/: api-cfn
|
|
||||||
/healthcheck: healthcheck
|
|
||||||
|
|
||||||
[composite:api]
|
|
||||||
paste.composite_factory = heat.api:pipeline_factory
|
|
||||||
default = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authtoken context osprofiler apiv1app
|
|
||||||
standalone = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword context apiv1app
|
|
||||||
custombackend = cors request_id context faultwrap versionnegotiation custombackendauth apiv1app
|
|
||||||
noauth = cors request_id faultwrap noauth context http_proxy_to_wsgi versionnegotiation apiv1app
|
|
||||||
|
|
||||||
[composite:api-cfn]
|
|
||||||
paste.composite_factory = heat.api:pipeline_factory
|
|
||||||
default = cors request_id http_proxy_to_wsgi cfnversionnegotiation ec2authtoken authtoken context osprofiler apicfnv1app
|
|
||||||
standalone = cors request_id http_proxy_to_wsgi cfnversionnegotiation ec2authtoken context apicfnv1app
|
|
||||||
|
|
||||||
[app:apiv1app]
|
[app:apiv1app]
|
||||||
paste.app_factory = heat.common.wsgi:app_factory
|
paste.app_factory = heat.common.wsgi:app_factory
|
||||||
@ -69,9 +46,6 @@ heat.app_factory = heat.api.openstack.v1:API
|
|||||||
paste.app_factory = heat.common.wsgi:app_factory
|
paste.app_factory = heat.common.wsgi:app_factory
|
||||||
heat.app_factory = heat.api.cfn.v1:API
|
heat.app_factory = heat.api.cfn.v1:API
|
||||||
|
|
||||||
[app:healthcheck]
|
|
||||||
paste.app_factory = oslo_middleware:Healthcheck.app_factory
|
|
||||||
|
|
||||||
[filter:versionnegotiation]
|
[filter:versionnegotiation]
|
||||||
paste.filter_factory = heat.common.wsgi:filter_factory
|
paste.filter_factory = heat.common.wsgi:filter_factory
|
||||||
heat.filter_factory = heat.api.openstack:version_negotiation_filter
|
heat.filter_factory = heat.api.openstack:version_negotiation_filter
|
||||||
@ -126,3 +100,6 @@ paste.filter_factory = oslo_middleware.request_id:RequestId.factory
|
|||||||
|
|
||||||
[filter:osprofiler]
|
[filter:osprofiler]
|
||||||
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
|
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
|
||||||
|
|
||||||
|
[filter:healthcheck]
|
||||||
|
paste.filter_factory = oslo_middleware:Healthcheck.factory
|
||||||
|
@ -7,7 +7,7 @@ plugin_dirs = /usr/lib64/heat,/usr/lib/heat
|
|||||||
environment_dir=/etc/heat/environment.d
|
environment_dir=/etc/heat/environment.d
|
||||||
deferred_auth_method=password
|
deferred_auth_method=password
|
||||||
host=heat
|
host=heat
|
||||||
auth_encryption_key={{ encryption_key }}
|
auth_encryption_key={{ peers.auth_encryption_key }}
|
||||||
|
|
||||||
transport_url = {{ amqp.transport_url }}
|
transport_url = {{ amqp.transport_url }}
|
||||||
|
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
# This file is managed centrally. If you find the need to modify this as a
|
# This file is managed centrally by release-tools and should not be modified
|
||||||
# one-off, please don't. Intead, consult #openstack-charms and ask about
|
# within individual charm repos. See the 'global' dir contents for available
|
||||||
# requirements management in charms via bot-control. Thank you.
|
# choices of *requirements.txt files for OpenStack Charms:
|
||||||
charm-tools>=2.4.4
|
# https://github.com/openstack-charmers/release-tools
|
||||||
coverage>=3.6
|
#
|
||||||
mock>=1.2
|
|
||||||
flake8>=2.2.4,<=2.4.1
|
pwgen
|
||||||
pyflakes==2.1.1
|
coverage
|
||||||
stestr>=2.2.0
|
mock
|
||||||
requests>=2.18.4
|
flake8
|
||||||
psutil
|
stestr
|
||||||
# oslo.i18n dropped py35 support
|
git+https://github.com/openstack-charmers/zaza.git@libjuju-3.1#egg=zaza
|
||||||
oslo.i18n<4.0.0
|
|
||||||
git+https://github.com/openstack-charmers/zaza.git#egg=zaza
|
|
||||||
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
|
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
|
||||||
pytz # workaround for 14.04 pip/tox
|
git+https://opendev.org/openstack/tempest.git#egg=tempest
|
||||||
pyudev # for ceph-* charm unit tests (not mocked?)
|
ops
|
||||||
|
71
charms/heat-k8s/tests/bundles/smoke.yaml
Normal file
71
charms/heat-k8s/tests/bundles/smoke.yaml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
bundle: kubernetes
|
||||||
|
applications:
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
charm: ch:mysql-k8s
|
||||||
|
channel: 8.0/stable
|
||||||
|
scale: 1
|
||||||
|
trust: false
|
||||||
|
|
||||||
|
# Currently traefik is required for networking things.
|
||||||
|
# If this isn't present, the units will hang at "installing agent".
|
||||||
|
traefik:
|
||||||
|
charm: ch:traefik-k8s
|
||||||
|
channel: 1.0/stable
|
||||||
|
scale: 1
|
||||||
|
trust: true
|
||||||
|
|
||||||
|
traefik-public:
|
||||||
|
charm: ch:traefik-k8s
|
||||||
|
channel: 1.0/stable
|
||||||
|
scale: 1
|
||||||
|
trust: true
|
||||||
|
options:
|
||||||
|
kubernetes-service-annotations: metallb.universe.tf/address-pool=public
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
charm: ch:rabbitmq-k8s
|
||||||
|
channel: 3.9/edge
|
||||||
|
scale: 1
|
||||||
|
trust: true
|
||||||
|
|
||||||
|
keystone:
|
||||||
|
charm: ch:keystone-k8s
|
||||||
|
channel: 2023.1/edge
|
||||||
|
scale: 1
|
||||||
|
trust: true
|
||||||
|
options:
|
||||||
|
admin-role: admin
|
||||||
|
storage:
|
||||||
|
fernet-keys: 5M
|
||||||
|
credential-keys: 5M
|
||||||
|
|
||||||
|
heat:
|
||||||
|
charm: ../../heat-k8s.charm
|
||||||
|
scale: 1
|
||||||
|
trust: true
|
||||||
|
resources:
|
||||||
|
heat-api-image: docker.io/kolla/ubuntu-binary-heat-api@sha256:ca80d57606525facb404d8b0374701c02609c2ade5cb7e28ba132e666dd85949
|
||||||
|
heat-api-cfn-image: docker.io/kolla/ubuntu-binary-heat-api-cfn@sha256:6eec5915066b55696414022c86c42360cdbd4b8b1250e06b470fee25af394b66
|
||||||
|
heat-engine-image: docker.io/kolla/ubuntu-binary-heat-engine@sha256:a54491f7e09eedeaa42c046cedc478f8ba78fc455a6ba285a52a5d0f8ae1df84
|
||||||
|
|
||||||
|
relations:
|
||||||
|
- - traefik:ingress
|
||||||
|
- keystone:ingress-internal
|
||||||
|
- - traefik-public:ingress
|
||||||
|
- keystone:ingress-public
|
||||||
|
|
||||||
|
- - mysql:database
|
||||||
|
- keystone:database
|
||||||
|
|
||||||
|
- - mysql:database
|
||||||
|
- heat:database
|
||||||
|
- - keystone:identity-service
|
||||||
|
- heat:identity-service
|
||||||
|
- - traefik:ingress
|
||||||
|
- heat:ingress-internal
|
||||||
|
- - traefik-public:ingress
|
||||||
|
- heat:ingress-public
|
||||||
|
- - rabbitmq:amqp
|
||||||
|
- heat:amqp
|
||||||
|
|
1
charms/heat-k8s/tests/config.yaml
Symbolic link
1
charms/heat-k8s/tests/config.yaml
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../config.yaml
|
@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# Copyright 2023 Felipe Reyes <felipe.reyes@canonical.com>
|
|
||||||
# See LICENSE file for licensing details.
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
import yaml
|
|
||||||
from pytest_operator.plugin import OpsTest
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
|
|
||||||
APP_NAME = METADATA["name"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.abort_on_fail
|
|
||||||
async def test_build_and_deploy(ops_test: OpsTest):
|
|
||||||
"""Build the charm-under-test and deploy it together with related charms.
|
|
||||||
|
|
||||||
Assert on the unit status before any relations/configurations take place.
|
|
||||||
"""
|
|
||||||
# Build and deploy charm from local source folder
|
|
||||||
charm = await ops_test.build_charm(".")
|
|
||||||
resources = {"httpbin-image": METADATA["resources"]["httpbin-image"]["upstream-source"]}
|
|
||||||
|
|
||||||
# Deploy the charm and wait for active/idle status
|
|
||||||
await asyncio.gather(
|
|
||||||
ops_test.model.deploy(charm, resources=resources, application_name=APP_NAME),
|
|
||||||
ops_test.model.wait_for_idle(
|
|
||||||
apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000
|
|
||||||
),
|
|
||||||
)
|
|
35
charms/heat-k8s/tests/tests.yaml
Normal file
35
charms/heat-k8s/tests/tests.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
gate_bundles:
|
||||||
|
- smoke
|
||||||
|
smoke_bundles:
|
||||||
|
- smoke
|
||||||
|
# There is no storage provider at the moment so cannot run tests.
|
||||||
|
configure:
|
||||||
|
- zaza.charm_tests.noop.setup.basic_setup
|
||||||
|
tests:
|
||||||
|
- zaza.charm_tests.noop.tests.NoopTest
|
||||||
|
tests_options:
|
||||||
|
trust:
|
||||||
|
- smoke
|
||||||
|
ignore_hard_deploy_errors:
|
||||||
|
- smoke
|
||||||
|
|
||||||
|
target_deploy_status:
|
||||||
|
traefik:
|
||||||
|
workload-status: active
|
||||||
|
workload-status-message-regex: '^$'
|
||||||
|
traefik-public:
|
||||||
|
workload-status: active
|
||||||
|
workload-status-message-regex: '^$'
|
||||||
|
rabbitmq:
|
||||||
|
workload-status: active
|
||||||
|
workload-status-message-regex: '^$'
|
||||||
|
keystone:
|
||||||
|
workload-status: active
|
||||||
|
workload-status-message-regex: '^$'
|
||||||
|
mysql:
|
||||||
|
workload-status: active
|
||||||
|
workload-status-message-regex: '^.*$'
|
||||||
|
heat:
|
||||||
|
workload-status: active
|
||||||
|
workload-status-message-regex: '^.*$'
|
||||||
|
|
15
charms/heat-k8s/tests/unit/__init__.py
Normal file
15
charms/heat-k8s/tests/unit/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2022 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.
|
||||||
|
|
||||||
|
"""Unit tests for Heat operator."""
|
@ -1,74 +0,0 @@
|
|||||||
# Copyright 2023 Felipe Reyes <felipe.reyes@canonical.com>
|
|
||||||
# See LICENSE file for licensing details.
|
|
||||||
#
|
|
||||||
# Learn more about testing at: https://juju.is/docs/sdk/testing
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import ops.testing
|
|
||||||
from charm import HeatK8SCharm
|
|
||||||
from ops.model import ActiveStatus, BlockedStatus, WaitingStatus
|
|
||||||
from ops.testing import Harness
|
|
||||||
|
|
||||||
|
|
||||||
class TestCharm(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
# Enable more accurate simulation of container networking.
|
|
||||||
# For more information, see https://juju.is/docs/sdk/testing#heading--simulate-can-connect
|
|
||||||
ops.testing.SIMULATE_CAN_CONNECT = True
|
|
||||||
self.addCleanup(setattr, ops.testing, "SIMULATE_CAN_CONNECT", False)
|
|
||||||
|
|
||||||
self.harness = Harness(HeatK8SCharm)
|
|
||||||
self.addCleanup(self.harness.cleanup)
|
|
||||||
self.harness.begin()
|
|
||||||
|
|
||||||
def test_httpbin_pebble_ready(self):
|
|
||||||
# Expected plan after Pebble ready with default config
|
|
||||||
expected_plan = {
|
|
||||||
"services": {
|
|
||||||
"httpbin": {
|
|
||||||
"override": "replace",
|
|
||||||
"summary": "httpbin",
|
|
||||||
"command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent",
|
|
||||||
"startup": "enabled",
|
|
||||||
"environment": {"GUNICORN_CMD_ARGS": "--log-level info"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
# Simulate the container coming up and emission of pebble-ready event
|
|
||||||
self.harness.container_pebble_ready("httpbin")
|
|
||||||
# Get the plan now we've run PebbleReady
|
|
||||||
updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict()
|
|
||||||
# Check we've got the plan we expected
|
|
||||||
self.assertEqual(expected_plan, updated_plan)
|
|
||||||
# Check the service was started
|
|
||||||
service = self.harness.model.unit.get_container("httpbin").get_service("httpbin")
|
|
||||||
self.assertTrue(service.is_running())
|
|
||||||
# Ensure we set an ActiveStatus with no message
|
|
||||||
self.assertEqual(self.harness.model.unit.status, ActiveStatus())
|
|
||||||
|
|
||||||
def test_config_changed_valid_can_connect(self):
|
|
||||||
# Ensure the simulated Pebble API is reachable
|
|
||||||
self.harness.set_can_connect("httpbin", True)
|
|
||||||
# Trigger a config-changed event with an updated value
|
|
||||||
self.harness.update_config({"log-level": "debug"})
|
|
||||||
# Get the plan now we've run PebbleReady
|
|
||||||
updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict()
|
|
||||||
updated_env = updated_plan["services"]["httpbin"]["environment"]
|
|
||||||
# Check the config change was effective
|
|
||||||
self.assertEqual(updated_env, {"GUNICORN_CMD_ARGS": "--log-level debug"})
|
|
||||||
self.assertEqual(self.harness.model.unit.status, ActiveStatus())
|
|
||||||
|
|
||||||
def test_config_changed_valid_cannot_connect(self):
|
|
||||||
# Trigger a config-changed event with an updated value
|
|
||||||
self.harness.update_config({"log-level": "debug"})
|
|
||||||
# Check the charm is in WaitingStatus
|
|
||||||
self.assertIsInstance(self.harness.model.unit.status, WaitingStatus)
|
|
||||||
|
|
||||||
def test_config_changed_invalid(self):
|
|
||||||
# Ensure the simulated Pebble API is reachable
|
|
||||||
self.harness.set_can_connect("httpbin", True)
|
|
||||||
# Trigger a config-changed event with an updated value
|
|
||||||
self.harness.update_config({"log-level": "foobar"})
|
|
||||||
# Check the charm is in BlockedStatus
|
|
||||||
self.assertIsInstance(self.harness.model.unit.status, BlockedStatus)
|
|
94
charms/heat-k8s/tests/unit/test_heat_charm.py
Normal file
94
charms/heat-k8s/tests/unit/test_heat_charm.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright 2021 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.
|
||||||
|
|
||||||
|
"""Unit tests for Heat operator."""
|
||||||
|
|
||||||
|
import ops_sunbeam.test_utils as test_utils
|
||||||
|
|
||||||
|
import charm
|
||||||
|
|
||||||
|
|
||||||
|
class _HeatTestOperatorCharm(charm.HeatOperatorCharm):
|
||||||
|
"""Test Operator Charm for Heat Operator."""
|
||||||
|
|
||||||
|
def __init__(self, framework):
|
||||||
|
self.seen_events = []
|
||||||
|
super().__init__(framework)
|
||||||
|
|
||||||
|
def _log_event(self, event):
|
||||||
|
self.seen_events.append(type(event).__name__)
|
||||||
|
|
||||||
|
def configure_charm(self, event):
|
||||||
|
super().configure_charm(event)
|
||||||
|
self._log_event(event)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def public_ingress_address(self):
|
||||||
|
return "heat.juju"
|
||||||
|
|
||||||
|
|
||||||
|
class TestHeatOperatorCharm(test_utils.CharmTestCase):
|
||||||
|
"""Unit tests for Heat Operator."""
|
||||||
|
|
||||||
|
PATCHES = []
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Run setup for unit tests."""
|
||||||
|
super().setUp(charm, self.PATCHES)
|
||||||
|
self.harness = test_utils.get_harness(
|
||||||
|
_HeatTestOperatorCharm, container_calls=self.container_calls
|
||||||
|
)
|
||||||
|
|
||||||
|
# clean up events that were dynamically defined,
|
||||||
|
# otherwise we get issues because they'll be redefined,
|
||||||
|
# which is not allowed.
|
||||||
|
from charms.data_platform_libs.v0.database_requires import (
|
||||||
|
DatabaseEvents,
|
||||||
|
)
|
||||||
|
|
||||||
|
for attr in (
|
||||||
|
"database_database_created",
|
||||||
|
"database_endpoints_changed",
|
||||||
|
"database_read_only_endpoints_changed",
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
delattr(DatabaseEvents, attr)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.addCleanup(self.harness.cleanup)
|
||||||
|
self.harness.begin()
|
||||||
|
|
||||||
|
def test_pebble_ready_handler(self):
|
||||||
|
"""Test pebble ready handler."""
|
||||||
|
self.assertEqual(self.harness.charm.seen_events, [])
|
||||||
|
test_utils.set_all_pebbles_ready(self.harness)
|
||||||
|
self.assertEqual(len(self.harness.charm.seen_events), 3)
|
||||||
|
|
||||||
|
def test_all_relations(self):
|
||||||
|
"""Test all integrations for operator."""
|
||||||
|
self.harness.set_leader()
|
||||||
|
test_utils.set_all_pebbles_ready(self.harness)
|
||||||
|
# this adds all the default/common relations
|
||||||
|
test_utils.add_all_relations(self.harness)
|
||||||
|
test_utils.add_complete_ingress_relation(self.harness)
|
||||||
|
|
||||||
|
setup_cmds = [["heat-manage", "db_sync"]]
|
||||||
|
for cmd in setup_cmds:
|
||||||
|
self.assertIn(cmd, self.container_calls.execute["heat-api"])
|
||||||
|
config_files = ["/etc/heat/heat.conf", "/etc/heat/api-paste.ini"]
|
||||||
|
for f in config_files:
|
||||||
|
self.check_file("heat-api", f)
|
@ -1,77 +1,84 @@
|
|||||||
# Operator charm (with zaza): tox.ini
|
# Source charm: ./tox.ini
|
||||||
|
# This file is managed centrally by release-tools and should not be modified
|
||||||
|
# within individual charm repos. See the 'global' dir contents for available
|
||||||
|
# choices of tox.ini for OpenStack Charms:
|
||||||
|
# https://github.com/openstack-charmers/release-tools
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = pep8,py3
|
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
# NOTE: Avoid build/test env pollution by not enabling sitepackages.
|
envlist = pep8,py3
|
||||||
sitepackages = False
|
sitepackages = False
|
||||||
# NOTE: Avoid false positives by not skipping missing interpreters.
|
|
||||||
skip_missing_interpreters = False
|
skip_missing_interpreters = False
|
||||||
# NOTES:
|
minversion = 3.18.0
|
||||||
# * We avoid the new dependency resolver by pinning pip < 20.3, see
|
|
||||||
# https://github.com/pypa/pip/issues/9187
|
[vars]
|
||||||
# * Pinning dependencies requires tox >= 3.2.0, see
|
src_path = {toxinidir}/src/
|
||||||
# https://tox.readthedocs.io/en/latest/config.html#conf-requires
|
tst_path = {toxinidir}/tests/
|
||||||
# * It is also necessary to pin virtualenv as a newer virtualenv would still
|
lib_path = {toxinidir}/lib/
|
||||||
# lead to fetching the latest pip in the func* tox targets, see
|
pyproject_toml = {toxinidir}/pyproject.toml
|
||||||
# https://stackoverflow.com/a/38133283
|
all_path = {[vars]src_path} {[vars]tst_path}
|
||||||
requires = pip < 20.3
|
|
||||||
virtualenv < 20.0
|
|
||||||
# NOTE: https://wiki.canonical.com/engineering/OpenStack/InstallLatestToxOnOsci
|
|
||||||
minversion = 3.2.0
|
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
setenv = VIRTUAL_ENV={envdir}
|
basepython = python3
|
||||||
PYTHONHASHSEED=0
|
setenv =
|
||||||
CHARM_DIR={envdir}
|
PYTHONPATH = {toxinidir}:{[vars]lib_path}:{[vars]src_path}
|
||||||
|
passenv =
|
||||||
|
HOME
|
||||||
|
PYTHONPATH
|
||||||
install_command =
|
install_command =
|
||||||
pip install {opts} {packages}
|
pip install {opts} {packages}
|
||||||
commands = stestr run --slowest {posargs}
|
commands = stestr run --slowest {posargs}
|
||||||
whitelist_externals =
|
allowlist_externals =
|
||||||
git
|
git
|
||||||
add-to-archive.py
|
charmcraft
|
||||||
bash
|
{toxinidir}/fetch-libs.sh
|
||||||
charmcraft
|
{toxinidir}/rename.sh
|
||||||
passenv = HOME TERM CS_* OS_* TEST_*
|
deps =
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
|
||||||
[testenv:py35]
|
[testenv:fmt]
|
||||||
basepython = python3.5
|
description = Apply coding style standards to code
|
||||||
# python3.5 is irrelevant on a focal+ charm.
|
deps =
|
||||||
commands = /bin/true
|
black
|
||||||
|
isort
|
||||||
|
commands =
|
||||||
|
isort {[vars]all_path} --skip-glob {[vars]lib_path} --skip {toxinidir}/.tox
|
||||||
|
black --config {[vars]pyproject_toml} {[vars]all_path} --exclude {[vars]lib_path}
|
||||||
|
|
||||||
[testenv:py36]
|
[testenv:build]
|
||||||
basepython = python3.6
|
basepython = python3
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps =
|
||||||
-r{toxinidir}/test-requirements.txt
|
commands =
|
||||||
|
charmcraft -v pack
|
||||||
|
{toxinidir}/rename.sh
|
||||||
|
|
||||||
[testenv:py37]
|
[testenv:fetch]
|
||||||
basepython = python3.7
|
basepython = python3
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps =
|
||||||
-r{toxinidir}/test-requirements.txt
|
commands =
|
||||||
|
{toxinidir}/fetch-libs.sh
|
||||||
[testenv:py38]
|
|
||||||
basepython = python3.8
|
|
||||||
deps = -r{toxinidir}/requirements.txt
|
|
||||||
-r{toxinidir}/test-requirements.txt
|
|
||||||
|
|
||||||
[testenv:py3]
|
[testenv:py3]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps =
|
||||||
-r{toxinidir}/test-requirements.txt
|
{[testenv]deps}
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:py38]
|
||||||
basepython = python3
|
basepython = python3.8
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = {[testenv:py3]deps}
|
||||||
-r{toxinidir}/test-requirements.txt
|
|
||||||
commands = flake8 {posargs} src unit_tests tests
|
[testenv:py39]
|
||||||
|
basepython = python3.9
|
||||||
|
deps = {[testenv:py3]deps}
|
||||||
|
|
||||||
|
[testenv:py310]
|
||||||
|
basepython = python3.10
|
||||||
|
deps = {[testenv:py3]deps}
|
||||||
|
|
||||||
[testenv:cover]
|
[testenv:cover]
|
||||||
# Technique based heavily upon
|
|
||||||
# https://github.com/openstack/nova/blob/master/tox.ini
|
|
||||||
basepython = python3
|
basepython = python3
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = {[testenv:py3]deps}
|
||||||
-r{toxinidir}/test-requirements.txt
|
|
||||||
setenv =
|
setenv =
|
||||||
{[testenv]setenv}
|
{[testenv]setenv}
|
||||||
PYTHON=coverage run
|
PYTHON=coverage run
|
||||||
@ -83,6 +90,66 @@ commands =
|
|||||||
coverage xml -o cover/coverage.xml
|
coverage xml -o cover/coverage.xml
|
||||||
coverage report
|
coverage report
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
description = Alias for lint
|
||||||
|
deps = {[testenv:lint]deps}
|
||||||
|
commands = {[testenv:lint]commands}
|
||||||
|
|
||||||
|
[testenv:lint]
|
||||||
|
description = Check code against coding style standards
|
||||||
|
deps =
|
||||||
|
black
|
||||||
|
flake8<6 # Pin version until https://github.com/savoirfairelinux/flake8-copyright/issues/19 is merged
|
||||||
|
flake8-docstrings
|
||||||
|
flake8-copyright
|
||||||
|
flake8-builtins
|
||||||
|
pyproject-flake8
|
||||||
|
pep8-naming
|
||||||
|
isort
|
||||||
|
codespell
|
||||||
|
commands =
|
||||||
|
codespell {[vars]all_path}
|
||||||
|
# pflake8 wrapper supports config from pyproject.toml
|
||||||
|
pflake8 --exclude {[vars]lib_path} --config {toxinidir}/pyproject.toml {[vars]all_path}
|
||||||
|
isort --check-only --diff {[vars]all_path} --skip-glob {[vars]lib_path}
|
||||||
|
black --config {[vars]pyproject_toml} --check --diff {[vars]all_path} --exclude {[vars]lib_path}
|
||||||
|
|
||||||
|
[testenv:func-noop]
|
||||||
|
basepython = python3
|
||||||
|
deps =
|
||||||
|
git+https://github.com/openstack-charmers/zaza.git@libjuju-3.1#egg=zaza
|
||||||
|
git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
|
||||||
|
git+https://opendev.org/openstack/tempest.git#egg=tempest
|
||||||
|
commands =
|
||||||
|
functest-run-suite --help
|
||||||
|
|
||||||
|
[testenv:func]
|
||||||
|
basepython = python3
|
||||||
|
deps = {[testenv:func-noop]deps}
|
||||||
|
commands =
|
||||||
|
functest-run-suite --keep-model
|
||||||
|
|
||||||
|
[testenv:func-smoke]
|
||||||
|
basepython = python3
|
||||||
|
deps = {[testenv:func-noop]deps}
|
||||||
|
setenv =
|
||||||
|
TEST_MODEL_SETTINGS = automatically-retry-hooks=true
|
||||||
|
TEST_MAX_RESOLVE_COUNT = 5
|
||||||
|
commands =
|
||||||
|
functest-run-suite --keep-model --smoke
|
||||||
|
|
||||||
|
[testenv:func-dev]
|
||||||
|
basepython = python3
|
||||||
|
deps = {[testenv:func-noop]deps}
|
||||||
|
commands =
|
||||||
|
functest-run-suite --keep-model --dev
|
||||||
|
|
||||||
|
[testenv:func-target]
|
||||||
|
basepython = python3
|
||||||
|
deps = {[testenv:func-noop]deps}
|
||||||
|
commands =
|
||||||
|
functest-run-suite --keep-model --bundle {posargs}
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
branch = True
|
branch = True
|
||||||
concurrency = multiprocessing
|
concurrency = multiprocessing
|
||||||
@ -91,44 +158,8 @@ source =
|
|||||||
.
|
.
|
||||||
omit =
|
omit =
|
||||||
.tox/*
|
.tox/*
|
||||||
*/charmhelpers/*
|
tests/*
|
||||||
unit_tests/*
|
src/templates/*
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
basepython = python3
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
[testenv:build]
|
|
||||||
basepython = python3
|
|
||||||
deps = -r{toxinidir}/build-requirements.txt
|
|
||||||
commands =
|
|
||||||
charmcraft build
|
|
||||||
|
|
||||||
[testenv:func-noop]
|
|
||||||
basepython = python3
|
|
||||||
commands =
|
|
||||||
functest-run-suite --help
|
|
||||||
|
|
||||||
[testenv:func]
|
|
||||||
basepython = python3
|
|
||||||
commands =
|
|
||||||
functest-run-suite --keep-model
|
|
||||||
|
|
||||||
[testenv:func-smoke]
|
|
||||||
basepython = python3
|
|
||||||
commands =
|
|
||||||
functest-run-suite --keep-model --smoke
|
|
||||||
|
|
||||||
[testenv:func-dev]
|
|
||||||
basepython = python3
|
|
||||||
commands =
|
|
||||||
functest-run-suite --keep-model --dev
|
|
||||||
|
|
||||||
[testenv:func-target]
|
|
||||||
basepython = python3
|
|
||||||
commands =
|
|
||||||
functest-run-suite --keep-model --bundle {posargs}
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# Ignore E902 because the unit_tests directory is missing in the built charm.
|
ignore=E226,W504
|
||||||
ignore = E402,E226,E902
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user