-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recommended instantiation with ActiveStatus leads to issues #22
Comments
To spitball a fix, here's what we could do in the import logging
import subprocess
from jinja2 import Environment, FileSystemLoader
from ops.charm import CharmBase
from ops.framework import StoredState
from ops.main import main
from ops.model import ActiveStatus, BlockedStatus
from serialized_data_interface import NoCompatibleVersions, NoVersionsListed, get_interfaces
class Operator(CharmBase):
storage = StoredState()
def __init__(self, *args):
super().__init__(*args)
self.storage.set_default(blockers=set())
if not self.unit.is_leader():
self.storage.blockers.add("Waiting for leadership")
self.update_status()
return
else:
if "Waiting for leadership" in self.storage.blockers:
self.storage.blockers.remove("Waiting for leadership")
try:
self.interfaces = get_interfaces(self)
except NoVersionsListed:
self.storage.blockers.add('NoVersionsListed')
self.update_status()
return
except NoCompatibleVersions:
self.storage.blockers.add('NoCompatibleVersions')
self.update_status()
return
else:
if 'NoVersionsListed' in self.storage.blockers:
self.storage.blockers.remove('NoVersionsListed')
if 'NoCompatibleVersions' in self.storage.blockers:
self.storage.blockers.remove('NoCompatibleVersions')
self.update_status()
self.log = logging.getLogger(__name__)
self.framework.observe(self.on.install, self.install)
self.framework.observe(self.on["istio-pilot"].relation_changed, self.install)
self.framework.observe(self.on.config_changed, self.install)
def update_status(self):
if self.storage.blockers:
self.model.unit.status = BlockedStatus('; '.join(self.storage.blockers))
else:
self.model.unit.status = ActiveStatus()
def install(self, event):
"""Install charm."""
if self.model.config['kind'] not in ('ingress', 'egress'):
self.storage.blockers.add('Config item `kind` must be set')
self.update_status()
return
else:
if 'Config item `kind` must be set' in self.storage.blockers:
self.storage.blockers.remove('Config item `kind` must be set')
if not self.model.relations['istio-pilot']:
self.storage.blockers.add("Waiting for istio-pilot relation")
self.update_status()
return
else:
if "Waiting for istio-pilot relation" in self.storage.blockers:
self.storage.blockers.remove("Waiting for istio-pilot relation")
if not ((pilot := self.interfaces["istio-pilot"]) and pilot.get_data()):
self.storage.blockers.add("Waiting for istio-pilot relation data")
self.update_status()
return
else:
if "Waiting for istio-pilot relation data" in self.storage.blockers:
self.storage.blockers.remove("Waiting for istio-pilot relation data")
pilot = list(pilot.get_data().values())[0]
env = Environment(loader=FileSystemLoader('src'))
template = env.get_template('manifest.yaml')
rendered = template.render(
kind=self.model.config['kind'],
namespace=self.model.name,
pilot_host=pilot['service-name'],
pilot_port=pilot['service-port'],
)
subprocess.run(["./kubectl", "apply", "-f-"], input=rendered.encode('utf-8'), check=True)
self.update_status()
if __name__ == "__main__":
main(Operator) |
Reading about the
Our main issue with CI, when removing the Now, of course, if the relation data is unavailable for a critical part of the charm (e.g. creating the resources) we should be aware of that and notify it. Therefore I think that the Waiting status should be set in the |
To transform my wall of text into something understandable here is how the code would look like more or less. @knkski @ca-scribner Please let me know if I'm missing something. class Operator(CharmBase):
storage = StoredState()
def __init__(self, *args):
super().__init__(*args)
self.log = logging.getLogger(__name__)
if not self.unit.is_leader():
self.storage.blockers.add("Waiting for leadership")
return
try:
self.interfaces = get_interfaces(self)
except NoVersionsListed:
# Log that the data is not available yet, but don't set any status
# In this part of the code it is not our problem yet.
self.log.info("log something about it")
except NoCompatibleVersions:
# I understand this will only occur if something is actually wrong.
self.model.unit.status = BlockedStatus('NoCompatible Versions')
return
self.framework.observe(self.on.install, self.install)
self.framework.observe(self.on["istio-pilot"].relation_changed, self.install)
self.framework.observe(self.on.config_changed, self.install)
def install(self, event):
"""Install charm."""
if self.model.config['kind'] not in ('ingress', 'egress'):
self.model.unit.status = BlockedStatus('Config item `kind` must be set')
# A event.defer() shouldn't be needed here as config_changed will also call this method.
return
if not ((pilot := self.interfaces["istio-pilot"]) and pilot.get_data()):
# Actually set a Waiting Status as we can't proceed without it
self.model.unit.status = WaitingStatus('Istio-pilot data not ready')
# Revisit the install event once the data is available.
event.defer()
return
pilot = list(pilot.get_data().values())[0]
env = Environment(loader=FileSystemLoader('src'))
template = env.get_template('manifest.yaml')
rendered = template.render(
kind=self.model.config['kind'],
namespace=self.model.name,
pilot_host=pilot['service-name'],
pilot_port=pilot['service-port'],
)
subprocess.run(["./kubectl", "apply", "-f-"], input=rendered.encode('utf-8'), check=True)
# Make sure we set the ActiveStatus once it actually is active and not anywhere else
self.model.unit.status = ActiveStatus()
if __name__ == "__main__":
main(Operator) |
A minor edit to @DomFleischmann's suggestion, using constants for status to avoid easy typos. We could probably template things like this too if there's a need (eg: if there's a "relation X missing value 'somethingImportant'", we could have WAITERS = {
"istio_pilot": "Istio-pilot data not ready"
}
BLOCKERS = {
"no_interface_version": "No compatible versions",
"no_kind": "Config item `kind` must be set",
"not_leader": "Waiting for leadership",
}
ERRORS = {
"something_could": "be here too",
}
class Operator(CharmBase):
storage = StoredState()
def __init__(self, *args):
super().__init__(*args)
self.storage.set_default(blockers=set())
self.log = logging.getLogger(__name__)
if not self.unit.is_leader():
self.storage.blockers.add(BLOCKERS["not_leader"])
return
try:
self.interfaces = get_interfaces(self)
except NoVersionsListed:
# Log that the data is not available yet, but don't set any status
# In this part of the code it is not our problem yet.
self.log.info("log something about it")
except NoCompatibleVersions:
# I understand this will only occur if something is actually wrong.
self.model.unit.status = BlockedStatus(BLOCKERS["no_interface_version"])
return
self.framework.observe(self.on.install, self.install)
self.framework.observe(self.on["istio-pilot"].relation_changed, self.install)
self.framework.observe(self.on.config_changed, self.install)
def install(self, event):
"""Install charm."""
if self.model.config['kind'] not in ('ingress', 'egress'):
self.model.unit.status = BlockedStatus(BLOCKERS["no_kind"])
# A event.defer() shouldn't be needed here as config_changed will also call this method.
return
if not ((pilot := self.interfaces["istio-pilot"]) and pilot.get_data()):
# Actually set a Waiting Status as we can't proceed without it
self.model.unit.status = WaitingStatus(WAITERS["istio_pilot"])
# Revisit the install event once the data is available.
event.defer()
return
pilot = list(pilot.get_data().values())[0]
env = Environment(loader=FileSystemLoader('src'))
template = env.get_template('manifest.yaml')
rendered = template.render(
kind=self.model.config['kind'],
namespace=self.model.name,
pilot_host=pilot['service-name'],
pilot_port=pilot['service-port'],
)
subprocess.run(["./kubectl", "apply", "-f-"], input=rendered.encode('utf-8'), check=True)
# Make sure we set the ActiveStatus once it actually is active and not anywhere else
self.model.unit.status = ActiveStatus()
if __name__ == "__main__":
main(Operator) |
I'm wondering if the install hook is the right hook for us to install on. It sounds odd, but at |
We recommend using this library like this:
That leads to an issue where
ActiveStatus
can overwrite aBlockedStatus
. This can be reproduced with:Which resolves to revision 6 as of this writing. It should be blocking on needing a relation to
istio-pilot
, but that status gets overwritten.juju show-status-log
shows this:Deploying
istio-gateway
manually without theelse: self.model.unit.status = ActiveStatus()
clause worked initially, but broke after repeated relation joins and removals:The text was updated successfully, but these errors were encountered: