Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Gutyán Gábor
/
circlestack
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
cab22b32
authored
Mar 19, 2014
by
Őry Máté
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature-flush-node'
Add Node.flush *
✅
model *
✅
tasks *
☑
view
parents
0964aa9c
c086ab65
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
255 additions
and
34 deletions
+255
-34
circle/dashboard/static/dashboard/dashboard.js
+15
-0
circle/dashboard/templates/dashboard/confirm/ajax-node-flush.html
+27
-0
circle/dashboard/templates/dashboard/confirm/node-flush.html
+33
-0
circle/dashboard/templates/dashboard/node-detail.html
+4
-9
circle/dashboard/templates/dashboard/node-list/column-actions.html
+6
-11
circle/dashboard/templates/dashboard/node-list/column-admin.html
+1
-1
circle/dashboard/urls.py
+8
-6
circle/dashboard/views.py
+36
-1
circle/vm/models/instance.py
+6
-1
circle/vm/models/node.py
+23
-3
circle/vm/tasks/local_tasks.py
+5
-0
circle/vm/tests/test_models.py
+91
-2
No files found.
circle/dashboard/static/dashboard/dashboard.js
View file @
cab22b32
...
...
@@ -137,6 +137,21 @@ $(function () {
return
false
;
});
/* for Node flush buttons */
$
(
'.node-flush'
).
click
(
function
()
{
var
node_pk
=
$
(
this
).
data
(
'node-pk'
);
var
postto
=
$
(
this
).
attr
(
'href'
);
var
dir
=
window
.
location
.
pathname
.
indexOf
(
'list'
)
==
-
1
;
addModalConfirmation
(
function
(){},
{
'url'
:
postto
,
'data'
:
[],
'pk'
:
node_pk
,
'type'
:
"node"
,
'redirect'
:
dir
});
return
false
;
});
/* for Group removes buttons */
$
(
'.group-delete'
).
click
(
function
()
{
var
group_pk
=
$
(
this
).
data
(
'group-pk'
);
...
...
circle/dashboard/templates/dashboard/confirm/ajax-node-flush.html
0 → 100644
View file @
cab22b32
{% load i18n %}
<div
class=
"modal fade"
id=
"confirmation-modal"
tabindex=
"-1"
role=
"dialog"
>
<div
class=
"modal-dialog"
>
<div
class=
"modal-content"
>
<div
class=
"modal-body"
>
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to flush
<strong>
{{ object }}
</strong>
?
{%endblocktrans%}
{% endif %}
<div
class=
"pull-right"
>
<form
action=
"{% url "
dashboard
.
views
.
flush-node
"
pk=
node.pk
%}?
next=
{{next}}"
method=
"POST"
>
{% csrf_token %}
<button
type=
"button"
class=
"btn btn-default"
data-dismiss=
"modal"
>
{% trans "Cancel" %}
</button>
<input
type=
"hidden"
name=
"flush"
value=
""
/>
<button
class=
"btn btn-warning"
>
{% trans "Yes" %}
</button>
</form>
</div>
<div
class=
"clearfix"
></div>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
circle/dashboard/templates/dashboard/confirm/node-flush.html
0 → 100644
View file @
cab22b32
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div
class=
"body-content"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<h3
class=
"no-margin"
>
{% if title %}
{{ title }}
{% else %}
Flush confirmation
{% endif %}
</h3>
</div>
<div
class=
"panel-body"
>
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to flush
<strong>
{{ object }}
</strong>
?
{%endblocktrans%}
{% endif %}
<div
class=
"pull-right"
>
<form
action=
""
method=
"POST"
>
{% csrf_token %}
<a
class=
"btn btn-default"
>
{% trans "Back" %}
</a>
<input
type=
"hidden"
name=
"flush"
value=
""
/>
<button
class=
"btn btn-warning"
>
{% trans "Yes" %}
</button>
</form>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/node-detail.html
View file @
cab22b32
...
...
@@ -29,15 +29,10 @@
<button
type=
"button"
class=
"btn {{ btn_size }} btn-warning nojs-dropdown-toogle dropdown-toggle"
data-toggle=
"dropdown"
>
Action
<i
class=
"icon-caret-down"
></i></button>
<ul
class=
"dropdown-menu nojs-dropdown-toogle"
role=
"menu"
>
<li><a
href=
"#"
class=
"node-details-rename-button"
><i
class=
"icon-pencil"
></i>
{% trans "Rename" %}
</a></li>
<li><a
href=
"#"
><i
class=
"icon-cloud-upload"
></i>
Flush
</a></li>
{% if node.enabled %}
<li><a
style=
"display:none"
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-check"
></i>
Enable
</a>
<a
style=
"display:block"
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-remove"
></i>
Disable
</a></li>
{% else %}
<li><a
style=
"display:block"
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
>
<i
class=
"icon-check"
></i>
Enable
</a>
<a
style=
"display:none"
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-remove"
></i>
Disable
</a></li>
{% endif %}
<li><a
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-flush"
href=
"{% url "
dashboard
.
views
.
flush-node
"
pk=
node.pk
%}"
><i
class=
"icon-cloud-upload"
></i>
{% trans "Flush" %}
</a>
<li>
<a
style=
"display:{% if node.enabled %}none{% else %}block{% endif %}"
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-check"
></i>
{% trans "Enable" %}
</a>
<a
style=
"display:{% if not node.enabled %}none{% else %}block{% endif %}"
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-remove"
></i>
{% trans "Disable" %}
</a></li>
<li><a
data-node-pk=
"{{ node.pk }}"
class=
"real-link node-delete"
href=
"{% url "
dashboard
.
views
.
delete-node
"
pk=
node.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-trash"
></i>
Delete
</a></li>
</ul>
</div>
...
...
circle/dashboard/templates/dashboard/node-list/column-actions.html
View file @
cab22b32
{% load i18n %}
<div
class=
"btn-group"
>
<button
type=
"button"
class=
"btn {{ btn_size }} btn-warning nojs-dropdown-toogle dropdown-toggle"
data-toggle=
"dropdown"
>
Action
<i
class=
"icon-caret-down"
></i></button>
<ul
class=
"dropdown-menu nojs-dropdown-toogle"
role=
"menu"
>
<li><a
href=
"#"
><i
class=
"icon-cloud-upload"
></i>
Flush
</a></li>
{% if record.enabled %}
<li><a
style=
"display:none"
data-status=
"enable"
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}&
status=
enable"
><i
class=
"icon-check"
></i>
Enable
</a>
<a
style=
"display:block"
data-status=
"disable"
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}&
status=
disable"
><i
class=
"icon-remove"
></i>
Disable
</a></li>
{% else %}
<li><a
style=
"display:block"
data-status=
"enable"
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}&
status=
enable"
>
<i
class=
"icon-check"
></i>
Enable
</a>
<a
style=
"display:none"
data-status=
"disable"
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}&
status=
disable"
><i
class=
"icon-remove"
></i>
Disable
</a></li>
{% endif %}
<li><a
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-delete"
href=
"{% url "
dashboard
.
views
.
delete-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-trash"
></i>
Delete
</a></li>
<li><a
href=
"#"
class=
"node-details-rename-button"
><i
class=
"icon-pencil"
></i>
{% trans "Rename" %}
</a></li>
<li><a
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-flush"
href=
"{% url "
dashboard
.
views
.
flush-node
"
pk=
record.pk
%}"
><i
class=
"icon-cloud-upload"
></i>
{% trans "Flush" %}
</a>
<li><a
style=
{%
if
record
.
enabled
%}"
display:none
"{%
else
%}"
display:block
"{%
endif
%}
data-status=
"enable"
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}&
status=
enable"
><i
class=
"icon-check"
></i>
{% trans "Enable" %}
</a>
<a
style=
{%
if
record
.
enabled
%}"
display:block
"{%
else
%}"
display:none
"{%
endif
%}
data-status=
"disable"
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-enable"
href=
"{% url "
dashboard
.
views
.
status-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}&
status=
disable"
><i
class=
"icon-remove"
></i>
{% trans "Disable" %}
</a></li>
<li><a
data-node-pk=
"{{ record.pk }}"
class=
"real-link node-delete"
href=
"{% url "
dashboard
.
views
.
delete-node
"
pk=
record.pk
%}?
next=
{{
request
.
path
}}"
><i
class=
"icon-trash"
></i>
{% trans "Delete" %}
</a></li>
</ul>
</div>
circle/dashboard/templates/dashboard/node-list/column-admin.html
View file @
cab22b32
...
...
@@ -4,4 +4,4 @@
<a
id=
"node-list-rename-button"
class=
"btn btn-default btn-xs"
title
data-original-title=
"Rename"
>
<i
class=
"icon-pencil"
></i>
</a>
circle/dashboard/urls.py
View file @
cab22b32
...
...
@@ -5,12 +5,12 @@ from .views import (
AclUpdateView
,
DiskAddView
,
FavouriteView
,
GroupAclUpdateView
,
GroupDelete
,
GroupDetailView
,
GroupList
,
GroupUserDelete
,
IndexView
,
LeaseCreate
,
LeaseDelete
,
LeaseDetail
,
MyPreferencesView
,
NodeAddTraitView
,
NodeCreate
,
NodeDelete
,
NodeDetailView
,
Node
GraphView
,
NodeList
,
NodeStatus
,
No
tificationView
,
PortDelete
,
TemplateAclUpdateView
,
TemplateCreate
,
Template
Delete
,
TemplateDetail
,
TemplateList
,
TransferOwnershipConfirmView
,
TransferOwnership
View
,
vm_activity
,
VmCreate
,
VmDelete
,
VmDetailView
,
VmDe
tailVncTokenView
,
VmGraphView
,
VmList
,
VmMassDelete
,
VmMigrateView
,
VmRenewView
,
NodeDelete
,
NodeDetailView
,
Node
FlushView
,
NodeGraphView
,
NodeList
,
No
deStatus
,
NotificationView
,
PortDelete
,
TemplateAclUpdateView
,
Template
Create
,
TemplateDelete
,
TemplateDetail
,
TemplateList
,
TransferOwnership
ConfirmView
,
TransferOwnershipView
,
vm_activity
,
VmCreate
,
VmDe
lete
,
VmDetailView
,
VmDetailVncTokenView
,
VmGraphView
,
VmList
,
Vm
MassDelete
,
VmMigrateView
,
Vm
RenewView
,
)
urlpatterns
=
patterns
(
...
...
@@ -68,6 +68,8 @@ urlpatterns = patterns(
name
=
"dashboard.views.delete-node"
),
url
(
r'^node/status/(?P<pk>\d+)/$'
,
NodeStatus
.
as_view
(),
name
=
"dashboard.views.status-node"
),
url
(
r'^node/flush/(?P<pk>\d+)/$'
,
NodeFlushView
.
as_view
(),
name
=
"dashboard.views.flush-node"
),
url
(
r'^node/create/$'
,
NodeCreate
.
as_view
(),
name
=
'dashboard.views.node-create'
),
...
...
circle/dashboard/views.py
View file @
cab22b32
...
...
@@ -505,7 +505,6 @@ class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
return
context
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
print
request
.
POST
if
request
.
POST
.
get
(
'new_name'
):
return
self
.
__set_name
(
request
)
if
request
.
POST
.
get
(
'change_status'
)
is
not
None
:
...
...
@@ -1401,6 +1400,42 @@ class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
return
redirect
(
self
.
get_success_url
())
class
NodeFlushView
(
LoginRequiredMixin
,
SuperuserRequiredMixin
,
DetailView
):
template_name
=
"dashboard/confirm/node-flush.html"
model
=
Node
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-node-flush.html'
]
else
:
return
[
'dashboard/confirm/node-flush.html'
]
def
get_success_url
(
self
):
next
=
self
.
request
.
GET
.
get
(
'next'
)
if
next
:
return
next
else
:
return
reverse_lazy
(
"dashboard.views.node-detail"
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
})
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
NodeFlushView
,
self
)
.
get_context_data
(
**
kwargs
)
return
context
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
if
request
.
POST
.
get
(
'flush'
)
is
not
None
:
return
self
.
__flush
(
request
)
return
redirect
(
reverse_lazy
(
"dashboard.views.node-detail"
,
kwargs
=
{
'pk'
:
self
.
get_object
()
.
pk
}))
def
__flush
(
self
,
request
):
self
.
object
=
self
.
get_object
()
self
.
object
.
flush_async
(
user
=
request
.
user
)
success_message
=
_
(
"Node successfully flushed!"
)
messages
.
success
(
request
,
success_message
)
return
redirect
(
self
.
get_success_url
())
class
PortDelete
(
LoginRequiredMixin
,
DeleteView
):
model
=
Rule
pk_url_kwarg
=
'rule'
...
...
circle/vm/models/instance.py
View file @
cab22b32
...
...
@@ -1096,10 +1096,15 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel,
return
local_tasks
.
migrate
.
apply_async
(
args
=
[
self
,
to_node
,
user
],
queue
=
"localhost.man"
)
def
migrate
(
self
,
to_node
,
user
=
None
,
task_uuid
=
None
,
timeout
=
120
):
def
migrate
(
self
,
to_node
=
None
,
user
=
None
,
task_uuid
=
None
,
timeout
=
120
):
"""Live migrate running vm to another node. """
with
instance_activity
(
code_suffix
=
'migrate'
,
instance
=
self
,
task_uuid
=
task_uuid
,
user
=
user
)
as
act
:
if
not
to_node
:
with
act
.
sub_activity
(
'scheduling'
)
as
sa
:
to_node
=
self
.
select_node
()
sa
.
result
=
to_node
# Destroy networks
with
act
.
sub_activity
(
'destroying_net'
):
for
net
in
self
.
interface_set
.
all
():
...
...
circle/vm/models/node.py
View file @
cab22b32
...
...
@@ -13,7 +13,7 @@ from taggit.managers import TaggableManager
from
common.models
import
method_cache
,
WorkerNotFound
from
firewall.models
import
Host
from
..tasks
import
vm_tasks
from
..tasks
import
vm_tasks
,
local_tasks
from
.common
import
Trait
from
.activity
import
node_activity
,
NodeActivity
...
...
@@ -106,13 +106,33 @@ class Node(TimeStampedModel):
def
get_status_display
(
self
):
return
self
.
STATES
[
self
.
enabled
][
self
.
online
][
1
]
def
disable
(
self
,
user
=
None
):
def
disable
(
self
,
user
=
None
,
base_activity
=
None
):
''' Disable the node.'''
if
self
.
enabled
:
with
node_activity
(
code_suffix
=
'disable'
,
node
=
self
,
user
=
user
):
if
base_activity
:
act_ctx
=
base_activity
.
sub_activity
(
'disable'
)
else
:
act_ctx
=
node_activity
(
'disable'
,
node
=
self
,
user
=
user
)
with
act_ctx
:
self
.
enabled
=
False
self
.
save
()
def
flush
(
self
,
user
=
None
,
task_uuid
=
None
):
"""Disable node and move all instances to other ones.
"""
with
node_activity
(
'flush'
,
node
=
self
,
user
=
user
,
task_uuid
=
task_uuid
)
as
act
:
self
.
disable
(
user
,
act
)
for
i
in
self
.
instance_set
.
all
():
with
act
.
sub_activity
(
'migrate_instance_
%
d'
%
i
.
pk
):
i
.
migrate
()
def
flush_async
(
self
,
user
=
None
):
"""Execute flush asynchronously.
"""
return
local_tasks
.
flush
.
apply_async
(
args
=
[
self
,
user
],
queue
=
"localhost.man"
)
def
enable
(
self
,
user
=
None
):
''' Enable the node. '''
if
self
.
enabled
is
not
True
:
...
...
circle/vm/tasks/local_tasks.py
View file @
cab22b32
...
...
@@ -57,3 +57,8 @@ def reboot(instance, user):
@celery.task
def
migrate
(
instance
,
to_node
,
user
):
instance
.
migrate
(
to_node
,
task_uuid
=
migrate
.
request
.
id
,
user
=
user
)
@celery.task
def
flush
(
node
,
user
):
node
.
flush
(
task_uuid
=
flush
.
request
.
id
,
user
=
user
)
circle/vm/tests/test_models.py
View file @
cab22b32
from
datetime
import
datetime
from
django.contrib.auth.models
import
User
from
django.test
import
TestCase
from
django.utils.translation
import
ugettext_lazy
as
_
from
mock
import
Mock
,
MagicMock
,
patch
from
mock
import
Mock
,
MagicMock
,
patch
,
call
from
..models
import
(
Lease
,
Node
,
Interface
,
Instance
,
InstanceTemplate
,
InstanceActivity
,
...
...
@@ -68,12 +70,38 @@ class InstanceTestCase(TestCase):
self
.
assertTrue
(
inst
.
destroyed_at
)
inst
.
save
.
assert_called
()
def
test_migrate_with_scheduling
(
self
):
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
node
=
MagicMock
(
spec
=
Node
)
with
patch
(
'vm.models.instance.instance_activity'
)
as
ia
,
\
patch
(
'vm.models.instance.vm_tasks.migrate'
)
as
migr
:
Instance
.
migrate
(
inst
)
migr
.
apply_async
.
assert_called
()
self
.
assertIn
(
call
()
.
__enter__
()
.
sub_activity
(
u'scheduling'
),
ia
.
mock_calls
)
inst
.
select_node
.
assert_called
()
def
test_migrate_wo_scheduling
(
self
):
inst
=
MagicMock
(
spec
=
Instance
)
inst
.
interface_set
.
all
.
return_value
=
[]
inst
.
node
=
MagicMock
(
spec
=
Node
)
with
patch
(
'vm.models.instance.instance_activity'
)
as
ia
,
\
patch
(
'vm.models.instance.vm_tasks.migrate'
)
as
migr
:
inst
.
select_node
.
side_effect
=
AssertionError
Instance
.
migrate
(
inst
,
inst
.
node
)
migr
.
apply_async
.
assert_called
()
self
.
assertNotIn
(
call
()
.
__enter__
()
.
sub_activity
(
u'scheduling'
),
ia
.
mock_calls
)
class
InterfaceTestCase
(
TestCase
):
def
test_interface_create
(
self
):
from
firewall.models
import
Vlan
,
Domain
from
django.contrib.auth.models
import
User
owner
=
User
()
owner
.
save
()
i
=
Instance
(
id
=
10
,
owner
=
owner
,
access_method
=
'rdp'
)
...
...
@@ -165,3 +193,64 @@ class InstanceActivityTestCase(TestCase):
patch
(
'vm.models.activity.timezone.now'
):
original_method
(
iaobj
,
"test"
,
concurrency_check
=
False
)
ia
.
save
.
assert_called
()
def
test_disable_enabled
(
self
):
node
=
MagicMock
(
spec
=
Node
,
enabled
=
True
)
with
patch
(
'vm.models.node.node_activity'
)
as
nac
:
na
=
MagicMock
()
nac
.
return_value
=
na
na
.
__enter__
.
return_value
=
MagicMock
()
Node
.
disable
(
node
)
self
.
assertFalse
(
node
.
enabled
)
node
.
save
.
assert_called_once
()
na
.
assert_called
()
def
test_disable_disabled
(
self
):
node
=
MagicMock
(
spec
=
Node
,
enabled
=
False
)
with
patch
(
'vm.models.node.node_activity'
)
as
nac
:
na
=
MagicMock
()
na
.
__enter__
.
side_effect
=
AssertionError
nac
.
return_value
=
na
Node
.
disable
(
node
)
self
.
assertFalse
(
node
.
enabled
)
def
test_disable_enabled_sub
(
self
):
node
=
MagicMock
(
spec
=
Node
,
enabled
=
True
)
act
=
MagicMock
()
subact
=
MagicMock
()
act
.
sub_activity
.
return_value
=
subact
Node
.
disable
(
node
,
base_activity
=
act
)
self
.
assertFalse
(
node
.
enabled
)
subact
.
__enter__
.
assert_called
()
def
test_flush
(
self
):
node
=
MagicMock
(
spec
=
Node
,
enabled
=
True
)
user
=
MagicMock
(
spec
=
User
)
insts
=
[
MagicMock
(
spec
=
Instance
),
MagicMock
(
spec
=
Instance
)]
with
patch
(
'vm.models.node.node_activity'
)
as
na
:
act
=
na
.
return_value
.
__enter__
.
return_value
=
MagicMock
()
node
.
instance_set
.
all
.
return_value
=
insts
Node
.
flush
(
node
,
user
)
na
.
__enter__
.
assert_called
()
node
.
disable
.
assert_called_with
(
user
,
act
)
for
i
in
insts
:
i
.
migrate
.
assert_called
()
def
test_flush_disabled_wo_user
(
self
):
node
=
MagicMock
(
spec
=
Node
,
enabled
=
False
)
insts
=
[
MagicMock
(
spec
=
Instance
),
MagicMock
(
spec
=
Instance
)]
with
patch
(
'vm.models.node.node_activity'
)
as
na
:
act
=
na
.
return_value
.
__enter__
.
return_value
=
MagicMock
()
node
.
instance_set
.
all
.
return_value
=
insts
Node
.
flush
(
node
)
node
.
disable
.
assert_called_with
(
None
,
act
)
# ^ should be called, but real method no-ops if disabled
na
.
__enter__
.
assert_called
()
for
i
in
insts
:
i
.
migrate
.
assert_called
()
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