Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Nicolas Wavrant
slapos.core
Commits
598a69c1
Commit
598a69c1
authored
Aug 30, 2013
by
Vivien Alger
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support Request Generation: move to slapos_crm + first tests
parent
73c42017
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1319 additions
and
2 deletions
+1319
-2
master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_check_cloud_is_full.xml
...TemplateItem/portal_alarms/slapos_check_cloud_is_full.xml
+103
-0
master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_check_computer_state.xml
...emplateItem/portal_alarms/slapos_check_computer_state.xml
+103
-0
master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_check_software_instance_state.xml
...em/portal_alarms/slapos_check_software_instance_state.xml
+103
-0
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_checkCloudIsFull.xml
...teItem/portal_skins/slapos_crm/Alarm_checkCloudIsFull.xml
+80
-0
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_checkComputerState.xml
...Item/portal_skins/slapos_crm/Alarm_checkComputerState.xml
+80
-0
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_checkSoftwareInstanceState.xml
...tal_skins/slapos_crm/Alarm_checkSoftwareInstanceState.xml
+80
-0
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Computer_checkSoftwareInstanceState.xml
..._skins/slapos_crm/Computer_checkSoftwareInstanceState.xml
+77
-0
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Computer_checkState.xml
...plateItem/portal_skins/slapos_crm/Computer_checkState.xml
+84
-0
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/SoftwareInstance_checkState.xml
...m/portal_skins/slapos_crm/SoftwareInstance_checkState.xml
+105
-0
master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMSupportRequestGeneration.py
...TestTemplateItem/testSlapOSCRMSupportRequestGeneration.py
+498
-0
master/bt5/slapos_crm/bt/revision
master/bt5/slapos_crm/bt/revision
+1
-1
master/bt5/slapos_crm/bt/template_path_list
master/bt5/slapos_crm/bt/template_path_list
+3
-0
master/bt5/slapos_crm/bt/template_test_id_list
master/bt5/slapos_crm/bt/template_test_id_list
+2
-1
No files found.
master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_check_cloud_is_full.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Alarm"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
active_sense_method_id
</string>
</key>
<value>
<string>
Alarm_checkCloudIsFull
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
enabled
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
slapos_check_cloud_is_full
</string>
</value>
</item>
<item>
<key>
<string>
periodicity_hour
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_hour_frequency
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
<item>
<key>
<string>
periodicity_minute
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_minute_frequency
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
periodicity_month
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_month_day
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_start_date
</string>
</key>
<value>
<object>
<klass>
<global
name=
"DateTime"
module=
"DateTime.DateTime"
/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>
1377608640.0
</float>
<string>
GMT
</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key>
<string>
periodicity_week
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Alarm
</string>
</value>
</item>
<item>
<key>
<string>
sense_method_id
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string>
Check if vifib cloud is full
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_check_computer_state.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Alarm"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
active_sense_method_id
</string>
</key>
<value>
<string>
Alarm_checkComputerState
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
enabled
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
slapos_check_computer_state
</string>
</value>
</item>
<item>
<key>
<string>
periodicity_hour
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_hour_frequency
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
<item>
<key>
<string>
periodicity_minute
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_minute_frequency
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
periodicity_month
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_month_day
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_start_date
</string>
</key>
<value>
<object>
<klass>
<global
name=
"DateTime"
module=
"DateTime.DateTime"
/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>
1288051200.0
</float>
<string>
GMT
</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key>
<string>
periodicity_week
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Alarm
</string>
</value>
</item>
<item>
<key>
<string>
sense_method_id
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string>
Check computer\'s state
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/PathTemplateItem/portal_alarms/slapos_check_software_instance_state.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Alarm"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
active_sense_method_id
</string>
</key>
<value>
<string>
Alarm_checkSoftwareInstanceState
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
enabled
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
slapos_check_software_instance_state
</string>
</value>
</item>
<item>
<key>
<string>
periodicity_hour
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_hour_frequency
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
<item>
<key>
<string>
periodicity_minute
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_minute_frequency
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
periodicity_month
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_month_day
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
periodicity_start_date
</string>
</key>
<value>
<object>
<klass>
<global
name=
"DateTime"
module=
"DateTime.DateTime"
/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>
1377608640.0
</float>
<string>
GMT
</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key>
<string>
periodicity_week
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Alarm
</string>
</value>
</item>
<item>
<key>
<string>
sense_method_id
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string>
Check instances\' state
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_checkCloudIsFull.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"PythonScript"
module=
"Products.PythonScripts.PythonScript"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
Script_magic
</string>
</key>
<value>
<int>
3
</int>
</value>
</item>
<item>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
</klass>
<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>
_body
</string>
</key>
<value>
<string>
portal = context.getPortalObject()\n
\n
computer_list = portal.portal_catalog(\n
portal_type=\'Computer\',\n
validation_state = \'validated\',\n
capacity_scope = \'open\',\n
limit = 1\n
)\n
\n
if len(computer_list) == 0:\n
context.activate(tag=tag).Base_generateSupportRequestForSlapOS(\n
"VIFIB public cloud is full",\n
"No more computer with open capacity scope, add more ASAP"\n
)\n
</string>
</value>
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string>
*args, **kw
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
Alarm_checkCloudIsFull
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_checkComputerState.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"PythonScript"
module=
"Products.PythonScripts.PythonScript"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
Script_magic
</string>
</key>
<value>
<int>
3
</int>
</value>
</item>
<item>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
</klass>
<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>
_body
</string>
</key>
<value>
<string>
portal = context.getPortalObject()\n
category_uid = portal.restrictedTraverse(\n
"portal_categories/allocation_scope/open/public", None).getUid()\n
\n
if category_uid is not None:\n
portal.portal_catalog.searchAndActivate(\n
portal_type = \'Computer\',\n
validation_state = \'validated\',\n
default_allocation_scope_uid = category_uid,\n
method_id = \'Computer_checkState\',\n
activate_kw = {\'tag\':tag} \n
)\n
\n
context.activate(after_tag=tag).getId()\n
</string>
</value>
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string>
tag, fixit, params
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
Alarm_checkComputerState
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Alarm_checkSoftwareInstanceState.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"PythonScript"
module=
"Products.PythonScripts.PythonScript"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
Script_magic
</string>
</key>
<value>
<int>
3
</int>
</value>
</item>
<item>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
</klass>
<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>
_body
</string>
</key>
<value>
<string>
portal = context.getPortalObject()\n
category_uid = portal.restrictedTraverse(\n
"portal_categories/allocation_scope/open/public", None).getUid()\n
\n
if category_uid is not None:\n
portal.portal_catalog.searchAndActivate(\n
portal_type = \'Computer\',\n
validation_state = \'validated\',\n
default_allocation_scope_uid = category_uid,\n
method_id = \'Computer_checkSoftwareInstanceState\',\n
activate_kw = {\'tag\':tag} \n
)\n
\n
context.activate(after_tag=tag).getId()\n
</string>
</value>
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string>
tag, fixit, params
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
Alarm_checkSoftwareInstanceState
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Computer_checkSoftwareInstanceState.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"PythonScript"
module=
"Products.PythonScripts.PythonScript"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
Script_magic
</string>
</key>
<value>
<int>
3
</int>
</value>
</item>
<item>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
</klass>
<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>
_body
</string>
</key>
<value>
<string>
portal = context.getPortalObject()\n
\n
partition_list = portal.portal_catalog(portal_type = \'Computer Partition\',\n
free_for_request = 0,\n
parent_uid = context.getUid()\n
)\n
\n
for partition in partition_list:\n
instance = partition.getAggregateRelatedValue()\n
if instance is not None:\n
instance.activate().SoftwareInstance_checkState()\n
</string>
</value>
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string></string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
Computer_checkSoftwareInstanceState
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/Computer_checkState.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"PythonScript"
module=
"Products.PythonScripts.PythonScript"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
Script_magic
</string>
</key>
<value>
<int>
3
</int>
</value>
</item>
<item>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
</klass>
<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>
_body
</string>
</key>
<value>
<string
encoding=
"cdata"
>
<![CDATA[
from DateTime import DateTime\n
\n
portal = context.getPortalObject()\n
\n
last_contact = context.Computer_getLastestContactedDate()\n
reference = context.getReference()\n
\n
if last_contact != "Computer didn\'t contact the server":\n
if (DateTime() - last_contact) >
1:\n
tag = "Support Request generation for %s" % reference\n
context.activate(tag=tag).Base_generateSupportRequestForSlapOS(\n
"Support Request for %s" % reference,\n
"%s has not contacted the server for more than 24 hours" % reference\n
)\n
]]>
</string>
</value>
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string></string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
Computer_checkState
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/SkinTemplateItem/portal_skins/slapos_crm/SoftwareInstance_checkState.xml
0 → 100644
View file @
598a69c1
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"PythonScript"
module=
"Products.PythonScripts.PythonScript"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
Script_magic
</string>
</key>
<value>
<int>
3
</int>
</value>
</item>
<item>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
</klass>
<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>
_body
</string>
</key>
<value>
<string
encoding=
"cdata"
>
<![CDATA[
from DateTime import DateTime\n
import json\n
\n
reference = context.getReference()\n
memcached_dict = context.getPortalObject().portal_memcached.getMemcachedDict(\n
key_prefix=\'slap_tool\',\n
plugin_path=\'portal_memcached/default_memcached_plugin\')\n
\n
try:\n
d = memcached_dict[reference]\n
except KeyError:\n
return \n
\n
d = json.loads(d)\n
result = d[\'text\']\n
last_contact = DateTime(d.get(\'created_at\'))\n
now = DateTime()\n
\n
if result.startswith(\'#error \'):\n
# If no change in the last 2 hours generate a support request\n
if (now - last_contact) >
0.08:\n
tag = "Support Request generation for %s" % reference\n
context.activate(tag=tag).Base_generateSupportRequestForSlapOS(\n
"Support Request for %s" % reference,\n
"%s has been in error state for more than 2 hours" % reference\n
)\n
\n
elif result.startswith(\'#access \'):\n
# If there was no contact for more than one day generate a support request\n
if (now - last_contact) > 1:\n
tag = "Support Request generation for %s" % reference\n
context.activate(tag=tag).Base_generateSupportRequestForSlapOS(\n
"Support Request for %s" % reference,\n
"%s has not contacted the server for more than 24 hours" % reference\n
)\n
]]>
</string>
</value>
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string></string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
SoftwareInstance_checkState
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
master/bt5/slapos_crm/TestTemplateItem/testSlapOSCRMSupportRequestGeneration.py
0 → 100644
View file @
598a69c1
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2013 Nexedi SA and Contributors. All Rights Reserved.
#
# 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 adviced 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
##############################################################################
import
transaction
from
Products.SlapOS.tests.testSlapOSMixin
import
\
testSlapOSMixin
from
Products.ERP5Type.tests.utils
import
createZODBPythonScript
import
json
from
DateTime
import
DateTime
class
TestSlapOSCloudSupportRequestGeneration
(
testSlapOSMixin
):
def
afterSetUp
(
self
):
super
(
TestSlapOSCloudSupportRequestGeneration
,
self
).
afterSetUp
()
self
.
new_id
=
self
.
generateNewId
()
def
_makeComputer
(
self
,
new_id
):
# Clone computer document
computer
=
self
.
portal
.
computer_module
\
.
template_computer
.
Base_createCloneDocument
(
batch_mode
=
1
)
computer
.
edit
(
title
=
"computer ticket %s"
%
(
new_id
,
),
reference
=
"TESTCOMPT-%s"
%
(
new_id
,
)
)
computer
.
validate
()
return
computer
def
_makeComputerPartitions
(
self
,
computer
):
for
i
in
range
(
1
,
5
):
id_
=
'partition%s'
%
(
i
,
)
p
=
computer
.
newContent
(
portal_type
=
'Computer Partition'
,
id
=
id_
,
title
=
id_
,
reference
=
id_
,
default_network_address_ip_address
=
'ip_address_%s'
%
i
,
default_network_address_netmask
=
'netmask_%s'
%
i
)
p
.
markFree
()
p
.
validate
()
def
_makeHostingSubscription
(
self
,
new_id
):
hosting_subscription
=
self
.
portal
\
.
hosting_subscription_module
.
template_hosting_subscription
\
.
Base_createCloneDocument
(
batch_mode
=
1
)
hosting_subscription
.
validate
()
hosting_subscription
.
edit
(
title
=
"Test hosting sub ticket %s"
%
new_id
,
reference
=
"TESTHST-%s"
%
new_id
,
)
return
hosting_subscription
def
_makeSoftwareInstance
(
self
,
hosting_subscription
,
software_url
):
kw
=
dict
(
software_release
=
software_url
,
software_type
=
self
.
generateNewSoftwareType
(),
instance_xml
=
self
.
generateSafeXml
(),
sla_xml
=
self
.
generateSafeXml
(),
shared
=
False
,
software_title
=
hosting_subscription
.
getTitle
(),
state
=
'started'
)
hosting_subscription
.
requestStart
(
**
kw
)
hosting_subscription
.
requestInstance
(
**
kw
)
def
test_Base_generateSupportRequestForSlapOS
(
self
):
title
=
"Test Support Request %s"
%
self
.
new_id
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
Base_generateSupportRequestForSlapOS
(
title
,
title
)
self
.
tic
()
support_request
=
self
.
portal
.
portal_catalog
.
getResultValue
(
portal_type
=
'Support Request'
,
title
=
title
,
simulation_state
=
[
"validated"
,
"submitted"
,
"suspended"
]
)
self
.
assertNotEqual
(
support_request
,
None
)
def
test_Base_generateSupportRequestForSlapOS_do_not_recreate_if_open
(
self
):
title
=
"Test Support Request %s"
%
self
.
new_id
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
Base_generateSupportRequestForSlapOS
(
title
,
title
)
self
.
tic
()
computer
.
Base_generateSupportRequestForSlapOS
(
title
,
title
)
self
.
tic
()
support_request_list
=
self
.
portal
.
portal_catalog
(
portal_type
=
'Support Request'
,
title
=
title
,
simulation_state
=
[
"validated"
,
"submitted"
,
"suspended"
]
)
self
.
assertEqual
(
len
(
support_request_list
),
1
)
def
test_Base_generateSupportRequestForSlapOS_recreate_if_closed
(
self
):
title
=
"Test Support Request %s"
%
self
.
new_id
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
Base_generateSupportRequestForSlapOS
(
title
,
title
)
self
.
tic
()
support_request
=
self
.
portal
.
portal_catalog
.
getResultValue
(
portal_type
=
'Support Request'
,
title
=
title
,
simulation_state
=
[
"validated"
,
"submitted"
,
"suspended"
]
)
support_request
.
invalidate
()
self
.
tic
()
computer
.
Base_generateSupportRequestForSlapOS
(
title
,
title
)
self
.
tic
()
support_request_list
=
self
.
portal
.
portal_catalog
(
portal_type
=
'Support Request'
,
title
=
title
)
self
.
assertEqual
(
len
(
support_request_list
),
2
)
support_request_list
=
self
.
portal
.
portal_catalog
(
portal_type
=
'Support Request'
,
title
=
title
,
simulation_state
=
[
"validated"
,
"submitted"
,
"suspended"
]
)
self
.
assertEqual
(
len
(
support_request_list
),
1
)
def
_simulateBase_generateSupportRequestForSlapOS
(
self
):
script_name
=
'Base_generateSupportRequestForSlapOS'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
raise
ValueError
(
'Precondition failed: %s exists in custom'
%
script_name
)
createZODBPythonScript
(
self
.
portal
.
portal_skins
.
custom
,
script_name
,
'*args, **kw'
,
'# Script body
\
n
'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by Base_generateSupportRequestForSlapOS') """
)
transaction
.
commit
()
def
_dropBase_generateSupportRequestForSlapOS
(
self
):
script_name
=
'Base_generateSupportRequestForSlapOS'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
self
.
portal
.
portal_skins
.
custom
.
manage_delObjects
(
script_name
)
transaction
.
commit
()
def
test_Computer_checkState
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
memcached_dict
=
self
.
portal
.
portal_memcached
.
getMemcachedDict
(
key_prefix
=
'slap_tool'
,
plugin_path
=
'portal_memcached/default_memcached_plugin'
)
memcached_dict
[
computer
.
getReference
()]
=
json
.
dumps
(
{
"created_at"
:
"%s"
%
(
DateTime
()
-
1.1
)}
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
computer
.
Computer_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
computer
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_Computer_checkState_empty_cache
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
computer
.
Computer_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertNotEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
computer
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_SoftwareInstance_checkState_error_out_time
(
self
):
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
memcached_dict
=
self
.
portal
.
portal_memcached
.
getMemcachedDict
(
key_prefix
=
'slap_tool'
,
plugin_path
=
'portal_memcached/default_memcached_plugin'
)
memcached_dict
[
instance
.
getReference
()]
=
json
.
dumps
(
{
"created_at"
:
"%s"
%
(
DateTime
()
-
0.1
),
"text"
:
"#error "
}
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
instance
.
SoftwareInstance_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_SoftwareInstance_checkState_error_in_time
(
self
):
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
memcached_dict
=
self
.
portal
.
portal_memcached
.
getMemcachedDict
(
key_prefix
=
'slap_tool'
,
plugin_path
=
'portal_memcached/default_memcached_plugin'
)
memcached_dict
[
instance
.
getReference
()]
=
json
.
dumps
(
{
"created_at"
:
"%s"
%
(
DateTime
()),
"text"
:
"#error "
}
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
instance
.
SoftwareInstance_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertNotEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_SoftwareInstance_checkState_access_out_time
(
self
):
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
memcached_dict
=
self
.
portal
.
portal_memcached
.
getMemcachedDict
(
key_prefix
=
'slap_tool'
,
plugin_path
=
'portal_memcached/default_memcached_plugin'
)
memcached_dict
[
instance
.
getReference
()]
=
json
.
dumps
(
{
"created_at"
:
"%s"
%
(
DateTime
()
-
1.1
),
"text"
:
"#access "
}
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
instance
.
SoftwareInstance_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_SoftwareInstance_checkState_access_in_time
(
self
):
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
memcached_dict
=
self
.
portal
.
portal_memcached
.
getMemcachedDict
(
key_prefix
=
'slap_tool'
,
plugin_path
=
'portal_memcached/default_memcached_plugin'
)
memcached_dict
[
instance
.
getReference
()]
=
json
.
dumps
(
{
"created_at"
:
"%s"
%
(
DateTime
()),
"text"
:
"#access "
}
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
instance
.
SoftwareInstance_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertNotEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_SoftwareInstance_checkState_empty_cache
(
self
):
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
instance
.
SoftwareInstance_checkState
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertNotEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_Alarm_checkCloudIsFull
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
edit
(
capacity_scope
=
'close'
)
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
self
.
portal
.
portal_alarms
.
slapos_check_cloud_is_full
.
activeSense
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
self
.
portal
.
portal_alarms
.
slapos_check_cloud_is_full
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_Alarm_checkCloudIsFull_open_computer
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
edit
(
capacity_scope
=
'open'
)
self
.
tic
()
# Reset slapos_check_cloud_is_full workflow_history for edit_workflow last action
self
.
portal
.
portal_alarms
.
slapos_check_cloud_is_full
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
]
=
''
self
.
_simulateBase_generateSupportRequestForSlapOS
()
try
:
self
.
portal
.
portal_alarms
.
slapos_check_cloud_is_full
.
activeSense
()
self
.
tic
()
finally
:
self
.
_dropBase_generateSupportRequestForSlapOS
()
self
.
assertNotEqual
(
'Visited by Base_generateSupportRequestForSlapOS'
,
self
.
portal
.
portal_alarms
.
slapos_check_cloud_is_full
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
_simulateSoftwareInstance_checkState
(
self
):
script_name
=
'SoftwareInstance_checkState'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
raise
ValueError
(
'Precondition failed: %s exists in custom'
%
script_name
)
createZODBPythonScript
(
self
.
portal
.
portal_skins
.
custom
,
script_name
,
'*args, **kw'
,
'# Script body
\
n
'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by SoftwareInstance_checkState') """
)
transaction
.
commit
()
def
_dropSoftwareInstance_checkState
(
self
):
script_name
=
'SoftwareInstance_checkState'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
self
.
portal
.
portal_skins
.
custom
.
manage_delObjects
(
script_name
)
transaction
.
commit
()
def
test_Computer_checkSoftwareInstanceState
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
self
.
_makeComputerPartitions
(
computer
)
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
instance
.
edit
(
aggregate_value
=
computer
.
partition1
.
getRelativeUrl
())
computer
.
partition1
.
markBusy
()
self
.
tic
()
self
.
_simulateSoftwareInstance_checkState
()
try
:
computer
.
Computer_checkSoftwareInstanceState
()
self
.
tic
()
finally
:
self
.
_dropSoftwareInstance_checkState
()
self
.
assertEqual
(
'Visited by SoftwareInstance_checkState'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_Computer_checkSoftwareInstanceState_instance_not_allocated
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
host_sub
=
self
.
_makeHostingSubscription
(
self
.
new_id
)
self
.
_makeSoftwareInstance
(
host_sub
,
self
.
generateNewSoftwareReleaseUrl
())
instance
=
host_sub
.
getPredecessorValue
()
self
.
tic
()
self
.
_simulateSoftwareInstance_checkState
()
try
:
computer
.
Computer_checkSoftwareInstanceState
()
self
.
tic
()
finally
:
self
.
_dropSoftwareInstance_checkState
()
self
.
assertNotEqual
(
'Visited by SoftwareInstance_checkState'
,
instance
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
_simulateComputer_checkState
(
self
):
script_name
=
'Computer_checkState'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
raise
ValueError
(
'Precondition failed: %s exists in custom'
%
script_name
)
createZODBPythonScript
(
self
.
portal
.
portal_skins
.
custom
,
script_name
,
'*args, **kw'
,
'# Script body
\
n
'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by Computer_checkState') """
)
transaction
.
commit
()
def
_dropComputer_checkState
(
self
):
script_name
=
'Computer_checkState'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
self
.
portal
.
portal_skins
.
custom
.
manage_delObjects
(
script_name
)
transaction
.
commit
()
def
test_Alarm_checkComputerState
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
edit
(
allocation_scope
=
'open/public'
)
self
.
_simulateComputer_checkState
()
try
:
self
.
portal
.
portal_alarms
.
slapos_check_computer_state
.
activeSense
()
self
.
tic
()
finally
:
self
.
_dropComputer_checkState
()
self
.
assertEqual
(
'Visited by Computer_checkState'
,
computer
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_Alarm_checkComputerState_no_public_computer
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
self
.
_simulateComputer_checkState
()
try
:
self
.
portal
.
portal_alarms
.
slapos_check_computer_state
.
activeSense
()
self
.
tic
()
finally
:
self
.
_dropComputer_checkState
()
self
.
assertNotEqual
(
'Visited by Computer_checkState'
,
computer
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
_simulateComputer_checkSoftwareInstanceState
(
self
):
script_name
=
'Computer_checkSoftwareInstanceState'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
raise
ValueError
(
'Precondition failed: %s exists in custom'
%
script_name
)
createZODBPythonScript
(
self
.
portal
.
portal_skins
.
custom
,
script_name
,
'*args, **kw'
,
'# Script body
\
n
'
"""portal_workflow = context.portal_workflow
portal_workflow.doActionFor(context, action='edit_action', comment='Visited by Computer_checkSoftwareInstanceState') """
)
transaction
.
commit
()
def
_dropComputer_checkSoftwareInstanceState
(
self
):
script_name
=
'Computer_checkSoftwareInstanceState'
if
script_name
in
self
.
portal
.
portal_skins
.
custom
.
objectIds
():
self
.
portal
.
portal_skins
.
custom
.
manage_delObjects
(
script_name
)
transaction
.
commit
()
def
test_Alarm_checkSoftwareInstanceState
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
computer
.
edit
(
allocation_scope
=
'open/public'
)
self
.
_simulateComputer_checkSoftwareInstanceState
()
try
:
self
.
portal
.
portal_alarms
.
slapos_check_software_instance_state
.
activeSense
()
self
.
tic
()
finally
:
self
.
_dropComputer_checkSoftwareInstanceState
()
self
.
assertEqual
(
'Visited by Computer_checkSoftwareInstanceState'
,
computer
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
def
test_Alarm_checkSoftwareInstanceState_no_public_computer
(
self
):
computer
=
self
.
_makeComputer
(
self
.
new_id
)
self
.
_simulateComputer_checkSoftwareInstanceState
()
try
:
self
.
portal
.
portal_alarms
.
slapos_check_software_instance_state
.
activeSense
()
self
.
tic
()
finally
:
self
.
_dropComputer_checkSoftwareInstanceState
()
self
.
assertNotEqual
(
'Visited by Computer_checkSoftwareInstanceState'
,
computer
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
master/bt5/slapos_crm/bt/revision
View file @
598a69c1
20
21
\ No newline at end of file
\ No newline at end of file
master/bt5/slapos_crm/bt/template_path_list
View file @
598a69c1
event_module/slapos_crm_web_message_template
event_module/slapos_crm_web_message_template
portal_alarms/slapos_check_cloud_is_full
portal_alarms/slapos_check_computer_state
portal_alarms/slapos_check_software_instance_state
portal_alarms/slapos_crm_cancel_invoice
portal_alarms/slapos_crm_cancel_invoice
portal_alarms/slapos_crm_create_regularisation_request
portal_alarms/slapos_crm_create_regularisation_request
portal_alarms/slapos_crm_delete_hosting_subscription
portal_alarms/slapos_crm_delete_hosting_subscription
...
...
master/bt5/slapos_crm/bt/template_test_id_list
View file @
598a69c1
testSlapOSCRMSkins
testSlapOSCRMSkins
testSlapOSCRMAlarm
testSlapOSCRMAlarm
\ No newline at end of file
testSlapOSCRMSupportRequestGeneration
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment