Commit 8418eeb0 authored by Romain Courteaud's avatar Romain Courteaud

slapos_subscription_request:

* test that Subscription Request is created from Compute Node
* drop SubscriptionRequest_processStopped
* drop SubscriptionRequest_processStarted
* drop SubscriptionRequest_processOrdered
* drop SubscriptionRequest_processConfirmed
* drop SubscriptionRequest_processRequest
* drop SubscriptionCondition_renderKVMClusterParameter
* drop Base_instanceXmlToDict
* drop SubscriptionCondition_renderParameter
* drop testSlapOSERP5VirtualMasterSubscriptionRequestScenario
* ensure Subscription Request is consistent
* allow to choose the currency when creating a Subscription Request
* try to close the Subscription Request as soon as user paid an invoice
* block subscription process if there is no deposit
* do not accept new services if the virtual master subscription state is not expected
* use Base_reindexAndSenseAlarm
parent 4144073e
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from lxml import etree
from zExceptions import Unauthorized from zExceptions import Unauthorized
from zLOG import LOG, INFO
def SubscriptionRequest_saveTransactionalUser(self, person=None): def SubscriptionRequest_saveTransactionalUser(self, person=None):
if person.getPortalType() == "Person": if person.getPortalType() == "Person":
...@@ -14,31 +12,6 @@ def SubscriptionRequest_getTransactionalUser(self, REQUEST=None): ...@@ -14,31 +12,6 @@ def SubscriptionRequest_getTransactionalUser(self, REQUEST=None):
raise Unauthorized raise Unauthorized
return getTransactionalVariable().get("transactional_user", None) return getTransactionalVariable().get("transactional_user", None)
def Base_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, #pylint: disable=catching-non-exception
etree.XMLSchemaValidateError, etree.XMLSyntaxError): #pylint: disable=catching-non-exception
LOG('SubscriptionRequest', INFO, 'Issue during parsing xml:', error=True)
return result_dict
def SubscriptionCondition_renderParameter(self, amount=0, **kw):
method_id = self.getParameterTemplateRendererMethodId()
if method_id is not None:
return getattr(self, method_id)(amount=amount, **kw)
return self.getTextContent()
def SubscriptionRequest_searchExistingUserByEmail(self, email, REQUEST=None): def SubscriptionRequest_searchExistingUserByEmail(self, email, REQUEST=None):
if REQUEST is not None: if REQUEST is not None:
raise Unauthorized raise Unauthorized
......
...@@ -6,12 +6,6 @@ ...@@ -6,12 +6,6 @@
</pickle> </pickle>
<pickle> <pickle>
<dictionary> <dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item> <item>
<key> <string>default_reference</string> </key> <key> <string>default_reference</string> </key>
<value> <string>SlapOSSubscriptionRequest</string> </value> <value> <string>SlapOSSubscriptionRequest</string> </value>
...@@ -55,28 +49,13 @@ ...@@ -55,28 +49,13 @@
<item> <item>
<key> <string>workflow_history</string> </key> <key> <string>workflow_history</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="2" aka="AAAAAAAAAAI="> <record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<global name="PersistentMapping" module="Persistence.mapping"/> <global name="PersistentMapping" module="Persistence.mapping"/>
</pickle> </pickle>
...@@ -89,7 +68,7 @@ ...@@ -89,7 +68,7 @@
<item> <item>
<key> <string>component_validation_workflow</string> </key> <key> <string>component_validation_workflow</string> </key>
<value> <value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent> <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value> </value>
</item> </item>
</dictionary> </dictionary>
...@@ -98,7 +77,7 @@ ...@@ -98,7 +77,7 @@
</dictionary> </dictionary>
</pickle> </pickle>
</record> </record>
<record id="4" aka="AAAAAAAAAAQ="> <record id="3" aka="AAAAAAAAAAM=">
<pickle> <pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/> <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle> </pickle>
......
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
<type>Instance Tree</type> <type>Instance Tree</type>
<workflow>slapos_subscription_request_interaction_workflow</workflow> <workflow>slapos_subscription_request_interaction_workflow</workflow>
</chain> </chain>
<chain>
<type>Sale Invoice Transaction Line</type>
<workflow>slapos_subscription_request_interaction_workflow</workflow>
</chain>
<chain> <chain>
<type>Subscription Condition</type> <type>Subscription Condition</type>
<workflow>commerce_validation_workflow, edit_workflow</workflow> <workflow>commerce_validation_workflow, edit_workflow</workflow>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>Base_instanceXmlToDict</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>SlapOSSubscriptionRequest</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_instanceXmlToDict</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -49,12 +49,17 @@ tmp_sale_order = module.newContent( ...@@ -49,12 +49,17 @@ tmp_sale_order = module.newContent(
source_project_value=source_project_value, source_project_value=source_project_value,
ledger_value=portal.portal_categories.ledger.automated, ledger_value=portal.portal_categories.ledger.automated,
# XXX XXX destination_project_value=instance_tree.getFollowUpValue(), # XXX XXX destination_project_value=instance_tree.getFollowUpValue(),
price_currency_value=currency_value
) )
tmp_sale_order.SaleOrder_applySaleTradeCondition(batch_mode=1, force=1) tmp_sale_order.SaleOrder_applySaleTradeCondition(batch_mode=1, force=1)
if tmp_sale_order.getSpecialise(None) is None: if tmp_sale_order.getSpecialise(None) is None:
raise AssertionError('Can not find a trade condition to generate the Subscription Request') raise AssertionError('Can not find a trade condition to generate the Subscription Request')
if currency_value is not None:
if currency_value.getRelativeUrl() != tmp_sale_order.getPriceCurrency():
raise AssertionError('Unexpected different currency: %s %s' % (currency_value.getRelativeUrl(), tmp_sale_order.getPriceCurrency()))
# If no accounting is needed, no need to check the price # If no accounting is needed, no need to check the price
if (tmp_sale_order.getSourceSection(None) == tmp_sale_order.getDestinationSection(None)) or \ if (tmp_sale_order.getSourceSection(None) == tmp_sale_order.getDestinationSection(None)) or \
(tmp_sale_order.getSourceSection(None) is None): (tmp_sale_order.getSourceSection(None) is None):
...@@ -118,10 +123,9 @@ subscription_request = portal.subscription_request_module.newContent( ...@@ -118,10 +123,9 @@ subscription_request = portal.subscription_request_module.newContent(
price=price, price=price,
# XXX activate_kw=activate_kw # XXX activate_kw=activate_kw
) )
"""
if len(subscription_request.checkConsistency()) != 0: if len(subscription_request.checkConsistency()) != 0:
raise NotImplementedError(subscription_request.checkConsistency()) raise AssertionError(subscription_request.checkConsistency())
"""
subscription_request.submit() subscription_request.submit()
return subscription_request return subscription_request
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>subscriber_person_value, variation_category_list, project_value</string> </value> <value> <string>subscriber_person_value, variation_category_list, project_value, currency_value=None</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
"""
Take the Text Content and adapt it as template for create
an variation for a specific software release.
This adapt the definition of KVM to instantiate multiple VMs instead a single
for example.
"""
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
import json
parameter_text = context.getTextContent()
if not amount or parameter_text is None:
return None
json_parameter = context.Base_instanceXmlToDict(parameter_text)
if "_" in json_parameter:
# This is json-in-xml serialisation
json_parameter = json.loads(json_parameter["_"])
else:
raise ValueError("KVM Cluster only supports serialised values!")
KVM0 = json_parameter["kvm-partition-dict"]["KVM0"]
for i in range(amount):
if i == 0:
k = KVM0.copy()
k["sticky-compute-node"] = True
json_parameter["kvm-partition-dict"]["KVM" + str(i)] = k
else:
json_parameter["kvm-partition-dict"]["KVM" + str(i)] = KVM0
xml_parameter = """<?xml version="1.0" encoding="utf-8"?>
<instance>
<parameter id="_">%s</parameter>
</instance>""" % json.dumps(json_parameter, indent=2)
return xml_parameter
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>amount=0, REQUEST=None, **kw</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionCondition_renderKVMClusterParameter</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>SubscriptionCondition_renderParameter</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>SlapOSSubscriptionRequest</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionCondition_renderParameter</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
instance_tree = context.getAggregateValue()
if instance_tree is None:
# Probably we should raise here
return
# Instance is already destroyed so move into stopped state diretly.
if instance_tree.getValidationState() == "archived":
comment="Instance Tree is Destroyed and archived, Stop the Subscription Request"
context.start(comment=comment)
context.stop(comment=comment)
return comment
request_kw = dict(
software_release=instance_tree.getUrlString(),
software_title=instance_tree.getTitle(),
software_type=instance_tree.getSourceReference(),
instance_xml=instance_tree.getTextContent(),
sla_xml=instance_tree.getSlaXml(),
shared=instance_tree.isRootSlave(),
project_reference=instance_tree.getFollowUpReference()
)
if not context.SubscriptionRequest_testPaymentBalance():
# Payment isn't paid by the user, so we stop the instance and wait
if instance_tree.getSlapState() == "start_requested":
person = instance_tree.getDefaultDestinationSectionValue()
person.requestSoftwareInstance(state='stopped', **request_kw)
return "Skipped (Payment is pending)"
if instance_tree.getSlapState() == "stop_requested":
person = instance_tree.getDefaultDestinationSectionValue()
person.requestSoftwareInstance(state='started', **request_kw)
# Return to because it is useless continue right the way.
return "Skipped (Started instance)"
if not context.SubscriptionRequest_verifyInstanceIsAllocated():
# Only continue if instance is ready
return "Skipped (Instance is failing)"
if context.SubscriptionRequest_notifyInstanceIsReady():
context.start(comment="Instance is ready")
return "Instance is ready"
return "Skipped (Instance isn't ready)"
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionRequest_processConfirmed</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
if context.getSimulationState() != "ordered":
# Skip if the instance isn't ordered anymore
return
instance_tree = context.getAggregateValue()
# Don't request again if it is already requested.
if instance_tree is None:
context.SubscriptionRequest_processRequest()
# Don't perform everything on the same transaction
return "Skipped (Instance Requested)"
if instance_tree is not None:
if instance_tree.getCausalityState() == "diverged":
# Call it as soon as possible
subscription_condition = context.getSpecialiseValue(portal_type='Subscription Condition')
trade_condition = subscription_condition.getSpecialiseValue(portal_type='Sale Trade Condition')
if trade_condition is None:
return 'Skipped (No Trade Condition)'
instance_tree.InstanceTree_requestUpdateOpenSaleOrder(trade_condition.getRelativeUrl())
instance = instance_tree.getSuccessorValue()
# This ensure that the user has a valid cloud contract.
# At this stage he already have a paied invoice for the reservation,
# so the cloud contact will be just created.
instance.SoftwareInstance_requestValidationPayment()
# create a Deduction for his fee
context.SubscriptionRequest_generateReservationRefoundSalePackingList()
# Instance is already destroyed so move into stopped state diretly.
if instance_tree.getValidationState() == "archived":
comment="Instance Tree is Destroyed and archived, Stop the Subscription Request"
context.confirm(comment=comment)
context.start(comment=comment)
context.stop(comment=comment)
invoice = context.SubscriptionRequest_verifyPaymentBalanceIsReady()
if not invoice:
# Invoice isn't available for the user to Pay
return "Skipped (Payment isn't ready)"
if not context.SubscriptionRequest_verifyInstanceIsAllocated():
# Only continue if instance is ready
return "Skipped (Instance isn't ready)"
# Link to be sent is the invoice one
if context.SubscriptionRequest_notifyPaymentIsReady(invoice):
context.confirm(comment="Payment is ready for the user")
return "Payment is ready for the user"
return "Skipped (User isn't notified)"
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionRequest_processOrdered</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
if context.getAggregate() is not None:
return
subscription_condition = context.getSpecialiseValue(portal_type='Subscription Condition')
if subscription_condition is None:
return
person = context.getDestinationSectionValue()
if person is None:
return
if context.getSimulationState() == "confirmed":
return
request_kw = {}
default_xml = """<?xml version="1.0" encoding="utf-8"?>
<instance>
</instance>
"""
if subscription_condition.getUrlString() is None:
raise ValueError("url_string cannot be None")
request_kw.update(
software_release=subscription_condition.getUrlString(),
# Bad title
software_title=context.getTitle() + " %s" % str(context.getUid()),
software_type=subscription_condition.getSourceReference("default"),
instance_xml = (subscription_condition.SubscriptionCondition_renderParameter(
amount=int(context.getQuantity())
) or default_xml).strip(),
sla_xml=subscription_condition.getSlaXml(default_xml).strip(),
shared=bool(subscription_condition.getRootSlave(0)),
state="started",
project_reference=subscription_condition.getFollowUpReference()
)
person.requestSoftwareInstance(**request_kw)
requested_software_instance = context.REQUEST.get('request_instance')
if requested_software_instance is None:
return
# Save the requested instance tree
context.setAggregate(requested_software_instance.getSpecialise())
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionRequest_processRequest</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
instance_tree = context.getAggregateValue()
if instance_tree is not None and \
instance_tree.getSlapState() == "destroy_requested":
context.stop(comment="Instance is Destroyed")
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionRequest_processStarted</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# Search by SPL for the first period
portal = context.getPortalObject()
# This is normally one, but we navegate in case
for packing_list in portal.portal_catalog(
portal_type="Sale Packing List",
causality_uid=context.getUid(),
specialise_uid=portal.restrictedTraverse(
portal.portal_preferences.getPreferredAggregatedSubscriptionSaleTradeCondition()).getUid(),
):
for invoice in packing_list.getCausalityRelatedValueList(
portal_type="Sale Invoice Transaction"):
for payment in invoice.getCausalityRelatedValueList(
portal_type=["Payment Transaction", "Sale Invoice Transaction"]):
if payment.getSimulationState() not in ["confirmed", "started"]:
if round(payment.PaymentTransaction_getTotalPayablePrice(), 2) == 0.0:
continue
return
# No open Payments pending.
context.deliver()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SubscriptionRequest_processStopped</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -8,6 +8,55 @@ if item is None: ...@@ -8,6 +8,55 @@ if item is None:
# Use list setter, to ensure it crashes if item is still None # Use list setter, to ensure it crashes if item is still None
# subscription_request.setAggregateValueList([item]) # subscription_request.setAggregateValueList([item])
# If the virtual master is not in the expected subscription status,
# do not accept any new service (compute node, instance) for it
if (((subscription_request.getSourceProjectValue() is not None) and
(subscription_request.getSourceProjectValue().Item_getSubscriptionStatus() != 'subscribed')) or
((subscription_request.getDestinationProjectValue() is not None) and
(subscription_request.getDestinationProjectValue().Item_getSubscriptionStatus() != 'subscribed'))):
return
# Accept the subscription only if user paid the security payment
if (subscription_request.getSourceValue() is None) or \
(subscription_request.getSource() != subscription_request.getDestination()):
# Check that user has enough guarantee deposit to request a new service
portal = context.getPortalObject()
assert_price_kw = {
'resource_uid': subscription_request.getPriceCurrencyUid(),
'portal_type': portal.getPortalAccountingMovementTypeList(),
}
deposit_amount = portal.portal_simulation.getInventoryAssetPrice(
section_uid= subscription_request.getSourceSectionUid(),
mirror_section_uid= subscription_request.getDestinationSectionUid(),
mirror_node_uid=portal.restrictedTraverse('account_module/deposit').getUid(),
#node_category_strict_membership=['account_type/income'],
simulation_state= ('stopped', 'delivered'),
#src__=1,
**assert_price_kw
)
#return deposit_amount
payable_amount = portal.portal_simulation.getInventoryAssetPrice(
mirror_section_uid= subscription_request.getSourceSectionUid(),
section_uid= subscription_request.getDestinationSectionUid(),
node_category_strict_membership=['account_type/asset/receivable',
'account_type/liability/payable'],
grouping_reference=None,
**assert_price_kw
)
total_price = subscription_request.getTotalPrice()
# XXX what is the guarantee deposit account_type?
if deposit_amount < payable_amount + total_price:
# if not enough, user will have to pay a deposit for the subscription
# XXX probably create an event asking for a deposit
#pass
return 'NO deposit_amount %s\npayable_amount %s\ntotal_price %s' % (deposit_amount, payable_amount, total_price)
#return 'YES deposit_amount %s\npayable_amount %s\ntotal_price %s' % (deposit_amount, payable_amount, total_price)
subscription_request.SubscriptionRequest_createOpenSaleOrder() subscription_request.SubscriptionRequest_createOpenSaleOrder()
subscription_request.validate() subscription_request.validate()
subscription_request.invalidate() subscription_request.invalidate()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testSlapOSERP5VirtualMasterSubscriptionRequestScenario</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSERP5VirtualMasterSubscriptionRequestScenario</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -23,42 +23,48 @@ from erp5.component.test.SlapOSTestCaseMixin import \ ...@@ -23,42 +23,48 @@ from erp5.component.test.SlapOSTestCaseMixin import \
class TestSlapOSSubscriptionRequestProcessAlarm(SlapOSTestCaseMixin): class TestSlapOSSubscriptionRequestProcessAlarm(SlapOSTestCaseMixin):
def test_alarm_slapos_subscription_request_create_from_orphaned_item(self): def _test_alarm_slapos_subscription_request_create_from_orphaned_item(self, portal_type):
script_name = "Item_createSubscriptionRequest" script_name = "Item_createSubscriptionRequest"
alarm = self.portal.portal_alarms.slapos_subscription_request_create_from_orphaned_item alarm = self.portal.portal_alarms.slapos_subscription_request_create_from_orphaned_item
##################################################### #####################################################
# Instance Tree without Subscription Request # Instance Tree without Subscription Request
instance_tree = self.portal.instance_tree_module.newContent( document = self.portal.getDefaultModule(portal_type).newContent(
portal_type='Instance Tree', portal_type=portal_type,
title="Test Instance Tree no subscription %s" % self.new_id title="Test %s no subscription %s" % (portal_type, self.new_id)
) )
self._test_alarm(alarm, instance_tree, script_name) self._test_alarm(alarm, document, script_name)
##################################################### #####################################################
# Instance Tree with Subscription Request # Instance Tree with Subscription Request
instance_tree = self.portal.instance_tree_module.newContent( document = self.portal.getDefaultModule(portal_type).newContent(
portal_type='Instance Tree', portal_type=portal_type,
title="Test Instance Tree no subscription %s" % self.new_id title="Test %s no subscription %s" % (portal_type, self.new_id)
) )
self.portal.subscription_request_module.newContent( self.portal.subscription_request_module.newContent(
portal_type='Subscription Request', portal_type='Subscription Request',
title="Test Subscription Request %s" % self.new_id, title="Test Subscription Request %s" % self.new_id,
aggregate_value=instance_tree aggregate_value=document
) )
self._test_alarm_not_visited(alarm, instance_tree, script_name) self._test_alarm_not_visited(alarm, document, script_name)
##################################################### #####################################################
# Instance Tree aggregated to another portal type # Instance Tree aggregated to another portal type
instance_tree = self.portal.instance_tree_module.newContent( document = self.portal.getDefaultModule(portal_type).newContent(
portal_type='Instance Tree', portal_type=portal_type,
title="Test Instance Tree another portal type %s" % self.new_id title="Test %s another portal type %s" % (portal_type, self.new_id)
) )
self.portal.sale_packing_list_module.newContent( self.portal.sale_packing_list_module.newContent(
portal_type='Sale Packing List', portal_type='Sale Packing List',
title="Test Sale Packing List %s" % self.new_id, title="Test Sale Packing List %s" % self.new_id,
).newContent( ).newContent(
portal_type="Sale Packing List Line", portal_type="Sale Packing List Line",
aggregate_value=instance_tree aggregate_value=document
) )
self._test_alarm(alarm, instance_tree, script_name) self._test_alarm(alarm, document, script_name)
def test_alarm_slapos_subscription_request_create_from_orphaned_instance_tree(self):
self._test_alarm_slapos_subscription_request_create_from_orphaned_item("Instance Tree")
def test_alarm_slapos_subscription_request_create_from_orphaned_compute_node(self):
self._test_alarm_slapos_subscription_request_create_from_orphaned_item("Compute Node")
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction Workflow Interaction" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>after_script/portal_workflow/slapos_subscription_request_interaction_workflow/script_Base_triggerValidationAlarm</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_SaleInvoiceTransactionLine_setGroupingReference</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Interaction Workflow Interaction</string> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<tuple>
<string>Sale Invoice Transaction Line</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<value>
<tuple>
<string>_setGroupingReference</string>
<string>_baseSetGroupingReference</string>
</tuple>
</value>
</item>
<item>
<key> <string>trigger_once_per_transaction</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
if alarm.getParentValue().isSubscribed() and not alarm.isActive() and alarm.isEnabled():
alarm.activate(queue='SQLQueue', **activate_kw).activeSense()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>alarm, activate_kw</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_Alarm_safeTrigger</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
base = state_change['object'] return state_change['object'].Base_reindexAndSenseAlarm(['slapos_subscription_request_create_from_orphaned_item'])
portal = base.getPortalObject()
tag = script.id
base.reindexObject(activate_kw={'tag': tag})
context.Alarm_safeTrigger(portal.portal_alarms.slapos_subscription_request_create_from_orphaned_item, {'after_tag': tag})
base = state_change['object'] return state_change['object'].Base_reindexAndSenseAlarm(['slapos_subscription_request_validate_submitted'])
portal = base.getPortalObject()
tag = script.id
base.reindexObject(activate_kw={'tag': tag})
context.Alarm_safeTrigger(portal.portal_alarms.slapos_subscription_request_validate_submitted, {'after_tag': tag})
Compute Node | slapos_subscription_request_interaction_workflow Compute Node | slapos_subscription_request_interaction_workflow
Email | -coordinate_interaction_workflow Email | -coordinate_interaction_workflow
Instance Tree | slapos_subscription_request_interaction_workflow Instance Tree | slapos_subscription_request_interaction_workflow
Sale Invoice Transaction Line | slapos_subscription_request_interaction_workflow
Subscription Condition | commerce_validation_workflow Subscription Condition | commerce_validation_workflow
Subscription Condition | edit_workflow Subscription Condition | edit_workflow
Subscription Request | edit_workflow Subscription Request | edit_workflow
......
test.erp5.testSlapOSERP5VirtualMasterSubscriptionRequestScenario
test.erp5.testSlapOSSubscriptionAlarm test.erp5.testSlapOSSubscriptionAlarm
test.erp5.testSlapOSSubscriptionSkins test.erp5.testSlapOSSubscriptionSkins
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment