Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
fb4f60b6
authored
Apr 09, 2020
by
Máhonfai Bálint
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Get list of importable disks from store and show them on import form
parent
6b634da4
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
78 additions
and
76 deletions
+78
-76
circle/dashboard/forms.py
+51
-53
circle/dashboard/store_api.py
+15
-5
circle/dashboard/views/vm.py
+12
-18
No files found.
circle/dashboard/forms.py
View file @
fb4f60b6
...
...
@@ -17,56 +17,51 @@
from
__future__
import
absolute_import
from
datetime
import
timedelta
from
urlparse
import
urlparse
import
os
import
pyotp
from
django.forms
import
ModelForm
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
PasswordChangeForm
,
)
from
django.contrib.auth.models
import
User
,
Group
from
django.core.validators
import
URLValidator
from
django.core.exceptions
import
PermissionDenied
,
ValidationError
from
dal
import
autocomplete
from
crispy_forms.bootstrap
import
FormActions
from
crispy_forms.helper
import
FormHelper
from
crispy_forms.layout
import
(
Layout
,
Div
,
BaseInput
,
Field
,
HTML
,
Submit
,
TEMPLATE_PACK
,
Fieldset
)
from
crispy_forms.utils
import
render_field
from
crispy_forms.bootstrap
import
FormActions
from
dal
import
autocomplete
from
datetime
import
timedelta
from
django
import
forms
from
django.contrib.admin.widgets
import
FilteredSelectMultiple
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
PasswordChangeForm
,
)
from
django.contrib.auth.forms
import
UserCreationForm
as
OrgUserCreationForm
from
django.contrib.auth.models
import
Permission
from
django.contrib.auth.models
import
User
,
Group
from
django.core.exceptions
import
PermissionDenied
,
ValidationError
from
django.core.urlresolvers
import
reverse_lazy
from
django.core.validators
import
URLValidator
from
django.forms
import
ModelForm
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.template.loader
import
render_to_string
from
django.utils.html
import
escape
,
format_html
from
django.utils.safestring
import
mark_safe
from
django.utils.translation
import
string_concat
from
django.utils.translation
import
ugettext_lazy
as
_
from
django_sshkey.models
import
UserKey
from
sizefield.widgets
import
FileSizeWidget
from
django.core.urlresolvers
import
reverse_lazy
from
django_sshkey.models
import
UserKey
from
circle.settings.base
import
LANGUAGES
,
MAX_NODE_RAM
,
MAX_NODE_CPU_CORE
from
dashboard.models
import
ConnectCommand
,
create_profile
from
dashboard.store_api
import
Store
from
firewall.models
import
Vlan
,
Host
from
storage.models
import
DataStore
,
Disk
from
vm.models
import
(
InstanceTemplate
,
Lease
,
InterfaceTemplate
,
Node
,
Trait
,
Instance
)
from
storage.models
import
DataStore
,
Disk
from
django.contrib.admin.widgets
import
FilteredSelectMultiple
from
django.contrib.auth.models
import
Permission
from
.models
import
Profile
,
GroupProfile
,
Message
from
circle.settings.base
import
LANGUAGES
,
MAX_NODE_RAM
,
MAX_NODE_CPU_CORE
from
django.utils.translation
import
string_concat
from
.validators
import
domain_validator
from
dashboard.models
import
ConnectCommand
,
create_profile
LANGUAGES_WITH_CODE
=
((
l
[
0
],
string_concat
(
l
[
1
],
" ("
,
l
[
0
],
")"
))
for
l
in
LANGUAGES
)
...
...
@@ -189,7 +184,7 @@ class VmCustomizeForm(forms.Form):
self
.
initial
[
'ram_size'
]
=
self
.
template
.
ram_size
else
:
self
.
allowed_fields
=
(
"name"
,
"template"
,
"customized"
,
)
self
.
allowed_fields
=
(
"name"
,
"template"
,
"customized"
,)
# initial name and template pk
self
.
initial
[
'name'
]
=
self
.
template
.
name
...
...
@@ -214,7 +209,6 @@ class VmCustomizeForm(forms.Form):
class
GroupCreateForm
(
NoFormTagMixin
,
forms
.
ModelForm
):
description
=
forms
.
CharField
(
label
=
_
(
"Description"
),
required
=
False
,
widget
=
forms
.
Textarea
(
attrs
=
{
'rows'
:
3
}))
...
...
@@ -258,7 +252,7 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
class
Meta
:
model
=
Group
fields
=
(
'name'
,
)
fields
=
(
'name'
,)
class
GroupProfileUpdateForm
(
NoFormTagMixin
,
forms
.
ModelForm
):
...
...
@@ -513,7 +507,7 @@ class TemplateForm(forms.ModelForm):
self
.
allowed_fields
+=
tuple
(
set
(
self
.
fields
.
keys
())
-
set
([
'raw_data'
]))
if
self
.
user
.
is_superuser
:
self
.
allowed_fields
+=
(
'raw_data'
,
)
self
.
allowed_fields
+=
(
'raw_data'
,)
for
name
,
field
in
self
.
fields
.
items
():
if
name
not
in
self
.
allowed_fields
:
field
.
widget
.
attrs
[
'disabled'
]
=
'disabled'
...
...
@@ -525,8 +519,8 @@ class TemplateForm(forms.ModelForm):
self
.
initial
[
'max_ram_size'
]
=
512
lease_queryset
=
(
Lease
.
get_objects_with_level
(
"operator"
,
self
.
user
)
.
distinct
()
|
Lease
.
objects
.
filter
(
pk
=
self
.
instance
.
lease_id
)
.
distinct
())
Lease
.
get_objects_with_level
(
"operator"
,
self
.
user
)
.
distinct
()
|
Lease
.
objects
.
filter
(
pk
=
self
.
instance
.
lease_id
)
.
distinct
())
self
.
fields
[
"lease"
]
.
queryset
=
lease_queryset
...
...
@@ -602,7 +596,7 @@ class TemplateForm(forms.ModelForm):
class
Meta
:
model
=
InstanceTemplate
exclude
=
(
'state'
,
'disks'
,
)
exclude
=
(
'state'
,
'disks'
,)
widgets
=
{
'system'
:
forms
.
TextInput
,
'max_ram_size'
:
forms
.
HiddenInput
,
...
...
@@ -745,7 +739,6 @@ class LeaseForm(forms.ModelForm):
class
VmRenewForm
(
OperationForm
):
force
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Set expiration times even if they are shorter than "
"the current value."
))
...
...
@@ -785,11 +778,10 @@ class VmMigrateForm(forms.Form):
class
VmStateChangeForm
(
OperationForm
):
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Forcibly interrupt all running activities."
),
help_text
=
_
(
"Set all activities to finished state, "
"but don't interrupt any tasks."
))
help_text
=
_
(
"Set all activities to finished state, "
"but don't interrupt any tasks."
))
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
"New status"
))
reset_node
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Reset node"
))
...
...
@@ -932,8 +924,17 @@ class VmDiskRemoveForm(OperationForm):
class
VmImportDiskForm
(
OperationForm
):
name
=
forms
.
CharField
(
max_length
=
50
,
label
=
_
(
'Name'
))
disk_file
=
forms
.
FileField
(
label
=
_
(
'File'
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
'user'
)
super
(
VmImportDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
disk_paths
=
Store
(
self
.
user
)
.
get_disk_images
()
disk_filenames
=
[
os
.
path
.
basename
(
item
)
for
item
in
disk_paths
]
self
.
choices
=
zip
(
disk_paths
,
disk_filenames
)
self
.
fields
[
'name'
]
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
'Name'
))
self
.
fields
[
'disk_path'
]
=
forms
.
ChoiceField
(
label
=
_
(
'Disk image'
),
choices
=
self
.
choices
)
class
VmDownloadDiskForm
(
OperationForm
):
...
...
@@ -1175,7 +1176,6 @@ class CircleSetPasswordForm(SetPasswordForm):
class
LinkButton
(
BaseInput
):
"""
Used to create a link button descriptor for the {
%
crispy
%
} template tag::
...
...
@@ -1261,7 +1261,7 @@ class MyProfileForm(forms.ModelForm):
class
Meta
:
fields
=
(
'preferred_language'
,
'email_notifications'
,
'desktop_notifications'
,
'use_gravatar'
,
)
'desktop_notifications'
,
'use_gravatar'
,)
model
=
Profile
@property
...
...
@@ -1276,9 +1276,8 @@ class MyProfileForm(forms.ModelForm):
class
UnsubscribeForm
(
forms
.
ModelForm
):
class
Meta
:
fields
=
(
'email_notifications'
,
)
fields
=
(
'email_notifications'
,)
model
=
Profile
@property
...
...
@@ -1351,14 +1350,14 @@ class UserEditForm(forms.ModelForm):
class
Meta
:
model
=
User
fields
=
(
'email'
,
'first_name'
,
'last_name'
,
'instance_limit'
,
'is_active'
,
"two_factor_secret"
,
)
'is_active'
,
"two_factor_secret"
,)
def
save
(
self
,
commit
=
True
):
user
=
super
(
UserEditForm
,
self
)
.
save
()
user
.
profile
.
instance_limit
=
(
self
.
cleaned_data
[
'instance_limit'
]
or
None
)
self
.
cleaned_data
[
'instance_limit'
]
or
None
)
user
.
profile
.
two_factor_secret
=
(
self
.
cleaned_data
[
'two_factor_secret'
]
or
None
)
self
.
cleaned_data
[
'two_factor_secret'
]
or
None
)
user
.
profile
.
save
()
return
user
...
...
@@ -1442,10 +1441,9 @@ class ConnectCommandForm(forms.ModelForm):
class
TraitsForm
(
forms
.
ModelForm
):
class
Meta
:
model
=
Instance
fields
=
(
'req_traits'
,
)
fields
=
(
'req_traits'
,)
@property
def
helper
(
self
):
...
...
@@ -1465,7 +1463,7 @@ class RawDataForm(forms.ModelForm):
class
Meta
:
model
=
Instance
fields
=
(
'raw_data'
,
)
fields
=
(
'raw_data'
,)
@property
def
helper
(
self
):
...
...
@@ -1527,7 +1525,7 @@ class GroupPermissionForm(forms.ModelForm):
class
Meta
:
model
=
Group
fields
=
(
'permissions'
,
)
fields
=
(
'permissions'
,)
@property
def
helper
(
self
):
...
...
@@ -1575,7 +1573,7 @@ class VmResourcesForm(forms.ModelForm):
class
Meta
:
model
=
Instance
fields
=
(
'num_cores'
,
'priority'
,
'ram_size'
,
)
fields
=
(
'num_cores'
,
'priority'
,
'ram_size'
,)
class
VmRenameForm
(
forms
.
Form
):
...
...
@@ -1666,7 +1664,7 @@ class DataStoreForm(ModelForm):
class
Meta
:
model
=
DataStore
fields
=
(
"name"
,
"path"
,
"hostname"
,
)
fields
=
(
"name"
,
"path"
,
"hostname"
,)
class
DiskForm
(
ModelForm
):
...
...
@@ -1684,7 +1682,7 @@ class DiskForm(ModelForm):
class
Meta
:
model
=
Disk
fields
=
(
"name"
,
"filename"
,
"datastore"
,
"type"
,
"bus"
,
"size"
,
"base"
,
"dev_num"
,
"destroyed"
,
"is_ready"
,
)
"base"
,
"dev_num"
,
"destroyed"
,
"is_ready"
,)
class
MessageForm
(
ModelForm
):
...
...
circle/dashboard/store_api.py
View file @
fb4f60b6
...
...
@@ -14,19 +14,20 @@
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
os.path
import
splitext
import
json
import
logging
from
urlparse
import
urljoin
from
datetime
import
datetime
from
django.http
import
Http404
import
os
from
datetime
import
datetime
from
django.conf
import
settings
from
django.http
import
Http404
from
os.path
import
splitext
from
requests
import
get
,
post
,
codes
from
requests.exceptions
import
Timeout
# noqa
from
sizefield.utils
import
filesizeformat
from
storage.models
import
Disk
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -102,6 +103,15 @@ class Store(object):
else
:
return
result
def
get_disk_images
(
self
,
path
=
'/'
):
images
=
[]
file_list
=
self
.
list
(
path
,
process
=
False
)
export_formats
=
[
item
[
0
]
for
item
in
Disk
.
EXPORT_FORMATS
]
for
item
in
file_list
:
if
os
.
path
.
splitext
(
item
[
'NAME'
])[
1
]
.
strip
(
'.'
)
in
export_formats
:
images
.
append
(
os
.
path
.
join
(
path
,
item
[
'NAME'
]))
return
images
def
request_download
(
self
,
path
):
r
=
self
.
_request_cmd
(
"DOWNLOAD"
,
PATH
=
path
,
timeout
=
10
)
return
r
.
json
()[
'LINK'
]
...
...
circle/dashboard/views/vm.py
View file @
fb4f60b6
...
...
@@ -167,8 +167,8 @@ class VmDetailView(GraphMixin, CheckedDetailView):
# resources forms
can_edit
=
(
instance
.
has_level
(
user
,
"owner"
)
and
self
.
request
.
user
.
has_perm
(
"vm.change_resources"
))
instance
.
has_level
(
user
,
"owner"
)
and
self
.
request
.
user
.
has_perm
(
"vm.change_resources"
))
context
[
'resources_form'
]
=
VmResourcesForm
(
can_edit
=
can_edit
,
instance
=
instance
)
...
...
@@ -302,7 +302,6 @@ class VmRawDataUpdate(SuperuserRequiredMixin, UpdateView):
class
VmOperationView
(
AjaxOperationMixin
,
OperationView
):
model
=
Instance
context_object_name
=
'instance'
# much simpler to mock object
...
...
@@ -351,7 +350,6 @@ class VmRemoveInterfaceView(FormOperationMixin, VmOperationView):
class
VmAddInterfaceView
(
FormOperationMixin
,
VmOperationView
):
op
=
'add_interface'
form_class
=
VmAddInterfaceForm
show_in_toolbar
=
False
...
...
@@ -392,7 +390,6 @@ class VmDiskModifyView(FormOperationMixin, VmOperationView):
class
VmCreateDiskView
(
FormOperationMixin
,
VmOperationView
):
op
=
'create_disk'
form_class
=
VmCreateDiskForm
show_in_toolbar
=
False
...
...
@@ -410,7 +407,6 @@ class VmCreateDiskView(FormOperationMixin, VmOperationView):
class
VmImportDiskView
(
FormOperationMixin
,
VmOperationView
):
op
=
'import_disk'
form_class
=
VmImportDiskForm
show_in_toolbar
=
False
...
...
@@ -419,9 +415,13 @@ class VmImportDiskView(FormOperationMixin, VmOperationView):
is_disk_operation
=
True
with_reload
=
True
def
get_form_kwargs
(
self
):
val
=
super
(
VmImportDiskView
,
self
)
.
get_form_kwargs
()
val
.
update
({
'user'
:
self
.
request
.
user
})
return
val
class
VmDownloadDiskView
(
FormOperationMixin
,
VmOperationView
):
class
VmDownloadDiskView
(
FormOperationMixin
,
VmOperationView
):
op
=
'download_disk'
form_class
=
VmDownloadDiskForm
show_in_toolbar
=
False
...
...
@@ -432,7 +432,6 @@ class VmDownloadDiskView(FormOperationMixin, VmOperationView):
class
VmMigrateView
(
FormOperationMixin
,
VmOperationView
):
op
=
'migrate'
icon
=
'truck'
effect
=
'info'
...
...
@@ -462,7 +461,7 @@ class VmMigrateView(FormOperationMixin, VmOperationView):
nodes_w_traits
=
[
n
.
pk
for
n
in
Node
.
objects
.
filter
(
enabled
=
True
)
if
n
.
online
and
has_traits
(
inst
.
req_traits
.
all
(),
n
)
has_traits
(
inst
.
req_traits
.
all
(),
n
)
]
ctx
[
'nodes_w_traits'
]
=
nodes_w_traits
...
...
@@ -470,7 +469,6 @@ class VmMigrateView(FormOperationMixin, VmOperationView):
class
VmPortRemoveView
(
FormOperationMixin
,
VmOperationView
):
template_name
=
'dashboard/_vm-remove-port.html'
op
=
'remove_port'
show_in_toolbar
=
False
...
...
@@ -499,7 +497,6 @@ class VmPortRemoveView(FormOperationMixin, VmOperationView):
class
VmPortAddView
(
FormOperationMixin
,
VmOperationView
):
op
=
'add_port'
show_in_toolbar
=
False
with_reload
=
True
...
...
@@ -526,7 +523,6 @@ class VmPortAddView(FormOperationMixin, VmOperationView):
class
VmSaveView
(
FormOperationMixin
,
VmOperationView
):
op
=
'save_as_template'
icon
=
'save'
effect
=
'info'
...
...
@@ -582,7 +578,7 @@ class TokenOperationView(OperationView):
User can do the action with a valid token instead of logging in.
"""
token_max_age
=
3
*
24
*
3600
redirect_exception_classes
=
(
PermissionDenied
,
SuspiciousOperation
,
)
redirect_exception_classes
=
(
PermissionDenied
,
SuspiciousOperation
,)
@classmethod
def
get_salt
(
cls
):
...
...
@@ -654,7 +650,6 @@ class TokenOperationView(OperationView):
class
VmRenewView
(
FormOperationMixin
,
TokenOperationView
,
VmOperationView
):
op
=
'renew'
icon
=
'calendar'
effect
=
'success'
...
...
@@ -1030,7 +1025,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
# remove "-" that means descending order
# also check if the column name is valid
if
(
sort
and
(
sort
[
1
:]
if
sort
[
0
]
==
"-"
else
sort
)
(
sort
[
1
:]
if
sort
[
0
]
==
"-"
else
sort
)
in
[
i
.
name
for
i
in
Instance
.
_meta
.
fields
]
+
[
"pk"
]):
queryset
=
queryset
.
order_by
(
sort
)
...
...
@@ -1041,7 +1036,6 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
class
VmCreate
(
LoginRequiredMixin
,
TemplateView
):
form_class
=
VmCustomizeForm
form
=
None
...
...
@@ -1150,7 +1144,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
messages
.
success
(
request
,
ungettext_lazy
(
"Successfully created
%(count)
d VM."
,
# this should not happen
"Successfully created
%(count)
d VMs."
,
len
(
instances
))
%
{
'count'
:
len
(
instances
)})
'count'
:
len
(
instances
)})
path
=
"
%
s?stype=owned"
%
reverse
(
"dashboard.views.vm-list"
)
else
:
messages
.
success
(
request
,
_
(
"VM successfully created."
))
...
...
@@ -1342,7 +1336,7 @@ class TransferInstanceOwnershipConfirmView(TransferOwnershipConfirmView):
def
change_owner
(
self
,
instance
,
new_owner
):
with
instance
.
activity
(
code_suffix
=
'ownership-transferred'
,
code_suffix
=
'ownership-transferred'
,
readable_name
=
ugettext_noop
(
"transfer ownership"
),
concurrency_check
=
False
,
user
=
new_owner
):
super
(
TransferInstanceOwnershipConfirmView
,
self
)
.
change_owner
(
...
...
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