Start of cookie cutter
This commit is contained in:
parent
e6e421fa3d
commit
76034ff83a
@ -1,5 +1,30 @@
|
|||||||
Advanced Sunbeam OpenStack
|
========================================
|
||||||
|
Advanced Sunbeam OpenStack Documentation
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Tuturials
|
||||||
|
#########
|
||||||
|
|
||||||
|
`Writing an OpenStack API charm with ASO <writing-OS-API-charm.rst>`_.
|
||||||
|
|
||||||
|
How-Tos
|
||||||
|
#######
|
||||||
|
|
||||||
|
|
||||||
|
`How-To write a pebble handler <howto-pebble-handler.rst>`_.
|
||||||
|
|
||||||
|
`How-To write a relation handler <howto-relation-handler.rst>`_.
|
||||||
|
|
||||||
|
`How-To write a charm context <howto-config-context.rst>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Reference
|
||||||
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Concepts
|
||||||
|
########
|
||||||
|
|
||||||
`How to Write a charm with ASO <howto-write-charm.rst>`_.
|
|
||||||
|
|
||||||
`ASO Concepts <concepts.rst>`_.
|
`ASO Concepts <concepts.rst>`_.
|
||||||
|
5
ops-sunbeam/aso-charm-init.sh
Executable file
5
ops-sunbeam/aso-charm-init.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
[ -e .tox/cookie/bin/activate ] || tox -e cookie
|
||||||
|
source .tox/cookie/bin/activate
|
||||||
|
shared_code/aso-charm-init.py $@
|
@ -113,7 +113,7 @@ Charms
|
|||||||
|
|
||||||
ASO currently provides two base classes to choose from when writing a charm.
|
ASO currently provides two base classes to choose from when writing a charm.
|
||||||
The first is `OSBaseOperatorCharm` and the second, which is derived from the
|
The first is `OSBaseOperatorCharm` and the second, which is derived from the
|
||||||
first, `OSBaseOperatorAPICharm`.
|
first, `OSBaseOperatorAPICharm`.
|
||||||
|
|
||||||
The base classes setup a default set of relation handlers (based on what
|
The base classes setup a default set of relation handlers (based on what
|
||||||
relations are present in the charm metadata) and default container handlers.
|
relations are present in the charm metadata) and default container handlers.
|
||||||
|
1
ops-sunbeam/cookie-requirements.txt
Normal file
1
ops-sunbeam/cookie-requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
cookiecutter
|
55
ops-sunbeam/howto-config-context.rst
Normal file
55
ops-sunbeam/howto-config-context.rst
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
=============================
|
||||||
|
How-To Write a config context
|
||||||
|
=============================
|
||||||
|
|
||||||
|
A config context is an additional context that is passed to the template
|
||||||
|
renderer in its own namespace. They are usually useful when some logic
|
||||||
|
needs to be applied to user supplied charm configuration. The context
|
||||||
|
has access to the charm object.
|
||||||
|
|
||||||
|
Below is an example which applies logic to the charm config as well as
|
||||||
|
collecting the application name to constuct the context.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
|
||||||
|
class CinderCephConfigurationContext(ConfigContext):
|
||||||
|
"""Cinder Ceph configuration context."""
|
||||||
|
|
||||||
|
def context(self) -> None:
|
||||||
|
"""Cinder Ceph configuration context."""
|
||||||
|
config = self.charm.model.config.get
|
||||||
|
data_pool_name = config('rbd-pool-name') or self.charm.app.name
|
||||||
|
if config('pool-type') == "erasure-coded":
|
||||||
|
pool_name = (
|
||||||
|
config('ec-rbd-metadata-pool') or
|
||||||
|
f"{data_pool_name}-metadata"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pool_name = data_pool_name
|
||||||
|
backend_name = config('volume-backend-name') or self.charm.app.name
|
||||||
|
return {
|
||||||
|
'cluster_name': self.charm.app.name,
|
||||||
|
'rbd_pool': pool_name,
|
||||||
|
'rbd_user': self.charm.app.name,
|
||||||
|
'backend_name': backend_name,
|
||||||
|
'backend_availability_zone': config('backend-availability-zone'),
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuring Charm to use custom config context
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The charm can append the new context onto those provided by the base class.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class MyCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||||
|
"""Charm the service."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config_contexts(self) -> List[sunbeam_ctxts.ConfigContext]:
|
||||||
|
"""Configuration contexts for the operator."""
|
||||||
|
contexts = super().config_contexts
|
||||||
|
contexts.append(
|
||||||
|
sunbeam_ctxts.CinderCephConfigurationContext(self, "cinder_ceph"))
|
||||||
|
return contexts
|
132
ops-sunbeam/howto-pebble-handler.rst
Normal file
132
ops-sunbeam/howto-pebble-handler.rst
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
=============================
|
||||||
|
How-To Write a pebble handler
|
||||||
|
=============================
|
||||||
|
|
||||||
|
A pebble handler sits between a charm and a container it manages. A pebble
|
||||||
|
handler presents the charm with a consistent method of interaction with
|
||||||
|
the container. For example the charm can query the handler to check config
|
||||||
|
has been rendered and services started. It can call the `execute` method
|
||||||
|
to run commands in the container or call `write_config` to render the
|
||||||
|
defined files into the container.
|
||||||
|
|
||||||
|
Common Pebble handler changes
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
ASO provides a pebble handler base classes which provide the starting point
|
||||||
|
for writing a new handler. If the container runs a service then the
|
||||||
|
`ServicePebbleHandler` should be used. If the container does not provide a
|
||||||
|
service (perhaps its just an environment for executing commands that affact
|
||||||
|
other container) then `PebbleHandler` should be used.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import container_handlers
|
||||||
|
|
||||||
|
class MyServicePebbleHandler(container_handlers.ServicePebbleHandler):
|
||||||
|
"""Manage MyService Container."""
|
||||||
|
|
||||||
|
The handlers can create directories in the container once the pebble is
|
||||||
|
available.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
@property
|
||||||
|
def directories(self) -> List[sunbeam_chandlers.ContainerDir]:
|
||||||
|
"""Directories to create in container."""
|
||||||
|
return [
|
||||||
|
sunbeam_chandlers.ContainerDir(
|
||||||
|
'/var/log/my-service',
|
||||||
|
'root',
|
||||||
|
'root'),
|
||||||
|
|
||||||
|
In addition to directories the handler can list configuration files which need
|
||||||
|
to be rendered into the container. These will be rendered as templates using
|
||||||
|
all available contexts.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def default_container_configs(
|
||||||
|
self
|
||||||
|
) -> List[sunbeam_core.ContainerConfigFile]:
|
||||||
|
"""Files to render into containers."""
|
||||||
|
return [
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
'/etc/mysvc/mvsvc.conf',
|
||||||
|
'root',
|
||||||
|
'root')]
|
||||||
|
|
||||||
|
If a service should be running in the conainer the handler specifies the
|
||||||
|
layer describing the service that will be passed to pebble.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def get_layer(self) -> dict:
|
||||||
|
"""Pebble configuration layer for MyService service."""
|
||||||
|
return {
|
||||||
|
"summary": "My service",
|
||||||
|
"description": "Pebble config layer for MyService",
|
||||||
|
"services": {
|
||||||
|
'my_svc': {
|
||||||
|
"override": "replace",
|
||||||
|
"summary": "My Super Service",
|
||||||
|
"command": "/usr/bin/my-svc",
|
||||||
|
"startup": "disabled",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Advanced Pebble handler changes
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default the pebble handler is the observer of pebble events. If this
|
||||||
|
behaviour needs to be altered then `setup_pebble_handler` method can be
|
||||||
|
changed.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def setup_pebble_handler(self) -> None:
|
||||||
|
"""Configure handler for pebble ready event."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
Or perhaps it is ok for the pebble handler to observe the event but a
|
||||||
|
different reaction is required. In this case the method associated
|
||||||
|
with the event can be overridden.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def _on_service_pebble_ready(
|
||||||
|
self, event: ops.charm.PebbleReadyEvent
|
||||||
|
) -> None:
|
||||||
|
"""Handle pebble ready event."""
|
||||||
|
container = event.workload
|
||||||
|
container.add_layer(self.service_name, self.get_layer(), combine=True)
|
||||||
|
self.execute(["run", "special", "command"])
|
||||||
|
logger.debug(f"Plan: {container.get_plan()}")
|
||||||
|
self.ready = True
|
||||||
|
self._state.pebble_ready = True
|
||||||
|
self.charm.configure_charm(event)
|
||||||
|
|
||||||
|
Configuring Charm to use custom pebble handler
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The charms `get_pebble_handlers` method dictates which pebble handlers are used.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class MyCharmCharm(NeutronOperatorCharm):
|
||||||
|
|
||||||
|
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
|
||||||
|
"""Pebble handlers for the service."""
|
||||||
|
return [
|
||||||
|
MyServicePebbleHandler(
|
||||||
|
self,
|
||||||
|
'my-server-container',
|
||||||
|
self.service_name,
|
||||||
|
self.container_configs,
|
||||||
|
self.template_dir,
|
||||||
|
self.openstack_release,
|
||||||
|
self.configure_charm,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
142
ops-sunbeam/howto-relation-handler.rst
Normal file
142
ops-sunbeam/howto-relation-handler.rst
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
===============================
|
||||||
|
How-To Write a relation handler
|
||||||
|
===============================
|
||||||
|
|
||||||
|
A relation handler gives the charm a consistent method of interacting with
|
||||||
|
relation interfaces. It can also encapsulate common interface tasks, this
|
||||||
|
removes the need for duplicate code across multiple charms.
|
||||||
|
|
||||||
|
This how-to will walk through the steps to write a database relation handler
|
||||||
|
for the requires side.
|
||||||
|
|
||||||
|
In this database interface the database charm expects the client to provide the name
|
||||||
|
of the database(s) to be created. To model this the relation handler will require
|
||||||
|
the charm to specify the database name(s) when the class is instantiated
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
|
||||||
|
class DBHandler(RelationHandler):
|
||||||
|
"""Handler for DB relations."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
charm: ops.charm.CharmBase,
|
||||||
|
relation_name: str,
|
||||||
|
callback_f: Callable,
|
||||||
|
databases: List[str] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Run constructor."""
|
||||||
|
self.databases = databases
|
||||||
|
super().__init__(charm, relation_name, callback_f)
|
||||||
|
|
||||||
|
The handler initialises the interface with the database names and also sets up
|
||||||
|
an observer for relation changed events.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def setup_event_handler(self) -> ops.charm.Object:
|
||||||
|
"""Configure event handlers for a MySQL relation."""
|
||||||
|
logger.debug("Setting up DB event handler")
|
||||||
|
# Lazy import to ensure this lib is only required if the charm
|
||||||
|
# has this relation.
|
||||||
|
import charms.sunbeam_mysql_k8s.v0.mysql as mysql
|
||||||
|
db = mysql.MySQLConsumer(
|
||||||
|
self.charm, self.relation_name, databases=self.databases
|
||||||
|
)
|
||||||
|
_rname = self.relation_name.replace("-", "_")
|
||||||
|
db_relation_event = getattr(
|
||||||
|
self.charm.on, f"{_rname}_relation_changed"
|
||||||
|
)
|
||||||
|
self.framework.observe(db_relation_event, self._on_database_changed)
|
||||||
|
return db
|
||||||
|
|
||||||
|
The method run when tha changed event is seen checks whether all required data
|
||||||
|
has been provided. If it is then it calls back to the charm, if not then no
|
||||||
|
action is taken.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def _on_database_changed(self, event: ops.framework.EventBase) -> None:
|
||||||
|
"""Handle database change events."""
|
||||||
|
databases = self.interface.databases()
|
||||||
|
logger.info(f"Received databases: {databases}")
|
||||||
|
if not self.ready:
|
||||||
|
return
|
||||||
|
self.callback_f(event)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ready(self) -> bool:
|
||||||
|
"""Whether the handler is ready for use."""
|
||||||
|
try:
|
||||||
|
# Nothing to wait for
|
||||||
|
return bool(self.interface.databases())
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
The `ready` property is common across all handlers and allows the charm to
|
||||||
|
check the state of any relation in a consistent way.
|
||||||
|
|
||||||
|
The relation handlers also provide a context which can be used when rendering
|
||||||
|
templates. ASO places each relation context in its own namespace.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
def context(self) -> dict:
|
||||||
|
"""Context containing database connection data."""
|
||||||
|
try:
|
||||||
|
databases = self.interface.databases()
|
||||||
|
except AttributeError:
|
||||||
|
return {}
|
||||||
|
if not databases:
|
||||||
|
return {}
|
||||||
|
ctxt = {}
|
||||||
|
conn_data = {
|
||||||
|
"database_host": self.interface.credentials().get("address"),
|
||||||
|
"database_password": self.interface.credentials().get("password"),
|
||||||
|
"database_user": self.interface.credentials().get("username"),
|
||||||
|
"database_type": "mysql+pymysql",
|
||||||
|
}
|
||||||
|
|
||||||
|
for db in self.interface.databases():
|
||||||
|
ctxt[db] = {"database": db}
|
||||||
|
ctxt[db].update(conn_data)
|
||||||
|
connection = (
|
||||||
|
"{database_type}://{database_user}:{database_password}"
|
||||||
|
"@{database_host}/{database}")
|
||||||
|
if conn_data.get("database_ssl_ca"):
|
||||||
|
connection = connection + "?ssl_ca={database_ssl_ca}"
|
||||||
|
if conn_data.get("database_ssl_cert"):
|
||||||
|
connection = connection + (
|
||||||
|
"&ssl_cert={database_ssl_cert}"
|
||||||
|
"&ssl_key={database_ssl_key}")
|
||||||
|
ctxt[db]["connection"] = str(connection.format(
|
||||||
|
**ctxt[db]))
|
||||||
|
return ctxt
|
||||||
|
|
||||||
|
Configuring Charm to use custom relation handler
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The base class will add the default relation handlers for any interfaces
|
||||||
|
which do not yet have a handler. Therefore the custom handler is added to
|
||||||
|
the list and then passed to the super method. The base charm class will
|
||||||
|
see a handler already exists for shared-db and not add the default one.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class MyCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||||
|
"""Charm the service."""
|
||||||
|
|
||||||
|
def get_relation_handlers(self, handlers=None) -> List[
|
||||||
|
sunbeam_rhandlers.RelationHandler]:
|
||||||
|
"""Relation handlers for the service."""
|
||||||
|
handlers = handlers or []
|
||||||
|
if self.can_add_handler("shared-db", handlers):
|
||||||
|
self.db = sunbeam_rhandlers.DBHandler(
|
||||||
|
self, "shared-db", self.configure_charm, self.databases
|
||||||
|
)
|
||||||
|
handlers.append(self.db)
|
||||||
|
handlers = super().get_relation_handlers(handlers)
|
||||||
|
return handlers
|
||||||
|
|
||||||
|
|
@ -1,432 +0,0 @@
|
|||||||
=============
|
|
||||||
New API Charm
|
|
||||||
=============
|
|
||||||
|
|
||||||
The example below will walk through the creation of a basic API charm for the
|
|
||||||
OpenStack `Glance <https://wiki.openstack.org/wiki/Glance>`__ service designed
|
|
||||||
to run on kubernetes.
|
|
||||||
|
|
||||||
Create the skeleton charm
|
|
||||||
=========================
|
|
||||||
|
|
||||||
Prerequisite
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The charmcraft tool builds a skeleton charm.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
mkdir charm-glance-operator
|
|
||||||
cd charm-glance-operator/
|
|
||||||
charmcraft init --name sunbeam-glance-operator
|
|
||||||
|
|
||||||
Some useful files can be found in ASO so that needs
|
|
||||||
to be available locally
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
git clone https://github.com/openstack-charmers/advanced-sunbeam-openstack
|
|
||||||
|
|
||||||
Amend charmcraft file to include git at build time:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
parts:
|
|
||||||
charm:
|
|
||||||
build-packages:
|
|
||||||
- git
|
|
||||||
|
|
||||||
Add Metadata
|
|
||||||
============
|
|
||||||
|
|
||||||
The first job is to write the metadata yaml.
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
# Copyright 2021 Canonical Ltd
|
|
||||||
# See LICENSE file for licensing details.
|
|
||||||
name: sunbeam-glance-operator
|
|
||||||
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
|
|
||||||
summary: OpenStack Image Registry and Delivery Service
|
|
||||||
description: |
|
|
||||||
The Glance project provides an image registration and discovery service
|
|
||||||
and an image delivery service. These services are used in conjunction
|
|
||||||
by Nova to deliver images.
|
|
||||||
version: 3
|
|
||||||
bases:
|
|
||||||
- name: ubuntu
|
|
||||||
channel: 20.04/stable
|
|
||||||
tags:
|
|
||||||
- openstack
|
|
||||||
- storage
|
|
||||||
- misc
|
|
||||||
|
|
||||||
containers:
|
|
||||||
glance-api:
|
|
||||||
resource: glance-api-image
|
|
||||||
|
|
||||||
resources:
|
|
||||||
glance-api-image:
|
|
||||||
type: oci-image
|
|
||||||
description: OCI image for OpenStack Glance (kolla/glance-api-image)
|
|
||||||
|
|
||||||
requires:
|
|
||||||
shared-db:
|
|
||||||
interface: mysql_datastore
|
|
||||||
limit: 1
|
|
||||||
ingress:
|
|
||||||
interface: ingress
|
|
||||||
identity-service:
|
|
||||||
interface: keystone
|
|
||||||
limit: 1
|
|
||||||
amqp:
|
|
||||||
interface: rabbitmq
|
|
||||||
image-service:
|
|
||||||
interface: glance
|
|
||||||
ceph:
|
|
||||||
interface: ceph-client
|
|
||||||
|
|
||||||
peers:
|
|
||||||
peers:
|
|
||||||
interface: glance-peer
|
|
||||||
|
|
||||||
The first part of the metadata is pretty self explanatory, is sets out the some
|
|
||||||
general information about the charm. The `containers` section lists all the
|
|
||||||
containers that this charm will manage. Glance consists of just one container
|
|
||||||
so just one container is listed here. Similarly in the resources section all
|
|
||||||
the container images are listed. Since there is just one container only one
|
|
||||||
image is listed here.
|
|
||||||
|
|
||||||
The requires section lists all the relations this charm is reliant on. These
|
|
||||||
are all standard for an OpenStack API charm plus the additional ceph relation.
|
|
||||||
|
|
||||||
Common Files
|
|
||||||
============
|
|
||||||
|
|
||||||
ASO contains some common files which need to copied into the charm.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
cp advanced-sunbeam-openstack/shared_code/tox.ini charm-glance-operator/
|
|
||||||
cp advanced-sunbeam-openstack/shared_code/requirements.txt charm-glance-operator/
|
|
||||||
cp -r advanced-sunbeam-openstack/shared_code/templates charm-glance-operator/src/
|
|
||||||
cp advanced-sunbeam-openstack/shared_code/.stestr.conf charm-glance-operator/
|
|
||||||
cp advanced-sunbeam-openstack/shared_code/test-requirements.txt charm-glance-operator/
|
|
||||||
|
|
||||||
At the moment the wsgi template needs to be renamed to add incluse the
|
|
||||||
service name.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
cd charm-glance-operator
|
|
||||||
mv /src/templates/wsgi-template.conf.j2 ./src/templates/wsgi-glance-api.conf.j2
|
|
||||||
|
|
||||||
There are some config options which are common accross the OpenStack api charms. Since
|
|
||||||
this charm uses ceph add the ceph config options too.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
cd advanced-sunbeam-openstack/shared_code/
|
|
||||||
echo "options:" > ../../charm-glance-operator/config.yaml
|
|
||||||
cat config-api.yaml >> ../../charm-glance-operator/config.yaml
|
|
||||||
cat config-ceph-options.yaml >> ../../charm-glance-operator/config.yaml
|
|
||||||
|
|
||||||
Fetch interface libs corresponding to the requires interfaces:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
cd charm-glance-operator
|
|
||||||
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
|
|
||||||
charmcraft fetch-lib charms.sunbeam_mysql_k8s.v0.mysql
|
|
||||||
charmcraft fetch-lib charms.sunbeam_keystone_operator.v0.identity_service
|
|
||||||
charmcraft fetch-lib charms.sunbeam_rabbitmq_operator.v0.amqp
|
|
||||||
charmcraft fetch-lib charms.observability_libs.v0.kubernetes_service_patch
|
|
||||||
|
|
||||||
Templates
|
|
||||||
=========
|
|
||||||
|
|
||||||
Much of the glance configuration is covered by common templates which were copied
|
|
||||||
into the charm in the previous step. The only additional template for this charm
|
|
||||||
is for `glance-api.conf`. Add the following into `./src/templates/glance-api.conf.j2`
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# [ WARNING ]
|
|
||||||
# glance configuration file maintained by Juju
|
|
||||||
# local changes may be overwritten.
|
|
||||||
###############################################################################
|
|
||||||
[DEFAULT]
|
|
||||||
debug = {{ options.debug }}
|
|
||||||
transport_url = {{ amqp.transport_url }}
|
|
||||||
|
|
||||||
{% include "parts/section-database" %}
|
|
||||||
|
|
||||||
{% include "parts/section-identity" %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[glance_store]
|
|
||||||
default_backend = ceph
|
|
||||||
filesystem_store_datadir = /var/lib/glance/images/
|
|
||||||
|
|
||||||
[ceph]
|
|
||||||
rbd_store_chunk_size = 8
|
|
||||||
rbd_store_pool = glance
|
|
||||||
rbd_store_user = glance
|
|
||||||
rados_connect_timeout = 0
|
|
||||||
rbd_store_ceph_conf = /etc/ceph/ceph.conf
|
|
||||||
|
|
||||||
[paste_deploy]
|
|
||||||
flavor = keystone
|
|
||||||
|
|
||||||
Charm
|
|
||||||
=====
|
|
||||||
|
|
||||||
This is subject to change as more of the common code is generalised into aso.
|
|
||||||
|
|
||||||
Inherit from OSBaseOperatorAPICharm
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Start by creating a charm class that inherits from the `OSBaseOperatorAPICharm`
|
|
||||||
class which contains all the code which is common accross OpenStack API charms.
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Glance Operator Charm.
|
|
||||||
|
|
||||||
This charm provide Glance services as part of an OpenStack deployment
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from ops.framework import StoredState
|
|
||||||
from ops.main import main
|
|
||||||
|
|
||||||
import advanced_sunbeam_openstack.cprocess as sunbeam_cprocess
|
|
||||||
import advanced_sunbeam_openstack.charm as sunbeam_charm
|
|
||||||
import advanced_sunbeam_openstack.core as sunbeam_core
|
|
||||||
import advanced_sunbeam_openstack.relation_handlers as sunbeam_rhandlers
|
|
||||||
import advanced_sunbeam_openstack.config_contexts as sunbeam_ctxts
|
|
||||||
|
|
||||||
from charms.observability_libs.v0.kubernetes_service_patch \
|
|
||||||
import KubernetesServicePatch
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class GlanceOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|
||||||
"""Charm the service."""
|
|
||||||
|
|
||||||
ceph_conf = "/etc/ceph/ceph.conf"
|
|
||||||
|
|
||||||
_state = StoredState()
|
|
||||||
service_name = "glance-api"
|
|
||||||
wsgi_admin_script = '/usr/bin/glance-wsgi-api'
|
|
||||||
wsgi_public_script = '/usr/bin/glance-wsgi-api'
|
|
||||||
|
|
||||||
def __init__(self, framework):
|
|
||||||
super().__init__(framework)
|
|
||||||
self.service_patcher = KubernetesServicePatch(
|
|
||||||
self,
|
|
||||||
[
|
|
||||||
('public', self.default_public_ingress_port),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
The `KubernetesServicePatch` module is used to expose the service within kubernetes
|
|
||||||
so that it is externally visable. Hopefully this will eventually be accomplished by
|
|
||||||
Juju and and can be removed.
|
|
||||||
|
|
||||||
Ceph Support
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This glance charm with relate to Ceph to store uploaded images. A relation to Ceph
|
|
||||||
is not common accross the api charms to we need to add the components from ASO to
|
|
||||||
support the ceph relation.
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config_contexts(self) -> List[sunbeam_ctxts.ConfigContext]:
|
|
||||||
"""Configuration contexts for the operator."""
|
|
||||||
contexts = super().config_contexts
|
|
||||||
contexts.append(
|
|
||||||
sunbeam_ctxts.CephConfigurationContext(self, "ceph_config"))
|
|
||||||
return contexts
|
|
||||||
|
|
||||||
@property
|
|
||||||
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
|
|
||||||
"""Container configurations for the operator."""
|
|
||||||
_cconfigs = super().container_configs
|
|
||||||
_cconfigs.extend(
|
|
||||||
[
|
|
||||||
sunbeam_core.ContainerConfigFile(
|
|
||||||
[self.service_name],
|
|
||||||
self.ceph_conf,
|
|
||||||
self.service_user,
|
|
||||||
self.service_group,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return _cconfigs
|
|
||||||
|
|
||||||
def get_relation_handlers(self) -> List[sunbeam_rhandlers.RelationHandler]:
|
|
||||||
"""Relation handlers for the service."""
|
|
||||||
handlers = super().get_relation_handlers()
|
|
||||||
self.ceph = sunbeam_rhandlers.CephClientHandler(
|
|
||||||
self,
|
|
||||||
"ceph",
|
|
||||||
self.configure_charm,
|
|
||||||
allow_ec_overwrites=True,
|
|
||||||
app_name='rbd'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
In the `config_contexts` `sunbeam_ctxts.CephConfigurationContext` is added to the list
|
|
||||||
of config contexts. This will look after transalting some of the charms
|
|
||||||
configuration options into Ceph configuration.
|
|
||||||
|
|
||||||
In `container_configs` the `ceph.conf` is added to the list of configuration
|
|
||||||
files to be rendered in containers.
|
|
||||||
|
|
||||||
Finally in `get_relation_handlers` the relation handler for the `ceph` relation is
|
|
||||||
added.
|
|
||||||
|
|
||||||
OpenStack Endpoints
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
`OSBaseOperatorAPICharm` makes assumptions based on the self.service_name but a few
|
|
||||||
of these are broken as there is a mix between `glance` and `glance_api`. Finally the
|
|
||||||
charm needs to specify what endpoint should be registered in the keystone catalgue
|
|
||||||
each charm needs to explicitly state this as there is a lot of variation between
|
|
||||||
services
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@property
|
|
||||||
def service_conf(self) -> str:
|
|
||||||
"""Service default configuration file."""
|
|
||||||
return f"/etc/glance/glance-api.conf"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def service_user(self) -> str:
|
|
||||||
"""Service user file and directory ownership."""
|
|
||||||
return 'glance'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def service_group(self) -> str:
|
|
||||||
"""Service group file and directory ownership."""
|
|
||||||
return 'glance'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def service_endpoints(self):
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'service_name': 'glance',
|
|
||||||
'type': 'image',
|
|
||||||
'description': "OpenStack Image",
|
|
||||||
'internal_url': f'{self.internal_url}',
|
|
||||||
'public_url': f'{self.public_url}',
|
|
||||||
'admin_url': f'{self.admin_url}'}]
|
|
||||||
|
|
||||||
@property
|
|
||||||
return 9292
|
|
||||||
|
|
||||||
Bootstrap
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
Currently ASO does not support database migrations, this will be fixed soon but until
|
|
||||||
then add a db sync to the bootstrap process.
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def _do_bootstrap(self):
|
|
||||||
"""
|
|
||||||
Starts the appropriate services in the order they are needed.
|
|
||||||
If the service has not yet been bootstrapped, then this will
|
|
||||||
1. Create the database
|
|
||||||
"""
|
|
||||||
super()._do_bootstrap()
|
|
||||||
try:
|
|
||||||
container = self.unit.get_container(self.wsgi_container_name)
|
|
||||||
logger.info("Syncing database...")
|
|
||||||
out = sunbeam_cprocess.check_output(
|
|
||||||
container,
|
|
||||||
[
|
|
||||||
'sudo', '-u', 'glance',
|
|
||||||
'glance-manage', '--config-dir',
|
|
||||||
'/etc/glance', 'db', 'sync'],
|
|
||||||
service_name='keystone-db-sync',
|
|
||||||
timeout=180)
|
|
||||||
logging.debug(f'Output from database sync: \n{out}')
|
|
||||||
except sunbeam_cprocess.ContainerProcessError:
|
|
||||||
logger.exception('Failed to bootstrap')
|
|
||||||
self._state.bootstrapped = False
|
|
||||||
return
|
|
||||||
|
|
||||||
Configure Charm
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The container used by this charm should include `ceph-common` but it currently does
|
|
||||||
not. To work around this install it in the container. As glance communicates with Ceph
|
|
||||||
another specialisation is needed to run `ceph-authtool`.
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def configure_charm(self, event) -> None:
|
|
||||||
"""Catchall handler to cconfigure charm services."""
|
|
||||||
if not self.relation_handlers_ready():
|
|
||||||
logging.debug("Defering configuration, charm relations not ready")
|
|
||||||
return
|
|
||||||
|
|
||||||
for ph in self.pebble_handlers:
|
|
||||||
if ph.pebble_ready:
|
|
||||||
container = self.unit.get_container(
|
|
||||||
ph.container_name
|
|
||||||
)
|
|
||||||
sunbeam_cprocess.check_call(
|
|
||||||
container,
|
|
||||||
['apt', 'update'])
|
|
||||||
sunbeam_cprocess.check_call(
|
|
||||||
container,
|
|
||||||
['apt', 'install', '-y', 'ceph-common'])
|
|
||||||
try:
|
|
||||||
sunbeam_cprocess.check_call(
|
|
||||||
container,
|
|
||||||
['ceph-authtool',
|
|
||||||
f'/etc/ceph/ceph.client.{self.app.name}.keyring',
|
|
||||||
'--create-keyring',
|
|
||||||
f'--name=client.{self.app.name}',
|
|
||||||
f'--add-key={self.ceph.key}']
|
|
||||||
)
|
|
||||||
except sunbeam_cprocess.ContainerProcessError:
|
|
||||||
pass
|
|
||||||
ph.init_service(self.contexts())
|
|
||||||
|
|
||||||
super().configure_charm(event)
|
|
||||||
# Restarting services after bootstrap should be in aso
|
|
||||||
if self._state.bootstrapped:
|
|
||||||
for handler in self.pebble_handlers:
|
|
||||||
handler.start_service()
|
|
||||||
|
|
||||||
OpenStack Release
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This charm is spefic to a particular release so the final step is to add a
|
|
||||||
release specific class.
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
class GlanceWallabyOperatorCharm(GlanceOperatorCharm):
|
|
||||||
|
|
||||||
openstack_release = 'wallaby'
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Note: use_juju_for_storage=True required per
|
|
||||||
# https://github.com/canonical/operator/issues/506
|
|
||||||
main(GlanceWallabyOperatorCharm, use_juju_for_storage=True)
|
|
76
ops-sunbeam/shared_code/aso-charm-init.py
Executable file
76
ops-sunbeam/shared_code/aso-charm-init.py
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import yaml
|
||||||
|
import argparse
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
from cookiecutter.main import cookiecutter
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def start_msg():
|
||||||
|
print("This tool is designed to be used after 'charmcraft init' was initially run")
|
||||||
|
|
||||||
|
def cookie(output_dir, extra_context):
|
||||||
|
cookiecutter(
|
||||||
|
'aso_charm/',
|
||||||
|
extra_context=extra_context,
|
||||||
|
output_dir=output_dir)
|
||||||
|
|
||||||
|
def arg_parser():
|
||||||
|
parser = argparse.ArgumentParser(description='Process some integers.')
|
||||||
|
parser.add_argument('charm_path', help='path to charm')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def read_metadata_file(charm_dir):
|
||||||
|
with open(f'{charm_dir}/metadata.yaml', 'r') as f:
|
||||||
|
metadata = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
def switch_dir():
|
||||||
|
abspath = os.path.abspath(__file__)
|
||||||
|
dname = os.path.dirname(abspath)
|
||||||
|
os.chdir(dname)
|
||||||
|
|
||||||
|
def get_extra_context(charm_dir):
|
||||||
|
metadata = read_metadata_file(charm_dir)
|
||||||
|
charm_name = metadata['name']
|
||||||
|
service_name = charm_name.replace('sunbeam-', '')
|
||||||
|
service_name = service_name.replace('-operator', '')
|
||||||
|
ctxt = {
|
||||||
|
'service_name': service_name,
|
||||||
|
'charm_name': charm_name}
|
||||||
|
# XXX REMOVE
|
||||||
|
ctxt['db_sync_command'] = 'ironic-dbsync --config-file /etc/ironic/ironic.conf create_schema'
|
||||||
|
ctxt['ingress_port'] = 6385
|
||||||
|
return ctxt
|
||||||
|
|
||||||
|
def sync_code(src_dir, target_dir):
|
||||||
|
cmd = ['rsync', '-r', '-v', f'{src_dir}/', target_dir]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""Echo the input arguments to standard output"""
|
||||||
|
start_msg()
|
||||||
|
args = arg_parser()
|
||||||
|
charm_dir = args.charm_path
|
||||||
|
switch_dir()
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
extra_context = get_extra_context(charm_dir)
|
||||||
|
service_name = extra_context['service_name']
|
||||||
|
cookie(
|
||||||
|
tmpdirname,
|
||||||
|
extra_context)
|
||||||
|
src_dir = f"{tmpdirname}/{service_name}"
|
||||||
|
shutil.copyfile(
|
||||||
|
f'{src_dir}/src/templates/wsgi-template.conf.j2',
|
||||||
|
f'{src_dir}/src/templates/wsgi-{service_name}-api.conf')
|
||||||
|
sync_code(src_dir, charm_dir)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
9
ops-sunbeam/shared_code/aso_charm/cookiecutter.json
Normal file
9
ops-sunbeam/shared_code/aso_charm/cookiecutter.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"service_name": "",
|
||||||
|
"charm_name": "",
|
||||||
|
"ingress_port": "",
|
||||||
|
"db_sync_command": "",
|
||||||
|
"_copy_without_render": [
|
||||||
|
"src/templates"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
# NOTE: no actions yet!
|
||||||
|
{ }
|
@ -0,0 +1,17 @@
|
|||||||
|
type: "charm"
|
||||||
|
bases:
|
||||||
|
- build-on:
|
||||||
|
- name: "ubuntu"
|
||||||
|
channel: "20.04"
|
||||||
|
run-on:
|
||||||
|
- name: "ubuntu"
|
||||||
|
channel: "20.04"
|
||||||
|
parts:
|
||||||
|
charm:
|
||||||
|
build-packages:
|
||||||
|
- git
|
||||||
|
- libffi-dev
|
||||||
|
- libssl-dev
|
||||||
|
charm-python-packages:
|
||||||
|
- setuptools < 58
|
||||||
|
- cryptography < 3.4
|
@ -0,0 +1,27 @@
|
|||||||
|
options:
|
||||||
|
debug:
|
||||||
|
default: False
|
||||||
|
description: Enable debug logging.
|
||||||
|
type: boolean
|
||||||
|
os-admin-hostname:
|
||||||
|
default: glance.juju
|
||||||
|
description: |
|
||||||
|
The hostname or address of the admin endpoints that should be advertised
|
||||||
|
in the glance image provider.
|
||||||
|
type: string
|
||||||
|
os-internal-hostname:
|
||||||
|
default: glance.juju
|
||||||
|
description: |
|
||||||
|
The hostname or address of the internal endpoints that should be advertised
|
||||||
|
in the glance image provider.
|
||||||
|
type: string
|
||||||
|
os-public-hostname:
|
||||||
|
default: glance.juju
|
||||||
|
description: |
|
||||||
|
The hostname or address of the internal endpoints that should be advertised
|
||||||
|
in the glance image provider.
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
default: RegionOne
|
||||||
|
description: Space delimited list of OpenStack regions
|
||||||
|
type: string
|
@ -0,0 +1,42 @@
|
|||||||
|
name: {{ cookiecutter.charm_name }}
|
||||||
|
summary: OpenStack {{ cookiecutter.service_name }} service
|
||||||
|
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
|
||||||
|
description: |
|
||||||
|
OpenStack {{ cookiecutter.service_name }} provides an HTTP service for managing, selecting,
|
||||||
|
and claiming providers of classes of inventory representing available
|
||||||
|
resources in a cloud.
|
||||||
|
.
|
||||||
|
version: 3
|
||||||
|
bases:
|
||||||
|
- name: ubuntu
|
||||||
|
channel: 20.04/stable
|
||||||
|
tags:
|
||||||
|
- openstack
|
||||||
|
|
||||||
|
containers:
|
||||||
|
{{ cookiecutter.service_name }}-api:
|
||||||
|
resource: {{ cookiecutter.service_name }}-api-image
|
||||||
|
|
||||||
|
resources:
|
||||||
|
{{ cookiecutter.service_name }}-api-image:
|
||||||
|
type: oci-image
|
||||||
|
description: OCI image for OpenStack {{ cookiecutter.service_name }}
|
||||||
|
|
||||||
|
requires:
|
||||||
|
shared-db:
|
||||||
|
interface: mysql_datastore
|
||||||
|
limit: 1
|
||||||
|
identity-service:
|
||||||
|
interface: keystone
|
||||||
|
ingress:
|
||||||
|
interface: ingress
|
||||||
|
amqp:
|
||||||
|
interface: rabbitmq
|
||||||
|
|
||||||
|
provides:
|
||||||
|
{{ cookiecutter.service_name }}:
|
||||||
|
interface: {{ cookiecutter.service_name }}
|
||||||
|
|
||||||
|
peers:
|
||||||
|
peers:
|
||||||
|
interface: {{ cookiecutter.service_name }}-peer
|
@ -1,6 +1,5 @@
|
|||||||
# ops >= 1.2.0
|
ops
|
||||||
jinja2
|
jinja2
|
||||||
git+https://github.com/canonical/operator@2875e73e#egg=ops
|
|
||||||
git+https://opendev.org/openstack/charm-ops-openstack#egg=ops_openstack
|
git+https://opendev.org/openstack/charm-ops-openstack#egg=ops_openstack
|
||||||
git+https://github.com/openstack-charmers/advanced-sunbeam-openstack#egg=advanced_sunbeam_openstack
|
git+https://github.com/openstack-charmers/advanced-sunbeam-openstack#egg=advanced_sunbeam_openstack
|
||||||
lightkube
|
lightkube
|
@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""{{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }} Operator Charm.
|
||||||
|
|
||||||
|
This charm provide {{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }} services as part of an OpenStack deployment
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ops.framework import StoredState
|
||||||
|
from ops.main import main
|
||||||
|
|
||||||
|
import advanced_sunbeam_openstack.charm as sunbeam_charm
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class {{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }}OperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||||
|
"""Charm the service."""
|
||||||
|
|
||||||
|
_state = StoredState()
|
||||||
|
service_name = "{{ cookiecutter.service_name }}-api"
|
||||||
|
wsgi_admin_script = '/usr/bin/{{ cookiecutter.service_name }}-api-wsgi'
|
||||||
|
wsgi_public_script = '/usr/bin/{{ cookiecutter.service_name }}-api-wsgi'
|
||||||
|
|
||||||
|
db_sync_cmds = [
|
||||||
|
{{ cookiecutter.db_sync_command.split() }}
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_conf(self) -> str:
|
||||||
|
"""Service default configuration file."""
|
||||||
|
return f"/etc/{{ cookiecutter.service_name }}/{{ cookiecutter.service_name }}.conf"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_user(self) -> str:
|
||||||
|
"""Service user file and directory ownership."""
|
||||||
|
return '{{ cookiecutter.service_name }}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_group(self) -> str:
|
||||||
|
"""Service group file and directory ownership."""
|
||||||
|
return '{{ cookiecutter.service_name }}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_endpoints(self):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'service_name': '{{ cookiecutter.service_name }}',
|
||||||
|
'type': '{{ cookiecutter.service_name }}',
|
||||||
|
'description': "OpenStack {{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }} API",
|
||||||
|
'internal_url': f'{self.internal_url}',
|
||||||
|
'public_url': f'{self.public_url}',
|
||||||
|
'admin_url': f'{self.admin_url}'}]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_public_ingress_port(self):
|
||||||
|
return {{ cookiecutter.ingress_port }}
|
||||||
|
|
||||||
|
|
||||||
|
class {{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }}WallabyOperatorCharm({{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }}OperatorCharm):
|
||||||
|
|
||||||
|
openstack_release = 'wallaby'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Note: use_juju_for_storage=True required per
|
||||||
|
# https://github.com/canonical/operator/issues/506
|
||||||
|
main({{ cookiecutter.service_name[0]|upper}}{{cookiecutter.service_name[1:] }}WallabyOperatorCharm, use_juju_for_storage=True)
|
@ -0,0 +1,2 @@
|
|||||||
|
[keystone_authtoken]
|
||||||
|
{% include "parts/identity-data" %}
|
1
ops-sunbeam/shared_code/templates
Symbolic link
1
ops-sunbeam/shared_code/templates
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
aso_charm/{{cookiecutter.service_name}}/src/templates
|
@ -1,2 +0,0 @@
|
|||||||
[keystone_authtoken]
|
|
||||||
{% include "parts/identity-connection" %}
|
|
@ -42,6 +42,11 @@ deps =
|
|||||||
commands =
|
commands =
|
||||||
./fetch-libs.sh
|
./fetch-libs.sh
|
||||||
|
|
||||||
|
[testenv:cookie]
|
||||||
|
basepython = python3
|
||||||
|
deps = -r{toxinidir}/cookie-requirements.txt
|
||||||
|
commands = /bin/true
|
||||||
|
|
||||||
[testenv:py3.8]
|
[testenv:py3.8]
|
||||||
basepython = python3.8
|
basepython = python3.8
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
|
154
ops-sunbeam/writing-OS-API-charm.rst
Normal file
154
ops-sunbeam/writing-OS-API-charm.rst
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
=============
|
||||||
|
New API Charm
|
||||||
|
=============
|
||||||
|
|
||||||
|
The example below will walk through the creation of a basic API charm for the
|
||||||
|
OpenStack `Ironic <https://wiki.openstack.org/wiki/Ironic>`__ service designed
|
||||||
|
to run on kubernetes.
|
||||||
|
|
||||||
|
Create the skeleton charm
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Prerequisite
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Build a base geneeric charm with the `charmcraft` tool.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
mkdir charm-ironic-operator
|
||||||
|
cd charm-ironic-operator
|
||||||
|
charmcraft init --name sunbeam-ironic-operator
|
||||||
|
|
||||||
|
Add ASO common files to new charm. The script will ask a few basic questions:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
git clone https://github.com/openstack-charmers/advanced-sunbeam-openstack
|
||||||
|
cd advanced-sunbeam-openstack/shared_code
|
||||||
|
./aso-charm-init.sh ~/branches/charm-ironic-operator
|
||||||
|
|
||||||
|
This tool is designed to be used after 'charmcraft init' was initially run
|
||||||
|
service_name [ironic]: ironic
|
||||||
|
charm_name [sunbeam-ironic-operator]: sunbeam-ironic-operator
|
||||||
|
ingress_port []: 6385
|
||||||
|
db_sync_command [] ironic-dbsync --config-file /etc/ironic/ironic.conf create_schema:
|
||||||
|
|
||||||
|
Fetch interface libs corresponding to the requires interfaces:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
cd charm-ironic-operator
|
||||||
|
charmcraft login
|
||||||
|
charmcraft fetch-lib charms.nginx_ingress_integrator.v0.ingress
|
||||||
|
charmcraft fetch-lib charms.sunbeam_mysql_k8s.v0.mysql
|
||||||
|
charmcraft fetch-lib charms.sunbeam_keystone_operator.v0.identity_service
|
||||||
|
charmcraft fetch-lib charms.sunbeam_rabbitmq_operator.v0.amqp
|
||||||
|
charmcraft fetch-lib charms.observability_libs.v0.kubernetes_service_patch
|
||||||
|
|
||||||
|
Templates
|
||||||
|
=========
|
||||||
|
|
||||||
|
Much of the service configuration is covered by common templates which were copied
|
||||||
|
into the charm in the previous step. The only additional template for this charm
|
||||||
|
is for `ironic.conf`. Add the following into `./src/templates/ironic.conf.j2`
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
debug = {{ options.debug }}
|
||||||
|
auth_strategy=keystone
|
||||||
|
transport_url = {{ amqp.transport_url }}
|
||||||
|
|
||||||
|
[keystone_authtoken]
|
||||||
|
{% include "parts/identity-data" %}
|
||||||
|
|
||||||
|
[database]
|
||||||
|
{% include "parts/database-connection" %}
|
||||||
|
|
||||||
|
[neutron]
|
||||||
|
{% include "parts/identity-data" %}
|
||||||
|
|
||||||
|
[glance]
|
||||||
|
{% include "parts/identity-data" %}
|
||||||
|
|
||||||
|
[cinder]
|
||||||
|
{% include "parts/identity-data" %}
|
||||||
|
|
||||||
|
[service_catalog]
|
||||||
|
{% include "parts/identity-data" %}
|
||||||
|
|
||||||
|
|
||||||
|
Make charm deployable
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The next step is to pack the charm into a deployable format
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
cd charm-ironic-operator
|
||||||
|
charmcraft pack
|
||||||
|
|
||||||
|
|
||||||
|
Deploy Charm
|
||||||
|
============
|
||||||
|
|
||||||
|
The charm can now be deployed. The Kolla project has images that can be used to
|
||||||
|
run the service. Juju can pull the image directly from dockerhub.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
juju deploy ./sunbeam-ironic-operator_ubuntu-20.04-amd64.charm --resource ironic-api-image=kolla/ubuntu-binary-ironic-api:wallaby ironic
|
||||||
|
juju add-relation ironic mysql
|
||||||
|
juju add-relation ironic keystone
|
||||||
|
juju add-relation ironic rabbitmq
|
||||||
|
|
||||||
|
Test Service
|
||||||
|
============
|
||||||
|
|
||||||
|
Check that the juju status shows the charms is active and no error messages are
|
||||||
|
preset. Then check the ironic api service is reponding.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ juju status ironic
|
||||||
|
Model Controller Cloud/Region Version SLA Timestamp
|
||||||
|
ks micro microk8s/localhost 2.9.22 unsupported 13:31:41Z
|
||||||
|
|
||||||
|
App Version Status Scale Charm Store Channel Rev OS Address Message
|
||||||
|
ironic active 1 sunbeam-ironic-operator local 0 kubernetes 10.152.183.73
|
||||||
|
|
||||||
|
Unit Workload Agent Address Ports Message
|
||||||
|
ironic/0* active idle 10.1.155.106
|
||||||
|
|
||||||
|
$ curl http://10.1.155.106:6385 | jq '.'
|
||||||
|
{
|
||||||
|
"name": "OpenStack Ironic API",
|
||||||
|
"description": "Ironic is an OpenStack project which aims to provision baremetal machines.",
|
||||||
|
"default_version": {
|
||||||
|
"id": "v1",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://10.1.155.106:6385/v1/",
|
||||||
|
"rel": "self"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "CURRENT",
|
||||||
|
"min_version": "1.1",
|
||||||
|
"version": "1.72"
|
||||||
|
},
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"id": "v1",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://10.1.155.106:6385/v1/",
|
||||||
|
"rel": "self"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "CURRENT",
|
||||||
|
"min_version": "1.1",
|
||||||
|
"version": "1.72"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user