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
1
Merge Requests
1
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
Romain Courteaud
slapos.core
Commits
1a2dcd97
Commit
1a2dcd97
authored
Jan 23, 2023
by
Romain Courteaud
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
slapos_pdm: check upgrade
parent
3f096f85
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
143 additions
and
1235 deletions
+143
-1235
master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/UpgradeDecision_processUpgrade.py
...portal_skins/slapos_pdm/UpgradeDecision_processUpgrade.py
+40
-0
master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/UpgradeDecision_processUpgrade.xml
...ortal_skins/slapos_pdm/UpgradeDecision_processUpgrade.xml
+3
-25
master/bt5/slapos_pdm/TestTemplateItem/portal_components/test.erp5.testSlapOSPDMAlarm.py
...ateItem/portal_components/test.erp5.testSlapOSPDMAlarm.py
+12
-72
master/bt5/slapos_pdm/TestTemplateItem/portal_components/test.erp5.testSlapOSPDMCreateUpgradeDecisionSkins.py
...ents/test.erp5.testSlapOSPDMCreateUpgradeDecisionSkins.py
+0
-67
master/bt5/slapos_pdm/TestTemplateItem/portal_components/test.erp5.testSlapOSPDMSkins.py
...ateItem/portal_components/test.erp5.testSlapOSPDMSkins.py
+88
-953
master/bt5/slapos_pdm/WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/script_UpgradeDecision_requestUpgrade.py
...terface_workflow/script_UpgradeDecision_requestUpgrade.py
+0
-51
master/bt5/slapos_pdm/WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/state_draft.xml
..._workflow/upgrade_slap_interface_workflow/state_draft.xml
+0
-1
master/bt5/slapos_pdm/WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/transition_request_upgrade.xml
...de_slap_interface_workflow/transition_request_upgrade.xml
+0
-66
No files found.
master/bt5/slapos_pdm/SkinTemplateItem/portal_skins/slapos_pdm/UpgradeDecision_processUpgrade.py
0 → 100644
View file @
1a2dcd97
from
DateTime
import
DateTime
upgrade_decision
=
context
if
upgrade_decision
.
getSimulationState
()
!=
'started'
:
# Update Decision is not on started state, Upgrade is not possible!
return
instance_tree
=
upgrade_decision
.
getAggregateValue
(
"Instance Tree"
)
software_release_url
=
upgrade_decision
.
getSoftwareReleaseValue
().
getUrlString
()
status
=
instance_tree
.
getSlapState
()
if
status
==
"start_requested"
:
state
=
"started"
elif
status
==
"stop_requested"
:
state
=
"stopped"
elif
status
==
"destroy_requested"
:
state
=
"destroyed"
else
:
raise
ValueError
(
'Unhandled state: %s'
%
status
)
if
state
==
'destroyed'
:
# Nothing to do
upgrade_decision
.
reject
(
comment
=
"Instance Tree destroyed"
)
else
:
person
=
instance_tree
.
getDestinationSectionValue
(
portal_type
=
"Person"
)
person
.
requestSoftwareInstance
(
state
=
state
,
software_release
=
software_release_url
,
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
()
)
upgrade_decision
.
stop
(
comment
=
"Upgrade Processed for the Instance Tree!"
)
master/bt5/slapos_pdm/
WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/script_UpgradeDecision_request
Upgrade.xml
→
master/bt5/slapos_pdm/
SkinTemplateItem/portal_skins/slapos_pdm/UpgradeDecision_process
Upgrade.xml
View file @
1a2dcd97
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
<ZopeData>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<pickle>
<global
name=
"
Workflow Script"
module=
"erp5.portal_type
"
/>
<global
name=
"
PythonScript"
module=
"Products.PythonScripts.PythonScript
"
/>
</pickle>
</pickle>
<pickle>
<pickle>
<dictionary>
<dictionary>
...
@@ -50,33 +50,11 @@
...
@@ -50,33 +50,11 @@
</item>
</item>
<item>
<item>
<key>
<string>
_params
</string>
</key>
<key>
<string>
_params
</string>
</key>
<value>
<string>
state_change
</string>
</value>
<value>
<string></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>
<item>
<item>
<key>
<string>
id
</string>
</key>
<key>
<string>
id
</string>
</key>
<value>
<string>
script_UpgradeDecision_requestUpgrade
</string>
</value>
<value>
<string>
UpgradeDecision_processUpgrade
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Workflow Script
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string>
UpgradeDecision_requestUpgrade
</string>
</value>
</item>
</item>
</dictionary>
</dictionary>
</pickle>
</pickle>
...
...
master/bt5/slapos_pdm/TestTemplateItem/portal_components/test.erp5.testSlapOSPDMAlarm.py
View file @
1a2dcd97
...
@@ -21,15 +21,12 @@
...
@@ -21,15 +21,12 @@
from
erp5.component.test.SlapOSTestCaseMixin
import
SlapOSTestCaseMixin
,
TemporaryAlarmScript
from
erp5.component.test.SlapOSTestCaseMixin
import
SlapOSTestCaseMixin
,
TemporaryAlarmScript
class
TestSlapOSUpgradeDecisionProcess
(
SlapOSTestCaseMixin
):
class
TestSlapOSUpgradeDecisionProcess
(
SlapOSTestCaseMixin
):
def
afterSetUp
(
self
):
SlapOSTestCaseMixin
.
afterSetUp
(
self
)
self
.
new_id
=
self
.
generateNewId
()
def
_makeUpgradeDecision
(
self
,
confirm
=
True
):
def
_makeUpgradeDecision
(
self
,
confirm
=
True
):
upgrade_decision
=
self
.
portal
.
\
upgrade_decision
=
self
.
portal
.
\
upgrade_decision_module
.
newContent
(
upgrade_decision_module
.
newContent
(
portal_type
=
"Upgrade Decision"
,
portal_type
=
"Upgrade Decision"
,
title
=
"TESTUPDE-%s"
%
self
.
new_id
)
title
=
"TESTUPDE-%s"
%
self
.
generateNewId
()
)
if
confirm
:
if
confirm
:
upgrade_decision
.
confirm
()
upgrade_decision
.
confirm
()
return
upgrade_decision
return
upgrade_decision
...
@@ -39,85 +36,28 @@ class TestSlapOSUpgradeDecisionProcess(SlapOSTestCaseMixin):
...
@@ -39,85 +36,28 @@ class TestSlapOSUpgradeDecisionProcess(SlapOSTestCaseMixin):
.
instance_tree_module
.
template_instance_tree
\
.
instance_tree_module
.
template_instance_tree
\
.
Base_createCloneDocument
(
batch_mode
=
1
)
.
Base_createCloneDocument
(
batch_mode
=
1
)
instance_tree
.
validate
()
instance_tree
.
validate
()
new_id
=
self
.
generateNewId
()
instance_tree
.
edit
(
instance_tree
.
edit
(
title
=
"Test hosting sub start %s"
%
self
.
new_id
,
title
=
"Test hosting sub start %s"
%
new_id
,
reference
=
"TESTHSS-%s"
%
self
.
new_id
,
reference
=
"TESTHSS-%s"
%
new_id
)
)
self
.
portal
.
portal_workflow
.
_jumpToStateFor
(
instance_tree
,
slap_state
)
self
.
portal
.
portal_workflow
.
_jumpToStateFor
(
instance_tree
,
slap_state
)
return
instance_tree
return
instance_tree
def
test_
tocheck_
alarm_upgrade_decision_process_instance_tree
(
self
):
def
test_alarm_upgrade_decision_process_instance_tree
(
self
):
upgrade_decision
=
self
.
_makeUpgradeDecision
()
upgrade_decision
=
self
.
_makeUpgradeDecision
()
upgrade_decision
.
start
()
upgrade_decision
.
start
()
self
.
tic
()
self
.
tic
()
self
.
_test_alarm
(
self
.
portal
.
portal_alarms
.
slapos_pdm_upgrade_decision_process_started
,
upgrade_decision
,
'UpgradeDecision_processUpgrade'
)
def
test_tocheck_alarm_upgrade_decision_process_planned
(
self
):
upgrade_decision
=
self
.
_makeUpgradeDecision
(
confirm
=
0
)
upgrade_decision
.
plan
()
self
.
_test_alarm
(
self
.
portal
.
portal_alarms
.
slapos_pdm_upgrade_decision_process_planned
,
upgrade_decision
,
'UpgradeDecision_notify'
)
def
test_tocheck_alarm_upgrade_decision_process_stopped
(
self
):
upgrade_decision
=
self
.
_makeUpgradeDecision
()
upgrade_decision
.
start
()
upgrade_decision
.
stop
()
self
.
tic
()
self
.
_test_alarm
(
self
.
portal
.
portal_alarms
.
slapos_pdm_upgrade_decision_process_stopped
,
upgrade_decision
,
'UpgradeDecision_notifyDelivered'
)
def
_test_alarm_compute_node_create_upgrade_decision
(
self
,
allocation_scope
,
upgrade_scope
):
compute_node
=
self
.
_makeComputeNode
(
allocation_scope
=
allocation_scope
)[
0
]
compute_node
.
setUpgradeScope
(
upgrade_scope
)
self
.
_test_alarm
(
self
.
portal
.
portal_alarms
.
slapos_pdm_compute_node_create_upgrade_decision
,
compute_node
,
'ComputeNode_checkAndCreateUpgradeDecision'
)
def
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
self
,
allocation_scope
,
upgrade_scope
):
compute_node
=
self
.
_makeComputeNode
(
allocation_scope
=
allocation_scope
)[
0
]
compute_node
.
setUpgradeScope
(
upgrade_scope
)
self
.
_test_alarm_not_visited
(
self
.
portal
.
portal_alarms
.
slapos_pdm_compute_node_create_upgrade_decision
,
compute_node
,
'ComputeNode_checkAndCreateUpgradeDecision'
)
def
test_alarm_compute_node_create_upgrade_decision_auto
(
self
):
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'open/public'
,
'auto'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'open/personal'
,
'auto'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'open/subscription'
,
'auto'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/outdated'
,
'auto'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/maintanance'
,
'auto'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/termination'
,
'auto'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/noallocation'
,
'auto'
)
def
test_alarm_compute_node_create_upgrade_decision_ask_confirmation
(
self
):
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'open/public'
,
'confirmation'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'open/personal'
,
'confirmation'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'open/subscription'
,
'confirmation'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/outdated'
,
'confirmation'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/maintanance'
,
'confirmation'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/termination'
,
'confirmation'
)
self
.
_test_alarm_compute_node_create_upgrade_decision
(
'close/noallocation'
,
'confirmation'
)
with
TemporaryAlarmScript
(
self
.
portal
,
'UpgradeDecision_processUpgrade'
):
self
.
portal
.
portal_alarms
.
slapos_pdm_upgrade_decision_process_started
.
\
activeSense
()
self
.
tic
()
def
test_alarm_compute_node_create_upgrade_decision_never
(
self
):
self
.
assertEqual
(
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'open/public'
,
'never'
)
'Visited by UpgradeDecision_processUpgrade'
,
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'open/personal'
,
'never'
)
upgrade_decision
.
workflow_history
[
'edit_workflow'
][
-
1
][
'comment'
])
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'open/subscription'
,
'never'
)
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'close/outdated'
,
'never'
)
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'close/maintanance'
,
'never'
)
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'close/termination'
,
'never'
)
self
.
_test_alarm_compute_node_create_upgrade_decision_not_visited
(
'close/noallocation'
,
'never'
)
def
test_alarm_instance_tree_create_upgrade_decision
(
self
):
def
test_alarm_instance_tree_create_upgrade_decision
(
self
):
instance_tree
=
self
.
_makeInstanceTree
()
instance_tree
=
self
.
_makeInstanceTree
()
...
...
master/bt5/slapos_pdm/TestTemplateItem/portal_components/test.erp5.testSlapOSPDMCreateUpgradeDecisionSkins.py
View file @
1a2dcd97
...
@@ -37,73 +37,6 @@ class TestSlapOSPDMCreateUpgradeDecisionSkins(TestSlapOSPDMMixinSkins):
...
@@ -37,73 +37,6 @@ class TestSlapOSPDMCreateUpgradeDecisionSkins(TestSlapOSPDMMixinSkins):
)
)
self
.
assertEqual
(
None
,
instance_tree
.
InstanceTree_createUpgradeDecision
())
self
.
assertEqual
(
None
,
instance_tree
.
InstanceTree_createUpgradeDecision
())
def
bootstrapAllocableInstanceTree
(
self
,
is_allocated
=
False
,
shared
=
False
,
node
=
"compute"
):
project
=
self
.
addProject
()
person
=
self
.
_makePerson
(
project
)
software_product
=
self
.
_makeSoftwareProduct
(
project
)
release_variation
=
software_product
.
contentValues
(
portal_type
=
'Software Product Release Variation'
)[
0
]
type_variation
=
software_product
.
contentValues
(
portal_type
=
'Software Product Type Variation'
)[
0
]
self
.
tic
()
if
node
==
"compute"
:
person
.
requestComputeNode
(
compute_node_title
=
'test compute node'
,
project_reference
=
project
.
getReference
())
self
.
tic
()
compute_node
=
self
.
portal
.
portal_catalog
.
getResultValue
(
portal_type
=
'Compute Node'
,
reference
=
self
.
portal
.
REQUEST
.
get
(
'compute_node_reference'
)
)
assert
compute_node
is
not
None
elif
node
==
"remote"
:
compute_node
=
self
.
portal
.
compute_node_module
.
newContent
(
portal_type
=
"Remote Node"
,
follow_up_value
=
project
)
elif
node
==
"instance"
:
compute_node
=
self
.
portal
.
compute_node_module
.
newContent
(
portal_type
=
"Instance Node"
,
follow_up_value
=
project
)
else
:
raise
ValueError
(
"Unsupported node value: %s"
%
node
)
request_kw
=
dict
(
software_release
=
release_variation
.
getUrlString
(),
software_type
=
type_variation
.
getTitle
(),
instance_xml
=
self
.
generateSafeXml
(),
sla_xml
=
self
.
generateSafeXml
(),
shared
=
shared
,
software_title
=
'test tree'
,
state
=
'started'
,
project_reference
=
project
.
getReference
()
)
person
.
requestSoftwareInstance
(
**
request_kw
)
instance_tree
=
self
.
portal
.
REQUEST
.
get
(
'request_instance_tree'
)
if
is_allocated
:
if
(
node
==
"instance"
)
and
(
shared
):
real_compute_node
=
self
.
portal
.
compute_node_module
.
newContent
(
portal_type
=
"Compute Node"
,
follow_up_value
=
project
)
partition
=
real_compute_node
.
newContent
(
portal_type
=
'Compute Partition'
)
software_instance
=
self
.
portal
.
software_instance_module
.
newContent
(
portal_type
=
"Software Instance"
,
follow_up_value
=
project
,
aggregate_value
=
partition
)
compute_node
.
edit
(
specialise_value
=
software_instance
)
elif
(
node
==
"instance"
)
and
(
not
shared
):
raise
NotImplementedError
(
'can not allocate on instance node'
)
else
:
partition
=
compute_node
.
newContent
(
portal_type
=
'Compute Partition'
)
instance
=
instance_tree
.
getSuccessorValue
()
instance
.
edit
(
aggregate_value
=
partition
)
self
.
tic
()
return
software_product
,
release_variation
,
type_variation
,
compute_node
,
instance_tree
def
checkCreatedUpgradeDecision
(
self
,
upgrade_decision
,
instance_tree
,
def
checkCreatedUpgradeDecision
(
self
,
upgrade_decision
,
instance_tree
,
software_product
,
release_variation
,
software_product
,
release_variation
,
type_variation
):
type_variation
):
...
...
master/bt5/slapos_pdm/TestTemplateItem/portal_components/test.erp5.testSlapOSPDMSkins.py
View file @
1a2dcd97
This diff is collapsed.
Click to expand it.
master/bt5/slapos_pdm/WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/script_UpgradeDecision_requestUpgrade.py
deleted
100644 → 0
View file @
3f096f85
upgrade_decision
=
state_change
[
"object"
]
from
DateTime
import
DateTime
if
upgrade_decision
.
getSimulationState
()
!=
'started'
:
# Update Decision is not on started state, Upgrade is not possible!
return
instance_tree
=
upgrade_decision
.
UpgradeDecision_getAggregateValue
(
"Instance Tree"
)
software_release
=
upgrade_decision
.
UpgradeDecision_getAggregateValue
(
"Software Release"
)
compute_node
=
upgrade_decision
.
UpgradeDecision_getAggregateValue
(
"Compute Node"
)
if
software_release
is
None
:
return
if
compute_node
is
None
and
instance_tree
is
None
:
return
if
compute_node
is
not
None
and
instance_tree
is
not
None
:
raise
ValueError
(
"Something is wrong, you cannot upgrade Compute Node and Instance Tree on the same decision."
)
software_release_url
=
software_release
.
getUrlString
()
if
compute_node
is
not
None
:
compute_node
.
requestSoftwareRelease
(
software_release_url
=
software_release_url
,
state
=
"available"
)
upgrade_decision
.
stop
(
comment
=
"Upgrade Processed for the Compute Node!"
)
return
status
=
instance_tree
.
getSlapState
()
if
status
==
"start_requested"
:
state
=
"started"
elif
status
==
"stop_requested"
:
state
=
"stopped"
elif
status
==
"destroy_requested"
:
state
=
"destroyed"
person
=
instance_tree
.
getDestinationSectionValue
(
portal_type
=
"Person"
)
person
.
requestSoftwareInstance
(
state
=
state
,
software_release
=
software_release_url
,
software_title
=
instance_tree
.
getTitle
(),
software_type
=
instance_tree
.
getSourceReference
(),
instance_xml
=
instance_tree
.
getTextContent
(),
sla_xml
=
instance_tree
.
getSlaXml
(),
shared
=
instance_tree
.
isRootSlave
()
)
upgrade_decision
.
stop
(
comment
=
"Upgrade Processed for the Instance Tree!"
)
master/bt5/slapos_pdm/WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/state_draft.xml
View file @
1a2dcd97
...
@@ -18,7 +18,6 @@
...
@@ -18,7 +18,6 @@
<tuple>
<tuple>
<string>
destination/portal_workflow/upgrade_slap_interface_workflow/transition_approve_registration
</string>
<string>
destination/portal_workflow/upgrade_slap_interface_workflow/transition_approve_registration
</string>
<string>
destination/portal_workflow/upgrade_slap_interface_workflow/transition_notify
</string>
<string>
destination/portal_workflow/upgrade_slap_interface_workflow/transition_notify
</string>
<string>
destination/portal_workflow/upgrade_slap_interface_workflow/transition_request_upgrade
</string>
</tuple>
</tuple>
</value>
</value>
</item>
</item>
...
...
master/bt5/slapos_pdm/WorkflowTemplateItem/portal_workflow/upgrade_slap_interface_workflow/transition_request_upgrade.xml
deleted
100644 → 0
View file @
3f096f85
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"Workflow Transition"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
action_name
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
categories
</string>
</key>
<value>
<tuple>
<string>
action_type/workflow
</string>
<string>
before_script/portal_workflow/upgrade_slap_interface_workflow/script_UpgradeDecision_checkConsistency
</string>
<string>
after_script/portal_workflow/upgrade_slap_interface_workflow/script_UpgradeDecision_requestUpgrade
</string>
</tuple>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<string>
Perform the Upgrade
</string>
</value>
</item>
<item>
<key>
<string>
guard_permission
</string>
</key>
<value>
<tuple/>
</value>
</item>
<item>
<key>
<string>
icon
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
transition_request_upgrade
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Workflow Transition
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string>
Request Upgrade
</string>
</value>
</item>
<item>
<key>
<string>
trigger_type
</string>
</key>
<value>
<int>
2
</int>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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