Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-gtools / synnefo / ganeti / hook.py @ 22ee6892

History | View | Annotate | Download (8.3 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 45ebfd48 Vangelis Koukis
import subprocess
47 ff55193e Vangelis Koukis
48 ff55193e Vangelis Koukis
import json
49 ff55193e Vangelis Koukis
import socket
50 ff55193e Vangelis Koukis
import logging
51 ff55193e Vangelis Koukis
52 c4e55622 Christos Stavrakakis
from time import time
53 ff55193e Vangelis Koukis
54 6d6b8f88 Kostas Papadimitriou
from synnefo import settings
55 c4e55622 Christos Stavrakakis
from synnefo.lib.amqp import AMQPClient
56 c4e55622 Christos Stavrakakis
from synnefo.lib.utils import split_time
57 45ebfd48 Vangelis Koukis
58 45ebfd48 Vangelis Koukis
59 45ebfd48 Vangelis Koukis
def mac2eui64(mac, prefixstr):
60 45ebfd48 Vangelis Koukis
    process = subprocess.Popen(["mac2eui64", mac, prefixstr],
61 45ebfd48 Vangelis Koukis
                                stdout=subprocess.PIPE)
62 45ebfd48 Vangelis Koukis
    return process.stdout.read().rstrip()
63 ff55193e Vangelis Koukis
64 6d6b8f88 Kostas Papadimitriou
65 ff55193e Vangelis Koukis
def ganeti_net_status(logger, environ):
66 ff55193e Vangelis Koukis
    """Produce notifications of type 'Ganeti-net-status'
67 737b0e28 Vangelis Koukis

68 ff55193e Vangelis Koukis
    Process all GANETI_INSTANCE_NICx_y environment variables,
69 ff55193e Vangelis Koukis
    where x is the NIC index, starting at 0,
70 f533f224 Vangelis Koukis
    and y is one of "MAC", "IP", "BRIDGE".
71 ff55193e Vangelis Koukis

72 ff55193e Vangelis Koukis
    The result is returned as a single notification message
73 f533f224 Vangelis Koukis
    of type 'ganeti-net-status', detailing the NIC configuration
74 ff55193e Vangelis Koukis
    of a Ganeti instance.
75 ff55193e Vangelis Koukis

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

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

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

188 b9eef123 Vangelis Koukis
    This hook only runs on the Ganeti master.
189 b9eef123 Vangelis Koukis

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