<html><head><style type='text/css'>p { margin: 0; }</style></head><body><div style='font-family: times new roman,new york,times,serif; font-size: 12pt; color: #000000'>Hi guys,<br><br>it's been a while but here comes the latest revision of UI Plugins proof-of-concept patch (please find it attached).<br><br>This revision was originally meant to focus solely on server-side components of the plugin infrastructure. However, I ended up implementing all the major concepts and ideas as discussed on engine-devel mailing list, impacting both client-side and server-side parts of the plugin infrastructure. As a result, UI plugin infrastructure should be pretty much complete now, so we can focus on specific plugin API features in upcoming PoC revisions.<br><br>There's a whole bunch of changes and improvements in this revision, so I'll try to cover all the relevant parts step by step. If you have any comments, questions or ideas, please let me know!<br><br>So here we go... (or if you just want to get the patch, find the link at the end of this message)<br><br><hr style="width: 100%; height: 2px;"><br><strong>0. Added new Engine configuration values</strong><br style="font-weight: bold;"><br><u>UI plugin data path</u> is represented by <em>ConfigValues.UIPluginDataPath</em> enum option ("UIPluginDataPath" in <span style="font-style: italic;">vdc_options</span> table), and resolved relative to <span style="font-style: italic;">ConfigValues.DataDir</span> if possible. Following (default) values:<br><ul><li><span style="font-style: italic;">UIPluginDataPath = ui-plugins</span></li><li><span style="font-style: italic;">DataDir = /usr/share/ovirt-engine<br></span></li></ul>result in UI plugin data path: <span style="font-style: italic;">/usr/share/ovirt-engine/ui-plugins</span><br><br><span style="text-decoration: underline;">UI plugin config path</span> is represented by <span style="font-style: italic;">ConfigValues.UIPluginConfigPath</span> enum option ("UIPluginConfigPath" in <span style="font-style: italic;">vdc_options</span> table), and resolved relative to <span style="font-style: italic;">ConfigValues.ConfigDir</span> if possible. Following (default) values:<br><ul><li><span style="font-style: italic;">UIPluginConfigPath = ui-plugins</span></li><li><span style="font-style: italic;">ConfigDir = /etc/ovirt-engine</span></li></ul><p>result in UI plugin config path: <span style="font-style: italic;">/etc/ovirt-engine/</span><span style="font-style: italic;">ui-plugins</span></p><br><span style="font-weight: bold;">1. Processing UI plugin data on the server</span><br style="font-weight: bold;"><br><span style="font-style: italic;">PluginDataManager</span> is the class responsible for reading, validating and caching UI plugin descriptor/configuration data on the server (Engine). It has two main responsibilities:<br><ul><li>return a snapshot of currently valid plugin data (<span style="font-style: italic;">getCurrentData</span> method)<br></li><li>reload plugin data from local file system if necessary (<span style="font-style: italic;">reloadData</span> method)</li></ul><p></p><p>The <span style="font-style: italic;">reloadData</span> method doesn't modify "live" plugin data directly. Instead, it creates a local working copy of current plugin data, updates this copy as it reads/validates plugin descriptor and configuration files, and attempts to update "live" plugin data through conditional reference re-assignment (using <span style="font-style: italic;">java.util.concurrent.atomic.AtomicReference.compareAndSet</span> method).</p><p><br></p><p>In other words, <span style="font-style: italic;">reloadData</span> method makes no attempts with regard to Java lock-based synchronization, in favor of dealing with "live" data through <span style="font-style: italic;">AtomicReference</span> (reference that involves atomic <span style="font-style: italic;">volatile</span> reads and writes):</p><ul><li> In the best case, a thread will succeed in updating "live" data (<span style="font-style: italic;">AtomicReference.compareAndSet</span> == true), which means that "live" data remained unchanged since this thread acquired a reference of current plugin data.</li><li>In the worst case, a thread will NOT succeed in updating "live" data (<span style="font-style: italic;">AtomicReference.compareAndSet</span> == false), which means that "live" data was already changed by another thread since this thread acquired a reference of current plugin data.</li></ul><p>In my opinion, when dealing with external resources like the local file system, this is a good compromise between performance and up-to-date data. While we might not get "completely-up-to-date" data at the given point in time (<span style="font-style: italic;">reloadData + </span><span style="font-style: italic;">getCurrentData</span>), we are guaranteed to get "recently-up-to-date" and consistent data. In other words, the requirement of "completely-up-to-date" data would involve <span style="font-style: italic;">synchronized</span> statements that would hurt performance. In my (very humble) opinion, the benefit of having "completely-up-to-date" data, at the cost of reduced performance, is not really worth it, especially in our case when the user can just hit refresh (F5) to reload WebAdmin and its plugin data.<br></p><p><br></p><p><span style="text-decoration: underline;">Plugin descriptor files</span> are expected to be placed in UI plugin data path, for example: <span style="font-style: italic;">/usr/share/ovirt-engine/ui-plugins/foo.json</span></p><p><br></p><p>Following descriptor file attributes are implemented and recognized by the plugin infrastructure:</p><ul><li><span style="font-style: italic;">name</span>: A name that uniquely identifies the plugin (required attribute).</li><li><span style="font-style: italic;">url</span>: URL of plugin host page that invokes the plugin code (required attribute).</li><li><span style="font-style: italic;">config</span>: Default configuration object associated with the plugin (optional attribute).</li><li><span style="font-style: italic;">resourcePath</span>: Path to plugin static resources, relative to UI plugin data path (optional attribute). This is used when serving plugin files through Engine <span style="font-style: italic;">PluginResourceServlet</span> (more on this below).<br></li></ul><p></p><p><span style="text-decoration: underline;">Plugin configuration files</span> are expected to be placed in UI plugin config path, for example: <span style="font-style: italic;">/etc/engine/ui-plugins/foo-config.json</span><br></p><p><br></p><p>Note that plugin configuration files follow the "<descriptorFileName>-config.json" convention.</p><p><br></p><p>Following configuration file attributes are implemented and recognized by the plugin infrastructure:</p><ul><li><span style="font-style: italic;">config</span>: Custom configuration object associated with the plugin (optional attribute). This overrides the default plugin descriptor configuration, if any.</li><li><span style="font-style: italic;">enabled</span>: Indicates whether the plugin should be loaded on WebAdmin startup (optional attribute). Default value is 'true'.</li><li><span style="font-style: italic;">order</span>: Defines the relative order in which the plugin will be loaded on WebAdmin startup (optional attribute). Default value is <span style="font-style: italic;">Integer.MAX_VALUE</span> (lowest order).</li></ul><p>The concept of merging custom configuration (<span style="font-style: italic;">config</span> attribute in <span style="font-style: italic;">foo-config.json</span>), if any, on top of default configuration (<span style="font-style: italic;">config</span> attribute in <span style="font-style: italic;">foo.json</span>), if any, remains unchanged. This makes the plugin configuration quite flexible - in my opinion, the added complexity of handling/merging such configuration is definitely worth the effort.<br></p><p><br></p><p>The <span style="font-style: italic;">enabled</span> attribute is straight-forward, allowing users to turn the given plugin off, if necessary. In future, users should still be able to load such plugins through WebAdmin GUI.<br></p><p><br></p><p>The <span style="font-style: italic;">order</span> attribute controls the order in which plugins are loaded on WebAdmin startup. Since plugin resources are fetched asynchronously by the browser, this is basically a way of imposing some degree of determinism in the "generally-non-deterministic" plugin environment, which is helpful when troubleshooting problems with multiple plugins. This attribute is also helpful due to file listing methods in <span style="font-style: italic;">java.io.File</span> giving no guarantees that files would be listed in any particular order (otherwise we could just go for the "NN-<descriptorFileName>.json" convention, with NN being the order number).<br></p><br><span style="font-weight: bold;">2. Modified behavior of WebadminDynamicHostingServlet</span><br style="font-weight: bold;"><br><span style="font-style: italic;">WebadminDynamicHostingServlet</span> is the servlet used to serve WebAdmin application host page (HTML page that bootstraps WebAdmin JavaScript code).<br><br>In addition to its former behavior, as part of handling the given request, <span style="font-style: italic;">WebadminDynamicHostingServlet</span>:<br><ul><li><span style="font-style: italic;"></span>reloads descriptor/configuration data from local file system if necessary, and obtains a snapshot of currently valid plugin data (<span style="font-style: italic;">PluginDataManager.reloadAndGetCurrentData</span>)</li><li>embeds all plugin meta-data, suitable for use in client-side plugin infrastructure, into WebAdmin host page as "pluginDefinitions" JavaScript array (<span style="font-style: italic;">PluginDefinitions</span>)<br></li></ul>As a result, reloading UI plugin descriptor/configuration data is as simple as refreshing (F5) WebAdmin application in the browser (no need to restart Engine).<br><br><span style="font-weight: bold;">3. Added servlet for serving plugin static resources</span><br style="font-weight: bold;"><br><span style="font-style: italic;">PluginResourceServlet</span> is the servlet used to serve UI plugin static files (plugin host page, 3rd party JavaScript, etc.) from the local file system.<br><br>For example, requesting URL:<br><ul><li><span style="font-style: italic;">http://<EngineManagerHost>:8700/webadmin/webadmin/plugin/foo/content/start.html</span></li></ul><p>will send the content of:</p><ul><li><span style="font-style: italic;">/usr/share/ovirt-engine/ui-plugins/<resourcePath>/</span><span style="font-style: italic;">content/start.html</span></li></ul><p>to the client.</p><p><br></p><p>As shown in the above example:</p><ul><li><span style="font-style: italic;">/webadmin/webadmin/plugin/</span> is the servlet root path for <span style="font-style: italic;">PluginResourceServlet</span><br></li><li>in the extra path beyond the servlet root path (<span style="font-style: italic;">/foo/content/start.html</span>):<span style="white-space:pre"></span></li><ul><li><span style="font-style: italic;">/foo</span> represents the name of the plugin</li><li><span style="font-style: italic;">/content/start.html</span> represents the path to requested resource, relative to "UIPluginDataPath / <resourcePath>"<br></li></ul></ul>Note that each plugin using <span style="font-style: italic;">PluginResourceServlet</span> to serve its static files must declare non-empty <span style="font-style: italic;">resourcePath</span> attribute in within the plugin descriptor.<br><br>Also note that <span style="font-style: italic;">PluginResourceServlet</span>, unlike <span style="font-style: italic;">WebadminDynamicHostingServlet</span>, does NOT reload descriptor/configuration data from local file system as part of handling the given request. In other words, it's assumed that plugin data has already been (re)loaded when serving WebAdmin application host page, with subsequent requests to <span style="font-style: italic;">PluginResourceServlet</span> reading the current plugin information.<br><br>Until we solve the cross-origin issue in a clean way, <span style="font-style: italic;">PluginResourceServlet</span> should be used to serve all plugin resources from local file system.<br><br><span style="font-weight: bold;">4. Plugin lifecycle improved to deal with misbehaving plugins</span><br><br><span style="font-style: italic;">PluginState</span> enum has been modified to deal with plugins that allow uncaught exceptions to escape from plugin event handler functions (e.g. "UiInit"):<br><ul><li>removed state <span style="font-style: italic;">INITIALIZED</span></li><li>added state <span style="font-style: italic;">INITIALIZING</span>: The plugin is (currently) being initialized by calling UiInit event handler function.</li><li>added state <span style="font-style: italic;">IN_USE</span>: Plugin's UiInit event handler function has completed successfully, we can now call other event handler functions as necessary. The plugin is in use now.</li><li>added state <span style="font-style: italic;">FAILED</span>: An uncaught exception escaped while calling an event handler function, which indicates internal error within the plugin code. The plugin is removed from service.<br></li></ul>I've attached a simple state diagram that illustrates different states and transitions between them (green color is initial state, red color is end state).<br><br>Uncaught exceptions in plugin event handler functions will be caught and handled by the plugin infrastructure. This prevents a misbehaving plugin from breaking WebAdmin application, since WebAdmin is the caller (initiator) of the function call. In such case, the plugin will be removed from service.<br><br><span style="font-weight: bold;">Update on cross-origin issue (consequence of same-origin policy)</span><br><br>In order for the plugin to access WebAdmin plugin API, plugin host page (e.g. <span style="font-style: italic;">start.html</span>) must be served from URL on same origin as Engine origin. Otherwise, plugin code running in the context of an iframe'd host page will fail to evaluate "parent.pluginApi" expression, with "parent" being top-level (WebAdmin) window, and "pluginApi" being the global plugin API object exposed by WebAdmin.<br><br>This is why <span style="font-style: italic;">PluginResourceServlet</span>, available on Engine origin, should be used to serve all plugin resources from local file system.<br><br>There's only one issue that remains to be solved: cross-origin "plugin vs. remote service" communication, with "remote service" being anything other than Engine (REST API). In future, we'll address this with Apache reverse proxy configuration, so that users can configure Apache server (placed in front of Engine JBoss AS) to put arbitrary (local or remote non-Engine) services on same origin. However, this requires a change in current Apache configuration. Until then, users can manually edit the Engine Apache configuration file (<span style="font-style: italic;">/etc/httpd/conf.d/ovirt-engine.conf</span>).<br><br><span style="font-weight: bold;"></span><hr style="width: 100%; height: 2px;"><br>I've attached some sample plugin files for you to experiment with. Instead of attaching actual patch file (92 kB) to this email, I've submitted the patch to oVirt Gerrit: http://gerrit.ovirt.org/8120<br><br>Let me know what you think!<br><br>Cheers,<br>Vojtech<br><br></div></body></html>