Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-gtools / synnefo / ganeti / hook.py @ 9e20fcee

History | View | Annotate | Download (8.4 kB)

1 ff55193e Vangelis Koukis
#!/usr/bin/env python
2 ff55193e Vangelis Koukis
#
3 737b0e28 Vangelis Koukis
# -*- coding: utf-8 -*-
4 737b0e28 Vangelis Koukis
#
5 737b0e28 Vangelis Koukis
# Copyright 2011 GRNET S.A. All rights reserved.
6 737b0e28 Vangelis Koukis
#
7 737b0e28 Vangelis Koukis
# Redistribution and use in source and binary forms, with or
8 737b0e28 Vangelis Koukis
# without modification, are permitted provided that the following
9 737b0e28 Vangelis Koukis
# conditions are met:
10 737b0e28 Vangelis Koukis
#
11 737b0e28 Vangelis Koukis
#   1. Redistributions of source code must retain the above
12 737b0e28 Vangelis Koukis
#      copyright notice, this list of conditions and the following
13 737b0e28 Vangelis Koukis
#      disclaimer.
14 737b0e28 Vangelis Koukis
#
15 737b0e28 Vangelis Koukis
#   2. Redistributions in binary form must reproduce the above
16 737b0e28 Vangelis Koukis
#      copyright notice, this list of conditions and the following
17 737b0e28 Vangelis Koukis
#      disclaimer in the documentation and/or other materials
18 737b0e28 Vangelis Koukis
#      provided with the distribution.
19 737b0e28 Vangelis Koukis
#
20 737b0e28 Vangelis Koukis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 737b0e28 Vangelis Koukis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 737b0e28 Vangelis Koukis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 737b0e28 Vangelis Koukis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 737b0e28 Vangelis Koukis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 737b0e28 Vangelis Koukis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 737b0e28 Vangelis Koukis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 737b0e28 Vangelis Koukis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 737b0e28 Vangelis Koukis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 737b0e28 Vangelis Koukis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 737b0e28 Vangelis Koukis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 737b0e28 Vangelis Koukis
# POSSIBILITY OF SUCH DAMAGE.
32 737b0e28 Vangelis Koukis
#
33 737b0e28 Vangelis Koukis
# The views and conclusions contained in the software and
34 737b0e28 Vangelis Koukis
# documentation are those of the authors and should not be
35 737b0e28 Vangelis Koukis
# interpreted as representing official policies, either expressed
36 737b0e28 Vangelis Koukis
# or implied, of GRNET S.A.
37 ff55193e Vangelis Koukis
#
38 ff55193e Vangelis Koukis
"""Ganeti hooks for Synnefo
39 ff55193e Vangelis Koukis

40 ff55193e Vangelis Koukis
These are the individual Ganeti hooks for Synnefo.
41 ff55193e Vangelis Koukis

42 ff55193e Vangelis Koukis
"""
43 ff55193e Vangelis Koukis
44 ff55193e Vangelis Koukis
import sys
45 ff55193e Vangelis Koukis
import os
46 ff55193e Vangelis Koukis
import json
47 ff55193e Vangelis Koukis
import socket
48 ff55193e Vangelis Koukis
import logging
49 ff55193e Vangelis Koukis
50 c4e55622 Christos Stavrakakis
from time import time
51 ff55193e Vangelis Koukis
52 6d6b8f88 Kostas Papadimitriou
from synnefo import settings
53 c4e55622 Christos Stavrakakis
from synnefo.lib.amqp import AMQPClient
54 c4e55622 Christos Stavrakakis
from synnefo.lib.utils import split_time
55 9e20fcee Christos Stavrakakis
from synnefo.util.mac2eui64 import mac2eui64
56 ff55193e Vangelis Koukis
57 6d6b8f88 Kostas Papadimitriou
58 ff55193e Vangelis Koukis
def ganeti_net_status(logger, environ):
59 ff55193e Vangelis Koukis
    """Produce notifications of type 'Ganeti-net-status'
60 737b0e28 Vangelis Koukis

61 ff55193e Vangelis Koukis
    Process all GANETI_INSTANCE_NICx_y environment variables,
62 ff55193e Vangelis Koukis
    where x is the NIC index, starting at 0,
63 f533f224 Vangelis Koukis
    and y is one of "MAC", "IP", "BRIDGE".
64 ff55193e Vangelis Koukis

65 ff55193e Vangelis Koukis
    The result is returned as a single notification message
66 f533f224 Vangelis Koukis
    of type 'ganeti-net-status', detailing the NIC configuration
67 ff55193e Vangelis Koukis
    of a Ganeti instance.
68 ff55193e Vangelis Koukis

69 ff55193e Vangelis Koukis
    """
70 ff55193e Vangelis Koukis
    nics = {}
71 ff55193e Vangelis Koukis
72 cc3f266e Christos Stavrakakis
    key_to_attr = {'IP': 'ip',
73 cc3f266e Christos Stavrakakis
                   'MAC': 'mac',
74 cc3f266e Christos Stavrakakis
                   'BRIDGE': 'link',
75 cc3f266e Christos Stavrakakis
                   'NETWORK': 'network'}
76 f533f224 Vangelis Koukis
77 ff55193e Vangelis Koukis
    for env in environ.keys():
78 ff55193e Vangelis Koukis
        if env.startswith("GANETI_INSTANCE_NIC"):
79 ff55193e Vangelis Koukis
            s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
80 cc3f266e Christos Stavrakakis
            if len(s) == 2 and s[0].isdigit() and\
81 cc3f266e Christos Stavrakakis
               s[1] in ('MAC', 'IP', 'BRIDGE', 'NETWORK'):
82 ff55193e Vangelis Koukis
                index = int(s[0])
83 f533f224 Vangelis Koukis
                key = key_to_attr[s[1]]
84 ff55193e Vangelis Koukis
85 cc3f266e Christos Stavrakakis
                if index in nics:
86 ff55193e Vangelis Koukis
                    nics[index][key] = environ[env]
87 ff55193e Vangelis Koukis
                else:
88 cc3f266e Christos Stavrakakis
                    nics[index] = {key: environ[env]}
89 ff55193e Vangelis Koukis
90 746c6bf4 Vangelis Koukis
                # IPv6 support:
91 746c6bf4 Vangelis Koukis
                #
92 cc3f266e Christos Stavrakakis
                # The IPv6 is derived using an EUI64 scheme.
93 cc3f266e Christos Stavrakakis
                if key == 'mac':
94 cc3f266e Christos Stavrakakis
                    subnet6 = environ.get("GANETI_INSTANCE_NIC" + s[0] +
95 cc3f266e Christos Stavrakakis
                                          "_NETWORK_SUBNET6", None)
96 cc3f266e Christos Stavrakakis
                    if subnet6:
97 cc3f266e Christos Stavrakakis
                        nics[index]['ipv6'] = mac2eui64(nics[index]['mac'],
98 cc3f266e Christos Stavrakakis
                                                        subnet6)
99 746c6bf4 Vangelis Koukis
100 eab0602e Vangelis Koukis
    # Amend notification with firewall settings
101 eab0602e Vangelis Koukis
    tags = environ.get('GANETI_INSTANCE_TAGS', '')
102 eab0602e Vangelis Koukis
    for tag in tags.split(' '):
103 eab0602e Vangelis Koukis
        t = tag.split(':')
104 eab0602e Vangelis Koukis
        if t[0:2] == ['synnefo', 'network']:
105 eab0602e Vangelis Koukis
            if len(t) != 4:
106 eab0602e Vangelis Koukis
                logger.error("Malformed synnefo tag %s", tag)
107 eab0602e Vangelis Koukis
                continue
108 eab0602e Vangelis Koukis
            try:
109 eab0602e Vangelis Koukis
                index = int(t[2])
110 eab0602e Vangelis Koukis
                nics[index]['firewall'] = t[3]
111 eab0602e Vangelis Koukis
            except ValueError:
112 eab0602e Vangelis Koukis
                logger.error("Malformed synnefo tag %s", tag)
113 eab0602e Vangelis Koukis
            except KeyError:
114 eab0602e Vangelis Koukis
                logger.error("Found tag %s for non-existent NIC %d",
115 eab0602e Vangelis Koukis
                             tag, index)
116 eab0602e Vangelis Koukis
117 ff55193e Vangelis Koukis
    # Verify our findings are consistent with the Ganeti environment
118 ff55193e Vangelis Koukis
    indexes = list(nics.keys())
119 ff55193e Vangelis Koukis
    ganeti_nic_count = int(environ['GANETI_INSTANCE_NIC_COUNT'])
120 ff55193e Vangelis Koukis
    if len(indexes) != ganeti_nic_count:
121 ff55193e Vangelis Koukis
        logger.error("I have %d NICs, Ganeti says number of NICs is %d",
122 ff55193e Vangelis Koukis
            len(indexes), ganeti_nic_count)
123 ff55193e Vangelis Koukis
        raise Exception("Inconsistent number of NICs in Ganeti environment")
124 ff55193e Vangelis Koukis
125 ff55193e Vangelis Koukis
    if indexes != range(0, len(indexes)):
126 ff55193e Vangelis Koukis
        logger.error("Ganeti NIC indexes are not consecutive starting at zero.");
127 ff55193e Vangelis Koukis
        logger.error("NIC indexes are: %s. Environment is: %s", indexes, environ)
128 ff55193e Vangelis Koukis
        raise Exception("Unexpected inconsistency in the Ganeti environment")
129 ff55193e Vangelis Koukis
130 ff55193e Vangelis Koukis
    # Construct the notification
131 ff55193e Vangelis Koukis
    instance = environ['GANETI_INSTANCE_NAME']
132 ff55193e Vangelis Koukis
133 ff55193e Vangelis Koukis
    nics_list = []
134 ff55193e Vangelis Koukis
    for i in indexes:
135 ff55193e Vangelis Koukis
        nics_list.append(nics[i])
136 ff55193e Vangelis Koukis
137 ff55193e Vangelis Koukis
    msg = {
138 c4e55622 Christos Stavrakakis
        "event_time": split_time(time()),
139 ff55193e Vangelis Koukis
        "type": "ganeti-net-status",
140 ff55193e Vangelis Koukis
        "instance": instance,
141 ff55193e Vangelis Koukis
        "nics": nics_list
142 ff55193e Vangelis Koukis
    }
143 ff55193e Vangelis Koukis
144 ff55193e Vangelis Koukis
    return msg
145 ff55193e Vangelis Koukis
146 b9eef123 Vangelis Koukis
147 b9eef123 Vangelis Koukis
class GanetiHook():
148 b9eef123 Vangelis Koukis
    def __init__(self, logger, environ, instance, prefix):
149 b9eef123 Vangelis Koukis
        self.logger = logger
150 b9eef123 Vangelis Koukis
        self.environ = environ
151 b9eef123 Vangelis Koukis
        self.instance = instance
152 b9eef123 Vangelis Koukis
        self.prefix = prefix
153 c4e55622 Christos Stavrakakis
        # Retry up to two times(per host) to open a channel to RabbitMQ.
154 c4e55622 Christos Stavrakakis
        # The hook needs to abort if this count is exceeded, because it
155 c4e55622 Christos Stavrakakis
        # runs synchronously with VM creation inside Ganeti, and may only
156 c4e55622 Christos Stavrakakis
        # run for a finite amount of time.
157 c4e55622 Christos Stavrakakis
158 c4e55622 Christos Stavrakakis
        # FIXME: We need a reconciliation mechanism between the DB and
159 c4e55622 Christos Stavrakakis
        #        Ganeti, for cases exactly like this.
160 996e5d53 Christos Stavrakakis
        self.client = AMQPClient(hosts=settings.AMQP_HOSTS,
161 a8858945 Christos Stavrakakis
                                 max_retries=2 * len(settings.AMQP_HOSTS),
162 a8858945 Christos Stavrakakis
                                 logger=logger)
163 c4e55622 Christos Stavrakakis
        self.client.connect()
164 b9eef123 Vangelis Koukis
165 b9eef123 Vangelis Koukis
    def on_master(self):
166 b9eef123 Vangelis Koukis
        """Return True if running on the Ganeti master"""
167 b9eef123 Vangelis Koukis
        return socket.getfqdn() == self.environ['GANETI_MASTER']
168 b9eef123 Vangelis Koukis
169 b9eef123 Vangelis Koukis
    def publish_msgs(self, msgs):
170 b9eef123 Vangelis Koukis
        for (msgtype, msg) in msgs:
171 b9eef123 Vangelis Koukis
            routekey = "ganeti.%s.event.%s" % (self.prefix, msgtype)
172 b9eef123 Vangelis Koukis
            self.logger.debug("Pushing message to RabbitMQ: %s (key = %s)",
173 c4e55622 Christos Stavrakakis
                              json.dumps(msg), routekey)
174 c4e55622 Christos Stavrakakis
            msg = json.dumps(msg)
175 c4e55622 Christos Stavrakakis
            self.client.basic_publish(exchange=settings.EXCHANGE_GANETI,
176 c4e55622 Christos Stavrakakis
                                      routing_key=routekey,
177 c4e55622 Christos Stavrakakis
                                      body=msg)
178 c4e55622 Christos Stavrakakis
        self.client.close()
179 b9eef123 Vangelis Koukis
180 cc3f266e Christos Stavrakakis
181 b9eef123 Vangelis Koukis
class PostStartHook(GanetiHook):
182 b9eef123 Vangelis Koukis
    """Post-instance-startup Ganeti Hook.
183 b9eef123 Vangelis Koukis

184 b9eef123 Vangelis Koukis
    Produce notifications to the rest of the Synnefo
185 b9eef123 Vangelis Koukis
    infrastructure in the post-instance-start phase of Ganeti.
186 b9eef123 Vangelis Koukis

187 ff55193e Vangelis Koukis
    Currently, this list only contains a single message,
188 ff55193e Vangelis Koukis
    detailing the net configuration of an instance.
189 ff55193e Vangelis Koukis

190 b9eef123 Vangelis Koukis
    This hook only runs on the Ganeti master.
191 b9eef123 Vangelis Koukis

192 ff55193e Vangelis Koukis
    """
193 b9eef123 Vangelis Koukis
    def run(self):
194 b9eef123 Vangelis Koukis
        if self.on_master():
195 b9eef123 Vangelis Koukis
            notifs = []
196 b9eef123 Vangelis Koukis
            notifs.append(("net", ganeti_net_status(self.logger, self.environ)))
197 737b0e28 Vangelis Koukis
198 b9eef123 Vangelis Koukis
            self.publish_msgs(notifs)
199 b9eef123 Vangelis Koukis
200 7ca9e930 Vangelis Koukis
        return 0
201 ff55193e Vangelis Koukis
202 ff55193e Vangelis Koukis
203 b9eef123 Vangelis Koukis
class PostStopHook(GanetiHook):
204 b9eef123 Vangelis Koukis
    def run(self):
205 b9eef123 Vangelis Koukis
        return 0
206 ff55193e Vangelis Koukis
207 45ebfd48 Vangelis Koukis
208 45ebfd48 Vangelis Koukis
def main():
209 45ebfd48 Vangelis Koukis
    logging.basicConfig(level=logging.DEBUG)
210 45ebfd48 Vangelis Koukis
    logger = logging.getLogger("synnefo.ganeti")
211 45ebfd48 Vangelis Koukis
212 45ebfd48 Vangelis Koukis
    try:
213 45ebfd48 Vangelis Koukis
        instance = os.environ['GANETI_INSTANCE_NAME']
214 45ebfd48 Vangelis Koukis
        op = os.environ['GANETI_HOOKS_PATH']
215 45ebfd48 Vangelis Koukis
        phase = os.environ['GANETI_HOOKS_PHASE']
216 45ebfd48 Vangelis Koukis
    except KeyError:
217 45ebfd48 Vangelis Koukis
        raise Exception("Environment missing one of: " \
218 45ebfd48 Vangelis Koukis
            "GANETI_INSTANCE_NAME, GANETI_HOOKS_PATH, GANETI_HOOKS_PHASE")
219 45ebfd48 Vangelis Koukis
220 45ebfd48 Vangelis Koukis
    prefix = instance.split('-')[0]
221 45ebfd48 Vangelis Koukis
222 45ebfd48 Vangelis Koukis
    # FIXME: The hooks should only run for Synnefo instances.
223 45ebfd48 Vangelis Koukis
    # Uncomment the following lines for a shared Ganeti deployment.
224 45ebfd48 Vangelis Koukis
    # Currently, the following code is commented out because multiple
225 45ebfd48 Vangelis Koukis
    # backend prefixes are used in the same Ganeti installation during development.
226 45ebfd48 Vangelis Koukis
    #if not instance.startswith(settings.BACKEND_PREFIX_ID):
227 45ebfd48 Vangelis Koukis
    #    logger.warning("Ignoring non-Synnefo instance %s", instance)
228 45ebfd48 Vangelis Koukis
    #    return 0
229 45ebfd48 Vangelis Koukis
230 45ebfd48 Vangelis Koukis
    hooks = {
231 45ebfd48 Vangelis Koukis
        ("instance-add", "post"): PostStartHook,
232 45ebfd48 Vangelis Koukis
        ("instance-start", "post"): PostStartHook,
233 45ebfd48 Vangelis Koukis
        ("instance-reboot", "post"): PostStartHook,
234 45ebfd48 Vangelis Koukis
        ("instance-stop", "post"): PostStopHook,
235 45ebfd48 Vangelis Koukis
        ("instance-modify", "post"): PostStartHook
236 45ebfd48 Vangelis Koukis
    }
237 45ebfd48 Vangelis Koukis
238 45ebfd48 Vangelis Koukis
    try:
239 45ebfd48 Vangelis Koukis
        hook = hooks[(op, phase)](logger, os.environ, instance, prefix)
240 45ebfd48 Vangelis Koukis
    except KeyError:
241 45ebfd48 Vangelis Koukis
        raise Exception("No hook found for operation = '%s', phase = '%s'" % (op, phase))
242 45ebfd48 Vangelis Koukis
    return hook.run()
243 45ebfd48 Vangelis Koukis
244 45ebfd48 Vangelis Koukis
if __name__ == "__main__":
245 45ebfd48 Vangelis Koukis
    sys.exit(main())