diff --git a/provides.py b/provides.py index bf4ffe0..eca7d67 100644 --- a/provides.py +++ b/provides.py @@ -36,9 +36,15 @@ class ManilaPluginProvides(reactive.RelationBase): # with a basic documentation string provided. auto_accessors = ['_authentication_data'] + class states(reactive.bus.StateList): + connected = reactive.bus.State('{relation_name}.connected') + available = reactive.bus.State('{relation_name}.available') + changed = reactive.bus.State('{relation_name}.changed') + @reactive.hook('{provides:manila-plugin}-relation-joined') def joined(self): - self.set_state('{relation_name}.connected') + conversation = self.conversation() + conversation.set_state(self.states.connected) self.update_status() @reactive.hook('{provides:manila-plugin}-relation-changed') @@ -54,9 +60,10 @@ class ManilaPluginProvides(reactive.RelationBase): @reactive.hook('{provides:manila-plugin}-relation-{broken,departed}') def departed(self): - self.remove_state('{relation_name}.changed') - self.remove_state('{relation_name}.available') - self.remove_state('{relation_name}.connected') + conversation = self.conversation() + conversation.remove_state(self.states.connected) + conversation.remove_state(self.states.available) + conversation.remove_state(self.states.changed) def update_status(self): """Set the .available and .changed state if both the plugin name and @@ -68,15 +75,26 @@ class ManilaPluginProvides(reactive.RelationBase): configuration files as needed. The interface will NOT set .changed without having .available at the - same time. + same time. Also, the interface will not set .changed unless the + authentication data has changed. """ - if self._authentication_data() is not None: - self.set_state('{relation_name}.available') - self.set_state('{relation_name}.changed') + auth_data = self._authentication_data() + conversation = self.conversation() + if auth_data is not None: + conversation.set_state(self.states.available) + scope = conversation.scope + local_auth_data = self.get_local('_authentication_data', + default=None, + scope=scope) + if (local_auth_data is None or local_auth_data != auth_data): + conversation.set_state(self.states.changed) + conversation.set_local(_authentication_data=auth_data, + scope=scope) def clear_changed(self): """Provide a convenient method to clear the .changed relation""" - self.remove_state('{relation_name}.changed') + conversation = self.conversation() + conversation.remove_state(self.states.changed) @property def name(self): @@ -148,13 +166,12 @@ class ManilaPluginProvides(reactive.RelationBase): The format of the data is: { "complete": , - '': { - '
: ( - (key, value), - (key, value), - ) + '': "" } + Note that the string for the should be suitable for + replacing/adding into the configuration file specified. + Thus data has to be JSONable. :param data: object that describes the plugin data to be sent. diff --git a/requires.py b/requires.py index 8a627dc..c9fbccb 100644 --- a/requires.py +++ b/requires.py @@ -21,19 +21,14 @@ import charms.reactive as reactive class ManilaPluginRequires(reactive.RelationBase): """The is the Manila 'end' of the relation. - The auto accessors are underscored as for some reason RelationBase only - provides these as 'calls'; i.e. they have to be used as `self._name()`. - This class therefore provides @properties `name` and `plugin_data` that can - be used directly. + The auto accessors are underscored as RelationBase only provides these as + 'calls'; i.e. they have to be used as `self._name()`. This class therefore + provides @properties `name` and `plugin_data` that can be used directly. This side of the interface sends the manila service user authentication information to the plugin charm (which is a subordinate) and gets configuration segments for the various files that the manila charm 'owns' and, therefore, writes out. - - The most important property is the 'ready' property which indicates that - the configuration data on the interface is valid and thus can be written to - the configuration files by the manila charm. """ scope = reactive.scopes.UNIT @@ -41,6 +36,11 @@ class ManilaPluginRequires(reactive.RelationBase): # with a basic documentation string provided. auto_accessors = ['_name', '_configuration_data'] + class states(reactive.bus.StateList): + connected = reactive.bus.State('{relation_name}.connected') + available = reactive.bus.State('{relation_name}.available') + changed = reactive.bus.State('{relation_name}.changed') + @reactive.hook('{requires:manila-plugin}-relation-joined') def joined(self): """At least one manila-plugin has joined. Thus we set the connected @@ -48,7 +48,8 @@ class ManilaPluginRequires(reactive.RelationBase): We also update the status, as this may or may not be another plugin. """ - self.set_state('{relation_name}.connected') + conversation = self.conversation() + conversation.set_state(self.states.connected) self.update_status() @reactive.hook('{requires:manila-plugin}-relation-changed') @@ -89,7 +90,7 @@ class ManilaPluginRequires(reactive.RelationBase): count_conversations += 1 # try to see if we've already had this conversation conversation_available = self.get_local( - '_available', default=None, scope=conversation.scope) + '_available', default=False, scope=conversation.scope) name = self.get_remote( '_name', default=None, scope=conversation.scope) configuration_data = self.get_remote( @@ -108,18 +109,19 @@ class ManilaPluginRequires(reactive.RelationBase): # now update the relation states to convey what is happening. if count_changed: - self.set_state('{relation_name}.changed') + self.set_state(self.states.changed) if count_available: - self.set_state('{relation_name}.available') + self.set_state(self.states.available) else: - self.remove_state('{relation_name}.available') + self.remove_state(self.states.available) if not count_conversations: - self.remove_state('{relation_name}.connected') - self.remove_state('{relation_name}.changed') + self.remove_state(self.states.connected) + self.remove_state(self.states.changed) def clear_changed(self): """Provide a convenient method to clear the .changed relation""" - self.remove_state('{relation_name}.changed') + conversation = self.conversation() + conversation.remove_state(self.states.changed) def set_authentication_data(self, value, name=None): """Set the authentication data to the plugin charm. This is to enable @@ -201,35 +203,60 @@ class ManilaPluginRequires(reactive.RelationBase): def get_configuration_data(self, name=None): """Return the configuration_data from the plugin if it is available. - The format of the data returned is: - { - '': { - '
: ( - (key, value), - (key, value), - "or string", - ) - } - - if 'name' is provided, then only the configuration data for that name + If 'name' is provided, then only the configuration data for that name is returned, otherwise all of the configuration data for all conversations is returned as an amalgamated dict. + Note, that multiple backends are supported through this one interface. + so this function needs to potentially return all of the results for all + of the backends, which also may be wanting to write configuration to + the same configuration file. + + This is for the files that the manila charm owns. If a configuration + charm has its own files, not managed by the manila charm, then it + doesn't (and shouldn't) send them over this interface -- it should just + write them locally. + + Each backend sends it's data in the following format: + + { + "": , + "": + } + + This function amalgamates the data from multiple backends by using the + name of the backend as the key to a dictionary: + + { + "": { + "": , + "": + }, + "": { + "": , + }, + } + + NOTE: this function will only return results if the subordinate sets + the _name parameter. Otherwise, it will not return anything. + :param name: OPTIONAL: specify the name of the interface (_name) - :returns: data object that was passed. + :returns: data object described above """ result = {} for conversation in self.conversations(): if conversation.scope is None: # the conversation has gone away; ignore it continue - if name: - _name = self.get_remote('_name', default=None, - scope=conversation.scope) - if _name != name: - continue - config = self.get_remote('_configuration_data', default=None, + _name = self.get_remote('_name', default=None, + scope=conversation.scope) + # if name is not None then check to see if this is the one that is + # wanted. + if name and _name != name: + continue + config = self.get_remote('_configuration_data', + default=None, scope=conversation.scope) - if config: - result.update(json.loads(config)["data"]) + if _name and config: + result[_name] = json.loads(config)["data"] return result