# See the License for the specific language governing permissions and
# limitations under the License.
-set -eux
-
function build() {
cd $1 && tox -e build && cd ..
}
build 'nbi-k8s'
build 'pol-k8s'
build 'ro-k8s'
-build 'ui-k8s'
-build 'keystone'
-build 'ng-ui'
-build 'pla'
\ No newline at end of file
+# build 'ui-k8s'
+
+charms="nbi pla pol mon lcm ng-ui keystone"
+charms="nbi"
+if [ -z `which charmcraft` ]; then
+ sudo snap install charmcraft --beta
+fi
+
+for charm_directory in $charms; do
+ echo "Building charm $charm_directory..."
+ cd $charm_directory
+ charmcraft build
+ cd ..
+done
)
):
self.state.message_host = message_host
- self.state.message_port = message_port
+ self.state.message_port = int(message_port)
self.on.configure_pod.emit()
def _on_kafka_relation_departed(self, event: EventBase) -> NoReturn:
)
):
self.state.keystone_host = keystone_host
- self.state.keystone_port = keystone_port
+ self.state.keystone_port = int(keystone_port)
self.state.keystone_user_domain_name = keystone_user_domain_name
self.state.keystone_project_domain_name = keystone_project_domain_name
self.state.keystone_username = keystone_username
)
):
self.state.prometheus_host = prometheus_host
- self.state.prometheus_port = prometheus_port
+ self.state.prometheus_port = int(prometheus_port)
self.on.configure_pod.emit()
def _on_prometheus_relation_departed(self, event: EventBase) -> NoReturn:
Args:
event (EventBase): NBI relation event.
"""
- if self.unit.is_leader():
- rel_data = {
- "host": self.model.app.name,
- "port": str(NBI_PORT),
- }
- for k, v in rel_data.items():
- event.relation.data[self.model.app][k] = v
+ rel_data = {
+ "host": self.model.app.name,
+ "port": str(NBI_PORT),
+ }
+ for k, v in rel_data.items():
+ event.relation.data[self.unit][k] = v
def _missing_relations(self) -> str:
"""Checks if there missing relations.
annotations = {
"nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
- max_file_size + "m" if max_file_size > 0 else max_file_size
+ str(max_file_size) + "m" if max_file_size > 0 else max_file_size
),
"nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
}
```bash
juju deploy . # cs:~charmed-osm/ng-ui --channel edge
-juju relate ng-ui nbi-k8s
+juju relate ng-ui nbi
```
## How to expose the NG-UI through ingress
```bash
-juju config ng-ui juju-external-hostname=ng.<k8s_worker_ip>.xip.io
+juju config ng-ui site_url=ng.<k8s_worker_ip>.xip.io
juju expose ng-ui
```
juju scale-application ng-ui 3
```
-## How to use certificates
-
-Generate your own certificate if you don't have one already:
-
-```bash
-sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl_certificate.key -out ssl_certificate.crt
-sudo chown $USER:$USER ssl_certificate.key
-juju config ng-ui ssl_certificate=`cat ssl_certificate.crt | base64 -w 0`
-juju config ng-ui ssl_certificate_key=`cat ssl_certificate.key | base64 -w 0`
-```
## Config Examples
juju config ng-ui image=opensourcemano/ng-ui:<tag>
juju config ng-ui port=80
juju config server_name=<name>
-juju config client_max_body_size=25M
+juju config max_file_size=25
```
# limitations under the License.
options:
- image:
- description: Docker image name
- type: string
- default: opensourcemano/ng-ui:8
- image_username:
- description: Docker repository username
- type: string
- default: ""
- image_password:
- description: Docker repository password
+ server_name:
+ description: Server name
type: string
- default: ""
+ default: localhost
port:
- description: Port number
+ description: Port to expose
type: int
default: 80
- https_port:
- description: Port number
+ max_file_size:
type: int
- default: 443
- server_name:
- description: Server name
- type: string
- default: localhost
- client_max_body_size:
- description: Client maximum body size
+ 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_whitelist_source_range:
type: string
- default: 15M
- ssl_certificate:
- description: Base64 encoded ssl certificate
+ description: |
+ A comma-separated list of CIDRs to store in the
+ ingress.kubernetes.io/whitelist-source-range annotation.
+ default: ""
+ tls_secret_name:
type: string
- ssl_certificate_key:
- description: Base64 encoded ssl certificate key
+ description: TLS Secret name
+ default: ""
+ site_url:
type: string
+ description: Ingress URL
+ default: ""
server {
- listen $http_port;
- listen $https_port default ssl;
+ listen $port;
server_name $server_name;
root /usr/share/nginx/html;
index index.html index.htm;
- client_max_body_size $client_max_body_size;
- $ssl_crt
- $ssl_crt_key
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
+ 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;
requires:
nbi:
interface: osm-nbi
+resources:
+ image:
+ type: oci-image
+ description: OSM docker image for NBI
+ upstream-source: "opensourcemano/ng-ui:8"
# See the License for the specific language governing permissions and
# limitations under the License.
ops
+pydantic
+git+https://github.com/juju-solutions/resource-oci-image/@c5778285d332edf3d9a538f9d0c06154b7ec1b0b#egg=oci-image
#!/usr/bin/env python3
-# Copyright 2020 Canonical Ltd.
+# 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
+# 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
+# http://www.apache.org/licenses/LICENSE-2.0
#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import base64
-from glob import glob
+# 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 pathlib import Path
-from string import Template
-import sys
+from typing import Any, Dict, NoReturn
+from pydantic import ValidationError
-from ops.charm import CharmBase
-from ops.framework import StoredState, Object
+from ops.charm import CharmBase, CharmEvents
+from ops.framework import EventBase, EventSource, StoredState
from ops.main import main
-from ops.model import (
- ActiveStatus,
- MaintenanceStatus,
- BlockedStatus,
- ModelError,
- WaitingStatus,
-)
+from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus
+from oci_image import OCIImageResource, OCIImageResourceError
+from pod_spec import make_pod_spec
logger = logging.getLogger(__name__)
+NGUI_PORT = 80
+
+
+class ConfigurePodEvent(EventBase):
+ """Configure Pod event"""
+
+ pass
+
+
+class NgUiEvents(CharmEvents):
+ """NGUI Events"""
+
+ configure_pod = EventSource(ConfigurePodEvent)
+
+
+class NgUiCharm(CharmBase):
+ """NGUI Charm."""
-class NGUICharm(CharmBase):
state = StoredState()
+ on = NgUiEvents()
+
+ def __init__(self, *args) -> NoReturn:
+ """NGUI Charm constructor."""
+ super().__init__(*args)
- def __init__(self, framework, key):
- super().__init__(framework, key)
- self.state.set_default(spec=None)
+ # Internal state initialization
+ self.state.set_default(pod_spec=None)
+
+ # North bound interface initialization
self.state.set_default(nbi_host=None)
self.state.set_default(nbi_port=None)
- # Observe Charm related events
- self.framework.observe(self.on.config_changed, self.on_config_changed)
- self.framework.observe(self.on.start, self.on_start)
- self.framework.observe(self.on.upgrade_charm, self.on_upgrade_charm)
+ self.http_port = NGUI_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 changed events
self.framework.observe(
- self.on.nbi_relation_changed, self.on_nbi_relation_changed
+ self.on.nbi_relation_changed, self._on_nbi_relation_changed
)
- # SSL Certificate path
- self.ssl_folder = "/certs"
- self.ssl_crt_name = "ssl_certificate.crt"
- self.ssl_key_name = "ssl_certificate.key"
-
- def _apply_spec(self):
- # Only apply the spec if this unit is a leader.
- unit = self.model.unit
- if not unit.is_leader():
- unit.status = ActiveStatus("ready")
- return
- if not self.state.nbi_host or not self.state.nbi_port:
- unit.status = WaitingStatus("Waiting for NBI")
- return
- unit.status = MaintenanceStatus("Applying new pod spec")
+ # Registering required relation departed events
+ self.framework.observe(
+ self.on.nbi_relation_departed, self._on_nbi_relation_departed
+ )
- new_spec = self.make_pod_spec()
- if new_spec == self.state.spec:
- unit.status = ActiveStatus("ready")
- return
- self.framework.model.pod.set_spec(new_spec)
- self.state.spec = new_spec
- unit.status = ActiveStatus("ready")
-
- def make_pod_spec(self):
- config = self.framework.model.config
-
- config_spec = {
- "http_port": config["port"],
- "https_port": config["https_port"],
- "server_name": config["server_name"],
- "client_max_body_size": config["client_max_body_size"],
- "nbi_host": self.state.nbi_host or config["nbi_host"],
- "nbi_port": self.state.nbi_port or config["nbi_port"],
- "ssl_crt": "",
- "ssl_crt_key": "",
+ def _on_nbi_relation_changed(self, event: EventBase) -> NoReturn:
+ """Reads information about the nbi relation.
+
+ Args:
+ event (EventBase): NBI relation event.
+ """
+ data_loc = event.unit if event.unit else event.app
+ logger.error(dict(event.relation.data))
+ nbi_host = event.relation.data[data_loc].get("host")
+ nbi_port = event.relation.data[data_loc].get("port")
+
+ if (
+ nbi_host
+ and nbi_port
+ and (self.state.nbi_host != nbi_host or self.state.nbi_port != nbi_port)
+ ):
+ self.state.nbi_host = nbi_host
+ self.state.nbi_port = nbi_port
+ self.on.configure_pod.emit()
+
+ def _on_nbi_relation_departed(self, event: EventBase) -> NoReturn:
+ """Clears data from nbi relation.
+
+ Args:
+ event (EventBase): NBI relation event.
+ """
+ self.state.nbi_host = None
+ self.state.nbi_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 = {
+ "nbi": self.state.nbi_host,
}
- ssl_certificate = None
- ssl_certificate_key = None
- ssl_enabled = False
-
- if "ssl_certificate" in config and "ssl_certificate_key" in config:
- # Get bytes of cert and key
- cert_b = base64.b64decode(config["ssl_certificate"])
- key_b = base64.b64decode(config["ssl_certificate_key"])
- # Decode key and cert
- ssl_certificate = cert_b.decode("utf-8")
- ssl_certificate_key = key_b.decode("utf-8")
- # Get paths
- cert_path = "{}/{}".format(self.ssl_folder, self.ssl_crt_name)
- key_path = "{}/{}".format(self.ssl_folder, self.ssl_key_name)
-
- config_spec["port"] = "{} ssl".format(config["https_port"])
- config_spec["ssl_crt"] = "ssl_certificate {};".format(cert_path)
- config_spec["ssl_crt_key"] = "ssl_certificate_key {};".format(key_path)
- ssl_enabled = True
- else:
- config_spec["ssl_crt"] = ""
- config_spec["ssl_crt_key"] = ""
-
- files = [
- {
- "name": "configuration",
- "mountPath": "/etc/nginx/sites-available/",
- "files": {
- Path(filename)
- .name: Template(Path(filename).read_text())
- .substitute(config_spec)
- for filename in glob("files/*")
- },
- }
- ]
- port = config["https_port"] if ssl_enabled else config["port"]
- ports = [
- {
- "name": "port",
- "containerPort": port,
- "protocol": "TCP",
- },
- ]
-
- kubernetes = {
- "readinessProbe": {
- "tcpSocket": {"port": port},
- "timeoutSeconds": 5,
- "periodSeconds": 5,
- "initialDelaySeconds": 10,
- },
- "livenessProbe": {
- "tcpSocket": {"port": port},
- "timeoutSeconds": 5,
- "initialDelaySeconds": 45,
- },
- }
+ missing_relations = [k for k, v in data_status.items() if not v]
- if ssl_certificate and ssl_certificate_key:
- files.append(
- {
- "name": "ssl",
- "mountPath": self.ssl_folder,
- "files": {
- self.ssl_crt_name: ssl_certificate,
- self.ssl_key_name: ssl_certificate_key,
- },
- }
- )
+ return ", ".join(missing_relations)
+
+ @property
+ def relation_state(self) -> Dict[str, Any]:
+ """Collects relation state configuration for pod spec assembly.
- logger.debug(files)
-
- spec = {
- "version": 2,
- "containers": [
- {
- "name": self.framework.model.app.name,
- "imageDetails": {
- "imagePath": config["image"],
- "username": config["image_username"],
- "password": config["image_password"],
- },
- "ports": ports,
- "kubernetes": kubernetes,
- "files": files,
- }
- ],
+ Returns:
+ Dict[str, Any]: relation state information.
+ """
+ relation_state = {
+ "nbi_host": self.state.nbi_host,
+ "nbi_port": self.state.nbi_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(
+ f"Waiting for {missing} relation{'s' if ',' in missing else ''}"
+ )
+ return
- return spec
+ if not self.unit.is_leader():
+ self.unit.status = ActiveStatus("ready")
+ return
- def on_config_changed(self, event):
- """Handle changes in configuration"""
- self._apply_spec()
+ self.unit.status = MaintenanceStatus("Assembling pod spec")
- def on_start(self, event):
- """Called when the charm is being installed"""
- self._apply_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
- def on_upgrade_charm(self, event):
- """Upgrade the charm."""
- unit = self.model.unit
- unit.status = MaintenanceStatus("Upgrading charm")
- self.on_start(event)
+ try:
+ pod_spec = make_pod_spec(
+ image_info,
+ self.config,
+ self.relation_state,
+ self.model.app.name,
+ )
+ except ValidationError as exc:
+ logger.exception("Config/Relation data validation error")
+ self.unit.status = BlockedStatus(str(exc))
+ return
- def on_nbi_relation_changed(self, event):
- nbi_host = event.relation.data[event.unit].get("host")
- nbi_port = event.relation.data[event.unit].get("port")
- if nbi_host and self.state.nbi_host != nbi_host:
- self.state.nbi_host = nbi_host
- if nbi_port and self.state.nbi_port != nbi_port:
- self.state.nbi_port = nbi_port
- self._apply_spec()
+ 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(NGUICharm)
+ 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
+##
+
+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
+#!/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
+
+# 2. Release charms
+# Reactive charms
+charms="lcm-k8s mon-k8s pol-k8s ro-k8s"
+charms=""
+for charm in $charms; do
+ cs_revision=`charm push $charm/release cs:~charmed-osm/$charm | tail -n +1 | head -1 | awk '{print $2}'`
+ charm release --channel $channel $cs_revision
+ echo "$charm charm released!"
+done
+
+# New charms (with no resources)
+charms="pla keystone"
+charms=""
+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}'`
+ charm release --channel $channel $cs_revision
+ echo "$charm charm released!"
+done
+
+# New charms (with resources)
+charms="ng-ui nbi"
+charms="nbi"
+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
IMAGES_OVERLAY_FILE=~/.osm/images-overlay.yaml
PATH=/snap/bin:${PATH}
+MODEL_NAME=osm
+
function check_arguments(){
while [ $# -gt 0 ] ; do
case $1 in
create_overlay
echo "Creating OSM model"
if [ -v KUBECFG ]; then
- juju add-model osm $K8S_CLOUD_NAME
+ juju add-model $MODEL_NAME $K8S_CLOUD_NAME
else
- sg ${KUBEGRP} -c "juju add-model osm $K8S_CLOUD_NAME"
+ sg ${KUBEGRP} -c "juju add-model $MODEL_NAME $K8S_CLOUD_NAME"
fi
echo "Deploying OSM with charms"
images_overlay=""
[ -v TAG ] && generate_images_overlay && images_overlay="--overlay $IMAGES_OVERLAY_FILE"
if [ -v BUNDLE ]; then
- juju deploy $BUNDLE --overlay ~/.osm/vca-overlay.yaml $images_overlay
+ juju deploy -m $MODEL_NAME $BUNDLE --overlay ~/.osm/vca-overlay.yaml $images_overlay
else
- juju deploy cs:osm-52 --overlay ~/.osm/vca-overlay.yaml $images_overlay
+ juju deploy -m $MODEL_NAME cs:osm-53 --overlay ~/.osm/vca-overlay.yaml $images_overlay
fi
echo "Waiting for deployment to finish..."
fi
# Expose OSM services
- # Expose Grafana
- juju config grafana-k8s juju-external-hostname=grafana.${API_SERVER}.xip.io
- juju expose grafana-k8s
- wait_for_port grafana-k8s 0
-
# Expose NBI
- juju config nbi-k8s juju-external-hostname=nbi.${API_SERVER}.xip.io
- juju expose nbi-k8s
- wait_for_port nbi-k8s 1
+ juju config -m $MODEL_NAME nbi site_url=https://nbi.${API_SERVER}.xip.io
+ juju config -m $MODEL_NAME ng-ui site_url=https://ui.${API_SERVER}.xip.io
- # Expose NG UI
- juju config ng-ui juju-external-hostname=ui.${API_SERVER}.xip.io
- juju expose ng-ui
- wait_for_port ng-ui 2
+ # Expose Grafana
+ juju config -m $MODEL_NAME grafana-k8s juju-external-hostname=grafana.${API_SERVER}.xip.io
+ juju expose -m $MODEL_NAME grafana-k8s
+ wait_for_port grafana-k8s 0
# Expose Prometheus
- juju config prometheus-k8s juju-external-hostname=prometheus.${API_SERVER}.xip.io
- juju expose prometheus-k8s
- wait_for_port prometheus-k8s 3
+ juju config -m $MODEL_NAME prometheus-k8s juju-external-hostname=prometheus.${API_SERVER}.xip.io
+ juju expose -m $MODEL_NAME prometheus-k8s
+ wait_for_port prometheus-k8s 1
# Apply annotations
- sg ${KUBEGRP} -c "${KUBECTL} annotate ingresses.networking nginx.ingress.kubernetes.io/backend-protocol=HTTPS -n osm -l juju-app=nbi-k8s"
- sg ${KUBEGRP} -c "${KUBECTL} annotate ingresses.networking nginx.ingress.kubernetes.io/proxy-body-size=0 -n osm -l juju-app=nbi-k8s"
sg ${KUBEGRP} -c "${KUBECTL} annotate ingresses.networking nginx.ingress.kubernetes.io/proxy-body-size=0 -n osm -l juju-app=ng-ui"
}
previous_count=0
while true
do
- service_count=$(juju status | grep kubernetes | grep active | wc -l)
+ service_count=$(juju status -m $MODEL_NAME | grep kubernetes | grep active | wc -l)
echo "$service_count / $total_service_count services active"
if [ $service_count -eq $total_service_count ]; then
break
}
function generate_images_overlay(){
+ cat << EOF > /tmp/nbi_registry.yaml
+registrypath: ${REGISTRY_URL}opensourcemano/nbi:$TAG
+EOF
+ cat << EOF > /tmp/ng_ui_registry.yaml
+registrypath: ${REGISTRY_URL}opensourcemano/ng-ui:$TAG
+EOF
if [ ! -z "$REGISTRY_USERNAME" ] ; then
REGISTRY_CREDENTIALS=$(cat <<EOF
image_password: $REGISTRY_PASSWORD
EOF
);
+ echo username: $REGISTRY_USERNAME >> /tmp/nbi_registry.yaml
+ echo password: $REGISTRY_PASSWORD >> /tmp/nbi_registry.yaml
+ echo username: $REGISTRY_USERNAME >> /tmp/ng_ui_registry.yaml
+ echo password: $REGISTRY_PASSWORD >> /tmp/ng_ui_registry.yaml
fi
cat << EOF > /tmp/images-overlay.yaml
ro-k8s:
options:
image: ${REGISTRY_URL}opensourcemano/ro:$TAG ${REGISTRY_CREDENTIALS}
- nbi-k8s:
- options:
- image: ${REGISTRY_URL}opensourcemano/nbi:$TAG ${REGISTRY_CREDENTIALS}
+ nbi:
+ resources:
+ image: /tmp/nbi_registry.yaml
pol-k8s:
options:
image: ${REGISTRY_URL}opensourcemano/pol:$TAG ${REGISTRY_CREDENTIALS}
options:
image: ${REGISTRY_URL}opensourcemano/pla:$TAG ${REGISTRY_CREDENTIALS}
ng-ui:
- options:
- image: ${REGISTRY_URL}opensourcemano/ng-ui:$TAG ${REGISTRY_CREDENTIALS}
+ resources:
+ image: /tmp/ng_ui_registry.yaml
keystone:
options:
image: ${REGISTRY_URL}opensourcemano/keystone:$TAG ${REGISTRY_CREDENTIALS}
ubuntu1604
ssh-keygen -t rsa -N "" -f ~/.ssh/microstack
microstack.openstack keypair create --public-key ~/.ssh/microstack.pub microstack
- export OSM_HOSTNAME=`juju status --format json | jq -rc '.applications."nbi-k8s".address'`
+ export OSM_HOSTNAME=`juju status --format json | jq -rc '.applications."nbi".address'`
osm vim-create --name microstack-site \
--user admin \
--password keystone \
install_microstack
fi
-OSM_HOSTNAME=$(juju config nbi-k8s juju-external-hostname):443
+OSM_HOSTNAME=$(juju config nbi site_url | sed "s/http.*\?:\/\///"):443
echo "Your installation is now complete, follow these steps for configuring the osmclient:"
echo