Statistics
| Branch: | Tag: | Revision:

root / ganeti / hooks.py @ 7ca9e930

History | View | Annotate | Download (5.2 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".
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
    for env in environ.keys():
39
        if env.startswith("GANETI_INSTANCE_NIC"):
40
            s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
41
            if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP'):
42
                index = int(s[0])
43
                key = s[1].lower()
44

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

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

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

    
63
    # Construct the notification
64
    instance = environ['GANETI_INSTANCE_NAME']
65

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

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

    
76
    return msg
77

    
78

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

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

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

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

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

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

    
136

    
137
class PostStartHook(GanetiHook):
138
    """Post-instance-startup Ganeti Hook.
139

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

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

146
    This hook only runs on the Ganeti master.
147

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

    
156
        return 0
157

    
158

    
159
class PostStopHook(GanetiHook):
160
    def run(self):
161
        return 0
162