Commit 052beafc by Czémán Arnold

vm: add refresh credential node operation, extend migrate operation

storage, dashboard: automatic credential refresh if ceph specific attributes changes,
minimal refactor on DataStore
parent 5c743bdc
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
"endpoints": [], "endpoints": [],
"type": "file", "type": "file",
"ceph_user": null, "ceph_user": null,
"secret_uuid": null, "secret": null,
"path": "/disks", "path": "/disks",
"hostname": "wut", "hostname": "wut",
"name": "diszkek" "name": "diszkek"
......
...@@ -1661,7 +1661,7 @@ class CephDataStoreForm(DataStoreForm): ...@@ -1661,7 +1661,7 @@ class CephDataStoreForm(DataStoreForm):
Fieldset( Fieldset(
'', '',
'ceph_user', 'ceph_user',
'secret_uuid', 'secret',
) )
) )
return helper return helper
...@@ -1669,7 +1669,7 @@ class CephDataStoreForm(DataStoreForm): ...@@ -1669,7 +1669,7 @@ class CephDataStoreForm(DataStoreForm):
class Meta: class Meta:
model = DataStore model = DataStore
fields = ("type", "name", "path", "hostname", fields = ("type", "name", "path", "hostname",
"ceph_user", "secret_uuid", "endpoints") "ceph_user", "secret", "endpoints")
widgets = {"endpoints": FilteredSelectMultiple(_("Endpoints"), widgets = {"endpoints": FilteredSelectMultiple(_("Endpoints"),
is_stacked=True)} is_stacked=True)}
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<fieldset> <fieldset>
<legend>{% trans "Ceph block storage authentication settings" %}</legend> <legend>{% trans "Ceph block storage authentication settings" %}</legend>
{{ form.ceph_user|as_crispy_field }} {{ form.ceph_user|as_crispy_field }}
{{ form.secret_uuid|as_crispy_field }} {{ form.secret|as_crispy_field }}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "Select or add new Ceph monitor endpoints(s)" %}</legend> <legend>{% trans "Select or add new Ceph monitor endpoints(s)" %}</legend>
......
...@@ -47,6 +47,8 @@ from .util import FilterMixin ...@@ -47,6 +47,8 @@ from .util import FilterMixin
import json import json
from celery.exceptions import TimeoutError from celery.exceptions import TimeoutError
from vm.models import Node
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -282,6 +284,23 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView): ...@@ -282,6 +284,23 @@ class StorageDetail(SuperuserRequiredMixin, UpdateView):
ds = self.get_object() ds = self.get_object()
return reverse("dashboard.views.storage-detail", kwargs={"pk": ds.id}) return reverse("dashboard.views.storage-detail", kwargs={"pk": ds.id})
def form_valid(self, form):
# automatic credential refresh
changed = False
if self.object.type == "ceph_block":
changed = (self.object.tracker.has_changed("secret")
or self.object.tracker.has_changed("ceph_user"))
response = super(StorageDetail, self).form_valid(form)
if changed:
nodes = Node.objects.all()
for node in nodes:
if node.get_online():
node.refresh_credential(
user=self.request.user,
username=self.object.ceph_user,
secret=self.object.secret)
return response
class StorageDelete(SuperuserRequiredMixin, DeleteView): class StorageDelete(SuperuserRequiredMixin, DeleteView):
model = DataStore model = DataStore
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('storage', '0007_datastore_destroyed'),
]
operations = [
migrations.RemoveField(
model_name='datastore',
name='secret_uuid',
),
migrations.AddField(
model_name='datastore',
name='secret',
field=models.CharField(max_length=255, null=True, verbose_name='secret key', blank=True),
),
]
...@@ -33,6 +33,7 @@ from django.core.urlresolvers import reverse ...@@ -33,6 +33,7 @@ from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from model_utils import FieldTracker
from sizefield.models import FileSizeField from sizefield.models import FileSizeField
from .tasks import local_tasks, storage_tasks from .tasks import local_tasks, storage_tasks
...@@ -88,10 +89,12 @@ class DataStore(Model): ...@@ -88,10 +89,12 @@ class DataStore(Model):
verbose_name=_('endpoints')) verbose_name=_('endpoints'))
ceph_user = CharField(max_length=255, null=True, blank=True, ceph_user = CharField(max_length=255, null=True, blank=True,
verbose_name=_('Ceph username')) verbose_name=_('Ceph username'))
secret_uuid = CharField(max_length=255, null=True, blank=True, secret = CharField(max_length=255, null=True, blank=True,
verbose_name=_('uuid of secret key')) verbose_name=_('secret key'))
destroyed = DateTimeField(blank=True, default=None, null=True) destroyed = DateTimeField(blank=True, default=None, null=True)
tracker = FieldTracker(fields=["ceph_user", "secret"])
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
verbose_name = _('datastore') verbose_name = _('datastore')
...@@ -480,7 +483,7 @@ class Disk(TimeStampedModel): ...@@ -480,7 +483,7 @@ class Disk(TimeStampedModel):
desc = self.get_vmdisk_desc_for_filesystem() desc = self.get_vmdisk_desc_for_filesystem()
desc["endpoints"] = self.datastore.get_endpoints() desc["endpoints"] = self.datastore.get_endpoints()
desc["ceph_user"] = self.datastore.ceph_user desc["ceph_user"] = self.datastore.ceph_user
desc["secret_uuid"] = self.datastore.secret_uuid desc["secret"] = self.datastore.secret
return desc return desc
......
...@@ -333,7 +333,6 @@ class Node(OperatedMixin, TimeStampedModel): ...@@ -333,7 +333,6 @@ class Node(OperatedMixin, TimeStampedModel):
try: try:
logger.info('%s %s', settings.GRAPHITE_URL, params) logger.info('%s %s', settings.GRAPHITE_URL, params)
response = requests.get(settings.GRAPHITE_URL, params=params) response = requests.get(settings.GRAPHITE_URL, params=params)
retval = {} retval = {}
for target in response.json(): for target in response.json():
# Example: # Example:
...@@ -364,12 +363,12 @@ class Node(OperatedMixin, TimeStampedModel): ...@@ -364,12 +363,12 @@ class Node(OperatedMixin, TimeStampedModel):
@property @property
@node_available @node_available
def cpu_usage(self): def cpu_usage(self):
return self.monitor_info.get('cpu.percent') / 100 return self.monitor_info.get('cpu.percent', 0) / 100
@property @property
@node_available @node_available
def ram_usage(self): def ram_usage(self):
return self.monitor_info.get('memory.usage') / 100 return self.monitor_info.get('memory.usage', 0) / 100
@property @property
@node_available @node_available
......
...@@ -562,7 +562,7 @@ class MigrateOperation(RemoteInstanceOperation): ...@@ -562,7 +562,7 @@ class MigrateOperation(RemoteInstanceOperation):
"redeploy network (rollback)")): "redeploy network (rollback)")):
self.instance.deploy_net() self.instance.deploy_net()
def _operation(self, activity, to_node=None, live_migration=True): def _operation(self, activity, user, to_node=None, live_migration=True):
if not to_node: if not to_node:
with activity.sub_activity('scheduling', with activity.sub_activity('scheduling',
readable_name=ugettext_noop( readable_name=ugettext_noop(
...@@ -572,6 +572,17 @@ class MigrateOperation(RemoteInstanceOperation): ...@@ -572,6 +572,17 @@ class MigrateOperation(RemoteInstanceOperation):
try: try:
with activity.sub_activity( with activity.sub_activity(
'refresh_credential', readable_name=create_readable(
ugettext_noop("refresh credential on %(node)s"),
node=to_node)):
ceph_blocks = self.instance.disks.filter(
datastore__type="ceph_block")
if ceph_blocks.exists():
ds = ceph_blocks[0].datastore
to_node.refresh_credential(user=user,
username=ds.ceph_user,
secret=ds.secret)
with activity.sub_activity(
'migrate_vm', readable_name=create_readable( 'migrate_vm', readable_name=create_readable(
ugettext_noop("migrate to %(node)s"), node=to_node)): ugettext_noop("migrate to %(node)s"), node=to_node)):
super(MigrateOperation, self)._operation( super(MigrateOperation, self)._operation(
...@@ -1143,6 +1154,14 @@ class NodeOperation(Operation): ...@@ -1143,6 +1154,14 @@ class NodeOperation(Operation):
user=user, readable_name=name) user=user, readable_name=name)
class RemoteNodeOperation(RemoteOperationMixin, NodeOperation):
remote_queue = ('vm', 'fast')
def _get_remote_queue(self):
return self.node.get_remote_queue_name(*self.remote_queue)
@register_operation @register_operation
class ResetNodeOperation(NodeOperation): class ResetNodeOperation(NodeOperation):
id = 'reset' id = 'reset'
...@@ -1333,6 +1352,22 @@ class UpdateNodeOperation(NodeOperation): ...@@ -1333,6 +1352,22 @@ class UpdateNodeOperation(NodeOperation):
@register_operation @register_operation
class RefreshCredentialOperation(RemoteNodeOperation):
id = 'refresh_credential'
name = _("refresh credential")
description = _("Refresh credential.")
required_perms = ()
task = vm_tasks.refresh_credential
def _get_remote_args(self, **kwargs):
return [kwargs["username"], kwargs["secret"]]
def _operation(self, activity, username, secret):
super(RefreshCredentialOperation, self)._operation(
username=username, secret=secret)
@register_operation
class ScreenshotOperation(RemoteInstanceOperation): class ScreenshotOperation(RemoteInstanceOperation):
id = 'screenshot' id = 'screenshot'
name = _("screenshot") name = _("screenshot")
......
...@@ -185,3 +185,8 @@ def get_node_metrics(params): ...@@ -185,3 +185,8 @@ def get_node_metrics(params):
@celery.task(name='vmdriver.screenshot') @celery.task(name='vmdriver.screenshot')
def screenshot(params): def screenshot(params):
pass pass
@celery.task(name='vmdriver.refresh_secret')
def refresh_credential(user, secret):
pass
...@@ -36,6 +36,15 @@ from ..operations import ( ...@@ -36,6 +36,15 @@ from ..operations import (
) )
class DiskQuerySet(list):
def filter(self, *args, **kwargs):
return DiskQuerySet()
def exists(self):
return False
class PortFinderTestCase(TestCase): class PortFinderTestCase(TestCase):
def test_find_unused_port_without_used_ports(self): def test_find_unused_port_without_used_ports(self):
...@@ -106,6 +115,7 @@ class InstanceTestCase(TestCase): ...@@ -106,6 +115,7 @@ class InstanceTestCase(TestCase):
inst = Mock(destroyed_at=None, spec=Instance) inst = Mock(destroyed_at=None, spec=Instance)
inst.interface_set.all.return_value = [] inst.interface_set.all.return_value = []
inst.node = MagicMock(spec=Node) inst.node = MagicMock(spec=Node)
inst.disks = DiskQuerySet()
inst.status = 'RUNNING' inst.status = 'RUNNING'
migrate_op = MigrateOperation(inst) migrate_op = MigrateOperation(inst)
with patch('vm.operations.vm_tasks.migrate') as migr, \ with patch('vm.operations.vm_tasks.migrate') as migr, \
...@@ -124,6 +134,7 @@ class InstanceTestCase(TestCase): ...@@ -124,6 +134,7 @@ class InstanceTestCase(TestCase):
inst.interface_set.all.return_value = [] inst.interface_set.all.return_value = []
inst.node = MagicMock(spec=Node) inst.node = MagicMock(spec=Node)
inst.status = 'RUNNING' inst.status = 'RUNNING'
inst.disks = DiskQuerySet()
migrate_op = MigrateOperation(inst) migrate_op = MigrateOperation(inst)
with patch('vm.operations.vm_tasks.migrate') as migr, \ with patch('vm.operations.vm_tasks.migrate') as migr, \
patch.object(RemoteOperationMixin, "_operation"): patch.object(RemoteOperationMixin, "_operation"):
......
...@@ -25,6 +25,7 @@ from vm.operations import ( ...@@ -25,6 +25,7 @@ from vm.operations import (
RebootOperation, ResetOperation, SaveAsTemplateOperation, RebootOperation, ResetOperation, SaveAsTemplateOperation,
ShutdownOperation, ShutOffOperation, SleepOperation, WakeUpOperation, ShutdownOperation, ShutOffOperation, SleepOperation, WakeUpOperation,
) )
from test_models import DiskQuerySet
class DeployOperationTestCase(TestCase): class DeployOperationTestCase(TestCase):
...@@ -55,9 +56,10 @@ class MigrateOperationTestCase(TestCase): ...@@ -55,9 +56,10 @@ class MigrateOperationTestCase(TestCase):
op = MigrateOperation(inst) op = MigrateOperation(inst)
op._get_remote_args = MagicMock(side_effect=MigrateException()) op._get_remote_args = MagicMock(side_effect=MigrateException())
inst.select_node = MagicMock(return_value='test') inst.select_node = MagicMock(return_value='test')
inst.disks = DiskQuerySet()
self.assertRaises( self.assertRaises(
MigrateException, op._operation, MigrateException, op._operation,
act, to_node=None) act, user=None, to_node=None)
assert inst.select_node.called assert inst.select_node.called
op._get_remote_args.assert_called_once_with( op._get_remote_args.assert_called_once_with(
to_node='test', live_migration=True) to_node='test', live_migration=True)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment