[Kimchi-devel] [PATCH] [Wok] Add federation feature to Wok

Aline Manera posted 1 patch 7 years, 6 months ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/kimchi tags/patchew/20170502165912.27169-1-alinefm@linux.vnet.ibm.com
docs/API/config.md               |  2 ++
docs/API/peers.md                | 20 +++++++++++
docs/README-federation.md        | 60 ++++++++++++++++++++++++++++++++
docs/wokd.8.in                   |  6 +++-
src/wok.conf.in                  |  4 +++
src/wok/config.py.in             |  1 +
src/wok/control/peers.py         | 30 ++++++++++++++++
src/wok/model/config.py          |  1 +
src/wok/model/peers.py           | 74 +++++++++++++++++++++++++++++++++++++++
src/wokd.in                      |  5 +++
tests/test_api.py                |  6 +++-
tests/test_config_model.py       |  2 +-
tests/test_server_root.py        |  6 ++--
ui/js/src/wok.api.js             | 14 ++++++++
ui/js/wok.peers.js               | 75 ++++++++++++++++++++++++++++++++++++++++
ui/pages/tabs/settings.html.tmpl | 34 +++++++++++++++++-
16 files changed, 333 insertions(+), 7 deletions(-)
create mode 100644 docs/API/peers.md
create mode 100644 docs/README-federation.md
create mode 100644 src/wok/control/peers.py
create mode 100644 src/wok/model/peers.py
create mode 100644 ui/js/wok.peers.js
[Kimchi-devel] [PATCH] [Wok] Add federation feature to Wok
Posted by Aline Manera 7 years, 6 months ago
It was primarily implemented on Kimchi project. But it makes more sense
to be on Wok since the idea is to allow user add/select on which server
Wok should manage.
For example, the user could have a Wok installation on server A to
manage server B.

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com>
---
 docs/API/config.md               |  2 ++
 docs/API/peers.md                | 20 +++++++++++
 docs/README-federation.md        | 60 ++++++++++++++++++++++++++++++++
 docs/wokd.8.in                   |  6 +++-
 src/wok.conf.in                  |  4 +++
 src/wok/config.py.in             |  1 +
 src/wok/control/peers.py         | 30 ++++++++++++++++
 src/wok/model/config.py          |  1 +
 src/wok/model/peers.py           | 74 +++++++++++++++++++++++++++++++++++++++
 src/wokd.in                      |  5 +++
 tests/test_api.py                |  6 +++-
 tests/test_config_model.py       |  2 +-
 tests/test_server_root.py        |  6 ++--
 ui/js/src/wok.api.js             | 14 ++++++++
 ui/js/wok.peers.js               | 75 ++++++++++++++++++++++++++++++++++++++++
 ui/pages/tabs/settings.html.tmpl | 34 +++++++++++++++++-
 16 files changed, 333 insertions(+), 7 deletions(-)
 create mode 100644 docs/API/peers.md
 create mode 100644 docs/README-federation.md
 create mode 100644 src/wok/control/peers.py
 create mode 100644 src/wok/model/peers.py
 create mode 100644 ui/js/wok.peers.js

diff --git a/docs/API/config.md b/docs/API/config.md
index d1d1007..0e47f5f 100644
--- a/docs/API/config.md
+++ b/docs/API/config.md
@@ -12,6 +12,8 @@ Contains information about the application environment and configuration.
     * proxy_port: SSL port to list on
     * websockets_port: Port for websocket proxy to listen on
     * auth: Authentication method used to log in to Wok
+    * server_root: Relative path to Wok server. No value is specified by default, ie, Wok will run on '/'
+    * federation: 'on' if federation feature is enabled, 'off' otherwise.
     * version: Wok version
 * **POST**: *See Task Actions*
 
diff --git a/docs/API/peers.md b/docs/API/peers.md
new file mode 100644
index 0000000..fa5cf41
--- /dev/null
+++ b/docs/API/peers.md
@@ -0,0 +1,20 @@
+## REST API Specification for Peers
+
+### Collection: Peers
+
+**URI:** /peers
+
+Return a list of Wok peers in the same network.
+(It uses openSLP for discovering)
+
+**Methods:**
+
+* **GET**: Retrieve a list peers URLs.
+
+#### Examples
+GET /peers
+[
+ https://wok-peer0:8001,
+ https://wok-peer1:8001,
+ https://wok-peer2:8001,
+]
diff --git a/docs/README-federation.md b/docs/README-federation.md
new file mode 100644
index 0000000..18e19c7
--- /dev/null
+++ b/docs/README-federation.md
@@ -0,0 +1,60 @@
+Wok Project - Federation Feature
+===================================
+
+Federation feature is a mechanism to discover Wok peers in the same
+network. It uses openSLP tool (http://www.openslp.org/) to register and find Wok
+servers.
+
+By default this feature is disabled on Wok as it is not critical for KVM
+virtualization and requires additional software installation.
+
+To enable it, do the following:
+
+1. Install openslp and openslp-server rpm packages,
+   or install slpd and slptool deb packages.
+
+2. openSLP uses port 427 (UDP) and port 427 (TCP) so make sure to open those
+   ports in your firewall configuration
+
+   For system using firewalld, do:
+   sudo firewall-cmd --permanent --add-port=427/udp
+   sudo firewall-cmd --permanent --add-port=427/tcp
+   sudo firewall-cmd --reload
+
+   For openSUSE systems, do:
+   sudo /sbin/SuSEfirewall2 open EXT TCP 427
+   sudo /sbin/SuSEfirewall2 open EXT UDP 427
+
+   For system using iptables, do:
+   sudo iptables -A INPUT -p tcp --dport 427 -j ACCEPT
+   sudo iptables -A INPUT -p udp --dport 427 -j ACCEPT
+
+3. In addition to the openSLP ports, you also need to allow multicast in the
+   firewall configuration
+
+   For system using firewalld, do:
+   sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -s <subnet> -j ACCEPT
+
+   For openSUSE systems, do:
+   Add the subnet to the trusted networks listed on FW_TRUSTED_NETS in
+   /etc/sysconfig/SuSEfirewall2 file.
+   Make sure to restart /sbin/SuSEfirewall2 after modifying /etc/sysconfig/SuSEfirewall2
+
+   For system using iptables, do:
+   sudo iptables -A INPUT -s <subnet> -j ACCEPT
+
+4. Start slpd service and make sure it is up while running Wok
+   sudo service slpd start
+
+5. Enable federation on Wok by editing the /etc/wok/wok.conf file:
+
+   federation = on
+
+6. Then start Wok service
+   sudo service wokd start
+
+The Wok server will be registered on openSLP on server starting up and will
+be found by other Wok peers (with federation feature enabled) in the same
+network.
+
+Enjoy!
diff --git a/docs/wokd.8.in b/docs/wokd.8.in
index 217d9f6..09b5847 100644
--- a/docs/wokd.8.in
+++ b/docs/wokd.8.in
@@ -7,7 +7,7 @@ wokd - Launch Wok web server
 [\fB--cherrypy_port\fP \fICHERRYPY_PORT\fP] [\fB--websockets_port\fP \fIWEBSOCKETS_PORT\fP]
 [\fB--session_timeout\fP \fISESSION_TIMEOUT\fP] [\fB--log_level\fP \fILOG_LEVEL\fP]
 [\fB--log_dir\fP \fILOG_DIR\fP] [\fB--environment\fP \fIENV\fP]
-[\fB--server_root\fP \fISERVER_ROOT\fP] [\fB--test\fP]
+[\fB--server_root\fP \fISERVER_ROOT\fP] [\fB--federation\fP \fIfederation\fP] [\fB--test\fP]
 .SH DESCRIPTION
 \fBWok\fP is a cherrypy-based web framework with HTML5 support originated from Kimchi.
 It can be extended by plugins which expose functionality through REST APIs.
@@ -55,6 +55,10 @@ Check cherrypy documentation for more details (default \fIproduction\fP).
 \fB\-\-server_root\fP [\fISERVER_ROOT\fP]
 Relative path to Wok server. No value is specified by default, ie, Wok will run on '/'.
 .TP
+\fB\-\-federation\fP [\fIon\fP | \fIoff\fP]
+Register and discover Wok peers in the same network using OpenSLP. Check
+below the \fBREAD-federation\fP for more details (default \fIoff\fP).
+.TP
 \fB\-\-test\fP
 Run Wok on a mock version that does not affect the system. For testing proposals.
 It depends on how plugins implements the mock environment as well.
diff --git a/src/wok.conf.in b/src/wok.conf.in
index 1ebdacf..1ed6310 100644
--- a/src/wok.conf.in
+++ b/src/wok.conf.in
@@ -27,6 +27,10 @@
 # uncomment the following:
 #server_root=/wok
 
+# Federation feature: register Wok server on openSLP and discover peers
+# in the same network. Check README-federation for more details.
+#federation = off
+
 [logging]
 # Log directory
 
diff --git a/src/wok/config.py.in b/src/wok/config.py.in
index bbf2c09..39d7ad2 100644
--- a/src/wok/config.py.in
+++ b/src/wok/config.py.in
@@ -277,6 +277,7 @@ def _get_config():
     config.set("server", "environment", "production")
     config.set('server', 'max_body_size', '4*1024*1024')
     config.set("server", "server_root", "")
+    config.set("server", "federation", "off")
     config.set("server", "test", "")
     config.add_section("authentication")
     config.set("authentication", "method", "pam")
diff --git a/src/wok/control/peers.py b/src/wok/control/peers.py
new file mode 100644
index 0000000..2a14815
--- /dev/null
+++ b/src/wok/control/peers.py
@@ -0,0 +1,30 @@
+#
+# Project Wok
+#
+# Copyright IBM Corp, 2017
+#
+# Code derived from Kimchi Project
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+from wok.control.base import SimpleCollection
+from wok.control.utils import UrlSubNode
+
+
+@UrlSubNode("peers", True)
+class Peers(SimpleCollection):
+    def __init__(self, model):
+        super(Peers, self).__init__(model)
+        self.admin_methods = ['GET']
diff --git a/src/wok/model/config.py b/src/wok/model/config.py
index b69f2dd..887ceba 100644
--- a/src/wok/model/config.py
+++ b/src/wok/model/config.py
@@ -34,6 +34,7 @@ class ConfigModel(object):
                 'websockets_port': config.get('server', 'websockets_port'),
                 'auth': config.get('authentication', 'method'),
                 'server_root': config.get('server', 'server_root'),
+                'federation': config.get('server', 'federation'),
                 'version': get_version()}
 
     def reload(self, name):
diff --git a/src/wok/model/peers.py b/src/wok/model/peers.py
new file mode 100644
index 0000000..02afd59
--- /dev/null
+++ b/src/wok/model/peers.py
@@ -0,0 +1,74 @@
+#
+# Project Wok
+#
+# Copyright IBM Corp, 2017
+#
+# Code derived from Kimchi Project
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+import cherrypy
+import re
+import socket
+
+from wok.config import config
+from wok.utils import run_command, wok_log
+
+
+class PeersModel(object):
+    def __init__(self, **kargs):
+        # check federation feature is enabled on Wok server
+        if not config.get('server', 'federation') == 'off':
+            return
+
+        # register server on openslp
+        hostname = socket.getfqdn()
+        port = config.get("server", "proxy_port")
+        self.url = hostname + ":" + port
+
+        cmd = ["slptool", "register",
+               "service:wokd://%s" % self.url]
+        out, error, ret = run_command(cmd)
+        if out and len(out) != 0:
+            wok_log.error("Unable to register server on openSLP."
+                          " Details: %s" % out)
+        cherrypy.engine.subscribe('exit', self._peer_deregister)
+
+    def _peer_deregister(self):
+        cmd = ["slptool", "deregister",
+               "service:wokd://%s" % self.url]
+        out, error, ret = run_command(cmd)
+        if out and len(out) != 0:
+            wok_log.error("Unable to deregister server on openSLP."
+                          " Details: %s" % out)
+
+    def get_list(self):
+        # check federation feature is enabled on Wok server
+        if not config.get('server', 'federation') == 'off':
+            return []
+
+        cmd = ["slptool", "findsrvs", "service:wokd"]
+        out, error, ret = run_command(cmd)
+        if ret != 0:
+            return []
+
+        peers = []
+        for server in out.strip().split("\n"):
+            match = re.match("service:wokd://(.*?),.*", server)
+            peer = match.group(1)
+            if peer != self.url:
+                peers.append("https://" + peer)
+
+        return peers
diff --git a/src/wokd.in b/src/wokd.in
index 29586a0..dfe85a8 100644
--- a/src/wokd.in
+++ b/src/wokd.in
@@ -48,6 +48,7 @@ def main(options):
     websockets_port = config.config.get("server", "websockets_port")
     session_timeout = config.config.get("server", "session_timeout")
     runningEnv = config.config.get("server", "environment")
+    federation = config.config.get("server", "federation")
     server_root = config.config.get("server", "server_root")
     logDir = config.config.get("logging", "log_dir")
     logLevel = config.config.get("logging", "log_level")
@@ -71,6 +72,10 @@ def main(options):
                       help="Running environment of wok server")
     parser.add_option('--server_root', type="string", default=server_root,
                       help="Relative path to server")
+    parser.add_option('--federation', default=federation,
+                      help="Register and discover Wok peers in the same "
+                           "network using openSLP. Check README-federation for"
+                           " more details.")
     parser.add_option('--test', action='store_true',
                       help="Run server in mock model")
     (options, args) = parser.parse_args()
diff --git a/tests/test_api.py b/tests/test_api.py
index 6fbee75..f978c93 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -49,11 +49,15 @@ class APITests(unittest.TestCase):
     def setUp(self):
         self.request = partial(utils.request)
 
+    def test_peers(self):
+        resp = self.request('/peers').read()
+        self.assertEquals([], json.loads(resp))
+
     def test_config(self):
         resp = self.request('/config').read()
         conf = json.loads(resp)
         keys = ["auth", "proxy_port", "websockets_port", "version",
-                "server_root"]
+                "server_root", "federation"]
         self.assertEquals(sorted(keys), sorted(conf.keys()))
 
     def test_config_plugins(self):
diff --git a/tests/test_config_model.py b/tests/test_config_model.py
index f8b0848..565b4df 100644
--- a/tests/test_config_model.py
+++ b/tests/test_config_model.py
@@ -30,7 +30,7 @@ class ConfigModelTests(unittest.TestCase):
         config = inst.config_lookup('')
         self.assertItemsEqual(
             ['proxy_port', 'websockets_port', 'auth',
-             'server_root', 'version'],
+             'server_root', 'version', 'federation'],
             config.keys()
         )
 
diff --git a/tests/test_server_root.py b/tests/test_server_root.py
index e95a13b..89f34d3 100644
--- a/tests/test_server_root.py
+++ b/tests/test_server_root.py
@@ -1,7 +1,7 @@
 #
 # Project Wok
 #
-# Copyright IBM Corp, 2016
+# Copyright IBM Corp, 2016-2017
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -49,7 +49,7 @@ class ServerRootTests(unittest.TestCase):
         # check if server_root in config is the same used to start server
         resp = request(server_root + '/config').read()
         conf = json.loads(resp)
-        self.assertEquals(len(conf), 5)
+        self.assertEquals(len(conf), 6)
 
     def test_development_env(self):
         """
@@ -61,4 +61,4 @@ class ServerRootTests(unittest.TestCase):
         # check if server_root in config is the same used to start server
         resp = request(server_root + '/config').read()
         conf = json.loads(resp)
-        self.assertEquals(len(conf), 5)
+        self.assertEquals(len(conf), 6)
diff --git a/ui/js/src/wok.api.js b/ui/js/src/wok.api.js
index 06b97aa..4e68eee 100644
--- a/ui/js/src/wok.api.js
+++ b/ui/js/src/wok.api.js
@@ -71,6 +71,20 @@ var wok = {
         });
     },
 
+    getPeers: function(suc, err) {
+        wok.requestJSON({
+            url: 'peers',
+            type: 'GET',
+            contentType: 'application/json',
+            dataType: 'json',
+            resend: true,
+            success: suc,
+            error: err ? err : function(data) {
+                wok.message.error(data.responseJSON.reason);
+            }
+        });
+    },
+
     getNotifications: function (suc, err) {
         wok.requestJSON({
             url: 'notifications',
diff --git a/ui/js/wok.peers.js b/ui/js/wok.peers.js
new file mode 100644
index 0000000..67e58b0
--- /dev/null
+++ b/ui/js/wok.peers.js
@@ -0,0 +1,75 @@
+/*
+ * Project Wok
+ *
+ * Copyright IBM Corp, 2017
+ *
+ * Code derived from Kimchi Project
+ *
+ * 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.
+ */
+
+wok.initPeers = function() {
+    var peersDatatableTable;
+    var peers = new Array();
+
+    var peersDatatable = function(nwConfigDataSet) {
+        peersDatatableTable = $('#peers-list').DataTable({
+            "processing": true,
+            "data": peers,
+            "language": {
+                "emptyTable": i18n['WOKSETT0010M']
+            },
+            "order": [],
+            "paging": false,
+            "dom": '<"row"<"col-sm-12"t>>',
+            "scrollY": "269px",
+            "scrollCollapse": true,
+            "columnDefs": [{
+                "targets": 0,
+                "searchable": false,
+                "orderable": false,
+                "width": "100%",
+                "className": "tabular-data",
+                "render": function(data, type, full, meta) {
+                    return '<a href="' + data + '" target="_blank">' + data + '</a>';
+                }
+            }],
+            "initComplete": function(settings, json) {
+                $('#peers-content-area > .wok-mask').addClass('hidden');
+            }
+        });
+    };
+
+    var getPeers = function() {
+        wok.getPeers(function(result) {
+            peers.length = 0;
+            for (var i = 0; i < result.length; i++) {
+                var tempArr = [];
+                tempArr.push(result[i]);
+                peers.push(tempArr);
+            }
+            peersDatatable(peers);
+        }, function(err) {
+            wok.message.error(err.responseJSON.reason, '#peers-alert-container', true);
+        });
+    };
+    getPeers();
+
+}
+
+if (wok.config.federation == 'on') {
+    $("#peers-accordion").removeClass('hidden');
+    wok.initPeers();
+} else {
+    $("#peers-accordion").addClass('hidden');
+}
diff --git a/ui/pages/tabs/settings.html.tmpl b/ui/pages/tabs/settings.html.tmpl
index ccc5b01..5f52839 100644
--- a/ui/pages/tabs/settings.html.tmpl
+++ b/ui/pages/tabs/settings.html.tmpl
@@ -27,6 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 <head>
   <link rel="stylesheet" type="text/css" href="$href('css/settings.css')">
   <script type="text/javascript" src="$href('js/wok.settings.js')"></script>
+  <script type="text/javascript" src="$href('js/wok.peers.js')"></script>
   <script type="text/javascript" src="$href('js/wok.bootgrid.js')"></script>
 </head>
 
@@ -34,7 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
   <div id="wok-root-container" class="wok">
     <div class="container">
       <div id="wokSettings" class="wok-settings">
-      <!-- Plugins Management -->
+        <!-- Plugins Management -->
         <div class="panel-group accordion" id="plugins-mgmt-accordion" role="tablist" aria-multiselectable="false">
           <h3>
             <a role="button" data-toggle="collapse" data-parent="#plugin-mgmt-accordion" href="#plugins-mgmt-content-area" aria-expanded="true" aria-controls="plugins-mgmt-content-area" class="">
@@ -65,6 +66,37 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
           </div>
         </div>
         <!-- -->
+        <!-- Peers -->
+        <div class='panel-group federation-enabled accordion hidden' id='peers-accordion' role='tablist' aria-multiselectable='true'>
+          <h3>
+            <a role='button' data-toggle='collapse' data-parent='#peers-accordion' href='#peers-content-area' aria-expanded='false' aria-controls='peers-content-area' class=''>
+              <span class='accordion-icon'></span><span class='accordion-text' id='#peers-title'>$_("Peers")</span>
+            </a>
+          </h3>
+          <div id='peers-content-area' class='panel-collapse collapse' role='tabpanel' aria-labelledby='peers-title'>
+            <div id='peers-alert-container'></div>
+            <div class='row'>
+              <div class='col-sm-12'>
+                <table id='peers-list' class='table table-striped' cellspacing='0' width='100%'>
+                  <thead>
+                    <tr>
+                      <th><span class='sr-only'>$_("Peers")</span></th>
+                    </tr>
+                  </thead>
+                </table>
+              </div>
+            </div>
+            <div class='wok-mask' role='presentation'>
+              <div class='wok-mask-loader-container'>
+                <div class='wok-mask-loading'>
+                  <div class='wok-mask-loading-icon'></div>
+                  <div class='wok-mask-loading-text'>$_("Loading...")</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- -->
       </div>
     </div>
   </div>
-- 
2.9.3

_______________________________________________
Kimchi-devel mailing list
Kimchi-devel@ovirt.org
http://lists.ovirt.org/mailman/listinfo/kimchi-devel
Re: [Kimchi-devel] [PATCH] [Wok] Add federation feature to Wok
Posted by Aline Manera 7 years, 5 months ago
Applied. Thanks.

Regards,

Aline Manera

_______________________________________________
Kimchi-devel mailing list
Kimchi-devel@ovirt.org
http://lists.ovirt.org/mailman/listinfo/kimchi-devel
Re: [Kimchi-devel] [PATCH] [Wok] Add federation feature to Wok
Posted by Daniel Henrique Barboza 7 years, 6 months ago
Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com>

On 05/02/2017 01:59 PM, Aline Manera wrote:
> It was primarily implemented on Kimchi project. But it makes more sense
> to be on Wok since the idea is to allow user add/select on which server
> Wok should manage.
> For example, the user could have a Wok installation on server A to
> manage server B.
>
> Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com>
> ---
>   docs/API/config.md               |  2 ++
>   docs/API/peers.md                | 20 +++++++++++
>   docs/README-federation.md        | 60 ++++++++++++++++++++++++++++++++
>   docs/wokd.8.in                   |  6 +++-
>   src/wok.conf.in                  |  4 +++
>   src/wok/config.py.in             |  1 +
>   src/wok/control/peers.py         | 30 ++++++++++++++++
>   src/wok/model/config.py          |  1 +
>   src/wok/model/peers.py           | 74 +++++++++++++++++++++++++++++++++++++++
>   src/wokd.in                      |  5 +++
>   tests/test_api.py                |  6 +++-
>   tests/test_config_model.py       |  2 +-
>   tests/test_server_root.py        |  6 ++--
>   ui/js/src/wok.api.js             | 14 ++++++++
>   ui/js/wok.peers.js               | 75 ++++++++++++++++++++++++++++++++++++++++
>   ui/pages/tabs/settings.html.tmpl | 34 +++++++++++++++++-
>   16 files changed, 333 insertions(+), 7 deletions(-)
>   create mode 100644 docs/API/peers.md
>   create mode 100644 docs/README-federation.md
>   create mode 100644 src/wok/control/peers.py
>   create mode 100644 src/wok/model/peers.py
>   create mode 100644 ui/js/wok.peers.js
>
> diff --git a/docs/API/config.md b/docs/API/config.md
> index d1d1007..0e47f5f 100644
> --- a/docs/API/config.md
> +++ b/docs/API/config.md
> @@ -12,6 +12,8 @@ Contains information about the application environment and configuration.
>       * proxy_port: SSL port to list on
>       * websockets_port: Port for websocket proxy to listen on
>       * auth: Authentication method used to log in to Wok
> +    * server_root: Relative path to Wok server. No value is specified by default, ie, Wok will run on '/'
> +    * federation: 'on' if federation feature is enabled, 'off' otherwise.
>       * version: Wok version
>   * **POST**: *See Task Actions*
>   
> diff --git a/docs/API/peers.md b/docs/API/peers.md
> new file mode 100644
> index 0000000..fa5cf41
> --- /dev/null
> +++ b/docs/API/peers.md
> @@ -0,0 +1,20 @@
> +## REST API Specification for Peers
> +
> +### Collection: Peers
> +
> +**URI:** /peers
> +
> +Return a list of Wok peers in the same network.
> +(It uses openSLP for discovering)
> +
> +**Methods:**
> +
> +* **GET**: Retrieve a list peers URLs.
> +
> +#### Examples
> +GET /peers
> +[
> + https://wok-peer0:8001,
> + https://wok-peer1:8001,
> + https://wok-peer2:8001,
> +]
> diff --git a/docs/README-federation.md b/docs/README-federation.md
> new file mode 100644
> index 0000000..18e19c7
> --- /dev/null
> +++ b/docs/README-federation.md
> @@ -0,0 +1,60 @@
> +Wok Project - Federation Feature
> +===================================
> +
> +Federation feature is a mechanism to discover Wok peers in the same
> +network. It uses openSLP tool (http://www.openslp.org/) to register and find Wok
> +servers.
> +
> +By default this feature is disabled on Wok as it is not critical for KVM
> +virtualization and requires additional software installation.
> +
> +To enable it, do the following:
> +
> +1. Install openslp and openslp-server rpm packages,
> +   or install slpd and slptool deb packages.
> +
> +2. openSLP uses port 427 (UDP) and port 427 (TCP) so make sure to open those
> +   ports in your firewall configuration
> +
> +   For system using firewalld, do:
> +   sudo firewall-cmd --permanent --add-port=427/udp
> +   sudo firewall-cmd --permanent --add-port=427/tcp
> +   sudo firewall-cmd --reload
> +
> +   For openSUSE systems, do:
> +   sudo /sbin/SuSEfirewall2 open EXT TCP 427
> +   sudo /sbin/SuSEfirewall2 open EXT UDP 427
> +
> +   For system using iptables, do:
> +   sudo iptables -A INPUT -p tcp --dport 427 -j ACCEPT
> +   sudo iptables -A INPUT -p udp --dport 427 -j ACCEPT
> +
> +3. In addition to the openSLP ports, you also need to allow multicast in the
> +   firewall configuration
> +
> +   For system using firewalld, do:
> +   sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -s <subnet> -j ACCEPT
> +
> +   For openSUSE systems, do:
> +   Add the subnet to the trusted networks listed on FW_TRUSTED_NETS in
> +   /etc/sysconfig/SuSEfirewall2 file.
> +   Make sure to restart /sbin/SuSEfirewall2 after modifying /etc/sysconfig/SuSEfirewall2
> +
> +   For system using iptables, do:
> +   sudo iptables -A INPUT -s <subnet> -j ACCEPT
> +
> +4. Start slpd service and make sure it is up while running Wok
> +   sudo service slpd start
> +
> +5. Enable federation on Wok by editing the /etc/wok/wok.conf file:
> +
> +   federation = on
> +
> +6. Then start Wok service
> +   sudo service wokd start
> +
> +The Wok server will be registered on openSLP on server starting up and will
> +be found by other Wok peers (with federation feature enabled) in the same
> +network.
> +
> +Enjoy!
> diff --git a/docs/wokd.8.in b/docs/wokd.8.in
> index 217d9f6..09b5847 100644
> --- a/docs/wokd.8.in
> +++ b/docs/wokd.8.in
> @@ -7,7 +7,7 @@ wokd - Launch Wok web server
>   [\fB--cherrypy_port\fP \fICHERRYPY_PORT\fP] [\fB--websockets_port\fP \fIWEBSOCKETS_PORT\fP]
>   [\fB--session_timeout\fP \fISESSION_TIMEOUT\fP] [\fB--log_level\fP \fILOG_LEVEL\fP]
>   [\fB--log_dir\fP \fILOG_DIR\fP] [\fB--environment\fP \fIENV\fP]
> -[\fB--server_root\fP \fISERVER_ROOT\fP] [\fB--test\fP]
> +[\fB--server_root\fP \fISERVER_ROOT\fP] [\fB--federation\fP \fIfederation\fP] [\fB--test\fP]
>   .SH DESCRIPTION
>   \fBWok\fP is a cherrypy-based web framework with HTML5 support originated from Kimchi.
>   It can be extended by plugins which expose functionality through REST APIs.
> @@ -55,6 +55,10 @@ Check cherrypy documentation for more details (default \fIproduction\fP).
>   \fB\-\-server_root\fP [\fISERVER_ROOT\fP]
>   Relative path to Wok server. No value is specified by default, ie, Wok will run on '/'.
>   .TP
> +\fB\-\-federation\fP [\fIon\fP | \fIoff\fP]
> +Register and discover Wok peers in the same network using OpenSLP. Check
> +below the \fBREAD-federation\fP for more details (default \fIoff\fP).
> +.TP
>   \fB\-\-test\fP
>   Run Wok on a mock version that does not affect the system. For testing proposals.
>   It depends on how plugins implements the mock environment as well.
> diff --git a/src/wok.conf.in b/src/wok.conf.in
> index 1ebdacf..1ed6310 100644
> --- a/src/wok.conf.in
> +++ b/src/wok.conf.in
> @@ -27,6 +27,10 @@
>   # uncomment the following:
>   #server_root=/wok
>   
> +# Federation feature: register Wok server on openSLP and discover peers
> +# in the same network. Check README-federation for more details.
> +#federation = off
> +
>   [logging]
>   # Log directory
>   
> diff --git a/src/wok/config.py.in b/src/wok/config.py.in
> index bbf2c09..39d7ad2 100644
> --- a/src/wok/config.py.in
> +++ b/src/wok/config.py.in
> @@ -277,6 +277,7 @@ def _get_config():
>       config.set("server", "environment", "production")
>       config.set('server', 'max_body_size', '4*1024*1024')
>       config.set("server", "server_root", "")
> +    config.set("server", "federation", "off")
>       config.set("server", "test", "")
>       config.add_section("authentication")
>       config.set("authentication", "method", "pam")
> diff --git a/src/wok/control/peers.py b/src/wok/control/peers.py
> new file mode 100644
> index 0000000..2a14815
> --- /dev/null
> +++ b/src/wok/control/peers.py
> @@ -0,0 +1,30 @@
> +#
> +# Project Wok
> +#
> +# Copyright IBM Corp, 2017
> +#
> +# Code derived from Kimchi Project
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from wok.control.base import SimpleCollection
> +from wok.control.utils import UrlSubNode
> +
> +
> +@UrlSubNode("peers", True)
> +class Peers(SimpleCollection):
> +    def __init__(self, model):
> +        super(Peers, self).__init__(model)
> +        self.admin_methods = ['GET']
> diff --git a/src/wok/model/config.py b/src/wok/model/config.py
> index b69f2dd..887ceba 100644
> --- a/src/wok/model/config.py
> +++ b/src/wok/model/config.py
> @@ -34,6 +34,7 @@ class ConfigModel(object):
>                   'websockets_port': config.get('server', 'websockets_port'),
>                   'auth': config.get('authentication', 'method'),
>                   'server_root': config.get('server', 'server_root'),
> +                'federation': config.get('server', 'federation'),
>                   'version': get_version()}
>   
>       def reload(self, name):
> diff --git a/src/wok/model/peers.py b/src/wok/model/peers.py
> new file mode 100644
> index 0000000..02afd59
> --- /dev/null
> +++ b/src/wok/model/peers.py
> @@ -0,0 +1,74 @@
> +#
> +# Project Wok
> +#
> +# Copyright IBM Corp, 2017
> +#
> +# Code derived from Kimchi Project
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +import cherrypy
> +import re
> +import socket
> +
> +from wok.config import config
> +from wok.utils import run_command, wok_log
> +
> +
> +class PeersModel(object):
> +    def __init__(self, **kargs):
> +        # check federation feature is enabled on Wok server
> +        if not config.get('server', 'federation') == 'off':
> +            return
> +
> +        # register server on openslp
> +        hostname = socket.getfqdn()
> +        port = config.get("server", "proxy_port")
> +        self.url = hostname + ":" + port
> +
> +        cmd = ["slptool", "register",
> +               "service:wokd://%s" % self.url]
> +        out, error, ret = run_command(cmd)
> +        if out and len(out) != 0:
> +            wok_log.error("Unable to register server on openSLP."
> +                          " Details: %s" % out)
> +        cherrypy.engine.subscribe('exit', self._peer_deregister)
> +
> +    def _peer_deregister(self):
> +        cmd = ["slptool", "deregister",
> +               "service:wokd://%s" % self.url]
> +        out, error, ret = run_command(cmd)
> +        if out and len(out) != 0:
> +            wok_log.error("Unable to deregister server on openSLP."
> +                          " Details: %s" % out)
> +
> +    def get_list(self):
> +        # check federation feature is enabled on Wok server
> +        if not config.get('server', 'federation') == 'off':
> +            return []
> +
> +        cmd = ["slptool", "findsrvs", "service:wokd"]
> +        out, error, ret = run_command(cmd)
> +        if ret != 0:
> +            return []
> +
> +        peers = []
> +        for server in out.strip().split("\n"):
> +            match = re.match("service:wokd://(.*?),.*", server)
> +            peer = match.group(1)
> +            if peer != self.url:
> +                peers.append("https://" + peer)
> +
> +        return peers
> diff --git a/src/wokd.in b/src/wokd.in
> index 29586a0..dfe85a8 100644
> --- a/src/wokd.in
> +++ b/src/wokd.in
> @@ -48,6 +48,7 @@ def main(options):
>       websockets_port = config.config.get("server", "websockets_port")
>       session_timeout = config.config.get("server", "session_timeout")
>       runningEnv = config.config.get("server", "environment")
> +    federation = config.config.get("server", "federation")
>       server_root = config.config.get("server", "server_root")
>       logDir = config.config.get("logging", "log_dir")
>       logLevel = config.config.get("logging", "log_level")
> @@ -71,6 +72,10 @@ def main(options):
>                         help="Running environment of wok server")
>       parser.add_option('--server_root', type="string", default=server_root,
>                         help="Relative path to server")
> +    parser.add_option('--federation', default=federation,
> +                      help="Register and discover Wok peers in the same "
> +                           "network using openSLP. Check README-federation for"
> +                           " more details.")
>       parser.add_option('--test', action='store_true',
>                         help="Run server in mock model")
>       (options, args) = parser.parse_args()
> diff --git a/tests/test_api.py b/tests/test_api.py
> index 6fbee75..f978c93 100644
> --- a/tests/test_api.py
> +++ b/tests/test_api.py
> @@ -49,11 +49,15 @@ class APITests(unittest.TestCase):
>       def setUp(self):
>           self.request = partial(utils.request)
>   
> +    def test_peers(self):
> +        resp = self.request('/peers').read()
> +        self.assertEquals([], json.loads(resp))
> +
>       def test_config(self):
>           resp = self.request('/config').read()
>           conf = json.loads(resp)
>           keys = ["auth", "proxy_port", "websockets_port", "version",
> -                "server_root"]
> +                "server_root", "federation"]
>           self.assertEquals(sorted(keys), sorted(conf.keys()))
>   
>       def test_config_plugins(self):
> diff --git a/tests/test_config_model.py b/tests/test_config_model.py
> index f8b0848..565b4df 100644
> --- a/tests/test_config_model.py
> +++ b/tests/test_config_model.py
> @@ -30,7 +30,7 @@ class ConfigModelTests(unittest.TestCase):
>           config = inst.config_lookup('')
>           self.assertItemsEqual(
>               ['proxy_port', 'websockets_port', 'auth',
> -             'server_root', 'version'],
> +             'server_root', 'version', 'federation'],
>               config.keys()
>           )
>   
> diff --git a/tests/test_server_root.py b/tests/test_server_root.py
> index e95a13b..89f34d3 100644
> --- a/tests/test_server_root.py
> +++ b/tests/test_server_root.py
> @@ -1,7 +1,7 @@
>   #
>   # Project Wok
>   #
> -# Copyright IBM Corp, 2016
> +# Copyright IBM Corp, 2016-2017
>   #
>   # This library is free software; you can redistribute it and/or
>   # modify it under the terms of the GNU Lesser General Public
> @@ -49,7 +49,7 @@ class ServerRootTests(unittest.TestCase):
>           # check if server_root in config is the same used to start server
>           resp = request(server_root + '/config').read()
>           conf = json.loads(resp)
> -        self.assertEquals(len(conf), 5)
> +        self.assertEquals(len(conf), 6)
>   
>       def test_development_env(self):
>           """
> @@ -61,4 +61,4 @@ class ServerRootTests(unittest.TestCase):
>           # check if server_root in config is the same used to start server
>           resp = request(server_root + '/config').read()
>           conf = json.loads(resp)
> -        self.assertEquals(len(conf), 5)
> +        self.assertEquals(len(conf), 6)
> diff --git a/ui/js/src/wok.api.js b/ui/js/src/wok.api.js
> index 06b97aa..4e68eee 100644
> --- a/ui/js/src/wok.api.js
> +++ b/ui/js/src/wok.api.js
> @@ -71,6 +71,20 @@ var wok = {
>           });
>       },
>   
> +    getPeers: function(suc, err) {
> +        wok.requestJSON({
> +            url: 'peers',
> +            type: 'GET',
> +            contentType: 'application/json',
> +            dataType: 'json',
> +            resend: true,
> +            success: suc,
> +            error: err ? err : function(data) {
> +                wok.message.error(data.responseJSON.reason);
> +            }
> +        });
> +    },
> +
>       getNotifications: function (suc, err) {
>           wok.requestJSON({
>               url: 'notifications',
> diff --git a/ui/js/wok.peers.js b/ui/js/wok.peers.js
> new file mode 100644
> index 0000000..67e58b0
> --- /dev/null
> +++ b/ui/js/wok.peers.js
> @@ -0,0 +1,75 @@
> +/*
> + * Project Wok
> + *
> + * Copyright IBM Corp, 2017
> + *
> + * Code derived from Kimchi Project
> + *
> + * 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.
> + */
> +
> +wok.initPeers = function() {
> +    var peersDatatableTable;
> +    var peers = new Array();
> +
> +    var peersDatatable = function(nwConfigDataSet) {
> +        peersDatatableTable = $('#peers-list').DataTable({
> +            "processing": true,
> +            "data": peers,
> +            "language": {
> +                "emptyTable": i18n['WOKSETT0010M']
> +            },
> +            "order": [],
> +            "paging": false,
> +            "dom": '<"row"<"col-sm-12"t>>',
> +            "scrollY": "269px",
> +            "scrollCollapse": true,
> +            "columnDefs": [{
> +                "targets": 0,
> +                "searchable": false,
> +                "orderable": false,
> +                "width": "100%",
> +                "className": "tabular-data",
> +                "render": function(data, type, full, meta) {
> +                    return '<a href="' + data + '" target="_blank">' + data + '</a>';
> +                }
> +            }],
> +            "initComplete": function(settings, json) {
> +                $('#peers-content-area > .wok-mask').addClass('hidden');
> +            }
> +        });
> +    };
> +
> +    var getPeers = function() {
> +        wok.getPeers(function(result) {
> +            peers.length = 0;
> +            for (var i = 0; i < result.length; i++) {
> +                var tempArr = [];
> +                tempArr.push(result[i]);
> +                peers.push(tempArr);
> +            }
> +            peersDatatable(peers);
> +        }, function(err) {
> +            wok.message.error(err.responseJSON.reason, '#peers-alert-container', true);
> +        });
> +    };
> +    getPeers();
> +
> +}
> +
> +if (wok.config.federation == 'on') {
> +    $("#peers-accordion").removeClass('hidden');
> +    wok.initPeers();
> +} else {
> +    $("#peers-accordion").addClass('hidden');
> +}
> diff --git a/ui/pages/tabs/settings.html.tmpl b/ui/pages/tabs/settings.html.tmpl
> index ccc5b01..5f52839 100644
> --- a/ui/pages/tabs/settings.html.tmpl
> +++ b/ui/pages/tabs/settings.html.tmpl
> @@ -27,6 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
>   <head>
>     <link rel="stylesheet" type="text/css" href="$href('css/settings.css')">
>     <script type="text/javascript" src="$href('js/wok.settings.js')"></script>
> +  <script type="text/javascript" src="$href('js/wok.peers.js')"></script>
>     <script type="text/javascript" src="$href('js/wok.bootgrid.js')"></script>
>   </head>
>   
> @@ -34,7 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
>     <div id="wok-root-container" class="wok">
>       <div class="container">
>         <div id="wokSettings" class="wok-settings">
> -      <!-- Plugins Management -->
> +        <!-- Plugins Management -->
>           <div class="panel-group accordion" id="plugins-mgmt-accordion" role="tablist" aria-multiselectable="false">
>             <h3>
>               <a role="button" data-toggle="collapse" data-parent="#plugin-mgmt-accordion" href="#plugins-mgmt-content-area" aria-expanded="true" aria-controls="plugins-mgmt-content-area" class="">
> @@ -65,6 +66,37 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
>             </div>
>           </div>
>           <!-- -->
> +        <!-- Peers -->
> +        <div class='panel-group federation-enabled accordion hidden' id='peers-accordion' role='tablist' aria-multiselectable='true'>
> +          <h3>
> +            <a role='button' data-toggle='collapse' data-parent='#peers-accordion' href='#peers-content-area' aria-expanded='false' aria-controls='peers-content-area' class=''>
> +              <span class='accordion-icon'></span><span class='accordion-text' id='#peers-title'>$_("Peers")</span>
> +            </a>
> +          </h3>
> +          <div id='peers-content-area' class='panel-collapse collapse' role='tabpanel' aria-labelledby='peers-title'>
> +            <div id='peers-alert-container'></div>
> +            <div class='row'>
> +              <div class='col-sm-12'>
> +                <table id='peers-list' class='table table-striped' cellspacing='0' width='100%'>
> +                  <thead>
> +                    <tr>
> +                      <th><span class='sr-only'>$_("Peers")</span></th>
> +                    </tr>
> +                  </thead>
> +                </table>
> +              </div>
> +            </div>
> +            <div class='wok-mask' role='presentation'>
> +              <div class='wok-mask-loader-container'>
> +                <div class='wok-mask-loading'>
> +                  <div class='wok-mask-loading-icon'></div>
> +                  <div class='wok-mask-loading-text'>$_("Loading...")</div>
> +                </div>
> +              </div>
> +            </div>
> +          </div>
> +        </div>
> +        <!-- -->
>         </div>
>       </div>
>     </div>

_______________________________________________
Kimchi-devel mailing list
Kimchi-devel@ovirt.org
http://lists.ovirt.org/mailman/listinfo/kimchi-devel