node.py 6.09 KB
Newer Older
1
from __future__ import absolute_import, unicode_literals
Őry Máté committed
2 3 4
from logging import getLogger

from django.db.models import (
5
    CharField, IntegerField, ForeignKey, BooleanField, ManyToManyField,
6
    FloatField, permalink,
Őry Máté committed
7 8 9 10 11 12 13
)
from django.utils.translation import ugettext_lazy as _

from celery.exceptions import TimeoutError
from model_utils.models import TimeStampedModel
from taggit.managers import TaggableManager

14
from common.models import method_cache
Őry Máté committed
15 16
from firewall.models import Host
from ..tasks import vm_tasks
17
from .common import Trait
Őry Máté committed
18

Gregory Nagy committed
19 20
from monitor.calvin.calvin import Query
from monitor.calvin.calvin import GraphiteHandler
21

Őry Máté committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
logger = getLogger(__name__)


class Node(TimeStampedModel):

    """A VM host machine, a hypervisor.
    """
    name = CharField(max_length=50, unique=True,
                     verbose_name=_('name'),
                     help_text=_('Human readable name of node.'))
    priority = IntegerField(verbose_name=_('priority'),
                            help_text=_('Node usage priority.'))
    host = ForeignKey(Host, verbose_name=_('host'),
                      help_text=_('Host in firewall.'))
    enabled = BooleanField(verbose_name=_('enabled'), default=False,
                           help_text=_('Indicates whether the node can '
                                       'be used for hosting.'))
    traits = ManyToManyField(Trait, blank=True,
                             help_text=_("Declared traits."),
                             verbose_name=_('traits'))
    tags = TaggableManager(blank=True, verbose_name=_("tags"))
    overcommit = FloatField(default=1.0, verbose_name=_("overcommit ratio"),
                            help_text=_("The ratio of total memory with "
                                        "to without overcommit."))

    class Meta:
        app_label = 'vm'
        db_table = 'vm_node'
        permissions = ()

Őry Máté committed
52 53 54
    def __unicode__(self):
        return self.name

Őry Máté committed
55 56 57 58
    @property
    @method_cache(10, 5)
    def online(self):

Őry Máté committed
59
        return self.remote_query(vm_tasks.ping, timeout=1, default=False)
Őry Máté committed
60 61 62 63

    @property
    @method_cache(300)
    def num_cores(self):
Őry Máté committed
64 65
        """Number of CPU threads available to the virtual machines.
        """
66

Őry Máté committed
67
        return self.remote_query(vm_tasks.get_core_num)
68

69 70 71 72 73
    @property
    def state(self):
        """Node state.
        """

74
        if self.enabled and self.online:
75 76 77 78 79 80 81
            return 'online'
        elif self.enabled and not self.online:
            return 'missing'
        elif not self.enabled and self.online:
            return 'disabled'
        else:
            return 'offline'
Őry Máté committed
82 83 84 85

    @property
    @method_cache(300)
    def ram_size(self):
Őry Máté committed
86 87
        """Bytes of total memory in the node.
        """
Őry Máté committed
88 89 90 91 92 93 94 95 96 97 98 99

        return self.remote_query(vm_tasks.get_ram_size)

    @property
    def ram_size_with_overcommit(self):
        """Bytes of total memory including overcommit margin.
        """
        return self.ram_size * self.overcommit

    def get_remote_queue_name(self, queue_id):
        return self.host.hostname + "." + queue_id

100
    def remote_query(self, task, timeout=30, raise_=False, default=None):
Őry Máté committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114
        """Query the given task, and get the result.

        If the result is not ready in timeout secs, return default value or
        raise a TimeoutError."""
        r = task.apply_async(
            queue=self.get_remote_queue_name('vm'), expires=timeout + 60)
        try:
            return r.get(timeout=timeout)
        except TimeoutError:
            if raise_:
                raise
            else:
                return default

115
    def get_monitor_info(self):
116 117 118 119 120 121 122
        collected = {}
        try:
            handler = GraphiteHandler()
        except:
            collected["cpu.usage"] = None
            collected["memory.usage"] = self.ram_size
            return collected
123
        query = Query()
Gregory Nagy committed
124 125 126
        query.set_target(self.host.hostname + ".circle")
        query.set_format("json")
        query.set_relative_start(5, "minutes")
127
        metrics = ["cpu.usage", "memory.usage"]
128 129
        collected = {}
        for metric in metrics:
Gregory Nagy committed
130
            query.set_metric(metric)
131 132 133 134
            query.generate()
            handler.put(query)
            handler.send()
        for metric in metrics:
Gregory Nagy committed
135
            response = handler.pop()
136 137 138 139 140 141 142 143 144 145 146 147 148
            length = len(response[0]["datapoints"])
            cache = response[0]["datapoints"][length - 1][0]
            if cache is None:
                cache = 0
            collected[metric] = cache
        return collected

    def cpu_usage(self):
        return self.get_monitor_info()["cpu.usage"]

    def ram_usage(self):
        return self.get_monitor_info()["memory.usage"]

Őry Máté committed
149 150 151 152 153 154 155 156 157 158 159
    def update_vm_states(self):
        domains = {}
        for i in self.remote_query(vm_tasks.list_domains_info, timeout=5):
            # [{'name': 'cloud-1234', 'state': 'RUNNING', ...}, ...]
            try:
                id = int(i['name'].split('-')[1])
            except:
                pass  # name format doesn't match
            else:
                domains[id] = i['state']

160 161
        instances = [{'id': i.id, 'state': i.state}
                     for i in self.instance_set.order_by('id').all()]
Őry Máté committed
162 163 164 165 166 167 168 169 170 171 172
        for i in instances:
            try:
                d = domains[i['id']]
            except KeyError:
                logger.info('Node %s update: instance %s missing from '
                            'libvirt', self, i['id'])
            else:
                if d != i['state']:
                    logger.info('Node %s update: instance %s state changed '
                                '(libvirt: %s, db: %s)',
                                self, i['id'], d, i['state'])
173
                    self.instance_set.get(id=i['id']).vm_state_changed(d)
Őry Máté committed
174 175 176 177 178

                del domains[i['id']]
        for i in domains.keys():
            logger.info('Node %s update: domain %s in libvirt but not in db.',
                        self, i)
179 180 181

    @classmethod
    def get_state_count(cls, online, enabled):
182 183
        return len([1 for i in cls.objects.filter(enabled=enabled).all()
                    if i.online == online])
184 185 186 187

    @permalink
    def get_absolute_url(self):
        return ('dashboard.views.node-detail', None, {'pk': self.id})