# Execute tests for charms
CHARM_PATH="./installers/charm"
-CHARM_NAMES="keystone lcm mon nbi ng-ui pla pol prometheus ro grafana mongodb-exporter mysqld-exporter kafka-exporter"
+CHARM_NAMES="keystone prometheus grafana"
for charm in $CHARM_NAMES; do
cd $CHARM_PATH/$charm
TOX_PARALLEL_NO_SPINNER=1 tox --parallel=auto
+++ /dev/null
-#!/bin/bash
-# Copyright 2020 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.
-
-function build() {
- cd $1 && tox -qe build && cd ..
-}
-
-charms="ro nbi pla pol mon lcm ng-ui grafana prometheus mongodb-exporter kafka-exporter mysqld-exporter"
-if [ -z `which charmcraft` ]; then
- sudo snap install charmcraft --classic
-fi
-
-for charm_directory in $charms; do
- build $charm_directory
-done
-wait
\ No newline at end of file
+++ /dev/null
-# Copyright 2020 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.
-name: keystone
-summary: Keystone Interface
-version: 1
+++ /dev/null
-# Copyright 2020 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.
-from charms.reactive import Endpoint
-from charms.reactive import when
-from charms.reactive import set_flag, clear_flag
-
-
-class KeystoneProvides(Endpoint):
- @when("endpoint.{endpoint_name}.joined")
- def _joined(self):
- set_flag(self.expand_name("{endpoint_name}.joined"))
-
- @when("endpoint.{endpoint_name}.changed")
- def _changed(self):
- set_flag(self.expand_name("{endpoint_name}.ready"))
-
- @when("endpoint.{endpoint_name}.departed")
- def _departed(self):
- set_flag(self.expand_name("{endpoint_name}.departed"))
- clear_flag(self.expand_name("{endpoint_name}.joined"))
-
- def publish_info(
- self,
- host,
- port,
- keystone_db_password,
- region_id,
- user_domain_name,
- project_domain_name,
- admin_username,
- admin_password,
- admin_project_name,
- username,
- password,
- service,
- ):
- for relation in self.relations:
- relation.to_publish["host"] = host
- relation.to_publish["port"] = port
- relation.to_publish["keystone_db_password"] = keystone_db_password
- relation.to_publish["region_id"] = region_id
- relation.to_publish["user_domain_name"] = user_domain_name
- relation.to_publish["project_domain_name"] = project_domain_name
- relation.to_publish["admin_username"] = admin_username
- relation.to_publish["admin_password"] = admin_password
- relation.to_publish["admin_project_name"] = admin_project_name
- relation.to_publish["username"] = username
- relation.to_publish["password"] = password
- relation.to_publish["service"] = service
-
- def mark_complete(self):
- clear_flag(self.expand_name("{endpoint_name}.joined"))
+++ /dev/null
-# Copyright 2020 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.
-from charms.reactive import Endpoint
-from charms.reactive import when
-from charms.reactive import set_flag, clear_flag
-
-
-class KeystoneRequires(Endpoint):
- @when("endpoint.{endpoint_name}.joined")
- def _joined(self):
- set_flag(self.expand_name("{endpoint_name}.joined"))
-
- @when("endpoint.{endpoint_name}.changed")
- def _changed(self):
- if len(self.keystones()) > 0:
- set_flag(self.expand_name("{endpoint_name}.ready"))
- else:
- clear_flag(self.expand_name("{endpoint_name}.ready"))
-
- @when("endpoint.{endpoint_name}.departed")
- def _departed(self):
- set_flag(self.expand_name("{endpoint_name}.departed"))
- clear_flag(self.expand_name("{endpoint_name}.joined"))
- clear_flag(self.expand_name("{endpoint_name}.ready"))
-
- def keystones(self):
- """
- Return Keystone Data:
- [{
- 'host': <host>,
- 'port': <port>,
- 'keystone_db_password: <keystone_db_password>,
- 'region_id: <region_id>,
- 'admin_username: <admin_username>,
- 'admin_password: <admin_password>,
- 'admin_project_name: <admin_project_name>,
- 'username: <username>,
- 'password: <password>,
- 'service: <service>
- }]
- """
- keystones = []
- for relation in self.relations:
- for unit in relation.units:
- data = {
- "host": unit.received["host"],
- "port": unit.received["port"],
- "keystone_db_password": unit.received["keystone_db_password"],
- "region_id": unit.received["region_id"],
- "user_domain_name": unit.received["user_domain_name"],
- "project_domain_name": unit.received["project_domain_name"],
- "admin_username": unit.received["admin_username"],
- "admin_password": unit.received["admin_password"],
- "admin_project_name": unit.received["admin_project_name"],
- "username": unit.received["username"],
- "password": unit.received["password"],
- "service": unit.received["service"],
- }
- if all(data.values()):
- keystones.append(data)
- return keystones
+++ /dev/null
-<!--
-Copyright 2020 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. -->
-
-# Overview
-
-This interface layer handles communication between Mongodb and its clients.
-
-## Usage
-
-### Provides
-
-To implement this relation to offer an nbi:
-
-In your charm's metadata.yaml:
-
-```yaml
-provides:
- nbi:
- interface: osm-nbi
-```
-
-reactive/mynbi.py:
-
-```python
-@when('nbi.joined')
-def send_config(nbi):
- nbi.send_connection(
- unit_get('private-address'),
- get_nbi_port()
- )
-```
-
-### Requires
-
-If you would like to use an nbi from your charm:
-
-metadata.yaml:
-
-```yaml
-requires:
- nbi:
- interface: osm-nbi
-```
-
-reactive/mycharm.py:
-
-```python
-@when('nbi.ready')
-def nbi_ready():
- nbi = endpoint_from_flag('nbi.ready')
- if nbi:
- for unit in nbi.nbis():
- add_nbi(unit['host'], unit['port'])
-```
+++ /dev/null
-Format: http://dep.debian.net/deps/dep5/
-
-Files: *
-Copyright: Copyright 2020, Canonical Ltd., All Rights Reserved.
-License: Apache License 2.0
- 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.
\ No newline at end of file
+++ /dev/null
-# Copyright 2020 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.
-name: osm-nbi
-summary: Interface for relating to a OSM Northbound Interface
-maintainer: '"Adam Israel" <adam@adamisrael.com>'
+++ /dev/null
-#!/usr/bin/python
-# Copyright 2020 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.
-
-from charms.reactive import RelationBase
-from charms.reactive import hook
-from charms.reactive import scopes
-
-
-class OsmNBIProvides(RelationBase):
- scope = scopes.GLOBAL
-
- @hook("{provides:osm-nbi}-relation-joined")
- def joined(self):
- self.set_state("{relation_name}.joined")
-
- @hook("{provides:osm-nbi}-relation-changed")
- def changed(self):
- self.set_state("{relation_name}.ready")
-
- @hook("{provides:osm-nbi}-relation-{broken,departed}")
- def broken_departed(self):
- self.remove_state("{relation_name}.ready")
- self.remove_state("{relation_name}.joined")
-
- @hook("{provides:osm-nbi}-relation-broken")
- def broken(self):
- self.set_state("{relation_name}.removed")
-
- def send_connection(self, host, port=9999):
- conv = self.conversation()
- conv.set_remote("host", host)
- conv.set_remote("port", port)
+++ /dev/null
-# Copyright 2020 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.
-
-from charms.reactive import RelationBase
-from charms.reactive import hook
-from charms.reactive import scopes
-
-
-class OsmNBIRequires(RelationBase):
- scope = scopes.GLOBAL
-
- @hook("{requires:osm-nbi}-relation-joined")
- def joined(self):
- conv = self.conversation()
- conv.set_state("{relation_name}.joined")
-
- @hook("{requires:osm-nbi}-relation-changed")
- def changed(self):
- conv = self.conversation()
- if self.nbis():
- conv.set_state("{relation_name}.ready")
- else:
- conv.remove_state("{relation_name}.ready")
-
- @hook("{requires:osm-nbi}-relation-departed")
- def departed(self):
- conv = self.conversation()
- conv.remove_state("{relation_name}.ready")
- conv.remove_state("{relation_name}.joined")
-
- def nbis(self):
- """Return the NBI's host and port.
-
- [{
- 'host': <host>,
- 'port': <port>,
- }]
- """
- nbis = []
- for conv in self.conversations():
- port = conv.get_remote("port")
- host = conv.get_remote("host") or conv.get_remote("private-address")
- if host and port:
- nbis.append({"host": host, "port": port})
- return nbis
+++ /dev/null
-<!--
-Copyright 2020 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. -->
-
-# Overview
-
-This interface layer handles communication between OSM's RO and its clients.
-
-## Usage
-
-### Provides
-
-To implement this relation to offer an ro:
-
-In your charm's metadata.yaml:
-
-```yaml
-provides:
- ro:
- interface: osm-ro
-```
-
-reactive/myro.py:
-
-```python
-@when('ro.joined')
-def send_config(ro):
- ro.send_connection(
- unit_get('private-address'),
- get_ro_port()
- )
-```
-
-### Requires
-
-If you would like to use a rodb from your charm:
-
-metadata.yaml:
-
-```yaml
-requires:
- ro:
- interface: osm-ro
-```
-
-reactive/mycharm.py:
-
-```python
-@when('ro.ready')
-def ro_ready():
- ro = endpoint_from_flag('ro.ready')
- if ro:
- for unit in ro.ros():
- add_ro(unit['host'], unit['port'])
-```
+++ /dev/null
-Format: http://dep.debian.net/deps/dep5/
-
-Files: *
-Copyright: Copyright 2020, Canonical Ltd., All Rights Reserved.
-License: Apache License 2.0
- 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.
+++ /dev/null
-# Copyright 2020 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.
-name: osm-ro
-summary: Interface for relating to a OSM Resource Orchestrator
-maintainer: '"Adam Israel" <adam@adamisrael.com>'
+++ /dev/null
-#!/usr/bin/python
-# Copyright 2020 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.
-
-from charms.reactive import RelationBase
-from charms.reactive import hook
-from charms.reactive import scopes
-
-
-class OsmROProvides(RelationBase):
- scope = scopes.GLOBAL
-
- @hook("{provides:osm-ro}-relation-joined")
- def joined(self):
- self.set_state("{relation_name}.joined")
-
- @hook("{provides:osm-ro}-relation-changed")
- def changed(self):
- self.set_state("{relation_name}.ready")
-
- @hook("{provides:osm-ro}-relation-{broken,departed}")
- def broken_departed(self):
- self.remove_state("{relation_name}.ready")
- self.remove_state("{relation_name}.joined")
-
- @hook("{provides:osm-ro}-relation-broken")
- def broken(self):
- self.set_state("{relation_name}.removed")
-
- def send_connection(self, host, port=9090):
- conv = self.conversation()
- conv.set_remote("host", host)
- conv.set_remote("port", port)
+++ /dev/null
-# Copyright 2020 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.
-
-from charms.reactive import RelationBase
-from charms.reactive import hook
-from charms.reactive import scopes
-
-
-class OsmRORequires(RelationBase):
- scope = scopes.GLOBAL
-
- @hook("{requires:osm-ro}-relation-joined")
- def joined(self):
- conv = self.conversation()
- conv.set_state("{relation_name}.joined")
-
- @hook("{requires:osm-ro}-relation-changed")
- def changed(self):
- conv = self.conversation()
- if self.ros():
- conv.set_state("{relation_name}.ready")
- else:
- conv.remove_state("{relation_name}.ready")
-
- @hook("{requires:osm-ro}-relation-departed")
- def departed(self):
- conv = self.conversation()
- conv.remove_state("{relation_name}.ready")
- conv.remove_state("{relation_name}.joined")
-
- def ros(self):
- """Return the NBI's host and port.
-
- [{
- 'host': <host>,
- 'port': <port>,
- }]
- """
- ros = []
- for conv in self.conversations():
- port = conv.get_remote("port")
- host = conv.get_remote("host") or conv.get_remote("private-address")
- if host and port:
- ros.append({"host": host, "port": port})
- return ros
+++ /dev/null
-<!-- Copyright 2020 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. -->
-
-# README
-
-WIP. Layer to share common functionality to write/deploy k8s charms for OSM demo
+++ /dev/null
-# Copyright 2020 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.
\ No newline at end of file
+++ /dev/null
-# Copyright 2020 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.
-
-from charmhelpers.core.hookenv import (
- network_get,
- relation_id,
- log,
-)
-
-
-def get_service_ip(endpoint):
- try:
- info = network_get(endpoint, relation_id())
- if 'ingress-addresses' in info:
- addr = info['ingress-addresses'][0]
- if len(addr):
- return addr
- else:
- log("No ingress-addresses: {}".format(info))
- except Exception as e:
- log("Caught exception checking for service IP: {}".format(e))
-
- return None
-
-
-def is_pod_up(endpoint):
- """Check to see if the pod of a relation is up.
-
- application-vimdb: 19:29:10 INFO unit.vimdb/0.juju-log network info
-
- In the example below:
- - 10.1.1.105 is the address of the application pod.
- - 10.152.183.199 is the service cluster ip
-
- {
- 'bind-addresses': [{
- 'macaddress': '',
- 'interfacename': '',
- 'addresses': [{
- 'hostname': '',
- 'address': '10.1.1.105',
- 'cidr': ''
- }]
- }],
- 'egress-subnets': [
- '10.152.183.199/32'
- ],
- 'ingress-addresses': [
- '10.152.183.199',
- '10.1.1.105'
- ]
- }
- """
- try:
- info = network_get(endpoint, relation_id())
-
- # Check to see if the pod has been assigned it's internal and
- # external ips
- for ingress in info['ingress-addresses']:
- if len(ingress) == 0:
- return False
- except:
- return False
-
- return True
+++ /dev/null
-# Copyright 2020 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.
\ No newline at end of file
+++ /dev/null
-# Copyright 2020 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.
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- Copyright 2020 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.
-
-For those usages not covered by the Apache License, Version 2.0 please
-contact: legal@canonical.com
-
-To get in touch with the maintainers, please contact:
-osm-charmers@lists.launchpad.net -->
-
-# LCM operator Charm for Kubernetes
-
-## Requirements
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-options:
- vca_host:
- type: string
- description: "The VCA host."
- vca_port:
- type: int
- description: "The VCA port."
- vca_user:
- type: string
- description: "The VCA user name."
- vca_secret:
- type: string
- description: "The VCA user secret."
- vca_pubkey:
- type: string
- description: "The VCA public key."
- vca_cacert:
- type: string
- description: "The VCA cacert."
- vca_apiproxy:
- type: string
- description: "The VCA api proxy (native charms)"
- vca_cloud:
- type: string
- description: "The VCA lxd cloud name"
- vca_k8s_cloud:
- type: string
- description: "The VCA K8s cloud name"
- database_commonkey:
- description: Database common key
- type: string
- default: osm
- mongodb_uri:
- type: string
- description: MongoDB URI (external database)
- log_level:
- description: "Log Level"
- type: string
- default: "INFO"
- vca_model_config_agent_metadata_url:
- description: The URL of the private stream.
- type: string
- vca_model_config_agent_stream:
- description: |
- The stream to use for deploy/upgrades of agents.
- See additional info below.
- type: string
- vca_model_config_apt_ftp_proxy:
- description: The APT FTP proxy for the model.
- type: string
- vca_model_config_apt_http_proxy:
- description: The APT HTTP proxy for the model.
- type: string
- vca_model_config_apt_https_proxy:
- description: The APT HTTPS proxy for the model.
- type: string
- vca_model_config_apt_mirror:
- description: The APT mirror for the model.
- type: string
- vca_model_config_apt_no_proxy:
- description: The APT no proxy for the model.
- type: string
- vca_model_config_automatically_retry_hooks:
- description: Set the policy on retying failed hooks.
- type: boolean
- vca_model_config_backup_dir:
- description: Backup directory
- type: string
- vca_model_config_cloudinit_userdata:
- description: Cloudinit userdata
- type: string
- vca_model_config_container_image_metadata_url:
- description: |
- Corresponds to 'image-metadata-url' (see below) for cloud-hosted
- KVM guests or LXD containers. Not needed for the localhost cloud.
- type: string
- vca_model_config_container_image_stream:
- description: |
- Corresponds to 'image-stream' (see below) for cloud-hosted KVM
- guests or LXD containers. Not needed for the localhost cloud.
- type: string
- vca_model_config_container_inherit_properties:
- description: |
- Set parameters to be inherited from a machine toits hosted
- containers (KVM or LXD).
- type: string
- vca_model_config_container_networking_method:
- description: |
- The FAN networking mode to use. Default values can be provider-specific.
- type: string
- vca_model_config_default_series:
- description: The default series of Ubuntu to use for deploying charms.
- type: string
- vca_model_config_default_space:
- description: |
- The space used as the default binding when deploying charms.
- Will be "alpha" by default.
- type: string
- vca_model_config_development:
- description: Set whether the model is in development mode.
- type: boolean
- vca_model_config_disable_network_management:
- description: |
- Set whether to give network control to the provider instead
- of Juju controlling configuration.
- type: boolean
- vca_model_config_egress_subnets:
- description: Egress subnets
- type: string
- vca_model_config_enable_os_refresh_update:
- description: |
- Set whether newly provisioned instances should run their
- respective OS's update capability.
- type: boolean
- vca_model_config_enable_os_upgrade:
- description: |
- Set whether newly provisioned instances should run their
- respective OS's upgrade capability.
- type: boolean
- vca_model_config_fan_config:
- description: |
- The FAN overlay and underlay networks in
- CIDR notation (space-separated).
- type: string
- vca_model_config_firewall_mode:
- description: The mode to use for network firewalling.
- type: string
- vca_model_config_ftp_proxy:
- description: |
- The FTP proxy value to configure on instances,
- in the FTP_PROXY environment variable.
- type: string
- vca_model_config_http_proxy:
- description: |
- The HTTP proxy value to configure on instances,
- in the HTTP_PROXY environment variable.
- type: string
- vca_model_config_https_proxy:
- description: |
- The HTTPS proxy value to configure on instances,
- in the HTTPS_PROXY environment variable.
- type: string
- vca_model_config_ignore_machine_addresses:
- description: |
- When true, the machine worker will not look up
- or discover any machine addresses.
- type: boolean
- vca_model_config_image_metadata_url:
- description: |
- The URL at which the metadata used to locate
- OS image ids is located.
- type: string
- vca_model_config_image_stream:
- description: |
- The simplestreams stream used to identify which image
- ids to search when starting an instance.
- type: string
- vca_model_config_juju_ftp_proxy:
- description: The charm-centric FTP proxy value.
- type: string
- vca_model_config_juju_http_proxy:
- description: The charm-centric HTTP proxy value.
- type: string
- vca_model_config_juju_https_proxy:
- description: The charm-centric HTTPS proxy value.
- type: string
- vca_model_config_juju_no_proxy:
- description: The charm-centric no-proxy value.
- type: string
- vca_model_config_logforward_enabled:
- description: Set whether the log forward function is enabled.
- type: boolean
- vca_model_config_logging_config:
- description: |
- The configuration string to use when configuring Juju agent logging
- type: string
- vca_model_config_lxd_snap_channel:
- description: LXD snap channel
- type: string
- vca_model_config_max_action_results_age:
- description: The maximum aget for status action results entries
- type: string
- vca_model_config_max_action_results_size:
- description: The maximum size for status action results entries
- type: string
- vca_model_config_max_status_history_age:
- description: |
- The maximum age for status history entries before they are pruned,
- in a human-readable time format.
- type: string
- vca_model_config_max_status_history_size:
- description: |
- The maximum size for the status history collection,
- in human-readable memory format.
- type: string
- vca_model_config_net_bond_reconfigure_delay:
- description: Net bond reconfigure delay
- type: int
- vca_model_config_no_proxy:
- description: List of domain addresses not to be proxied (comma-separated).
- type: string
- vca_model_config_provisioner_harvest_mode:
- description: Set what to do with unknown machines.
- type: string
- vca_model_config_proxy_ssh:
- description: |
- Set whether SSH commands should be proxied through the API server.
- type: boolean
- vca_model_config_snap_http_proxy:
- description: The snap-centric HTTP proxy value.
- type: string
- vca_model_config_snap_https_proxy:
- description: The snap-centric HTTPS proxy value.
- type: string
- vca_model_config_snap_store_assertions:
- description: |
- The collection of snap store assertions.
- Each entry should contain the snap store ID.
- type: string
- vca_model_config_snap_store_proxy:
- description: The snap store ID.
- type: string
- vca_model_config_snap_store_proxy_url:
- description: The snap store proxy url
- type: string
- vca_model_config_ssl_hostname_verification:
- description: Set whether SSL hostname verification is enabled.
- type: boolean
- vca_model_config_test_mode:
- description: |
- Set whether the model is intended for testing.
- If true, accessing the charm store does not affect
- statistical data of the store.
- type: boolean
- vca_model_config_transmit_vendor_metrics:
- description: |
- Set whether the controller will send metrics collected from
- this model for use in anonymized aggregate analytics.
- type: boolean
- vca_model_config_update_status_hook_interval:
- description: |
- The run frequency of the update-status hook.
- The value has a random +/- 20% offset applied to avoid hooks
- for all units firing at once. Value change only honoured
- during controller and model creation
- (bootstrap --config and add-model --config).
- type: string
- vca_stablerepourl:
- description: Stable repository URL for Helm charts
- type: string
- default: https://charts.helm.sh/stable
- vca_helm_ca_certs:
- description: CA certificates to validate access to Helm repository
- type: string
- default: ""
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- debug_mode:
- description: |
- If true, debug mode is activated. It means that the service will not run,
- and instead, the command for the container will be a `sleep infinity`.
- Note: If enabled, security_context will be disabled.
- type: boolean
- default: false
- debug_pubkey:
- description: |
- Public SSH key that will be injected to the application pod.
- type: string
- debug_lcm_local_path:
- description: |
- Local full path to the LCM project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_n2vc_local_path:
- description: |
- Local full path to the N2VC project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_common_local_path:
- description: |
- Local full path to the COMMON project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
+++ /dev/null
-# Copyright 2022 Canonical Ltd.
-# See LICENSE file for licensing details.
-#
-# 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.
-
-"""Kafka library.
-
-This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
-`kafka` [interface](https://juju.is/docs/sdk/relations).
-
-The *provider* side of this interface is implemented by the
-[kafka-k8s Charmed Operator](https://charmhub.io/kafka-k8s).
-
-Any Charmed Operator that *requires* Kafka for providing its
-service should implement the *requirer* side of this interface.
-
-In a nutshell using this library to implement a Charmed Operator *requiring*
-Kafka would look like
-
-```
-$ charmcraft fetch-lib charms.kafka_k8s.v0.kafka
-```
-
-`metadata.yaml`:
-
-```
-requires:
- kafka:
- interface: kafka
- limit: 1
-```
-
-`src/charm.py`:
-
-```
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.charm import CharmBase
-
-
-class MyCharm(CharmBase):
-
- on = KafkaEvents()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.kafka = KafkaRequires(self)
- self.framework.observe(
- self.on.kafka_available,
- self._on_kafka_available,
- )
- self.framework.observe(
- self.on.kafka_broken,
- self._on_kafka_broken,
- )
-
- def _on_kafka_available(self, event):
- # Get Kafka host and port
- host: str = self.kafka.host
- port: int = self.kafka.port
- # host => "kafka-k8s"
- # port => 9092
-
- def _on_kafka_broken(self, event):
- # Stop service
- # ...
- self.unit.status = BlockedStatus("need kafka relation")
-```
-
-You can file bugs
-[here](https://github.com/charmed-osm/kafka-k8s-operator/issues)!
-"""
-
-from typing import Optional
-
-from ops.charm import CharmBase, CharmEvents
-from ops.framework import EventBase, EventSource, Object
-
-# The unique Charmhub library identifier, never change it
-from ops.model import Relation
-
-LIBID = "eacc8c85082347c9aae740e0220b8376"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 0
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 3
-
-
-KAFKA_HOST_APP_KEY = "host"
-KAFKA_PORT_APP_KEY = "port"
-
-
-class _KafkaAvailableEvent(EventBase):
- """Event emitted when Kafka is available."""
-
-
-class _KafkaBrokenEvent(EventBase):
- """Event emitted when Kafka relation is broken."""
-
-
-class KafkaEvents(CharmEvents):
- """Kafka events.
-
- This class defines the events that Kafka can emit.
-
- Events:
- kafka_available (_KafkaAvailableEvent)
- """
-
- kafka_available = EventSource(_KafkaAvailableEvent)
- kafka_broken = EventSource(_KafkaBrokenEvent)
-
-
-class KafkaRequires(Object):
- """Requires-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self.charm = charm
- self._endpoint_name = endpoint_name
-
- # Observe relation events
- event_observe_mapping = {
- charm.on[self._endpoint_name].relation_changed: self._on_relation_changed,
- charm.on[self._endpoint_name].relation_broken: self._on_relation_broken,
- }
- for event, observer in event_observe_mapping.items():
- self.framework.observe(event, observer)
-
- def _on_relation_changed(self, event) -> None:
- if event.relation.app and all(
- key in event.relation.data[event.relation.app]
- for key in (KAFKA_HOST_APP_KEY, KAFKA_PORT_APP_KEY)
- ):
- self.charm.on.kafka_available.emit()
-
- def _on_relation_broken(self, _) -> None:
- self.charm.on.kafka_broken.emit()
-
- @property
- def host(self) -> str:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- relation.data[relation.app].get(KAFKA_HOST_APP_KEY)
- if relation and relation.app
- else None
- )
-
- @property
- def port(self) -> int:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- int(relation.data[relation.app].get(KAFKA_PORT_APP_KEY))
- if relation and relation.app
- else None
- )
-
-
-class KafkaProvides(Object):
- """Provides-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self._endpoint_name = endpoint_name
-
- def set_host_info(self, host: str, port: int, relation: Optional[Relation] = None) -> None:
- """Set Kafka host and port.
-
- This function writes in the application data of the relation, therefore,
- only the unit leader can call it.
-
- Args:
- host (str): Kafka hostname or IP address.
- port (int): Kafka port.
- relation (Optional[Relation]): Relation to update.
- If not specified, all relations will be updated.
-
- Raises:
- Exception: if a non-leader unit calls this function.
- """
- if not self.model.unit.is_leader():
- raise Exception("only the leader set host information.")
-
- if relation:
- self._update_relation_data(host, port, relation)
- return
-
- for relation in self.model.relations[self._endpoint_name]:
- self._update_relation_data(host, port, relation)
-
- def _update_relation_data(self, host: str, port: int, relation: Relation) -> None:
- """Update data in relation if needed."""
- relation.data[self.model.app][KAFKA_HOST_APP_KEY] = host
- relation.data[self.model.app][KAFKA_PORT_APP_KEY] = str(port)
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-name: osm-lcm
-summary: OSM Lifecycle Management (LCM)
-description: |
- A CAAS charm to deploy OSM's Lifecycle Management (LCM).
-series:
- - kubernetes
-tags:
- - kubernetes
- - osm
- - lcm
-min-juju-version: 2.8.0
-deployment:
- type: stateless
- service: cluster
-resources:
- image:
- type: oci-image
- description: OSM docker image for LCM
- upstream-source: "opensourcemano/lcm:latest"
-requires:
- kafka:
- interface: kafka
- limit: 1
- mongodb:
- interface: mongodb
- limit: 1
- ro:
- interface: http
- limit: 1
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
\ No newline at end of file
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-import logging
-from typing import NoReturn, Optional
-
-
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.http import HttpClient
-from opslib.osm.interfaces.mongo import MongoClient
-from opslib.osm.pod import ContainerV3Builder, PodRestartPolicy, PodSpecV3Builder
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-PORT = 9999
-
-
-class ConfigModel(ModelValidator):
- vca_host: Optional[str]
- vca_port: Optional[int]
- vca_user: Optional[str]
- vca_secret: Optional[str]
- vca_pubkey: Optional[str]
- vca_cacert: Optional[str]
- vca_cloud: Optional[str]
- vca_k8s_cloud: Optional[str]
- database_commonkey: str
- mongodb_uri: Optional[str]
- log_level: str
- vca_apiproxy: Optional[str]
- # Model-config options
- vca_model_config_agent_metadata_url: Optional[str]
- vca_model_config_agent_stream: Optional[str]
- vca_model_config_apt_ftp_proxy: Optional[str]
- vca_model_config_apt_http_proxy: Optional[str]
- vca_model_config_apt_https_proxy: Optional[str]
- vca_model_config_apt_mirror: Optional[str]
- vca_model_config_apt_no_proxy: Optional[str]
- vca_model_config_automatically_retry_hooks: Optional[bool]
- vca_model_config_backup_dir: Optional[str]
- vca_model_config_cloudinit_userdata: Optional[str]
- vca_model_config_container_image_metadata_url: Optional[str]
- vca_model_config_container_image_stream: Optional[str]
- vca_model_config_container_inherit_properties: Optional[str]
- vca_model_config_container_networking_method: Optional[str]
- vca_model_config_default_series: Optional[str]
- vca_model_config_default_space: Optional[str]
- vca_model_config_development: Optional[bool]
- vca_model_config_disable_network_management: Optional[bool]
- vca_model_config_egress_subnets: Optional[str]
- vca_model_config_enable_os_refresh_update: Optional[bool]
- vca_model_config_enable_os_upgrade: Optional[bool]
- vca_model_config_fan_config: Optional[str]
- vca_model_config_firewall_mode: Optional[str]
- vca_model_config_ftp_proxy: Optional[str]
- vca_model_config_http_proxy: Optional[str]
- vca_model_config_https_proxy: Optional[str]
- vca_model_config_ignore_machine_addresses: Optional[bool]
- vca_model_config_image_metadata_url: Optional[str]
- vca_model_config_image_stream: Optional[str]
- vca_model_config_juju_ftp_proxy: Optional[str]
- vca_model_config_juju_http_proxy: Optional[str]
- vca_model_config_juju_https_proxy: Optional[str]
- vca_model_config_juju_no_proxy: Optional[str]
- vca_model_config_logforward_enabled: Optional[bool]
- vca_model_config_logging_config: Optional[str]
- vca_model_config_lxd_snap_channel: Optional[str]
- vca_model_config_max_action_results_age: Optional[str]
- vca_model_config_max_action_results_size: Optional[str]
- vca_model_config_max_status_history_age: Optional[str]
- vca_model_config_max_status_history_size: Optional[str]
- vca_model_config_net_bond_reconfigure_delay: Optional[str]
- vca_model_config_no_proxy: Optional[str]
- vca_model_config_provisioner_harvest_mode: Optional[str]
- vca_model_config_proxy_ssh: Optional[bool]
- vca_model_config_snap_http_proxy: Optional[str]
- vca_model_config_snap_https_proxy: Optional[str]
- vca_model_config_snap_store_assertions: Optional[str]
- vca_model_config_snap_store_proxy: Optional[str]
- vca_model_config_snap_store_proxy_url: Optional[str]
- vca_model_config_ssl_hostname_verification: Optional[bool]
- vca_model_config_test_mode: Optional[bool]
- vca_model_config_transmit_vendor_metrics: Optional[bool]
- vca_model_config_update_status_hook_interval: Optional[str]
- vca_stablerepourl: Optional[str]
- vca_helm_ca_certs: Optional[str]
- image_pull_policy: str
- debug_mode: bool
- security_context: bool
-
- @validator("log_level")
- def validate_log_level(cls, v):
- if v not in {"INFO", "DEBUG"}:
- raise ValueError("value must be INFO or DEBUG")
- return v
-
- @validator("mongodb_uri")
- def validate_mongodb_uri(cls, v):
- if v and not v.startswith("mongodb://"):
- raise ValueError("mongodb_uri is not properly formed")
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
-
-class LcmCharm(CharmedOsmBase):
- on = KafkaEvents()
-
- def __init__(self, *args) -> NoReturn:
- super().__init__(
- *args,
- oci_image="image",
- vscode_workspace=VSCODE_WORKSPACE,
- )
- if self.config.get("debug_mode"):
- self.enable_debug_mode(
- pubkey=self.config.get("debug_pubkey"),
- hostpaths={
- "LCM": {
- "hostpath": self.config.get("debug_lcm_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_lcm",
- },
- "N2VC": {
- "hostpath": self.config.get("debug_n2vc_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/n2vc",
- },
- "osm_common": {
- "hostpath": self.config.get("debug_common_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_common",
- },
- },
- )
- self.kafka = KafkaRequires(self)
- self.framework.observe(self.on.kafka_available, self.configure_pod)
- self.framework.observe(self.on.kafka_broken, self.configure_pod)
-
- self.mongodb_client = MongoClient(self, "mongodb")
- self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
-
- self.ro_client = HttpClient(self, "ro")
- self.framework.observe(self.on["ro"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["ro"].relation_broken, self.configure_pod)
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if not self.kafka.host or not self.kafka.port:
- missing_relations.append("kafka")
- if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit():
- missing_relations.append("mongodb")
- if self.ro_client.is_missing_data_in_app():
- missing_relations.append("ro")
-
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
-
- if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit():
- raise Exception("Mongodb data cannot be provided via config and relation")
-
- # Check relations
- self._check_missing_dependencies(config)
-
- security_context_enabled = (
- config.security_context if not config.debug_mode else False
- )
-
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=security_context_enabled
- )
-
- # Add secrets to the pod
- mongodb_secret_name = f"{self.app.name}-mongodb-secret"
- pod_spec_builder.add_secret(
- mongodb_secret_name,
- {
- "uri": config.mongodb_uri or self.mongodb_client.connection_string,
- "commonkey": config.database_commonkey,
- "helm_ca_certs": config.vca_helm_ca_certs,
- },
- )
-
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=security_context_enabled,
- )
- container_builder.add_port(name=self.app.name, port=PORT)
- container_builder.add_envs(
- {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMLCM_GLOBAL_LOGLEVEL": config.log_level,
- # RO configuration
- "OSMLCM_RO_HOST": self.ro_client.host,
- "OSMLCM_RO_PORT": self.ro_client.port,
- "OSMLCM_RO_TENANT": "osm",
- # Kafka configuration
- "OSMLCM_MESSAGE_DRIVER": "kafka",
- "OSMLCM_MESSAGE_HOST": self.kafka.host,
- "OSMLCM_MESSAGE_PORT": self.kafka.port,
- # Database configuration
- "OSMLCM_DATABASE_DRIVER": "mongo",
- # Storage configuration
- "OSMLCM_STORAGE_DRIVER": "mongo",
- "OSMLCM_STORAGE_PATH": "/app/storage",
- "OSMLCM_STORAGE_COLLECTION": "files",
- "OSMLCM_VCA_STABLEREPOURL": config.vca_stablerepourl,
- }
- )
- container_builder.add_secret_envs(
- secret_name=mongodb_secret_name,
- envs={
- "OSMLCM_DATABASE_URI": "uri",
- "OSMLCM_DATABASE_COMMONKEY": "commonkey",
- "OSMLCM_STORAGE_URI": "uri",
- "OSMLCM_VCA_HELM_CA_CERTS": "helm_ca_certs",
- },
- )
- if config.vca_host:
- vca_secret_name = f"{self.app.name}-vca-secret"
- pod_spec_builder.add_secret(
- vca_secret_name,
- {
- "host": config.vca_host,
- "port": str(config.vca_port),
- "user": config.vca_user,
- "pubkey": config.vca_pubkey,
- "secret": config.vca_secret,
- "cacert": config.vca_cacert,
- "cloud": config.vca_cloud,
- "k8s_cloud": config.vca_k8s_cloud,
- },
- )
- container_builder.add_secret_envs(
- secret_name=vca_secret_name,
- envs={
- # VCA configuration
- "OSMLCM_VCA_HOST": "host",
- "OSMLCM_VCA_PORT": "port",
- "OSMLCM_VCA_USER": "user",
- "OSMLCM_VCA_PUBKEY": "pubkey",
- "OSMLCM_VCA_SECRET": "secret",
- "OSMLCM_VCA_CACERT": "cacert",
- "OSMLCM_VCA_CLOUD": "cloud",
- "OSMLCM_VCA_K8S_CLOUD": "k8s_cloud",
- },
- )
- if config.vca_apiproxy:
- container_builder.add_env("OSMLCM_VCA_APIPROXY", config.vca_apiproxy)
-
- model_config_envs = {
- f"OSMLCM_{k.upper()}": v
- for k, v in self.config.items()
- if k.startswith("vca_model_config")
- }
- if model_config_envs:
- container_builder.add_envs(model_config_envs)
- container = container_builder.build()
-
- # Add container to pod spec
- pod_spec_builder.add_container(container)
-
- # Add restart policy
- restart_policy = PodRestartPolicy()
- restart_policy.add_secrets()
- pod_spec_builder.set_restart_policy(restart_policy)
-
- return pod_spec_builder.build()
-
-
-VSCODE_WORKSPACE = {
- "folders": [
- {"path": "/usr/lib/python3/dist-packages/osm_lcm"},
- {"path": "/usr/lib/python3/dist-packages/n2vc"},
- {"path": "/usr/lib/python3/dist-packages/osm_common"},
- ],
- "settings": {},
- "launch": {
- "version": "0.2.0",
- "configurations": [
- {
- "name": "LCM",
- "type": "python",
- "request": "launch",
- "module": "osm_lcm.lcm",
- "justMyCode": False,
- }
- ],
- },
-}
-
-
-if __name__ == "__main__":
- main(LcmCharm)
-
-
-# class ConfigurePodEvent(EventBase):
-# """Configure Pod event"""
-
-# pass
-
-
-# class LcmEvents(CharmEvents):
-# """LCM Events"""
-
-# configure_pod = EventSource(ConfigurePodEvent)
-
-
-# class LcmCharm(CharmBase):
-# """LCM Charm."""
-
-# state = StoredState()
-# on = LcmEvents()
-
-# def __init__(self, *args) -> NoReturn:
-# """LCM Charm constructor."""
-# super().__init__(*args)
-
-# # Internal state initialization
-# self.state.set_default(pod_spec=None)
-
-# # Message bus data initialization
-# self.state.set_default(message_host=None)
-# self.state.set_default(message_port=None)
-
-# # Database data initialization
-# self.state.set_default(database_uri=None)
-
-# # RO data initialization
-# self.state.set_default(ro_host=None)
-# self.state.set_default(ro_port=None)
-
-# self.port = LCM_PORT
-# self.image = OCIImageResource(self, "image")
-
-# # Registering regular events
-# self.framework.observe(self.on.start, self.configure_pod)
-# self.framework.observe(self.on.config_changed, self.configure_pod)
-# self.framework.observe(self.on.upgrade_charm, self.configure_pod)
-
-# # Registering custom internal events
-# self.framework.observe(self.on.configure_pod, self.configure_pod)
-
-# # Registering required relation events
-# self.framework.observe(
-# self.on.kafka_relation_changed, self._on_kafka_relation_changed
-# )
-# self.framework.observe(
-# self.on.mongodb_relation_changed, self._on_mongodb_relation_changed
-# )
-# self.framework.observe(
-# self.on.ro_relation_changed, self._on_ro_relation_changed
-# )
-
-# # Registering required relation broken events
-# self.framework.observe(
-# self.on.kafka_relation_broken, self._on_kafka_relation_broken
-# )
-# self.framework.observe(
-# self.on.mongodb_relation_broken, self._on_mongodb_relation_broken
-# )
-# self.framework.observe(
-# self.on.ro_relation_broken, self._on_ro_relation_broken
-# )
-
-# def _on_kafka_relation_changed(self, event: EventBase) -> NoReturn:
-# """Reads information about the kafka relation.
-
-# Args:
-# event (EventBase): Kafka relation event.
-# """
-# message_host = event.relation.data[event.unit].get("host")
-# message_port = event.relation.data[event.unit].get("port")
-
-# if (
-# message_host
-# and message_port
-# and (
-# self.state.message_host != message_host
-# or self.state.message_port != message_port
-# )
-# ):
-# self.state.message_host = message_host
-# self.state.message_port = message_port
-# self.on.configure_pod.emit()
-
-# def _on_kafka_relation_broken(self, event: EventBase) -> NoReturn:
-# """Clears data from kafka relation.
-
-# Args:
-# event (EventBase): Kafka relation event.
-# """
-# self.state.message_host = None
-# self.state.message_port = None
-# self.on.configure_pod.emit()
-
-# def _on_mongodb_relation_changed(self, event: EventBase) -> NoReturn:
-# """Reads information about the DB relation.
-
-# Args:
-# event (EventBase): DB relation event.
-# """
-# database_uri = event.relation.data[event.unit].get("connection_string")
-
-# if database_uri and self.state.database_uri != database_uri:
-# self.state.database_uri = database_uri
-# self.on.configure_pod.emit()
-
-# def _on_mongodb_relation_broken(self, event: EventBase) -> NoReturn:
-# """Clears data from mongodb relation.
-
-# Args:
-# event (EventBase): DB relation event.
-# """
-# self.state.database_uri = None
-# self.on.configure_pod.emit()
-
-# def _on_ro_relation_changed(self, event: EventBase) -> NoReturn:
-# """Reads information about the RO relation.
-
-# Args:
-# event (EventBase): Keystone relation event.
-# """
-# ro_host = event.relation.data[event.unit].get("host")
-# ro_port = event.relation.data[event.unit].get("port")
-
-# if (
-# ro_host
-# and ro_port
-# and (self.state.ro_host != ro_host or self.state.ro_port != ro_port)
-# ):
-# self.state.ro_host = ro_host
-# self.state.ro_port = ro_port
-# self.on.configure_pod.emit()
-
-# def _on_ro_relation_broken(self, event: EventBase) -> NoReturn:
-# """Clears data from ro relation.
-
-# Args:
-# event (EventBase): Keystone relation event.
-# """
-# self.state.ro_host = None
-# self.state.ro_port = None
-# self.on.configure_pod.emit()
-
-# def _missing_relations(self) -> str:
-# """Checks if there missing relations.
-
-# Returns:
-# str: string with missing relations
-# """
-# data_status = {
-# "kafka": self.state.message_host,
-# "mongodb": self.state.database_uri,
-# "ro": self.state.ro_host,
-# }
-
-# missing_relations = [k for k, v in data_status.items() if not v]
-
-# return ", ".join(missing_relations)
-
-# @property
-# def relation_state(self) -> Dict[str, Any]:
-# """Collects relation state configuration for pod spec assembly.
-
-# Returns:
-# Dict[str, Any]: relation state information.
-# """
-# relation_state = {
-# "message_host": self.state.message_host,
-# "message_port": self.state.message_port,
-# "database_uri": self.state.database_uri,
-# "ro_host": self.state.ro_host,
-# "ro_port": self.state.ro_port,
-# }
-
-# return relation_state
-
-# def configure_pod(self, event: EventBase) -> NoReturn:
-# """Assemble the pod spec and apply it, if possible.
-
-# Args:
-# event (EventBase): Hook or Relation event that started the
-# function.
-# """
-# if missing := self._missing_relations():
-# self.unit.status = BlockedStatus(
-# "Waiting for {0} relation{1}".format(
-# missing, "s" if "," in missing else ""
-# )
-# )
-# return
-
-# if not self.unit.is_leader():
-# self.unit.status = ActiveStatus("ready")
-# return
-
-# self.unit.status = MaintenanceStatus("Assembling pod spec")
-
-# # Fetch image information
-# try:
-# self.unit.status = MaintenanceStatus("Fetching image information")
-# image_info = self.image.fetch()
-# except OCIImageResourceError:
-# self.unit.status = BlockedStatus("Error fetching image information")
-# return
-
-# try:
-# pod_spec = make_pod_spec(
-# image_info,
-# self.model.config,
-# self.relation_state,
-# self.model.app.name,
-# self.port,
-# )
-# except ValueError as exc:
-# logger.exception("Config/Relation data validation error")
-# self.unit.status = BlockedStatus(str(exc))
-# return
-
-# if self.state.pod_spec != pod_spec:
-# self.model.pod.set_spec(pod_spec)
-# self.state.pod_spec = pod_spec
-
-# self.unit.status = ActiveStatus("ready")
-
-
-# if __name__ == "__main__":
-# main(LcmCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import logging
-from typing import Any, Dict, List, NoReturn
-
-logger = logging.getLogger(__name__)
-
-
-def _validate_data(
- config_data: Dict[str, Any], relation_data: Dict[str, Any]
-) -> NoReturn:
- """Validate input data.
-
- Args:
- config_data (Dict[str, Any]): configuration data.
- relation_data (Dict[str, Any]): relation data.
- """
- config_validators = {
- "database_commonkey": lambda value, _: (
- isinstance(value, str) and len(value) > 1
- ),
- "log_level": lambda value, _: (
- isinstance(value, str) and value in ("INFO", "DEBUG")
- ),
- "vca_host": lambda value, _: isinstance(value, str) and len(value) > 1,
- "vca_port": lambda value, _: isinstance(value, int) and value > 0,
- "vca_user": lambda value, _: isinstance(value, str) and len(value) > 1,
- "vca_pubkey": lambda value, _: isinstance(value, str) and len(value) > 1,
- "vca_password": lambda value, _: isinstance(value, str) and len(value) > 1,
- "vca_cacert": lambda value, _: isinstance(value, str),
- "vca_cloud": lambda value, _: isinstance(value, str) and len(value) > 1,
- "vca_k8s_cloud": lambda value, _: isinstance(value, str) and len(value) > 1,
- "vca_apiproxy": lambda value, _: (isinstance(value, str) and len(value) > 1)
- if value
- else True,
- }
- relation_validators = {
- "ro_host": lambda value, _: isinstance(value, str) and len(value) > 1,
- "ro_port": lambda value, _: isinstance(value, int) and value > 0,
- "message_host": lambda value, _: isinstance(value, str) and len(value) > 1,
- "message_port": lambda value, _: isinstance(value, int) and value > 0,
- "database_uri": lambda value, _: isinstance(value, str) and len(value) > 1,
- }
- problems = []
-
- for key, validator in config_validators.items():
- valid = validator(config_data.get(key), config_data)
-
- if not valid:
- problems.append(key)
-
- for key, validator in relation_validators.items():
- valid = validator(relation_data.get(key), relation_data)
-
- if not valid:
- problems.append(key)
-
- if len(problems) > 0:
- raise ValueError("Errors found in: {}".format(", ".join(problems)))
-
-
-def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
- """Generate pod ports details.
-
- Args:
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod port details.
- """
- return [{"name": "lcm", "containerPort": port, "protocol": "TCP"}]
-
-
-def _make_pod_envconfig(
- config: Dict[str, Any], relation_state: Dict[str, Any]
-) -> Dict[str, Any]:
- """Generate pod environment configuration.
-
- Args:
- config (Dict[str, Any]): configuration information.
- relation_state (Dict[str, Any]): relation state information.
-
- Returns:
- Dict[str, Any]: pod environment configuration.
- """
- envconfig = {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMLCM_GLOBAL_LOGLEVEL": config["log_level"],
- # RO configuration
- "OSMLCM_RO_HOST": relation_state["ro_host"],
- "OSMLCM_RO_PORT": relation_state["ro_port"],
- "OSMLCM_RO_TENANT": "osm",
- # Kafka configuration
- "OSMLCM_MESSAGE_DRIVER": "kafka",
- "OSMLCM_MESSAGE_HOST": relation_state["message_host"],
- "OSMLCM_MESSAGE_PORT": relation_state["message_port"],
- # Database configuration
- "OSMLCM_DATABASE_DRIVER": "mongo",
- "OSMLCM_DATABASE_URI": relation_state["database_uri"],
- "OSMLCM_DATABASE_COMMONKEY": config["database_commonkey"],
- # Storage configuration
- "OSMLCM_STORAGE_DRIVER": "mongo",
- "OSMLCM_STORAGE_PATH": "/app/storage",
- "OSMLCM_STORAGE_COLLECTION": "files",
- "OSMLCM_STORAGE_URI": relation_state["database_uri"],
- # VCA configuration
- "OSMLCM_VCA_HOST": config["vca_host"],
- "OSMLCM_VCA_PORT": config["vca_port"],
- "OSMLCM_VCA_USER": config["vca_user"],
- "OSMLCM_VCA_PUBKEY": config["vca_pubkey"],
- "OSMLCM_VCA_SECRET": config["vca_password"],
- "OSMLCM_VCA_CACERT": config["vca_cacert"],
- "OSMLCM_VCA_CLOUD": config["vca_cloud"],
- "OSMLCM_VCA_K8S_CLOUD": config["vca_k8s_cloud"],
- }
-
- if "vca_apiproxy" in config and config["vca_apiproxy"]:
- envconfig["OSMLCM_VCA_APIPROXY"] = config["vca_apiproxy"]
-
- return envconfig
-
-
-def _make_startup_probe() -> Dict[str, Any]:
- """Generate startup probe.
-
- Returns:
- Dict[str, Any]: startup probe.
- """
- return {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
-
-def _make_readiness_probe(port: int) -> Dict[str, Any]:
- """Generate readiness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: readiness probe.
- """
- return {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
-
-def _make_liveness_probe(port: int) -> Dict[str, Any]:
- """Generate liveness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: liveness probe.
- """
- return {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
-
-def make_pod_spec(
- image_info: Dict[str, str],
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
- app_name: str = "lcm",
- port: int = 9999,
-) -> Dict[str, Any]:
- """Generate the pod spec information.
-
- Args:
- image_info (Dict[str, str]): Object provided by
- OCIImageResource("image").fetch().
- config (Dict[str, Any]): Configuration information.
- relation_state (Dict[str, Any]): Relation state information.
- app_name (str, optional): Application name. Defaults to "lcm".
- port (int, optional): Port for the container. Defaults to 9999.
-
- Returns:
- Dict[str, Any]: Pod spec dictionary for the charm.
- """
- if not image_info:
- return None
-
- _validate_data(config, relation_state)
-
- ports = _make_pod_ports(port)
- env_config = _make_pod_envconfig(config, relation_state)
-
- return {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": ports,
- "envConfig": env_config,
- }
- ],
- "kubernetesResources": {
- "ingressResources": [],
- },
- }
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import sys
-from typing import NoReturn
-import unittest
-
-from charm import LcmCharm
-import mock
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-class TestCharm(unittest.TestCase):
- """LCM Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(LcmCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "vca_host": "192.168.0.13",
- "vca_port": 17070,
- "vca_user": "admin",
- "vca_secret": "admin",
- "vca_pubkey": "key",
- "vca_cacert": "cacert",
- "vca_cloud": "cloud",
- "vca_k8s_cloud": "k8scloud",
- "database_commonkey": "commonkey",
- "mongodb_uri": "",
- "log_level": "INFO",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mongodb", "kafka", "ro"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations_and_mongodb_config(
- self,
- ) -> NoReturn:
- "Test with relations and mongodb config"
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- self.initialize_ro_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_ro_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_exception_mongodb_relation_and_config(
- self,
- ) -> NoReturn:
- "Test with all relations and config for mongodb. Must fail"
- self.initialize_mongo_relation()
- self.initialize_mongo_config()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- # def test_build_pod_spec(
- # self,
- # ) -> NoReturn:
- # expected_config = {
- # "OSMLCM_GLOBAL_LOGLEVEL": self.config["log_level"],
- # "OSMLCM_DATABASE_COMMONKEY": self.config["database_commonkey"],
- # }
- # expected_config.update(
- # {
- # f"OSMLCM_{k.upper()}": v
- # for k, v in self.config.items()
- # if k.startswith("vca_")
- # }
- # )
- # self.harness.charm._check_missing_dependencies = mock.Mock()
- # pod_spec = self.harness.charm.build_pod_spec(
- # {"imageDetails": {"imagePath": "lcm-image"}}
- # )
- # actual_config = pod_spec["containers"][0]["envConfig"]
-
- # self.assertDictContainsSubset(
- # expected_config,
- # actual_config,
- # )
- # for config_key in actual_config:
- # self.assertNotIn("VCA_MODEL_CONFIG", config_key)
-
- def test_build_pod_spec_with_model_config(
- self,
- ) -> NoReturn:
- self.harness.update_config(
- {
- "vca_model_config_agent_metadata_url": "string",
- "vca_model_config_agent_stream": "string",
- "vca_model_config_apt_ftp_proxy": "string",
- "vca_model_config_apt_http_proxy": "string",
- "vca_model_config_apt_https_proxy": "string",
- "vca_model_config_apt_mirror": "string",
- "vca_model_config_apt_no_proxy": "string",
- "vca_model_config_automatically_retry_hooks": False,
- "vca_model_config_backup_dir": "string",
- "vca_model_config_cloudinit_userdata": "string",
- "vca_model_config_container_image_metadata_url": "string",
- "vca_model_config_container_image_stream": "string",
- "vca_model_config_container_inherit_properties": "string",
- "vca_model_config_container_networking_method": "string",
- "vca_model_config_default_series": "string",
- "vca_model_config_default_space": "string",
- "vca_model_config_development": False,
- "vca_model_config_disable_network_management": False,
- "vca_model_config_egress_subnets": "string",
- "vca_model_config_enable_os_refresh_update": False,
- "vca_model_config_enable_os_upgrade": False,
- "vca_model_config_fan_config": "string",
- "vca_model_config_firewall_mode": "string",
- "vca_model_config_ftp_proxy": "string",
- "vca_model_config_http_proxy": "string",
- "vca_model_config_https_proxy": "string",
- "vca_model_config_ignore_machine_addresses": False,
- "vca_model_config_image_metadata_url": "string",
- "vca_model_config_image_stream": "string",
- "vca_model_config_juju_ftp_proxy": "string",
- "vca_model_config_juju_http_proxy": "string",
- "vca_model_config_juju_https_proxy": "string",
- "vca_model_config_juju_no_proxy": "string",
- "vca_model_config_logforward_enabled": False,
- "vca_model_config_logging_config": "string",
- "vca_model_config_lxd_snap_channel": "string",
- "vca_model_config_max_action_results_age": "string",
- "vca_model_config_max_action_results_size": "string",
- "vca_model_config_max_status_history_age": "string",
- "vca_model_config_max_status_history_size": "string",
- "vca_model_config_net_bond_reconfigure_delay": "string",
- "vca_model_config_no_proxy": "string",
- "vca_model_config_provisioner_harvest_mode": "string",
- "vca_model_config_proxy_ssh": False,
- "vca_model_config_snap_http_proxy": "string",
- "vca_model_config_snap_https_proxy": "string",
- "vca_model_config_snap_store_assertions": "string",
- "vca_model_config_snap_store_proxy": "string",
- "vca_model_config_snap_store_proxy_url": "string",
- "vca_model_config_ssl_hostname_verification": False,
- "vca_model_config_test_mode": False,
- "vca_model_config_transmit_vendor_metrics": False,
- "vca_model_config_update_status_hook_interval": "string",
- }
- )
- expected_config = {
- f"OSMLCM_{k.upper()}": v
- for k, v in self.config.items()
- if k.startswith("vca_model_config_")
- }
-
- self.harness.charm._check_missing_dependencies = mock.Mock()
- pod_spec = self.harness.charm.build_pod_spec(
- {"imageDetails": {"imagePath": "lcm-image"}}
- )
- actual_config = pod_spec["containers"][0]["envConfig"]
-
- self.assertDictContainsSubset(
- expected_config,
- actual_config,
- )
-
- def initialize_kafka_relation(self):
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- def initialize_mongo_config(self):
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- def initialize_mongo_relation(self):
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
- def initialize_ro_relation(self):
- http_relation_id = self.harness.add_relation("ro", "ro")
- self.harness.add_relation_unit(http_relation_id, "ro")
- self.harness.update_relation_data(
- http_relation_id,
- "ro",
- {"host": "ro", "port": 9090},
- )
-
-
-if __name__ == "__main__":
- unittest.main()
-
-
-# class TestCharm(unittest.TestCase):
-# """LCM Charm unit tests."""
-
-# def setUp(self) -> NoReturn:
-# """Test setup"""
-# self.harness = Harness(LcmCharm)
-# self.harness.set_leader(is_leader=True)
-# self.harness.begin()
-
-# def test_on_start_without_relations(self) -> NoReturn:
-# """Test installation without any relation."""
-# self.harness.charm.on.start.emit()
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertIn("ro", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_start_with_relations(self) -> NoReturn:
-# """Test deployment without keystone."""
-# expected_result = {
-# "version": 3,
-# "containers": [
-# {
-# "name": "lcm",
-# "imageDetails": self.harness.charm.image.fetch(),
-# "imagePullPolicy": "Always",
-# "ports": [
-# {
-# "name": "lcm",
-# "containerPort": 9999,
-# "protocol": "TCP",
-# }
-# ],
-# "envConfig": {
-# "ALLOW_ANONYMOUS_LOGIN": "yes",
-# "OSMLCM_GLOBAL_LOGLEVEL": "INFO",
-# "OSMLCM_RO_HOST": "ro",
-# "OSMLCM_RO_PORT": 9090,
-# "OSMLCM_RO_TENANT": "osm",
-# "OSMLCM_MESSAGE_DRIVER": "kafka",
-# "OSMLCM_MESSAGE_HOST": "kafka",
-# "OSMLCM_MESSAGE_PORT": 9092,
-# "OSMLCM_DATABASE_DRIVER": "mongo",
-# "OSMLCM_DATABASE_URI": "mongodb://mongo:27017",
-# "OSMLCM_DATABASE_COMMONKEY": "osm",
-# "OSMLCM_STORAGE_DRIVER": "mongo",
-# "OSMLCM_STORAGE_PATH": "/app/storage",
-# "OSMLCM_STORAGE_COLLECTION": "files",
-# "OSMLCM_STORAGE_URI": "mongodb://mongo:27017",
-# "OSMLCM_VCA_HOST": "admin",
-# "OSMLCM_VCA_PORT": 17070,
-# "OSMLCM_VCA_USER": "admin",
-# "OSMLCM_VCA_PUBKEY": "secret",
-# "OSMLCM_VCA_SECRET": "secret",
-# "OSMLCM_VCA_CACERT": "",
-# "OSMLCM_VCA_CLOUD": "localhost",
-# "OSMLCM_VCA_K8S_CLOUD": "k8scloud",
-# },
-# }
-# ],
-# "kubernetesResources": {"ingressResources": []},
-# }
-
-# self.harness.charm.on.start.emit()
-
-# # Check if kafka datastore is initialized
-# self.assertIsNone(self.harness.charm.state.message_host)
-# self.assertIsNone(self.harness.charm.state.message_port)
-
-# # Check if mongodb datastore is initialized
-# self.assertIsNone(self.harness.charm.state.database_uri)
-
-# # Check if RO datastore is initialized
-# self.assertIsNone(self.harness.charm.state.ro_host)
-# self.assertIsNone(self.harness.charm.state.ro_port)
-
-# # Initializing the kafka relation
-# kafka_relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# # Initializing the mongo relation
-# mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# mongodb_relation_id,
-# "mongodb/0",
-# {"connection_string": "mongodb://mongo:27017"},
-# )
-
-# # Initializing the RO relation
-# ro_relation_id = self.harness.add_relation("ro", "ro")
-# self.harness.add_relation_unit(ro_relation_id, "ro/0")
-# self.harness.update_relation_data(
-# ro_relation_id, "ro/0", {"host": "ro", "port": 9090}
-# )
-
-# # Checking if kafka data is stored
-# self.assertEqual(self.harness.charm.state.message_host, "kafka")
-# self.assertEqual(self.harness.charm.state.message_port, 9092)
-
-# # Checking if mongodb data is stored
-# self.assertEqual(self.harness.charm.state.database_uri, "mongodb://mongo:27017")
-
-# # Checking if RO data is stored
-# self.assertEqual(self.harness.charm.state.ro_host, "ro")
-# self.assertEqual(self.harness.charm.state.ro_port, 9090)
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# pod_spec, _ = self.harness.get_pod_spec()
-
-# self.assertDictEqual(expected_result, pod_spec)
-
-# def test_on_kafka_relation_unit_changed(self) -> NoReturn:
-# """Test to see if kafka relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.message_host)
-# self.assertIsNone(self.harness.charm.state.message_port)
-
-# relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# self.assertEqual(self.harness.charm.state.message_host, "kafka")
-# self.assertEqual(self.harness.charm.state.message_port, 9092)
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertNotIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertIn("ro", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_mongodb_unit_relation_changed(self) -> NoReturn:
-# """Test to see if mongodb relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.database_uri)
-
-# relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# relation_id, "mongodb/0", {"connection_string": "mongodb://mongo:27017"}
-# )
-
-# self.assertEqual(self.harness.charm.state.database_uri, "mongodb://mongo:27017")
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertNotIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertIn("ro", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_ro_unit_relation_changed(self) -> NoReturn:
-# """Test to see if RO relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.ro_host)
-# self.assertIsNone(self.harness.charm.state.ro_port)
-
-# relation_id = self.harness.add_relation("ro", "ro")
-# self.harness.add_relation_unit(relation_id, "ro/0")
-# self.harness.update_relation_data(
-# relation_id, "ro/0", {"host": "ro", "port": 9090}
-# )
-
-# self.assertEqual(self.harness.charm.state.ro_host, "ro")
-# self.assertEqual(self.harness.charm.state.ro_port, 9090)
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertNotIn("ro", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-
-# if __name__ == "__main__":
-# unittest.main()
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-from typing import NoReturn
-import unittest
-
-import pod_spec
-
-
-class TestPodSpec(unittest.TestCase):
- """Pod spec unit tests."""
-
- def test_make_pod_ports(self) -> NoReturn:
- """Testing make pod ports."""
- port = 9999
-
- expected_result = [
- {
- "name": "lcm",
- "containerPort": port,
- "protocol": "TCP",
- }
- ]
-
- pod_ports = pod_spec._make_pod_ports(9999)
-
- self.assertListEqual(expected_result, pod_ports)
-
- def test_make_pod_envconfig_without_vca_apiproxy(self) -> NoReturn:
- """Teting make pod envconfig without vca_apiproxy configuration."""
- config = {
- "database_commonkey": "commonkey",
- "log_level": "INFO",
- "vca_host": "vca",
- "vca_port": 1212,
- "vca_user": "vca_user",
- "vca_pubkey": "vca_pubkey",
- "vca_password": "vca_password",
- "vca_cacert": "vca_cacert",
- "vca_cloud": "vca_cloud",
- "vca_k8s_cloud": "vca_k8s_cloud",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 2181,
- "database_uri": "mongodb://mongo",
- "ro_host": "ro",
- "ro_port": 9090,
- }
-
- expected_result = {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMLCM_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMLCM_RO_HOST": relation_state["ro_host"],
- "OSMLCM_RO_PORT": relation_state["ro_port"],
- "OSMLCM_RO_TENANT": "osm",
- "OSMLCM_MESSAGE_DRIVER": "kafka",
- "OSMLCM_MESSAGE_HOST": relation_state["message_host"],
- "OSMLCM_MESSAGE_PORT": relation_state["message_port"],
- "OSMLCM_DATABASE_DRIVER": "mongo",
- "OSMLCM_DATABASE_URI": relation_state["database_uri"],
- "OSMLCM_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMLCM_STORAGE_DRIVER": "mongo",
- "OSMLCM_STORAGE_PATH": "/app/storage",
- "OSMLCM_STORAGE_COLLECTION": "files",
- "OSMLCM_STORAGE_URI": relation_state["database_uri"],
- "OSMLCM_VCA_HOST": config["vca_host"],
- "OSMLCM_VCA_PORT": config["vca_port"],
- "OSMLCM_VCA_USER": config["vca_user"],
- "OSMLCM_VCA_PUBKEY": config["vca_pubkey"],
- "OSMLCM_VCA_SECRET": config["vca_password"],
- "OSMLCM_VCA_CACERT": config["vca_cacert"],
- "OSMLCM_VCA_CLOUD": config["vca_cloud"],
- "OSMLCM_VCA_K8S_CLOUD": config["vca_k8s_cloud"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_pod_envconfig_with_vca_apiproxy(self) -> NoReturn:
- """Teting make pod envconfig with vca_apiproxy configuration."""
- config = {
- "database_commonkey": "commonkey",
- "log_level": "INFO",
- "vca_host": "vca",
- "vca_port": 1212,
- "vca_user": "vca_user",
- "vca_pubkey": "vca_pubkey",
- "vca_password": "vca_password",
- "vca_cacert": "vca_cacert",
- "vca_cloud": "vca_cloud",
- "vca_k8s_cloud": "vca_k8s_cloud",
- "vca_apiproxy": "vca_apiproxy",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 2181,
- "database_uri": "mongodb://mongo",
- "ro_host": "ro",
- "ro_port": 9090,
- }
-
- expected_result = {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMLCM_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMLCM_RO_HOST": relation_state["ro_host"],
- "OSMLCM_RO_PORT": relation_state["ro_port"],
- "OSMLCM_RO_TENANT": "osm",
- "OSMLCM_MESSAGE_DRIVER": "kafka",
- "OSMLCM_MESSAGE_HOST": relation_state["message_host"],
- "OSMLCM_MESSAGE_PORT": relation_state["message_port"],
- "OSMLCM_DATABASE_DRIVER": "mongo",
- "OSMLCM_DATABASE_URI": relation_state["database_uri"],
- "OSMLCM_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMLCM_STORAGE_DRIVER": "mongo",
- "OSMLCM_STORAGE_PATH": "/app/storage",
- "OSMLCM_STORAGE_COLLECTION": "files",
- "OSMLCM_STORAGE_URI": relation_state["database_uri"],
- "OSMLCM_VCA_HOST": config["vca_host"],
- "OSMLCM_VCA_PORT": config["vca_port"],
- "OSMLCM_VCA_USER": config["vca_user"],
- "OSMLCM_VCA_PUBKEY": config["vca_pubkey"],
- "OSMLCM_VCA_SECRET": config["vca_password"],
- "OSMLCM_VCA_CACERT": config["vca_cacert"],
- "OSMLCM_VCA_CLOUD": config["vca_cloud"],
- "OSMLCM_VCA_K8S_CLOUD": config["vca_k8s_cloud"],
- "OSMLCM_VCA_APIPROXY": config["vca_apiproxy"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_startup_probe(self) -> NoReturn:
- """Testing make startup probe."""
- expected_result = {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
- startup_probe = pod_spec._make_startup_probe()
-
- self.assertDictEqual(expected_result, startup_probe)
-
- def test_make_readiness_probe(self) -> NoReturn:
- """Testing make readiness probe."""
- port = 9999
-
- expected_result = {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
- readiness_probe = pod_spec._make_readiness_probe(port)
-
- self.assertDictEqual(expected_result, readiness_probe)
-
- def test_make_liveness_probe(self) -> NoReturn:
- """Testing make liveness probe."""
- port = 9999
-
- expected_result = {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
- liveness_probe = pod_spec._make_liveness_probe(port)
-
- self.assertDictEqual(expected_result, liveness_probe)
-
- def test_make_pod_spec(self) -> NoReturn:
- """Testing make pod spec."""
- image_info = {"upstream-source": "opensourcemano/lcm:8"}
- config = {
- "database_commonkey": "commonkey",
- "log_level": "INFO",
- "vca_host": "vca",
- "vca_port": 1212,
- "vca_user": "vca_user",
- "vca_pubkey": "vca_pubkey",
- "vca_password": "vca_password",
- "vca_cacert": "vca_cacert",
- "vca_cloud": "vca_cloud",
- "vca_k8s_cloud": "vca_k8s_cloud",
- "vca_apiproxy": "vca_apiproxy",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 2181,
- "database_uri": "mongodb://mongo",
- "ro_host": "ro",
- "ro_port": 9090,
- }
- app_name = "lcm"
- port = 9999
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": app_name,
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMLCM_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMLCM_RO_HOST": relation_state["ro_host"],
- "OSMLCM_RO_PORT": relation_state["ro_port"],
- "OSMLCM_RO_TENANT": "osm",
- "OSMLCM_MESSAGE_DRIVER": "kafka",
- "OSMLCM_MESSAGE_HOST": relation_state["message_host"],
- "OSMLCM_MESSAGE_PORT": relation_state["message_port"],
- "OSMLCM_DATABASE_DRIVER": "mongo",
- "OSMLCM_DATABASE_URI": relation_state["database_uri"],
- "OSMLCM_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMLCM_STORAGE_DRIVER": "mongo",
- "OSMLCM_STORAGE_PATH": "/app/storage",
- "OSMLCM_STORAGE_COLLECTION": "files",
- "OSMLCM_STORAGE_URI": relation_state["database_uri"],
- "OSMLCM_VCA_HOST": config["vca_host"],
- "OSMLCM_VCA_PORT": config["vca_port"],
- "OSMLCM_VCA_USER": config["vca_user"],
- "OSMLCM_VCA_PUBKEY": config["vca_pubkey"],
- "OSMLCM_VCA_SECRET": config["vca_password"],
- "OSMLCM_VCA_CACERT": config["vca_cacert"],
- "OSMLCM_VCA_CLOUD": config["vca_cloud"],
- "OSMLCM_VCA_K8S_CLOUD": config["vca_k8s_cloud"],
- "OSMLCM_VCA_APIPROXY": config["vca_apiproxy"],
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_with_vca_apiproxy(self) -> NoReturn:
- """Testing make pod spec with vca_apiproxy."""
- image_info = {"upstream-source": "opensourcemano/lcm:8"}
- config = {
- "database_commonkey": "commonkey",
- "log_level": "INFO",
- "vca_host": "vca",
- "vca_port": 1212,
- "vca_user": "vca_user",
- "vca_pubkey": "vca_pubkey",
- "vca_password": "vca_password",
- "vca_cacert": "vca_cacert",
- "vca_cloud": "vca_cloud",
- "vca_k8s_cloud": "vca_k8s_cloud",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 2181,
- "database_uri": "mongodb://mongo",
- "ro_host": "ro",
- "ro_port": 9090,
- }
- app_name = "lcm"
- port = 9999
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": app_name,
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMLCM_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMLCM_RO_HOST": relation_state["ro_host"],
- "OSMLCM_RO_PORT": relation_state["ro_port"],
- "OSMLCM_RO_TENANT": "osm",
- "OSMLCM_MESSAGE_DRIVER": "kafka",
- "OSMLCM_MESSAGE_HOST": relation_state["message_host"],
- "OSMLCM_MESSAGE_PORT": relation_state["message_port"],
- "OSMLCM_DATABASE_DRIVER": "mongo",
- "OSMLCM_DATABASE_URI": relation_state["database_uri"],
- "OSMLCM_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMLCM_STORAGE_DRIVER": "mongo",
- "OSMLCM_STORAGE_PATH": "/app/storage",
- "OSMLCM_STORAGE_COLLECTION": "files",
- "OSMLCM_STORAGE_URI": relation_state["database_uri"],
- "OSMLCM_VCA_HOST": config["vca_host"],
- "OSMLCM_VCA_PORT": config["vca_port"],
- "OSMLCM_VCA_USER": config["vca_user"],
- "OSMLCM_VCA_PUBKEY": config["vca_pubkey"],
- "OSMLCM_VCA_SECRET": config["vca_password"],
- "OSMLCM_VCA_CACERT": config["vca_cacert"],
- "OSMLCM_VCA_CLOUD": config["vca_cloud"],
- "OSMLCM_VCA_K8S_CLOUD": config["vca_k8s_cloud"],
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_without_image_info(self) -> NoReturn:
- """Testing make pod spec without image_info."""
- image_info = None
- config = {
- "database_commonkey": "commonkey",
- "log_level": "INFO",
- "vca_host": "vca",
- "vca_port": 1212,
- "vca_user": "vca_user",
- "vca_pubkey": "vca_pubkey",
- "vca_password": "vca_password",
- "vca_cacert": "vca_cacert",
- "vca_cloud": "vca_cloud",
- "vca_k8s_cloud": "vca_k8s_cloud",
- "vca_apiproxy": "vca_apiproxy",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 2181,
- "database_uri": "mongodb://mongo",
- "ro_host": "ro",
- "ro_port": 9090,
- }
- app_name = "lcm"
- port = 9999
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertIsNone(spec)
-
- def test_make_pod_spec_without_config(self) -> NoReturn:
- """Testing make pod spec without config."""
- image_info = {"upstream-source": "opensourcemano/lcm:8"}
- config = {}
- relation_state = {
- "message_host": "kafka",
- "message_port": 2181,
- "database_uri": "mongodb://mongo",
- "ro_host": "ro",
- "ro_port": 9090,
- }
- app_name = "lcm"
- port = 9999
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
- def test_make_pod_spec_without_relation_state(self) -> NoReturn:
- """Testing make pod spec without relation_state."""
- image_info = {"upstream-source": "opensourcemano/lcm:8"}
- config = {
- "database_commonkey": "commonkey",
- "log_level": "INFO",
- "vca_host": "vca",
- "vca_port": 1212,
- "vca_user": "vca_user",
- "vca_pubkey": "vca_pubkey",
- "vca_password": "vca_password",
- "vca_cacert": "vca_cacert",
- "vca_cloud": "vca_cloud",
- "vca_k8s_cloud": "vca_k8s_cloud",
- "vca_apiproxy": "vca_apiproxy",
- }
- relation_state = {}
- app_name = "lcm"
- port = 9999
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv =
- VIRTUAL_ENV={envdir}
- PYTHONPATH = {toxinidir}:{toxinidir}/lib:{toxinidir}/src
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-#!/bin/bash
-# Copyright 2020 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.
-
-set -eux
-
-function lint() {
- cd $1
- tox -e lint
- cd ..
-}
-
-lint 'lcm-k8s'
-lint 'mon-k8s'
-lint 'nbi-k8s'
-lint 'pol-k8s'
-lint 'ro-k8s'
-lint 'ui-k8s'
-lint 'keystone'
-lint 'ng-ui'
-lint 'pla'
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- Copyright 2020 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.
-
-For those usages not covered by the Apache License, Version 2.0 please
-contact: legal@canonical.com
-
-To get in touch with the maintainers, please contact:
-osm-charmers@lists.launchpad.net -->
-
-# MON operator Charm for Kubernetes
-
-## Requirements
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-options:
- openstack_default_granularity:
- description: Openstack default granularity
- type: int
- default: 300
- global_request_timeout:
- description: Global request timeout
- type: int
- default: 10
- log_level:
- description: Log level
- type: string
- default: INFO
- database_commonkey:
- description: Database common key
- type: string
- default: osm
- mongodb_uri:
- type: string
- description: MongoDB URI (external database)
- collector_interval:
- description: Collector interval
- type: int
- default: 30
- evaluator_interval:
- description: Evaluator interval
- type: int
- default: 30
- vca_host:
- type: string
- description: "The VCA host."
- default: "admin"
- vca_user:
- type: string
- description: "The VCA user name."
- default: "admin"
- vca_secret:
- type: string
- description: "The VCA user password."
- default: "secret"
- vca_cacert:
- type: string
- description: "The VCA cacert."
- default: ""
- grafana_url:
- description: Grafana URL
- type: string
- default: http://grafana:3000
- grafana_user:
- description: Grafana user
- type: string
- default: admin
- grafana_password:
- description: Grafana password
- type: string
- default: admin
- keystone_enabled:
- description: MON will use Keystone backend
- type: boolean
- default: false
- certificates:
- type: string
- description: |
- comma-separated list of <name>:<content> certificates.
- Where:
- name: name of the file for the certificate
- content: base64 content of the certificate
- The path for the files is /certs.
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- debug_mode:
- description: |
- If true, debug mode is activated. It means that the service will not run,
- and instead, the command for the container will be a `sleep infinity`.
- Note: If enabled, security_context will be disabled.
- type: boolean
- default: false
- debug_pubkey:
- description: |
- Public SSH key that will be injected to the application pod.
- type: string
- debug_mon_local_path:
- description: |
- Local full path to the MON project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_n2vc_local_path:
- description: |
- Local full path to the N2VC project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_common_local_path:
- description: |
- Local full path to the COMMON project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
- vm_infra_metrics:
- description: Enables querying the VIMs asking for the status of the VMs
- type: boolean
- default: true
+++ /dev/null
-# Copyright 2022 Canonical Ltd.
-# See LICENSE file for licensing details.
-#
-# 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.
-
-"""Kafka library.
-
-This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
-`kafka` [interface](https://juju.is/docs/sdk/relations).
-
-The *provider* side of this interface is implemented by the
-[kafka-k8s Charmed Operator](https://charmhub.io/kafka-k8s).
-
-Any Charmed Operator that *requires* Kafka for providing its
-service should implement the *requirer* side of this interface.
-
-In a nutshell using this library to implement a Charmed Operator *requiring*
-Kafka would look like
-
-```
-$ charmcraft fetch-lib charms.kafka_k8s.v0.kafka
-```
-
-`metadata.yaml`:
-
-```
-requires:
- kafka:
- interface: kafka
- limit: 1
-```
-
-`src/charm.py`:
-
-```
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.charm import CharmBase
-
-
-class MyCharm(CharmBase):
-
- on = KafkaEvents()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.kafka = KafkaRequires(self)
- self.framework.observe(
- self.on.kafka_available,
- self._on_kafka_available,
- )
- self.framework.observe(
- self.on.kafka_broken,
- self._on_kafka_broken,
- )
-
- def _on_kafka_available(self, event):
- # Get Kafka host and port
- host: str = self.kafka.host
- port: int = self.kafka.port
- # host => "kafka-k8s"
- # port => 9092
-
- def _on_kafka_broken(self, event):
- # Stop service
- # ...
- self.unit.status = BlockedStatus("need kafka relation")
-```
-
-You can file bugs
-[here](https://github.com/charmed-osm/kafka-k8s-operator/issues)!
-"""
-
-from typing import Optional
-
-from ops.charm import CharmBase, CharmEvents
-from ops.framework import EventBase, EventSource, Object
-
-# The unique Charmhub library identifier, never change it
-from ops.model import Relation
-
-LIBID = "eacc8c85082347c9aae740e0220b8376"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 0
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 3
-
-
-KAFKA_HOST_APP_KEY = "host"
-KAFKA_PORT_APP_KEY = "port"
-
-
-class _KafkaAvailableEvent(EventBase):
- """Event emitted when Kafka is available."""
-
-
-class _KafkaBrokenEvent(EventBase):
- """Event emitted when Kafka relation is broken."""
-
-
-class KafkaEvents(CharmEvents):
- """Kafka events.
-
- This class defines the events that Kafka can emit.
-
- Events:
- kafka_available (_KafkaAvailableEvent)
- """
-
- kafka_available = EventSource(_KafkaAvailableEvent)
- kafka_broken = EventSource(_KafkaBrokenEvent)
-
-
-class KafkaRequires(Object):
- """Requires-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self.charm = charm
- self._endpoint_name = endpoint_name
-
- # Observe relation events
- event_observe_mapping = {
- charm.on[self._endpoint_name].relation_changed: self._on_relation_changed,
- charm.on[self._endpoint_name].relation_broken: self._on_relation_broken,
- }
- for event, observer in event_observe_mapping.items():
- self.framework.observe(event, observer)
-
- def _on_relation_changed(self, event) -> None:
- if event.relation.app and all(
- key in event.relation.data[event.relation.app]
- for key in (KAFKA_HOST_APP_KEY, KAFKA_PORT_APP_KEY)
- ):
- self.charm.on.kafka_available.emit()
-
- def _on_relation_broken(self, _) -> None:
- self.charm.on.kafka_broken.emit()
-
- @property
- def host(self) -> str:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- relation.data[relation.app].get(KAFKA_HOST_APP_KEY)
- if relation and relation.app
- else None
- )
-
- @property
- def port(self) -> int:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- int(relation.data[relation.app].get(KAFKA_PORT_APP_KEY))
- if relation and relation.app
- else None
- )
-
-
-class KafkaProvides(Object):
- """Provides-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self._endpoint_name = endpoint_name
-
- def set_host_info(self, host: str, port: int, relation: Optional[Relation] = None) -> None:
- """Set Kafka host and port.
-
- This function writes in the application data of the relation, therefore,
- only the unit leader can call it.
-
- Args:
- host (str): Kafka hostname or IP address.
- port (int): Kafka port.
- relation (Optional[Relation]): Relation to update.
- If not specified, all relations will be updated.
-
- Raises:
- Exception: if a non-leader unit calls this function.
- """
- if not self.model.unit.is_leader():
- raise Exception("only the leader set host information.")
-
- if relation:
- self._update_relation_data(host, port, relation)
- return
-
- for relation in self.model.relations[self._endpoint_name]:
- self._update_relation_data(host, port, relation)
-
- def _update_relation_data(self, host: str, port: int, relation: Relation) -> None:
- """Update data in relation if needed."""
- relation.data[self.model.app][KAFKA_HOST_APP_KEY] = host
- relation.data[self.model.app][KAFKA_PORT_APP_KEY] = str(port)
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-name: osm-mon
-summary: OSM Monitoring (MON)
-description: |
- A CAAS charm to deploy OSM's Monitoring (MON).
-series:
- - kubernetes
-tags:
- - kubernetes
- - osm
- - mon
-min-juju-version: 2.8.0
-deployment:
- type: stateless
- service: cluster
-resources:
- image:
- type: oci-image
- description: OSM docker image for MON
- upstream-source: "opensourcemano/mon:latest"
-requires:
- kafka:
- interface: kafka
- mongodb:
- interface: mongodb
- prometheus:
- interface: prometheus
- keystone:
- interface: keystone
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
\ No newline at end of file
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-import base64
-import logging
-from typing import NoReturn, Optional
-
-
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.keystone import KeystoneClient
-from opslib.osm.interfaces.mongo import MongoClient
-from opslib.osm.interfaces.prometheus import PrometheusClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- FilesV3Builder,
- PodRestartPolicy,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-PORT = 8000
-
-
-def _check_certificate_data(name: str, content: str):
- if not name or not content:
- raise ValueError("certificate name and content must be a non-empty string")
-
-
-def _extract_certificates(certs_config: str):
- certificates = {}
- if certs_config:
- cert_list = certs_config.split(",")
- for cert in cert_list:
- name, content = cert.split(":")
- _check_certificate_data(name, content)
- certificates[name] = content
- return certificates
-
-
-def decode(content: str):
- return base64.b64decode(content.encode("utf-8")).decode("utf-8")
-
-
-class ConfigModel(ModelValidator):
- keystone_enabled: bool
- vca_host: str
- vca_user: str
- vca_secret: str
- vca_cacert: str
- database_commonkey: str
- mongodb_uri: Optional[str]
- log_level: str
- openstack_default_granularity: int
- global_request_timeout: int
- collector_interval: int
- vm_infra_metrics: bool
- evaluator_interval: int
- grafana_url: str
- grafana_user: str
- grafana_password: str
- certificates: Optional[str]
- image_pull_policy: str
- debug_mode: bool
- security_context: bool
-
- @validator("log_level")
- def validate_log_level(cls, v):
- if v not in {"INFO", "DEBUG"}:
- raise ValueError("value must be INFO or DEBUG")
- return v
-
- @validator("certificates")
- def validate_certificates(cls, v):
- # Raises an exception if it cannot extract the certificates
- _extract_certificates(v)
- return v
-
- @validator("mongodb_uri")
- def validate_mongodb_uri(cls, v):
- if v and not v.startswith("mongodb://"):
- raise ValueError("mongodb_uri is not properly formed")
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
- @property
- def certificates_dict(cls):
- return _extract_certificates(cls.certificates) if cls.certificates else {}
-
-
-class MonCharm(CharmedOsmBase):
- on = KafkaEvents()
-
- def __init__(self, *args) -> NoReturn:
- super().__init__(
- *args,
- oci_image="image",
- vscode_workspace=VSCODE_WORKSPACE,
- )
- if self.config.get("debug_mode"):
- self.enable_debug_mode(
- pubkey=self.config.get("debug_pubkey"),
- hostpaths={
- "MON": {
- "hostpath": self.config.get("debug_mon_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_mon",
- },
- "N2VC": {
- "hostpath": self.config.get("debug_n2vc_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/n2vc",
- },
- "osm_common": {
- "hostpath": self.config.get("debug_common_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_common",
- },
- },
- )
- self.kafka = KafkaRequires(self)
- self.framework.observe(self.on.kafka_available, self.configure_pod)
- self.framework.observe(self.on.kafka_broken, self.configure_pod)
-
- self.mongodb_client = MongoClient(self, "mongodb")
- self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
-
- self.prometheus_client = PrometheusClient(self, "prometheus")
- self.framework.observe(
- self.on["prometheus"].relation_changed, self.configure_pod
- )
- self.framework.observe(
- self.on["prometheus"].relation_broken, self.configure_pod
- )
-
- self.keystone_client = KeystoneClient(self, "keystone")
- self.framework.observe(self.on["keystone"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["keystone"].relation_broken, self.configure_pod)
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if not self.kafka.host or not self.kafka.port:
- missing_relations.append("kafka")
- if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit():
- missing_relations.append("mongodb")
- if self.prometheus_client.is_missing_data_in_app():
- missing_relations.append("prometheus")
- if config.keystone_enabled:
- if self.keystone_client.is_missing_data_in_app():
- missing_relations.append("keystone")
-
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def _build_cert_files(
- self,
- config: ConfigModel,
- ):
- cert_files_builder = FilesV3Builder()
- for name, content in config.certificates_dict.items():
- cert_files_builder.add_file(name, decode(content), mode=0o600)
- return cert_files_builder.build()
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
-
- if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit():
- raise Exception("Mongodb data cannot be provided via config and relation")
-
- # Check relations
- self._check_missing_dependencies(config)
-
- security_context_enabled = (
- config.security_context if not config.debug_mode else False
- )
-
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=security_context_enabled
- )
-
- # Add secrets to the pod
- mongodb_secret_name = f"{self.app.name}-mongodb-secret"
- pod_spec_builder.add_secret(
- mongodb_secret_name,
- {
- "uri": config.mongodb_uri or self.mongodb_client.connection_string,
- "commonkey": config.database_commonkey,
- },
- )
- grafana_secret_name = f"{self.app.name}-grafana-secret"
- pod_spec_builder.add_secret(
- grafana_secret_name,
- {
- "url": config.grafana_url,
- "user": config.grafana_user,
- "password": config.grafana_password,
- },
- )
-
- vca_secret_name = f"{self.app.name}-vca-secret"
- pod_spec_builder.add_secret(
- vca_secret_name,
- {
- "host": config.vca_host,
- "user": config.vca_user,
- "secret": config.vca_secret,
- "cacert": config.vca_cacert,
- },
- )
-
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=security_context_enabled,
- )
- certs_files = self._build_cert_files(config)
-
- if certs_files:
- container_builder.add_volume_config("certs", "/certs", certs_files)
-
- container_builder.add_port(name=self.app.name, port=PORT)
- container_builder.add_envs(
- {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": config.openstack_default_granularity,
- "OSMMON_GLOBAL_REQUEST_TIMEOUT": config.global_request_timeout,
- "OSMMON_GLOBAL_LOGLEVEL": config.log_level,
- "OSMMON_COLLECTOR_INTERVAL": config.collector_interval,
- "OSMMON_COLLECTOR_VM_INFRA_METRICS": config.vm_infra_metrics,
- "OSMMON_EVALUATOR_INTERVAL": config.evaluator_interval,
- # Kafka configuration
- "OSMMON_MESSAGE_DRIVER": "kafka",
- "OSMMON_MESSAGE_HOST": self.kafka.host,
- "OSMMON_MESSAGE_PORT": self.kafka.port,
- # Database configuration
- "OSMMON_DATABASE_DRIVER": "mongo",
- # Prometheus configuration
- "OSMMON_PROMETHEUS_URL": f"http://{self.prometheus_client.hostname}:{self.prometheus_client.port}",
- }
- )
- prometheus_user = self.prometheus_client.user
- prometheus_password = self.prometheus_client.password
- if prometheus_user and prometheus_password:
- container_builder.add_envs(
- {
- "OSMMON_PROMETHEUS_USER": prometheus_user,
- "OSMMON_PROMETHEUS_PASSWORD": prometheus_password,
- }
- )
- container_builder.add_secret_envs(
- secret_name=mongodb_secret_name,
- envs={
- "OSMMON_DATABASE_URI": "uri",
- "OSMMON_DATABASE_COMMONKEY": "commonkey",
- },
- )
- container_builder.add_secret_envs(
- secret_name=vca_secret_name,
- envs={
- "OSMMON_VCA_HOST": "host",
- "OSMMON_VCA_USER": "user",
- "OSMMON_VCA_SECRET": "secret",
- "OSMMON_VCA_CACERT": "cacert",
- },
- )
- container_builder.add_secret_envs(
- secret_name=grafana_secret_name,
- envs={
- "OSMMON_GRAFANA_URL": "url",
- "OSMMON_GRAFANA_USER": "user",
- "OSMMON_GRAFANA_PASSWORD": "password",
- },
- )
- if config.keystone_enabled:
- keystone_secret_name = f"{self.app.name}-keystone-secret"
- pod_spec_builder.add_secret(
- keystone_secret_name,
- {
- "url": self.keystone_client.host,
- "user_domain": self.keystone_client.user_domain_name,
- "project_domain": self.keystone_client.project_domain_name,
- "service_username": self.keystone_client.username,
- "service_password": self.keystone_client.password,
- "service_project": self.keystone_client.service,
- },
- )
- container_builder.add_env("OSMMON_KEYSTONE_ENABLED", True)
- container_builder.add_secret_envs(
- secret_name=keystone_secret_name,
- envs={
- "OSMMON_KEYSTONE_URL": "url",
- "OSMMON_KEYSTONE_DOMAIN_NAME": "user_domain",
- "OSMMON_KEYSTONE_PROJECT_DOMAIN_NAME": "project_domain",
- "OSMMON_KEYSTONE_SERVICE_USER": "service_username",
- "OSMMON_KEYSTONE_SERVICE_PASSWORD": "service_password",
- "OSMMON_KEYSTONE_SERVICE_PROJECT": "service_project",
- },
- )
- container = container_builder.build()
-
- # Add restart policy
- restart_policy = PodRestartPolicy()
- restart_policy.add_secrets()
- pod_spec_builder.set_restart_policy(restart_policy)
-
- # Add container to pod spec
- pod_spec_builder.add_container(container)
-
- return pod_spec_builder.build()
-
-
-VSCODE_WORKSPACE = {
- "folders": [
- {"path": "/usr/lib/python3/dist-packages/osm_mon"},
- {"path": "/usr/lib/python3/dist-packages/osm_common"},
- {"path": "/usr/lib/python3/dist-packages/n2vc"},
- ],
- "settings": {},
- "launch": {
- "version": "0.2.0",
- "configurations": [
- {
- "name": "MON Server",
- "type": "python",
- "request": "launch",
- "module": "osm_mon.cmd.mon_server",
- "justMyCode": False,
- },
- {
- "name": "MON evaluator",
- "type": "python",
- "request": "launch",
- "module": "osm_mon.cmd.mon_evaluator",
- "justMyCode": False,
- },
- {
- "name": "MON collector",
- "type": "python",
- "request": "launch",
- "module": "osm_mon.cmd.mon_collector",
- "justMyCode": False,
- },
- {
- "name": "MON dashboarder",
- "type": "python",
- "request": "launch",
- "module": "osm_mon.cmd.mon_dashboarder",
- "justMyCode": False,
- },
- ],
- },
-}
-if __name__ == "__main__":
- main(MonCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import logging
-from typing import Any, Dict, List, NoReturn
-
-logger = logging.getLogger(__name__)
-
-
-def _validate_data(
- config_data: Dict[str, Any], relation_data: Dict[str, Any]
-) -> NoReturn:
- """Validate input data.
-
- Args:
- config_data (Dict[str, Any]): configuration data.
- relation_data (Dict[str, Any]): relation data.
- """
- config_validators = {
- "openstack_default_granularity": lambda value, _: (
- isinstance(value, int) and value > 0
- ),
- "global_request_timeout": lambda value, _: isinstance(value, int) and value > 0,
- "log_level": lambda value, _: (
- isinstance(value, str) and value in ("INFO", "DEBUG")
- ),
- "collector_interval": lambda value, _: isinstance(value, int) and value > 0,
- "evaluator_interval": lambda value, _: isinstance(value, int) and value > 0,
- "database_commonkey": lambda value, _: (
- isinstance(value, str) and len(value) > 0
- ),
- "vca_host": lambda value, _: isinstance(value, str) and len(value) > 0,
- "vca_user": lambda value, _: isinstance(value, str) and len(value) > 0,
- "vca_password": lambda value, _: isinstance(value, str) and len(value) > 0,
- "vca_cacert": lambda value, _: isinstance(value, str),
- }
- relation_validators = {
- "message_host": lambda value, _: isinstance(value, str) and len(value) > 0,
- "message_port": lambda value, _: isinstance(value, int) and value > 0,
- "database_uri": lambda value, _: (
- isinstance(value, str) and value.startswith("mongodb://")
- ),
- "prometheus_host": lambda value, _: isinstance(value, str) and len(value) > 0,
- "prometheus_port": lambda value, _: isinstance(value, int) and value > 0,
- }
- problems = []
-
- for key, validator in config_validators.items():
- valid = validator(config_data.get(key), config_data)
-
- if not valid:
- problems.append(key)
-
- for key, validator in relation_validators.items():
- valid = validator(relation_data.get(key), relation_data)
-
- if not valid:
- problems.append(key)
-
- if len(problems) > 0:
- raise ValueError("Errors found in: {}".format(", ".join(problems)))
-
-
-def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
- """Generate pod ports details.
-
- Args:
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod port details.
- """
- return [{"name": "mon", "containerPort": port, "protocol": "TCP"}]
-
-
-def _make_pod_envconfig(
- config: Dict[str, Any], relation_state: Dict[str, Any]
-) -> Dict[str, Any]:
- """Generate pod environment configuration.
-
- Args:
- config (Dict[str, Any]): configuration information.
- relation_state (Dict[str, Any]): relation state information.
-
- Returns:
- Dict[str, Any]: pod environment configuration.
- """
- envconfig = {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": config["openstack_default_granularity"],
- "OSMMON_GLOBAL_REQUEST_TIMEOUT": config["global_request_timeout"],
- "OSMMON_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMMON_COLLECTOR_INTERVAL": config["collector_interval"],
- "OSMMON_EVALUATOR_INTERVAL": config["evaluator_interval"],
- # Kafka configuration
- "OSMMON_MESSAGE_DRIVER": "kafka",
- "OSMMON_MESSAGE_HOST": relation_state["message_host"],
- "OSMMON_MESSAGE_PORT": relation_state["message_port"],
- # Database configuration
- "OSMMON_DATABASE_DRIVER": "mongo",
- "OSMMON_DATABASE_URI": relation_state["database_uri"],
- "OSMMON_DATABASE_COMMONKEY": config["database_commonkey"],
- # Prometheus configuration
- "OSMMON_PROMETHEUS_URL": f"http://{relation_state['prometheus_host']}:{relation_state['prometheus_port']}",
- # VCA configuration
- "OSMMON_VCA_HOST": config["vca_host"],
- "OSMMON_VCA_USER": config["vca_user"],
- "OSMMON_VCA_SECRET": config["vca_password"],
- "OSMMON_VCA_CACERT": config["vca_cacert"],
- }
-
- return envconfig
-
-
-def _make_startup_probe() -> Dict[str, Any]:
- """Generate startup probe.
-
- Returns:
- Dict[str, Any]: startup probe.
- """
- return {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
-
-def _make_readiness_probe(port: int) -> Dict[str, Any]:
- """Generate readiness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: readiness probe.
- """
- return {
- "tcpSocket": {
- "port": port,
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
-
-def _make_liveness_probe(port: int) -> Dict[str, Any]:
- """Generate liveness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: liveness probe.
- """
- return {
- "tcpSocket": {
- "port": port,
- },
- "initialDelaySeconds": 45,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
-
-def make_pod_spec(
- image_info: Dict[str, str],
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
- app_name: str = "mon",
- port: int = 8000,
-) -> Dict[str, Any]:
- """Generate the pod spec information.
-
- Args:
- image_info (Dict[str, str]): Object provided by
- OCIImageResource("image").fetch().
- config (Dict[str, Any]): Configuration information.
- relation_state (Dict[str, Any]): Relation state information.
- app_name (str, optional): Application name. Defaults to "mon".
- port (int, optional): Port for the container. Defaults to 8000.
-
- Returns:
- Dict[str, Any]: Pod spec dictionary for the charm.
- """
- if not image_info:
- return None
-
- _validate_data(config, relation_state)
-
- ports = _make_pod_ports(port)
- env_config = _make_pod_envconfig(config, relation_state)
-
- return {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": ports,
- "envConfig": env_config,
- }
- ],
- "kubernetesResources": {
- "ingressResources": [],
- },
- }
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import base64
-import sys
-from typing import NoReturn
-import unittest
-
-from charm import MonCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-def encode(content: str):
- return base64.b64encode(content.encode("ascii")).decode("utf-8")
-
-
-certificate_pem = encode(
- """
------BEGIN CERTIFICATE-----
-MIIDazCCAlOgAwIBAgIUf1b0s3UKtrxHXH2rge7UaQyfJAMwDQYJKoZIhvcNAQEL
-BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
-GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTAzMjIxNzEyMjdaFw0zMTAz
-MjAxNzEyMjdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
-HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQCgCfCBgYAN6ON0yHDXuW407rFtJVRf0u46Jrp0Dk7J
-kkSZ1e7Kq14r7yFHazEBWv78oOdwBocvWrd8leLuf3bYGcHR65hRy6A/fbYm5Aje
-cKpwlFwaqfR4BLelwJl79jZ2rJX738cCBVrIk1nAVdOxGrXV4MTWUaKR2c+uKKvc
-OKRT+5VqCeP4N5FWeATZ/KqGu8uV9E9WhFgwIZyStemLyLaDbn5PmAQ6S9oeR5jJ
-o2gEEp/lDKvsqOWs76KFumSKa9hQs5Dw2lj0mb1UoyYK1gYc4ubzVChJadv44AU8
-MYtIjlFn1X1P+RjaKZNUIAGXkoLwYn6SizF6y6LiuFS9AgMBAAGjUzBRMB0GA1Ud
-DgQWBBRl+/23CB+FXczeAZRQyYcfOdy9YDAfBgNVHSMEGDAWgBRl+/23CB+FXcze
-AZRQyYcfOdy9YDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAd
-dkeDym6lRN8kWFtfu3IyiLF8G8sn91qNbH3Yr4TuTBhgcjYyW6PgisSbrNgA9ysE
-GoaF7ohb8GeVfCsQdK23+NpAlj/+DZ3OnGcxwXj1RUAz4yr9kanV1yuEtr1q2xJI
-UaECWr8HZlwGBAKNTGx2EXT2/2aFzgULpDcxzTKD+MRpKpMUrWhf9ULvVrclvHWe
-POLYhobUFuBHuo6rt5Rcq16j67zCX9EVTlAE3o2OECIWByK22sXdeOidYMpTkl4q
-8FrOqjNsx5d+SBPJBv/pqtBm4bA47Vx1P8tbWOQ4bXS0UmXgwpeBOU/O/ot30+KS
-JnKEy+dYyvVBKg77sRHw
------END CERTIFICATE-----
-"""
-)
-
-
-class TestCharm(unittest.TestCase):
- """Prometheus Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(MonCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "vca_host": "192.168.0.13",
- "vca_user": "admin",
- "vca_secret": "admin",
- "vca_cacert": "cacert",
- "database_commonkey": "commonkey",
- "mongodb_uri": "",
- "log_level": "INFO",
- "openstack_default_granularity": 10,
- "global_request_timeout": 10,
- "collector_interval": 30,
- "evaluator_interval": 30,
- "keystone_enabled": True,
- "certificates": f"cert1:{certificate_pem}",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mongodb", "kafka", "prometheus", "keystone"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations_and_mongodb_config(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- self.initialize_prometheus_relation()
- self.initialize_keystone_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_prometheus_relation()
- self.initialize_keystone_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_exception_mongodb_relation_and_config(
- self,
- ) -> NoReturn:
- "Test with relations and config for mongodb. Must fail"
- self.initialize_mongo_relation()
- self.initialize_mongo_config()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def initialize_kafka_relation(self):
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- def initialize_mongo_config(self):
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- def initialize_mongo_relation(self):
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
- def initialize_prometheus_relation(self):
- prometheus_relation_id = self.harness.add_relation("prometheus", "prometheus")
- self.harness.add_relation_unit(prometheus_relation_id, "prometheus/0")
- self.harness.update_relation_data(
- prometheus_relation_id,
- "prometheus",
- {"hostname": "prometheus", "port": 9090},
- )
-
- def initialize_keystone_relation(self):
- keystone_relation_id = self.harness.add_relation("keystone", "keystone")
- self.harness.add_relation_unit(keystone_relation_id, "keystone/0")
- self.harness.update_relation_data(
- keystone_relation_id,
- "keystone",
- {
- "host": "host",
- "port": 5000,
- "user_domain_name": "ud",
- "project_domain_name": "pd",
- "username": "u",
- "password": "p",
- "service": "s",
- "keystone_db_password": "something",
- "region_id": "something",
- "admin_username": "something",
- "admin_password": "something",
- "admin_project_name": "something",
- },
- )
-
-
-if __name__ == "__main__":
- unittest.main()
-
-
-# class TestCharm(unittest.TestCase):
-# """MON Charm unit tests."""
-
-# def setUp(self) -> NoReturn:
-# """Test setup"""
-# self.harness = Harness(MonCharm)
-# self.harness.set_leader(is_leader=True)
-# self.harness.begin()
-
-# def test_on_start_without_relations(self) -> NoReturn:
-# """Test installation without any relation."""
-# self.harness.charm.on.start.emit()
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertIn("prometheus", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_start_with_relations(self) -> NoReturn:
-# """Test deployment without keystone."""
-# expected_result = {
-# "version": 3,
-# "containers": [
-# {
-# "name": "mon",
-# "imageDetails": self.harness.charm.image.fetch(),
-# "imagePullPolicy": "Always",
-# "ports": [
-# {
-# "name": "mon",
-# "containerPort": 8000,
-# "protocol": "TCP",
-# }
-# ],
-# "envConfig": {
-# "ALLOW_ANONYMOUS_LOGIN": "yes",
-# "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": 300,
-# "OSMMON_GLOBAL_REQUEST_TIMEOUT": 10,
-# "OSMMON_GLOBAL_LOGLEVEL": "INFO",
-# "OSMMON_COLLECTOR_INTERVAL": 30,
-# "OSMMON_EVALUATOR_INTERVAL": 30,
-# "OSMMON_MESSAGE_DRIVER": "kafka",
-# "OSMMON_MESSAGE_HOST": "kafka",
-# "OSMMON_MESSAGE_PORT": 9092,
-# "OSMMON_DATABASE_DRIVER": "mongo",
-# "OSMMON_DATABASE_URI": "mongodb://mongo:27017",
-# "OSMMON_DATABASE_COMMONKEY": "osm",
-# "OSMMON_PROMETHEUS_URL": "http://prometheus:9090",
-# "OSMMON_VCA_HOST": "admin",
-# "OSMMON_VCA_USER": "admin",
-# "OSMMON_VCA_SECRET": "secret",
-# "OSMMON_VCA_CACERT": "",
-# },
-# }
-# ],
-# "kubernetesResources": {"ingressResources": []},
-# }
-
-# self.harness.charm.on.start.emit()
-
-# # Check if kafka datastore is initialized
-# self.assertIsNone(self.harness.charm.state.message_host)
-# self.assertIsNone(self.harness.charm.state.message_port)
-
-# # Check if mongodb datastore is initialized
-# self.assertIsNone(self.harness.charm.state.database_uri)
-
-# # Check if prometheus datastore is initialized
-# self.assertIsNone(self.harness.charm.state.prometheus_host)
-# self.assertIsNone(self.harness.charm.state.prometheus_port)
-
-# # Initializing the kafka relation
-# kafka_relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# # Initializing the mongo relation
-# mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# mongodb_relation_id,
-# "mongodb/0",
-# {"connection_string": "mongodb://mongo:27017"},
-# )
-
-# # Initializing the prometheus relation
-# prometheus_relation_id = self.harness.add_relation("prometheus", "prometheus")
-# self.harness.add_relation_unit(prometheus_relation_id, "prometheus/0")
-# self.harness.update_relation_data(
-# prometheus_relation_id,
-# "prometheus",
-# {"hostname": "prometheus", "port": 9090},
-# )
-
-# # Checking if kafka data is stored
-# self.assertEqual(self.harness.charm.state.message_host, "kafka")
-# self.assertEqual(self.harness.charm.state.message_port, 9092)
-
-# # Checking if mongodb data is stored
-# self.assertEqual(self.harness.charm.state.database_uri, "mongodb://mongo:27017")
-
-# # Checking if prometheus data is stored
-# self.assertEqual(self.harness.charm.state.prometheus_host, "prometheus")
-# self.assertEqual(self.harness.charm.state.prometheus_port, 9090)
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# pod_spec, _ = self.harness.get_pod_spec()
-
-# self.assertDictEqual(expected_result, pod_spec)
-
-# def test_on_kafka_unit_relation_changed(self) -> NoReturn:
-# """Test to see if kafka relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.message_host)
-# self.assertIsNone(self.harness.charm.state.message_port)
-
-# relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# self.assertEqual(self.harness.charm.state.message_host, "kafka")
-# self.assertEqual(self.harness.charm.state.message_port, 9092)
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertNotIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertIn("prometheus", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_mongodb_unit_relation_changed(self) -> NoReturn:
-# """Test to see if mongodb relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.database_uri)
-
-# relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# relation_id, "mongodb/0", {"connection_string": "mongodb://mongo:27017"}
-# )
-
-# self.assertEqual(self.harness.charm.state.database_uri, "mongodb://mongo:27017")
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertNotIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertIn("prometheus", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_prometheus_unit_relation_changed(self) -> NoReturn:
-# """Test to see if prometheus relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.prometheus_host)
-# self.assertIsNone(self.harness.charm.state.prometheus_port)
-
-# relation_id = self.harness.add_relation("prometheus", "prometheus")
-# self.harness.add_relation_unit(relation_id, "prometheus/0")
-# self.harness.update_relation_data(
-# relation_id, "prometheus", {"hostname": "prometheus", "port": 9090}
-# )
-
-# self.assertEqual(self.harness.charm.state.prometheus_host, "prometheus")
-# self.assertEqual(self.harness.charm.state.prometheus_port, 9090)
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertNotIn("prometheus", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-
-# if __name__ == "__main__":
-# unittest.main()
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-from typing import NoReturn
-import unittest
-
-import pod_spec
-
-
-class TestPodSpec(unittest.TestCase):
- """Pod spec unit tests."""
-
- def test_make_pod_ports(self) -> NoReturn:
- """Testing make pod ports."""
- port = 8000
-
- expected_result = [
- {
- "name": "mon",
- "containerPort": port,
- "protocol": "TCP",
- }
- ]
-
- pod_ports = pod_spec._make_pod_ports(port)
-
- self.assertListEqual(expected_result, pod_ports)
-
- def test_make_pod_envconfig(self) -> NoReturn:
- """Testing make pod envconfig."""
- config = {
- "openstack_default_granularity": 300,
- "global_request_timeout": 10,
- "log_level": "INFO",
- "database_commonkey": "osm",
- "collector_interval": 30,
- "evaluator_interval": 30,
- "vca_host": "admin",
- "vca_user": "admin",
- "vca_password": "secret",
- "vca_cacert": "",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
-
- expected_result = {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": config[
- "openstack_default_granularity"
- ],
- "OSMMON_GLOBAL_REQUEST_TIMEOUT": config["global_request_timeout"],
- "OSMMON_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMMON_COLLECTOR_INTERVAL": config["collector_interval"],
- "OSMMON_EVALUATOR_INTERVAL": config["evaluator_interval"],
- "OSMMON_MESSAGE_DRIVER": "kafka",
- "OSMMON_MESSAGE_HOST": relation_state["message_host"],
- "OSMMON_MESSAGE_PORT": relation_state["message_port"],
- "OSMMON_DATABASE_DRIVER": "mongo",
- "OSMMON_DATABASE_URI": relation_state["database_uri"],
- "OSMMON_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMMON_PROMETHEUS_URL": f"http://{relation_state['prometheus_host']}:{relation_state['prometheus_port']}",
- "OSMMON_VCA_HOST": config["vca_host"],
- "OSMMON_VCA_USER": config["vca_user"],
- "OSMMON_VCA_SECRET": config["vca_password"],
- "OSMMON_VCA_CACERT": config["vca_cacert"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_startup_probe(self) -> NoReturn:
- """Testing make startup probe."""
- expected_result = {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
- startup_probe = pod_spec._make_startup_probe()
-
- self.assertDictEqual(expected_result, startup_probe)
-
- def test_make_readiness_probe(self) -> NoReturn:
- """Testing make readiness probe."""
- port = 8000
-
- expected_result = {
- "tcpSocket": {
- "port": port,
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
- readiness_probe = pod_spec._make_readiness_probe(port)
-
- self.assertDictEqual(expected_result, readiness_probe)
-
- def test_make_liveness_probe(self) -> NoReturn:
- """Testing make liveness probe."""
- port = 8000
-
- expected_result = {
- "tcpSocket": {
- "port": port,
- },
- "initialDelaySeconds": 45,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
- liveness_probe = pod_spec._make_liveness_probe(port)
-
- self.assertDictEqual(expected_result, liveness_probe)
-
- def test_make_pod_spec(self) -> NoReturn:
- """Testing make pod spec."""
- image_info = {"upstream-source": "opensourcemano/mon:8"}
- config = {
- "site_url": "",
- "openstack_default_granularity": 300,
- "global_request_timeout": 10,
- "log_level": "INFO",
- "database_commonkey": "osm",
- "collector_interval": 30,
- "evaluator_interval": 30,
- "vca_host": "admin",
- "vca_user": "admin",
- "vca_password": "secret",
- "vca_cacert": "",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
- app_name = "mon"
- port = 8000
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": app_name,
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": config[
- "openstack_default_granularity"
- ],
- "OSMMON_GLOBAL_REQUEST_TIMEOUT": config[
- "global_request_timeout"
- ],
- "OSMMON_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMMON_COLLECTOR_INTERVAL": config["collector_interval"],
- "OSMMON_EVALUATOR_INTERVAL": config["evaluator_interval"],
- "OSMMON_MESSAGE_DRIVER": "kafka",
- "OSMMON_MESSAGE_HOST": relation_state["message_host"],
- "OSMMON_MESSAGE_PORT": relation_state["message_port"],
- "OSMMON_DATABASE_DRIVER": "mongo",
- "OSMMON_DATABASE_URI": relation_state["database_uri"],
- "OSMMON_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMMON_PROMETHEUS_URL": (
- f"http://{relation_state['prometheus_host']}:{relation_state['prometheus_port']}"
- ),
- "OSMMON_VCA_HOST": config["vca_host"],
- "OSMMON_VCA_USER": config["vca_user"],
- "OSMMON_VCA_SECRET": config["vca_password"],
- "OSMMON_VCA_CACERT": config["vca_cacert"],
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_without_image_info(self) -> NoReturn:
- """Testing make pod spec without image_info."""
- image_info = None
- config = {
- "site_url": "",
- "openstack_default_granularity": 300,
- "global_request_timeout": 10,
- "log_level": "INFO",
- "database_commonkey": "osm",
- "collector_interval": 30,
- "evaluator_interval": 30,
- "vca_host": "admin",
- "vca_user": "admin",
- "vca_password": "secret",
- "vca_cacert": "",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
- app_name = "mon"
- port = 8000
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertIsNone(spec)
-
- def test_make_pod_spec_without_config(self) -> NoReturn:
- """Testing make pod spec without config."""
- image_info = {"upstream-source": "opensourcemano/mon:8"}
- config = {}
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
- app_name = "mon"
- port = 8000
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
- def test_make_pod_spec_without_relation_state(self) -> NoReturn:
- """Testing make pod spec without relation_state."""
- image_info = {"upstream-source": "opensourcemano/mon:8"}
- config = {
- "site_url": "",
- "openstack_default_granularity": 300,
- "global_request_timeout": 10,
- "log_level": "INFO",
- "database_commonkey": "osm",
- "collector_interval": 30,
- "evaluator_interval": 30,
- "vca_host": "admin",
- "vca_user": "admin",
- "vca_password": "secret",
- "vca_cacert": "",
- }
- relation_state = {}
- app_name = "mon"
- port = 8000
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv =
- VIRTUAL_ENV={envdir}
- PYTHONPATH = {toxinidir}:{toxinidir}/lib:{toxinidir}/src
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- Copyright 2020 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.
-
-For those usages not covered by the Apache License, Version 2.0 please
-contact: legal@canonical.com
-
-To get in touch with the maintainers, please contact:
-osm-charmers@lists.launchpad.net -->
-
-# NBI operator Charm for Kubernetes
-
-## Requirements
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-options:
- max_file_size:
- type: int
- description: |
- The maximum file size, in megabytes. If there is a reverse proxy in front
- of Keystone, it may need to be configured to handle the requested size.
- Note: if set to 0, there is no limit.
- default: 0
- ingress_class:
- type: string
- description: |
- Ingress class name. This is useful for selecting the ingress to be used
- in case there are multiple ingresses in the underlying k8s clusters.
- ingress_whitelist_source_range:
- type: string
- description: |
- A comma-separated list of CIDRs to store in the
- ingress.kubernetes.io/whitelist-source-range annotation.
-
- This can be used to lock down access to
- Keystone based on source IP address.
- default: ""
- tls_secret_name:
- type: string
- description: TLS Secret name
- default: ""
- site_url:
- type: string
- description: Ingress URL
- default: ""
- cluster_issuer:
- type: string
- description: Name of the cluster issuer for TLS certificates
- default: ""
- log_level:
- description: "Log Level"
- type: string
- default: "INFO"
- database_commonkey:
- description: Database COMMON KEY
- type: string
- default: osm
- auth_backend:
- type: string
- description: Authentication backend ('internal' or 'keystone')
- default: internal
- enable_test:
- type: boolean
- description: Enable test endpoints of NBI.
- default: false
- mongodb_uri:
- type: string
- description: MongoDB URI (external database)
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- debug_mode:
- description: |
- If true, debug mode is activated. It means that the service will not run,
- and instead, the command for the container will be a `sleep infinity`.
- Note: If enabled, security_context will be disabled.
- type: boolean
- default: false
- debug_pubkey:
- description: |
- Public SSH key that will be injected to the application pod.
- type: string
- debug_nbi_local_path:
- description: |
- Local full path to the NBI project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_common_local_path:
- description: |
- Local full path to the COMMON project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
+++ /dev/null
-# Copyright 2022 Canonical Ltd.
-# See LICENSE file for licensing details.
-#
-# 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.
-
-"""Kafka library.
-
-This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
-`kafka` [interface](https://juju.is/docs/sdk/relations).
-
-The *provider* side of this interface is implemented by the
-[kafka-k8s Charmed Operator](https://charmhub.io/kafka-k8s).
-
-Any Charmed Operator that *requires* Kafka for providing its
-service should implement the *requirer* side of this interface.
-
-In a nutshell using this library to implement a Charmed Operator *requiring*
-Kafka would look like
-
-```
-$ charmcraft fetch-lib charms.kafka_k8s.v0.kafka
-```
-
-`metadata.yaml`:
-
-```
-requires:
- kafka:
- interface: kafka
- limit: 1
-```
-
-`src/charm.py`:
-
-```
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.charm import CharmBase
-
-
-class MyCharm(CharmBase):
-
- on = KafkaEvents()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.kafka = KafkaRequires(self)
- self.framework.observe(
- self.on.kafka_available,
- self._on_kafka_available,
- )
- self.framework.observe(
- self.on.kafka_broken,
- self._on_kafka_broken,
- )
-
- def _on_kafka_available(self, event):
- # Get Kafka host and port
- host: str = self.kafka.host
- port: int = self.kafka.port
- # host => "kafka-k8s"
- # port => 9092
-
- def _on_kafka_broken(self, event):
- # Stop service
- # ...
- self.unit.status = BlockedStatus("need kafka relation")
-```
-
-You can file bugs
-[here](https://github.com/charmed-osm/kafka-k8s-operator/issues)!
-"""
-
-from typing import Optional
-
-from ops.charm import CharmBase, CharmEvents
-from ops.framework import EventBase, EventSource, Object
-
-# The unique Charmhub library identifier, never change it
-from ops.model import Relation
-
-LIBID = "eacc8c85082347c9aae740e0220b8376"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 0
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 3
-
-
-KAFKA_HOST_APP_KEY = "host"
-KAFKA_PORT_APP_KEY = "port"
-
-
-class _KafkaAvailableEvent(EventBase):
- """Event emitted when Kafka is available."""
-
-
-class _KafkaBrokenEvent(EventBase):
- """Event emitted when Kafka relation is broken."""
-
-
-class KafkaEvents(CharmEvents):
- """Kafka events.
-
- This class defines the events that Kafka can emit.
-
- Events:
- kafka_available (_KafkaAvailableEvent)
- """
-
- kafka_available = EventSource(_KafkaAvailableEvent)
- kafka_broken = EventSource(_KafkaBrokenEvent)
-
-
-class KafkaRequires(Object):
- """Requires-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self.charm = charm
- self._endpoint_name = endpoint_name
-
- # Observe relation events
- event_observe_mapping = {
- charm.on[self._endpoint_name].relation_changed: self._on_relation_changed,
- charm.on[self._endpoint_name].relation_broken: self._on_relation_broken,
- }
- for event, observer in event_observe_mapping.items():
- self.framework.observe(event, observer)
-
- def _on_relation_changed(self, event) -> None:
- if event.relation.app and all(
- key in event.relation.data[event.relation.app]
- for key in (KAFKA_HOST_APP_KEY, KAFKA_PORT_APP_KEY)
- ):
- self.charm.on.kafka_available.emit()
-
- def _on_relation_broken(self, _) -> None:
- self.charm.on.kafka_broken.emit()
-
- @property
- def host(self) -> str:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- relation.data[relation.app].get(KAFKA_HOST_APP_KEY)
- if relation and relation.app
- else None
- )
-
- @property
- def port(self) -> int:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- int(relation.data[relation.app].get(KAFKA_PORT_APP_KEY))
- if relation and relation.app
- else None
- )
-
-
-class KafkaProvides(Object):
- """Provides-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self._endpoint_name = endpoint_name
-
- def set_host_info(self, host: str, port: int, relation: Optional[Relation] = None) -> None:
- """Set Kafka host and port.
-
- This function writes in the application data of the relation, therefore,
- only the unit leader can call it.
-
- Args:
- host (str): Kafka hostname or IP address.
- port (int): Kafka port.
- relation (Optional[Relation]): Relation to update.
- If not specified, all relations will be updated.
-
- Raises:
- Exception: if a non-leader unit calls this function.
- """
- if not self.model.unit.is_leader():
- raise Exception("only the leader set host information.")
-
- if relation:
- self._update_relation_data(host, port, relation)
- return
-
- for relation in self.model.relations[self._endpoint_name]:
- self._update_relation_data(host, port, relation)
-
- def _update_relation_data(self, host: str, port: int, relation: Relation) -> None:
- """Update data in relation if needed."""
- relation.data[self.model.app][KAFKA_HOST_APP_KEY] = host
- relation.data[self.model.app][KAFKA_PORT_APP_KEY] = str(port)
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-name: osm-nbi
-summary: OSM Northbound Interface (NBI)
-description: |
- A CAAS charm to deploy OSM's Northbound Interface (NBI).
-series:
- - kubernetes
-tags:
- - kubernetes
- - osm
- - nbi
-min-juju-version: 2.8.0
-deployment:
- type: stateless
- service: cluster
-resources:
- image:
- type: oci-image
- description: OSM docker image for NBI
- upstream-source: "opensourcemano/nbi:latest"
-requires:
- kafka:
- interface: kafka
- limit: 1
- mongodb:
- interface: mongodb
- limit: 1
- keystone:
- interface: keystone
- limit: 1
- prometheus:
- interface: prometheus
- limit: 1
-provides:
- nbi:
- interface: http
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-from ipaddress import ip_network
-import logging
-from typing import NoReturn, Optional
-from urllib.parse import urlparse
-
-
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.http import HttpServer
-from opslib.osm.interfaces.keystone import KeystoneClient
-from opslib.osm.interfaces.mongo import MongoClient
-from opslib.osm.interfaces.prometheus import PrometheusClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- IngressResourceV3Builder,
- PodRestartPolicy,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-PORT = 9999
-
-
-class ConfigModel(ModelValidator):
- enable_test: bool
- auth_backend: str
- database_commonkey: str
- log_level: str
- max_file_size: int
- site_url: Optional[str]
- cluster_issuer: Optional[str]
- ingress_class: Optional[str]
- ingress_whitelist_source_range: Optional[str]
- tls_secret_name: Optional[str]
- mongodb_uri: Optional[str]
- image_pull_policy: str
- debug_mode: bool
- security_context: bool
-
- @validator("auth_backend")
- def validate_auth_backend(cls, v):
- if v not in {"internal", "keystone"}:
- raise ValueError("value must be 'internal' or 'keystone'")
- return v
-
- @validator("log_level")
- def validate_log_level(cls, v):
- if v not in {"INFO", "DEBUG"}:
- raise ValueError("value must be INFO or DEBUG")
- return v
-
- @validator("max_file_size")
- def validate_max_file_size(cls, v):
- if v < 0:
- raise ValueError("value must be equal or greater than 0")
- return v
-
- @validator("site_url")
- def validate_site_url(cls, v):
- if v:
- parsed = urlparse(v)
- if not parsed.scheme.startswith("http"):
- raise ValueError("value must start with http")
- return v
-
- @validator("ingress_whitelist_source_range")
- def validate_ingress_whitelist_source_range(cls, v):
- if v:
- ip_network(v)
- return v
-
- @validator("mongodb_uri")
- def validate_mongodb_uri(cls, v):
- if v and not v.startswith("mongodb://"):
- raise ValueError("mongodb_uri is not properly formed")
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
-
-class NbiCharm(CharmedOsmBase):
- on = KafkaEvents()
-
- def __init__(self, *args) -> NoReturn:
- super().__init__(
- *args,
- oci_image="image",
- vscode_workspace=VSCODE_WORKSPACE,
- )
- if self.config.get("debug_mode"):
- self.enable_debug_mode(
- pubkey=self.config.get("debug_pubkey"),
- hostpaths={
- "NBI": {
- "hostpath": self.config.get("debug_nbi_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_nbi",
- },
- "osm_common": {
- "hostpath": self.config.get("debug_common_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_common",
- },
- },
- )
-
- self.kafka = KafkaRequires(self)
- self.framework.observe(self.on.kafka_available, self.configure_pod)
- self.framework.observe(self.on.kafka_broken, self.configure_pod)
-
- self.mongodb_client = MongoClient(self, "mongodb")
- self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
-
- self.prometheus_client = PrometheusClient(self, "prometheus")
- self.framework.observe(
- self.on["prometheus"].relation_changed, self.configure_pod
- )
- self.framework.observe(
- self.on["prometheus"].relation_broken, self.configure_pod
- )
-
- self.keystone_client = KeystoneClient(self, "keystone")
- self.framework.observe(self.on["keystone"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["keystone"].relation_broken, self.configure_pod)
-
- self.http_server = HttpServer(self, "nbi")
- self.framework.observe(self.on["nbi"].relation_joined, self._publish_nbi_info)
-
- def _publish_nbi_info(self, event):
- """Publishes NBI information.
-
- Args:
- event (EventBase): RO relation event.
- """
- if self.unit.is_leader():
- self.http_server.publish_info(self.app.name, PORT)
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if not self.kafka.host or not self.kafka.port:
- missing_relations.append("kafka")
- if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit():
- missing_relations.append("mongodb")
- if self.prometheus_client.is_missing_data_in_app():
- missing_relations.append("prometheus")
- if config.auth_backend == "keystone":
- if self.keystone_client.is_missing_data_in_app():
- missing_relations.append("keystone")
-
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
-
- if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit():
- raise Exception("Mongodb data cannot be provided via config and relation")
-
- # Check relations
- self._check_missing_dependencies(config)
-
- security_context_enabled = (
- config.security_context if not config.debug_mode else False
- )
-
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=security_context_enabled
- )
-
- # Add secrets to the pod
- mongodb_secret_name = f"{self.app.name}-mongodb-secret"
- pod_spec_builder.add_secret(
- mongodb_secret_name,
- {
- "uri": config.mongodb_uri or self.mongodb_client.connection_string,
- "commonkey": config.database_commonkey,
- },
- )
-
- # Build Init Container
- pod_spec_builder.add_init_container(
- {
- "name": "init-check",
- "image": "alpine:latest",
- "command": [
- "sh",
- "-c",
- f"until (nc -zvw1 {self.kafka.host} {self.kafka.port} ); do sleep 3; done; exit 0",
- ],
- }
- )
-
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=security_context_enabled,
- )
- container_builder.add_port(name=self.app.name, port=PORT)
- container_builder.add_tcpsocket_readiness_probe(
- PORT,
- initial_delay_seconds=5,
- timeout_seconds=5,
- )
- container_builder.add_tcpsocket_liveness_probe(
- PORT,
- initial_delay_seconds=45,
- timeout_seconds=10,
- )
- container_builder.add_envs(
- {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMNBI_SERVER_ENABLE_TEST": config.enable_test,
- "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
- # Kafka configuration
- "OSMNBI_MESSAGE_HOST": self.kafka.host,
- "OSMNBI_MESSAGE_DRIVER": "kafka",
- "OSMNBI_MESSAGE_PORT": self.kafka.port,
- # Database configuration
- "OSMNBI_DATABASE_DRIVER": "mongo",
- # Storage configuration
- "OSMNBI_STORAGE_DRIVER": "mongo",
- "OSMNBI_STORAGE_PATH": "/app/storage",
- "OSMNBI_STORAGE_COLLECTION": "files",
- # Prometheus configuration
- "OSMNBI_PROMETHEUS_HOST": self.prometheus_client.hostname,
- "OSMNBI_PROMETHEUS_PORT": self.prometheus_client.port,
- # Log configuration
- "OSMNBI_LOG_LEVEL": config.log_level,
- }
- )
- container_builder.add_secret_envs(
- secret_name=mongodb_secret_name,
- envs={
- "OSMNBI_DATABASE_URI": "uri",
- "OSMNBI_DATABASE_COMMONKEY": "commonkey",
- "OSMNBI_STORAGE_URI": "uri",
- },
- )
- if config.auth_backend == "internal":
- container_builder.add_env("OSMNBI_AUTHENTICATION_BACKEND", "internal")
- elif config.auth_backend == "keystone":
- keystone_secret_name = f"{self.app.name}-keystone-secret"
- pod_spec_builder.add_secret(
- keystone_secret_name,
- {
- "url": self.keystone_client.host,
- "port": self.keystone_client.port,
- "user_domain": self.keystone_client.user_domain_name,
- "project_domain": self.keystone_client.project_domain_name,
- "service_username": self.keystone_client.username,
- "service_password": self.keystone_client.password,
- "service_project": self.keystone_client.service,
- },
- )
- container_builder.add_env("OSMNBI_AUTHENTICATION_BACKEND", "keystone")
- container_builder.add_secret_envs(
- secret_name=keystone_secret_name,
- envs={
- "OSMNBI_AUTHENTICATION_AUTH_URL": "url",
- "OSMNBI_AUTHENTICATION_AUTH_PORT": "port",
- "OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME": "user_domain",
- "OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME": "project_domain",
- "OSMNBI_AUTHENTICATION_SERVICE_USERNAME": "service_username",
- "OSMNBI_AUTHENTICATION_SERVICE_PASSWORD": "service_password",
- "OSMNBI_AUTHENTICATION_SERVICE_PROJECT": "service_project",
- },
- )
- container = container_builder.build()
-
- # Add container to pod spec
- pod_spec_builder.add_container(container)
-
- # Add ingress resources to pod spec if site url exists
- if config.site_url:
- parsed = urlparse(config.site_url)
- annotations = {
- "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
- str(config.max_file_size) + "m"
- if config.max_file_size > 0
- else config.max_file_size
- ),
- "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
- }
- if config.ingress_class:
- annotations["kubernetes.io/ingress.class"] = config.ingress_class
- ingress_resource_builder = IngressResourceV3Builder(
- f"{self.app.name}-ingress", annotations
- )
-
- if config.ingress_whitelist_source_range:
- annotations[
- "nginx.ingress.kubernetes.io/whitelist-source-range"
- ] = config.ingress_whitelist_source_range
-
- if config.cluster_issuer:
- annotations["cert-manager.io/cluster-issuer"] = config.cluster_issuer
-
- if parsed.scheme == "https":
- ingress_resource_builder.add_tls(
- [parsed.hostname], config.tls_secret_name
- )
- else:
- annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
-
- ingress_resource_builder.add_rule(parsed.hostname, self.app.name, PORT)
- ingress_resource = ingress_resource_builder.build()
- pod_spec_builder.add_ingress_resource(ingress_resource)
-
- # Add restart policy
- restart_policy = PodRestartPolicy()
- restart_policy.add_secrets()
- pod_spec_builder.set_restart_policy(restart_policy)
-
- return pod_spec_builder.build()
-
-
-VSCODE_WORKSPACE = {
- "folders": [
- {"path": "/usr/lib/python3/dist-packages/osm_nbi"},
- {"path": "/usr/lib/python3/dist-packages/osm_common"},
- {"path": "/usr/lib/python3/dist-packages/osm_im"},
- ],
- "settings": {},
- "launch": {
- "version": "0.2.0",
- "configurations": [
- {
- "name": "NBI",
- "type": "python",
- "request": "launch",
- "module": "osm_nbi.nbi",
- "justMyCode": False,
- }
- ],
- },
-}
-
-
-if __name__ == "__main__":
- main(NbiCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-from ipaddress import ip_network
-from typing import Any, Callable, Dict, List, NoReturn
-from urllib.parse import urlparse
-
-
-def _validate_max_file_size(max_file_size: int, site_url: str) -> bool:
- """Validate max_file_size.
-
- Args:
- max_file_size (int): maximum file size allowed.
- site_url (str): endpoint url.
-
- Returns:
- bool: True if valid, false otherwise.
- """
- if not site_url:
- return True
-
- parsed = urlparse(site_url)
-
- if not parsed.scheme.startswith("http"):
- return True
-
- if max_file_size is None:
- return False
-
- return max_file_size >= 0
-
-
-def _validate_ip_network(network: str) -> bool:
- """Validate IP network.
-
- Args:
- network (str): IP network range.
-
- Returns:
- bool: True if valid, false otherwise.
- """
- if not network:
- return True
-
- try:
- ip_network(network)
- except ValueError:
- return False
-
- return True
-
-
-def _validate_keystone_config(keystone: bool, value: Any, validator: Callable) -> bool:
- """Validate keystone configurations.
-
- Args:
- keystone (bool): is keystone enabled, true if so, false otherwise.
- value (Any): value to be validated.
- validator (Callable): function to validate configuration.
-
- Returns:
- bool: true if valid, false otherwise.
- """
- if not keystone:
- return True
-
- return validator(value)
-
-
-def _validate_data(
- config_data: Dict[str, Any], relation_data: Dict[str, Any], keystone: bool
-) -> NoReturn:
- """Validate input data.
-
- Args:
- config_data (Dict[str, Any]): configuration data.
- relation_data (Dict[str, Any]): relation data.
- keystone (bool): is keystone to be used.
- """
- config_validators = {
- "enable_test": lambda value, _: isinstance(value, bool),
- "database_commonkey": lambda value, _: (
- isinstance(value, str) and len(value) > 1
- ),
- "log_level": lambda value, _: (
- isinstance(value, str) and value in ("INFO", "DEBUG")
- ),
- "auth_backend": lambda value, _: (
- isinstance(value, str) and (value == "internal" or value == "keystone")
- ),
- "site_url": lambda value, _: isinstance(value, str)
- if value is not None
- else True,
- "max_file_size": lambda value, values: _validate_max_file_size(
- value, values.get("site_url")
- ),
- "ingress_whitelist_source_range": lambda value, _: _validate_ip_network(value),
- "tls_secret_name": lambda value, _: isinstance(value, str)
- if value is not None
- else True,
- }
- relation_validators = {
- "message_host": lambda value, _: isinstance(value, str),
- "message_port": lambda value, _: isinstance(value, int) and value > 0,
- "database_uri": lambda value, _: (
- isinstance(value, str) and value.startswith("mongodb://")
- ),
- "prometheus_host": lambda value, _: isinstance(value, str),
- "prometheus_port": lambda value, _: isinstance(value, int) and value > 0,
- "keystone_host": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, str) and len(x) > 0
- ),
- "keystone_port": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, int) and x > 0
- ),
- "keystone_user_domain_name": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, str) and len(x) > 0
- ),
- "keystone_project_domain_name": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, str) and len(x) > 0
- ),
- "keystone_username": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, str) and len(x) > 0
- ),
- "keystone_password": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, str) and len(x) > 0
- ),
- "keystone_service": lambda value, _: _validate_keystone_config(
- keystone, value, lambda x: isinstance(x, str) and len(x) > 0
- ),
- }
- problems = []
-
- for key, validator in config_validators.items():
- valid = validator(config_data.get(key), config_data)
-
- if not valid:
- problems.append(key)
-
- for key, validator in relation_validators.items():
- valid = validator(relation_data.get(key), relation_data)
-
- if not valid:
- problems.append(key)
-
- if len(problems) > 0:
- raise ValueError("Errors found in: {}".format(", ".join(problems)))
-
-
-def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
- """Generate pod ports details.
-
- Args:
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod port details.
- """
- return [{"name": "nbi", "containerPort": port, "protocol": "TCP"}]
-
-
-def _make_pod_envconfig(
- config: Dict[str, Any], relation_state: Dict[str, Any]
-) -> Dict[str, Any]:
- """Generate pod environment configuration.
-
- Args:
- config (Dict[str, Any]): configuration information.
- relation_state (Dict[str, Any]): relation state information.
-
- Returns:
- Dict[str, Any]: pod environment configuration.
- """
- envconfig = {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMNBI_SERVER_ENABLE_TEST": config["enable_test"],
- "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
- # Kafka configuration
- "OSMNBI_MESSAGE_HOST": relation_state["message_host"],
- "OSMNBI_MESSAGE_DRIVER": "kafka",
- "OSMNBI_MESSAGE_PORT": relation_state["message_port"],
- # Database configuration
- "OSMNBI_DATABASE_DRIVER": "mongo",
- "OSMNBI_DATABASE_URI": relation_state["database_uri"],
- "OSMNBI_DATABASE_COMMONKEY": config["database_commonkey"],
- # Storage configuration
- "OSMNBI_STORAGE_DRIVER": "mongo",
- "OSMNBI_STORAGE_PATH": "/app/storage",
- "OSMNBI_STORAGE_COLLECTION": "files",
- "OSMNBI_STORAGE_URI": relation_state["database_uri"],
- # Prometheus configuration
- "OSMNBI_PROMETHEUS_HOST": relation_state["prometheus_host"],
- "OSMNBI_PROMETHEUS_PORT": relation_state["prometheus_port"],
- # Log configuration
- "OSMNBI_LOG_LEVEL": config["log_level"],
- }
-
- if config["auth_backend"] == "internal":
- envconfig["OSMNBI_AUTHENTICATION_BACKEND"] = "internal"
- elif config["auth_backend"] == "keystone":
- envconfig.update(
- {
- "OSMNBI_AUTHENTICATION_BACKEND": "keystone",
- "OSMNBI_AUTHENTICATION_AUTH_URL": relation_state["keystone_host"],
- "OSMNBI_AUTHENTICATION_AUTH_PORT": relation_state["keystone_port"],
- "OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME": relation_state[
- "keystone_user_domain_name"
- ],
- "OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME": relation_state[
- "keystone_project_domain_name"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_USERNAME": relation_state[
- "keystone_username"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_PASSWORD": relation_state[
- "keystone_password"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_PROJECT": relation_state[
- "keystone_service"
- ],
- }
- )
- else:
- raise ValueError("auth_backend needs to be either internal or keystone")
-
- return envconfig
-
-
-def _make_pod_ingress_resources(
- config: Dict[str, Any], app_name: str, port: int
-) -> List[Dict[str, Any]]:
- """Generate pod ingress resources.
-
- Args:
- config (Dict[str, Any]): configuration information.
- app_name (str): application name.
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod ingress resources.
- """
- site_url = config.get("site_url")
-
- if not site_url:
- return
-
- parsed = urlparse(site_url)
-
- if not parsed.scheme.startswith("http"):
- return
-
- max_file_size = config["max_file_size"]
- ingress_whitelist_source_range = config["ingress_whitelist_source_range"]
-
- annotations = {
- "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
- str(max_file_size) + "m" if max_file_size > 0 else max_file_size
- ),
- "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
- }
-
- if ingress_whitelist_source_range:
- annotations[
- "nginx.ingress.kubernetes.io/whitelist-source-range"
- ] = ingress_whitelist_source_range
-
- ingress_spec_tls = None
-
- if parsed.scheme == "https":
- ingress_spec_tls = [{"hosts": [parsed.hostname]}]
- tls_secret_name = config["tls_secret_name"]
- if tls_secret_name:
- ingress_spec_tls[0]["secretName"] = tls_secret_name
- else:
- annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
-
- ingress = {
- "name": "{}-ingress".format(app_name),
- "annotations": annotations,
- "spec": {
- "rules": [
- {
- "host": parsed.hostname,
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": app_name,
- "servicePort": port,
- },
- }
- ]
- },
- }
- ]
- },
- }
- if ingress_spec_tls:
- ingress["spec"]["tls"] = ingress_spec_tls
-
- return [ingress]
-
-
-def _make_startup_probe() -> Dict[str, Any]:
- """Generate startup probe.
-
- Returns:
- Dict[str, Any]: startup probe.
- """
- return {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
-
-def _make_readiness_probe(port: int) -> Dict[str, Any]:
- """Generate readiness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: readiness probe.
- """
- return {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
-
-def _make_liveness_probe(port: int) -> Dict[str, Any]:
- """Generate liveness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: liveness probe.
- """
- return {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
-
-def make_pod_spec(
- image_info: Dict[str, str],
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
- app_name: str = "nbi",
- port: int = 9999,
-) -> Dict[str, Any]:
- """Generate the pod spec information.
-
- Args:
- image_info (Dict[str, str]): Object provided by
- OCIImageResource("image").fetch().
- config (Dict[str, Any]): Configuration information.
- relation_state (Dict[str, Any]): Relation state information.
- app_name (str, optional): Application name. Defaults to "nbi".
- port (int, optional): Port for the container. Defaults to 9999.
-
- Returns:
- Dict[str, Any]: Pod spec dictionary for the charm.
- """
- if not image_info:
- return None
-
- _validate_data(config, relation_state, config.get("auth_backend") == "keystone")
-
- ports = _make_pod_ports(port)
- env_config = _make_pod_envconfig(config, relation_state)
- ingress_resources = _make_pod_ingress_resources(config, app_name, port)
-
- return {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": ports,
- "envConfig": env_config,
- }
- ],
- "kubernetesResources": {
- "ingressResources": ingress_resources or [],
- },
- }
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import sys
-from typing import NoReturn
-import unittest
-
-
-from charm import NbiCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-class TestCharm(unittest.TestCase):
- """Prometheus Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(NbiCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "enable_test": False,
- "auth_backend": "internal",
- "database_commonkey": "key",
- "mongodb_uri": "",
- "log_level": "INFO",
- "max_file_size": 0,
- "ingress_whitelist_source_range": "",
- "tls_secret_name": "",
- "site_url": "https://nbi.192.168.100.100.nip.io",
- "cluster_issuer": "vault-issuer",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mongodb", "kafka", "prometheus"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations_internal_and_mongodb_config(
- self,
- ) -> NoReturn:
- "Test with relations and mongodb config (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- self.initialize_prometheus_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations_internal(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_prometheus_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations_and_mongodb_config_with_keystone_missing(
- self,
- ) -> NoReturn:
- "Test with relations and mongodb config (keystone)"
- self.harness.update_config({"auth_backend": "keystone"})
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- self.initialize_prometheus_relation()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue("keystone" in self.harness.charm.unit.status.message)
-
- def test_with_relations_keystone_missing(
- self,
- ) -> NoReturn:
- "Test with relations (keystone)"
- self.harness.update_config({"auth_backend": "keystone"})
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_prometheus_relation()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue("keystone" in self.harness.charm.unit.status.message)
-
- def test_with_relations_and_mongodb_config_with_keystone(
- self,
- ) -> NoReturn:
- "Test with relations (keystone)"
- self.harness.update_config({"auth_backend": "keystone"})
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- self.initialize_prometheus_relation()
- self.initialize_keystone_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations_keystone(
- self,
- ) -> NoReturn:
- "Test with relations (keystone)"
- self.harness.update_config({"auth_backend": "keystone"})
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_prometheus_relation()
- self.initialize_keystone_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_mongodb_exception_relation_and_config(
- self,
- ) -> NoReturn:
- self.initialize_mongo_config()
- self.initialize_mongo_relation()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def initialize_kafka_relation(self):
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- def initialize_mongo_config(self):
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- def initialize_mongo_relation(self):
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
- def initialize_keystone_relation(self):
- keystone_relation_id = self.harness.add_relation("keystone", "keystone")
- self.harness.add_relation_unit(keystone_relation_id, "keystone/0")
- self.harness.update_relation_data(
- keystone_relation_id,
- "keystone",
- {
- "host": "host",
- "port": 5000,
- "user_domain_name": "ud",
- "project_domain_name": "pd",
- "username": "u",
- "password": "p",
- "service": "s",
- "keystone_db_password": "something",
- "region_id": "something",
- "admin_username": "something",
- "admin_password": "something",
- "admin_project_name": "something",
- },
- )
-
- def initialize_prometheus_relation(self):
- prometheus_relation_id = self.harness.add_relation("prometheus", "prometheus")
- self.harness.add_relation_unit(prometheus_relation_id, "prometheus/0")
- self.harness.update_relation_data(
- prometheus_relation_id,
- "prometheus",
- {"hostname": "prometheus", "port": 9090},
- )
-
-
-if __name__ == "__main__":
- unittest.main()
-
-
-# class TestCharm(unittest.TestCase):
-# """Prometheus Charm unit tests."""
-
-# def setUp(self) -> NoReturn:
-# """Test setup"""
-# self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
-# self.harness = Harness(NbiCharm)
-# self.harness.set_leader(is_leader=True)
-# self.harness.begin()
-# self.config = {
-# "enable_ng_ro": True,
-# "database_commonkey": "commonkey",
-# "log_level": "INFO",
-# "vim_database": "db_name",
-# "ro_database": "ro_db_name",
-# "openmano_tenant": "mano",
-# }
-
-# def test_config_changed_no_relations(
-# self,
-# ) -> NoReturn:
-# """Test ingress resources without HTTP."""
-
-# self.harness.charm.on.config_changed.emit()
-
-# # Assertions
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-# self.assertTrue(
-# all(
-# relation in self.harness.charm.unit.status.message
-# for relation in ["mongodb", "kafka"]
-# )
-# )
-
-# # Disable ng-ro
-# self.harness.update_config({"enable_ng_ro": False})
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-# self.assertTrue(
-# all(
-# relation in self.harness.charm.unit.status.message
-# for relation in ["mysql"]
-# )
-# )
-
-# def test_config_changed_non_leader(
-# self,
-# ) -> NoReturn:
-# """Test ingress resources without HTTP."""
-# self.harness.set_leader(is_leader=False)
-# self.harness.charm.on.config_changed.emit()
-
-# # Assertions
-# self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
-# def test_with_relations_ng(
-# self,
-# ) -> NoReturn:
-# "Test with relations (ng-ro)"
-
-# # Initializing the kafka relation
-# kafka_relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# # Initializing the mongo relation
-# mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# mongodb_relation_id,
-# "mongodb/0",
-# {"connection_string": "mongodb://mongo:27017"},
-# )
-
-# self.harness.charm.on.config_changed.emit()
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-
-# if __name__ == "__main__":
-# unittest.main()
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-from typing import NoReturn
-import unittest
-
-import pod_spec
-
-
-class TestPodSpec(unittest.TestCase):
- """Pod spec unit tests."""
-
- def test_make_pod_ports(self) -> NoReturn:
- """Testing make pod ports."""
- port = 9999
-
- expected_result = [
- {
- "name": "nbi",
- "containerPort": port,
- "protocol": "TCP",
- }
- ]
-
- pod_ports = pod_spec._make_pod_ports(port)
-
- self.assertListEqual(expected_result, pod_ports)
-
- def test_make_pod_envconfig_without_keystone(self) -> NoReturn:
- """Teting make pod envconfig without Keystone."""
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "internal",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
-
- expected_result = {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMNBI_SERVER_ENABLE_TEST": config["enable_test"],
- "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
- "OSMNBI_MESSAGE_HOST": relation_state["message_host"],
- "OSMNBI_MESSAGE_DRIVER": "kafka",
- "OSMNBI_MESSAGE_PORT": relation_state["message_port"],
- "OSMNBI_DATABASE_DRIVER": "mongo",
- "OSMNBI_DATABASE_URI": relation_state["database_uri"],
- "OSMNBI_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMNBI_STORAGE_DRIVER": "mongo",
- "OSMNBI_STORAGE_PATH": "/app/storage",
- "OSMNBI_STORAGE_COLLECTION": "files",
- "OSMNBI_STORAGE_URI": relation_state["database_uri"],
- "OSMNBI_PROMETHEUS_HOST": relation_state["prometheus_host"],
- "OSMNBI_PROMETHEUS_PORT": relation_state["prometheus_port"],
- "OSMNBI_LOG_LEVEL": config["log_level"],
- "OSMNBI_AUTHENTICATION_BACKEND": config["auth_backend"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_pod_envconfig_with_keystone(self) -> NoReturn:
- """Teting make pod envconfig with Keystone."""
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "keystone",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- "keystone_host": "keystone",
- "keystone_port": 5000,
- "keystone_user_domain_name": "user_domain",
- "keystone_project_domain_name": "project_domain",
- "keystone_username": "username",
- "keystone_password": "password",
- "keystone_service": "service",
- }
-
- expected_result = {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMNBI_SERVER_ENABLE_TEST": config["enable_test"],
- "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
- "OSMNBI_MESSAGE_HOST": relation_state["message_host"],
- "OSMNBI_MESSAGE_DRIVER": "kafka",
- "OSMNBI_MESSAGE_PORT": relation_state["message_port"],
- "OSMNBI_DATABASE_DRIVER": "mongo",
- "OSMNBI_DATABASE_URI": relation_state["database_uri"],
- "OSMNBI_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMNBI_STORAGE_DRIVER": "mongo",
- "OSMNBI_STORAGE_PATH": "/app/storage",
- "OSMNBI_STORAGE_COLLECTION": "files",
- "OSMNBI_STORAGE_URI": relation_state["database_uri"],
- "OSMNBI_PROMETHEUS_HOST": relation_state["prometheus_host"],
- "OSMNBI_PROMETHEUS_PORT": relation_state["prometheus_port"],
- "OSMNBI_LOG_LEVEL": config["log_level"],
- "OSMNBI_AUTHENTICATION_BACKEND": config["auth_backend"],
- "OSMNBI_AUTHENTICATION_AUTH_URL": relation_state["keystone_host"],
- "OSMNBI_AUTHENTICATION_AUTH_PORT": relation_state["keystone_port"],
- "OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME": relation_state[
- "keystone_user_domain_name"
- ],
- "OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME": relation_state[
- "keystone_project_domain_name"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_USERNAME": relation_state[
- "keystone_username"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_PASSWORD": relation_state[
- "keystone_password"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_PROJECT": relation_state["keystone_service"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_pod_envconfig_wrong_auth_backend(self) -> NoReturn:
- """Teting make pod envconfig with wrong auth_backend."""
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "kerberos",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- "keystone_host": "keystone",
- "keystone_port": 5000,
- "keystone_user_domain_name": "user_domain",
- "keystone_project_domain_name": "project_domain",
- "keystone_username": "username",
- "keystone_password": "password",
- "keystone_service": "service",
- }
-
- with self.assertRaises(ValueError) as exc:
- pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertTrue(
- "auth_backend needs to be either internal or keystone" in str(exc.exception)
- )
-
- def test_make_pod_ingress_resources_without_site_url(self) -> NoReturn:
- """Testing make pod ingress resources without site_url."""
- config = {"site_url": ""}
- app_name = "nbi"
- port = 9999
-
- pod_ingress_resources = pod_spec._make_pod_ingress_resources(
- config, app_name, port
- )
-
- self.assertIsNone(pod_ingress_resources)
-
- def test_make_pod_ingress_resources(self) -> NoReturn:
- """Testing make pod ingress resources."""
- config = {
- "site_url": "http://nbi",
- "max_file_size": 0,
- "ingress_whitelist_source_range": "",
- }
- app_name = "nbi"
- port = 9999
-
- expected_result = [
- {
- "name": f"{app_name}-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": f"{config['max_file_size']}",
- "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
- "nginx.ingress.kubernetes.io/ssl-redirect": "false",
- },
- "spec": {
- "rules": [
- {
- "host": app_name,
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": app_name,
- "servicePort": port,
- },
- }
- ]
- },
- }
- ]
- },
- }
- ]
-
- pod_ingress_resources = pod_spec._make_pod_ingress_resources(
- config, app_name, port
- )
-
- self.assertListEqual(expected_result, pod_ingress_resources)
-
- def test_make_pod_ingress_resources_with_whitelist_source_range(self) -> NoReturn:
- """Testing make pod ingress resources with whitelist_source_range."""
- config = {
- "site_url": "http://nbi",
- "max_file_size": 0,
- "ingress_whitelist_source_range": "0.0.0.0/0",
- }
- app_name = "nbi"
- port = 9999
-
- expected_result = [
- {
- "name": f"{app_name}-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": f"{config['max_file_size']}",
- "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
- "nginx.ingress.kubernetes.io/ssl-redirect": "false",
- "nginx.ingress.kubernetes.io/whitelist-source-range": config[
- "ingress_whitelist_source_range"
- ],
- },
- "spec": {
- "rules": [
- {
- "host": app_name,
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": app_name,
- "servicePort": port,
- },
- }
- ]
- },
- }
- ]
- },
- }
- ]
-
- pod_ingress_resources = pod_spec._make_pod_ingress_resources(
- config, app_name, port
- )
-
- self.assertListEqual(expected_result, pod_ingress_resources)
-
- def test_make_pod_ingress_resources_with_https(self) -> NoReturn:
- """Testing make pod ingress resources with HTTPs."""
- config = {
- "site_url": "https://nbi",
- "max_file_size": 0,
- "ingress_whitelist_source_range": "",
- "tls_secret_name": "",
- }
- app_name = "nbi"
- port = 9999
-
- expected_result = [
- {
- "name": f"{app_name}-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": f"{config['max_file_size']}",
- "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
- },
- "spec": {
- "rules": [
- {
- "host": app_name,
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": app_name,
- "servicePort": port,
- },
- }
- ]
- },
- }
- ],
- "tls": [{"hosts": [app_name]}],
- },
- }
- ]
-
- pod_ingress_resources = pod_spec._make_pod_ingress_resources(
- config, app_name, port
- )
-
- self.assertListEqual(expected_result, pod_ingress_resources)
-
- def test_make_pod_ingress_resources_with_https_tls_secret_name(self) -> NoReturn:
- """Testing make pod ingress resources with HTTPs and TLS secret name."""
- config = {
- "site_url": "https://nbi",
- "max_file_size": 0,
- "ingress_whitelist_source_range": "",
- "tls_secret_name": "secret_name",
- }
- app_name = "nbi"
- port = 9999
-
- expected_result = [
- {
- "name": f"{app_name}-ingress",
- "annotations": {
- "nginx.ingress.kubernetes.io/proxy-body-size": f"{config['max_file_size']}",
- "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
- },
- "spec": {
- "rules": [
- {
- "host": app_name,
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": app_name,
- "servicePort": port,
- },
- }
- ]
- },
- }
- ],
- "tls": [
- {"hosts": [app_name], "secretName": config["tls_secret_name"]}
- ],
- },
- }
- ]
-
- pod_ingress_resources = pod_spec._make_pod_ingress_resources(
- config, app_name, port
- )
-
- self.assertListEqual(expected_result, pod_ingress_resources)
-
- def test_make_startup_probe(self) -> NoReturn:
- """Testing make startup probe."""
- expected_result = {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
- startup_probe = pod_spec._make_startup_probe()
-
- self.assertDictEqual(expected_result, startup_probe)
-
- def test_make_readiness_probe(self) -> NoReturn:
- """Testing make readiness probe."""
- port = 9999
-
- expected_result = {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
- readiness_probe = pod_spec._make_readiness_probe(port)
-
- self.assertDictEqual(expected_result, readiness_probe)
-
- def test_make_liveness_probe(self) -> NoReturn:
- """Testing make liveness probe."""
- port = 9999
-
- expected_result = {
- "httpGet": {
- "path": "/osm/",
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
- liveness_probe = pod_spec._make_liveness_probe(port)
-
- self.assertDictEqual(expected_result, liveness_probe)
-
- def test_make_pod_spec_without_image_info(self) -> NoReturn:
- """Testing make pod spec without image_info."""
- image_info = None
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "internal",
- "site_url": "",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
- app_name = "nbi"
- port = 9999
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertIsNone(spec)
-
- def test_make_pod_spec_without_config(self) -> NoReturn:
- """Testing make pod spec without config."""
- image_info = {"upstream-source": "opensourcemano/nbi:8"}
- config = {}
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
- app_name = "nbi"
- port = 9999
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
- def test_make_pod_spec_without_relation_state(self) -> NoReturn:
- """Testing make pod spec without relation_state."""
- image_info = {"upstream-source": "opensourcemano/nbi:8"}
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "internal",
- "site_url": "",
- }
- relation_state = {}
- app_name = "nbi"
- port = 9999
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
- def test_make_pod_spec(self) -> NoReturn:
- """Testing make pod spec."""
- image_info = {"upstream-source": "opensourcemano/nbi:8"}
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "internal",
- "site_url": "",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- }
- app_name = "nbi"
- port = 9999
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": "nbi",
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMNBI_SERVER_ENABLE_TEST": config["enable_test"],
- "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
- "OSMNBI_MESSAGE_HOST": relation_state["message_host"],
- "OSMNBI_MESSAGE_DRIVER": "kafka",
- "OSMNBI_MESSAGE_PORT": relation_state["message_port"],
- "OSMNBI_DATABASE_DRIVER": "mongo",
- "OSMNBI_DATABASE_URI": relation_state["database_uri"],
- "OSMNBI_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMNBI_STORAGE_DRIVER": "mongo",
- "OSMNBI_STORAGE_PATH": "/app/storage",
- "OSMNBI_STORAGE_COLLECTION": "files",
- "OSMNBI_STORAGE_URI": relation_state["database_uri"],
- "OSMNBI_PROMETHEUS_HOST": relation_state["prometheus_host"],
- "OSMNBI_PROMETHEUS_PORT": relation_state["prometheus_port"],
- "OSMNBI_LOG_LEVEL": config["log_level"],
- "OSMNBI_AUTHENTICATION_BACKEND": config["auth_backend"],
- },
- }
- ],
- "kubernetesResources": {
- "ingressResources": [],
- },
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_with_keystone(self) -> NoReturn:
- """Testing make pod spec with keystone."""
- image_info = {"upstream-source": "opensourcemano/nbi:8"}
- config = {
- "enable_test": False,
- "database_commonkey": "commonkey",
- "log_level": "DEBUG",
- "auth_backend": "keystone",
- "site_url": "",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- "prometheus_host": "prometheus",
- "prometheus_port": 9082,
- "keystone_host": "keystone",
- "keystone_port": 5000,
- "keystone_user_domain_name": "user_domain",
- "keystone_project_domain_name": "project_domain",
- "keystone_username": "username",
- "keystone_password": "password",
- "keystone_service": "service",
- }
- app_name = "nbi"
- port = 9999
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": "nbi",
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMNBI_SERVER_ENABLE_TEST": config["enable_test"],
- "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
- "OSMNBI_MESSAGE_HOST": relation_state["message_host"],
- "OSMNBI_MESSAGE_DRIVER": "kafka",
- "OSMNBI_MESSAGE_PORT": relation_state["message_port"],
- "OSMNBI_DATABASE_DRIVER": "mongo",
- "OSMNBI_DATABASE_URI": relation_state["database_uri"],
- "OSMNBI_DATABASE_COMMONKEY": config["database_commonkey"],
- "OSMNBI_STORAGE_DRIVER": "mongo",
- "OSMNBI_STORAGE_PATH": "/app/storage",
- "OSMNBI_STORAGE_COLLECTION": "files",
- "OSMNBI_STORAGE_URI": relation_state["database_uri"],
- "OSMNBI_PROMETHEUS_HOST": relation_state["prometheus_host"],
- "OSMNBI_PROMETHEUS_PORT": relation_state["prometheus_port"],
- "OSMNBI_LOG_LEVEL": config["log_level"],
- "OSMNBI_AUTHENTICATION_BACKEND": config["auth_backend"],
- "OSMNBI_AUTHENTICATION_AUTH_URL": relation_state[
- "keystone_host"
- ],
- "OSMNBI_AUTHENTICATION_AUTH_PORT": relation_state[
- "keystone_port"
- ],
- "OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME": relation_state[
- "keystone_user_domain_name"
- ],
- "OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME": relation_state[
- "keystone_project_domain_name"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_USERNAME": relation_state[
- "keystone_username"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_PASSWORD": relation_state[
- "keystone_password"
- ],
- "OSMNBI_AUTHENTICATION_SERVICE_PROJECT": relation_state[
- "keystone_service"
- ],
- },
- }
- ],
- "kubernetesResources": {
- "ingressResources": [],
- },
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv =
- VIRTUAL_ENV={envdir}
- PYTHONPATH = {toxinidir}:{toxinidir}/lib:{toxinidir}/src
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- # Copyright 2020 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. -->
-
-# NG-UI Charm
-
-## How to deploy
-
-```bash
-juju deploy . # cs:~charmed-osm/ng-ui --channel edge
-juju relate ng-ui nbi
-```
-
-## How to expose the NG-UI through ingress
-
-```bash
-juju config ng-ui site_url=ng.<k8s_worker_ip>.xip.io
-juju expose ng-ui
-```
-
-> Note: The <k8s_worker_ip> is the IP of the K8s worker node. With microk8s, you can see the IP with `microk8s.config`. It is usually the IP of your host machine.
-
-## How to scale
-
-```bash
- juju scale-application ng-ui 3
-```
-
-
-## Config Examples
-
-```bash
-juju config ng-ui image=opensourcemano/ng-ui:<tag>
-juju config ng-ui port=80
-juju config server_name=<name>
-juju config max_file_size=25
-```
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright 2020 Arctos Labs Scandinavia AB
-#
-# 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.
-
-options:
- server_name:
- description: Server name
- type: string
- default: localhost
- port:
- description: Port to expose
- type: int
- default: 80
- max_file_size:
- type: int
- description: |
- The maximum file size, in megabytes. If there is a reverse proxy in front
- of Keystone, it may need to be configured to handle the requested size.
- Note: if set to 0, there is no limit.
- default: 0
- ingress_class:
- type: string
- description: |
- Ingress class name. This is useful for selecting the ingress to be used
- in case there are multiple ingresses in the underlying k8s clusters.
- ingress_whitelist_source_range:
- type: string
- description: |
- A comma-separated list of CIDRs to store in the
- ingress.kubernetes.io/whitelist-source-range annotation.
- default: ""
- tls_secret_name:
- type: string
- description: TLS Secret name
- default: ""
- site_url:
- type: string
- description: Ingress URL
- default: ""
- cluster_issuer:
- type: string
- description: Name of the cluster issuer for TLS certificates
- default: ""
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
+++ /dev/null
-# Copyright 2020 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.
-
-name: osm-ng-ui
-summary: A Next Generation UI charm for Opensource MANO
-description: |
- New UI for OSM
-series:
- - kubernetes
-min-juju-version: 2.7.0
-deployment:
- type: stateless
- service: cluster
-requires:
- nbi:
- interface: http
-resources:
- image:
- type: oci-image
- description: OSM docker image for NBI
- upstream-source: "opensourcemano/ng-ui:latest"
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-pydantic # TODO: remove it
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
\ No newline at end of file
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-from ipaddress import ip_network
-import logging
-from pathlib import Path
-from string import Template
-from typing import NoReturn, Optional
-from urllib.parse import urlparse
-
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.http import HttpClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- FilesV3Builder,
- IngressResourceV3Builder,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-
-class ConfigModel(ModelValidator):
- port: int
- server_name: str
- max_file_size: int
- site_url: Optional[str]
- cluster_issuer: Optional[str]
- ingress_class: Optional[str]
- ingress_whitelist_source_range: Optional[str]
- tls_secret_name: Optional[str]
- image_pull_policy: str
- security_context: bool
-
- @validator("port")
- def validate_port(cls, v):
- if v <= 0:
- raise ValueError("value must be greater than 0")
- return v
-
- @validator("max_file_size")
- def validate_max_file_size(cls, v):
- if v < 0:
- raise ValueError("value must be equal or greater than 0")
- return v
-
- @validator("site_url")
- def validate_site_url(cls, v):
- if v:
- parsed = urlparse(v)
- if not parsed.scheme.startswith("http"):
- raise ValueError("value must start with http")
- return v
-
- @validator("ingress_whitelist_source_range")
- def validate_ingress_whitelist_source_range(cls, v):
- if v:
- ip_network(v)
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
-
-class NgUiCharm(CharmedOsmBase):
- def __init__(self, *args) -> NoReturn:
- super().__init__(*args, oci_image="image")
-
- self.nbi_client = HttpClient(self, "nbi")
- self.framework.observe(self.on["nbi"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["nbi"].relation_broken, self.configure_pod)
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if self.nbi_client.is_missing_data_in_app():
- missing_relations.append("nbi")
-
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def _build_files(self, config: ConfigModel):
- files_builder = FilesV3Builder()
- files_builder.add_file(
- "default",
- Template(Path("templates/default.template").read_text()).substitute(
- port=config.port,
- server_name=config.server_name,
- max_file_size=config.max_file_size,
- nbi_host=self.nbi_client.host,
- nbi_port=self.nbi_client.port,
- ),
- )
- return files_builder.build()
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
- # Check relations
- self._check_missing_dependencies(config)
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=config.security_context
- )
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=config.security_context,
- )
- container_builder.add_port(name=self.app.name, port=config.port)
- container = container_builder.build()
- container_builder.add_tcpsocket_readiness_probe(
- config.port,
- initial_delay_seconds=45,
- timeout_seconds=5,
- )
- container_builder.add_tcpsocket_liveness_probe(
- config.port,
- initial_delay_seconds=45,
- timeout_seconds=15,
- )
- container_builder.add_volume_config(
- "configuration",
- "/etc/nginx/sites-available/",
- self._build_files(config),
- )
- # Add container to pod spec
- pod_spec_builder.add_container(container)
- # Add ingress resources to pod spec if site url exists
- if config.site_url:
- parsed = urlparse(config.site_url)
- annotations = {
- "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
- str(config.max_file_size) + "m"
- if config.max_file_size > 0
- else config.max_file_size
- )
- }
- if config.ingress_class:
- annotations["kubernetes.io/ingress.class"] = config.ingress_class
- ingress_resource_builder = IngressResourceV3Builder(
- f"{self.app.name}-ingress", annotations
- )
-
- if config.ingress_whitelist_source_range:
- annotations[
- "nginx.ingress.kubernetes.io/whitelist-source-range"
- ] = config.ingress_whitelist_source_range
-
- if config.cluster_issuer:
- annotations["cert-manager.io/cluster-issuer"] = config.cluster_issuer
-
- if parsed.scheme == "https":
- ingress_resource_builder.add_tls(
- [parsed.hostname], config.tls_secret_name
- )
- else:
- annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
-
- ingress_resource_builder.add_rule(
- parsed.hostname, self.app.name, config.port
- )
- ingress_resource = ingress_resource_builder.build()
- pod_spec_builder.add_ingress_resource(ingress_resource)
- return pod_spec_builder.build()
-
-
-if __name__ == "__main__":
- main(NgUiCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213,E0611
-
-
-import logging
-from pydantic import (
- BaseModel,
- conint,
- IPvAnyNetwork,
- PositiveInt,
- validator,
-)
-from typing import Any, Dict, List, Optional
-from urllib.parse import urlparse
-from pathlib import Path
-from string import Template
-
-logger = logging.getLogger(__name__)
-
-
-class ConfigData(BaseModel):
- """Configuration data model."""
-
- port: PositiveInt
- site_url: Optional[str]
- max_file_size: Optional[conint(ge=0)]
- ingress_whitelist_source_range: Optional[IPvAnyNetwork]
- tls_secret_name: Optional[str]
-
- @validator("max_file_size", pre=True, always=True)
- def validate_max_file_size(cls, value, values, **kwargs):
- site_url = values.get("site_url")
-
- if not site_url:
- return value
-
- parsed = urlparse(site_url)
-
- if not parsed.scheme.startswith("http"):
- return value
-
- if value is None:
- raise ValueError("max_file_size needs to be defined if site_url is defined")
-
- return value
-
- @validator("ingress_whitelist_source_range", pre=True, always=True)
- def validate_ingress_whitelist_source_range(cls, value, values, **kwargs):
- if not value:
- return None
-
- return value
-
-
-class RelationData(BaseModel):
- """Relation data model."""
-
- nbi_host: str
- nbi_port: PositiveInt
-
-
-def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
- """Generate pod ports details.
-
- Args:
- port (int): Port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod port details.
- """
- return [
- {"name": "http", "containerPort": port, "protocol": "TCP"},
- ]
-
-
-def _make_pod_ingress_resources(
- config: Dict[str, Any], app_name: str, port: int
-) -> List[Dict[str, Any]]:
- """Generate pod ingress resources.
-
- Args:
- config (Dict[str, Any]): configuration information.
- app_name (str): application name.
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod ingress resources.
- """
- site_url = config.get("site_url")
-
- if not site_url:
- return
-
- parsed = urlparse(site_url)
-
- if not parsed.scheme.startswith("http"):
- return
-
- max_file_size = config["max_file_size"]
- ingress_whitelist_source_range = config["ingress_whitelist_source_range"]
-
- annotations = {
- "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
- str(max_file_size) + "m" if max_file_size > 0 else max_file_size
- ),
- }
-
- if ingress_whitelist_source_range:
- annotations[
- "nginx.ingress.kubernetes.io/whitelist-source-range"
- ] = ingress_whitelist_source_range
-
- ingress_spec_tls = None
-
- if parsed.scheme == "https":
- ingress_spec_tls = [{"hosts": [parsed.hostname]}]
- tls_secret_name = config["tls_secret_name"]
- if tls_secret_name:
- ingress_spec_tls[0]["secretName"] = tls_secret_name
- else:
- annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
-
- ingress = {
- "name": "{}-ingress".format(app_name),
- "annotations": annotations,
- "spec": {
- "rules": [
- {
- "host": parsed.hostname,
- "http": {
- "paths": [
- {
- "path": "/",
- "backend": {
- "serviceName": app_name,
- "servicePort": port,
- },
- }
- ]
- },
- }
- ]
- },
- }
- if ingress_spec_tls:
- ingress["spec"]["tls"] = ingress_spec_tls
-
- return [ingress]
-
-
-def _make_startup_probe() -> Dict[str, Any]:
- """Generate startup probe.
-
- Returns:
- Dict[str, Any]: startup probe.
- """
- return {
- "exec": {"command": ["/usr/bin/pgrep python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
-
-def _make_readiness_probe(port: int) -> Dict[str, Any]:
- """Generate readiness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: readiness probe.
- """
- return {
- "tcpSocket": {
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
-
-def _make_liveness_probe(port: int) -> Dict[str, Any]:
- """Generate liveness probe.
-
- Args:
- port (int): [description]
-
- Returns:
- Dict[str, Any]: liveness probe.
- """
- return {
- "tcpSocket": {
- "port": port,
- },
- "initialDelaySeconds": 45,
- "timeoutSeconds": 5,
- }
-
-
-def _make_pod_volume_config(
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
-) -> List[Dict[str, Any]]:
- """Generate volume config with files.
-
- Args:
- config (Dict[str, Any]): configuration information.
-
- Returns:
- Dict[str, Any]: volume config.
- """
- template_data = {**config, **relation_state}
- template_data["max_file_size"] = f'{template_data["max_file_size"]}M'
- return [
- {
- "name": "configuration",
- "mountPath": "/etc/nginx/sites-available/",
- "files": [
- {
- "path": "default",
- "content": Template(Path("files/default").read_text()).substitute(
- template_data
- ),
- }
- ],
- }
- ]
-
-
-def make_pod_spec(
- image_info: Dict[str, str],
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
- app_name: str = "ng-ui",
-) -> Dict[str, Any]:
- """Generate the pod spec information.
-
- Args:
- image_info (Dict[str, str]): Object provided by
- OCIImageResource("image").fetch().
- config (Dict[str, Any]): Configuration information.
- relation_state (Dict[str, Any]): Relation state information.
- app_name (str, optional): Application name. Defaults to "ng-ui".
- port (int, optional): Port for the container. Defaults to 80.
-
- Returns:
- Dict[str, Any]: Pod spec dictionary for the charm.
- """
- if not image_info:
- return None
-
- ConfigData(**(config))
- RelationData(**(relation_state))
-
- ports = _make_pod_ports(config["port"])
- ingress_resources = _make_pod_ingress_resources(config, app_name, config["port"])
- kubernetes = {
- # "startupProbe": _make_startup_probe(),
- "readinessProbe": _make_readiness_probe(config["port"]),
- "livenessProbe": _make_liveness_probe(config["port"]),
- }
- volume_config = _make_pod_volume_config(config, relation_state)
- return {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": ports,
- "kubernetes": kubernetes,
- "volumeConfig": volume_config,
- }
- ],
- "kubernetesResources": {
- "ingressResources": ingress_resources or [],
- },
- }
+++ /dev/null
-# Copyright 2020 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.
-
-
-
-server {
- listen $port;
- server_name $server_name;
- root /usr/share/nginx/html;
- index index.html index.htm;
- client_max_body_size $max_file_size;
-
- location /osm {
- proxy_pass https://$nbi_host:$nbi_port;
- proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
- proxy_set_header Accept-Encoding "";
- }
-
- location / {
- try_files $$uri $$uri/ /index.html;
- }
-}
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import sys
-from typing import NoReturn
-import unittest
-
-from charm import NgUiCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-class TestCharm(unittest.TestCase):
- """Prometheus Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(NgUiCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "server_name": "localhost",
- "port": 80,
- "max_file_size": 0,
- "ingress_whitelist_source_range": "",
- "tls_secret_name": "",
- "site_url": "https://ui.192.168.100.100.nip.io",
- "cluster_issuer": "vault-issuer",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["nbi"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_nbi_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def initialize_nbi_relation(self):
- http_relation_id = self.harness.add_relation("nbi", "nbi")
- self.harness.add_relation_unit(http_relation_id, "nbi")
- self.harness.update_relation_data(
- http_relation_id,
- "nbi",
- {"host": "nbi", "port": 9999},
- )
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv = VIRTUAL_ENV={envdir}
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/ --exclude=*pod_spec*
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- # Copyright 2020 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. -->
-# PLA Charm
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-# Copyright 2020 Arctos Labs Scandinavia AB
-#
-# 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.
-
-options:
- log_level:
- description: Log level
- type: string
- default: INFO
- database_commonkey:
- description: Common Key for Mongo database
- type: string
- default: osm
- mongodb_uri:
- type: string
- description: MongoDB URI (external database)
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
+++ /dev/null
-# Copyright 2022 Canonical Ltd.
-# See LICENSE file for licensing details.
-#
-# 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.
-
-"""Kafka library.
-
-This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
-`kafka` [interface](https://juju.is/docs/sdk/relations).
-
-The *provider* side of this interface is implemented by the
-[kafka-k8s Charmed Operator](https://charmhub.io/kafka-k8s).
-
-Any Charmed Operator that *requires* Kafka for providing its
-service should implement the *requirer* side of this interface.
-
-In a nutshell using this library to implement a Charmed Operator *requiring*
-Kafka would look like
-
-```
-$ charmcraft fetch-lib charms.kafka_k8s.v0.kafka
-```
-
-`metadata.yaml`:
-
-```
-requires:
- kafka:
- interface: kafka
- limit: 1
-```
-
-`src/charm.py`:
-
-```
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.charm import CharmBase
-
-
-class MyCharm(CharmBase):
-
- on = KafkaEvents()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.kafka = KafkaRequires(self)
- self.framework.observe(
- self.on.kafka_available,
- self._on_kafka_available,
- )
- self.framework.observe(
- self.on.kafka_broken,
- self._on_kafka_broken,
- )
-
- def _on_kafka_available(self, event):
- # Get Kafka host and port
- host: str = self.kafka.host
- port: int = self.kafka.port
- # host => "kafka-k8s"
- # port => 9092
-
- def _on_kafka_broken(self, event):
- # Stop service
- # ...
- self.unit.status = BlockedStatus("need kafka relation")
-```
-
-You can file bugs
-[here](https://github.com/charmed-osm/kafka-k8s-operator/issues)!
-"""
-
-from typing import Optional
-
-from ops.charm import CharmBase, CharmEvents
-from ops.framework import EventBase, EventSource, Object
-
-# The unique Charmhub library identifier, never change it
-from ops.model import Relation
-
-LIBID = "eacc8c85082347c9aae740e0220b8376"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 0
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 3
-
-
-KAFKA_HOST_APP_KEY = "host"
-KAFKA_PORT_APP_KEY = "port"
-
-
-class _KafkaAvailableEvent(EventBase):
- """Event emitted when Kafka is available."""
-
-
-class _KafkaBrokenEvent(EventBase):
- """Event emitted when Kafka relation is broken."""
-
-
-class KafkaEvents(CharmEvents):
- """Kafka events.
-
- This class defines the events that Kafka can emit.
-
- Events:
- kafka_available (_KafkaAvailableEvent)
- """
-
- kafka_available = EventSource(_KafkaAvailableEvent)
- kafka_broken = EventSource(_KafkaBrokenEvent)
-
-
-class KafkaRequires(Object):
- """Requires-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self.charm = charm
- self._endpoint_name = endpoint_name
-
- # Observe relation events
- event_observe_mapping = {
- charm.on[self._endpoint_name].relation_changed: self._on_relation_changed,
- charm.on[self._endpoint_name].relation_broken: self._on_relation_broken,
- }
- for event, observer in event_observe_mapping.items():
- self.framework.observe(event, observer)
-
- def _on_relation_changed(self, event) -> None:
- if event.relation.app and all(
- key in event.relation.data[event.relation.app]
- for key in (KAFKA_HOST_APP_KEY, KAFKA_PORT_APP_KEY)
- ):
- self.charm.on.kafka_available.emit()
-
- def _on_relation_broken(self, _) -> None:
- self.charm.on.kafka_broken.emit()
-
- @property
- def host(self) -> str:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- relation.data[relation.app].get(KAFKA_HOST_APP_KEY)
- if relation and relation.app
- else None
- )
-
- @property
- def port(self) -> int:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- int(relation.data[relation.app].get(KAFKA_PORT_APP_KEY))
- if relation and relation.app
- else None
- )
-
-
-class KafkaProvides(Object):
- """Provides-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self._endpoint_name = endpoint_name
-
- def set_host_info(self, host: str, port: int, relation: Optional[Relation] = None) -> None:
- """Set Kafka host and port.
-
- This function writes in the application data of the relation, therefore,
- only the unit leader can call it.
-
- Args:
- host (str): Kafka hostname or IP address.
- port (int): Kafka port.
- relation (Optional[Relation]): Relation to update.
- If not specified, all relations will be updated.
-
- Raises:
- Exception: if a non-leader unit calls this function.
- """
- if not self.model.unit.is_leader():
- raise Exception("only the leader set host information.")
-
- if relation:
- self._update_relation_data(host, port, relation)
- return
-
- for relation in self.model.relations[self._endpoint_name]:
- self._update_relation_data(host, port, relation)
-
- def _update_relation_data(self, host: str, port: int, relation: Relation) -> None:
- """Update data in relation if needed."""
- relation.data[self.model.app][KAFKA_HOST_APP_KEY] = host
- relation.data[self.model.app][KAFKA_PORT_APP_KEY] = str(port)
+++ /dev/null
-# Copyright 2020 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.
-
-name: osm-pla
-summary: A Placement charm for Opensource MANO
-description: |
- Placement module for OSM
-series:
- - kubernetes
-min-juju-version: 2.7.0
-deployment:
- type: stateless
- service: cluster
-resources:
- image:
- type: oci-image
- description: OSM docker image for POL
- upstream-source: "opensourcemano/pla:latest"
-requires:
- kafka:
- interface: kafka
- mongodb:
- interface: mongodb
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
\ No newline at end of file
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-import logging
-from typing import NoReturn, Optional
-
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.mongo import MongoClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- PodRestartPolicy,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-PORT = 9999
-
-
-class ConfigModel(ModelValidator):
- database_commonkey: str
- mongodb_uri: Optional[str]
- log_level: str
- image_pull_policy: str
- security_context: bool
-
- @validator("log_level")
- def validate_log_level(cls, v):
- if v not in {"INFO", "DEBUG"}:
- raise ValueError("value must be INFO or DEBUG")
- return v
-
- @validator("mongodb_uri")
- def validate_mongodb_uri(cls, v):
- if v and not v.startswith("mongodb://"):
- raise ValueError("mongodb_uri is not properly formed")
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
-
-class PlaCharm(CharmedOsmBase):
- on = KafkaEvents()
-
- def __init__(self, *args) -> NoReturn:
- super().__init__(*args, oci_image="image")
-
- self.kafka = KafkaRequires(self)
- self.framework.observe(self.on.kafka_available, self.configure_pod)
- self.framework.observe(self.on.kafka_broken, self.configure_pod)
-
- self.mongodb_client = MongoClient(self, "mongodb")
- self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if not self.kafka.host or not self.kafka.port:
- missing_relations.append("kafka")
- if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit():
- missing_relations.append("mongodb")
-
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
-
- if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit():
- raise Exception("Mongodb data cannot be provided via config and relation")
-
- # Check relations
- self._check_missing_dependencies(config)
-
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=config.security_context
- )
-
- # Add secrets to the pod
- mongodb_secret_name = f"{self.app.name}-mongodb-secret"
- pod_spec_builder.add_secret(
- mongodb_secret_name,
- {
- "uri": config.mongodb_uri or self.mongodb_client.connection_string,
- "commonkey": config.database_commonkey,
- },
- )
-
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=config.security_context,
- )
- container_builder.add_port(name=self.app.name, port=PORT)
- container_builder.add_envs(
- {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMPLA_GLOBAL_LOG_LEVEL": config.log_level,
- # Kafka configuration
- "OSMPLA_MESSAGE_DRIVER": "kafka",
- "OSMPLA_MESSAGE_HOST": self.kafka.host,
- "OSMPLA_MESSAGE_PORT": self.kafka.port,
- # Database configuration
- "OSMPLA_DATABASE_DRIVER": "mongo",
- }
- )
-
- container_builder.add_secret_envs(
- secret_name=mongodb_secret_name,
- envs={
- "OSMPLA_DATABASE_URI": "uri",
- "OSMPLA_DATABASE_COMMONKEY": "commonkey",
- },
- )
-
- container = container_builder.build()
-
- # Add Pod restart policy
- restart_policy = PodRestartPolicy()
- restart_policy.add_secrets(secret_names=(mongodb_secret_name,))
- pod_spec_builder.set_restart_policy(restart_policy)
-
- # Add container to pod spec
- pod_spec_builder.add_container(container)
-
- return pod_spec_builder.build()
-
-
-if __name__ == "__main__":
- main(PlaCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import sys
-from typing import NoReturn
-import unittest
-
-
-from charm import PlaCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-class TestCharm(unittest.TestCase):
- """Pla Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(PlaCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "log_level": "INFO",
- "mongodb_uri": "",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mongodb", "kafka"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations_and_mongodb_config(
- self,
- ) -> NoReturn:
- "Test with relations and mongodb config (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_exception_mongodb_relation_and_config(
- self,
- ) -> NoReturn:
- "Test with relation and config for Mongodb. Test must fail"
- self.initialize_mongo_relation()
- self.initialize_mongo_config()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def initialize_kafka_relation(self):
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- def initialize_mongo_config(self):
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- def initialize_mongo_relation(self):
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv =
- VIRTUAL_ENV={envdir}
- PYTHONPATH = {toxinidir}:{toxinidir}/lib:{toxinidir}/src
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- Copyright 2020 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.
-
-For those usages not covered by the Apache License, Version 2.0 please
-contact: legal@canonical.com
-
-To get in touch with the maintainers, please contact:
-osm-charmers@lists.launchpad.net -->
-
-# POL operator Charm for Kubernetes
-
-## Requirements
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-options:
- log_level:
- description: "Log Level"
- type: string
- default: "INFO"
- mongodb_uri:
- type: string
- description: MongoDB URI (external database)
- mysql_uri:
- type: string
- description: |
- Mysql URI with the following format:
- mysql://<user>:<password>@<mysql_host>:<mysql_port>/<database>
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- debug_mode:
- description: |
- If true, debug mode is activated. It means that the service will not run,
- and instead, the command for the container will be a `sleep infinity`.
- Note: If enabled, security_context will be disabled.
- type: boolean
- default: false
- debug_pubkey:
- description: |
- Public SSH key that will be injected to the application pod.
- type: string
- debug_pol_local_path:
- description: |
- Local full path to the POL project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_common_local_path:
- description: |
- Local full path to the COMMON project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
+++ /dev/null
-# Copyright 2022 Canonical Ltd.
-# See LICENSE file for licensing details.
-#
-# 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.
-
-"""Kafka library.
-
-This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
-`kafka` [interface](https://juju.is/docs/sdk/relations).
-
-The *provider* side of this interface is implemented by the
-[kafka-k8s Charmed Operator](https://charmhub.io/kafka-k8s).
-
-Any Charmed Operator that *requires* Kafka for providing its
-service should implement the *requirer* side of this interface.
-
-In a nutshell using this library to implement a Charmed Operator *requiring*
-Kafka would look like
-
-```
-$ charmcraft fetch-lib charms.kafka_k8s.v0.kafka
-```
-
-`metadata.yaml`:
-
-```
-requires:
- kafka:
- interface: kafka
- limit: 1
-```
-
-`src/charm.py`:
-
-```
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.charm import CharmBase
-
-
-class MyCharm(CharmBase):
-
- on = KafkaEvents()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.kafka = KafkaRequires(self)
- self.framework.observe(
- self.on.kafka_available,
- self._on_kafka_available,
- )
- self.framework.observe(
- self.on.kafka_broken,
- self._on_kafka_broken,
- )
-
- def _on_kafka_available(self, event):
- # Get Kafka host and port
- host: str = self.kafka.host
- port: int = self.kafka.port
- # host => "kafka-k8s"
- # port => 9092
-
- def _on_kafka_broken(self, event):
- # Stop service
- # ...
- self.unit.status = BlockedStatus("need kafka relation")
-```
-
-You can file bugs
-[here](https://github.com/charmed-osm/kafka-k8s-operator/issues)!
-"""
-
-from typing import Optional
-
-from ops.charm import CharmBase, CharmEvents
-from ops.framework import EventBase, EventSource, Object
-
-# The unique Charmhub library identifier, never change it
-from ops.model import Relation
-
-LIBID = "eacc8c85082347c9aae740e0220b8376"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 0
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 3
-
-
-KAFKA_HOST_APP_KEY = "host"
-KAFKA_PORT_APP_KEY = "port"
-
-
-class _KafkaAvailableEvent(EventBase):
- """Event emitted when Kafka is available."""
-
-
-class _KafkaBrokenEvent(EventBase):
- """Event emitted when Kafka relation is broken."""
-
-
-class KafkaEvents(CharmEvents):
- """Kafka events.
-
- This class defines the events that Kafka can emit.
-
- Events:
- kafka_available (_KafkaAvailableEvent)
- """
-
- kafka_available = EventSource(_KafkaAvailableEvent)
- kafka_broken = EventSource(_KafkaBrokenEvent)
-
-
-class KafkaRequires(Object):
- """Requires-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self.charm = charm
- self._endpoint_name = endpoint_name
-
- # Observe relation events
- event_observe_mapping = {
- charm.on[self._endpoint_name].relation_changed: self._on_relation_changed,
- charm.on[self._endpoint_name].relation_broken: self._on_relation_broken,
- }
- for event, observer in event_observe_mapping.items():
- self.framework.observe(event, observer)
-
- def _on_relation_changed(self, event) -> None:
- if event.relation.app and all(
- key in event.relation.data[event.relation.app]
- for key in (KAFKA_HOST_APP_KEY, KAFKA_PORT_APP_KEY)
- ):
- self.charm.on.kafka_available.emit()
-
- def _on_relation_broken(self, _) -> None:
- self.charm.on.kafka_broken.emit()
-
- @property
- def host(self) -> str:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- relation.data[relation.app].get(KAFKA_HOST_APP_KEY)
- if relation and relation.app
- else None
- )
-
- @property
- def port(self) -> int:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- int(relation.data[relation.app].get(KAFKA_PORT_APP_KEY))
- if relation and relation.app
- else None
- )
-
-
-class KafkaProvides(Object):
- """Provides-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self._endpoint_name = endpoint_name
-
- def set_host_info(self, host: str, port: int, relation: Optional[Relation] = None) -> None:
- """Set Kafka host and port.
-
- This function writes in the application data of the relation, therefore,
- only the unit leader can call it.
-
- Args:
- host (str): Kafka hostname or IP address.
- port (int): Kafka port.
- relation (Optional[Relation]): Relation to update.
- If not specified, all relations will be updated.
-
- Raises:
- Exception: if a non-leader unit calls this function.
- """
- if not self.model.unit.is_leader():
- raise Exception("only the leader set host information.")
-
- if relation:
- self._update_relation_data(host, port, relation)
- return
-
- for relation in self.model.relations[self._endpoint_name]:
- self._update_relation_data(host, port, relation)
-
- def _update_relation_data(self, host: str, port: int, relation: Relation) -> None:
- """Update data in relation if needed."""
- relation.data[self.model.app][KAFKA_HOST_APP_KEY] = host
- relation.data[self.model.app][KAFKA_PORT_APP_KEY] = str(port)
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-name: osm-pol
-summary: OSM Policy Module (POL)
-description: |
- A CAAS charm to deploy OSM's Policy Module (POL).
-series:
- - kubernetes
-tags:
- - kubernetes
- - osm
- - pol
-min-juju-version: 2.8.0
-deployment:
- type: stateless
- service: cluster
-resources:
- image:
- type: oci-image
- description: OSM docker image for POL
- upstream-source: "opensourcemano/pol:latest"
-requires:
- kafka:
- interface: kafka
- mongodb:
- interface: mongodb
- mysql:
- interface: mysql
- limit: 1
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
\ No newline at end of file
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-import logging
-import re
-from typing import NoReturn, Optional
-
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.mongo import MongoClient
-from opslib.osm.interfaces.mysql import MysqlClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- PodRestartPolicy,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-PORT = 9999
-DEFAULT_MYSQL_DATABASE = "pol"
-
-
-class ConfigModel(ModelValidator):
- log_level: str
- mongodb_uri: Optional[str]
- mysql_uri: Optional[str]
- image_pull_policy: str
- debug_mode: bool
- security_context: bool
-
- @validator("log_level")
- def validate_log_level(cls, v):
- if v not in {"INFO", "DEBUG"}:
- raise ValueError("value must be INFO or DEBUG")
- return v
-
- @validator("mongoddb_uri")
- def validate_mongodb_uri(cls, v):
- if v and not v.startswith("mongodb://"):
- raise ValueError("mongodb_uri is not properly formed")
- return v
-
- @validator("mysql_uri")
- def validate_mysql_uri(cls, v):
- pattern = re.compile("^mysql:\/\/.*:.*@.*:\d+\/.*$") # noqa: W605
- if v and not pattern.search(v):
- raise ValueError("mysql_uri is not properly formed")
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
-
-class PolCharm(CharmedOsmBase):
- on = KafkaEvents()
-
- def __init__(self, *args) -> NoReturn:
- super().__init__(
- *args,
- oci_image="image",
- vscode_workspace=VSCODE_WORKSPACE,
- )
- if self.config.get("debug_mode"):
- self.enable_debug_mode(
- pubkey=self.config.get("debug_pubkey"),
- hostpaths={
- "POL": {
- "hostpath": self.config.get("debug_pol_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_policy_module",
- },
- "osm_common": {
- "hostpath": self.config.get("debug_common_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_common",
- },
- },
- )
- self.kafka = KafkaRequires(self)
- self.framework.observe(self.on.kafka_available, self.configure_pod)
- self.framework.observe(self.on.kafka_broken, self.configure_pod)
-
- self.mongodb_client = MongoClient(self, "mongodb")
- self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
-
- self.mysql_client = MysqlClient(self, "mysql")
- self.framework.observe(self.on["mysql"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mysql"].relation_broken, self.configure_pod)
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if not self.kafka.host or not self.kafka.port:
- missing_relations.append("kafka")
- if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit():
- missing_relations.append("mongodb")
- if not config.mysql_uri and self.mysql_client.is_missing_data_in_unit():
- missing_relations.append("mysql")
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
-
- if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit():
- raise Exception("Mongodb data cannot be provided via config and relation")
- if config.mysql_uri and not self.mysql_client.is_missing_data_in_unit():
- raise Exception("Mysql data cannot be provided via config and relation")
-
- # Check relations
- self._check_missing_dependencies(config)
-
- security_context_enabled = (
- config.security_context if not config.debug_mode else False
- )
-
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=security_context_enabled
- )
-
- # Add secrets to the pod
- mongodb_secret_name = f"{self.app.name}-mongodb-secret"
- pod_spec_builder.add_secret(
- mongodb_secret_name,
- {"uri": config.mongodb_uri or self.mongodb_client.connection_string},
- )
- mysql_secret_name = f"{self.app.name}-mysql-secret"
- pod_spec_builder.add_secret(
- mysql_secret_name,
- {
- "uri": config.mysql_uri
- or self.mysql_client.get_root_uri(DEFAULT_MYSQL_DATABASE)
- },
- )
-
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=security_context_enabled,
- )
- container_builder.add_port(name=self.app.name, port=PORT)
- container_builder.add_envs(
- {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMPOL_GLOBAL_LOGLEVEL": config.log_level,
- # Kafka configuration
- "OSMPOL_MESSAGE_DRIVER": "kafka",
- "OSMPOL_MESSAGE_HOST": self.kafka.host,
- "OSMPOL_MESSAGE_PORT": self.kafka.port,
- # Database configuration
- "OSMPOL_DATABASE_DRIVER": "mongo",
- }
- )
- container_builder.add_secret_envs(
- mongodb_secret_name, {"OSMPOL_DATABASE_URI": "uri"}
- )
- container_builder.add_secret_envs(
- mysql_secret_name, {"OSMPOL_SQL_DATABASE_URI": "uri"}
- )
- container = container_builder.build()
-
- # Add Pod restart policy
- restart_policy = PodRestartPolicy()
- restart_policy.add_secrets(
- secret_names=(mongodb_secret_name, mysql_secret_name)
- )
- pod_spec_builder.set_restart_policy(restart_policy)
-
- # Add container to pod spec
- pod_spec_builder.add_container(container)
-
- return pod_spec_builder.build()
-
-
-VSCODE_WORKSPACE = {
- "folders": [
- {"path": "/usr/lib/python3/dist-packages/osm_policy_module"},
- {"path": "/usr/lib/python3/dist-packages/osm_common"},
- ],
- "settings": {},
- "launch": {
- "version": "0.2.0",
- "configurations": [
- {
- "name": "POL",
- "type": "python",
- "request": "launch",
- "module": "osm_policy_module.cmd.policy_module_agent",
- "justMyCode": False,
- }
- ],
- },
-}
-
-
-if __name__ == "__main__":
- main(PolCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import logging
-from typing import Any, Dict, List, NoReturn
-
-logger = logging.getLogger(__name__)
-
-
-def _validate_data(
- config_data: Dict[str, Any], relation_data: Dict[str, Any]
-) -> NoReturn:
- """Validate input data.
-
- Args:
- config_data (Dict[str, Any]): configuration data.
- relation_data (Dict[str, Any]): relation data.
- """
- config_validators = {
- "log_level": lambda value, _: (
- isinstance(value, str) and value in ("INFO", "DEBUG")
- ),
- }
- relation_validators = {
- "message_host": lambda value, _: isinstance(value, str) and len(value) > 0,
- "message_port": lambda value, _: isinstance(value, int) and value > 0,
- "database_uri": lambda value, _: (
- isinstance(value, str) and value.startswith("mongodb://")
- ),
- }
- problems = []
-
- for key, validator in config_validators.items():
- valid = validator(config_data.get(key), config_data)
-
- if not valid:
- problems.append(key)
-
- for key, validator in relation_validators.items():
- valid = validator(relation_data.get(key), relation_data)
-
- if not valid:
- problems.append(key)
-
- if len(problems) > 0:
- raise ValueError("Errors found in: {}".format(", ".join(problems)))
-
-
-def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
- """Generate pod ports details.
-
- Args:
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod port details.
- """
- return [{"name": "pol", "containerPort": port, "protocol": "TCP"}]
-
-
-def _make_pod_envconfig(
- config: Dict[str, Any], relation_state: Dict[str, Any]
-) -> Dict[str, Any]:
- """Generate pod environment configuration.
-
- Args:
- config (Dict[str, Any]): configuration information.
- relation_state (Dict[str, Any]): relation state information.
-
- Returns:
- Dict[str, Any]: pod environment configuration.
- """
- envconfig = {
- # General configuration
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMPOL_GLOBAL_LOGLEVEL": config["log_level"],
- # Kafka configuration
- "OSMPOL_MESSAGE_HOST": relation_state["message_host"],
- "OSMPOL_MESSAGE_DRIVER": "kafka",
- "OSMPOL_MESSAGE_PORT": relation_state["message_port"],
- # Database configuration
- "OSMPOL_DATABASE_DRIVER": "mongo",
- "OSMPOL_DATABASE_URI": relation_state["database_uri"],
- }
-
- return envconfig
-
-
-def _make_startup_probe() -> Dict[str, Any]:
- """Generate startup probe.
-
- Returns:
- Dict[str, Any]: startup probe.
- """
- return {
- "exec": {"command": ["/usr/bin/pgrep", "python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
-
-def _make_readiness_probe() -> Dict[str, Any]:
- """Generate readiness probe.
-
- Returns:
- Dict[str, Any]: readiness probe.
- """
- return {
- "exec": {
- "command": ["sh", "-c", "osm-pol-healthcheck || exit 1"],
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
-
-def _make_liveness_probe() -> Dict[str, Any]:
- """Generate liveness probe.
-
- Returns:
- Dict[str, Any]: liveness probe.
- """
- return {
- "exec": {
- "command": ["sh", "-c", "osm-pol-healthcheck || exit 1"],
- },
- "initialDelaySeconds": 45,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
-
-def make_pod_spec(
- image_info: Dict[str, str],
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
- app_name: str = "pol",
- port: int = 80,
-) -> Dict[str, Any]:
- """Generate the pod spec information.
-
- Args:
- image_info (Dict[str, str]): Object provided by
- OCIImageResource("image").fetch().
- config (Dict[str, Any]): Configuration information.
- relation_state (Dict[str, Any]): Relation state information.
- app_name (str, optional): Application name. Defaults to "pol".
- port (int, optional): Port for the container. Defaults to 80.
-
- Returns:
- Dict[str, Any]: Pod spec dictionary for the charm.
- """
- if not image_info:
- return None
-
- _validate_data(config, relation_state)
-
- ports = _make_pod_ports(port)
- env_config = _make_pod_envconfig(config, relation_state)
-
- return {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": ports,
- "envConfig": env_config,
- }
- ],
- "kubernetesResources": {
- "ingressResources": [],
- },
- }
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import sys
-from typing import NoReturn
-import unittest
-
-from charm import PolCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-class TestCharm(unittest.TestCase):
- """Pol Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(PolCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "log_level": "INFO",
- "mongodb_uri": "",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mongodb", "kafka"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations_and_mongodb_config(
- self,
- ) -> NoReturn:
- "Test with relations and mongodb config (internal)"
- self.initialize_mysql_relation()
- self.initialize_kafka_relation()
- self.initialize_mongo_config()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations(
- self,
- ) -> NoReturn:
- "Test with relations (internal)"
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_mysql_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_exception_mongodb_relation_and_config(
- self,
- ) -> NoReturn:
- "Test with relation and config for Mongodb. Must fail"
- self.initialize_mongo_relation()
- self.initialize_mongo_config()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_mysql_config_success(self):
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_mysql_config()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_mysql_config_wrong_value(self):
- self.initialize_kafka_relation()
- self.initialize_mongo_relation()
- self.initialize_mysql_config(uri="wrong_uri")
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertIn(
- "mysql_uri is not properly formed",
- self.harness.charm.unit.status.message,
- )
-
- def test_mysql_config_and_relation(self):
- self.initialize_mysql_relation()
- self.initialize_mysql_config()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- # import pdb; pdb.set_trace()
- self.assertIn(
- "Mysql data cannot be provided via config and relation",
- self.harness.charm.unit.status.message,
- )
-
- def initialize_kafka_relation(self):
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- def initialize_mongo_config(self):
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- def initialize_mongo_relation(self):
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
- def initialize_mysql_config(self, uri=None):
- self.harness.update_config(
- {"mysql_uri": uri or "mysql://user:pass@mysql-host:3306/database"}
- )
-
- def initialize_mysql_relation(self):
- mongodb_relation_id = self.harness.add_relation("mysql", "mysql")
- self.harness.add_relation_unit(mongodb_relation_id, "mysql/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mysql/0",
- {
- "user": "user",
- "password": "pass",
- "host": "host",
- "port": "1234",
- "database": "pol",
- "root_password": "root_password",
- },
- )
-
-
-if __name__ == "__main__":
- unittest.main()
-
-
-# class TestCharm(unittest.TestCase):
-# """POL Charm unit tests."""
-
-# def setUp(self) -> NoReturn:
-# """Test setup"""
-# self.harness = Harness(PolCharm)
-# self.harness.set_leader(is_leader=True)
-# self.harness.begin()
-
-# def test_on_start_without_relations(self) -> NoReturn:
-# """Test installation without any relation."""
-# self.harness.charm.on.start.emit()
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_start_with_relations(self) -> NoReturn:
-# """Test deployment without keystone."""
-# expected_result = {
-# "version": 3,
-# "containers": [
-# {
-# "name": "pol",
-# "imageDetails": self.harness.charm.image.fetch(),
-# "imagePullPolicy": "Always",
-# "ports": [
-# {
-# "name": "pol",
-# "containerPort": 80,
-# "protocol": "TCP",
-# }
-# ],
-# "envConfig": {
-# "ALLOW_ANONYMOUS_LOGIN": "yes",
-# "OSMPOL_GLOBAL_LOGLEVEL": "INFO",
-# "OSMPOL_MESSAGE_HOST": "kafka",
-# "OSMPOL_MESSAGE_DRIVER": "kafka",
-# "OSMPOL_MESSAGE_PORT": 9092,
-# "OSMPOL_DATABASE_DRIVER": "mongo",
-# "OSMPOL_DATABASE_URI": "mongodb://mongo:27017",
-# },
-# }
-# ],
-# "kubernetesResources": {"ingressResources": []},
-# }
-
-# self.harness.charm.on.start.emit()
-
-# # Check if kafka datastore is initialized
-# self.assertIsNone(self.harness.charm.state.message_host)
-# self.assertIsNone(self.harness.charm.state.message_port)
-
-# # Check if mongodb datastore is initialized
-# self.assertIsNone(self.harness.charm.state.database_uri)
-
-# # Initializing the kafka relation
-# kafka_relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# # Initializing the mongo relation
-# mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# mongodb_relation_id,
-# "mongodb/0",
-# {"connection_string": "mongodb://mongo:27017"},
-# )
-
-# # Checking if kafka data is stored
-# self.assertEqual(self.harness.charm.state.message_host, "kafka")
-# self.assertEqual(self.harness.charm.state.message_port, 9092)
-
-# # Checking if mongodb data is stored
-# self.assertEqual(self.harness.charm.state.database_uri, "mongodb://mongo:27017")
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# pod_spec, _ = self.harness.get_pod_spec()
-
-# self.assertDictEqual(expected_result, pod_spec)
-
-# def test_on_kafka_unit_relation_changed(self) -> NoReturn:
-# """Test to see if kafka relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.message_host)
-# self.assertIsNone(self.harness.charm.state.message_port)
-
-# relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# self.assertEqual(self.harness.charm.state.message_host, "kafka")
-# self.assertEqual(self.harness.charm.state.message_port, 9092)
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertNotIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation"))
-
-# def test_on_mongodb_unit_relation_changed(self) -> NoReturn:
-# """Test to see if mongodb relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# self.assertIsNone(self.harness.charm.state.database_uri)
-
-# relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# relation_id, "mongodb/0", {"connection_string": "mongodb://mongo:27017"}
-# )
-
-# self.assertEqual(self.harness.charm.state.database_uri, "mongodb://mongo:27017")
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertNotIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation"))
-
-
-# if __name__ == "__main__":
-# unittest.main()
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-from typing import NoReturn
-import unittest
-
-import pod_spec
-
-
-class TestPodSpec(unittest.TestCase):
- """Pod spec unit tests."""
-
- def test_make_pod_ports(self) -> NoReturn:
- """Testing make pod ports."""
- port = 80
-
- expected_result = [
- {
- "name": "pol",
- "containerPort": port,
- "protocol": "TCP",
- }
- ]
-
- pod_ports = pod_spec._make_pod_ports(port)
-
- self.assertListEqual(expected_result, pod_ports)
-
- def test_make_pod_envconfig(self) -> NoReturn:
- """Teting make pod envconfig."""
- config = {
- "log_level": "INFO",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- }
-
- expected_result = {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMPOL_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMPOL_MESSAGE_HOST": relation_state["message_host"],
- "OSMPOL_MESSAGE_DRIVER": "kafka",
- "OSMPOL_MESSAGE_PORT": relation_state["message_port"],
- "OSMPOL_DATABASE_DRIVER": "mongo",
- "OSMPOL_DATABASE_URI": relation_state["database_uri"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_startup_probe(self) -> NoReturn:
- """Testing make startup probe."""
- expected_result = {
- "exec": {"command": ["/usr/bin/pgrep", "python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
- startup_probe = pod_spec._make_startup_probe()
-
- self.assertDictEqual(expected_result, startup_probe)
-
- def test_make_readiness_probe(self) -> NoReturn:
- """Testing make readiness probe."""
- expected_result = {
- "exec": {
- "command": ["sh", "-c", "osm-pol-healthcheck || exit 1"],
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
- readiness_probe = pod_spec._make_readiness_probe()
-
- self.assertDictEqual(expected_result, readiness_probe)
-
- def test_make_liveness_probe(self) -> NoReturn:
- """Testing make liveness probe."""
- expected_result = {
- "exec": {
- "command": ["sh", "-c", "osm-pol-healthcheck || exit 1"],
- },
- "initialDelaySeconds": 45,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
- liveness_probe = pod_spec._make_liveness_probe()
-
- self.assertDictEqual(expected_result, liveness_probe)
-
- def test_make_pod_spec(self) -> NoReturn:
- """Testing make pod spec."""
- image_info = {"upstream-source": "opensourcemano/pol:8"}
- config = {
- "log_level": "INFO",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- }
- app_name = "pol"
- port = 80
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": app_name,
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "ALLOW_ANONYMOUS_LOGIN": "yes",
- "OSMPOL_GLOBAL_LOGLEVEL": config["log_level"],
- "OSMPOL_MESSAGE_HOST": relation_state["message_host"],
- "OSMPOL_MESSAGE_DRIVER": "kafka",
- "OSMPOL_MESSAGE_PORT": relation_state["message_port"],
- "OSMPOL_DATABASE_DRIVER": "mongo",
- "OSMPOL_DATABASE_URI": relation_state["database_uri"],
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_without_image_info(self) -> NoReturn:
- """Testing make pod spec without image_info."""
- image_info = None
- config = {
- "log_level": "INFO",
- }
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- }
- app_name = "pol"
- port = 80
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertIsNone(spec)
-
- def test_make_pod_spec_without_config(self) -> NoReturn:
- """Testing make pod spec without config."""
- image_info = {"upstream-source": "opensourcemano/pol:8"}
- config = {}
- relation_state = {
- "message_host": "kafka",
- "message_port": 9090,
- "database_uri": "mongodb://mongo",
- }
- app_name = "pol"
- port = 80
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
- def test_make_pod_spec_without_relation_state(self) -> NoReturn:
- """Testing make pod spec without relation_state."""
- image_info = {"upstream-source": "opensourcemano/pol:8"}
- config = {
- "log_level": "INFO",
- }
- relation_state = {}
- app_name = "pol"
- port = 80
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv =
- VIRTUAL_ENV={envdir}
- PYTHONPATH = {toxinidir}:{toxinidir}/lib:{toxinidir}/src
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-#!/bin/bash
-# Copyright 2020 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.
-set -eux
-
-channel=edge
-tag=testing-daily
-
-# 1. Build charms
-./build.sh
-
-
-# New charms (with resources)
-charms="ng-ui nbi pla keystone ro lcm mon pol"
-for charm in $charms; do
- echo "Releasing $charm charm"
- cs_revision=$(charm push $charm/$charm.charm cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}')
- resource_revision=$(charm attach $cs_revision image=external::opensourcemano/$charm:$tag | tail -n +1 | sed 's/[^0-9]*//g')
- image_revision_num=$(echo $resource_revision | awk '{print $NF}')
- resources_string="--resource image-$image_revision_num"
- charm release --channel $channel $cs_revision $resources_string
- echo "$charm charm released!"
-done
-
-charms="mongodb-exporter kafka-exporter mysqld-exporter"
-for charm in $charms; do
- echo "Releasing $charm charm"
- cs_revision=$(charm push $charm/$charm.charm cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}')
- resource_revision=$(charm attach $cs_revision image=external::bitnami/$charm:latest | tail -n +1 | sed 's/[^0-9]*//g')
- image_revision_num=$(echo $resource_revision | awk '{print $NF}')
- resources_string="--resource image-$image_revision_num"
- charm release --channel $channel $cs_revision $resources_string
- echo "$charm charm released!"
-done
-
-charm="prometheus"
-echo "Releasing $charm charm"
-cs_revision=$(charm push $charm/$charm.charm cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}')
-resource_revision=$(charm attach $cs_revision image=external::ubuntu/$charm:latest | tail -n +1 | sed 's/[^0-9]*//g')
-image_revision_num=$(echo $resource_revision | awk '{print $NF}')
-backup_resource_revision=$(charm attach $cs_revision backup-image=external::ed1000/prometheus-backup:latest | tail -n +1 | sed 's/[^0-9]*//g')
-backup_image_revision_num=$(echo $backup_resource_revision | awk '{print $NF}')
-resources_string="--resource image-$image_revision_num --resource backup-image-$backup_image_revision_num"
-charm release --channel $channel $cs_revision $resources_string
-echo "$charm charm released!"
-
-
-charm="grafana"
-echo "Releasing $charm charm"
-cs_revision=$(charm push $charm/$charm.charm cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}')
-resource_revision=$(charm attach $cs_revision image=external::ubuntu/$charm:latest | tail -n +1 | sed 's/[^0-9]*//g')
-image_revision_num=$(echo $resource_revision | awk '{print $NF}')
-resources_string="--resource image-$image_revision_num"
-charm release --channel $channel $cs_revision $resources_string
-echo "$charm charm released!"
-
-
-charm="zookeeper"
-echo "Releasing $charm charm"
-cs_revision=$(charm push $charm/$charm.charm cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}')
-resource_revision=$(charm attach $cs_revision image=external::rocks.canonical.com:443/k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10 | tail -n +1 | sed 's/[^0-9]*//g')
-image_revision_num=$(echo $resource_revision | awk '{print $NF}')
-resources_string="--resource image-$image_revision_num"
-charm release --channel $channel $cs_revision $resources_string
-echo "$charm charm released!"
-
-
-charm="kafka"
-echo "Releasing $charm charm"
-cs_revision=$(charm push $charm/$charm.charm cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}')
-resource_revision=$(charm attach $cs_revision image=external::rocks.canonical.com:443/wurstmeister/kafka:2.12-2.2.1 | tail -n +1 | sed 's/[^0-9]*//g')
-image_revision_num=$(echo $resource_revision | awk '{print $NF}')
-resources_string="--resource image-$image_revision_num"
-charm release --channel $channel $cs_revision $resources_string
-echo "$charm charm released!"
-
-
-# 3. Grant permissions
-all_charms="ng-ui nbi pla keystone ro lcm mon pol grafana prometheus mongodb-exporter kafka-exporter mysqld-exporter zookeeper kafka"
-for charm in $all_charms; do
- echo "Granting permission for $charm charm"
- charm grant cs:~charmed-osm/$charm --channel $channel --acl read everyone
-done
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.stestr
-cover
-release
\ No newline at end of file
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-venv
-.vscode
-build
-*.charm
-.coverage
-coverage.xml
-.gitignore
-.stestr
-cover
-release
-tests/
-requirements*
-tox.ini
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
----
-extends: default
-
-yaml-files:
- - "*.yaml"
- - "*.yml"
- - ".yamllint"
-ignore: |
- .tox
- cover/
- build/
- venv
- release/
+++ /dev/null
-<!-- Copyright 2020 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.
-
-For those usages not covered by the Apache License, Version 2.0 please
-contact: legal@canonical.com
-
-To get in touch with the maintainers, please contact:
-osm-charmers@lists.launchpad.net -->
-
-# RO operator Charm for Kubernetes
-
-## Requirements
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-type: charm
-bases:
- - build-on:
- - name: ubuntu
- channel: "20.04"
- architectures: ["amd64"]
- run-on:
- - name: ubuntu
- channel: "20.04"
- architectures:
- - amd64
- - aarch64
- - arm64
-parts:
- charm:
- build-packages: [git]
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-options:
- enable_ng_ro:
- description: Enable NG-RO
- type: boolean
- default: true
- database_commonkey:
- description: Database COMMON KEY
- type: string
- default: osm
- mongodb_uri:
- type: string
- description: MongoDB URI (external database)
- log_level:
- description: "Log Level"
- type: string
- default: "INFO"
- period_refresh_active:
- type: int
- description: |
- Updates the VNF status from VIM for every given period of time seconds.
- Values equal or greater than 60 is allowed.
- Disable the updates from VIM by setting -1.
- Example:
- $ juju config ro period_refresh_active=-1
- $ juju config ro period_refresh_active=100
- mysql_host:
- type: string
- description: MySQL Host (external database)
- mysql_port:
- type: int
- description: MySQL Port (external database)
- mysql_user:
- type: string
- description: MySQL User (external database)
- mysql_password:
- type: string
- description: MySQL Password (external database)
- mysql_root_password:
- type: string
- description: MySQL Root Password (external database)
- vim_database:
- type: string
- description: "The database name."
- default: "mano_vim_db"
- ro_database:
- type: string
- description: "The database name."
- default: "mano_db"
- openmano_tenant:
- type: string
- description: "Openmano Tenant"
- default: "osm"
- certificates:
- type: string
- description: |
- comma-separated list of <name>:<content> certificates.
- Where:
- name: name of the file for the certificate
- content: base64 content of the certificate
- The path for the files is /certs.
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- debug_mode:
- description: |
- If true, debug mode is activated. It means that the service will not run,
- and instead, the command for the container will be a `sleep infinity`.
- Note: If enabled, security_context will be disabled.
- type: boolean
- default: false
- debug_pubkey:
- description: |
- Public SSH key that will be injected to the application pod.
- type: string
- debug_ro_local_path:
- description: |
- Local full path to the RO project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- debug_common_local_path:
- description: |
- Local full path to the COMMON project.
-
- The path will be mounted to the docker image,
- which means changes during the debugging will be saved in your local path.
- type: string
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
+++ /dev/null
-# Copyright 2022 Canonical Ltd.
-# See LICENSE file for licensing details.
-#
-# 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.
-
-"""Kafka library.
-
-This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
-`kafka` [interface](https://juju.is/docs/sdk/relations).
-
-The *provider* side of this interface is implemented by the
-[kafka-k8s Charmed Operator](https://charmhub.io/kafka-k8s).
-
-Any Charmed Operator that *requires* Kafka for providing its
-service should implement the *requirer* side of this interface.
-
-In a nutshell using this library to implement a Charmed Operator *requiring*
-Kafka would look like
-
-```
-$ charmcraft fetch-lib charms.kafka_k8s.v0.kafka
-```
-
-`metadata.yaml`:
-
-```
-requires:
- kafka:
- interface: kafka
- limit: 1
-```
-
-`src/charm.py`:
-
-```
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.charm import CharmBase
-
-
-class MyCharm(CharmBase):
-
- on = KafkaEvents()
-
- def __init__(self, *args):
- super().__init__(*args)
- self.kafka = KafkaRequires(self)
- self.framework.observe(
- self.on.kafka_available,
- self._on_kafka_available,
- )
- self.framework.observe(
- self.on.kafka_broken,
- self._on_kafka_broken,
- )
-
- def _on_kafka_available(self, event):
- # Get Kafka host and port
- host: str = self.kafka.host
- port: int = self.kafka.port
- # host => "kafka-k8s"
- # port => 9092
-
- def _on_kafka_broken(self, event):
- # Stop service
- # ...
- self.unit.status = BlockedStatus("need kafka relation")
-```
-
-You can file bugs
-[here](https://github.com/charmed-osm/kafka-k8s-operator/issues)!
-"""
-
-from typing import Optional
-
-from ops.charm import CharmBase, CharmEvents
-from ops.framework import EventBase, EventSource, Object
-
-# The unique Charmhub library identifier, never change it
-from ops.model import Relation
-
-LIBID = "eacc8c85082347c9aae740e0220b8376"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 0
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 3
-
-
-KAFKA_HOST_APP_KEY = "host"
-KAFKA_PORT_APP_KEY = "port"
-
-
-class _KafkaAvailableEvent(EventBase):
- """Event emitted when Kafka is available."""
-
-
-class _KafkaBrokenEvent(EventBase):
- """Event emitted when Kafka relation is broken."""
-
-
-class KafkaEvents(CharmEvents):
- """Kafka events.
-
- This class defines the events that Kafka can emit.
-
- Events:
- kafka_available (_KafkaAvailableEvent)
- """
-
- kafka_available = EventSource(_KafkaAvailableEvent)
- kafka_broken = EventSource(_KafkaBrokenEvent)
-
-
-class KafkaRequires(Object):
- """Requires-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self.charm = charm
- self._endpoint_name = endpoint_name
-
- # Observe relation events
- event_observe_mapping = {
- charm.on[self._endpoint_name].relation_changed: self._on_relation_changed,
- charm.on[self._endpoint_name].relation_broken: self._on_relation_broken,
- }
- for event, observer in event_observe_mapping.items():
- self.framework.observe(event, observer)
-
- def _on_relation_changed(self, event) -> None:
- if event.relation.app and all(
- key in event.relation.data[event.relation.app]
- for key in (KAFKA_HOST_APP_KEY, KAFKA_PORT_APP_KEY)
- ):
- self.charm.on.kafka_available.emit()
-
- def _on_relation_broken(self, _) -> None:
- self.charm.on.kafka_broken.emit()
-
- @property
- def host(self) -> str:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- relation.data[relation.app].get(KAFKA_HOST_APP_KEY)
- if relation and relation.app
- else None
- )
-
- @property
- def port(self) -> int:
- relation: Relation = self.model.get_relation(self._endpoint_name)
- return (
- int(relation.data[relation.app].get(KAFKA_PORT_APP_KEY))
- if relation and relation.app
- else None
- )
-
-
-class KafkaProvides(Object):
- """Provides-side of the Kafka relation."""
-
- def __init__(self, charm: CharmBase, endpoint_name: str = "kafka") -> None:
- super().__init__(charm, endpoint_name)
- self._endpoint_name = endpoint_name
-
- def set_host_info(self, host: str, port: int, relation: Optional[Relation] = None) -> None:
- """Set Kafka host and port.
-
- This function writes in the application data of the relation, therefore,
- only the unit leader can call it.
-
- Args:
- host (str): Kafka hostname or IP address.
- port (int): Kafka port.
- relation (Optional[Relation]): Relation to update.
- If not specified, all relations will be updated.
-
- Raises:
- Exception: if a non-leader unit calls this function.
- """
- if not self.model.unit.is_leader():
- raise Exception("only the leader set host information.")
-
- if relation:
- self._update_relation_data(host, port, relation)
- return
-
- for relation in self.model.relations[self._endpoint_name]:
- self._update_relation_data(host, port, relation)
-
- def _update_relation_data(self, host: str, port: int, relation: Relation) -> None:
- """Update data in relation if needed."""
- relation.data[self.model.app][KAFKA_HOST_APP_KEY] = host
- relation.data[self.model.app][KAFKA_PORT_APP_KEY] = str(port)
+++ /dev/null
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-name: osm-ro
-summary: OSM Resource Orchestrator (RO)
-description: |
- A CAAS charm to deploy OSM's Resource Orchestrator (RO).
-series:
- - kubernetes
-tags:
- - kubernetes
- - osm
- - ro
-min-juju-version: 2.8.0
-deployment:
- type: stateless
- service: cluster
-resources:
- image:
- type: oci-image
- description: OSM docker image for RO
- upstream-source: "opensourcemano/ro:8"
-provides:
- ro:
- interface: http
-requires:
- kafka:
- interface: kafka
- limit: 1
- mongodb:
- interface: mongodb
- limit: 1
- mysql:
- interface: mysql
- limit: 1
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-mock==4.0.3
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-git+https://github.com/charmed-osm/ops-lib-charmed-osm/@master
\ No newline at end of file
+++ /dev/null
-#!/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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-import base64
-import logging
-from typing import Dict, NoReturn, Optional
-
-from charms.kafka_k8s.v0.kafka import KafkaEvents, KafkaRequires
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.mongo import MongoClient
-from opslib.osm.interfaces.mysql import MysqlClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- FilesV3Builder,
- PodRestartPolicy,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-logger = logging.getLogger(__name__)
-
-PORT = 9090
-
-
-def _check_certificate_data(name: str, content: str):
- if not name or not content:
- raise ValueError("certificate name and content must be a non-empty string")
-
-
-def _extract_certificates(certs_config: str):
- certificates = {}
- if certs_config:
- cert_list = certs_config.split(",")
- for cert in cert_list:
- name, content = cert.split(":")
- _check_certificate_data(name, content)
- certificates[name] = content
- return certificates
-
-
-def decode(content: str):
- return base64.b64decode(content.encode("utf-8")).decode("utf-8")
-
-
-class ConfigModel(ModelValidator):
- enable_ng_ro: bool
- database_commonkey: str
- mongodb_uri: Optional[str]
- log_level: str
- mysql_host: Optional[str]
- mysql_port: Optional[int]
- mysql_user: Optional[str]
- mysql_password: Optional[str]
- mysql_root_password: Optional[str]
- vim_database: str
- ro_database: str
- openmano_tenant: str
- certificates: Optional[str]
- image_pull_policy: str
- debug_mode: bool
- security_context: bool
- period_refresh_active: Optional[int]
-
- @validator("log_level")
- def validate_log_level(cls, v):
- if v not in {"INFO", "DEBUG"}:
- raise ValueError("value must be INFO or DEBUG")
- return v
-
- @validator("certificates")
- def validate_certificates(cls, v):
- # Raises an exception if it cannot extract the certificates
- _extract_certificates(v)
- return v
-
- @validator("mongodb_uri")
- def validate_mongodb_uri(cls, v):
- if v and not v.startswith("mongodb://"):
- raise ValueError("mongodb_uri is not properly formed")
- return v
-
- @validator("mysql_port")
- def validate_mysql_port(cls, v):
- if v and (v <= 0 or v >= 65535):
- raise ValueError("Mysql port out of range")
- return v
-
- @validator("image_pull_policy")
- def validate_image_pull_policy(cls, v):
- values = {
- "always": "Always",
- "ifnotpresent": "IfNotPresent",
- "never": "Never",
- }
- v = v.lower()
- if v not in values.keys():
- raise ValueError("value must be always, ifnotpresent or never")
- return values[v]
-
- @property
- def certificates_dict(cls):
- return _extract_certificates(cls.certificates) if cls.certificates else {}
-
- @validator("period_refresh_active")
- def validate_vim_refresh_period(cls, v):
- if v and v < 60 and v != -1:
- raise ValueError(
- "Refresh Period is too tight, insert >= 60 seconds or disable using -1"
- )
- return v
-
-
-class RoCharm(CharmedOsmBase):
- """GrafanaCharm Charm."""
-
- on = KafkaEvents()
-
- def __init__(self, *args) -> NoReturn:
- """Prometheus Charm constructor."""
- super().__init__(
- *args,
- oci_image="image",
- vscode_workspace=VSCODE_WORKSPACE,
- )
- if self.config.get("debug_mode"):
- self.enable_debug_mode(
- pubkey=self.config.get("debug_pubkey"),
- hostpaths={
- "osm_common": {
- "hostpath": self.config.get("debug_common_local_path"),
- "container-path": "/usr/lib/python3/dist-packages/osm_common",
- },
- **_get_ro_host_paths(self.config.get("debug_ro_local_path")),
- },
- )
- self.kafka = KafkaRequires(self)
- self.framework.observe(self.on.kafka_available, self.configure_pod)
- self.framework.observe(self.on.kafka_broken, self.configure_pod)
-
- self.mysql_client = MysqlClient(self, "mysql")
- self.framework.observe(self.on["mysql"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mysql"].relation_broken, self.configure_pod)
-
- self.mongodb_client = MongoClient(self, "mongodb")
- self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
- self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
-
- self.framework.observe(self.on["ro"].relation_joined, self._publish_ro_info)
-
- def _publish_ro_info(self, event):
- """Publishes RO information.
-
- Args:
- event (EventBase): RO relation event.
- """
- if self.unit.is_leader():
- rel_data = {
- "host": self.model.app.name,
- "port": str(PORT),
- }
- for k, v in rel_data.items():
- event.relation.data[self.app][k] = v
-
- def _check_missing_dependencies(self, config: ConfigModel):
- missing_relations = []
-
- if config.enable_ng_ro:
- if not self.kafka.host or not self.kafka.port:
- missing_relations.append("kafka")
- if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit():
- missing_relations.append("mongodb")
- else:
- if not config.mysql_host and self.mysql_client.is_missing_data_in_unit():
- missing_relations.append("mysql")
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def _validate_mysql_config(self, config: ConfigModel):
- invalid_values = []
- if not config.mysql_user:
- invalid_values.append("Mysql user is empty")
- if not config.mysql_password:
- invalid_values.append("Mysql password is empty")
- if not config.mysql_root_password:
- invalid_values.append("Mysql root password empty")
-
- if invalid_values:
- raise ValueError("Invalid values: " + ", ".join(invalid_values))
-
- def _build_cert_files(
- self,
- config: ConfigModel,
- ):
- cert_files_builder = FilesV3Builder()
- for name, content in config.certificates_dict.items():
- cert_files_builder.add_file(name, decode(content), mode=0o600)
- return cert_files_builder.build()
-
- def build_pod_spec(self, image_info):
- # Validate config
- config = ConfigModel(**dict(self.config))
-
- if config.enable_ng_ro:
- if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit():
- raise Exception(
- "Mongodb data cannot be provided via config and relation"
- )
- else:
- if config.mysql_host and not self.mysql_client.is_missing_data_in_unit():
- raise Exception("Mysql data cannot be provided via config and relation")
-
- if config.mysql_host:
- self._validate_mysql_config(config)
-
- # Check relations
- self._check_missing_dependencies(config)
-
- security_context_enabled = (
- config.security_context if not config.debug_mode else False
- )
-
- # Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder(
- enable_security_context=security_context_enabled
- )
-
- # Build Container
- container_builder = ContainerV3Builder(
- self.app.name,
- image_info,
- config.image_pull_policy,
- run_as_non_root=security_context_enabled,
- )
- certs_files = self._build_cert_files(config)
-
- if certs_files:
- container_builder.add_volume_config("certs", "/certs", certs_files)
-
- container_builder.add_port(name=self.app.name, port=PORT)
- container_builder.add_http_readiness_probe(
- "/ro/" if config.enable_ng_ro else "/openmano/tenants",
- PORT,
- initial_delay_seconds=10,
- period_seconds=10,
- timeout_seconds=5,
- failure_threshold=3,
- )
- container_builder.add_http_liveness_probe(
- "/ro/" if config.enable_ng_ro else "/openmano/tenants",
- PORT,
- initial_delay_seconds=600,
- period_seconds=10,
- timeout_seconds=5,
- failure_threshold=3,
- )
- container_builder.add_envs(
- {
- "OSMRO_LOG_LEVEL": config.log_level,
- }
- )
- if config.period_refresh_active:
- container_builder.add_envs(
- {
- "OSMRO_PERIOD_REFRESH_ACTIVE": config.period_refresh_active,
- }
- )
- if config.enable_ng_ro:
- # Add secrets to the pod
- mongodb_secret_name = f"{self.app.name}-mongodb-secret"
- pod_spec_builder.add_secret(
- mongodb_secret_name,
- {
- "uri": config.mongodb_uri or self.mongodb_client.connection_string,
- "commonkey": config.database_commonkey,
- },
- )
- container_builder.add_envs(
- {
- "OSMRO_MESSAGE_DRIVER": "kafka",
- "OSMRO_MESSAGE_HOST": self.kafka.host,
- "OSMRO_MESSAGE_PORT": self.kafka.port,
- # MongoDB configuration
- "OSMRO_DATABASE_DRIVER": "mongo",
- }
- )
- container_builder.add_secret_envs(
- secret_name=mongodb_secret_name,
- envs={
- "OSMRO_DATABASE_URI": "uri",
- "OSMRO_DATABASE_COMMONKEY": "commonkey",
- },
- )
- restart_policy = PodRestartPolicy()
- restart_policy.add_secrets(secret_names=(mongodb_secret_name,))
- pod_spec_builder.set_restart_policy(restart_policy)
-
- else:
- container_builder.add_envs(
- {
- "RO_DB_HOST": config.mysql_host or self.mysql_client.host,
- "RO_DB_OVIM_HOST": config.mysql_host or self.mysql_client.host,
- "RO_DB_PORT": config.mysql_port or self.mysql_client.port,
- "RO_DB_OVIM_PORT": config.mysql_port or self.mysql_client.port,
- "RO_DB_USER": config.mysql_user or self.mysql_client.user,
- "RO_DB_OVIM_USER": config.mysql_user or self.mysql_client.user,
- "RO_DB_PASSWORD": config.mysql_password
- or self.mysql_client.password,
- "RO_DB_OVIM_PASSWORD": config.mysql_password
- or self.mysql_client.password,
- "RO_DB_ROOT_PASSWORD": config.mysql_root_password
- or self.mysql_client.root_password,
- "RO_DB_OVIM_ROOT_PASSWORD": config.mysql_root_password
- or self.mysql_client.root_password,
- "RO_DB_NAME": config.ro_database,
- "RO_DB_OVIM_NAME": config.vim_database,
- "OPENMANO_TENANT": config.openmano_tenant,
- }
- )
- container = container_builder.build()
-
- # Add container to pod spec
- pod_spec_builder.add_container(container)
-
- return pod_spec_builder.build()
-
-
-VSCODE_WORKSPACE = {
- "folders": [
- {"path": "/usr/lib/python3/dist-packages/osm_ng_ro"},
- {"path": "/usr/lib/python3/dist-packages/osm_common"},
- {"path": "/usr/lib/python3/dist-packages/osm_ro_plugin"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_arista_cloudvision"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_dpb"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_dynpac"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_floodlightof"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_ietfl2vpn"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_juniper_contrail"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_odlof"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_onos_vpls"},
- {"path": "/usr/lib/python3/dist-packages/osm_rosdn_onosof"},
- {"path": "/usr/lib/python3/dist-packages/osm_rovim_aws"},
- {"path": "/usr/lib/python3/dist-packages/osm_rovim_azure"},
- {"path": "/usr/lib/python3/dist-packages/osm_rovim_gcp"},
- {"path": "/usr/lib/python3/dist-packages/osm_rovim_openstack"},
- {"path": "/usr/lib/python3/dist-packages/osm_rovim_openvim"},
- {"path": "/usr/lib/python3/dist-packages/osm_rovim_vmware"},
- ],
- "launch": {
- "configurations": [
- {
- "module": "osm_ng_ro.ro_main",
- "name": "NG RO",
- "request": "launch",
- "type": "python",
- "justMyCode": False,
- }
- ],
- "version": "0.2.0",
- },
- "settings": {},
-}
-
-
-def _get_ro_host_paths(ro_host_path: str) -> Dict:
- """Get RO host paths"""
- return (
- {
- "NG-RO": {
- "hostpath": f"{ro_host_path}/NG-RO",
- "container-path": "/usr/lib/python3/dist-packages/osm_ng_ro",
- },
- "RO-plugin": {
- "hostpath": f"{ro_host_path}/RO-plugin",
- "container-path": "/usr/lib/python3/dist-packages/osm_ro_plugin",
- },
- "RO-SDN-arista_cloudvision": {
- "hostpath": f"{ro_host_path}/RO-SDN-arista_cloudvision",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_arista_cloudvision",
- },
- "RO-SDN-dpb": {
- "hostpath": f"{ro_host_path}/RO-SDN-dpb",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_dpb",
- },
- "RO-SDN-dynpac": {
- "hostpath": f"{ro_host_path}/RO-SDN-dynpac",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_dynpac",
- },
- "RO-SDN-floodlight_openflow": {
- "hostpath": f"{ro_host_path}/RO-SDN-floodlight_openflow",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_floodlightof",
- },
- "RO-SDN-ietfl2vpn": {
- "hostpath": f"{ro_host_path}/RO-SDN-ietfl2vpn",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_ietfl2vpn",
- },
- "RO-SDN-juniper_contrail": {
- "hostpath": f"{ro_host_path}/RO-SDN-juniper_contrail",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_juniper_contrail",
- },
- "RO-SDN-odl_openflow": {
- "hostpath": f"{ro_host_path}/RO-SDN-odl_openflow",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_odlof",
- },
- "RO-SDN-onos_openflow": {
- "hostpath": f"{ro_host_path}/RO-SDN-onos_openflow",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_onosof",
- },
- "RO-SDN-onos_vpls": {
- "hostpath": f"{ro_host_path}/RO-SDN-onos_vpls",
- "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_onos_vpls",
- },
- "RO-VIM-aws": {
- "hostpath": f"{ro_host_path}/RO-VIM-aws",
- "container-path": "/usr/lib/python3/dist-packages/osm_rovim_aws",
- },
- "RO-VIM-azure": {
- "hostpath": f"{ro_host_path}/RO-VIM-azure",
- "container-path": "/usr/lib/python3/dist-packages/osm_rovim_azure",
- },
- "RO-VIM-gcp": {
- "hostpath": f"{ro_host_path}/RO-VIM-gcp",
- "container-path": "/usr/lib/python3/dist-packages/osm_rovim_gcp",
- },
- "RO-VIM-openstack": {
- "hostpath": f"{ro_host_path}/RO-VIM-openstack",
- "container-path": "/usr/lib/python3/dist-packages/osm_rovim_openstack",
- },
- "RO-VIM-openvim": {
- "hostpath": f"{ro_host_path}/RO-VIM-openvim",
- "container-path": "/usr/lib/python3/dist-packages/osm_rovim_openvim",
- },
- "RO-VIM-vmware": {
- "hostpath": f"{ro_host_path}/RO-VIM-vmware",
- "container-path": "/usr/lib/python3/dist-packages/osm_rovim_vmware",
- },
- }
- if ro_host_path
- else {}
- )
-
-
-if __name__ == "__main__":
- main(RoCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import logging
-from typing import Any, Dict, List, NoReturn
-
-logger = logging.getLogger(__name__)
-
-
-def _validate_data(
- config_data: Dict[str, Any], relation_data: Dict[str, Any]
-) -> NoReturn:
- """Validates passed information.
-
- Args:
- config_data (Dict[str, Any]): configuration information.
- relation_data (Dict[str, Any]): relation information
-
- Raises:
- ValueError: when config and/or relation data is not valid.
- """
- config_validators = {
- "enable_ng_ro": lambda value, _: isinstance(value, bool),
- "database_commonkey": lambda value, values: (
- isinstance(value, str) and len(value) > 0
- )
- if values.get("enable_ng_ro", True)
- else True,
- "log_level": lambda value, _: (
- isinstance(value, str) and value in ("INFO", "DEBUG")
- ),
- "vim_database": lambda value, values: (
- isinstance(value, str) and len(value) > 0
- )
- if not values.get("enable_ng_ro", True)
- else True,
- "ro_database": lambda value, values: (isinstance(value, str) and len(value) > 0)
- if not values.get("enable_ng_ro", True)
- else True,
- "openmano_tenant": lambda value, values: (
- isinstance(value, str) and len(value) > 0
- )
- if not values.get("enable_ng_ro", True)
- else True,
- }
- relation_validators = {
- "kafka_host": lambda value, _: (isinstance(value, str) and len(value) > 0)
- if config_data.get("enable_ng_ro", True)
- else True,
- "kafka_port": lambda value, _: (isinstance(value, str) and len(value) > 0)
- if config_data.get("enable_ng_ro", True)
- else True,
- "mongodb_connection_string": lambda value, _: (
- isinstance(value, str) and value.startswith("mongodb://")
- )
- if config_data.get("enable_ng_ro", True)
- else True,
- "mysql_host": lambda value, _: (isinstance(value, str) and len(value) > 0)
- if not config_data.get("enable_ng_ro", True)
- else True,
- "mysql_port": lambda value, _: (isinstance(value, int) and value > 0)
- if not config_data.get("enable_ng_ro", True)
- else True,
- "mysql_user": lambda value, _: (isinstance(value, str) and len(value) > 0)
- if not config_data.get("enable_ng_ro", True)
- else True,
- "mysql_password": lambda value, _: (isinstance(value, str) and len(value) > 0)
- if not config_data.get("enable_ng_ro", True)
- else True,
- "mysql_root_password": lambda value, _: (
- isinstance(value, str) and len(value) > 0
- )
- if not config_data.get("enable_ng_ro", True)
- else True,
- }
- problems = []
-
- for key, validator in config_validators.items():
- valid = validator(config_data.get(key), config_data)
-
- if not valid:
- problems.append(key)
-
- for key, validator in relation_validators.items():
- valid = validator(relation_data.get(key), relation_data)
-
- if not valid:
- problems.append(key)
-
- if len(problems) > 0:
- raise ValueError("Errors found in: {}".format(", ".join(problems)))
-
-
-def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
- """Generate pod ports details.
-
- Args:
- port (int): port to expose.
-
- Returns:
- List[Dict[str, Any]]: pod port details.
- """
- return [{"name": "ro", "containerPort": port, "protocol": "TCP"}]
-
-
-def _make_pod_envconfig(
- config: Dict[str, Any], relation_state: Dict[str, Any]
-) -> Dict[str, Any]:
- """Generate pod environment configuration.
-
- Args:
- config (Dict[str, Any]): configuration information.
- relation_state (Dict[str, Any]): relation state information.
-
- Returns:
- Dict[str, Any]: pod environment configuration.
- """
- envconfig = {
- # General configuration
- "OSMRO_LOG_LEVEL": config["log_level"],
- }
-
- if config.get("enable_ng_ro", True):
- # Kafka configuration
- envconfig["OSMRO_MESSAGE_DRIVER"] = "kafka"
- envconfig["OSMRO_MESSAGE_HOST"] = relation_state["kafka_host"]
- envconfig["OSMRO_MESSAGE_PORT"] = relation_state["kafka_port"]
-
- # MongoDB configuration
- envconfig["OSMRO_DATABASE_DRIVER"] = "mongo"
- envconfig["OSMRO_DATABASE_URI"] = relation_state["mongodb_connection_string"]
- envconfig["OSMRO_DATABASE_COMMONKEY"] = config["database_commonkey"]
- else:
- envconfig["RO_DB_HOST"] = relation_state["mysql_host"]
- envconfig["RO_DB_OVIM_HOST"] = relation_state["mysql_host"]
- envconfig["RO_DB_PORT"] = relation_state["mysql_port"]
- envconfig["RO_DB_OVIM_PORT"] = relation_state["mysql_port"]
- envconfig["RO_DB_USER"] = relation_state["mysql_user"]
- envconfig["RO_DB_OVIM_USER"] = relation_state["mysql_user"]
- envconfig["RO_DB_PASSWORD"] = relation_state["mysql_password"]
- envconfig["RO_DB_OVIM_PASSWORD"] = relation_state["mysql_password"]
- envconfig["RO_DB_ROOT_PASSWORD"] = relation_state["mysql_root_password"]
- envconfig["RO_DB_OVIM_ROOT_PASSWORD"] = relation_state["mysql_root_password"]
- envconfig["RO_DB_NAME"] = config["ro_database"]
- envconfig["RO_DB_OVIM_NAME"] = config["vim_database"]
- envconfig["OPENMANO_TENANT"] = config["openmano_tenant"]
-
- return envconfig
-
-
-def _make_startup_probe() -> Dict[str, Any]:
- """Generate startup probe.
-
- Returns:
- Dict[str, Any]: startup probe.
- """
- return {
- "exec": {"command": ["/usr/bin/pgrep", "python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
-
-def _make_readiness_probe(port: int) -> Dict[str, Any]:
- """Generate readiness probe.
-
- Args:
- port (int): service port.
-
- Returns:
- Dict[str, Any]: readiness probe.
- """
- return {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
-
-def _make_liveness_probe(port: int) -> Dict[str, Any]:
- """Generate liveness probe.
-
- Args:
- port (int): service port.
-
- Returns:
- Dict[str, Any]: liveness probe.
- """
- return {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "initialDelaySeconds": 600,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
-
-def make_pod_spec(
- image_info: Dict[str, str],
- config: Dict[str, Any],
- relation_state: Dict[str, Any],
- app_name: str = "ro",
- port: int = 9090,
-) -> Dict[str, Any]:
- """Generate the pod spec information.
-
- Args:
- image_info (Dict[str, str]): Object provided by
- OCIImageResource("image").fetch().
- config (Dict[str, Any]): Configuration information.
- relation_state (Dict[str, Any]): Relation state information.
- app_name (str, optional): Application name. Defaults to "ro".
- port (int, optional): Port for the container. Defaults to 9090.
-
- Returns:
- Dict[str, Any]: Pod spec dictionary for the charm.
- """
- if not image_info:
- return None
-
- _validate_data(config, relation_state)
-
- ports = _make_pod_ports(port)
- env_config = _make_pod_envconfig(config, relation_state)
- startup_probe = _make_startup_probe()
- readiness_probe = _make_readiness_probe(port)
- liveness_probe = _make_liveness_probe(port)
-
- return {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": ports,
- "envConfig": env_config,
- "kubernetes": {
- "startupProbe": startup_probe,
- "readinessProbe": readiness_probe,
- "livenessProbe": liveness_probe,
- },
- }
- ],
- "kubernetesResources": {
- "ingressResources": [],
- },
- }
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-"""Init mocking for unit tests."""
-
-import sys
-
-
-import mock
-
-
-class OCIImageResourceErrorMock(Exception):
- pass
-
-
-sys.path.append("src")
-
-oci_image = mock.MagicMock()
-oci_image.OCIImageResourceError = OCIImageResourceErrorMock
-sys.modules["oci_image"] = oci_image
-sys.modules["oci_image"].OCIImageResource().fetch.return_value = {}
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import base64
-from typing import NoReturn
-import unittest
-
-from charm import RoCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-def encode(content: str):
- return base64.b64encode(content.encode("ascii")).decode("utf-8")
-
-
-certificate_pem = encode(
- """
------BEGIN CERTIFICATE-----
-MIIDazCCAlOgAwIBAgIUf1b0s3UKtrxHXH2rge7UaQyfJAMwDQYJKoZIhvcNAQEL
-BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
-GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTAzMjIxNzEyMjdaFw0zMTAz
-MjAxNzEyMjdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
-HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
-AQUAA4IBDwAwggEKAoIBAQCgCfCBgYAN6ON0yHDXuW407rFtJVRf0u46Jrp0Dk7J
-kkSZ1e7Kq14r7yFHazEBWv78oOdwBocvWrd8leLuf3bYGcHR65hRy6A/fbYm5Aje
-cKpwlFwaqfR4BLelwJl79jZ2rJX738cCBVrIk1nAVdOxGrXV4MTWUaKR2c+uKKvc
-OKRT+5VqCeP4N5FWeATZ/KqGu8uV9E9WhFgwIZyStemLyLaDbn5PmAQ6S9oeR5jJ
-o2gEEp/lDKvsqOWs76KFumSKa9hQs5Dw2lj0mb1UoyYK1gYc4ubzVChJadv44AU8
-MYtIjlFn1X1P+RjaKZNUIAGXkoLwYn6SizF6y6LiuFS9AgMBAAGjUzBRMB0GA1Ud
-DgQWBBRl+/23CB+FXczeAZRQyYcfOdy9YDAfBgNVHSMEGDAWgBRl+/23CB+FXcze
-AZRQyYcfOdy9YDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAd
-dkeDym6lRN8kWFtfu3IyiLF8G8sn91qNbH3Yr4TuTBhgcjYyW6PgisSbrNgA9ysE
-GoaF7ohb8GeVfCsQdK23+NpAlj/+DZ3OnGcxwXj1RUAz4yr9kanV1yuEtr1q2xJI
-UaECWr8HZlwGBAKNTGx2EXT2/2aFzgULpDcxzTKD+MRpKpMUrWhf9ULvVrclvHWe
-POLYhobUFuBHuo6rt5Rcq16j67zCX9EVTlAE3o2OECIWByK22sXdeOidYMpTkl4q
-8FrOqjNsx5d+SBPJBv/pqtBm4bA47Vx1P8tbWOQ4bXS0UmXgwpeBOU/O/ot30+KS
-JnKEy+dYyvVBKg77sRHw
------END CERTIFICATE-----
-"""
-)
-
-
-class TestCharm(unittest.TestCase):
- """Prometheus Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.harness = Harness(RoCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "enable_ng_ro": True,
- "database_commonkey": "commonkey",
- "mongodb_uri": "",
- "log_level": "INFO",
- "vim_database": "db_name",
- "ro_database": "ro_db_name",
- "openmano_tenant": "mano",
- "certificates": f"cert1:{certificate_pem}",
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mongodb", "kafka"]
- )
- )
-
- # Disable ng-ro
- self.harness.update_config({"enable_ng_ro": False})
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mysql"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_relations_and_mongodb_config_ng(
- self,
- ) -> NoReturn:
- "Test with relations (ng-ro)"
-
- # Initializing the kafka relation
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- # Initializing the mongodb config
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations_ng(
- self,
- ) -> NoReturn:
- "Test with relations (ng-ro)"
-
- # Initializing the kafka relation
- kafka_relation_id = self.harness.add_relation("kafka", "kafka")
- self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
- self.harness.update_relation_data(
- kafka_relation_id, "kafka", {"host": "kafka", "port": 9092}
- )
-
- # Initializing the mongo relation
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_ng_exception_mongodb_relation_and_config(
- self,
- ) -> NoReturn:
- "Test NG-RO mongodb relation and config. Must fail"
- # Initializing the mongo relation
- mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
- self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
- self.harness.update_relation_data(
- mongodb_relation_id,
- "mongodb/0",
- {"connection_string": "mongodb://mongo:27017"},
- )
-
- # Initializing the mongodb config
- self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"})
-
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-
-if __name__ == "__main__":
- unittest.main()
-
-# class TestCharm(unittest.TestCase):
-# """RO Charm unit tests."""
-
-# def setUp(self) -> NoReturn:
-# """Test setup"""
-# self.harness = Harness(RoCharm)
-# self.harness.set_leader(is_leader=True)
-# self.harness.begin()
-
-# def test_on_start_without_relations_ng_ro(self) -> NoReturn:
-# """Test installation without any relation."""
-# self.harness.charm.on.start.emit()
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_on_start_without_relations_no_ng_ro(self) -> NoReturn:
-# """Test installation without any relation."""
-# self.harness.update_config({"enable_ng_ro": False})
-
-# self.harness.charm.on.start.emit()
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("mysql", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation"))
-
-# def test_on_start_with_relations_ng_ro(self) -> NoReturn:
-# """Test deployment with NG-RO."""
-# expected_result = {
-# "version": 3,
-# "containers": [
-# {
-# "name": "ro",
-# "imageDetails": self.harness.charm.image.fetch(),
-# "imagePullPolicy": "Always",
-# "ports": [
-# {
-# "name": "ro",
-# "containerPort": 9090,
-# "protocol": "TCP",
-# }
-# ],
-# "envConfig": {
-# "OSMRO_LOG_LEVEL": "INFO",
-# "OSMRO_MESSAGE_DRIVER": "kafka",
-# "OSMRO_MESSAGE_HOST": "kafka",
-# "OSMRO_MESSAGE_PORT": "9090",
-# "OSMRO_DATABASE_DRIVER": "mongo",
-# "OSMRO_DATABASE_URI": "mongodb://mongo",
-# "OSMRO_DATABASE_COMMONKEY": "osm",
-# },
-# "kubernetes": {
-# "startupProbe": {
-# "exec": {"command": ["/usr/bin/pgrep", "python3"]},
-# "initialDelaySeconds": 60,
-# "timeoutSeconds": 5,
-# },
-# "readinessProbe": {
-# "httpGet": {
-# "path": "/openmano/tenants",
-# "port": 9090,
-# },
-# "periodSeconds": 10,
-# "timeoutSeconds": 5,
-# "successThreshold": 1,
-# "failureThreshold": 3,
-# },
-# "livenessProbe": {
-# "httpGet": {
-# "path": "/openmano/tenants",
-# "port": 9090,
-# },
-# "initialDelaySeconds": 600,
-# "periodSeconds": 10,
-# "timeoutSeconds": 5,
-# "successThreshold": 1,
-# "failureThreshold": 3,
-# },
-# },
-# }
-# ],
-# "kubernetesResources": {"ingressResources": []},
-# }
-
-# self.harness.charm.on.start.emit()
-
-# # Initializing the kafka relation
-# relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# relation_id,
-# "kafka/0",
-# {
-# "host": "kafka",
-# "port": "9090",
-# },
-# )
-
-# # Initializing the mongodb relation
-# relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# relation_id,
-# "mongodb/0",
-# {
-# "connection_string": "mongodb://mongo",
-# },
-# )
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# pod_spec, _ = self.harness.get_pod_spec()
-
-# self.assertDictEqual(expected_result, pod_spec)
-
-# def test_on_start_with_relations_no_ng_ro(self) -> NoReturn:
-# """Test deployment with old RO."""
-# self.harness.update_config({"enable_ng_ro": False})
-
-# expected_result = {
-# "version": 3,
-# "containers": [
-# {
-# "name": "ro",
-# "imageDetails": self.harness.charm.image.fetch(),
-# "imagePullPolicy": "Always",
-# "ports": [
-# {
-# "name": "ro",
-# "containerPort": 9090,
-# "protocol": "TCP",
-# }
-# ],
-# "envConfig": {
-# "OSMRO_LOG_LEVEL": "INFO",
-# "RO_DB_HOST": "mysql",
-# "RO_DB_OVIM_HOST": "mysql",
-# "RO_DB_PORT": 3306,
-# "RO_DB_OVIM_PORT": 3306,
-# "RO_DB_USER": "mano",
-# "RO_DB_OVIM_USER": "mano",
-# "RO_DB_PASSWORD": "manopw",
-# "RO_DB_OVIM_PASSWORD": "manopw",
-# "RO_DB_ROOT_PASSWORD": "rootmanopw",
-# "RO_DB_OVIM_ROOT_PASSWORD": "rootmanopw",
-# "RO_DB_NAME": "mano_db",
-# "RO_DB_OVIM_NAME": "mano_vim_db",
-# "OPENMANO_TENANT": "osm",
-# },
-# "kubernetes": {
-# "startupProbe": {
-# "exec": {"command": ["/usr/bin/pgrep", "python3"]},
-# "initialDelaySeconds": 60,
-# "timeoutSeconds": 5,
-# },
-# "readinessProbe": {
-# "httpGet": {
-# "path": "/openmano/tenants",
-# "port": 9090,
-# },
-# "periodSeconds": 10,
-# "timeoutSeconds": 5,
-# "successThreshold": 1,
-# "failureThreshold": 3,
-# },
-# "livenessProbe": {
-# "httpGet": {
-# "path": "/openmano/tenants",
-# "port": 9090,
-# },
-# "initialDelaySeconds": 600,
-# "periodSeconds": 10,
-# "timeoutSeconds": 5,
-# "successThreshold": 1,
-# "failureThreshold": 3,
-# },
-# },
-# }
-# ],
-# "kubernetesResources": {"ingressResources": []},
-# }
-
-# self.harness.charm.on.start.emit()
-
-# # Initializing the mysql relation
-# relation_id = self.harness.add_relation("mysql", "mysql")
-# self.harness.add_relation_unit(relation_id, "mysql/0")
-# self.harness.update_relation_data(
-# relation_id,
-# "mysql/0",
-# {
-# "host": "mysql",
-# "port": 3306,
-# "user": "mano",
-# "password": "manopw",
-# "root_password": "rootmanopw",
-# },
-# )
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# pod_spec, _ = self.harness.get_pod_spec()
-
-# self.assertDictEqual(expected_result, pod_spec)
-
-# def test_on_kafka_unit_relation_changed(self) -> NoReturn:
-# """Test to see if kafka relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# relation_id,
-# "kafka/0",
-# {
-# "host": "kafka",
-# "port": 9090,
-# },
-# )
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation"))
-
-# def test_on_mongodb_unit_relation_changed(self) -> NoReturn:
-# """Test to see if mongodb relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# relation_id,
-# "mongodb/0",
-# {
-# "connection_string": "mongodb://mongo",
-# },
-# )
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relation"))
-
-# def test_on_mysql_unit_relation_changed(self) -> NoReturn:
-# """Test to see if mysql relation is updated."""
-# self.harness.charm.on.start.emit()
-
-# relation_id = self.harness.add_relation("mysql", "mysql")
-# self.harness.add_relation_unit(relation_id, "mysql/0")
-# self.harness.update_relation_data(
-# relation_id,
-# "mysql/0",
-# {
-# "host": "mysql",
-# "port": 3306,
-# "user": "mano",
-# "password": "manopw",
-# "root_password": "rootmanopw",
-# },
-# )
-
-# # Verifying status
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-# # Verifying status message
-# self.assertGreater(len(self.harness.charm.unit.status.message), 0)
-# self.assertTrue(
-# self.harness.charm.unit.status.message.startswith("Waiting for ")
-# )
-# self.assertIn("kafka", self.harness.charm.unit.status.message)
-# self.assertIn("mongodb", self.harness.charm.unit.status.message)
-# self.assertTrue(self.harness.charm.unit.status.message.endswith(" relations"))
-
-# def test_publish_ro_info(self) -> NoReturn:
-# """Test to see if ro relation is updated."""
-# expected_result = {
-# "host": "ro",
-# "port": "9090",
-# }
-
-# self.harness.charm.on.start.emit()
-
-# relation_id = self.harness.add_relation("ro", "lcm")
-# self.harness.add_relation_unit(relation_id, "lcm/0")
-# relation_data = self.harness.get_relation_data(relation_id, "ro")
-
-# self.assertDictEqual(expected_result, relation_data)
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2020 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-from typing import NoReturn
-import unittest
-
-import pod_spec
-
-
-class TestPodSpec(unittest.TestCase):
- """Pod spec unit tests."""
-
- def test_make_pod_ports(self) -> NoReturn:
- """Testing make pod ports."""
- port = 9090
-
- expected_result = [
- {
- "name": "ro",
- "containerPort": port,
- "protocol": "TCP",
- }
- ]
-
- pod_ports = pod_spec._make_pod_ports(port)
-
- self.assertListEqual(expected_result, pod_ports)
-
- def test_make_pod_envconfig_ng_ro(self) -> NoReturn:
- """Teting make pod envconfig."""
- config = {
- "enable_ng_ro": True,
- "database_commonkey": "osm",
- "log_level": "INFO",
- }
- relation_state = {
- "kafka_host": "kafka",
- "kafka_port": "9090",
- "mongodb_connection_string": "mongodb://mongo",
- }
-
- expected_result = {
- "OSMRO_LOG_LEVEL": config["log_level"],
- "OSMRO_MESSAGE_DRIVER": "kafka",
- "OSMRO_MESSAGE_HOST": relation_state["kafka_host"],
- "OSMRO_MESSAGE_PORT": relation_state["kafka_port"],
- "OSMRO_DATABASE_DRIVER": "mongo",
- "OSMRO_DATABASE_URI": relation_state["mongodb_connection_string"],
- "OSMRO_DATABASE_COMMONKEY": config["database_commonkey"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_pod_envconfig_no_ng_ro(self) -> NoReturn:
- """Teting make pod envconfig."""
- config = {
- "log_level": "INFO",
- "enable_ng_ro": False,
- "vim_database": "mano_vim_db",
- "ro_database": "mano_db",
- "openmano_tenant": "osm",
- }
- relation_state = {
- "mysql_host": "mysql",
- "mysql_port": 3306,
- "mysql_user": "mano",
- "mysql_password": "manopw",
- "mysql_root_password": "rootmanopw",
- }
-
- expected_result = {
- "OSMRO_LOG_LEVEL": config["log_level"],
- "RO_DB_HOST": relation_state["mysql_host"],
- "RO_DB_OVIM_HOST": relation_state["mysql_host"],
- "RO_DB_PORT": relation_state["mysql_port"],
- "RO_DB_OVIM_PORT": relation_state["mysql_port"],
- "RO_DB_USER": relation_state["mysql_user"],
- "RO_DB_OVIM_USER": relation_state["mysql_user"],
- "RO_DB_PASSWORD": relation_state["mysql_password"],
- "RO_DB_OVIM_PASSWORD": relation_state["mysql_password"],
- "RO_DB_ROOT_PASSWORD": relation_state["mysql_root_password"],
- "RO_DB_OVIM_ROOT_PASSWORD": relation_state["mysql_root_password"],
- "RO_DB_NAME": config["ro_database"],
- "RO_DB_OVIM_NAME": config["vim_database"],
- "OPENMANO_TENANT": config["openmano_tenant"],
- }
-
- pod_envconfig = pod_spec._make_pod_envconfig(config, relation_state)
-
- self.assertDictEqual(expected_result, pod_envconfig)
-
- def test_make_startup_probe(self) -> NoReturn:
- """Testing make startup probe."""
- expected_result = {
- "exec": {"command": ["/usr/bin/pgrep", "python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- }
-
- startup_probe = pod_spec._make_startup_probe()
-
- self.assertDictEqual(expected_result, startup_probe)
-
- def test_make_readiness_probe(self) -> NoReturn:
- """Testing make readiness probe."""
- port = 9090
-
- expected_result = {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
- readiness_probe = pod_spec._make_readiness_probe(port)
-
- self.assertDictEqual(expected_result, readiness_probe)
-
- def test_make_liveness_probe(self) -> NoReturn:
- """Testing make liveness probe."""
- port = 9090
-
- expected_result = {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "initialDelaySeconds": 600,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- }
-
- liveness_probe = pod_spec._make_liveness_probe(port)
-
- self.assertDictEqual(expected_result, liveness_probe)
-
- def test_make_pod_spec_ng_ro(self) -> NoReturn:
- """Testing make pod spec."""
- image_info = {"upstream-source": "opensourcemano/ro:8"}
- config = {
- "database_commonkey": "osm",
- "log_level": "INFO",
- "enable_ng_ro": True,
- }
- relation_state = {
- "kafka_host": "kafka",
- "kafka_port": "9090",
- "mongodb_connection_string": "mongodb://mongo",
- }
- app_name = "ro"
- port = 9090
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": app_name,
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "OSMRO_LOG_LEVEL": config["log_level"],
- "OSMRO_MESSAGE_DRIVER": "kafka",
- "OSMRO_MESSAGE_HOST": relation_state["kafka_host"],
- "OSMRO_MESSAGE_PORT": relation_state["kafka_port"],
- "OSMRO_DATABASE_DRIVER": "mongo",
- "OSMRO_DATABASE_URI": relation_state[
- "mongodb_connection_string"
- ],
- "OSMRO_DATABASE_COMMONKEY": config["database_commonkey"],
- },
- "kubernetes": {
- "startupProbe": {
- "exec": {"command": ["/usr/bin/pgrep", "python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- },
- "readinessProbe": {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- },
- "livenessProbe": {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "initialDelaySeconds": 600,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- },
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_no_ng_ro(self) -> NoReturn:
- """Testing make pod spec."""
- image_info = {"upstream-source": "opensourcemano/ro:8"}
- config = {
- "log_level": "INFO",
- "enable_ng_ro": False,
- "vim_database": "mano_vim_db",
- "ro_database": "mano_db",
- "openmano_tenant": "osm",
- }
- relation_state = {
- "mysql_host": "mysql",
- "mysql_port": 3306,
- "mysql_user": "mano",
- "mysql_password": "manopw",
- "mysql_root_password": "rootmanopw",
- }
- app_name = "ro"
- port = 9090
-
- expected_result = {
- "version": 3,
- "containers": [
- {
- "name": app_name,
- "imageDetails": image_info,
- "imagePullPolicy": "Always",
- "ports": [
- {
- "name": app_name,
- "containerPort": port,
- "protocol": "TCP",
- }
- ],
- "envConfig": {
- "OSMRO_LOG_LEVEL": config["log_level"],
- "RO_DB_HOST": relation_state["mysql_host"],
- "RO_DB_OVIM_HOST": relation_state["mysql_host"],
- "RO_DB_PORT": relation_state["mysql_port"],
- "RO_DB_OVIM_PORT": relation_state["mysql_port"],
- "RO_DB_USER": relation_state["mysql_user"],
- "RO_DB_OVIM_USER": relation_state["mysql_user"],
- "RO_DB_PASSWORD": relation_state["mysql_password"],
- "RO_DB_OVIM_PASSWORD": relation_state["mysql_password"],
- "RO_DB_ROOT_PASSWORD": relation_state["mysql_root_password"],
- "RO_DB_OVIM_ROOT_PASSWORD": relation_state[
- "mysql_root_password"
- ],
- "RO_DB_NAME": config["ro_database"],
- "RO_DB_OVIM_NAME": config["vim_database"],
- "OPENMANO_TENANT": config["openmano_tenant"],
- },
- "kubernetes": {
- "startupProbe": {
- "exec": {"command": ["/usr/bin/pgrep", "python3"]},
- "initialDelaySeconds": 60,
- "timeoutSeconds": 5,
- },
- "readinessProbe": {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- },
- "livenessProbe": {
- "httpGet": {
- "path": "/openmano/tenants",
- "port": port,
- },
- "initialDelaySeconds": 600,
- "periodSeconds": 10,
- "timeoutSeconds": 5,
- "successThreshold": 1,
- "failureThreshold": 3,
- },
- },
- }
- ],
- "kubernetesResources": {"ingressResources": []},
- }
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertDictEqual(expected_result, spec)
-
- def test_make_pod_spec_without_image_info(self) -> NoReturn:
- """Testing make pod spec without image_info."""
- image_info = None
- config = {
- "enable_ng_ro": True,
- "database_commonkey": "osm",
- "log_level": "INFO",
- }
- relation_state = {
- "kafka_host": "kafka",
- "kafka_port": 9090,
- "mongodb_connection_string": "mongodb://mongo",
- }
- app_name = "ro"
- port = 9090
-
- spec = pod_spec.make_pod_spec(
- image_info, config, relation_state, app_name, port
- )
-
- self.assertIsNone(spec)
-
- def test_make_pod_spec_without_config(self) -> NoReturn:
- """Testing make pod spec without config."""
- image_info = {"upstream-source": "opensourcemano/ro:8"}
- config = {}
- relation_state = {
- "kafka_host": "kafka",
- "kafka_port": 9090,
- "mongodb_connection_string": "mongodb://mongo",
- }
- app_name = "ro"
- port = 9090
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
- def test_make_pod_spec_without_relation_state(self) -> NoReturn:
- """Testing make pod spec without relation_state."""
- image_info = {"upstream-source": "opensourcemano/ro:8"}
- config = {
- "enable_ng_ro": True,
- "database_commonkey": "osm",
- "log_level": "INFO",
- }
- relation_state = {}
- app_name = "ro"
- port = 9090
-
- with self.assertRaises(ValueError):
- pod_spec.make_pod_spec(image_info, config, relation_state, app_name, port)
-
-
-if __name__ == "__main__":
- unittest.main()
+++ /dev/null
-# 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.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-#######################################################################################
-
-[tox]
-envlist = black, cover, flake8, pylint, yamllint, safety
-skipsdist = true
-
-[tox:jenkins]
-toxworkdir = /tmp/.tox
-
-[testenv]
-basepython = python3.8
-setenv =
- VIRTUAL_ENV={envdir}
- PYTHONPATH = {toxinidir}:{toxinidir}/lib:{toxinidir}/src
- PYTHONDONTWRITEBYTECODE = 1
-deps = -r{toxinidir}/requirements.txt
-
-
-#######################################################################################
-[testenv:black]
-deps = black
-commands =
- black --check --diff src/ tests/
-
-
-#######################################################################################
-[testenv:cover]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- coverage
- nose2
-commands =
- sh -c 'rm -f nosetests.xml'
- coverage erase
- nose2 -C --coverage src
- coverage report --omit='*tests*'
- coverage html -d ./cover --omit='*tests*'
- coverage xml -o coverage.xml --omit=*tests*
-whitelist_externals = sh
-
-
-#######################################################################################
-[testenv:flake8]
-deps = flake8
- flake8-import-order
-commands =
- flake8 src/ tests/
-
-
-#######################################################################################
-[testenv:pylint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- pylint==2.10.2
-commands =
- pylint -E src/ tests/
-
-
-#######################################################################################
-[testenv:safety]
-setenv =
- LC_ALL=C.UTF-8
- LANG=C.UTF-8
-deps = {[testenv]deps}
- safety
-commands =
- - safety check --full-report
-
-
-#######################################################################################
-[testenv:yamllint]
-deps = {[testenv]deps}
- -r{toxinidir}/requirements-test.txt
- yamllint
-commands = yamllint .
-
-#######################################################################################
-[testenv:build]
-passenv=HTTP_PROXY HTTPS_PROXY NO_PROXY
-whitelist_externals =
- charmcraft
- sh
-commands =
- charmcraft pack
- sh -c 'ubuntu_version=20.04; \
- architectures="amd64-aarch64-arm64"; \
- charm_name=`cat metadata.yaml | grep -E "^name: " | cut -f 2 -d " "`; \
- mv $charm_name"_ubuntu-"$ubuntu_version-$architectures.charm $charm_name.charm'
-
-#######################################################################################
-[flake8]
-ignore =
- W291,
- W293,
- W503,
- E123,
- E125,
- E226,
- E241,
-exclude =
- .git,
- __pycache__,
- .tox,
-max-line-length = 120
-show-source = True
-builtins = _
-max-complexity = 10
-import-order-style = google
+++ /dev/null
-##
-# Copyright 2019 ETSI
-#
-# 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.
-##
-
-charms=`cat bundles/osm/bundle.yaml | grep cs | grep -v k8s | awk '{print $2}' | tr -d \"`
-for charm_uri in $charms; do
- charm_without_rev=`echo $charm_uri| rev | cut -d "-" -f 2-5 | rev`
- latest_revision=`charm show --channel edge $charm_without_rev | grep Revision | awk '{print $2}'`
- new_charm_uri=$charm_without_rev-$latest_revision
- old_uri=`echo $charm_uri | sed 's/\//\\\\\//g'`
- new_uri=`echo $new_charm_uri | sed 's/\//\\\\\//g'`
- sed -i "s/"$old_uri"/"$new_uri"/g" bundles/osm/bundle.yaml
-done
-
-charms=`cat bundles/osm-ha/bundle.yaml | grep cs | grep -v k8s | awk '{print $2}' | tr -d \"`
-for charm_uri in $charms; do
- charm_without_rev=`echo $charm_uri| rev | cut -d "-" -f 2-5 | rev`
- latest_revision=`charm show --channel edge $charm_without_rev | grep Revision | awk '{print $2}'`
- new_charm_uri=$charm_without_rev-$latest_revision
- old_uri=`echo $charm_uri | sed 's/\//\\\\\//g'`
- new_uri=`echo $new_charm_uri | sed 's/\//\\\\\//g'`
- sed -i "s/"$old_uri"/"$new_uri"/g" bundles/osm-ha/bundle.yaml
-done
\ No newline at end of file