%define name sface
%define version 0.1
-%define taglevel 17
+%define taglevel 18
%define release %{taglevel}%{?pldistro:.%{pldistro}}%{?date:.%{date}}
%{_datadir}/sface/*
%changelog
+* Sun Sep 04 2011 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sface-0.1-18
+- usable with a different directory than ~/.sfi
+- either on the command line, or in the config. screen
+
* Thu Sep 01 2011 Thierry Parmentelat <thierry.parmentelat@sophia.inria.fr> - sface-0.1-17
- new user management screen to allow users to be added and deleted from slices
- known issue with the users screen regarding adding people who are listed as PIs to a sliver.
from sfa.util.version import version_core
from sface.version import version_dict
+from sface.sficreate import CreateWindow
+
static_labels = {
'slice' : [
"Sface : %s (%s)" % (version_dict()['code_tag'], version_dict()['code_url']),
glayout = QGridLayout()
row = 0
for (field,msg) in config.field_labels():
-
+
if static_labels.has_key(field):
labels=static_labels[field]
if not isinstance(labels,list): labels = [ labels, ]
hlayout.addWidget (edit)
hlayout.addSpacing(10)
+ conf_button ('createSlice', 'Create New Slice'),
conf_button ('apply','Apply Only'),
conf_button ('save','Apply && Save')
self.setLayout(layout)
self.inited=True
+ def createSlice(self):
+ dlg = CreateWindow(parent=self)
+ dlg.exec_()
+ if (dlg.sliceWasCreated):
+ self.slice.setText(dlg.getHrn())
+ self.save()
def apply(self):
print 'applying'
# switch to another config dir
def load(self):
# obtain new dor somehow
-
+
edit=self.retrieve_local('config_dirname')
newdir=str(edit.text())
newdir+='/'
#from sfa.util.rspecHelper import RSpec
from sfa.rspecs.rspec_parser import parse_rspec
from sface.config import config
-from sface.sfirenew import SfiRenewer
+from sface.sfirenew import RenewWindow
from sface.sfiprocess import SfiProcess
from sface.screens.sfascreen import SfaScreen
tagstring = QString("%s: %s" % (tagname, value))
tagItem = QStandardItem(tagstring)
status = QStandardItem(QString(tag_status['add']))
- nodeItem.appendRow([tagItem, status])
+ nodeItem.appendRow([tagItem, QStandardItem(QString("")), status])
elif status_data in (node_status['out'], node_status['remove']):
QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
def renew(self):
dlg = RenewWindow(parent=self)
- if (dlg.exec_() == QDialog.Accepted):
- self.setStatus("Renewing Slice.")
-
- self.renewProcess = SfiRenewer(config.getSlice(), dlg.get_new_expiration(), self)
- self.connect(self.renewProcess, SIGNAL('finished()'), self.renewFinished)
-
- def renewFinished(self):
- if self.renewProcess.statusMsg:
- self.setStatus("Renew " + self.renewProcess.status + ": " + self.renewProcess.statusMsg)
- else:
- self.setStatus("Renew " + self.renewProcess.status)
- self.disconnect(self.renewProcess, SIGNAL('finished()'), self.renewFinished)
- self.renewProcess = None
+ dlg.exec_()
def refresh(self):
if not config.getSlice():
def nodeSelectionChanged(self, hostname):
self.parent().nodeSelectionChanged(hostname)
-class RenewWindow(QDialog):
- def __init__(self, parent=None):
- super(RenewWindow, self).__init__(parent)
- self.setWindowTitle("Renew Slivers")
-
- self.duration = QComboBox()
-
- self.expirations = []
-
- durations = ( (1, "One Week"), (2, "Two Weeks"), (3, "Three Weeks"), (4, "One Month") )
-
- now = datetime.datetime.utcnow()
- for (weeks, desc) in durations:
- exp = now + datetime.timedelta(days = weeks * 7)
- desc = desc + " " + exp.strftime("%Y-%m-%d %H:%M:%S")
- self.expirations.append(exp)
- self.duration.addItem(desc)
-
- self.duration.setCurrentIndex(0)
-
- buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
- buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
-
- layout = QVBoxLayout()
- layout.addWidget(self.duration)
- layout.addWidget(buttonBox)
- self.setLayout(layout)
-
- self.connect(buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
- self.connect(buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
-
- def accept(self):
- QDialog.accept(self)
-
- def get_new_expiration(self):
- index = self.duration.currentIndex()
- return self.expirations[index]
-
class MainScreen(SfaScreen):
def __init__(self, parent):
SfaScreen.__init__(self, parent)
childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
if (childStatus == user_status['add']):
- slicerec.get_field("researcher").append(childName)
+ researcher = slicerec.get_field("researcher", [])
+ researcher.append(childName)
+ slicerec["researcher"] = researcher
change = True
elif (childStatus == user_status['remove']):
if childName in slicerec.get_field("PI"):
slicerec.get_field("researcher").remove(childName)
change = True
- print "XXX", slicerec.get_field("researcher")
return change
--- /dev/null
+import calendar
+import datetime
+import os
+import re
+import sys
+import time
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+from sface.config import config
+from sface.sfiprocess import SfiProcess
+
+class CreateWindow(QDialog):
+ def __init__(self, parent=None):
+ super(CreateWindow, self).__init__(parent)
+ self.setWindowTitle("Create new Slice")
+
+ self.renewProcess = None
+
+ self.sliceWasCreated = False
+
+ hrnLabel = QLabel("Slice HRN:")
+ self.hrnEdit = QLineEdit()
+ urlLabel = QLabel("Project URL:")
+ self.urlEdit = QLineEdit()
+ descLabel = QLabel("Description")
+ self.descEdit = QTextEdit()
+
+ self.status = QLabel("")
+
+ self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
+
+ layout = QVBoxLayout()
+ layout.addWidget(hrnLabel)
+ layout.addWidget(self.hrnEdit)
+ layout.addWidget(urlLabel)
+ layout.addWidget(self.urlEdit)
+ layout.addWidget(descLabel)
+ layout.addWidget(self.descEdit)
+ layout.addWidget(self.status)
+ layout.addWidget(self.buttonBox)
+ self.setLayout(layout)
+
+ self.connect(self.buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
+ self.connect(self.buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
+
+ def accept(self):
+ auth = config.getAuthority()
+ desc = str(self.descEdit.toPlainText())
+ hrn = str(self.hrnEdit.text())
+ type = "slice"
+ url = str(self.urlEdit.text())
+
+ if not hrn.startswith(auth):
+ QMessageBox.warning(self, "Invalid HRN", "HRN must be within your current authority (%s)" % auth)
+ return
+
+ if not (url.startswith("http://") or url.startswith("https://")):
+ QMessageBox.warning(self, "Invalid URL", "URL must start with http:// or https://")
+ return
+
+ if not desc:
+ QMessageBox.warning(self, "Invalid Description", "Description is too short")
+ return
+
+ self.setStatus("Registering Slice...")
+
+ self.createProcess = SfiProcess(self)
+ self.connect(self.createProcess, SIGNAL('finished()'), self.createFinished)
+
+ self.buttonBox.setEnabled(False)
+
+ newSliceRecord = os.path.expanduser("~/.sfi/newslice.record")
+ file(newSliceRecord, "w").write('<record authority="%s" description="%s" hrn="%s" type="%s" url="%s"></record>' % (auth, desc, hrn, type, url))
+ self.createProcess.addRecord(newSliceRecord)
+
+ def setStatus(self, x):
+ self.status.setText(x)
+
+ def createFinished(self):
+ faultString = self.createProcess.getFaultString()
+ if not faultString:
+ self.setStatus("<font color='green'>Slice created.</font>")
+ self.sliceWasCreated = True
+ self.buttonBox.setEnabled(True)
+ self.buttonBox.clear()
+ self.buttonBox.addButton(QDialogButtonBox.Close)
+ else:
+ self.setStatus("<font color='red'>Slice creation failed: %s</font>" % (faultString))
+ self.sliceWasCreated = False
+ self.buttonBox.setEnabled(True)
+
+ self.disconnect(self.createProcess, SIGNAL('finished()'), self.createFinished)
+ self.createProcess = None
+
+ def getHrn(self):
+ return self.hrnEdit.text()
+
from PyQt4.QtCore import *
from sface.config import config
-from sface.xmlrpcwindow import XmlrpcTracker
+from sface.xmlrpcwindow import XmlrpcTracker, XmlrpcReader
def find_executable(exec_name):
"""find the given executable in $PATH"""
self.connect(self.process, SIGNAL("finished(int, QProcess::ExitStatus)"),
self.processFinished)
- self.xmlrpctracker = XmlrpcTracker()
+ self.xmlrpcreader = XmlrpcReader() # this one is for parsing XMLRPC responses
+ self.xmlrpctracker = XmlrpcTracker() # this one is for the debug window
# holds aggregate output from processStandardOutput(); used by xmlrpc
# tracker.
self.args << "-d"
self.args << config.get_dirname()
- if config.debug:
- # this shows xmlrpc conversation, see sfi.py docs.
- self.args << QString('-D')
+ # this shows xmlrpc conversation, see sfi.py docs.
+ # always do this, so we can parse the XML result for faults and show
+ # then to the users.
+ self.args << QString('-D')
+
for arg in args:
self.args << QString(arg)
print "ReadError"
elif err == QProcess.UnknownError:
print "UnknownError"
+
+ # extract any faults from the XMLRPC response(s)
+ self.xmlrpcreader.responses = []
+ self.xmlrpcreader.store(self.output)
+ self.xmlrpcreader.extractXml()
+ self.responses = self.xmlrpcreader.responses
+ self.faults = [x for x in self.responses if (x["kind"]=="fault")]
+
self.trace_end()
self.emit(SIGNAL("finished()"))
+ def getFaultString(self):
+ if self.faults == []:
+ return None
+
+ return self.faults[0].get("faultString","") + " (" + self.faults[0].get("faultCode","") + ")"
+
def __getRSpec(self, mgr):
slice = config.getSlice()
# Write RSpec to file for testing.
self.start()
def start(self):
+ self.respones = []
+ self.faults = []
self.output = ""
self.trace_command()
self.process.start(self.exe, self.args)
import time
from PyQt4.QtCore import *
+from PyQt4.QtGui import *
from sface.config import config
from sface.sfiprocess import SfiProcess
+#from sface.sfithread import SfiThread
class SfiRenewer(QObject):
def __init__(self, hrn, newExpiration, parent=None):
QObject.__init__(self, parent)
self.hrn = hrn
self.newExpiration = newExpiration
+ self.faultString = None
self.renewProcess = SfiProcess(self)
self.connect(self.renewProcess, SIGNAL('finished()'), self.finishedGetRecord)
self.renewProcess.getRecord(hrn=config.getSlice(), filename="/tmp/slicerecord")
def finishedGetRecord(self):
+ faultString = self.renewProcess.getFaultString()
+ if faultString:
+ self.emitFinished("fault", faultString)
+ return
+
f = open("/tmp/slicerecord", "r")
data = f.read()
f.close()
self.renewProcess.updateRecord("/tmp/slicerecord")
def finishedUpdateRecord(self):
+ faultString = self.renewProcess.getFaultString()
+ if faultString:
+ self.emitFinished("fault", faultString)
+ return
+
# we have to force sfi.py to download an updated slice credential
sliceCredName = config.fullpath("slice_" + self.hrn.split(".")[-1] + ".cred")
if os.path.exists(sliceCredName):
self.renewProcess.renewSlivers(self.newExpiration.strftime("%Y-%m-%dT%H:%M:%SZ"))
def finishedRenewSlivers(self):
+ faultString = self.renewProcess.getFaultString()
+ if faultString:
+ self.emitFinished("fault", faultString)
+ return
+
self.emitFinished("success")
def emitFinished(self, status, statusMsg=None):
self.statusMsg = statusMsg
self.emit(SIGNAL("finished()"))
+class RenewWindow(QDialog):
+ def __init__(self, parent=None):
+ super(RenewWindow, self).__init__(parent)
+ self.setWindowTitle("Renew Slivers")
+
+ self.renewProcess = None
+
+ self.duration = QComboBox()
+
+ self.expirations = []
+
+ durations = ( (1, "One Week"), (2, "Two Weeks"), (3, "Three Weeks"), (4, "One Month") )
+
+ now = datetime.datetime.utcnow()
+ for (weeks, desc) in durations:
+ exp = now + datetime.timedelta(days = weeks * 7)
+ desc = desc + " " + exp.strftime("%Y-%m-%d %H:%M:%S")
+ self.expirations.append(exp)
+ self.duration.addItem(desc)
+
+ self.duration.setCurrentIndex(0)
+
+ self.status = QLabel("")
+
+ self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
+
+ layout = QVBoxLayout()
+ layout.addWidget(self.duration)
+ layout.addWidget(self.status)
+ layout.addWidget(self.buttonBox)
+ self.setLayout(layout)
+
+ #self.status.hide()
+
+ self.connect(self.buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
+ self.connect(self.buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
+
+ def accept(self):
+ self.setStatus("Renewing Slice...")
+
+ self.renewProcess = SfiRenewer(config.getSlice(), self.get_new_expiration(), self)
+ self.connect(self.renewProcess, SIGNAL('finished()'), self.renewFinished)
+
+ self.duration.setEnabled(False)
+ self.buttonBox.setEnabled(False)
+
+ def setStatus(self, x):
+ self.status.setText(x)
+
+ def renewFinished(self):
+ if self.renewProcess.status == "success":
+ color = "green"
+ # give the user the <close> button
+ self.buttonBox.clear()
+ self.buttonBox.addButton(QDialogButtonBox.Close)
+ else:
+ color = "red"
+
+ if self.renewProcess.statusMsg:
+ self.setStatus("<font color='%s'>Renew %s: %s</font>" % (color, self.renewProcess.status, self.renewProcess.statusMsg))
+ else:
+ self.setStatus("<font color='%s'>Renew %s</font>" % (color, self.renewProcess.status))
+
+ self.buttonBox.setEnabled(True)
+
+ self.disconnect(self.renewProcess, SIGNAL('finished()'), self.renewFinished)
+ self.renewProcess = None
+
+ def get_new_expiration(self):
+ index = self.duration.currentIndex()
+ return self.expirations[index]
import re
+from lxml import etree
from PyQt4.QtXml import QDomDocument
from sface.xmlwidget import XmlWindow, DomModel
-class XmlrpcTracker():
+class XmlrpcReader():
def __init__(self):
- self.xmlrpcWindow = XmlrpcWindow()
+ self.rawOutput = None
+ self.responses = []
def getAndPrint(self, rawOutput):
self.store(rawOutput)
# only popup the window if we have something to show
self.showXmlrpc()
- def showXmlrpc(self):
- self.xmlrpcWindow.show()
- self.xmlrpcWindow.resize(500, 640)
- self.xmlrpcWindow.raise_()
- self.xmlrpcWindow.activateWindow()
-
def store(self, rawOutput):
self.rawOutput = rawOutput
+ def parseMethodResponse(self, mr):
+ mr = str(mr) # PyQT supplies a QByteArray; make it a string
+
+ response = {"kind": "unknown"}
+
+ try:
+ tree = etree.fromstring(mr)
+ except etree.XMLSyntaxError, e:
+ print "failed to parse XML response", str(e)
+ #file("badparse.xml","w").write(mr)
+ return response
+
+ if tree.tag != "methodResponse" or (len(list(tree))==0):
+ return response
+
+ # a fault should look like:
+ # kind: "fault"
+ # faultCode: "102"
+ # faultString: "Register: Missing authority..."
+
+ faults = tree.xpath("//methodResponse/fault")
+ for fault in faults:
+ response["kind"] = "fault"
+ structs = fault.xpath("value/struct")
+ for struct in structs:
+ members = struct.xpath("member")
+ for member in members:
+ names = member.xpath("name")
+ values = member.xpath("value")
+ if (names) and (values):
+ name = names[0]
+ value = values[0]
+ if len(list(value))>0:
+ data = list(value)[0]
+ response[name.text] = data.text
+ # once we have the first fault, return
+ return response
+
+ # whatever didn't fault must have succeeded?
+ response["kind"] = "success"
+
+ return response
+
def extractXml(self):
pttrnAsk = '<methodCall>.*?</methodCall>'
pttrnAns = '<methodResponse>.*?</methodResponse>'
replies = re.compile(pttrnAns, re.DOTALL).findall(self.rawOutput)
# cleaning
answers = [ x.replace('\\n','\n') for x in answers ]
- replies = [ x.replace('\\n','\n').replace("'\nbody: '", '') for x in replies ]
+ replies = [ x.replace('\\n','\n').replace("'\nbody: '", '').replace("\"\nbody: '", '') for x in replies ]
replies.reverse() # so that I use pop() as popleft
# A well-formed XML document must have one, and only one, top-level element
- self.xml = '<debug>'
+
+ self.responses = []
+
+ self.xml = ""
for ans in answers:
- self.xml += ans + replies.pop()
- self.xml += '</debug>'
+ self.xml += ans
+ # we could have less responses than calls, so guard the pop
+ if replies:
+ replyXml = replies.pop()
+ self.xml += replyXml
+ self.responses.append(self.parseMethodResponse(replyXml))
+
+ # just in case somehow we ended up with more responses than calls
+ while replies:
+ replyXml = replies.pop()
+ self.xml += replyXml
+ self.responses.append(self.parseMethodResponse(replyXml))
+
+ return self.xml
def stats(self):
# statistics: round-trip time, size of the com
pass
+class XmlrpcTracker(XmlrpcReader):
+ def __init__(self):
+ XmlrpcReader.__init__(self)
+ self.xmlrpcWindow = XmlrpcWindow()
+
+ def getAndPrint(self, rawOutput):
+ self.store(rawOutput)
+ self.extractXml()
+ self.xmlrpcWindow.setData(self.xml)
+ if self.xml != "<debug></debug>":
+ # only popup the window if we have something to show
+ self.showXmlrpc()
+
+ def showXmlrpc(self):
+ self.xmlrpcWindow.show()
+ self.xmlrpcWindow.resize(500, 640)
+ self.xmlrpcWindow.raise_()
+ self.xmlrpcWindow.activateWindow()
+
+ def extractXml(self):
+ self.xml = "<debug>" + XmlrpcReader.extractXml(self) + "</debug>"
+
class XmlrpcWindow(XmlWindow):
def __init__(self, parent=None):
# super __init__() calls updateView,