1 # Copyright 2022 Canonical Ltd.
2 # See LICENSE file for licensing details.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
19 VCA stands for VNF Configuration and Abstraction, and is one of the core components
20 of OSM. The Juju Controller is in charged of this role.
22 This [library](https://juju.is/docs/sdk/libraries) implements both sides of the
23 `vca` [interface](https://juju.is/docs/sdk/relations).
25 The *provider* side of this interface is implemented by the
26 [osm-vca-integrator Charmed Operator](https://charmhub.io/osm-vca-integrator).
28 helps to integrate with the
29 vca-integrator charm, which provides data needed to the OSM components that need
30 to talk to the VCA, and
32 Any Charmed OSM component that *requires* to talk to the VCA should implement
33 the *requirer* side of this interface.
35 In a nutshell using this library to implement a Charmed Operator *requiring* VCA data
39 $ charmcraft fetch-lib charms.osm_vca_integrator.v0.vca
53 from charms.osm_vca_integrator.v0.vca import VcaData, VcaIntegratorEvents, VcaRequires
54 from ops.charm import CharmBase
57 class MyCharm(CharmBase):
59 on = VcaIntegratorEvents()
61 def __init__(self, *args):
62 super().__init__(*args)
63 self.vca = VcaRequires(self)
64 self.framework.observe(
65 self.on.vca_data_changed,
66 self._on_vca_data_changed,
69 def _on_vca_data_changed(self, event):
71 data: VcaData = self.vca.data
72 # data.endpoints => "localhost:17070"
76 [here](https://github.com/charmed-osm/osm-vca-integrator-operator/issues)!
81 from typing
import Any
, Dict
, Optional
83 from ops
.charm
import CharmBase
, CharmEvents
, RelationChangedEvent
84 from ops
.framework
import EventBase
, EventSource
, Object
86 # The unique Charmhub library identifier, never change it
87 from ops
.model
import Relation
89 # The unique Charmhub library identifier, never change it
90 LIBID
= "746b36c382984e5c8660b78192d84ef9"
92 # Increment this major API version when introducing breaking changes
95 # Increment this PATCH version before using `charmcraft publish-lib` or reset
96 # to 0 if you are raising the major API version
100 logger
= logging
.getLogger(__name__
)
103 class VcaDataChangedEvent(EventBase
):
104 """Event emitted whenever there is a change in the vca data."""
106 def __init__(self
, handle
):
107 super().__init
__(handle
)
110 class VcaIntegratorEvents(CharmEvents
):
111 """VCA Integrator events.
113 This class defines the events that ZooKeeper can emit.
116 vca_data_changed (_VcaDataChanged)
119 vca_data_changed
= EventSource(VcaDataChangedEvent
)
122 RELATION_MANDATORY_KEYS
= ("endpoints", "user", "secret", "public-key", "cacert", "model-configs")
126 """Vca data class."""
128 def __init__(self
, data
: Dict
[str, Any
]) -> None:
129 self
.data
: str = data
130 self
.endpoints
: str = data
["endpoints"]
131 self
.user
: str = data
["user"]
132 self
.secret
: str = data
["secret"]
133 self
.public_key
: str = data
["public-key"]
134 self
.cacert
: str = data
["cacert"]
135 self
.lxd_cloud
: str = data
.get("lxd-cloud")
136 self
.lxd_credentials
: str = data
.get("lxd-credentials")
137 self
.k8s_cloud
: str = data
.get("k8s-cloud")
138 self
.k8s_credentials
: str = data
.get("k8s-credentials")
139 self
.model_configs
: Dict
[str, Any
] = data
.get("model-configs", {})
142 class VcaDataMissingError(Exception):
143 """Data missing exception."""
146 class VcaRequires(Object
):
147 """Requires part of the vca relation.
150 endpoint_name: Endpoint name of the charm for the vca relation.
151 data: Vca data from the relation.
154 def __init__(self
, charm
: CharmBase
, endpoint_name
: str = "vca") -> None:
155 super().__init
__(charm
, endpoint_name
)
157 self
.endpoint_name
= endpoint_name
158 self
.framework
.observe(charm
.on
[endpoint_name
].relation_changed
, self
._on
_relation
_changed
)
161 def data(self
) -> Optional
[VcaData
]:
162 """Vca data from the relation."""
163 relation
: Relation
= self
.model
.get_relation(self
.endpoint_name
)
164 if not relation
or relation
.app
not in relation
.data
:
165 logger
.debug("no application data in the event")
168 relation_data
: Dict
= dict(relation
.data
[relation
.app
])
169 relation_data
["model-configs"] = json
.loads(relation_data
.get("model-configs", "{}"))
171 self
._validate
_relation
_data
(relation_data
)
172 return VcaData(relation_data
)
173 except VcaDataMissingError
as e
:
176 def _on_relation_changed(self
, event
: RelationChangedEvent
) -> None:
177 if event
.app
not in event
.relation
.data
:
178 logger
.debug("no application data in the event")
181 relation_data
= event
.relation
.data
[event
.app
]
183 self
._validate
_relation
_data
(relation_data
)
184 self
._charm
.on
.vca_data_changed
.emit()
185 except VcaDataMissingError
as e
:
188 def _validate_relation_data(self
, relation_data
: Dict
[str, str]) -> None:
189 if not all(required_key
in relation_data
for required_key
in RELATION_MANDATORY_KEYS
):
190 raise VcaDataMissingError("vca data not ready yet")
192 clouds
= ("lxd-cloud", "k8s-cloud")
193 if not any(cloud
in relation_data
for cloud
in clouds
):
194 raise VcaDataMissingError("no clouds defined yet")
197 class VcaProvides(Object
):
198 """Provides part of the vca relation.
201 endpoint_name: Endpoint name of the charm for the vca relation.
204 def __init__(self
, charm
: CharmBase
, endpoint_name
: str = "vca") -> None:
205 super().__init
__(charm
, endpoint_name
)
206 self
.endpoint_name
= endpoint_name
208 def update_vca_data(self
, vca_data
: VcaData
) -> None:
209 """Update vca data in relation.
212 vca_data: VcaData object.
215 for relation
in self
.model
.relations
[self
.endpoint_name
]:
216 if not relation
or self
.model
.app
not in relation
.data
:
217 logger
.debug("relation app data not ready yet")
218 for key
, value
in vca_data
.data
.items():
219 if key
== "model-configs":
220 value
= json
.dumps(value
)
221 relation
.data
[self
.model
.app
][key
] = value