Merge ~tilmanbaumann/interface-prometheus:reactive_endpoints into interface-prometheus:master

Proposed by Tilman Baumann
Status: Merged
Approved by: Stuart Bishop
Approved revision: 45a4be2ce15eb9ce170f06e2462e12693c78fc98
Merged at revision: defa8b8ead4b33f58826be29b254fff9c024ac59
Proposed branch: ~tilmanbaumann/interface-prometheus:reactive_endpoints
Merge into: interface-prometheus:master
Diff against target: 202 lines (+109/-55)
2 files modified
provides.py (+49/-21)
requires.py (+60/-34)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Canonical IS Reviewers Pending
Review via email: mp+367744@code.launchpad.net

Commit message

Switch from RelationBase class to Endpoint

Description of the change

I converted the interface to the new Endpoint class because of unfixable bugs in the old method.

The code stays compatible with old charms expecting RelationBase. But I will propose patches to all charms using this interface to make use of the new features.

To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote :

Yes please. This is going to be a teaching example, as it is simple enough to comprehend and not need any weird stuff, but complex enough to demonstrate most of the Endpoint stuff (including implementing both provider and consumer; this is the first non-trivial Endpoint I've seen that has done that). The most magic thing is mixing to_publish for implicit JSON encoding with to_publish_raw for the legacy fields that need to be unencoded.

So I'd like to see two minor changes needed to support cross model relations fixed in this branch, so that when people inevitably cut&paste the code they get the good stuff. Some other comments inline too, primarily with setting the old flags for backwards compatibility.

review: Needs Fixing
Revision history for this message
Tilman Baumann (tilmanbaumann) wrote :

Thanks, Stuart for the great review. I will be more than happy to implement those changes.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Stuart Bishop (stub) wrote :

The updates all look great :)

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision defa8b8ead4b33f58826be29b254fff9c024ac59

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/provides.py b/provides.py
index b28d2f2..d1f1683 100644
--- a/provides.py
+++ b/provides.py
@@ -1,31 +1,59 @@
1from charms.reactive import RelationBase, scopes, hook1from charms.reactive import Endpoint
2from charms.reactive import hook, when, when_not
2from charmhelpers.core import hookenv3from charmhelpers.core import hookenv
4from charms.reactive.flags import clear_flag, set_flag
35
46
5class PrometheusProvides(RelationBase):7'''
6 scope = scopes.UNIT8This interface is desiged to be compatible with a previous
9implementation based on RelationBase.
710
8 @hook('{provides:prometheus}-relation-{joined,changed}')11Specifically
9 def changed(self):12- the `{endpoint_name}.available` flags
10 self.set_state('{relation_name}.available')13- the use of `to_publish_raw` to avoid double quoting of the values
14 as the old interface used plain values here
15'''
1116
12 @hook('{provides:prometheus}-relation-{broken,departed}')17
13 def broken(self):18class PrometheusProvides(Endpoint):
14 self.remove_state('{relation_name}.available')
1519
16 def configure(self, port, path='/metrics',20 def configure(self, port, path='/metrics',
17 scrape_interval=None, scrape_timeout=None, labels={}):21 scrape_interval=None, scrape_timeout=None, labels={}):
18 unit_name = hookenv.local_unit()22 """
23 Interface method to set information provided to remote units
24 """
25
26 # Use our unit name if the label isn't provided
19 if labels.get('host') is None:27 if labels.get('host') is None:
28 unit_name = hookenv.local_unit()
20 labels['host'] = unit_name.replace("/", "-")29 labels['host'] = unit_name.replace("/", "-")
21 relation_info = {30
22 'hostname': hookenv.unit_private_ip(),31 for relation in self.relations:
23 'port': port,32 relation.to_publish_raw['hostname'] = hookenv.ingress_address(
24 'metrics_path': path,33 relation.relation_id, hookenv.local_unit()
25 'labels': labels,34 )
26 }35 relation.to_publish_raw['port'] = port
27 if scrape_interval is not None:36 relation.to_publish_raw['metrics_path'] = path
28 relation_info['scrape_interval'] = scrape_interval37 relation.to_publish['labels'] = labels
29 if scrape_timeout is not None:38 if scrape_interval is not None:
30 relation_info['scrape_timeout'] = scrape_timeout39 relation.to_publish_raw['scrape_interval'] = scrape_interval
31 self.set_remote(**relation_info)40 if scrape_timeout is not None:
41 relation.to_publish_raw['scrape_timeout'] = scrape_timeout
42
43 @when('endpoint.{endpoint_name}.joined')
44 def available(self):
45 """
46 Raising the available flag when a unit joined
47 """
48 set_flag(self.expand_name('endpoint.{endpoint_name}.available'))
49 set_flag(self.expand_name('{endpoint_name}.available')) #compatibility
50
51
52 @when('endpoint.{endpoint_name}.departed')
53 def broken(self):
54 """
55 Remove the available flag when the last unit has departed
56 """
57 if not self.is_joined:
58 clear_flag(self.expand_name('endpoint.{endpoint_name}.available'))
59 clear_flag(self.expand_name('{endpoint_name}.available')) #compatibility
32\ No newline at end of file60\ No newline at end of file
diff --git a/requires.py b/requires.py
index 83cc4f1..a2c10a2 100644
--- a/requires.py
+++ b/requires.py
@@ -1,27 +1,42 @@
1from charms.reactive import hook1from charms.reactive import Endpoint
2from charms.reactive import RelationBase2from charms.reactive import hook, when, when_not
3from charms.reactive import scopes3from charms.reactive.flags import clear_flag, set_flag
4from charmhelpers.core.hookenv import service_name, ingress_address
45
56
6class PrometheusRequires(RelationBase):7'''
7 scope = scopes.UNIT8This interface is desiged to be compatible with a previous
9implementation based on RelationBase.
810
9 @hook('{requires:prometheus}-relation-{joined,changed}')11The old `{endpoint_name}.available` flags are maintained
12'''
13
14class PrometheusRequires(Endpoint):
15
16 @when('endpoint.{endpoint_name}.changed')
10 def changed(self):17 def changed(self):
11 conv = self.conversation()18 """
12 if conv.get_remote('port'):19 Raising the availability flag once we have received the port field from a connected unit.
13 # this unit's conversation has a port, so20 It is convention that remote units signal availability this way.
14 # it is part of the set of available units21 """
15 conv.set_state('{relation_name}.available')22 if self.all_joined_units.received['port']:
23 set_flag(self.expand_name('endpoint.{endpoint_name}.available'))
24 set_flag(self.expand_name('{endpoint_name}.available')) #compatibility
25
1626
17 @hook('{requires:prometheus}-relation-{departed,broken}')27 @when('endpoint.{endpoint_name}.departed')
18 def broken(self):28 def broken(self):
19 conv = self.conversation()29 """
20 conv.remove_state('{relation_name}.available')30 Clearing the availability flag once the last unit departed.
31 """
32 if not self.is_joined:
33 clear_flag(self.expand_name('endpoint.{endpoint_name}.available'))
34 clear_flag(self.expand_name('{endpoint_name}.available')) #compatibility
35
2136
22 def targets(self):37 def targets(self):
23 """38 """
24 Returns a list of available prometheus targets.39 Interface method returns a list of available prometheus targets.
25 [40 [
26 {41 {
27 'job_name': name_of_job,42 'job_name': name_of_job,
@@ -35,23 +50,34 @@ class PrometheusRequires(RelationBase):
35 ]50 ]
36 """51 """
37 services = {}52 services = {}
38 for conv in self.conversations():53 for relation in self.relations:
39 service_name = conv.scope.split('/')[0]54 service_name = relation.application_name
40 service = services.setdefault(service_name, {55 for unit in relation.units:
41 'job_name': service_name,56 service = services.setdefault(service_name, {
42 'targets': [],57 'job_name': service_name,
43 })58 'targets': [],
44 host = conv.get_remote('hostname') or\59 })
45 conv.get_remote('private-address')60
46 port = conv.get_remote('port')61 # If the hostname is not provided we use the informaton from the relation
47 if host and port:62 host = (unit.received['hostname'] or
48 service['targets'].append('{}:{}'.format(host, port))63 ingress_address(relation.relation_id, unit))
49 if conv.get_remote('metrics_path'):64 port = unit.received['port']
50 service['metrics_path'] = conv.get_remote('metrics_path')65
51 if conv.get_remote('scrape_interval'):66 # Skipping this unit if it isn't ready yet
52 service['scrape_interval'] = conv.get_remote('scrape_interval')67 if host and port:
53 if conv.get_remote('scrape_timeout'):68 service['targets'].append('{}:{}'.format(host, port))
54 service['scrape_timeout'] = conv.get_remote('scrape_timeout')69 else:
55 if conv.get_remote('labels'):70 continue
56 service['labels'] = conv.get_remote('labels')71
72 if unit.received['metrics_path']:
73 service['metrics_path'] = unit.received['metrics_path']
74 if unit.received['labels']:
75 service['labels'] = unit.received['labels']
76
77 # Optional fields
78 if unit.received['scrape_interval']:
79 service['scrape_interval'] = unit.received['scrape_interval']
80 if unit.received['scrape_timeout']:
81 service['scrape_timeout'] = unit.received['scrape_timeout']
57 return [s for s in services.values() if s['targets']]82 return [s for s in services.values() if s['targets']]
83

Subscribers

People subscribed via source and target branches