# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2010-2011 Nexedi SA and Contributors. All Rights Reserved. # Ćukasz Nowak <luke@nexedi.com> # Romain Courteaud <romain@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsibility of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # guarantees and support are strongly advised to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## from AccessControl import ClassSecurityInfo from AccessControl import Unauthorized from AccessControl.Permissions import access_contents_information from AccessControl import getSecurityManager from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from OFS.Traversable import NotFound from Products.DCWorkflow.DCWorkflow import ValidationFailed from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type import Permissions from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE from Products.ERP5Type.Cache import CachingMethod from lxml import etree import time from Products.ERP5Type.tests.utils import DummyMailHostMixin try: from slapos.slap.slap import Computer from slapos.slap.slap import ComputerPartition as SlapComputerPartition from slapos.slap.slap import SoftwareInstance from slapos.slap.slap import SoftwareRelease except ImportError: # Do no prevent instance from starting # if libs are not installed class Computer: def __init__(self): raise ImportError class SlapComputerPartition: def __init__(self): raise ImportError class SoftwareInstance: def __init__(self): raise ImportError class SoftwareRelease: def __init__(self): raise ImportError from zLOG import LOG, INFO import xml_marshaller import StringIO import pkg_resources from Products.Vifib.Conduit import VifibConduit import json from DateTime import DateTime from App.Common import rfc1123_date class SoftwareInstanceNotReady(Exception): pass def convertToREST(function): """ Wrap the method to create a log entry for each invocation to the zope logger """ def wrapper(self, *args, **kwd): """ Log the call, and the result of the call """ try: retval = function(self, *args, **kwd) except (ValueError, AttributeError), log: LOG('SlapTool', INFO, 'Converting ValueError to NotFound, real error:', error=True) raise NotFound(log) except SoftwareInstanceNotReady, log: self.REQUEST.response.setStatus(408) self.REQUEST.response.setHeader('Cache-Control', 'private') return self.REQUEST.response except ValidationFailed: LOG('SlapTool', INFO, 'Converting ValidationFailed to ValidationFailed,'\ ' real error:', error=True) raise ValidationFailed except Unauthorized: LOG('SlapTool', INFO, 'Converting Unauthorized to Unauthorized,'\ ' real error:', error=True) raise Unauthorized self.REQUEST.response.setHeader('Content-Type', 'text/xml; charset=utf-8') return '%s' % retval wrapper.__doc__ = function.__doc__ return wrapper def _assertACI(document): sm = getSecurityManager() if sm.checkPermission(access_contents_information, document): return document raise Unauthorized('User %r has no access to %r' % (sm.getUser(), document)) _MARKER = [] class SlapTool(BaseTool): """SlapTool""" # TODO: # * catch and convert exceptions to HTTP codes (be restful) id = 'portal_slap' meta_type = 'ERP5 Slap Tool' portal_type = 'Slap Tool' security = ClassSecurityInfo() allowed_types = () security.declarePrivate('manage_afterAdd') def manage_afterAdd(self, item, container) : """Init permissions right after creation. Permissions in slap tool are simple: o Each member can access the tool. o Only manager can view and create. o Anonymous can not access """ item.manage_permission(Permissions.AddPortalContent, ['Manager']) item.manage_permission(Permissions.AccessContentsInformation, ['Member', 'Manager']) item.manage_permission(Permissions.View, ['Manager',]) BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container) #################################################### # Public GET methods #################################################### def _isTestRun(self): if issubclass(self.getPortalObject().MailHost.__class__, DummyMailHostMixin) \ or self.REQUEST.get('test_list'): return True return False def _getCachePlugin(self): return self.getPortalObject().portal_caches\ .getRamCacheRoot().get('computer_information_cache_factory')\ .getCachePluginList()[0] def _getCacheComputerInformation(self, computer_id, user): self.REQUEST.response.setHeader('Content-Type', 'text/xml; charset=utf-8') slap_computer = Computer(computer_id.decode("UTF-8")) parent_uid = self._getComputerUidByReference(computer_id) slap_computer._computer_partition_list = [] slap_computer._software_release_list = \ self._getSoftwareReleaseValueListForComputer(computer_id) for computer_partition in self.getPortalObject().portal_catalog.unrestrictedSearchResults( parent_uid=parent_uid, validation_state="validated", portal_type="Computer Partition"): slap_computer._computer_partition_list.append( self._getSlapPartitionByPackingList(_assertACI(computer_partition.getObject()))) return xml_marshaller.xml_marshaller.dumps(slap_computer) def _fillComputerInformationCache(self, computer_id, user): key = '%s_%s' % (computer_id, user) try: self._getCachePlugin().set(key, DEFAULT_CACHE_SCOPE, dict ( time=time.time(), data=self._getCacheComputerInformation(computer_id, user), ), cache_duration=self.getPortalObject().portal_caches\ .getRamCacheRoot().get('computer_information_cache_factory'\ ).cache_duration ) except (Unauthorized, IndexError): # XXX: Unauthorized hack. Race condition of not ready setup delivery which provides # security information shall not make this method fail, as it will be # called later anyway # Note: IndexError ignored, as it happend in case if full reindex is # called on site pass def _storeLastData(self, key, value): cache_plugin = self.getPortalObject().portal_caches\ .getRamCacheRoot().get('last_stored_data_cache_factory')\ .getCachePluginList()[0] cache_plugin.set(key, DEFAULT_CACHE_SCOPE, value, cache_duration=self.getPortalObject().portal_caches\ .getRamCacheRoot().get('last_stored_data_cache_factory').cache_duration) def _getLastData(self, key): cache_plugin = self.getPortalObject().portal_caches\ .getRamCacheRoot().get('last_stored_data_cache_factory')\ .getCachePluginList()[0] try: entry = cache_plugin.get(key, DEFAULT_CACHE_SCOPE) except KeyError: entry = None else: entry = entry.getValue() return entry def _activateFillComputerInformationCache(self, computer_id, user): tag = 'computer_information_cache_fill_%s_%s' % (computer_id, user) if self.getPortalObject().portal_activities.countMessageWithTag(tag) == 0: self.activate(activity='SQLQueue', tag=tag)._fillComputerInformationCache( computer_id, user) def _getComputerInformation(self, computer_id, user): user_document = _assertACI(self.getPortalObject().portal_catalog.unrestrictedGetResultValue( reference=user, portal_type=['Person', 'Computer', 'Software Instance'])) user_type = user_document.getPortalType() self.REQUEST.response.setHeader('Content-Type', 'text/xml; charset=utf-8') slap_computer = Computer(computer_id.decode("UTF-8")) parent_uid = self._getComputerUidByReference(computer_id) slap_computer._computer_partition_list = [] if user_type in ('Computer', 'Person'): if not self._isTestRun(): cache_plugin = self._getCachePlugin() try: key = '%s_%s' % (computer_id, user) entry = cache_plugin.get(key, DEFAULT_CACHE_SCOPE) except KeyError: entry = None if entry is not None and type(entry.getValue()) == type({}): result = entry.getValue()['data'] self._activateFillComputerInformationCache(computer_id, user) return result else: self._activateFillComputerInformationCache(computer_id, user) self.REQUEST.response.setStatus(503) return self.REQUEST.response else: return self._getCacheComputerInformation(computer_id, user) # return self._getCacheComputerInformation(computer_id, user) else: slap_computer._software_release_list = [] if user_type == 'Software Instance': computer = self.getPortalObject().portal_catalog.unrestrictedSearchResults( portal_type='Computer', reference=computer_id, validation_state="validated")[0].getObject() computer_partition_list = computer.contentValues( portal_type="Computer Partition", checked_permission="View") else: computer_partition_list = self.getPortalObject().portal_catalog.unrestrictedSearchResults( parent_uid=parent_uid, validation_state="validated", portal_type="Computer Partition") for computer_partition in computer_partition_list: slap_computer._computer_partition_list.append( self._getSlapPartitionByPackingList(_assertACI(computer_partition.getObject()))) return xml_marshaller.xml_marshaller.dumps(slap_computer) security.declareProtected(Permissions.AccessContentsInformation, 'getFullComputerInformation') def getFullComputerInformation(self, computer_id): """Returns marshalled XML of all needed information for computer Includes Software Releases, which may contain Software Instances. Reuses slap library for easy marshalling. """ user = self.getPortalObject().portal_membership.getAuthenticatedMember().getUserName() if str(user) == computer_id: self._logAccess(user, user, '#access %s' % computer_id) result = self._getComputerInformation(computer_id, user) if self.REQUEST.response.getStatus() == 200: # Keep in cache server for 7 days self.REQUEST.response.setHeader('Cache-Control', 'public, max-age=1, stale-if-error=604800') self.REQUEST.response.setHeader('Vary', 'REMOTE_USER') self.REQUEST.response.setHeader('Last-Modified', rfc1123_date(DateTime())) self.REQUEST.response.setBody(result) return self.REQUEST.response else: return result security.declareProtected(Permissions.AccessContentsInformation, 'getComputerPartitionCertificate') def getComputerPartitionCertificate(self, computer_id, computer_partition_id): """Method to fetch certificate""" self.REQUEST.response.setHeader('Content-Type', 'text/xml; charset=utf-8') software_instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) certificate_dict = dict( key=software_instance.getSslKey(), certificate=software_instance.getSslCertificate() ) result = xml_marshaller.xml_marshaller.dumps(certificate_dict) # Cache with revalidation self.REQUEST.response.setStatus(200) self.REQUEST.response.setHeader('Cache-Control', 'public, max-age=0, must-revalidate') self.REQUEST.response.setHeader('Vary', 'REMOTE_USER') self.REQUEST.response.setHeader('Last-Modified', rfc1123_date(software_instance.getModificationDate())) self.REQUEST.response.setBody(result) return self.REQUEST.response security.declareProtected(Permissions.AccessContentsInformation, 'getComputerInformation') getComputerInformation = getFullComputerInformation security.declareProtected(Permissions.AccessContentsInformation, 'getComputerPartitionStatus') def getComputerPartitionStatus(self, computer_id, computer_partition_id): """ Get the connection status of the partition """ try: instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) except NotFound: return self._getAccessStatus(None) else: return self._getAccessStatus(instance.getReference()) security.declareProtected(Permissions.AccessContentsInformation, 'getComputerStatus') def getComputerStatus(self, computer_id): """ Get the connection status of the partition """ computer = self.getPortalObject().portal_catalog.unrestrictedSearchResults( portal_type='Computer', reference=computer_id, validation_state="validated")[0].getObject() # Be sure to prevent accessing information to disallowed users computer = _assertACI(computer) return self._getAccessStatus(computer_id) #################################################### # Public POST methods #################################################### security.declareProtected(Permissions.AccessContentsInformation, 'setComputerPartitionConnectionXml') def setComputerPartitionConnectionXml(self, computer_id, computer_partition_id, connection_xml, slave_reference=None): """ Set instance parameter informations on the slagrid server """ # When None is passed in POST, it is converted to string if slave_reference is not None and slave_reference.lower() == "none": slave_reference = None return self._setComputerPartitionConnectionXml(computer_id, computer_partition_id, connection_xml, slave_reference) security.declareProtected(Permissions.AccessContentsInformation, 'supplySupply') def supplySupply(self, url, computer_id, state='available'): """ Request Software Release installation """ return self._supplySupply(url, computer_id, state) @convertToREST def _requestComputer(self, computer_title): portal = self.getPortalObject() person = portal.ERP5Site_getAuthenticatedMemberPersonValue() person.requestComputer(computer_title=computer_title) computer = Computer(self.REQUEST.get('computer_reference').decode("UTF-8")) return xml_marshaller.xml_marshaller.dumps(computer) security.declareProtected(Permissions.AccessContentsInformation, 'requestComputer') def requestComputer(self, computer_title): """ Request Computer """ return self._requestComputer(computer_title) security.declareProtected(Permissions.AccessContentsInformation, 'buildingSoftwareRelease') def buildingSoftwareRelease(self, url, computer_id): """ Reports that Software Release is being build """ return self._buildingSoftwareRelease(url, computer_id) security.declareProtected(Permissions.AccessContentsInformation, 'availableSoftwareRelease') def availableSoftwareRelease(self, url, computer_id): """ Reports that Software Release is available """ return self._availableSoftwareRelease(url, computer_id) security.declareProtected(Permissions.AccessContentsInformation, 'destroyedSoftwareRelease') def destroyedSoftwareRelease(self, url, computer_id): """ Reports that Software Release is available """ return self._destroyedSoftwareRelease(url, computer_id) security.declareProtected(Permissions.AccessContentsInformation, 'softwareReleaseError') def softwareReleaseError(self, url, computer_id, error_log): """ Add an error for a software Release workflow """ return self._softwareReleaseError(url, computer_id, error_log) security.declareProtected(Permissions.AccessContentsInformation, 'buildingComputerPartition') def buildingComputerPartition(self, computer_id, computer_partition_id): """ Reports that Computer Partition is being build """ return self._buildingComputerPartition(computer_id, computer_partition_id) security.declareProtected(Permissions.AccessContentsInformation, 'availableComputerPartition') def availableComputerPartition(self, computer_id, computer_partition_id): """ Reports that Computer Partition is available """ return self._availableComputerPartition(computer_id, computer_partition_id) security.declareProtected(Permissions.AccessContentsInformation, 'softwareInstanceError') def softwareInstanceError(self, computer_id, computer_partition_id, error_log): """ Add an error for the software Instance Workflow """ return self._softwareInstanceError(computer_id, computer_partition_id, error_log) security.declareProtected(Permissions.AccessContentsInformation, 'softwareInstanceRename') def softwareInstanceRename(self, new_name, computer_id, computer_partition_id, slave_reference=None): """ Change the title of a Software Instance using Workflow. """ return self._softwareInstanceRename(new_name, computer_id, computer_partition_id, slave_reference) security.declareProtected(Permissions.AccessContentsInformation, 'softwareInstanceBang') def softwareInstanceBang(self, computer_id, computer_partition_id, message): """ Fire up bang on this Software Instance """ return self._softwareInstanceBang(computer_id, computer_partition_id, message) security.declareProtected(Permissions.AccessContentsInformation, 'startedComputerPartition') def startedComputerPartition(self, computer_id, computer_partition_id): """ Reports that Computer Partition is started """ return self._startedComputerPartition(computer_id, computer_partition_id) security.declareProtected(Permissions.AccessContentsInformation, 'stoppedComputerPartition') def stoppedComputerPartition(self, computer_id, computer_partition_id): """ Reports that Computer Partition is stopped """ return self._stoppedComputerPartition(computer_id, computer_partition_id) security.declareProtected(Permissions.AccessContentsInformation, 'destroyedComputerPartition') def destroyedComputerPartition(self, computer_id, computer_partition_id): """ Reports that Computer Partition is destroyed """ return self._destroyedComputerPartition(computer_id, computer_partition_id) security.declareProtected(Permissions.AccessContentsInformation, 'requestComputerPartition') def requestComputerPartition(self, computer_id=None, computer_partition_id=None, software_release=None, software_type=None, partition_reference=None, partition_parameter_xml=None, filter_xml=None, state=None, shared_xml=_MARKER): """ Asynchronously requests creation of computer partition for assigned parameters Returns XML representation of partition with HTTP code 200 OK In case if this request is still being processed data contain "Computer Partition is being processed" and HTTP code is 408 Request Timeout In any other case returns not important data and HTTP code is 403 Forbidden """ return self._requestComputerPartition(computer_id, computer_partition_id, software_release, software_type, partition_reference, shared_xml, partition_parameter_xml, filter_xml, state) security.declareProtected(Permissions.AccessContentsInformation, 'useComputer') def useComputer(self, computer_id, use_string): """ Entry point to reporting usage of a computer. """ computer_consumption_model = \ pkg_resources.resource_string( 'slapos.slap', 'doc/computer_consumption.xsd') if self._validateXML(use_string, computer_consumption_model): computer = self._getComputerDocument(computer_id) tree = etree.fromstring(use_string) source_reference = \ tree.find('transaction').find('reference').text or "" source_reference = source_reference.encode("UTF-8") computer.Computer_reportComputerConsumption( source_reference, use_string) return "OK" else: self.REQUEST.response.setStatus(400) self.REQUEST.response.setBody("") return self.REQUEST.response @convertToREST def _computerBang(self, computer_id, message): """ Fire up bung on Computer """ user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess(user, computer_id, '#error bang') return self._getComputerDocument(computer_id).reportComputerBang( comment=message) security.declareProtected(Permissions.AccessContentsInformation, 'computerBang') def computerBang(self, computer_id, message): """ Fire up bang on this Software Instance """ return self._computerBang(computer_id, message) security.declareProtected(Permissions.AccessContentsInformation, 'loadComputerConfigurationFromXML') def loadComputerConfigurationFromXML(self, xml): "Load the given xml as configuration for the computer object" computer_dict = xml_marshaller.xml_marshaller.loads(xml) computer = self._getComputerDocument(computer_dict['reference']) computer.Computer_updateFromDict(computer_dict) return 'Content properly posted.' security.declareProtected(Permissions.AccessContentsInformation, 'useComputerPartition') def useComputerPartition(self, computer_id, computer_partition_id, use_string): """Warning : deprecated method.""" computer_document = self._getComputerDocument(computer_id) computer_partition_document = self._getComputerPartitionDocument( computer_document.getReference(), computer_partition_id) # easy way to start to store usage messages sent by client in related Web # Page text_content... self._reportUsage(computer_partition_document, use_string) return """Content properly posted. WARNING : this method is deprecated. Please use useComputer.""" @convertToREST def _generateComputerCertificate(self, computer_id): self._getComputerDocument(computer_id).generateCertificate() result = { 'certificate': self.REQUEST.get('computer_certificate').decode("UTF-8"), 'key': self.REQUEST.get('computer_key').decode("UTF-8") } return xml_marshaller.xml_marshaller.dumps(result) security.declareProtected(Permissions.AccessContentsInformation, 'generateComputerCertificate') def generateComputerCertificate(self, computer_id): """Fetches new computer certificate""" return self._generateComputerCertificate(computer_id) @convertToREST def _revokeComputerCertificate(self, computer_id): self._getComputerDocument(computer_id).revokeCertificate() security.declareProtected(Permissions.AccessContentsInformation, 'revokeComputerCertificate') def revokeComputerCertificate(self, computer_id): """Revokes existing computer certificate""" return self._revokeComputerCertificate(computer_id) security.declareProtected(Permissions.AccessContentsInformation, 'registerComputerPartition') def registerComputerPartition(self, computer_reference, computer_partition_reference): """ Registers connected representation of computer partition and returns Computer Partition class object """ # Try to get the computer partition to raise an exception if it doesn't # exist portal = self.getPortalObject() computer_partition_document = self._getComputerPartitionDocument( computer_reference, computer_partition_reference) slap_partition = SlapComputerPartition(computer_reference.decode("UTF-8"), computer_partition_reference.decode("UTF-8")) slap_partition._software_release_document = None slap_partition._requested_state = 'destroyed' slap_partition._need_modification = 0 software_instance = None if computer_partition_document.getSlapState() == 'busy': software_instance_list = portal.portal_catalog.unrestrictedSearchResults( portal_type="Software Instance", default_aggregate_uid=computer_partition_document.getUid(), validation_state="validated", limit=2, ) software_instance_count = len(software_instance_list) if software_instance_count == 1: software_instance = _assertACI(software_instance_list[0].getObject()) elif software_instance_count > 1: # XXX do not prevent the system to work if one partition is broken raise NotImplementedError, "Too many instances %s linked to %s" % \ ([x.path for x in software_instance_list], computer_partition_document.getRelativeUrl()) if software_instance is not None: # trick client side, that data has been synchronised already for given # document slap_partition._synced = True state = software_instance.getSlapState() if state == "stop_requested": slap_partition._requested_state = 'stopped' if state == "start_requested": slap_partition._requested_state = 'started' slap_partition._software_release_document = SoftwareRelease( software_release=software_instance.getUrlString().decode("UTF-8"), computer_guid=computer_reference.decode("UTF-8")) slap_partition._software_release_document._software_release = \ slap_partition._software_release_document._software_release.decode("UTF-8") slap_partition._need_modification = 1 parameter_dict = self._getSoftwareInstanceAsParameterDict( software_instance) # software instance has to define an xml parameter slap_partition._parameter_dict = self._instanceXmlToDict( parameter_dict.pop('xml')) slap_partition._connection_dict = self._instanceXmlToDict( parameter_dict.pop('connection_xml')) slap_partition._instance_guid = parameter_dict.pop('instance_guid') for slave_instance_dict in parameter_dict.get("slave_instance_list", []): if slave_instance_dict.has_key("connection_xml"): slave_instance_dict.update(self._instanceXmlToDict( slave_instance_dict.pop("connection_xml"))) if slave_instance_dict.has_key("xml"): slave_instance_dict.update(self._instanceXmlToDict( slave_instance_dict.pop("xml"))) slap_partition._parameter_dict.update(parameter_dict) result = xml_marshaller.xml_marshaller.dumps(slap_partition) # Keep in cache server for 7 days self.REQUEST.response.setStatus(200) self.REQUEST.response.setHeader('Cache-Control', 'public, max-age=1, stale-if-error=604800') self.REQUEST.response.setHeader('Vary', 'REMOTE_USER') self.REQUEST.response.setHeader('Last-Modified', rfc1123_date(DateTime())) self.REQUEST.response.setHeader('Content-Type', 'text/xml; charset=utf-8') self.REQUEST.response.setBody(result) return self.REQUEST.response #################################################### # Internal methods #################################################### def _getMemcachedDict(self): return self.getPortalObject().portal_memcached.getMemcachedDict( key_prefix='slap_tool', plugin_path='portal_memcached/default_memcached_plugin') def _logAccess(self, user_reference, context_reference, text): memcached_dict = self._getMemcachedDict() value = json.dumps({ 'user': '%s' % user_reference, 'created_at': '%s' % rfc1123_date(DateTime()), 'text': '%s' % text, }) memcached_dict[context_reference] = value def _validateXML(self, to_be_validated, xsd_model): """Will validate the xml file""" #We parse the XSD model xsd_model = StringIO.StringIO(xsd_model) xmlschema_doc = etree.parse(xsd_model) xmlschema = etree.XMLSchema(xmlschema_doc) string_to_validate = StringIO.StringIO(to_be_validated) try: document = etree.parse(string_to_validate) except (etree.XMLSyntaxError, etree.DocumentInvalid) as e: LOG('SlapTool::_validateXML', INFO, 'Failed to parse this XML reports : %s\n%s' % \ (to_be_validated, e)) return False if xmlschema.validate(document): return True return False def _instanceXmlToDict(self, xml): result_dict = {} try: if xml is not None and xml != '': tree = etree.fromstring(xml) for element in tree.findall('parameter'): key = element.get('id') value = result_dict.get(key, None) if value is not None: value = value + ' ' + element.text else: value = element.text result_dict[key] = value except (etree.XMLSchemaError, etree.XMLSchemaParseError, etree.XMLSchemaValidateError, etree.XMLSyntaxError): LOG('SlapTool', INFO, 'Issue during parsing xml:', error=True) return result_dict def _getSlapPartitionByPackingList(self, computer_partition_document): computer = computer_partition_document portal = self.getPortalObject() while computer.getPortalType() != 'Computer': computer = computer.getParentValue() computer_id = computer.getReference().decode("UTF-8") slap_partition = SlapComputerPartition(computer_id, computer_partition_document.getReference().decode("UTF-8")) slap_partition._software_release_document = None slap_partition._requested_state = 'destroyed' slap_partition._need_modification = 0 software_instance = None if computer_partition_document.getSlapState() == 'busy': software_instance_list = portal.portal_catalog.unrestrictedSearchResults( portal_type="Software Instance", default_aggregate_uid=computer_partition_document.getUid(), validation_state="validated", limit=2, ) software_instance_count = len(software_instance_list) if software_instance_count == 1: software_instance = _assertACI(software_instance_list[0].getObject()) elif software_instance_count > 1: # XXX do not prevent the system to work if one partition is broken raise NotImplementedError, "Too many instances %s linked to %s" % \ ([x.path for x in software_instance_list], computer_partition_document.getRelativeUrl()) if software_instance is not None: state = software_instance.getSlapState() if state == "stop_requested": slap_partition._requested_state = 'stopped' if state == "start_requested": slap_partition._requested_state = 'started' slap_partition._software_release_document = SoftwareRelease( software_release=software_instance.getUrlString().decode("UTF-8"), computer_guid=computer_id) slap_partition._software_release_document._software_release = \ slap_partition._software_release_document._software_release.decode("UTF-8") slap_partition._need_modification = 1 parameter_dict = self._getSoftwareInstanceAsParameterDict( software_instance) # software instance has to define an xml parameter slap_partition._parameter_dict = self._instanceXmlToDict( parameter_dict.pop('xml')) slap_partition._connection_dict = self._instanceXmlToDict( parameter_dict.pop('connection_xml')) slap_partition._instance_guid = parameter_dict.pop('instance_guid') for slave_instance_dict in parameter_dict.get("slave_instance_list", []): if slave_instance_dict.has_key("connection_xml"): slave_instance_dict.update(self._instanceXmlToDict( slave_instance_dict.pop("connection_xml"))) if slave_instance_dict.has_key("xml"): slave_instance_dict.update(self._instanceXmlToDict( slave_instance_dict.pop("xml"))) slap_partition._parameter_dict.update(parameter_dict) return slap_partition @convertToREST def _supplySupply(self, url, computer_id, state): """ Request Software Release installation """ computer_document = self._getComputerDocument(computer_id) computer_document.requestSoftwareRelease(software_release_url=url, state=state) @convertToREST def _buildingSoftwareRelease(self, url, computer_id): """ Log the computer status """ user = self.getPortalObject().portal_membership.\ getAuthenticatedMember().getUserName() self._logAccess(user, user, 'building software release %s' % url) @convertToREST def _availableSoftwareRelease(self, url, computer_id): """ Log the computer status """ user = self.getPortalObject().portal_membership.\ getAuthenticatedMember().getUserName() self._logAccess(user, user, '#access software release %s available' % \ url) @convertToREST def _destroyedSoftwareRelease(self, url, computer_id): """ Reports that Software Release is destroyed """ computer_document = self._getComputerDocument(computer_id) software_installation = self._getSoftwareInstallationForComputer(url, computer_document) if software_installation.getSlapState() != 'destroy_requested': raise NotFound if self.getPortalObject().portal_workflow.isTransitionPossible(software_installation, 'invalidate'): software_installation.invalidate( comment="Software Release destroyed report.") @convertToREST def _buildingComputerPartition(self, computer_id, computer_partition_id): """ Log the computer status """ instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess(user, instance.getReference(), 'building the instance') @convertToREST def _availableComputerPartition(self, computer_id, computer_partition_id): """ Log the computer status """ instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess(user, instance.getReference(), '#access instance available') @convertToREST def _softwareInstanceError(self, computer_id, computer_partition_id, error_log): """ Add an error for the software Instance Workflow """ instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess(user, instance.getReference(), '#error while instanciating') #return instance.reportComputerPartitionError() @convertToREST def _softwareInstanceRename(self, new_name, computer_id, computer_partition_id, slave_reference): software_instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id, slave_reference) hosting = software_instance.getSpecialise() for name in [software_instance.getTitle(), new_name]: # reset request cache key = '_'.join([hosting, name]) self._storeLastData(key, {}) return software_instance.rename(new_name=new_name, comment="Rename %s into %s" % (software_instance.title, new_name)) @convertToREST def _softwareInstanceBang(self, computer_id, computer_partition_id, message): """ Fire up bang on Software Instance Add an error for the software Instance Workflow """ software_instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) user = self.getPortalObject().portal_membership.\ getAuthenticatedMember().getUserName() self._logAccess(user, software_instance.getReference(), '#error bang called') return software_instance.bang(bang_tree=True, comment=message) def _getAccessStatus(self, context_reference): memcached_dict = self._getMemcachedDict() try: if context_reference is None: raise KeyError else: d = memcached_dict[context_reference] except KeyError: if context_reference is None: d = { "user": "SlapOS Master", 'created_at': '%s' % rfc1123_date(DateTime()), "text": "#error no data found" } else: d = { "user": "SlapOS Master", 'created_at': '%s' % rfc1123_date(DateTime()), "text": "#error no data found for %s" % context_reference } # Prepare for xml marshalling d["user"] = d["user"].decode("UTF-8") d["text"] = d["text"].decode("UTF-8") else: d = json.loads(d) # Keep in cache server for 7 days self.REQUEST.response.setStatus(200) self.REQUEST.response.setHeader('Cache-Control', 'public, max-age=60, stale-if-error=604800') self.REQUEST.response.setHeader('Vary', 'REMOTE_USER') self.REQUEST.response.setHeader('Last-Modified', rfc1123_date(DateTime())) self.REQUEST.response.setHeader('Content-Type', 'text/xml; charset=utf-8') self.REQUEST.response.setBody(xml_marshaller.xml_marshaller.dumps(d)) return self.REQUEST.response @convertToREST def _startedComputerPartition(self, computer_id, computer_partition_id): """ Log the computer status """ instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess(user, instance.getReference(), '#access Instance correctly started') @convertToREST def _stoppedComputerPartition(self, computer_id, computer_partition_id): """ Log the computer status """ instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess(user, instance.getReference(), '#access Instance correctly stopped') @convertToREST def _destroyedComputerPartition(self, computer_id, computer_partition_id): """ Reports that Computer Partition is destroyed """ instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id) if instance.getSlapState() == 'destroy_requested': # remove certificate from SI if instance.getSslKey() is not None or instance.getSslCertificate() is not None: instance.edit( ssl_key=None, ssl_certificate=None, ) if instance.getValidationState() == 'validated': instance.invalidate() # XXX Integrate with REST API # Code duplication will be needed until SlapTool is removed # revoke certificate portal = self.getPortalObject() try: portal.portal_certificate_authority\ .revokeCertificate(instance.getDestinationReference()) except ValueError: # Ignore already revoked certificates, as OpenSSL backend is # non transactional, so it is ok to allow multiple tries to destruction # even if certificate was already revoked pass @convertToREST def _setComputerPartitionConnectionXml(self, computer_id, computer_partition_id, connection_xml, slave_reference=None): """ Sets Computer Partition connection Xml """ software_instance = self._getSoftwareInstanceForComputerPartition( computer_id, computer_partition_id, slave_reference) partition_parameter_kw = xml_marshaller.xml_marshaller.loads( connection_xml) instance = etree.Element('instance') for parameter_id, parameter_value in partition_parameter_kw.iteritems(): etree.SubElement(instance, "parameter", attrib={'id':parameter_id}).text = parameter_value connection_xml = etree.tostring(instance, pretty_print=True, xml_declaration=True, encoding='utf-8') reference = software_instance.getReference() if self._getLastData(reference) != connection_xml: software_instance.updateConnection( connection_xml=connection_xml, ) self._storeLastData(reference, connection_xml) @convertToREST def _requestComputerPartition(self, computer_id, computer_partition_id, software_release, software_type, partition_reference, shared_xml, partition_parameter_xml, filter_xml, state): """ Asynchronously requests creation of computer partition for assigned parameters Returns XML representation of partition with HTTP code 200 OK In case if this request is still being processed data contain "Computer Partition is being processed" and HTTP code is 408 Request Timeout In any other case returns not important data and HTTP code is 403 Forbidden """ if state: state = xml_marshaller.xml_marshaller.loads(state) if state is None: state = 'started' if shared_xml is not _MARKER: shared = xml_marshaller.xml_marshaller.loads(shared_xml) else: shared = False if partition_parameter_xml: partition_parameter_kw = xml_marshaller.xml_marshaller.loads( partition_parameter_xml) else: partition_parameter_kw = dict() if filter_xml: filter_kw = xml_marshaller.xml_marshaller.loads(filter_xml) else: filter_kw = dict() instance = etree.Element('instance') for parameter_id, parameter_value in partition_parameter_kw.iteritems(): # cast everything to string parameter_value = str(parameter_value) etree.SubElement(instance, "parameter", attrib={'id':parameter_id}).text = parameter_value instance_xml = etree.tostring(instance, pretty_print=True, xml_declaration=True, encoding='utf-8') instance = etree.Element('instance') for parameter_id, parameter_value in filter_kw.iteritems(): # cast everything to string parameter_value = str(parameter_value) etree.SubElement(instance, "parameter", attrib={'id':parameter_id}).text = parameter_value sla_xml = etree.tostring(instance, pretty_print=True, xml_declaration=True, encoding='utf-8') portal = self.getPortalObject() if computer_id and computer_partition_id: # requested by Software Instance, there is already top part of tree software_instance_document = self.\ _getSoftwareInstanceForComputerPartition(computer_id, computer_partition_id) kw = dict(software_release=software_release, software_type=software_type, software_title=partition_reference, instance_xml=instance_xml, shared=shared, sla_xml=sla_xml, state=state) key = '_'.join([software_instance_document.getSpecialise(), partition_reference]) value = dict( hash='_'.join([software_instance_document.getRelativeUrl(), str(kw)]), ) last_data = self._getLastData(key) requested_software_instance = None if last_data is not None and type(last_data) == type({}): requested_software_instance = portal.restrictedTraverse( last_data.get('request_instance'), None) if last_data is None or type(last_data) != type(value) or \ last_data.get('hash') != value['hash'] or \ requested_software_instance is None: software_instance_document.requestInstance(**kw) requested_software_instance = self.REQUEST.get('request_instance') if requested_software_instance is not None: value['request_instance'] = requested_software_instance\ .getRelativeUrl() self._storeLastData(key, value) else: # requested as root, so done by human person = portal.ERP5Site_getAuthenticatedMemberPersonValue() kw = dict(software_release=software_release, software_type=software_type, software_title=partition_reference, shared=shared, instance_xml=instance_xml, sla_xml=sla_xml, state=state) key = '_'.join([person.getRelativeUrl(), partition_reference]) value = dict( hash=str(kw) ) last_data = self._getLastData(key) if last_data is not None and type(last_data) == type({}): requested_software_instance = portal.restrictedTraverse( last_data.get('request_instance'), None) if last_data is None or type(last_data) != type(value) or \ last_data.get('hash') != value['hash'] or \ requested_software_instance is None: person.requestSoftwareInstance(**kw) requested_software_instance = self.REQUEST.get('request_instance') if requested_software_instance is not None: value['request_instance'] = requested_software_instance\ .getRelativeUrl() self._storeLastData(key, value) if requested_software_instance is None: raise SoftwareInstanceNotReady else: if not requested_software_instance.getAggregate(portal_type="Computer Partition"): raise SoftwareInstanceNotReady else: parameter_dict = self._getSoftwareInstanceAsParameterDict(requested_software_instance) # software instance has to define an xml parameter xml = self._instanceXmlToDict( parameter_dict.pop('xml')) connection_xml = self._instanceXmlToDict( parameter_dict.pop('connection_xml')) instance_guid = parameter_dict.pop('instance_guid') software_instance = SoftwareInstance(**parameter_dict) software_instance._parameter_dict = xml software_instance._connection_dict = connection_xml software_instance._requested_state = state software_instance._instance_guid = instance_guid return xml_marshaller.xml_marshaller.dumps(software_instance) #################################################### # Internals methods #################################################### def _getDocument(self, **kwargs): # No need to get all results if an error is raised when at least 2 objects # are found l = self.getPortalObject().portal_catalog.unrestrictedSearchResults(limit=2, **kwargs) if len(l) != 1: raise NotFound, "No document found with parameters: %s" % kwargs else: return _assertACI(l[0].getObject()) def _getNonCachedComputerDocument(self, computer_reference): return self._getDocument( portal_type='Computer', # XXX Hardcoded validation state validation_state="validated", reference=computer_reference).getRelativeUrl() def _getComputerDocument(self, computer_reference): """ Get the validated computer with this reference. """ result = CachingMethod(self._getNonCachedComputerDocument, id='_getComputerDocument', cache_factory='slap_cache_factory')(computer_reference) return self.getPortalObject().restrictedTraverse(result) @UnrestrictedMethod def _getComputerUidByReference(self, computer_reference): return self.getPortalObject().portal_catalog.unrestrictedSearchResults( portal_type='Computer', reference=computer_reference, validation_state="validated")[0].UID def _getComputerPartitionDocument(self, computer_reference, computer_partition_reference): """ Get the computer partition defined in an available computer """ # Related key might be nice return self._getDocument(portal_type='Computer Partition', reference=computer_partition_reference, parent_uid=self._getComputerUidByReference( computer_reference)) def _getSoftwareInstallationForComputer(self, url, computer_document): software_installation_list = self.getPortalObject().portal_catalog.unrestrictedSearchResults( portal_type='Software Installation', default_aggregate_uid=computer_document.getUid(), validation_state='validated', limit=2, url_string={'query': url, 'key': 'ExactMatch'}, ) l = len(software_installation_list) if l == 1: return _assertACI(software_installation_list[0].getObject()) elif l == 0: raise NotFound('No software release %r found on computer %r' % (url, computer_document.getReference())) else: raise ValueError('Wrong list of software releases on %r: %s' % ( computer_document.getReference(), ', '.join([q.getRelativeUrl() for q \ in software_installation_list]) )) def _getSoftwareInstanceForComputerPartition(self, computer_id, computer_partition_id, slave_reference=None): computer_partition_document = self._getComputerPartitionDocument( computer_id, computer_partition_id) if computer_partition_document.getSlapState() != 'busy': LOG('SlapTool::_getSoftwareInstanceForComputerPartition', INFO, 'Computer partition %s shall be busy, is free' % computer_partition_document.getRelativeUrl()) raise NotFound, "No software instance found for: %s - %s" % (computer_id, computer_partition_id) else: query_kw = { 'validation_state': 'validated', 'portal_type': 'Slave Instance', 'default_aggregate_uid': computer_partition_document.getUid(), } if slave_reference is None: query_kw['portal_type'] = "Software Instance" else: query_kw['reference'] = slave_reference software_instance = _assertACI(self.getPortalObject().portal_catalog.unrestrictedGetResultValue(**query_kw)) if software_instance is None: raise NotFound, "No software instance found for: %s - %s" % ( computer_id, computer_partition_id) else: return software_instance @UnrestrictedMethod def _getSoftwareInstanceAsParameterDict(self, software_instance): portal = software_instance.getPortalObject() computer_partition = software_instance.getAggregateValue(portal_type="Computer Partition") timestamp = int(computer_partition.getModificationDate()) newtimestamp = int(software_instance.getBangTimestamp(int(software_instance.getModificationDate()))) if (newtimestamp > timestamp): timestamp = newtimestamp ip_list = [] for internet_protocol_address in computer_partition.contentValues(portal_type='Internet Protocol Address'): ip_list.append(( internet_protocol_address.getNetworkInterface('').decode("UTF-8"), internet_protocol_address.getIpAddress().decode("UTF-8"))) slave_instance_list = [] if (software_instance.getPortalType() == "Software Instance"): append = slave_instance_list.append slave_instance_sql_list = portal.portal_catalog.unrestrictedSearchResults( default_aggregate_uid=computer_partition.getUid(), portal_type='Slave Instance', validation_state="validated", ) for slave_instance in slave_instance_sql_list: slave_instance = _assertACI(slave_instance.getObject()) # XXX Use catalog to filter more efficiently if slave_instance.getSlapState() == "start_requested": append({ 'slave_title': slave_instance.getTitle().decode("UTF-8"), 'slap_software_type': \ slave_instance.getSourceReference().decode("UTF-8"), 'slave_reference': slave_instance.getReference().decode("UTF-8"), 'xml': slave_instance.getTextContent(), 'connection_xml': slave_instance.getConnectionXml(), }) newtimestamp = int(slave_instance.getBangTimestamp(int(software_instance.getModificationDate()))) if (newtimestamp > timestamp): timestamp = newtimestamp return { 'instance_guid': software_instance.getReference().decode("UTF-8"), 'xml': software_instance.getTextContent(), 'connection_xml': software_instance.getConnectionXml(), 'slap_computer_id': \ computer_partition.getParentValue().getReference().decode("UTF-8"), 'slap_computer_partition_id': \ computer_partition.getReference().decode("UTF-8"), 'slap_software_type': \ software_instance.getSourceReference().decode("UTF-8"), 'slap_software_release_url': \ software_instance.getUrlString().decode("UTF-8"), 'slave_instance_list': slave_instance_list, 'ip_list': ip_list, 'timestamp': "%i" % timestamp, } @UnrestrictedMethod def _getSoftwareReleaseValueListForComputer(self, computer_reference): """Returns list of Software Releases documentsfor computer""" computer_document = self._getComputerDocument(computer_reference) portal = self.getPortalObject() software_release_list = [] for software_installation in portal.portal_catalog.unrestrictedSearchResults( portal_type='Software Installation', default_aggregate_uid=computer_document.getUid(), validation_state='validated', ): software_installation = _assertACI(software_installation.getObject()) software_release_response = SoftwareRelease( software_release=software_installation.getUrlString().decode('UTF-8'), computer_guid=computer_reference.decode('UTF-8')) software_release_response._software_release = \ software_release_response._software_release.decode("UTF-8") if software_installation.getSlapState() == 'destroy_requested': software_release_response._requested_state = 'destroyed' else: software_release_response._requested_state = 'available' software_release_list.append(software_release_response) return software_release_list @convertToREST def _softwareReleaseError(self, url, computer_id, error_log): """ Log the computer status """ user = self.getPortalObject().portal_membership.getAuthenticatedMember()\ .getUserName() self._logAccess( user, computer_id, '#error while installing %s' % url) InitializeClass(SlapTool)