Statistics
| Branch: | Tag: | Revision:

root / ganeti / hooks.py @ c0f6fb49

History | View | Annotate | Download (6.3 kB)

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

7 ff55193e Vangelis Koukis
These are the individual Ganeti hooks for Synnefo.
8 ff55193e Vangelis Koukis

9 ff55193e Vangelis Koukis
"""
10 ff55193e Vangelis Koukis
11 ff55193e Vangelis Koukis
import sys
12 ff55193e Vangelis Koukis
import os
13 ff55193e Vangelis Koukis
14 ff55193e Vangelis Koukis
import time
15 ff55193e Vangelis Koukis
import json
16 ff55193e Vangelis Koukis
import socket
17 ff55193e Vangelis Koukis
import logging
18 ff55193e Vangelis Koukis
19 b9eef123 Vangelis Koukis
from amqplib import client_0_8 as amqp
20 ff55193e Vangelis Koukis
21 746c6bf4 Vangelis Koukis
from synnefo.util.mac2eui64 import mac2eui64
22 b9eef123 Vangelis Koukis
import synnefo.settings as settings
23 ff55193e Vangelis Koukis
24 ff55193e Vangelis Koukis
def ganeti_net_status(logger, environ):
25 ff55193e Vangelis Koukis
    """Produce notifications of type 'Ganeti-net-status'
26 ff55193e Vangelis Koukis
    
27 ff55193e Vangelis Koukis
    Process all GANETI_INSTANCE_NICx_y environment variables,
28 ff55193e Vangelis Koukis
    where x is the NIC index, starting at 0,
29 f533f224 Vangelis Koukis
    and y is one of "MAC", "IP", "BRIDGE".
30 ff55193e Vangelis Koukis

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

35 ff55193e Vangelis Koukis
    """
36 ff55193e Vangelis Koukis
    nics = {}
37 ff55193e Vangelis Koukis
38 f533f224 Vangelis Koukis
    key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link' }
39 f533f224 Vangelis Koukis
40 ff55193e Vangelis Koukis
    for env in environ.keys():
41 ff55193e Vangelis Koukis
        if env.startswith("GANETI_INSTANCE_NIC"):
42 ff55193e Vangelis Koukis
            s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
43 f533f224 Vangelis Koukis
            if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP', 'BRIDGE'):
44 ff55193e Vangelis Koukis
                index = int(s[0])
45 f533f224 Vangelis Koukis
                key = key_to_attr[s[1]]
46 ff55193e Vangelis Koukis
47 ff55193e Vangelis Koukis
                if nics.has_key(index):
48 ff55193e Vangelis Koukis
                    nics[index][key] = environ[env]
49 ff55193e Vangelis Koukis
                else:
50 ff55193e Vangelis Koukis
                    nics[index] = { key: environ[env] }
51 ff55193e Vangelis Koukis
52 746c6bf4 Vangelis Koukis
                # IPv6 support:
53 746c6bf4 Vangelis Koukis
                #
54 746c6bf4 Vangelis Koukis
                # The IPv6 of NIC with index 0 [the public NIC]
55 746c6bf4 Vangelis Koukis
                # is derived using an EUI64 scheme.
56 746c6bf4 Vangelis Koukis
                if index == 0 and key == 'mac':
57 746c6bf4 Vangelis Koukis
                    nics[0]['ipv6'] = mac2eui64(nics[0]['mac'],
58 746c6bf4 Vangelis Koukis
                                                settings.PUBLIC_IPV6_PREFIX)
59 746c6bf4 Vangelis Koukis
60 eab0602e Vangelis Koukis
    # Amend notification with firewall settings
61 eab0602e Vangelis Koukis
    tags = environ.get('GANETI_INSTANCE_TAGS', '')
62 eab0602e Vangelis Koukis
    for tag in tags.split(' '):
63 eab0602e Vangelis Koukis
        t = tag.split(':')
64 eab0602e Vangelis Koukis
        if t[0:2] == ['synnefo', 'network']:
65 eab0602e Vangelis Koukis
            if len(t) != 4:
66 eab0602e Vangelis Koukis
                logger.error("Malformed synnefo tag %s", tag)
67 eab0602e Vangelis Koukis
                continue
68 eab0602e Vangelis Koukis
            try:
69 eab0602e Vangelis Koukis
                index = int(t[2])
70 eab0602e Vangelis Koukis
                nics[index]['firewall'] = t[3]
71 eab0602e Vangelis Koukis
            except ValueError:
72 eab0602e Vangelis Koukis
                logger.error("Malformed synnefo tag %s", tag)
73 eab0602e Vangelis Koukis
            except KeyError:
74 eab0602e Vangelis Koukis
                logger.error("Found tag %s for non-existent NIC %d",
75 eab0602e Vangelis Koukis
                             tag, index)
76 eab0602e Vangelis Koukis
77 ff55193e Vangelis Koukis
    # Verify our findings are consistent with the Ganeti environment
78 ff55193e Vangelis Koukis
    indexes = list(nics.keys())
79 ff55193e Vangelis Koukis
    ganeti_nic_count = int(environ['GANETI_INSTANCE_NIC_COUNT'])
80 ff55193e Vangelis Koukis
    if len(indexes) != ganeti_nic_count:
81 ff55193e Vangelis Koukis
        logger.error("I have %d NICs, Ganeti says number of NICs is %d",
82 ff55193e Vangelis Koukis
            len(indexes), ganeti_nic_count)
83 ff55193e Vangelis Koukis
        raise Exception("Inconsistent number of NICs in Ganeti environment")
84 ff55193e Vangelis Koukis
85 ff55193e Vangelis Koukis
    if indexes != range(0, len(indexes)):
86 ff55193e Vangelis Koukis
        logger.error("Ganeti NIC indexes are not consecutive starting at zero.");
87 ff55193e Vangelis Koukis
        logger.error("NIC indexes are: %s. Environment is: %s", indexes, environ)
88 ff55193e Vangelis Koukis
        raise Exception("Unexpected inconsistency in the Ganeti environment")
89 ff55193e Vangelis Koukis
90 ff55193e Vangelis Koukis
    # Construct the notification
91 ff55193e Vangelis Koukis
    instance = environ['GANETI_INSTANCE_NAME']
92 ff55193e Vangelis Koukis
93 ff55193e Vangelis Koukis
    nics_list = []
94 ff55193e Vangelis Koukis
    for i in indexes:
95 ff55193e Vangelis Koukis
        nics_list.append(nics[i])
96 ff55193e Vangelis Koukis
97 ff55193e Vangelis Koukis
    msg = {
98 ff55193e Vangelis Koukis
        "type": "ganeti-net-status",
99 ff55193e Vangelis Koukis
        "instance": instance,
100 ff55193e Vangelis Koukis
        "nics": nics_list
101 ff55193e Vangelis Koukis
    }
102 ff55193e Vangelis Koukis
103 ff55193e Vangelis Koukis
    return msg
104 ff55193e Vangelis Koukis
105 b9eef123 Vangelis Koukis
106 b9eef123 Vangelis Koukis
class GanetiHook():
107 b9eef123 Vangelis Koukis
    def __init__(self, logger, environ, instance, prefix):
108 b9eef123 Vangelis Koukis
        self.logger = logger
109 b9eef123 Vangelis Koukis
        self.environ = environ
110 b9eef123 Vangelis Koukis
        self.instance = instance
111 b9eef123 Vangelis Koukis
        self.prefix = prefix
112 b9eef123 Vangelis Koukis
113 b9eef123 Vangelis Koukis
    def on_master(self):
114 b9eef123 Vangelis Koukis
        """Return True if running on the Ganeti master"""
115 b9eef123 Vangelis Koukis
        return socket.getfqdn() == self.environ['GANETI_MASTER']
116 b9eef123 Vangelis Koukis
117 b9eef123 Vangelis Koukis
    def publish_msgs(self, msgs):
118 b9eef123 Vangelis Koukis
        for (msgtype, msg) in msgs:
119 b9eef123 Vangelis Koukis
            routekey = "ganeti.%s.event.%s" % (self.prefix, msgtype)
120 b9eef123 Vangelis Koukis
            self.logger.debug("Pushing message to RabbitMQ: %s (key = %s)",
121 b9eef123 Vangelis Koukis
                json.dumps(msg), routekey)
122 b9eef123 Vangelis Koukis
            msg = amqp.Message(json.dumps(msg))
123 b9eef123 Vangelis Koukis
            msg.properties["delivery_mode"] = 2  # Persistent
124 b9eef123 Vangelis Koukis
125 b9eef123 Vangelis Koukis
            # Retry up to five times to open a channel to RabbitMQ.
126 b9eef123 Vangelis Koukis
            # The hook needs to abort if this count is exceeded, because it
127 b9eef123 Vangelis Koukis
            # runs synchronously with VM creation inside Ganeti, and may only
128 b9eef123 Vangelis Koukis
            # run for a finite amount of time.
129 b9eef123 Vangelis Koukis
            #
130 b9eef123 Vangelis Koukis
            # FIXME: We need a reconciliation mechanism between the DB and
131 b9eef123 Vangelis Koukis
            #        Ganeti, for cases exactly like this.
132 b9eef123 Vangelis Koukis
            conn = None
133 b9eef123 Vangelis Koukis
            sent = False
134 b9eef123 Vangelis Koukis
            retry = 0
135 b9eef123 Vangelis Koukis
            while not sent and retry < 5:
136 b9eef123 Vangelis Koukis
                self.logger.debug("Attempting to publish to RabbitMQ at %s",
137 b9eef123 Vangelis Koukis
                    settings.RABBIT_HOST)
138 b9eef123 Vangelis Koukis
                try:
139 b9eef123 Vangelis Koukis
                    if not conn:
140 b9eef123 Vangelis Koukis
                        conn = amqp.Connection(host=settings.RABBIT_HOST,
141 b9eef123 Vangelis Koukis
                            userid=settings.RABBIT_USERNAME,
142 b9eef123 Vangelis Koukis
                            password=settings.RABBIT_PASSWORD,
143 b9eef123 Vangelis Koukis
                            virtual_host=settings.RABBIT_VHOST)
144 b9eef123 Vangelis Koukis
                        chann = conn.channel()
145 b9eef123 Vangelis Koukis
                        self.logger.debug("Successfully connected to RabbitMQ at %s",
146 b9eef123 Vangelis Koukis
                            settings.RABBIT_HOST)
147 b9eef123 Vangelis Koukis
148 b9eef123 Vangelis Koukis
                    chann.basic_publish(msg,
149 b9eef123 Vangelis Koukis
                        exchange=settings.EXCHANGE_GANETI,
150 b9eef123 Vangelis Koukis
                        routing_key=routekey)
151 b9eef123 Vangelis Koukis
                    sent = True
152 b9eef123 Vangelis Koukis
                    self.logger.debug("Successfully sent message to RabbitMQ")
153 b9eef123 Vangelis Koukis
                except socket.error:
154 b9eef123 Vangelis Koukis
                    conn = False
155 b9eef123 Vangelis Koukis
                    retry += 1
156 b9eef123 Vangelis Koukis
                    self.logger.exception("Publish to RabbitMQ failed, retry=%d in 1s",
157 b9eef123 Vangelis Koukis
                        retry)
158 b9eef123 Vangelis Koukis
                    time.sleep(1)
159 b9eef123 Vangelis Koukis
160 b9eef123 Vangelis Koukis
            if not sent:
161 b9eef123 Vangelis Koukis
                raise Exception("Publish to RabbitMQ failed after %d tries, aborting" % retry)
162 b9eef123 Vangelis Koukis
163 b9eef123 Vangelis Koukis
164 b9eef123 Vangelis Koukis
class PostStartHook(GanetiHook):
165 b9eef123 Vangelis Koukis
    """Post-instance-startup Ganeti Hook.
166 b9eef123 Vangelis Koukis

167 b9eef123 Vangelis Koukis
    Produce notifications to the rest of the Synnefo
168 b9eef123 Vangelis Koukis
    infrastructure in the post-instance-start phase of Ganeti.
169 b9eef123 Vangelis Koukis

170 ff55193e Vangelis Koukis
    Currently, this list only contains a single message,
171 ff55193e Vangelis Koukis
    detailing the net configuration of an instance.
172 ff55193e Vangelis Koukis

173 b9eef123 Vangelis Koukis
    This hook only runs on the Ganeti master.
174 b9eef123 Vangelis Koukis

175 ff55193e Vangelis Koukis
    """
176 b9eef123 Vangelis Koukis
    def run(self):
177 b9eef123 Vangelis Koukis
        if self.on_master():
178 b9eef123 Vangelis Koukis
            notifs = []
179 b9eef123 Vangelis Koukis
            notifs.append(("net", ganeti_net_status(self.logger, self.environ)))
180 b9eef123 Vangelis Koukis
            
181 b9eef123 Vangelis Koukis
            self.publish_msgs(notifs)
182 b9eef123 Vangelis Koukis
183 7ca9e930 Vangelis Koukis
        return 0
184 ff55193e Vangelis Koukis
185 ff55193e Vangelis Koukis
186 b9eef123 Vangelis Koukis
class PostStopHook(GanetiHook):
187 b9eef123 Vangelis Koukis
    def run(self):
188 b9eef123 Vangelis Koukis
        return 0