8
8
)
9
9
from ops .model import ModelError
10
10
11
- from .base import LBBase
11
+ from .base import VersionedInterface
12
12
13
13
14
14
class LBProviderAvailable (EventBase ):
@@ -24,7 +24,7 @@ class LBProviderEvents(ObjectEvents):
24
24
responses_changed = EventSource (LBResponsesChanged )
25
25
26
26
27
- class LBProvider (LBBase ):
27
+ class LBProvider (VersionedInterface ):
28
28
""" API used to interact with the provider of loadbalancers.
29
29
"""
30
30
state = StoredState ()
@@ -52,15 +52,20 @@ def _check_provider(self, event):
52
52
self .on .available .emit ()
53
53
if self .is_changed :
54
54
self .on .responses_changed .emit ()
55
- else :
55
+ elif self . state . was_available :
56
56
self .state .was_available = False
57
+ if self .state .response_hashes :
58
+ self .state .response_hashes = {}
59
+ self .on .responses_changed .emit ()
57
60
58
61
@property
59
62
def relation (self ):
60
63
return self .relations [0 ] if self .relations else None
61
64
62
65
def get_request (self , name ):
63
66
""" Get or create a Load Balancer Request object.
67
+
68
+ May raise a ModelError if unable to create a request.
64
69
"""
65
70
if not self .charm .unit .is_leader ():
66
71
raise ModelError ('Unit is not leader' )
@@ -82,42 +87,105 @@ def get_request(self, name):
82
87
def get_response (self , name ):
83
88
""" Get a specific Load Balancer Response by name.
84
89
85
- This is equivalent to `get_request(name).response`.
90
+ This is equivalent to `get_request(name).response`, except that it
91
+ will return `None` if the response is not available.
86
92
"""
87
- return self .get_request (name ).response
93
+ if not self .is_available :
94
+ return None
95
+ request = self .get_request (name )
96
+ if not request .response :
97
+ return None
98
+ return request .response
88
99
89
100
def send_request (self , request ):
90
101
""" Send a specific request.
102
+
103
+ May raise a ModelError if unable to send the request.
91
104
"""
105
+ if not self .charm .unit .is_leader ():
106
+ raise ModelError ('Unit is not leader' )
107
+ if not self .relation :
108
+ raise ModelError ('Relation not available' )
109
+ # The sent_hash is used to tell when the provider's response has been
110
+ # updated to match our request. We can't used the request hash computed
111
+ # on the providing side because it may not match due to default values
112
+ # being filled in on that side (e.g., the backend addresses).
113
+ request .sent_hash = request .hash
92
114
key = 'request_' + request .name
93
115
self .relation .data [self .app ][key ] = request .dumps ()
94
- self .state .response_hashes [request .name ] = None
116
+
117
+ def remove_request (self , name ):
118
+ """ Remove a specific request.
119
+
120
+ May raise a ModelError if unable to remove the request.
121
+ """
122
+ if not self .charm .unit .is_leader ():
123
+ raise ModelError ('Unit is not leader' )
124
+ if not self .relation :
125
+ return
126
+ key = 'request_' + name
127
+ self .relation .data [self .app ].pop (key , None )
128
+ self .state .response_hashes .pop (name , None )
129
+
130
+ @property
131
+ def all_requests (self ):
132
+ """ A list of all requests which have been made.
133
+ """
134
+ requests = []
135
+ if self .relation :
136
+ for key in sorted (self .relation .data [self .app ].keys ()):
137
+ if not key .startswith ('request_' ):
138
+ continue
139
+ requests .append (self .get_request (key [len ('request_' ):]))
140
+ return requests
141
+
142
+ @property
143
+ def revoked_responses (self ):
144
+ """ A list of responses which are no longer available.
145
+ """
146
+ return [request .response
147
+ for request in self .all_requests
148
+ if not request .response
149
+ and request .name in self .state .response_hashes ]
95
150
96
151
@cached_property
97
152
def all_responses (self ):
98
153
""" A list of all responses which are available.
99
154
"""
100
- local_data = self .relation .data [self .app ]
101
- names = [key [len ('request_' ):]
102
- for key in local_data .keys ()
103
- if key .startswith ('request_' )]
104
- requests = [self .get_request (name ) for name in names ]
105
- return [request .response for request in requests if request .response ]
155
+ return [request .response
156
+ for request in self .all_requests
157
+ if request .response ]
158
+
159
+ @cached_property
160
+ def complete_responses (self ):
161
+ """ A list of all responses which are up to date with their associated
162
+ request.
163
+ """
164
+ return [request .response
165
+ for request in self .all_requests
166
+ if request .response .received_hash == request .sent_hash ]
106
167
107
168
@property
108
169
def new_responses (self ):
109
- """ A list of all responses which have not yet been acknowledged as
170
+ """ A list of complete responses which have not yet been acknowledged as
110
171
handled or which have changed.
111
172
"""
173
+ acked_responses = self .state .response_hashes
112
174
return [response
113
- for response in self .all_responses
114
- if self . state . response_hashes [ response .name ] != response .hash ]
175
+ for response in self .complete_responses
176
+ if response .hash != acked_responses . get ( response .name ) ]
115
177
116
178
def ack_response (self , response ):
117
179
""" Acknowledge that a given response has been handled.
180
+
181
+ Can be called on a revoked response as well to remove it
182
+ from the revoked_responses list.
118
183
"""
119
- self .state .response_hashes [response .name ] = response .hash
120
- if not self .new_responses :
184
+ if response :
185
+ self .state .response_hashes [response .name ] = response .hash
186
+ else :
187
+ self .state .response_hashes .pop (response .name , None )
188
+ if not self .is_changed :
121
189
try :
122
190
from charms .reactive import clear_flag
123
191
prefix = 'endpoint.' + self .relation_name
@@ -127,12 +195,16 @@ def ack_response(self, response):
127
195
128
196
@property
129
197
def is_changed (self ):
130
- return bool ( self .new_responses )
198
+ return self .new_responses or self . revoked_responses
131
199
132
200
@property
133
201
def is_available (self ):
134
202
return bool (self .relation )
135
203
204
+ @property
205
+ def can_request (self ):
206
+ return self .is_available and self .unit .is_leader ()
207
+
136
208
def manage_flags (self ):
137
209
""" Used to interact with charms.reactive-base charms.
138
210
"""
0 commit comments