Statistics
| Branch: | Tag: | Revision:

root / ganeti / hooks.py @ 9cb903f9

History | View | Annotate | Download (5.7 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
from synnefo.util.mac2eui64 import mac2eui64
22
import synnefo.settings as settings
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
                # IPv6 support:
53
                #
54
                # The IPv6 of NIC with index 0 [the public NIC]
55
                # is derived using an EUI64 scheme.
56
                if index == 0 and key == 'mac':
57
                    nics[0]['ipv6'] = mac2eui64(nics[0]['mac'],
58
                                                settings.PUBLIC_IPV6_PREFIX)
59

    
60
    # Verify our findings are consistent with the Ganeti environment
61
    indexes = list(nics.keys())
62
    ganeti_nic_count = int(environ['GANETI_INSTANCE_NIC_COUNT'])
63
    if len(indexes) != ganeti_nic_count:
64
        logger.error("I have %d NICs, Ganeti says number of NICs is %d",
65
            len(indexes), ganeti_nic_count)
66
        raise Exception("Inconsistent number of NICs in Ganeti environment")
67

    
68
    if indexes != range(0, len(indexes)):
69
        logger.error("Ganeti NIC indexes are not consecutive starting at zero.");
70
        logger.error("NIC indexes are: %s. Environment is: %s", indexes, environ)
71
        raise Exception("Unexpected inconsistency in the Ganeti environment")
72

    
73
    # Construct the notification
74
    instance = environ['GANETI_INSTANCE_NAME']
75

    
76
    nics_list = []
77
    for i in indexes:
78
        nics_list.append(nics[i])
79

    
80
    msg = {
81
        "type": "ganeti-net-status",
82
        "instance": instance,
83
        "nics": nics_list
84
    }
85

    
86
    return msg
87

    
88

    
89
class GanetiHook():
90
    def __init__(self, logger, environ, instance, prefix):
91
        self.logger = logger
92
        self.environ = environ
93
        self.instance = instance
94
        self.prefix = prefix
95

    
96
    def on_master(self):
97
        """Return True if running on the Ganeti master"""
98
        return socket.getfqdn() == self.environ['GANETI_MASTER']
99

    
100
    def publish_msgs(self, msgs):
101
        for (msgtype, msg) in msgs:
102
            routekey = "ganeti.%s.event.%s" % (self.prefix, msgtype)
103
            self.logger.debug("Pushing message to RabbitMQ: %s (key = %s)",
104
                json.dumps(msg), routekey)
105
            msg = amqp.Message(json.dumps(msg))
106
            msg.properties["delivery_mode"] = 2  # Persistent
107

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

    
131
                    chann.basic_publish(msg,
132
                        exchange=settings.EXCHANGE_GANETI,
133
                        routing_key=routekey)
134
                    sent = True
135
                    self.logger.debug("Successfully sent message to RabbitMQ")
136
                except socket.error:
137
                    conn = False
138
                    retry += 1
139
                    self.logger.exception("Publish to RabbitMQ failed, retry=%d in 1s",
140
                        retry)
141
                    time.sleep(1)
142

    
143
            if not sent:
144
                raise Exception("Publish to RabbitMQ failed after %d tries, aborting" % retry)
145

    
146

    
147
class PostStartHook(GanetiHook):
148
    """Post-instance-startup Ganeti Hook.
149

150
    Produce notifications to the rest of the Synnefo
151
    infrastructure in the post-instance-start phase of Ganeti.
152

153
    Currently, this list only contains a single message,
154
    detailing the net configuration of an instance.
155

156
    This hook only runs on the Ganeti master.
157

158
    """
159
    def run(self):
160
        if self.on_master():
161
            notifs = []
162
            notifs.append(("net", ganeti_net_status(self.logger, self.environ)))
163
            
164
            self.publish_msgs(notifs)
165

    
166
        return 0
167

    
168

    
169
class PostStopHook(GanetiHook):
170
    def run(self):
171
        return 0
172