# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2019 Nexedi SA and Contributors. All Rights Reserved.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################

import six
import six.moves.urllib.parse
from erp5.component.test.testSlapOSCloudSecurityGroup import TestSlapOSSecurityMixin
from erp5.component.test.testSlapOSJIOAPI import TestSlapOSJIOAPIMixin
from erp5.component.test.SlapOSTestCaseMixin import changeSkin
import re
import xml_marshaller
from AccessControl.SecurityManagement import getSecurityManager, \
             setSecurityManager

class DefaultScenarioMixin(TestSlapOSSecurityMixin, TestSlapOSJIOAPIMixin):

  def afterSetUp(self):
    TestSlapOSSecurityMixin.afterSetUp(self)
    preference = self.portal.portal_preferences.slapos_default_system_preference

    preference.edit(
      preferred_credential_alarm_automatic_call=0,
      preferred_credential_recovery_automatic_approval=0,
      preferred_credential_request_automatic_approval=1,
      preferred_cloud_contract_enabled=True
    )

    """
    self.web_site = self.portal.web_site_module.hostingjs
    self.callUpdateRevision()

    # Enable alarms for regularisation request
    self.portal.portal_alarms.slapos_crm_create_regularisation_request.setEnabled(True)
    self.portal.portal_alarms.slapos_crm_invalidate_suspended_regularisation_request.setEnabled(True)
    self.portal.portal_alarms.slapos_crm_trigger_stop_reminder_escalation.setEnabled(True)
    self.portal.portal_alarms.slapos_crm_trigger_stop_acknowledgment_escalation.setEnabled(True)
    self.portal.portal_alarms.slapos_crm_trigger_delete_reminder_escalation.setEnabled(True)
    self.portal.portal_alarms.slapos_crm_trigger_acknowledgment_escalation.setEnabled(True)
    """
    # Create akarls steps
    self.createAlarmStep()

  def createProject(self):
    project = self.portal.project_module.newContent(
      portal_type='Project',
      title='project-%s' % self.generateNewId()
    )
    project.validate()
    return project

  def createAdminUser(self, project):
    """ Create a Admin user, to manage compute_nodes and instances eventually """
    admin_user_login = self.portal.portal_catalog.getResultValue(
      portal_type="ERP5 Login",
      reference="admin_user",
      validation_state="validated"
    )

    if admin_user_login is None:
      admin_user = self.portal.person_module.template_member.\
                                 Base_createCloneDocument(batch_mode=1)

      admin_user.newContent(
        portal_type="ERP5 Login",
        reference="admin_user").validate()
      admin_user.edit(
        first_name="Admin User",
        reference="Admin_user",
        default_email_text="do_not_reply_to_admin@example.org",
      )

      admin_user.validate()

    else:
      admin_user = admin_user_login.getParentValue()

    admin_user.newContent(
      portal_type='Assignment',
      destination_project_value=project,
      function='production/manager'
    ).open()

    self.admin_user = admin_user

  @changeSkin('Hal')
  def joinSlapOS(self, web_site, reference):
    def findMessage(email, body):
      for candidate in reversed(self.portal.MailHost.getMessageList()):
        if [q for q in candidate[1] if email in q] and body in candidate[2]:
          return candidate[2]

    credential_request_form = self.web_site.hateoas.connection.join_form()

    expected_message = 'You will receive a confirmation email to activate your account.'
    self.assertTrue(expected_message in credential_request_form,
      '%s not in %s' % (expected_message, credential_request_form))

    email = '%s@example.com' % reference

    redirect_url = self.web_site.hateoas.connection.WebSection_newCredentialRequest(
      reference=reference,
      default_email_text=email,
      first_name="Joe",
      last_name=reference,
      password="demo_functional_user",
      default_telephone_text="12345678",
      corporate_name="Nexedi",
      default_address_city="Campos",
      default_address_street_address="Av Pelinca",
      default_address_zip_code="28480",
    )
    parsed_url = six.moves.urllib.parse.urlparse(redirect_url)
    self.assertEqual(parsed_url.path.split('/')[-1], 'login_form')
    self.assertEqual(
      sorted(six.iteritems(dict(six.moves.urllib.parse.parse_qsl(parsed_url.query)))), [
        ('portal_status_message', 'Thank you for your registration. You will receive an email to activate your account.'),
    ])
    self.tic()

    to_click_message = findMessage(email, 'You have requested one user')

    self.assertNotEqual(None, to_click_message)

    to_click_url = re.search('href="(.+?)"', to_click_message).group(1)

    self.assertIn('%s/hateoas/connection/ERP5Site_activeLogin' % self.web_site.getId(), to_click_url)
    
    join_key = to_click_url.split('=')[-1]
    self.assertNotEqual(join_key, None)
    web_site.hateoas.connection.ERP5Site_activeLogin(key=join_key)

    self.assertEqual(self.portal.REQUEST.RESPONSE.getStatus(), 303)
    self.assertIn(self.web_site.getId() + "/%23%21login%3Fp.page%3Dslapos%7B%26n.me%7D",
      self.portal.REQUEST.RESPONSE.getHeader("Location"))

    self.tic()

    welcome_message = findMessage(email, "the creation of you new")
    self.assertNotEqual(None, welcome_message)

  def _getCurrentInstanceTreeList(self):
    person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()

    if person is not None:
      return self.portal.portal_catalog(
        portal_type="Instance Tree",
        default_destination_section_uid=person.getUid(),
        validation_state='validated')

    return []

  def setAccessToMemcached(self, agent):
    agent.setAccessStatus("#access ")

  def requestComputeNode(self, title, project_reference):
    #requestXml = self.portal.portal_slap.requestComputer(title, project_reference)
    request_data = self.postToApi({
      "portal_type": "Compute Node",
      "title": title
    })
    self.tic()
    self.callUpdateRevisionAndTic()
    return request_data["compute_node_id"]

  def supplySoftware(self, server, url, state='available'):
    self.postToApi({
        "portal_type": "Software Installation",
        "compute_node_id": server.getReference(),
        "software_release_uri": url,
        "state": state,
    })
    self.tic()
    self.callUpdateRevisionAndTic()

    software_installation = self.portal.portal_catalog.getResultValue(
        portal_type='Software Installation',
        url_string=url,
        default_aggregate_uid=server.getUid())

    self.assertNotEqual(None, software_installation)

    if state=='available':
      self.assertEqual('start_requested', software_installation.getSlapState())
    else:
      self.assertEqual('destroy_requested', software_installation.getSlapState())

  @changeSkin('RJS')
  def setServerOpenPublic(self, server):
    server.edit(
        allocation_scope='open')
    self.assertEqual('open', server.getAllocationScope())
    self.assertEqual('close', server.getCapacityScope())
    server.edit(capacity_scope='open')
    self.tic()

  @changeSkin('RJS')
  def setServerOpenSubscription(self, server):
    server.edit(
        allocation_scope='open/subscription')
    self.assertEqual('open/subscription', server.getAllocationScope())
    self.assertEqual('close', server.getCapacityScope())
    server.edit(capacity_scope='open')
    self.tic()

  @changeSkin('RJS')
  def setServerOpenPersonal(self, server):
    server.edit(
        allocation_scope='open/personal', subject_list=[])
    self.assertEqual('open/personal', server.getAllocationScope())
    self.assertEqual('open', server.getCapacityScope())
    self.tic()

  def formatComputeNode(self, compute_node, partition_count=10):
    compute_node_dict = dict(
      compute_node_id=compute_node.getReference(),
      portal_type="Compute Node",
    )
    compute_node_dict['compute_partition_list'] = []
    a = compute_node_dict['compute_partition_list'].append
    for i in range(1, partition_count+1):
      a(dict(
        partition_id='part%s' % i,
        ip_list=[
          {
            "ip-address":'p%sa1' % i,
            "network-interface": 'tap%s' % i,
          },
          {
            "ip-address":'p%sa2' % i,
            "network-interface": 'tap%s' % i,
          },
          {
            "ip-address":'p%sa1' % i,
            "gateway-ip-address":'p%sn1' % i,
            "network-interface": 'tap%s' % i,
            "netmask": '255.255.255.0',
            "network-address": '128.0.0.1',
          },
          {
            "ip-address":'p%sa2' % i,
            "gateway-ip-address":'p%sn2' % i,
            "network-interface": 'tap%s' % i,
            "netmask": '255.255.255.0',
            "network-address": '128.0.0.1',
          }
        ]
      ))
    sm = getSecurityManager()
    try:
      self.login(compute_node.getUserId())
      self.putToApi(compute_node_dict)
      self.tic()
      self.assertEqual(partition_count,
          len(compute_node.contentValues(portal_type='Compute Partition')))
    finally:
      setSecurityManager(sm)

  def simulateSlapgridSR(self, compute_node):
    sm = getSecurityManager()
    compute_node_user_id = compute_node.getUserId()
    try:
      self.login(compute_node_user_id)
      software_release_list = self.allDocsToApi({
        "portal_type": "Software Installation",
        "compute_node_id": compute_node.getReference()
      })["result_list"]
      for software_release in software_release_list:
        if software_release["state"] == 'destroyed':
          self.putToApi({
            "portal_type": "Software Installation",
            "compute_noode_id": compute_node.getReference(),
            "software_release_uri": software_release["software_release_uri"],
            "reported_state": "destroyed",
          })
        else:
          self.putToApi({
            "portal_type": "Software Installation",
            "compute_noode_id": compute_node.getReference(),
            "software_release_uri": software_release["software_release_uri"],
            "reported_state": "available",
          })
    finally:
      setSecurityManager(sm)
    self.tic()

  def simulateSlapgridUR(self, compute_node):
    sm = getSecurityManager()
    compute_node_user_id = compute_node.getUserId()
    try:
      self.callUpdateRevisionAndTic()
      self.login(compute_node_user_id)
      instance_list = self.allDocsToApi({
        "portal_type": "Software Instance",
        "compute_node_id": compute_node.getReference()
      })["result_list"]
      destroyed_partition_id_list = []
      for partition in instance_list:
        if partition["state"] == 'destroyed':
          self.putToApi({
            "portal_type": "Software Instance",
            "reference": partition["reference"],
            "reported_state": "destroyed"
          })
          destroyed_partition_id_list.append(partition["compute_partition_id"])
    finally:
      setSecurityManager(sm)
    self.tic()
    self.stepCallSlaposFreeComputePartitionAlarm()
    self.tic()
    self.callUpdateRevisionAndTic()
    free_partition_id_list = []
    for partition in compute_node.contentValues(portal_type='Compute Partition'):
      if partition.getReference() in destroyed_partition_id_list \
          and partition.getSlapState() == 'free':
        free_partition_id_list.append(partition.getReference())
    self.assertSameSet(destroyed_partition_id_list, free_partition_id_list)

  def simulateSlapgridCP(self, compute_node):
    sm = getSecurityManager()
    compute_node_user_id = compute_node.getUserId()
    try:
      self.login(compute_node_user_id)
      instance_list = self.allDocsToApi({
        "portal_type": "Software Instance",
        "compute_node_id": compute_node.getReference()
      })["result_list"]
      for partition in instance_list:
        if partition["state"] in ('started', 'stopped'):
          partition = self.getToApi({
            "portal_type": "Software Instance",
            "reference": partition["reference"],
          })
          instance_reference = partition["reference"]
          ip_list = partition['ip_list']
          connection_dict = dict(
            url_1 = 'http://%s/' % ip_list[0][1],
            url_2 = 'http://%s/' % ip_list[1][1],
          )
          self.login()
          instance_user_id = self.portal.portal_catalog.getResultValue(
              reference=instance_reference, portal_type="Software Instance").getUserId()

          oldsm = getSecurityManager()
          try:
            self.login(instance_user_id)
            self.putToApi({
              "portal_type": "Software Instance",
              "reference": partition["reference"],
              "connection_parameters": connection_dict,
            })
            hosted_instance_list = self.allDocsToApi({
              "portal_type": "Shared Instance",
              "host_instance_reference": partition["reference"],
              "state": "started"
            })["result_list"]
            for hosted_instance in hosted_instance_list:
              hosted_reference = hosted_instance['reference']
              connection_dict = dict(
                url_1 = 'http://%s/%s' % (ip_list[0][1], hosted_reference),
                url_2 = 'http://%s/%s' % (ip_list[1][1], hosted_reference)
              )
              self.putToApi({
                "portal_type": "Software Instance",
                "reference": hosted_reference,
                "connection_parameters": connection_dict,
              })

          finally:
            setSecurityManager(oldsm)
    finally:
      setSecurityManager(sm)
    self.tic()

  def personRequestInstanceNotReady(self, **kw):
    kw["portal_type"] = "Software Instance"
    response = self.postToApi(kw)
    response.pop("$schema")
    response.pop("debug_id", None)
    expected_dict = {
      "message": "Software Instance Not Ready",
      "name": "SoftwareInstanceNotReady",
      "status": 102,
    } 
    if response != expected_dict:
      self.assertIsNone(response['compute_node_id'])
      self.assertIsNone(response['compute_partition_id'])

    self.tic()

  def personRequestInstance(self, **kw):
    kw["portal_type"] = "Software Instance"
    software_instance = self.postToApi(kw)
    self.assertTrue(software_instance.get("portal_type", "") in ("Slave Instance", "Software Instance"))
    self.tic()
    return software_instance

  def checkSlaveInstanceAllocation(self, person_user_id, person_reference,
      instance_title, software_release, software_type, server,
       project_reference):

    self.tic()
    self.login(person_user_id)
    self.personRequestInstanceNotReady(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      shared_xml='<marshal><bool>1</bool></marshal>',
      project_reference=project_reference
      title=instance_title,
      shared=True
    )

    self.tic()
    self.callUpdateRevisionAndTic()

    self.login(person_user_id)
    self.personRequestInstance(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      shared_xml='<marshal><bool>1</bool></marshal>',
      project_reference=project_reference
      title=instance_title,
      shared=True
    )

    # now instantiate it on compute_node and set some nice connection dict
    self.simulateSlapgridCP(server)

    # let's find instances of user and check connection strings
    instance_tree_list = [q.getObject() for q in
        self._getCurrentInstanceTreeList()
        if q.getTitle() == instance_title]
    self.assertEqual(1, len(instance_tree_list))
    instance_tree = instance_tree_list[0]

    software_instance = instance_tree.getSuccessorValue()
    self.assertEqual(software_instance.getTitle(),
        instance_tree.getTitle())
    connection_dict = software_instance.getConnectionXmlAsDict()
    self.assertSameSet(('url_1', 'url_2'), connection_dict.keys())
    self.login()
    partition = software_instance.getAggregateValue()
    self.assertSameSet(
        ['http://%s/%s' % (q.getIpAddress(), software_instance.getReference())
            for q in partition.contentValues(
                portal_type='Internet Protocol Address')],
        connection_dict.values())

  def checkInstanceTreeSlaveInstanceAllocation(
    self,
    person_user_id,
    person_reference,
    instance_tree_title, instance_title, software_release, software_type,
    server,
    project_reference
  ):

    self.login(person_user_id)

    # let's find instance of user
    instance_tree_list = [q.getObject() for q in
        self._getCurrentInstanceTreeList()
        if q.getTitle() == instance_tree_title]
    self.assertEqual(1, len(instance_tree_list))
    instance_tree = instance_tree_list[0]

    software_instance = instance_tree.getSuccessorValue()

    self.login()
    instance_user_id = software_instance.getUserId()
    compute_partition = software_instance.getAggregateValue()
    computer_id = compute_partition.getParentValue().getReference()
    computer_partition_id = compute_partition.getTitle()

    self.login(instance_user_id)

    response = self.portal.portal_slap.requestComputerPartition(
      computer_id=computer_id,
      computer_partition_id=computer_partition_id,
      software_release=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      shared_xml='<marshal><bool>2</bool></marshal>',
      project_reference=project_reference
    )
    status = getattr(response, 'status', None)
    self.assertEqual(408, status)
    self.tic()

    # now instantiate it on compute_node and set some nice connection dict
    self.simulateSlapgridCP(server)

    # let's find instances of user and check connection strings
    slave_instance = software_instance.getSuccessorValue()

    connection_dict = slave_instance.getConnectionXmlAsDict()
    self.assertSameSet(('url_1', 'url_2'), connection_dict.keys())
    self.login()
    partition = slave_instance.getAggregateValue()
    self.assertSameSet(
        ['http://%s/%s' % (q.getIpAddress(), slave_instance.getReference())
            for q in partition.contentValues(
                portal_type='Internet Protocol Address')],
        connection_dict.values())

  def checkRemoteInstanceAllocation(self, person_user_id, person_reference,
      instance_title, software_release, software_type, server,
      project_reference, connection_dict_to_check=None,
      slave=False):

    shared_xml = '<marshal><bool>%i</bool></marshal>' % int(slave)

    self.login(person_user_id)

    if connection_dict_to_check is None:
      self.personRequestInstanceNotReady(
        software_release=software_release,
        software_type=software_type,
        partition_reference=instance_title,
        project_reference=project_reference,
        shared_xml=shared_xml,
      )

      # XXX search only for this user
      instance_tree = self.portal.portal_catalog.getResultValue(
        portal_type="Instance Tree",
        title=instance_title,
        follow_up__reference=project_reference
      )
      self.checkServiceSubscriptionRequest(instance_tree)

      self.tic()
      self.login(person_user_id)

    self.personRequestInstance(
      software_release=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      project_reference=project_reference,
      shared_xml=shared_xml,
    )

    # now instantiate it on compute_node and set some nice connection dict
    # XXX XXX self.simulateSlapgridCP(server)

    # let's find instances of user and check connection strings
    instance_tree_list = [q.getObject() for q in
        self._getCurrentInstanceTreeList()
        if q.getTitle() == instance_title]
    self.assertEqual(1, len(instance_tree_list))
    instance_tree = instance_tree_list[0]

    software_instance = instance_tree.getSuccessorValue()
    self.assertEqual(software_instance.getTitle(),
        instance_tree.getTitle())
    connection_dict = software_instance.getConnectionXmlAsDict()

    if connection_dict_to_check is None:
      connection_dict_to_check = {}

    self.assertSameSet(connection_dict_to_check.keys(), connection_dict.keys())
    self.assertSameSet(
        connection_dict_to_check.values(),
        connection_dict.values())

  def checkSlaveInstanceUnallocation(self, person_user_id,
      person_reference, instance_title,
      software_release, software_type, server,
      project_reference):

    self.login(person_user_id)
    self.personRequestInstanceNotReady(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      shared_xml='<marshal><bool>1</bool></marshal>',
      state='<marshal><string>destroyed</string></marshal>',
      project_reference=project_reference
      title=instance_title,
      shared=True,
      state='destroyed',
    )

    # let's find instances of user and check connection strings
    instance_tree_list = [q.getObject() for q in
        self._getCurrentInstanceTreeList()
        if q.getTitle() == instance_title]

    self.assertEqual(0, len(instance_tree_list))

  def checkInstanceUnallocation(self, person_user_id,
      person_reference, instance_title,
      software_release, software_type, server, project_reference):

    self.login(person_user_id)
    self.personRequestInstanceNotReady(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      state='<marshal><string>destroyed</string></marshal>',
      project_reference=project_reference
      title=instance_title,
      state='destroyed'
    )

    # now instantiate it on compute_node and set some nice connection dict
    self.simulateSlapgridUR(server)

    # let's find instances of user and check connection strings
    instance_tree_list = [q.getObject() for q in
        self._getCurrentInstanceTreeList()
        if q.getTitle() == instance_title]
    self.assertEqual(0, len(instance_tree_list))

  def checkServiceSubscriptionRequest(self, service):
    self.login()

    subscription_request = self.portal.portal_catalog.getResultValue(
      portal_type="Subscription Request",
      aggregate__uid=service.getUid(),
      simulation_state='invalidated'
    )
    self.assertNotEqual(subscription_request, None)

  def checkInstanceAllocation(self, person_user_id, person_reference,
      instance_title, software_release, software_type, server,
      project_reference):

    self.login(person_user_id)

    self.personRequestInstanceNotReady(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      project_reference=project_reference
      title=instance_title,
    )
    self.tic()
    self.callUpdateRevisionAndTic()

    # XXX search only for this user
    instance_tree = self.portal.portal_catalog.getResultValue(
      portal_type="Instance Tree",
      title=instance_title,
      follow_up__reference=project_reference
    )
    self.checkServiceSubscriptionRequest(instance_tree)
    self.tic()

    self.login(person_user_id)
    self.personRequestInstance(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      project_reference=project_reference
      title=instance_title,
    )

    # now instantiate it on compute_node and set some nice connection dict
    self.simulateSlapgridCP(server)

    # let's find instances of user and check connection strings
    instance_tree_list = [q.getObject() for q in
        self._getCurrentInstanceTreeList()
        if q.getTitle() == instance_title]
    self.assertEqual(1, len(instance_tree_list))
    instance_tree = instance_tree_list[0]

    software_instance = instance_tree.getSuccessorValue()
    self.assertEqual(software_instance.getTitle(),
        instance_tree.getTitle())
    connection_dict = software_instance.getConnectionXmlAsDict()
    self.assertSameSet(('url_1', 'url_2'), connection_dict.keys())
    self.login()
    partition = software_instance.getAggregateValue()
    self.assertSameSet(
        ['http://%s/' % q.getIpAddress() for q in
            partition.contentValues(portal_type='Internet Protocol Address')],
        connection_dict.values())

  def assertInstanceTreeSimulationCoverage(self, subscription):
    self.login()
    # this is document level assertion, as simulation and its specific delivery
    # is covered by unit tests
    packing_list_line_list = subscription.getAggregateRelatedValueList(
        portal_type='Sale Packing List Line')
    self.assertEqual(len(packing_list_line_list), 1)
    for packing_list_line in packing_list_line_list:
      packing_list = packing_list_line.getParentValue()
      self.assertEqual('Sale Packing List',
          packing_list.getPortalType())
      self.assertEqual('delivered',
          packing_list.getSimulationState())
      causality_state = packing_list.getCausalityState()
      self.assertEqual('solved', causality_state)

  def assertAggregatedSalePackingList(self, delivery):
    self.assertEqual('delivered', delivery.getSimulationState())
    self.assertEqual('solved', delivery.getCausalityState())

    invoice_list= delivery.getCausalityRelatedValueList(
        portal_type='Sale Invoice Transaction')
    self.assertEqual(1, len(invoice_list))
    invoice = invoice_list[0].getObject()

    causality_list = invoice.getCausalityValueList()

    self.assertSameSet([delivery], causality_list)

    self.assertEqual('stopped', invoice.getSimulationState())
    self.assertEqual('solved', invoice.getCausalityState())

    payment_list = invoice.getCausalityRelatedValueList(
        portal_type='Payment Transaction')
    self.assertEqual(0, len(payment_list))

  def assertPersonDocumentCoverage(self, person):
    self.login()
    subscription_list = self.portal.portal_catalog(
        portal_type='Instance Tree',
        default_destination_section_uid=person.getUid())
    for subscription in subscription_list:
      self.assertInstanceTreeSimulationCoverage(
          subscription.getObject())

    aggregated_delivery_list = self.portal.portal_catalog(
        portal_type='Sale Packing List',
        default_destination_section_uid=person.getUid(),
        specialise_uid=[self.portal.restrictedTraverse(self.portal\
          .portal_preferences.getPreferredAggregatedSaleTradeCondition()\
          ).getUid()
        ] + [
          i.uid for i in self.portal.ERP5Site_searchRelatedInheritedSpecialiseList(
            specialise_uid=self.portal.restrictedTraverse(self.portal
                .portal_preferences.getPreferredAggregatedSaleTradeCondition()
                ).getUid())
        ]
    )

    if len(subscription_list) == 0:
      self.assertEqual(0, len(aggregated_delivery_list))
      return

    self.assertNotEqual(0, len(aggregated_delivery_list))
    for aggregated_delivery in aggregated_delivery_list:
      self.assertAggregatedSalePackingList(aggregated_delivery.getObject())

  def assertOpenSaleOrderCoverage(self, person_reference):
    self.login()
    person = self.portal.portal_catalog.getResultValue(
       portal_type='ERP5 Login',
       reference=person_reference).getParentValue()
    instance_tree_list = self.portal.portal_catalog(
        portal_type='Instance Tree',
        default_destination_section_uid=person.getUid()
    )

    open_sale_order_list = self.portal.portal_catalog(
        portal_type='Open Sale Order',
        default_destination_uid=person.getUid(),
    )

    if len(instance_tree_list) == 0:
      self.assertEqual(0, len(open_sale_order_list))
      return

    self.assertEqual(len(instance_tree_list), len(open_sale_order_list))

    archived_open_sale_order_list = [q for q in open_sale_order_list
                       if q.getValidationState() == 'archived']
    self.assertEqual(len(instance_tree_list), len(archived_open_sale_order_list))

    line_list = []
    for open_sale_order in archived_open_sale_order_list:
      archived_line_list = open_sale_order.contentValues(
          portal_type='Open Sale Order Line')
      self.assertEqual(1, len(archived_line_list))
      line_list.extend(archived_line_list)

    self.assertEqual(len(instance_tree_list), len(line_list))
    self.assertSameSet(
        [q.getRelativeUrl() for q in instance_tree_list],
        [q.getAggregate(portal_type="Instance Tree") for q in line_list]
    )

    # Every line must have 2 aggregate categories:
    # one Instance Tree and one Hosting Subscription
    for line in line_list:
      self.assertEqual(2, len(line.getAggregateList()))
      self.assertEqual(1, len(line.getAggregateList(portal_type="Hosting Subscription")))

    validated_open_sale_order_list = [q for q in open_sale_order_list
                       if q.getValidationState() == 'validated']
    # if no line, all open orders are kept archived
    self.assertEqual(len(validated_open_sale_order_list), 0)

  def findMessage(self, email, body):
    for candidate in reversed(self.portal.MailHost.getMessageList()):
      if [q for q in candidate[1] if email in q] and body in candidate[2]:
        return candidate[2]

  def assertInvoiceNotification(self, person, is_email_expected=True):

    if person.getLanguage() == "zh":
      expected_message = self.expected_invoice_zh_notification_message
    else:
      expected_message = self.expected_invoice_en_notification_message 

    to_click_message = self.findMessage(person.getDefaultEmailText(),
                                        expected_message)
    if is_email_expected:
      self.assertNotEqual(None, to_click_message)
    else:
      self.assertEqual(None, to_click_message)

  @changeSkin('RJS')
  def usePaymentManually(self, web_site, user_id, is_email_expected=True, subscription_request=None):
    person = self.portal.portal_catalog.getResultValue(
      portal_type="Person",
      user_id=user_id)

    self.assertNotEqual(person, None)
    self.assertInvoiceNotification(person, is_email_expected)

    invoice_list = person.Entity_getOutstandingAmountList()

    self.login() 
    if subscription_request is not None:
      expected_causality = subscription_request.getRelativeUrl()
      filtered_invoice_list = []
      for invoice in invoice_list:
        spl = invoice.getCausalityValue()
        if spl is not None and spl.getCausality() == expected_causality:
          filtered_invoice_list.append(invoice)
      
      self.assertEqual(len(filtered_invoice_list), 1)
      invoice_list = filtered_invoice_list
    else:
      self.assertEqual(len(invoice_list), 1)

    self.login(user_id)
    document_id = invoice_list[0].getId()
    web_site.accounting_module[document_id].\
      SaleInvoiceTransaction_redirectToManualSlapOSPayment()
    self.tic()

  def assertSubscriptionStopped(self, person):
    self.login()
    subscription_list = self.portal.portal_catalog(
        portal_type='Instance Tree',
        default_destination_section_uid=person.getUid())
    self.assertEqual(len(subscription_list), 1)
    for subscription in subscription_list:
      self.assertEqual(subscription.getSlapState(), "stop_requested")

  def assertSubscriptionDestroyed(self, person):
    self.login()
    subscription_list = self.portal.portal_catalog(
        portal_type='Instance Tree',
        default_destination_section_uid=person.getUid())
    self.assertEqual(len(subscription_list), 1)
    for subscription in subscription_list:
      self.assertEqual(subscription.getSlapState(), "destroy_requested")

  def requestInstance(self, person_user_id, instance_title,
      software_release, software_type, project_reference):

    self.login(person_user_id)
    self.personRequestInstanceNotReady(
      software_release_uri=software_release,
      software_type=software_type,
      partition_reference=instance_title,
      project_reference=project_reference
      title=instance_title,
    )

class DefaultScenarioMixinSlapTool(DefaultScenarioMixin):
  """
  Same as default scenario mixin but with SlapTool methods
  """

  def requestComputeNode(self, title):
    requestXml = self.portal.portal_slap.requestComputer(title)
    self.tic()
    self.assertTrue('marshal' in requestXml)
    compute_node = xml_marshaller.xml_marshaller.loads(requestXml)
    compute_node_id = getattr(compute_node, '_computer_id', None)
    self.assertNotEqual(None, compute_node_id)
    return compute_node_id.encode('UTF-8')

  def supplySoftware(self, server, url, state='available'):
    self.portal.portal_slap.supplySupply(url, server.getReference(), state)
    self.tic()

    software_installation = self.portal.portal_catalog.getResultValue(
        portal_type='Software Installation',
        url_string=url,
        default_aggregate_uid=server.getUid())

    self.assertNotEqual(None, software_installation)

    if state=='available':
      self.assertEqual('start_requested', software_installation.getSlapState())
    else:
      self.assertEqual('destroy_requested', software_installation.getSlapState())

  def formatComputeNode(self, compute_node, partition_count=10):
    compute_node_dict = dict(
      software_root='/opt',
      reference=compute_node.getReference(),
      netmask='255.255.255.0',
      address='128.0.0.1',
      instance_root='/srv'
    )
    compute_node_dict['partition_list'] = []
    a = compute_node_dict['partition_list'].append
    for i in range(1, partition_count+1):
      a(dict(
        reference='part%s' % i,
        tap=dict(name='tap%s' % i),
        address_list=[
          dict(addr='p%sa1' % i, netmask='p%sn1' % i),
          dict(addr='p%sa2' % i, netmask='p%sn2' % i)
        ]
      ))
    sm = getSecurityManager()
    try:
      self.login(compute_node.getUserId())
      self.portal.portal_slap.loadComputerConfigurationFromXML(
          xml_marshaller.xml_marshaller.dumps(compute_node_dict))
      self.tic()
      self.assertEqual(partition_count,
          len(compute_node.contentValues(portal_type='Compute Partition')))
    finally:
      setSecurityManager(sm)

  def simulateSlapgridSR(self, compute_node):
    sm = getSecurityManager()
    compute_node_user_id = compute_node.getUserId()
    try:
      self.login(compute_node_user_id)
      compute_node_xml = self.portal.portal_slap.getFullComputerInformation(
          computer_id=compute_node.getReference())
      if not isinstance(compute_node_xml, str):
        compute_node_xml = compute_node_xml.getBody()
      slap_compute_node = xml_marshaller.xml_marshaller.loads(compute_node_xml)
      self.assertEqual('Computer', slap_compute_node.__class__.__name__)
      for software_release in slap_compute_node._software_release_list:
        if software_release._requested_state == 'destroyed':
          self.portal.portal_slap.destroyedSoftwareRelease(
            software_release._software_release,
						compute_node.getReference())
        else:
          self.portal.portal_slap.availableSoftwareRelease(
            software_release._software_release,
						compute_node.getReference())
    finally:
      setSecurityManager(sm)
    self.tic()

  def simulateSlapgridUR(self, compute_node):
    sm = getSecurityManager()
    compute_node_user_id = compute_node.getUserId()
    try:
      self.login(compute_node_user_id)
      compute_node_xml = self.portal.portal_slap.getFullComputerInformation(
          computer_id=compute_node.getReference())
      if not isinstance(compute_node_xml, str):
        compute_node_xml = compute_node_xml.getBody()
      slap_compute_node = xml_marshaller.xml_marshaller.loads(compute_node_xml)
      self.assertEqual('Computer', slap_compute_node.__class__.__name__)
      destroyed_partition_id_list = []
      for partition in slap_compute_node._computer_partition_list:
        if partition._requested_state == 'destroyed' \
              and partition._need_modification == 1:
          self.portal.portal_slap.destroyedComputerPartition(compute_node.getReference(),
              partition._partition_id.encode("UTF-8")
              )
          destroyed_partition_id_list.append(partition._partition_id.encode("UTF-8"))
    finally:
      setSecurityManager(sm)
    self.tic()
    self.stepCallSlaposFreeComputePartitionAlarm()
    self.tic()
    free_partition_id_list = []
    for partition in compute_node.contentValues(portal_type='Compute Partition'):
      if partition.getReference() in destroyed_partition_id_list \
          and partition.getSlapState() == 'free':
        free_partition_id_list.append(partition.getReference())
    self.assertSameSet(destroyed_partition_id_list, free_partition_id_list)

  def simulateSlapgridCP(self, compute_node):
    sm = getSecurityManager()
    compute_node_reference = compute_node.getReference()
    compute_node_user_id = compute_node.getUserId()
    try:
      self.login(compute_node_user_id)
      compute_node_xml = self.portal.portal_slap.getFullComputerInformation(
        computer_id=compute_node.getReference())
      if not isinstance(compute_node_xml, str):
        compute_node_xml = compute_node_xml.getBody()
      slap_compute_node = xml_marshaller.xml_marshaller.loads(compute_node_xml)
      self.assertEqual('Computer', slap_compute_node.__class__.__name__)
      for partition in slap_compute_node._computer_partition_list:
        if partition._requested_state in ('started', 'stopped') \
              and partition._need_modification == 1:
          instance_reference = partition._instance_guid.encode('UTF-8')
          ip_list = partition._parameter_dict['ip_list']
          connection_xml = xml_marshaller.xml_marshaller.dumps(dict(
            url_1 = 'http://%s/' % ip_list[0][1],
            url_2 = 'http://%s/' % ip_list[1][1],
          ))
          self.login()
          instance_user_id = self.portal.portal_catalog.getResultValue(
              reference=instance_reference, portal_type="Software Instance").getUserId()

          oldsm = getSecurityManager()
          try:
            self.login(instance_user_id)
            self.portal.portal_slap.setComputerPartitionConnectionXml(
              computer_id=compute_node_reference,
              computer_partition_id=partition._partition_id,
              connection_xml=connection_xml
            )
            for slave in partition._parameter_dict['slave_instance_list']:
              slave_reference = slave['slave_reference']
              connection_xml = xml_marshaller.xml_marshaller.dumps(dict(
                url_1 = 'http://%s/%s' % (ip_list[0][1], slave_reference),
                url_2 = 'http://%s/%s' % (ip_list[1][1], slave_reference)
              ))
              self.portal.portal_slap.setComputerPartitionConnectionXml(
                computer_id=compute_node_reference,
                computer_partition_id=partition._partition_id,
                connection_xml=connection_xml,
                slave_reference=slave_reference
              )

          finally:
            setSecurityManager(oldsm)
    finally:
      setSecurityManager(sm)
    self.tic()

  def requestParameterBackwardCompatibility(self, request_dict):
    request_dict["partition_reference"] = request_dict.pop("title")
    if "software_release_uri" in request_dict:
      request_dict["software_release"] = request_dict.pop("software_release_uri")
    if "shared" in request_dict:
      shared = request_dict.pop("shared")
      if shared:
        request_dict["shared_xml"] = '<marshal><bool>1</bool></marshal>'
    if "state" in request_dict:
      request_dict["state"] = "<marshal><string>%s</string></marshal>" % request_dict["state"]
    return request_dict

  def personRequestInstanceNotReady(self, **kw):
    response = self.portal.portal_slap.requestComputerPartition(**self.requestParameterBackwardCompatibility(kw))
    status = getattr(response, 'status', None)
    self.assertEqual(408, status)
    self.tic()

  def personRequestInstance(self, **kw):
    response = self.portal.portal_slap.requestComputerPartition(**self.requestParameterBackwardCompatibility(kw))
    self.assertTrue(isinstance(response, str), "response is not a string: %s" % response)
    software_instance = xml_marshaller.xml_marshaller.loads(response)
    self.assertEqual('SoftwareInstance', software_instance.__class__.__name__)
    self.tic()
    return software_instance

