Michael Pasternak has uploaded a new change for review. Change subject: cli: introducing event driven messaging ......................................................................
cli: introducing event driven messaging Change-Id: I110a2b7d25a03ed1f256f24058f3c54535e27100 Signed-off-by: Michael pasternak <[email protected]> --- A src/ovirtcli/annotations/__init__.py A src/ovirtcli/annotations/requires.py A src/ovirtcli/events/__init__.py A src/ovirtcli/events/abstractevent.py A src/ovirtcli/events/event.py A src/ovirtcli/events/ievent.py A src/ovirtcli/listeners/__init__.py A src/ovirtcli/listeners/abstractlistener.py A src/ovirtcli/listeners/errorlistener.py A src/ovirtcli/listeners/ilistener.py M src/ovirtcli/shell/cmdshell.py M src/ovirtcli/shell/engineshell.py 12 files changed, 400 insertions(+), 31 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine-cli refs/changes/21/20321/1 diff --git a/src/ovirtcli/annotations/__init__.py b/src/ovirtcli/annotations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ovirtcli/annotations/__init__.py diff --git a/src/ovirtcli/annotations/requires.py b/src/ovirtcli/annotations/requires.py new file mode 100644 index 0000000..0f66499 --- /dev/null +++ b/src/ovirtcli/annotations/requires.py @@ -0,0 +1,48 @@ +# +# Copyright (c) 2013 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +class Requires(object): + """ + Checks that method arg is of a given type + """ + + def __init__(self, typ): + """ + Checks that method arg is of a given type + + @param typ: the type to validate against + """ + assert typ + self.typ = typ + + def __call__(self, original_func): + decorator_self = self + def wrappee(*args, **kwargs): + self.__check_list( + args[1], + decorator_self.typ + ) + return original_func(*args, **kwargs) + return wrappee + + def __check_list(self, candidate, typ): + if not isinstance(candidate, typ): + raise TypeError( + "%s instance is expected." + % + typ.__name__ + ) diff --git a/src/ovirtcli/events/__init__.py b/src/ovirtcli/events/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ovirtcli/events/__init__.py diff --git a/src/ovirtcli/events/abstractevent.py b/src/ovirtcli/events/abstractevent.py new file mode 100644 index 0000000..f7bbb87 --- /dev/null +++ b/src/ovirtcli/events/abstractevent.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2013 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from ovirtcli.events.ievent import IEvent +from abc import ABCMeta + +class AbstractEvent(IEvent): + ''' + The abstract event class + ''' + + __metaclass__ = ABCMeta + + def __init__(self): + self.__id = id(self) diff --git a/src/ovirtcli/events/event.py b/src/ovirtcli/events/event.py new file mode 100644 index 0000000..ae3d2f4 --- /dev/null +++ b/src/ovirtcli/events/event.py @@ -0,0 +1,61 @@ +# +# Copyright (c) 2013 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from ovirtcli.annotations.requires import Requires +from ovirtcli.listeners.ilistener import IListener +from ovirtcli.events.abstractevent import AbstractEvent + +class Event(AbstractEvent): + ''' + The event class implementation + ''' + + def __init__(self): + self.__listeners = [] + self.__id = id(self) + + @Requires(IListener) + def __iadd__(self, listener): + """ + adds event IListener + + @param listener: listener to register + """ + assert listener != None + self.__listeners.append(listener) + return self + + @Requires(IListener) + def __isub__(self, listener): + """ + removes event IListener + + @param listener: listener to unregister + """ + assert listener != None + self.__listeners.remove(listener) + return self + + def fire(self, *args, **keywargs): + ''' + event listeners notification + + @param args: a list o args + @param kwargs: a list o kwargs + ''' + for listener in self.__listeners: + listener.onEvent(*args, **keywargs) diff --git a/src/ovirtcli/events/ievent.py b/src/ovirtcli/events/ievent.py new file mode 100644 index 0000000..3433147 --- /dev/null +++ b/src/ovirtcli/events/ievent.py @@ -0,0 +1,58 @@ +# +# Copyright (c) 2013 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from ovirtcli.annotations.requires import Requires +from ovirtcli.listeners.ilistener import IListener + +from abc import ABCMeta, abstractmethod + +class IEvent(object): + ''' + The event interface + ''' + + __metaclass__ = ABCMeta + + @abstractmethod + @Requires(IListener) + def __iadd__(self, listener): + """ + adds event IListener + + @param listener: listener to register + """ + raise NotImplementedError + + @abstractmethod + @Requires(IListener) + def __isub__(self, listener): + """ + removes event IListener + + @param listener: listener to unregister + """ + raise NotImplementedError + + @abstractmethod + def fire(self, *args, **keywargs): + ''' + event listeners notification + + @param args: a list o args + @param kwargs: a list o kwargs + ''' + raise NotImplementedError diff --git a/src/ovirtcli/listeners/__init__.py b/src/ovirtcli/listeners/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ovirtcli/listeners/__init__.py diff --git a/src/ovirtcli/listeners/abstractlistener.py b/src/ovirtcli/listeners/abstractlistener.py new file mode 100644 index 0000000..ad0f0bf --- /dev/null +++ b/src/ovirtcli/listeners/abstractlistener.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2010 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from ovirtcli.listeners.ilistener import IListener +from abc import ABCMeta + +class AbstractListener(IListener): + ''' + Abstract listener, listens for the events + ''' + + __metaclass__ = ABCMeta + + def __init__(self): + self.__id = id(self) diff --git a/src/ovirtcli/listeners/errorlistener.py b/src/ovirtcli/listeners/errorlistener.py new file mode 100644 index 0000000..70e3197 --- /dev/null +++ b/src/ovirtcli/listeners/errorlistener.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2010 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from ovirtcli.prompt import PromptMode + +from cli.context import ExecutionContext + +from ovirtcli.listeners.abstractlistener import AbstractListener + +class ErrorListener(AbstractListener): + ''' + Listens for the error events + ''' + + def __init__(self, shell): + """ + @param shell: EngineShell instance + """ + assert shell != None + self.__shell = shell + + def onEvent(self, *args, **kwargs): + ''' + fired when error event is raised + + @param args: a list o args + @param kwargs: a list o kwargs + ''' + + if self.__shell.context.status == \ + ExecutionContext.COMMUNICATION_ERROR: + self.__shell.owner._set_prompt( + mode=PromptMode.Disconnected + ) + elif self.__shell.context.status == \ + ExecutionContext.AUTHENTICATION_ERROR: + self.__shell.owner._set_prompt( + mode=PromptMode.Unauthorized + ) + elif self.__shell._get_last_status() <> -1 and \ + ( + self.__shell._get_last_status() == \ + ExecutionContext.COMMUNICATION_ERROR + or \ + self.__shell._get_last_status() == \ + ExecutionContext.AUTHENTICATION_ERROR + ): + self.__shell.owner._set_prompt( + mode=PromptMode.Original + ) diff --git a/src/ovirtcli/listeners/ilistener.py b/src/ovirtcli/listeners/ilistener.py new file mode 100644 index 0000000..78b0bc3 --- /dev/null +++ b/src/ovirtcli/listeners/ilistener.py @@ -0,0 +1,35 @@ +# +# Copyright (c) 2010 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from abc import ABCMeta, abstractmethod + +class IListener(object): + ''' + The listener interface + ''' + + __metaclass__ = ABCMeta + + @abstractmethod + def onEvent(self, *args, **kwargs): + ''' + raised when state change event occurs + + @param args: a list o args + @param kwargs: a list o kwargs + ''' + raise NotImplementedError diff --git a/src/ovirtcli/shell/cmdshell.py b/src/ovirtcli/shell/cmdshell.py index 7ed5956..5a21781 100644 --- a/src/ovirtcli/shell/cmdshell.py +++ b/src/ovirtcli/shell/cmdshell.py @@ -15,9 +15,10 @@ # import sys import os -from ovirtcli.utils.typehelper import TypeHelper -from ovirtsdk.infrastructure import brokers + import itertools + +from ovirtcli.utils.typehelper import TypeHelper from ovirtcli.settings import OvirtCliSettings diff --git a/src/ovirtcli/shell/engineshell.py b/src/ovirtcli/shell/engineshell.py index 0efc20d..2a265cf 100644 --- a/src/ovirtcli/shell/engineshell.py +++ b/src/ovirtcli/shell/engineshell.py @@ -40,16 +40,17 @@ from ovirtcli.shell.infocmdshell import InfoCmdShell from ovirtcli.shell.capabilitiescmdshell import CapabilitiesCmdShell -from ovirtcli.settings import OvirtCliSettings -from ovirtcli.prompt import PromptMode - from cli.command.help import HelpCommand from cli.messages import Messages +from cli.executionmode import ExecutionMode from urlparse import urlparse from ovirtcli.utils.colorhelper import ColorHelper -from cli.executionmode import ExecutionMode +from ovirtcli.events.event import Event +from ovirtcli.listeners.errorlistener import ErrorListener +from ovirtcli.settings import OvirtCliSettings +from ovirtcli.prompt import PromptMode class EngineShell(cmd.Cmd, ConnectCmdShell, ActionCmdShell, \ ShowCmdShell, ListCmdShell, UpdateCmdShell, \ @@ -79,20 +80,31 @@ SummaryCmdShell.__init__(self, context, parser) CapabilitiesCmdShell.__init__(self, context, parser) - self.last_output = '' + self.onError = Event() + self.onInit = Event() + self.onExit = Event() + self.onPromptChange = Event() + self.onSigInt = Event() + + self.__last_output = '' self.__input_buffer = '' self.__org_prompt = '' self.__last_status = -1 - self._set_prompt(mode=PromptMode.Disconnected) + self.__register_sys_listeners() + self.__init_promt() + cmd.Cmd.doc_header = self.context.settings.get('ovirt-shell:commands') cmd.Cmd.undoc_header = self.context.settings.get('ovirt-shell:misc_commands') cmd.Cmd.intro = OvirtCliSettings.INTRO readline.set_completer_delims(' ') - signal.signal(signal.SIGINT, self.handler) + signal.signal(signal.SIGINT, self.__handler) - ########################### SYSTEM ################################# + self.onInit.fire() + + ############################ SHELL ################################## + def cmdloop(self, intro=None, clear=True): try: if clear: self.do_clear('') @@ -135,19 +147,45 @@ self.__input_buffer = '' self._set_prompt(mode=PromptMode.Original) return cmd.Cmd.onecmd(self, s) - finally: # if communication error occurred, change prompt state - if self.context.status == self.context.COMMUNICATION_ERROR: + finally: + if self.context.status == \ + self.context.SYNTAX_ERROR \ + or \ + self.context.status == \ + self.context.COMMAND_ERROR \ + or \ + self.context.status == \ + self.context.UNKNOWN_ERROR: + self.onError.fire() + elif self.context.status == \ + self.context.COMMUNICATION_ERROR: self.__last_status = self.context.status - self.owner._set_prompt(mode=PromptMode.Disconnected) - elif self.context.status == self.context.AUTHENTICATION_ERROR: + self.onError.fire() + elif self.context.status == \ + self.context.AUTHENTICATION_ERROR: self.__last_status = self.context.status - self.owner._set_prompt(mode=PromptMode.Unauthorized) - elif self.__last_status == self.context.COMMUNICATION_ERROR or \ - self.__last_status == self.context.AUTHENTICATION_ERROR: + self.onError.fire() + elif self.__last_status <> -1 and \ + ( + self.__last_status == \ + self.context.COMMUNICATION_ERROR + or \ + self.__last_status == \ + self.context.AUTHENTICATION_ERROR + ): + self.onError.fire() self.__last_status = -1 - self.owner._set_prompt(mode=PromptMode.Original) + + ########################### SYSTEM ################################# + + def __register_sys_listeners(self): + self.onError += ErrorListener(self) + + def __init_promt(self): + self._set_prompt(mode=PromptMode.Disconnected) def _set_prompt(self, mode=PromptMode.Default): + self.onPromptChange.fire() if mode == PromptMode.Multiline: if not self.__org_prompt: self.__org_prompt = self.prompt @@ -225,7 +263,7 @@ return cprompt - def __persistCmdOptions(self, opts): + def __persist_cmd_options(self, opts): """ Overrides config file options with cmdline's. """ @@ -238,10 +276,13 @@ self.context.settings['cli:' + k] = v + def _get_last_status(self): + return self.__last_status + def onecmd_loop(self, s): opts, args = self.parser.parse_args() del args - self.__persistCmdOptions(opts) + self.__persist_cmd_options(opts) if opts.connect or self.context.settings.get('cli:autoconnect'): self.do_clear('') self.do_connect(s) @@ -348,10 +389,19 @@ return None def _error(self, msg): + # this is a custom error, consumer responsibility + # to filter out duplicate calls on event + self.onError.fire() self.context._handle_exception(SyntaxError(msg)) def _print(self, msg): self.context._pint_text(msg) + + def __handler(self, signum, frame): + self.onSigInt.fire() + raise KeyboardInterrupt + + ############################# COMMANDS ################################# def do_EOF(self, line): """\ @@ -367,6 +417,7 @@ Ctrl+D """ + self.onExit.fire() self.emptyline(no_prompt=True) return True @@ -384,7 +435,7 @@ exit """ - + self.onExit.fire() sys.exit(0) def do_help(self, args): @@ -401,7 +452,6 @@ help show """ - if not args: self.context.execute_string('help\n') else: @@ -413,7 +463,7 @@ self.context.terminal.stdout.write('\n' + getattr(self, 'do_' + cmd).__doc__ + '\n') else: return self.context.execute_string('help ' + args + '\n') - ############################# SHELL ################################# + def do_shell(self, line): """\ == Usage == @@ -432,10 +482,9 @@ ! ls -la """ - output = os.popen(line).read() print output - self.last_output = output + self.__last_output = output def do_echo(self, line): """\ @@ -455,12 +504,8 @@ echo str """ - - if self.last_output: - print line.replace('$out', self.last_output) + if self.__last_output: + print line.replace('$out', self.__last_output) elif line: print line else: print self.prompt - ############################## COMMON ################################ - def handler(self, signum, frame): - raise KeyboardInterrupt -- To view, visit http://gerrit.ovirt.org/20321 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I110a2b7d25a03ed1f256f24058f3c54535e27100 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine-cli Gerrit-Branch: master Gerrit-Owner: Michael Pasternak <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
