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
8868d845
authored
Nov 12, 2012
by
cloud
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
random
parent
0bec92dc
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
90 additions
and
47 deletions
+90
-47
cloud/settings.py
+1
-0
cloud/urls.py
+1
-0
one/admin.py
+14
-1
one/models.py
+55
-28
one/templates/home.html
+10
-9
one/templates/show.html
+1
-1
one/util.py
+6
-5
one/views.py
+1
-2
school/models.py
+1
-1
No files found.
cloud/settings.py
View file @
8868d845
...
@@ -124,6 +124,7 @@ INSTALLED_APPS = (
...
@@ -124,6 +124,7 @@ INSTALLED_APPS = (
'school'
,
'school'
,
'cloud'
,
'cloud'
,
'south'
,
'south'
,
'django_bfm'
,
)
)
# A sample logging configuration. The only tangible logging
# A sample logging configuration. The only tangible logging
...
...
cloud/urls.py
View file @
8868d845
...
@@ -14,4 +14,5 @@ urlpatterns = patterns('',
...
@@ -14,4 +14,5 @@ urlpatterns = patterns('',
url
(
r'^vm/new/(?P<template>\d+)/$'
,
'one.views.vm_new'
,
name
=
'vm_new'
),
url
(
r'^vm/new/(?P<template>\d+)/$'
,
'one.views.vm_new'
,
name
=
'vm_new'
),
url
(
r'^vm/show/(?P<iid>\d+)/$'
,
'one.views.vm_show'
,
name
=
'vm_show'
),
url
(
r'^vm/show/(?P<iid>\d+)/$'
,
'one.views.vm_show'
,
name
=
'vm_show'
),
url
(
r'^vm/delete/(?P<iid>\d+)/$'
,
'one.views.vm_delete'
,
name
=
'vm_delete'
),
url
(
r'^vm/delete/(?P<iid>\d+)/$'
,
'one.views.vm_delete'
,
name
=
'vm_delete'
),
url
(
r'^files/'
,
include
(
'django_bfm.urls'
))
)
)
one/admin.py
View file @
8868d845
...
@@ -18,11 +18,16 @@ class DetailsInline(contrib.admin.StackedInline):
...
@@ -18,11 +18,16 @@ class DetailsInline(contrib.admin.StackedInline):
can_delete
=
False
can_delete
=
False
class
MyUserAdmin
(
contrib
.
auth
.
admin
.
UserAdmin
):
class
MyUserAdmin
(
contrib
.
auth
.
admin
.
UserAdmin
):
list_display
=
(
'username'
,
'
email'
,
'is_staff'
,
'date_joined'
,
'get_profile
'
)
list_display
=
(
'username'
,
'
full_name'
,
'email'
,
'date_joined'
,
'instance_count
'
)
try
:
try
:
inlines
=
inlines
+
(
PersonInline
,
SshKeyInline
,
DetailsInline
)
inlines
=
inlines
+
(
PersonInline
,
SshKeyInline
,
DetailsInline
)
except
NameError
:
except
NameError
:
inlines
=
(
PersonInline
,
SshKeyInline
,
DetailsInline
)
inlines
=
(
PersonInline
,
SshKeyInline
,
DetailsInline
)
def
instance_count
(
self
,
obj
):
return
obj
.
instance_set
.
count
()
def
full_name
(
self
,
obj
):
return
u"
%
s
%
s"
%
(
obj
.
last_name
,
obj
.
first_name
)
full_name
.
admin_order_field
=
'last_name'
...
@@ -49,6 +54,14 @@ class InstanceAdmin(contrib.admin.ModelAdmin):
...
@@ -49,6 +54,14 @@ class InstanceAdmin(contrib.admin.ModelAdmin):
list_display
=
[
'id'
,
'name'
,
'owner'
,
'state'
]
list_display
=
[
'id'
,
'name'
,
'owner'
,
'state'
]
readonly_fields
=
[
'ip'
,
'active_since'
,
'pw'
,
'template'
]
readonly_fields
=
[
'ip'
,
'active_since'
,
'pw'
,
'template'
]
list_filter
=
[
'owner'
,
'template'
,
'state'
]
list_filter
=
[
'owner'
,
'template'
,
'state'
]
class
DiskAdmin
(
contrib
.
admin
.
ModelAdmin
):
model
=
models
.
Disk
class
NetworkAdmin
(
contrib
.
admin
.
ModelAdmin
):
model
=
models
.
Network
contrib
.
admin
.
site
.
register
(
models
.
Template
,
TemplateAdmin
)
contrib
.
admin
.
site
.
register
(
models
.
Template
,
TemplateAdmin
)
contrib
.
admin
.
site
.
register
(
models
.
Instance
,
InstanceAdmin
)
contrib
.
admin
.
site
.
register
(
models
.
Instance
,
InstanceAdmin
)
contrib
.
admin
.
site
.
register
(
models
.
Network
,
NetworkAdmin
)
contrib
.
admin
.
site
.
register
(
models
.
Disk
,
DiskAdmin
)
one/models.py
View file @
8868d845
# coding=utf-8
# coding=utf-8
from
django.db
import
models
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.core.exceptions
import
ValidationError
from
django.core
import
signing
from
django.core
import
signing
from
django.db
import
models
from
django.db
import
transaction
from
django.db
import
transaction
from
school.models
import
Person
from
django.db.models.signals
import
post_save
from
django.core.exceptions
import
ValidationError
from
django
import
forms
import
subprocess
,
tempfile
,
os
,
stat
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
one.util
import
keygen
from
one.util
import
keygen
from
django.db.models.signals
import
post_save
from
school.models
import
Person
import
subprocess
,
tempfile
,
os
,
stat
pwgen
=
User
.
objects
.
make_random_password
pwgen
=
User
.
objects
.
make_random_password
...
@@ -23,7 +25,7 @@ class UserCloudDetails(models.Model):
...
@@ -23,7 +25,7 @@ class UserCloudDetails(models.Model):
user
=
models
.
ForeignKey
(
User
,
null
=
False
,
blank
=
False
,
unique
=
True
)
user
=
models
.
ForeignKey
(
User
,
null
=
False
,
blank
=
False
,
unique
=
True
)
smb_password
=
models
.
CharField
(
max_length
=
20
)
smb_password
=
models
.
CharField
(
max_length
=
20
)
ssh_key
=
models
.
ForeignKey
(
'SshKey'
,
null
=
True
)
ssh_key
=
models
.
ForeignKey
(
'SshKey'
,
null
=
True
)
ssh_private_key
=
models
.
CharField
(
max_length
=
1024
)
ssh_private_key
=
models
.
TextField
(
)
def
reset_keys
(
self
):
def
reset_keys
(
self
):
...
@@ -43,8 +45,8 @@ class UserCloudDetails(models.Model):
...
@@ -43,8 +45,8 @@ class UserCloudDetails(models.Model):
super
(
UserCloudDetails
,
self
)
.
clean
()
super
(
UserCloudDetails
,
self
)
.
clean
()
if
not
self
.
ssh_key
:
if
not
self
.
ssh_key
:
self
.
reset_keys
()
self
.
reset_keys
()
if
not
self
.
smb_password
or
len
(
self
.
smb_password
)
==
0
:
if
not
self
.
smb_password
or
len
(
self
.
smb_password
)
==
0
:
self
.
reset_smb
()
self
.
reset_smb
()
class
OpenSshKeyValidator
(
object
):
class
OpenSshKeyValidator
(
object
):
valid_types
=
[
'ssh-rsa'
,
'ssh-dsa'
]
valid_types
=
[
'ssh-rsa'
,
'ssh-dsa'
]
...
@@ -76,6 +78,13 @@ class SshKey(models.Model):
...
@@ -76,6 +78,13 @@ class SshKey(models.Model):
help_text
=
_
(
'<a href="/info/ssh/">SSH public key in OpenSSH format</a> used for shell login '
help_text
=
_
(
'<a href="/info/ssh/">SSH public key in OpenSSH format</a> used for shell login '
'(2048+ bit RSA preferred). Example: <code>ssh-rsa AAAAB...QtQ== '
'(2048+ bit RSA preferred). Example: <code>ssh-rsa AAAAB...QtQ== '
'john</code>.'
),
validators
=
[
OpenSshKeyValidator
()])
'john</code>.'
),
validators
=
[
OpenSshKeyValidator
()])
def
__unicode__
(
self
):
try
:
keycomment
=
self
.
key
.
split
(
None
,
2
)[
2
]
except
:
keycomment
=
_
(
"unnamed"
)
return
u"
%
s (
%
s)"
%
(
keycomment
,
self
.
user
)
class
Disk
(
models
.
Model
):
class
Disk
(
models
.
Model
):
...
@@ -90,10 +99,18 @@ class Disk(models.Model):
...
@@ -90,10 +99,18 @@ class Disk(models.Model):
from
xml.dom.minidom
import
parse
,
parseString
from
xml.dom.minidom
import
parse
,
parseString
x
=
parseString
(
out
)
x
=
parseString
(
out
)
with
transaction
.
commit_on_success
():
with
transaction
.
commit_on_success
():
Disk
.
objects
.
all
()
.
delete
()
l
=
[]
for
d
in
x
.
getElementsByTagName
(
"STORAGE"
):
for
d
in
x
.
getElementsByTagName
(
"STORAGE"
):
Disk
(
id
=
int
(
d
.
getAttributeNode
(
'href'
)
.
nodeValue
.
split
(
'/'
)[
-
1
]),
id
=
int
(
d
.
getAttributeNode
(
'href'
)
.
nodeValue
.
split
(
'/'
)[
-
1
])
name
=
d
.
getAttributeNode
(
'name'
)
.
nodeValue
)
.
save
()
name
=
d
.
getAttributeNode
(
'name'
)
.
nodeValue
try
:
d
=
Disk
.
objects
.
get
(
id
=
id
)
d
.
name
=
name
d
.
save
()
except
:
Disk
(
id
=
id
,
name
=
name
)
.
save
l
.
append
(
id
)
Disk
.
objects
.
exclude
(
id__in
=
l
)
.
delete
()
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
u"
%
s (#
%
d)"
%
(
self
.
name
,
self
.
id
)
return
u"
%
s (#
%
d)"
%
(
self
.
name
,
self
.
id
)
...
@@ -116,10 +133,19 @@ class Network(models.Model):
...
@@ -116,10 +133,19 @@ class Network(models.Model):
from
xml.dom.minidom
import
parse
,
parseString
from
xml.dom.minidom
import
parse
,
parseString
x
=
parseString
(
out
)
x
=
parseString
(
out
)
with
transaction
.
commit_on_success
():
with
transaction
.
commit_on_success
():
cls
.
objects
.
all
()
.
delete
()
l
=
[]
for
d
in
x
.
getElementsByTagName
(
"NETWORK"
):
for
d
in
x
.
getElementsByTagName
(
"NETWORK"
):
Network
(
id
=
int
(
d
.
getAttributeNode
(
'href'
)
.
nodeValue
.
split
(
'/'
)[
-
1
]),
id
=
int
(
d
.
getAttributeNode
(
'href'
)
.
nodeValue
.
split
(
'/'
)[
-
1
])
name
=
d
.
getAttributeNode
(
'name'
)
.
nodeValue
)
.
save
()
name
=
d
.
getAttributeNode
(
'name'
)
.
nodeValue
try
:
n
=
Network
.
objects
.
get
(
id
=
id
)
n
.
name
=
name
n
.
save
()
except
:
Network
(
id
=
id
,
name
=
name
)
.
save
()
l
.
append
(
id
)
cls
.
objects
.
exclude
(
id__in
=
l
)
.
delete
()
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
u"
%
s (vlan
%03
d)"
%
(
self
.
name
,
self
.
id
)
return
u"
%
s (vlan
%03
d)"
%
(
self
.
name
,
self
.
id
)
class
Meta
:
class
Meta
:
...
@@ -157,15 +183,15 @@ class Instance(models.Model):
...
@@ -157,15 +183,15 @@ class Instance(models.Model):
one_id
=
models
.
IntegerField
(
unique
=
True
,
blank
=
True
,
null
=
True
)
one_id
=
models
.
IntegerField
(
unique
=
True
,
blank
=
True
,
null
=
True
)
def
get_port
(
self
):
def
get_port
(
self
):
proto
=
self
.
template
.
access_type
proto
=
self
.
template
.
access_type
if
self
.
template
.
network
.
name
==
'bmenet'
:
if
self
.
template
.
network
.
nat
:
return
{
"rdp"
:
3389
,
"nx"
:
22
,
"ssh"
:
22
}[
proto
]
if
self
.
template
.
network
.
name
==
'vmnet'
:
return
{
"rdp"
:
23000
,
"nx"
:
22000
,
"ssh"
:
22000
}[
proto
]
+
int
(
self
.
ip
.
split
(
'.'
)[
3
])
return
{
"rdp"
:
23000
,
"nx"
:
22000
,
"ssh"
:
22000
}[
proto
]
+
int
(
self
.
ip
.
split
(
'.'
)[
3
])
else
:
return
{
"rdp"
:
3389
,
"nx"
:
22
,
"ssh"
:
22
}[
proto
]
def
get_connect_host
(
self
):
def
get_connect_host
(
self
):
if
self
.
template
.
network
.
name
==
'bmenet'
:
if
self
.
template
.
network
.
nat
:
return
self
.
ip
elif
self
.
template
.
network
.
name
==
'vmnet'
:
return
'cloud'
return
'cloud'
else
:
return
self
.
ip
def
get_connect_uri
(
self
):
def
get_connect_uri
(
self
):
try
:
try
:
proto
=
self
.
template
.
access_type
proto
=
self
.
template
.
access_type
...
@@ -234,7 +260,7 @@ class Instance(models.Model):
...
@@ -234,7 +260,7 @@ class Instance(models.Model):
tpl
=
u"""
tpl
=
u"""
<COMPUTE>
<COMPUTE>
<NAME>
%(n
eptun)
s
%(n
ame)
s</NAME>
<NAME>
%(name)
s</NAME>
<INSTANCE_TYPE href="http://www.opennebula.org/instance_type/
%(instance)
s"/>
<INSTANCE_TYPE href="http://www.opennebula.org/instance_type/
%(instance)
s"/>
<DISK>
<DISK>
<STORAGE href="http://www.opennebula.org/storage/
%(disk)
d"/>
<STORAGE href="http://www.opennebula.org/storage/
%(disk)
d"/>
...
@@ -244,10 +270,12 @@ class Instance(models.Model):
...
@@ -244,10 +270,12 @@ class Instance(models.Model):
</NIC>
</NIC>
<CONTEXT>
<CONTEXT>
<HOSTNAME>cloud-$VMID</HOSTNAME>
<HOSTNAME>cloud-$VMID</HOSTNAME>
<
USERNAME>
%(neptun)
s</USERNAME
>
<
NEPTUN>
%(neptun)
s</NEPTUN
>
<USERPW>
%(pw)
s</USERPW>
<USERPW>
%(pw)
s</USERPW>
<SMBPW>
%(smbpw)
s</SMBPW>
<SMBPW>
%(smbpw)
s</SMBPW>
<SSHPRIV>
%(sshkey)
s</SSHPRIV>
<SSHPRIV>
%(sshkey)
s</SSHPRIV>
<BOOTURL>
%(booturl)
s</BOOTURL>
<SERVER>152.66.243.73</SERVER>
</CONTEXT>
</CONTEXT>
</COMPUTE>"""
%
{
"name"
:
u"
%
s
%
d"
%
(
owner
.
username
,
inst
.
id
),
</COMPUTE>"""
%
{
"name"
:
u"
%
s
%
d"
%
(
owner
.
username
,
inst
.
id
),
"instance"
:
template
.
instance_type
,
"instance"
:
template
.
instance_type
,
...
@@ -255,10 +283,10 @@ class Instance(models.Model):
...
@@ -255,10 +283,10 @@ class Instance(models.Model):
"net"
:
template
.
network
.
id
,
"net"
:
template
.
network
.
id
,
"pw"
:
escape
(
inst
.
pw
),
"pw"
:
escape
(
inst
.
pw
),
"smbpw"
:
escape
(
details
.
smb_password
),
"smbpw"
:
escape
(
details
.
smb_password
),
"sshkey"
:
escape
(
details
.
ssh_private_key
),
"sshkey"
:
escape
(
details
.
ssh_private_key
),
"neptun"
:
escape
(
owner
.
username
),
"neptun"
:
escape
(
owner
.
username
),
"booturl"
:
"http://cloud.ik.bme.hu/b/
%
s/"
%
token
,
"booturl"
:
"http://cloud.ik.bme.hu/b/
%
s/"
%
token
,
}
}
f
.
write
(
tpl
)
f
.
write
(
tpl
)
f
.
close
()
f
.
close
()
import
subprocess
import
subprocess
...
@@ -280,9 +308,8 @@ class Instance(models.Model):
...
@@ -280,9 +308,8 @@ class Instance(models.Model):
return
inst
return
inst
def
delete
(
self
):
def
delete
(
self
):
get_object_or_404
(
Instance
,
id
=
id
,
owner
=
request
.
user
.
get_profile
())
proc
=
subprocess
.
Popen
([
"/var/lib/opennebula/bin/occi.sh"
,
"compute"
,
proc
=
subprocess
.
Popen
([
"/var/lib/opennebula/bin/occi.sh"
,
"compute"
,
"delete"
,
id
],
stdout
=
subprocess
.
PIPE
)
"delete"
,
"
%
d"
%
self
.
one_
id
],
stdout
=
subprocess
.
PIPE
)
(
out
,
err
)
=
proc
.
communicate
()
(
out
,
err
)
=
proc
.
communicate
()
class
Meta
:
class
Meta
:
...
...
one/templates/home.html
View file @
8868d845
...
@@ -26,16 +26,17 @@
...
@@ -26,16 +26,17 @@
{% endfor %}
{% endfor %}
</div>
</div>
<div
class=
"boxes"
>
<div
class=
"boxes"
>
{% for box in boxes %}
<div
class=
"contentblock"
>
{% if forloop.counter|divisibleby:2 %}
<h2>
Adattár
</h2>
<div
class=
"contentblock"
>
<div
class=
"content"
>
<h2>
{{ box.title }}
</h2>
<ul>
<div
class=
"content"
>
<li>
a.out
<span
class=
"file-size"
>
4K
</span>
<span
class=
"file-age"
>
(5 perce)
</span>
<a
href=
""
class=
"file-download"
>
Letöltés
</a></li>
{{ box.text|safe }}
<li>
a.out
<span
class=
"file-size"
>
4K
</span>
<span
class=
"file-age"
>
(5 perce)
</span>
<a
href=
""
class=
"file-download"
>
Letöltés
</a></li>
</div>
<li
class=
"file-details"
>
Tovább
</li>
<li
class=
"file-upload"
>
Fájl feltöltése
</li>
</ul>
</div>
</div>
{% endif %}
</div>
{% endfor %}
<div
class=
"contentblock"
id=
"state"
>
<div
class=
"contentblock"
id=
"state"
>
<h2>
A cluster állapota
</h2>
<h2>
A cluster állapota
</h2>
<div
class=
"content"
>
<div
class=
"content"
>
...
...
one/templates/show.html
View file @
8868d845
...
@@ -89,7 +89,7 @@
...
@@ -89,7 +89,7 @@
<tr><th>
Port:
</th><td>
{{ i.get_port}}
<tr><th>
Port:
</th><td>
{{ i.get_port}}
</td></tr>
</td></tr>
<tr><th>
Felhasználónév:
</th><td>
cloud
</td></tr>
<tr><th>
Felhasználónév:
</th><td>
cloud
</td></tr>
<tr><th>
Jelszó:
</th><td>
ezmiez
</td></tr>
<tr><th>
Jelszó:
</th><td>
{{ i.pw }}
</td></tr>
</table>
</table>
</div>
</div>
</div>
</div>
...
...
one/util.py
View file @
8868d845
def
keygen
(
len
=
1024
):
def
keygen
(
length
=
1024
):
import
os
import
os
,
base64
from
datetime
import
date
from
Crypto.PublicKey
import
RSA
from
Crypto.PublicKey
import
RSA
key
=
RSA
.
generate
(
len
,
os
.
urandom
)
key
=
RSA
.
generate
(
len
gth
,
os
.
urandom
)
try
:
try
:
pub
=
key
.
exportKey
(
'OpenSSH'
)
pub
=
key
.
exportKey
(
'OpenSSH'
)
if
not
pub
.
startswith
(
"ssh-"
):
if
not
pub
.
startswith
(
"ssh-"
):
raise
ValueError
(
pub
)
raise
ValueError
(
pub
)
except
ValueError
:
except
:
ssh_rsa
=
'00000007'
+
base64
.
b16encode
(
'ssh-rsa'
)
ssh_rsa
=
'00000007'
+
base64
.
b16encode
(
'ssh-rsa'
)
exponent
=
'
%
x'
%
(
key
.
e
,
)
exponent
=
'
%
x'
%
(
key
.
e
,
)
if
len
(
exponent
)
%
2
:
if
len
(
exponent
)
%
2
:
...
@@ -28,6 +29,6 @@ def keygen(len=1024):
...
@@ -28,6 +29,6 @@ def keygen(len=1024):
pub
=
'ssh-rsa
%
s'
%
(
pub
=
'ssh-rsa
%
s'
%
(
base64
.
b64encode
(
base64
.
b16decode
(
ssh_rsa
.
upper
())),
)
base64
.
b64encode
(
base64
.
b16decode
(
ssh_rsa
.
upper
())),
)
return
key
.
exportKey
(),
pub
return
key
.
exportKey
(),
"
%
s
%
s"
%
(
pub
,
"cloud-
%
s"
%
date
.
today
())
one/views.py
View file @
8868d845
...
@@ -110,9 +110,8 @@ def vm_show(request, iid):
...
@@ -110,9 +110,8 @@ def vm_show(request, iid):
class
VmDeleteView
(
View
):
class
VmDeleteView
(
View
):
def
post
(
self
,
request
,
iid
,
*
args
,
**
kwargs
):
def
post
(
self
,
request
,
iid
,
*
args
,
**
kwargs
):
try
:
try
:
get_object_or_404
(
Instance
,
id
=
i
d
,
owner
=
request
.
user
)
.
one_
delete
()
get_object_or_404
(
Instance
,
id
=
i
id
,
owner
=
request
.
user
)
.
delete
()
messages
.
success
(
request
,
_
(
'Virtual machine is successfully deleted.'
))
messages
.
success
(
request
,
_
(
'Virtual machine is successfully deleted.'
))
except
:
except
:
messages
.
error
(
request
,
_
(
'Failed to delete virtual machine.'
))
messages
.
error
(
request
,
_
(
'Failed to delete virtual machine.'
))
...
...
school/models.py
View file @
8868d845
...
@@ -12,7 +12,7 @@ post_save.connect(create_user_profile, sender=User)
...
@@ -12,7 +12,7 @@ post_save.connect(create_user_profile, sender=User)
LANGS
=
[(
'hu'
,
_
(
'Hungarian'
)),
(
'en_US'
,
_
(
'US English'
))]
LANGS
=
[(
'hu'
,
_
(
'Hungarian'
)),
(
'en_US'
,
_
(
'US English'
))]
class
Person
(
models
.
Model
):
class
Person
(
models
.
Model
):
user
=
models
.
ForeignKey
(
User
,
null
=
False
,
blank
=
False
,
unique
=
True
)
user
=
models
.
ForeignKey
(
User
,
null
=
False
,
blank
=
False
,
unique
=
True
)
language
=
models
.
CharField
(
max_length
=
6
,
choices
=
LANGS
,
language
=
models
.
CharField
(
max_length
=
6
,
choices
=
LANGS
,
default
=
'hu'
,
verbose_name
=
_
(
'Preferred language'
))
verbose_name
=
_
(
'Preferred language'
))
def
__unicode__
(
self
):
def
__unicode__
(
self
):
...
...
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