Shahar Havivi has uploaded a new change for review.

Change subject: First Commit
......................................................................

First Commit

Working sample of Python user portal using REST API

Change-Id: I1d15dd5df8992c84b2c9529168919d05ba88bc33
Signed-off-by: Shahar Havivi <[email protected]>
---
A python/Broker.py
A python/README
A python/RestClient.py
A python/RestCommand.py
A python/WebHandler.py
A python/ovirt.conf
6 files changed, 422 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/samples-portals refs/changes/87/9387/1

diff --git a/python/Broker.py b/python/Broker.py
new file mode 100755
index 0000000..2285394
--- /dev/null
+++ b/python/Broker.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+import WebHandler
+
+import time
+import ConfigParser
+import BaseHTTPServer
+
+config = ConfigParser.ConfigParser()
+config.read('ovirt.conf')
+
+hostName = config.get('Server', 'HostName')
+portNumber = config.getint('Server', 'PortNumber')
+
+server_class = BaseHTTPServer.HTTPServer
+httpd = server_class((hostName, portNumber), WebHandler.WebHandler)
+print time.asctime(), "Server Starts - %s:%s" % (hostName, portNumber)
+try:
+    httpd.serve_forever()
+except KeyboardInterrupt:
+    pass
+httpd.server_close()
+print time.asctime(), "Server Stops - %s:%s" % (hostName, portNumber)
diff --git a/python/README b/python/README
new file mode 100644
index 0000000..3c9fb13
--- /dev/null
+++ b/python/README
@@ -0,0 +1,27 @@
+This is a Python sample web-portal that uses REST API to view and run VMs from 
oVirt Engine.
+This project run a local web server which can be configure via the ovirt.conf 
file.
+
+o Running the web server:
+  ./Broker.py
+
+o conneting to the server:
+  http://localhost:8000/
+
+o classes:
+  Broker
+      main class, run the web server
+
+  RestClient
+      dispatch HTTP GET and POST RESTful methods
+
+  RestCommand:
+      parse the underline xml that return from RestCommand
+
+  WebHandler
+      implementation of web server
+
+o configuration file:
+  ovirt.conf:
+    BaseUrl: the url where the Engine is running
+    HostName: the local for the web server to listen to
+    PortNumber: the local port number for the server to listen to
diff --git a/python/RestClient.py b/python/RestClient.py
new file mode 100644
index 0000000..6791d2c
--- /dev/null
+++ b/python/RestClient.py
@@ -0,0 +1,49 @@
+import base64
+import httplib
+import urllib2
+import urlparse
+
+cookie = None
+
+'''
+RestClient: handler RESTful post/get methods
+'''
+class RestClient(object):
+
+    def doGetMethod(self, url, userName=None, password=None):
+        global cookie
+        req = urllib2.Request(url)
+        if cookie is None:
+            auth = "%s:%s" % (userName, password)
+            auth = base64.encodestring(auth)
+            req.add_header("Authorization", "Basic %s" % auth)
+        else:
+            req.add_header("Cookie", cookie)
+
+        # run in user level API
+        req.add_header('filter', 'true')
+        # for using cookies
+        req.add_header('Prefer', 'persistent-auth')
+        response = urllib2.urlopen(req)
+        if not response.info().getheader('Set-Cookie') is None:
+            cookie = response.info().getheader('Set-Cookie')
+
+        return response.read()
+
+    def doPostMethod(self, url):
+        global cookie
+        u = urlparse.urlparse(url)
+
+        headers = {"Content-type": "application/xml"}
+        headers['filter'] = 'true'
+        headers['Prefer'] = 'persistent-auth'
+        headers['Cookie'] = cookie
+
+        conn = httplib.HTTPConnection(u.hostname, u.port)
+        conn.request("POST", u.path, body="<action/>", headers=headers)
+        res = conn.getresponse()
+        return res.read()
+
+    def resetCookie(self):
+        global cookie
+        cookie = None
diff --git a/python/RestCommand.py b/python/RestCommand.py
new file mode 100644
index 0000000..097f7be
--- /dev/null
+++ b/python/RestCommand.py
@@ -0,0 +1,97 @@
+import RestClient
+
+import ConfigParser
+from xml.dom import minidom
+
+restClient = RestClient.RestClient()
+
+class RestCommand(object):
+
+    def __init__(self):
+        global restClient
+        self.config = ConfigParser.ConfigParser()
+        self.config.read('ovirt.conf')
+        self.baseUrl = self.config.get('oVirt', 'BaseUrl')
+
+    def _parseVm(self, xmlVm):
+        vm = {}
+        vm['startable'] = True
+        vm['stopable'] = True
+        vm['connectable'] = True
+        vm['vmid'] =  xmlVm.getAttribute('id')
+        vm['name'] = 
xmlVm.getElementsByTagName('name').item(0).firstChild.nodeValue
+        vm['status'] = 
xmlVm.getElementsByTagName('status').item(0).getElementsByTagName('state').item(0).firstChild.nodeValue
+        vm['display'] = 
xmlVm.getElementsByTagName('display').item(0).getElementsByTagName('type').item(0).firstChild.nodeValue
+        if len(xmlVm.getElementsByTagName('port')) > 0:
+            vm['port'] = 
xmlVm.getElementsByTagName('port').item(0).firstChild.nodeValue
+        else:
+            vm['port'] = '-1'
+        if len(xmlVm.getElementsByTagName('address')) > 0:
+            vm['address'] = 
xmlVm.getElementsByTagName('address').item(0).firstChild.nodeValue
+        else:
+            vm['address'] = ''
+        return vm
+
+    def login(self, userName, password):
+        self.userName = userName
+        self.password = password
+
+        try:
+            restClient.resetCookie()
+            xml = restClient.doGetMethod(self.baseUrl + '/api', userName, 
password)
+            return True
+        except: 
+            return False
+
+    def getUserVms(self):
+        global restClient
+        xml = restClient.doGetMethod(self.baseUrl + '/api/vms')
+        dom = minidom.parseString(xml)
+        xmlVms = dom.getElementsByTagName('vm')
+        vms = []
+        for xmlVm in xmlVms:
+            vms.append(self._parseVm(xmlVm))
+
+        return vms
+
+    def getVmByVmId(self, vmid):
+        global restClient
+        xml = restClient.doGetMethod(self.baseUrl + '/api/vms/' + vmid)
+        dom = minidom.parseString(xml)
+        return self._parseVm(dom.getElementsByTagName('vm')[0])
+
+    '''
+        Method return action results
+
+        input:
+            vmid: guid of vm
+            action: action to run (start, stop or ticket) - there are more 
actions...(never tested)
+
+        return value:
+            dictionary
+            { 'status': 'complete',
+              'reason': '...',    # only if failed
+              'detail': '...',    # only if failed
+              'value': 'XXYYZZ',  # the ticket (password)
+              'expired': '7200',  # in minutes
+            }
+    '''
+    def runAction(self, vmid, action):
+        global restClient
+        ret = {}
+        url = '%s/api/vms/%s/%s' % (self.baseUrl, vmid, action)
+
+        xml = restClient.doPostMethod(url)
+        dom = minidom.parseString(xml)
+
+        ret['status'] = 
dom.getElementsByTagName('state').item(0).firstChild.nodeValue
+        if ret['status'] == 'failed':
+            ret['reason'] = 
dom.getElementsByTagName('reason').item(0).firstChild.nodeValue
+            ret['detail'] = 
dom.getElementsByTagName('detail').item(0).firstChild.nodeValue
+
+        if action == 'ticket':
+            elem = dom.getElementsByTagName('ticket').item(0)
+            ret['value'] = 
elem.getElementsByTagName('value').item(0).firstChild.nodeValue
+            ret['expired'] = 
elem.getElementsByTagName('expiry').item(0).firstChild.nodeValue
+
+        return ret
diff --git a/python/WebHandler.py b/python/WebHandler.py
new file mode 100644
index 0000000..fdab351
--- /dev/null
+++ b/python/WebHandler.py
@@ -0,0 +1,220 @@
+import RestCommand
+
+import cgi
+import BaseHTTPServer
+
+restCommand = None
+
+'''
+WebHandler: simple Web Server
+'''
+class WebHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+    def do_HEAD(self):
+        self.send_response(200)
+        self.send_header("Content-type", "text/html")
+        self.end_headers()
+
+    def do_POST(self):
+        if self.path == '/':
+            form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, 
environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], 
})
+            self._login_method(form)
+        else:
+            self._page_error()
+
+    def do_GET(self):
+        # strip parameters from page name in get command
+        # ie /mycgi?param=value
+        # will get /mycgi
+        p = self.path
+        if p.find('?') != -1:
+            p = self.path[:self.path.find('?')]
+            params = self.path[self.path.find('?')+1:]
+
+        if p == '/':
+            self._login_method()
+        elif p == '/action':
+            self._action_method(params)
+        else:
+            self._page_error()
+
+    def _page_error(self):
+        self.send_response(404)
+        self.send_header("Content-type", "text/html")
+        self.end_headers()
+        self.wfile.write("<html><head><title>Page not found</title></head>")
+        self.wfile.write("<body><p>Page not found %s</p>" % self.path)
+        self.wfile.write("</body></html>")
+
+
+    def _action_method(self, params):
+        global restCommand
+        self.send_response(200)
+        self.send_header("Content-type", "text/html")
+        self.end_headers()
+
+        form = cgi.parse_qs(params)
+
+        vmid = form['vmid'][0]
+        action = form['action'][0]
+
+        res = restCommand.runAction(vmid, action)
+
+        if res['status'] == 'failed':
+            html = '''<html><body>
+            <p style='color:red'>Action faild!</p>
+            <br/>Reason: %s
+            <br/>Details: %s
+            <br/>
+            <button onclick=javascript:history.back()>Back</button>
+            </body></html>''' % (res['reason'], res['detail'])
+
+        elif action == 'ticket':
+            vm = restCommand.getVmByVmId(vmid)
+
+            if self.headers['user-agent'].lower().find('windows') >= 0:
+                html = self._ticketIE(vm, res)
+            else:
+                html = self._ticketFirefox(vm, res)
+
+        else:
+            html = '''<html><body>
+            VM '%s' successfully
+            <br/>
+            <button onclick=javascript:history.back()>Back</button>
+            </body></html>
+            ''' % action
+
+        self.wfile.write(html)
+
+    def _ticketIE(self, vm, res):
+        html = '''<html>
+   <script>
+       function onConnect() {
+           spice.HostIP = '%s';
+           spice.Port = '%s';
+           spice.Password = '%s'
+           spice.Connect();
+       }
+   </script>
+<body>
+    <OBJECT style='visibility: hidden' codebase='SpiceX.cab#version=1,0,0,1' 
ID='spice' CLASSID='CLSID:ACD6D89C-938D-49B4-8E81-DDBD13F4B48A'>
+    </OBJECT>
+    <form>
+        <input type=button onclick='javascript:onConnect();' value='Connect'/>
+    </form>
+</body>
+</html>''' % (vm['address'], vm['port'], res['value'])
+            
+        return html
+
+    def _ticketFirefox(self, vm, res):
+        html = '''<html>
+   <script>
+       function onConnect() {
+            spice.hostIP = '%s';
+            spice.port = '%s';
+            spice.Password = '%s';
+            spice.connect();
+            spice.show()
+       }
+   </script>
+<body>
+    <embed id='spice' type="application/x-spice" width=0 height=0><br>
+    <form>
+        <input type=button value='Connent' onclick='onConnect()'/>
+    </form>
+</body>
+</html>''' % (vm['address'], vm['port'], res['value'])
+            
+        return html
+
+
+    def _uservms_method(self):
+        #form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, 
environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], 
})
+
+        self.send_response(200)
+        self.send_header("Content-type", "text/html")
+        self.end_headers()
+
+        vms = restCommand.getUserVms()
+
+        # render the html
+        html = '''<html>
+<body>
+    <center><br/><br/>
+    <table cellpadding="5" style='border-width: 1px; border-spacing: 2px; 
border-style: outset; border-color: gray; border-collapse: separate; 
background-color: white;'>
+        <tr>
+            <th>VM Name</th>
+            <th>Status</th>
+            <th>Dispaly</th>
+            <th>Start</th>
+            <th>Stop</th>
+            <th>Connect</th>
+        </tr>
+        '''
+
+        for vm in vms:
+            startable = ''
+            if not vm['startable']:
+                startable = "disabled='disabled'"
+            stopable = ''
+            if not vm['stopable']:
+                stopable = "disabled='disabled'"
+            connectable = ''
+            if not vm['connectable'] or vm['display'] != 'spice':
+                connectable = "disabled='disabled'"
+
+            startbtn = "<button %s 
onclick=javascript:location.href='action?vmid=%s&action=start' 
type='button'>Start</button>" % (startable, vm['vmid'])
+            stopbtn = "<button %s 
onclick=javascript:location.href='action?vmid=%s&action=stop' 
type='button'>Stop</button>" % (stopable, vm['vmid'])
+            connectbtn = "<button %s 
onclick=javascript:location.href='action?vmid=%s&action=ticket' 
type='button'>Connect</button>" % (connectable, vm['vmid'])
+
+            html = html + '''       <tr>
+            <td>%s</td>
+            <td>%s</td>
+            <td>%s</td>
+            <td>%s</td>
+            <td>%s</td>
+            <td>%s</td>
+        </tr>''' % (vm['name'], vm['status'], vm['display'], startbtn, 
stopbtn, connectbtn)
+
+        html = html + '''   </table></center></body></html>'''
+        self.wfile.write(html)
+
+
+    def _login_method(self, form={}):
+        global restCommand
+        message = ''
+        if form.has_key("username") and form.has_key('password'):
+            restCommand = RestCommand.RestCommand()
+            if restCommand.login(form.getvalue("username"), 
form.getvalue("password")):
+                self._uservms_method()
+                return
+            else:
+                message = 'Login Error'
+
+        self.send_response(200)
+        self.send_header("Content-type", "text/html")
+        self.end_headers()
+
+        html = '''<html>
+<body>
+<center><br/><br/>
+<form name="input" action="/" method="post">
+        <table>
+            <tr>
+                <td>User name:</td>
+                <td><input type="text" name="username" value=""/></td>
+            <tr>
+                <td>Password:</td>
+                <td><input type="password" name="password" value=""/></td>
+            <tr>
+                <td/>
+                <td align="right"><input type="submit" value="Login"/></td>
+            </tr>
+            <tr>
+                <td colspan='2' style='color:red'>%s</td>
+            </tr>
+    </form>
+</body>
+</html>'''
+        self.wfile.write(html % message)
diff --git a/python/ovirt.conf b/python/ovirt.conf
new file mode 100644
index 0000000..777145f
--- /dev/null
+++ b/python/ovirt.conf
@@ -0,0 +1,6 @@
+[oVirt]
+BaseUrl: http://localhost:8080
+
+[Server]
+HostName: 0.0.0.0
+PortNumber: 8000


--
To view, visit http://gerrit.ovirt.org/9387
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I1d15dd5df8992c84b2c9529168919d05ba88bc33
Gerrit-PatchSet: 1
Gerrit-Project: samples-portals
Gerrit-Branch: master
Gerrit-Owner: Shahar Havivi <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to