Statistics
| Branch: | Tag: | Revision:

root / ganeti / hooks.py @ f533f224

History | View | Annotate | Download (5.3 kB)

1
#!/usr/bin/env python
2
#
3
# Copyright (c) 2010 Greek Research and Technology Network
4
#
5
"""Ganeti hooks for Synnefo
6

7
These are the individual Ganeti hooks for Synnefo.
8

9
"""
10

    
11
import sys
12
import os
13

    
14
import time
15
import json
16
import socket
17
import logging
18

    
19
from amqplib import client_0_8 as amqp
20

    
21
import synnefo.settings as settings
22

    
23

    
24
def ganeti_net_status(logger, environ):
25
    """Produce notifications of type 'Ganeti-net-status'
26
    
27
    Process all GANETI_INSTANCE_NICx_y environment variables,
28
    where x is the NIC index, starting at 0,
29
    and y is one of "MAC", "IP", "BRIDGE".
30

31
    The result is returned as a single notification message
32
    of type 'ganeti-net-status', detailing the NIC configuration
33
    of a Ganeti instance.
34

35
    """
36
    nics = {}
37

    
38
    key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link' }
39

    
40
    for env in environ.keys():
41
        if env.startswith("GANETI_INSTANCE_NIC"):
42
            s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
43
            if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP', 'BRIDGE'):
44
                index = int(s[0])
45
                key = key_to_attr[s[1]]
46

    
47
                if nics.has_key(index):
48
                    nics[index][key] = environ[env]
49
                else:
50
                    nics[index] = { key: environ[env] }
51

    
52
    # Verify our findings are consistent with the Ganeti environment
53
    indexes = list(nics.keys())
54
    ganeti_nic_count = int(environ['GANETI_INSTANCE_NIC_COUNT'])
55
    if len(indexes) != ganeti_nic_count:
56
        logger.error("I have %d NICs, Ganeti says number of NICs is %d",
57
            len(indexes), ganeti_nic_count)
58
        raise Exception("Inconsistent number of NICs in Ganeti environment")
59

    
60
    if indexes != range(0, len(indexes)):
61
        logger.error("Ganeti NIC indexes are not consecutive starting at zero.");
62
        logger.error("NIC indexes are: %s. Environment is: %s", indexes, environ)
63
        raise Exception("Unexpected inconsistency in the Ganeti environment")
64

    
65
    # Construct the notification
66
    instance = environ['GANETI_INSTANCE_NAME']
67

    
68
    nics_list = []
69
    for i in indexes:
70
        nics_list.append(nics[i])
71

    
72
    msg = {
73
        "type": "ganeti-net-status",
74
        "instance": instance,
75
        "nics": nics_list
76
    }
77

    
78
    return msg
79

    
80

    
81
class GanetiHook():
82
    def __init__(self, logger, environ, instance, prefix):
83
        self.logger = logger
84
        self.environ = environ
85
        self.instance = instance
86
        self.prefix = prefix
87

    
88
    def on_master(self):
89
        """Return True if running on the Ganeti master"""
90
        return socket.getfqdn() == self.environ['GANETI_MASTER']
91

    
92
    def publish_msgs(self, msgs):
93
        for (msgtype, msg) in msgs:
94
            routekey = "ganeti.%s.event.%s" % (self.prefix, msgtype)
95
            self.logger.debug("Pushing message to RabbitMQ: %s (key = %s)",
96
                json.dumps(msg), routekey)
97
            msg = amqp.Message(json.dumps(msg))
98
            msg.properties["delivery_mode"] = 2  # Persistent
99

    
100
            # Retry up to five times to open a channel to RabbitMQ.
101
            # The hook needs to abort if this count is exceeded, because it
102
            # runs synchronously with VM creation inside Ganeti, and may only
103
            # run for a finite amount of time.
104
            #
105
            # FIXME: We need a reconciliation mechanism between the DB and
106
            #        Ganeti, for cases exactly like this.
107
            conn = None
108
            sent = False
109
            retry = 0
110
            while not sent and retry < 5:
111
                self.logger.debug("Attempting to publish to RabbitMQ at %s",
112
                    settings.RABBIT_HOST)
113
                try:
114
                    if not conn:
115
                        conn = amqp.Connection(host=settings.RABBIT_HOST,
116
                            userid=settings.RABBIT_USERNAME,
117
                            password=settings.RABBIT_PASSWORD,
118
                            virtual_host=settings.RABBIT_VHOST)
119
                        chann = conn.channel()
120
                        self.logger.debug("Successfully connected to RabbitMQ at %s",
121
                            settings.RABBIT_HOST)
122

    
123
                    chann.basic_publish(msg,
124
                        exchange=settings.EXCHANGE_GANETI,
125
                        routing_key=routekey)
126
                    sent = True
127
                    self.logger.debug("Successfully sent message to RabbitMQ")
128
                except socket.error:
129
                    conn = False
130
                    retry += 1
131
                    self.logger.exception("Publish to RabbitMQ failed, retry=%d in 1s",
132
                        retry)
133
                    time.sleep(1)
134

    
135
            if not sent:
136
                raise Exception("Publish to RabbitMQ failed after %d tries, aborting" % retry)
137

    
138

    
139
class PostStartHook(GanetiHook):
140
    """Post-instance-startup Ganeti Hook.
141

142
    Produce notifications to the rest of the Synnefo
143
    infrastructure in the post-instance-start phase of Ganeti.
144

145
    Currently, this list only contains a single message,
146
    detailing the net configuration of an instance.
147

148
    This hook only runs on the Ganeti master.
149

150
    """
151
    def run(self):
152
        if self.on_master():
153
            notifs = []
154
            notifs.append(("net", ganeti_net_status(self.logger, self.environ)))
155
            
156
            self.publish_msgs(notifs)
157

    
158
        return 0
159

    
160

    
161
class PostStopHook(GanetiHook):
162
    def run(self):
163
        return 0
164