root / ganeti / hooks.py @ bd29052f
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 | 9cb903f9 | 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 | a1dccf43 | 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 | a1dccf43 | 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 | 9ab91008 | Vangelis Koukis | key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link' } |
39 | 9ab91008 | 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 | 9ab91008 | 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 | 9ab91008 | 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 | 9cb903f9 | Vangelis Koukis | # IPv6 support:
|
53 | 9cb903f9 | Vangelis Koukis | #
|
54 | 9cb903f9 | Vangelis Koukis | # The IPv6 of NIC with index 0 [the public NIC]
|
55 | 9cb903f9 | Vangelis Koukis | # is derived using an EUI64 scheme.
|
56 | 9cb903f9 | Vangelis Koukis | if index == 0 and key == 'mac': |
57 | 9cb903f9 | Vangelis Koukis | nics[0]['ipv6'] = mac2eui64(nics[0]['mac'], |
58 | 9cb903f9 | Vangelis Koukis | settings.PUBLIC_IPV6_PREFIX) |
59 | 9cb903f9 | Vangelis Koukis | |
60 | bd29052f | Vangelis Koukis | # Amend notification with firewall settings
|
61 | bd29052f | Vangelis Koukis | tags = environ.get('GANETI_INSTANCE_TAGS', '') |
62 | bd29052f | Vangelis Koukis | for tag in tags.split(' '): |
63 | bd29052f | Vangelis Koukis | t = tag.split(':')
|
64 | bd29052f | Vangelis Koukis | if t[0:2] == ['synnefo', 'network']: |
65 | bd29052f | Vangelis Koukis | if len(t) != 4: |
66 | bd29052f | Vangelis Koukis | logger.error("Malformed synnefo tag %s", tag)
|
67 | bd29052f | Vangelis Koukis | continue
|
68 | bd29052f | Vangelis Koukis | try:
|
69 | bd29052f | Vangelis Koukis | index = int(t[2]) |
70 | bd29052f | Vangelis Koukis | nics[index]['firewall'] = t[3] |
71 | bd29052f | Vangelis Koukis | except ValueError: |
72 | bd29052f | Vangelis Koukis | logger.error("Malformed synnefo tag %s", tag)
|
73 | bd29052f | Vangelis Koukis | except KeyError: |
74 | bd29052f | Vangelis Koukis | logger.error("Found tag %s for non-existent NIC %d",
|
75 | bd29052f | Vangelis Koukis | tag, index) |
76 | bd29052f | 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 |