Start all services in a container
A container service layer is a dictionary of services so there may be more than once service defintion. Account for this by iterating of the services and starting each one in turn. Change-Id: I4cba2e0cda156a6852e71059b0dc0cb1948ce9e6
This commit is contained in:
parent
017e56809d
commit
8ea8f2855a
@ -256,6 +256,15 @@ class PebbleHandler(ops.charm.Object):
|
|||||||
else:
|
else:
|
||||||
self.status = ''
|
self.status = ''
|
||||||
|
|
||||||
|
def _start_all(self) -> None:
|
||||||
|
"""Start services in container."""
|
||||||
|
container = self.charm.unit.get_container(self.container_name)
|
||||||
|
services = container.get_services()
|
||||||
|
for service_name, service in services.items():
|
||||||
|
if service.is_running():
|
||||||
|
container.stop(service_name)
|
||||||
|
container.start(service_name)
|
||||||
|
|
||||||
|
|
||||||
class ServicePebbleHandler(PebbleHandler):
|
class ServicePebbleHandler(PebbleHandler):
|
||||||
"""Container handler for containers which manage a service."""
|
"""Container handler for containers which manage a service."""
|
||||||
@ -272,7 +281,7 @@ class ServicePebbleHandler(PebbleHandler):
|
|||||||
self._state.service_ready = True
|
self._state.service_ready = True
|
||||||
|
|
||||||
def start_service(self) -> None:
|
def start_service(self) -> None:
|
||||||
"""Start service in container."""
|
"""Check and start services in container."""
|
||||||
container = self.charm.unit.get_container(self.container_name)
|
container = self.charm.unit.get_container(self.container_name)
|
||||||
if not container:
|
if not container:
|
||||||
logger.debug(f'{self.container_name} container is not ready. '
|
logger.debug(f'{self.container_name} container is not ready. '
|
||||||
@ -283,10 +292,7 @@ class ServicePebbleHandler(PebbleHandler):
|
|||||||
self.service_name,
|
self.service_name,
|
||||||
self.get_layer(),
|
self.get_layer(),
|
||||||
combine=True)
|
combine=True)
|
||||||
service = container.get_service(self.service_name)
|
self._start_all()
|
||||||
if service.is_running():
|
|
||||||
container.stop(self.service_name)
|
|
||||||
container.start(self.service_name)
|
|
||||||
|
|
||||||
|
|
||||||
class WSGIPebbleHandler(PebbleHandler):
|
class WSGIPebbleHandler(PebbleHandler):
|
||||||
@ -316,7 +322,7 @@ class WSGIPebbleHandler(PebbleHandler):
|
|||||||
self.wsgi_service_name = wsgi_service_name
|
self.wsgi_service_name = wsgi_service_name
|
||||||
|
|
||||||
def start_wsgi(self) -> None:
|
def start_wsgi(self) -> None:
|
||||||
"""Start WSGI service."""
|
"""Check and start services in container."""
|
||||||
container = self.charm.unit.get_container(self.container_name)
|
container = self.charm.unit.get_container(self.container_name)
|
||||||
if not container:
|
if not container:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@ -329,11 +335,7 @@ class WSGIPebbleHandler(PebbleHandler):
|
|||||||
self.service_name,
|
self.service_name,
|
||||||
self.get_layer(),
|
self.get_layer(),
|
||||||
combine=True)
|
combine=True)
|
||||||
service = container.get_service(self.wsgi_service_name)
|
self._start_all()
|
||||||
if service.is_running():
|
|
||||||
container.stop(self.wsgi_service_name)
|
|
||||||
|
|
||||||
container.start(self.wsgi_service_name)
|
|
||||||
|
|
||||||
def start_service(self) -> None:
|
def start_service(self) -> None:
|
||||||
"""Start the service."""
|
"""Start the service."""
|
||||||
|
@ -25,6 +25,7 @@ import sys
|
|||||||
import typing
|
import typing
|
||||||
import unittest
|
import unittest
|
||||||
import collections
|
import collections
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from mock import MagicMock, Mock, patch
|
from mock import MagicMock, Mock, patch
|
||||||
|
|
||||||
@ -140,11 +141,23 @@ class ContainerCalls:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Init container calls."""
|
"""Init container calls."""
|
||||||
|
self.start = collections.defaultdict(list)
|
||||||
self.push = collections.defaultdict(list)
|
self.push = collections.defaultdict(list)
|
||||||
self.pull = collections.defaultdict(list)
|
self.pull = collections.defaultdict(list)
|
||||||
self.execute = collections.defaultdict(list)
|
self.execute = collections.defaultdict(list)
|
||||||
self.remove_path = collections.defaultdict(list)
|
self.remove_path = collections.defaultdict(list)
|
||||||
|
|
||||||
|
def add_start(self, container_name: str, call: typing.Dict) -> None:
|
||||||
|
"""Log a start call."""
|
||||||
|
self.start[container_name].append(call)
|
||||||
|
|
||||||
|
def started_services(self, container_name: str) -> List:
|
||||||
|
"""Distinct unordered list of services that were started."""
|
||||||
|
return list(set([
|
||||||
|
svc
|
||||||
|
for svc_list in self.start[container_name]
|
||||||
|
for svc in svc_list]))
|
||||||
|
|
||||||
def add_push(self, container_name: str, call: typing.Dict) -> None:
|
def add_push(self, container_name: str, call: typing.Dict) -> None:
|
||||||
"""Log a push call."""
|
"""Log a push call."""
|
||||||
self.push[container_name].append(call)
|
self.push[container_name].append(call)
|
||||||
@ -607,6 +620,14 @@ def get_harness(
|
|||||||
process_mock.wait_output.return_value = ('', None)
|
process_mock.wait_output.return_value = ('', None)
|
||||||
return process_mock
|
return process_mock
|
||||||
|
|
||||||
|
def start_services(
|
||||||
|
self, services: List[str], timeout: float = 30.0,
|
||||||
|
delay: float = 0.1,) -> None:
|
||||||
|
"""Record start service events."""
|
||||||
|
container_calls.add_start(
|
||||||
|
self.container_name,
|
||||||
|
services)
|
||||||
|
|
||||||
class _OSTestingModelBackend(_TestingModelBackend):
|
class _OSTestingModelBackend(_TestingModelBackend):
|
||||||
def get_pebble(self, socket_path: str) -> _OSTestingPebbleClient:
|
def get_pebble(self, socket_path: str) -> _OSTestingPebbleClient:
|
||||||
"""Get the testing pebble client."""
|
"""Get the testing pebble client."""
|
||||||
|
@ -24,11 +24,13 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import ops.framework
|
import ops.framework
|
||||||
|
from typing import List
|
||||||
|
|
||||||
sys.path.append("unit_tests/lib") # noqa
|
sys.path.append("unit_tests/lib") # noqa
|
||||||
sys.path.append("src") # noqa
|
sys.path.append("src") # noqa
|
||||||
|
|
||||||
import ops_sunbeam.charm as sunbeam_charm
|
import ops_sunbeam.charm as sunbeam_charm
|
||||||
|
import ops_sunbeam.container_handlers as sunbeam_chandlers
|
||||||
|
|
||||||
CHARM_CONFIG = """
|
CHARM_CONFIG = """
|
||||||
options:
|
options:
|
||||||
@ -255,3 +257,48 @@ class MyAPICharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
def healthcheck_http_url(self) -> str:
|
def healthcheck_http_url(self) -> str:
|
||||||
"""Healthcheck HTTP URL for the service."""
|
"""Healthcheck HTTP URL for the service."""
|
||||||
return f'http://localhost:{self.default_public_ingress_port}/v3'
|
return f'http://localhost:{self.default_public_ingress_port}/v3'
|
||||||
|
|
||||||
|
|
||||||
|
class MultiSvcPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||||
|
"""Test pebble handler for multi service charm."""
|
||||||
|
|
||||||
|
def get_layer(self) -> dict:
|
||||||
|
"""Glance API service pebble layer.
|
||||||
|
|
||||||
|
:returns: pebble layer configuration for glance api service
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"summary": f"{self.service_name} layer",
|
||||||
|
"description": "pebble config layer for glance api service",
|
||||||
|
"services": {
|
||||||
|
f"{self.service_name}": {
|
||||||
|
"override": "replace",
|
||||||
|
"summary": f"{self.service_name} standalone",
|
||||||
|
"command": "/usr/bin/glance-api",
|
||||||
|
"startup": "disabled",
|
||||||
|
},
|
||||||
|
"apache forwarder": {
|
||||||
|
"override": "replace",
|
||||||
|
"summary": "apache",
|
||||||
|
"command": "/usr/sbin/apache2ctl -DFOREGROUND",
|
||||||
|
"startup": "disabled",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestMultiSvcCharm(MyAPICharm):
|
||||||
|
"""Test class of multi service charm."""
|
||||||
|
|
||||||
|
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
|
||||||
|
"""Pebble handlers for the service."""
|
||||||
|
return [
|
||||||
|
MultiSvcPebbleHandler(
|
||||||
|
self,
|
||||||
|
self.service_name,
|
||||||
|
self.service_name,
|
||||||
|
self.container_configs,
|
||||||
|
self.template_dir,
|
||||||
|
self.openstack_release,
|
||||||
|
self.configure_charm
|
||||||
|
)]
|
||||||
|
@ -73,21 +73,18 @@ class TestOSBaseOperatorCharm(test_utils.CharmTestCase):
|
|||||||
self.harness.charm.relation_handlers_ready())
|
self.harness.charm.relation_handlers_ready())
|
||||||
|
|
||||||
|
|
||||||
class TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
class _TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
||||||
"""Test for the OSBaseOperatorAPICharm class."""
|
"""Test for the OSBaseOperatorAPICharm class."""
|
||||||
|
|
||||||
PATCHES = []
|
PATCHES = []
|
||||||
|
|
||||||
@mock.patch(
|
def setUp(self, charm_to_test: test_charms.MyAPICharm) -> None:
|
||||||
'charms.observability_libs.v0.kubernetes_service_patch.'
|
|
||||||
'KubernetesServicePatch')
|
|
||||||
def setUp(self, mock_svc_patch: mock.patch) -> None:
|
|
||||||
"""Charm test class setup."""
|
"""Charm test class setup."""
|
||||||
self.container_calls = test_utils.ContainerCalls()
|
self.container_calls = test_utils.ContainerCalls()
|
||||||
|
|
||||||
super().setUp(sunbeam_charm, self.PATCHES)
|
super().setUp(sunbeam_charm, self.PATCHES)
|
||||||
self.harness = test_utils.get_harness(
|
self.harness = test_utils.get_harness(
|
||||||
test_charms.MyAPICharm,
|
charm_to_test,
|
||||||
test_charms.API_CHARM_METADATA,
|
test_charms.API_CHARM_METADATA,
|
||||||
self.container_calls,
|
self.container_calls,
|
||||||
charm_config=test_charms.CHARM_CONFIG,
|
charm_config=test_charms.CHARM_CONFIG,
|
||||||
@ -116,6 +113,17 @@ class TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
|||||||
"""Set pebble ready event."""
|
"""Set pebble ready event."""
|
||||||
self.harness.container_pebble_ready('my-service')
|
self.harness.container_pebble_ready('my-service')
|
||||||
|
|
||||||
|
|
||||||
|
class TestOSBaseOperatorAPICharm(_TestOSBaseOperatorAPICharm):
|
||||||
|
"""Test Charm with services."""
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'charms.observability_libs.v0.kubernetes_service_patch.'
|
||||||
|
'KubernetesServicePatch')
|
||||||
|
def setUp(self, mock_svc_patch: mock.patch) -> None:
|
||||||
|
"""Run test class setup."""
|
||||||
|
super().setUp(test_charms.MyAPICharm)
|
||||||
|
|
||||||
def test_write_config(self) -> None:
|
def test_write_config(self) -> None:
|
||||||
"""Test when charm is ready configs are written correctly."""
|
"""Test when charm is ready configs are written correctly."""
|
||||||
test_utils.add_complete_ingress_relation(self.harness)
|
test_utils.add_complete_ingress_relation(self.harness)
|
||||||
@ -150,6 +158,20 @@ class TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
|||||||
group='root',
|
group='root',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_start_services(self) -> None:
|
||||||
|
"""Test service is started."""
|
||||||
|
test_utils.add_complete_ingress_relation(self.harness)
|
||||||
|
self.harness.set_leader()
|
||||||
|
test_utils.add_complete_peer_relation(self.harness)
|
||||||
|
self.set_pebble_ready()
|
||||||
|
self.harness.charm.leader_set({'foo': 'bar'})
|
||||||
|
test_utils.add_api_relations(self.harness)
|
||||||
|
test_utils.add_complete_cloud_credentials_relation(self.harness)
|
||||||
|
self.harness.set_can_connect('my-service', True)
|
||||||
|
self.assertEqual(
|
||||||
|
self.container_calls.started_services('my-service'),
|
||||||
|
['wsgi-my-service'])
|
||||||
|
|
||||||
def test__on_database_changed(self) -> None:
|
def test__on_database_changed(self) -> None:
|
||||||
"""Test database is requested."""
|
"""Test database is requested."""
|
||||||
rel_id = self.harness.add_relation('peers', 'my-service')
|
rel_id = self.harness.add_relation('peers', 'my-service')
|
||||||
@ -300,3 +322,28 @@ class TestOSBaseOperatorAPICharm(test_utils.CharmTestCase):
|
|||||||
self.harness, ingress_rel_id, 'public')
|
self.harness, ingress_rel_id, 'public')
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
self.harness.charm.relation_handlers_ready())
|
self.harness.charm.relation_handlers_ready())
|
||||||
|
|
||||||
|
|
||||||
|
class TestOSBaseOperatorMultiSVCAPICharm(_TestOSBaseOperatorAPICharm):
|
||||||
|
"""Test Charm with multiple services."""
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'charms.observability_libs.v0.kubernetes_service_patch.'
|
||||||
|
'KubernetesServicePatch')
|
||||||
|
def setUp(self, mock_svc_patch: mock.patch) -> None:
|
||||||
|
"""Charm test class setip."""
|
||||||
|
super().setUp(test_charms.TestMultiSvcCharm)
|
||||||
|
|
||||||
|
def test_start_services(self) -> None:
|
||||||
|
"""Test multiple services are started."""
|
||||||
|
test_utils.add_complete_ingress_relation(self.harness)
|
||||||
|
self.harness.set_leader()
|
||||||
|
test_utils.add_complete_peer_relation(self.harness)
|
||||||
|
self.set_pebble_ready()
|
||||||
|
self.harness.charm.leader_set({'foo': 'bar'})
|
||||||
|
test_utils.add_api_relations(self.harness)
|
||||||
|
test_utils.add_complete_cloud_credentials_relation(self.harness)
|
||||||
|
self.harness.set_can_connect('my-service', True)
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(self.container_calls.started_services('my-service')),
|
||||||
|
sorted(['apache forwarder', 'my-service']))
|
||||||
|
Loading…
Reference in New Issue
Block a user