Commit 32e83265 by Bach Dániel

Merge branch 'feature-node-salt'

parents c711f3c1 8a293708
...@@ -19,7 +19,8 @@ from __future__ import absolute_import ...@@ -19,7 +19,8 @@ from __future__ import absolute_import
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django_tables2 import Table, A from django_tables2 import Table, A
from django_tables2.columns import TemplateColumn, Column, LinkColumn from django_tables2.columns import (TemplateColumn, Column, LinkColumn,
BooleanColumn)
from vm.models import Node, InstanceTemplate, Lease from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -67,12 +68,18 @@ class NodeListTable(Table): ...@@ -67,12 +68,18 @@ class NodeListTable(Table):
orderable=False, orderable=False,
) )
minion_online = BooleanColumn(
verbose_name=_("Minion online"),
attrs={'th': {'class': 'node-list-table-thin'}},
orderable=False,
)
class Meta: class Meta:
model = Node model = Node
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')} 'node-list-table')}
fields = ('pk', 'name', 'host', 'get_status_display', 'priority', fields = ('pk', 'name', 'host', 'get_status_display', 'priority',
'overcommit', 'number_of_VMs', ) 'minion_online', 'overcommit', 'number_of_VMs', )
class GroupListTable(Table): class GroupListTable(Table):
......
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
<dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.info.ram_size 1048576 1 %} MiB</dd> <dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.info.ram_size 1048576 1 %} MiB</dd>
<dt>{% trans "Architecture" %}:</dt><dd>{{ node.info.architecture }}</dd> <dt>{% trans "Architecture" %}:</dt><dd>{{ node.info.architecture }}</dd>
<dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd> <dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd> <dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled|yesno }}</dd>
<dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd> <dt>{% trans "Host online" %}:</dt><dd> {{ node.online|yesno }}</dd>
<dt>{% trans "Minion online" %}:</dt><dd> {{ node.minion_online|yesno }}</dd>
<dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd> <dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd>
<dt>{% trans "Driver Version:" %}</dt> <dt>{% trans "Driver Version:" %}</dt>
<dd> <dd>
......
...@@ -68,6 +68,8 @@ node_ops = OrderedDict([ ...@@ -68,6 +68,8 @@ node_ops = OrderedDict([
op='passivate', icon='play-circle-o', effect='info')), op='passivate', icon='play-circle-o', effect='info')),
('disable', NodeOperationView.factory( ('disable', NodeOperationView.factory(
op='disable', icon='times-circle-o', effect='danger')), op='disable', icon='times-circle-o', effect='danger')),
('update_node', NodeOperationView.factory(
op='update_node', icon='refresh', effect='warning')),
('reset', NodeOperationView.factory( ('reset', NodeOperationView.factory(
op='reset', icon='stethoscope', effect='danger')), op='reset', icon='stethoscope', effect='danger')),
('flush', NodeOperationView.factory( ('flush', NodeOperationView.factory(
......
...@@ -17,9 +17,15 @@ ...@@ -17,9 +17,15 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from functools import update_wrapper from functools import update_wrapper
from glob import glob
from logging import getLogger from logging import getLogger
import os.path
from warnings import warn from warnings import warn
import requests import requests
from salt.client import LocalClient
from salt.exceptions import SaltClientError
import salt.utils
from time import time, sleep
from django.conf import settings from django.conf import settings
from django.db.models import ( from django.db.models import (
...@@ -44,6 +50,56 @@ from .common import Trait ...@@ -44,6 +50,56 @@ from .common import Trait
logger = getLogger(__name__) logger = getLogger(__name__)
class MyLocalClient(LocalClient):
def get_returns(self, jid, minions, timeout=None):
'''
Get the returns for the command line interface via the event system
'''
minions = set(minions)
if timeout is None:
timeout = self.opts['timeout']
jid_dir = salt.utils.jid_dir(jid,
self.opts['cachedir'],
self.opts['hash_type'])
start = time()
timeout_at = start + timeout
found = set()
ret = {}
wtag = os.path.join(jid_dir, 'wtag*')
# Check to see if the jid is real, if not return the empty dict
if not os.path.isdir(jid_dir):
logger.warning("jid_dir (%s) does not exist", jid_dir)
return ret
# Wait for the hosts to check in
while True:
time_left = timeout_at - time()
raw = self.event.get_event(time_left, jid)
if raw is not None and 'return' in raw:
found.add(raw['id'])
ret[raw['id']] = raw['return']
if len(found.intersection(minions)) >= len(minions):
# All minions have returned, break out of the loop
logger.debug("jid %s found all minions", jid)
break
continue
# Then event system timeout was reached and nothing was returned
if len(found.intersection(minions)) >= len(minions):
# All minions have returned, break out of the loop
logger.debug("jid %s found all minions", jid)
break
if glob(wtag) and time() <= timeout_at + 1:
# The timeout +1 has not been reached and there is still a
# write tag for the syndic
continue
if time() > timeout_at:
logger.info('jid %s minions %s did not return in time',
jid, (minions - found))
break
sleep(0.01)
return ret
def node_available(function): def node_available(function):
"""Decorate methods to ignore disabled Nodes. """Decorate methods to ignore disabled Nodes.
""" """
...@@ -111,6 +167,18 @@ class Node(OperatedMixin, TimeStampedModel): ...@@ -111,6 +167,18 @@ class Node(OperatedMixin, TimeStampedModel):
online = property(get_online) online = property(get_online)
@method_cache(20)
def get_minion_online(self):
name = self.host.hostname
try:
client = MyLocalClient()
client.opts['timeout'] = 0.2
return bool(client.cmd(name, 'test.ping')[name])
except (KeyError, SaltClientError):
return False
minion_online = property(get_minion_online)
@node_available @node_available
@method_cache(300) @method_cache(300)
def get_info(self): def get_info(self):
......
...@@ -26,6 +26,7 @@ from StringIO import StringIO ...@@ -26,6 +26,7 @@ from StringIO import StringIO
from tarfile import TarFile, TarInfo from tarfile import TarFile, TarInfo
import time import time
from urlparse import urlsplit from urlparse import urlsplit
from salt.client import LocalClient
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.utils import timezone from django.utils import timezone
...@@ -1192,6 +1193,70 @@ class DisableOperation(NodeOperation): ...@@ -1192,6 +1193,70 @@ class DisableOperation(NodeOperation):
@register_operation @register_operation
class UpdateNodeOperation(NodeOperation):
id = 'update_node'
name = _("update node")
description = _("Upgrade or install node software (vmdriver, agentdriver, "
"monitor-client) with Salt.")
required_perms = ()
online_required = False
async_queue = "localhost.man.slow"
def minion_cmd(self, module, params, timeout=3600):
name = self.node.host.hostname
client = LocalClient()
data = client.cmd(
name, module, params, timeout=timeout)
try:
data = data[name]
except KeyError:
raise HumanReadableException.create(ugettext_noop(
"No minions matched the target."))
if not isinstance(data, dict):
raise HumanReadableException.create(ugettext_noop(
"Unhandled exception: %(msg)s"), msg=unicode(data))
return data
def _operation(self, activity):
with activity.sub_activity(
'upgrade_packages',
readable_name=ugettext_noop('upgrade packages')) as sa:
data = self.minion_cmd('pkg.upgrade', [])
upgraded = len(filter(lambda x: x['old'] and x['new'],
data.values()))
installed = len(filter(lambda x: not x['old'] and x['new'],
data.values()))
removed = len(filter(lambda x: x['old'] and not x['new'],
data.values()))
sa.result = create_readable(ugettext_noop(
"Upgraded: %(upgraded)s, Installed: %(installed)s, "
"Removed: %(removed)s"), upgraded=upgraded,
installed=installed, removed=removed)
data = self.minion_cmd('state.sls', ['node', 'nfs-client'])
failed = 0
for k, v in data.iteritems():
logger.debug('salt state %s %s', k, v)
act_name = ': '.join(k.split('_|-')[:2])
if not v["result"] or v["changes"]:
act = activity.create_sub(
act_name[:70], readable_name=act_name)
act.result = create_readable(ugettext_noop(
"Changes: %(changes)s Comment: %(comment)s"),
changes=v["changes"], comment=v["comment"])
act.finish(v["result"])
if not v["result"]:
failed += 1
if failed:
raise HumanReadableException.create(ugettext_noop(
"Failed: %(failed)s"), failed=failed)
@register_operation
class ScreenshotOperation(RemoteInstanceOperation): class ScreenshotOperation(RemoteInstanceOperation):
id = 'screenshot' id = 'screenshot'
name = _("screenshot") name = _("screenshot")
......
...@@ -28,6 +28,7 @@ pylibmc==1.3.0 ...@@ -28,6 +28,7 @@ pylibmc==1.3.0
python-dateutil==2.2 python-dateutil==2.2
pytz==2014.2 pytz==2014.2
requests==2.2.1 requests==2.2.1
salt==2014.1.0
simplejson==3.4.0 simplejson==3.4.0
six==1.6.1 six==1.6.1
South==0.8.4 South==0.8.4
......
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