Revision 45ebfd48

/dev/null
1
#!/bin/bash
2

  
3
# This is an example of a Ganeti kvm ifup script that configures network
4
# interfaces based on the initial deployment of the Okeanos project
5

  
6
TAP_CONSTANT_MAC=cc:47:52:4e:45:54 # GRNET in hex :-)
7
MAC2EUI64=/usr/bin/mac2eui64
8
NFDHCPD_STATE_DIR=/var/lib/nfdhcpd
9

  
10
function routed_setup_ipv4 {
11
	# get the link's default gateway
12
	gw=$(ip route list table $LINK | sed -n 's/default via \([^ ]\+\).*/\1/p' | head -1)
13

  
14
	# mangle ARPs to come from the gw's IP
15
	arptables -D OUTPUT -o $INTERFACE --opcode request -j mangle >/dev/null 2>&1
16
	arptables -A OUTPUT -o $INTERFACE --opcode request -j mangle --mangle-ip-s "$gw"
17

  
18
	# route interface to the proper routing table
19
	while ip rule del dev $INTERFACE; do :; done
20
	ip rule add dev $INTERFACE table $LINK
21

  
22
	# static route mapping IP -> INTERFACE
23
	ip route replace $IP table $LINK proto static dev $INTERFACE
24

  
25
	# Enable proxy ARP
26
	echo 1 > /proc/sys/net/ipv4/conf/$INTERFACE/proxy_arp
27
}
28

  
29
function routed_setup_ipv6 {
30
	# Add a routing entry for the eui-64
31
	prefix=$(ip -6 route list table $LINK | awk '/\/64/ {print $1; exit}')
32
	uplink=$(ip -6 route list table $LINK | sed -n 's/default via .* dev \([^ ]\+\).*/\1/p' | head -1)
33
	eui64=$($MAC2EUI64 $MAC $prefix)
34

  
35
	while ip -6 rule del dev $INTERFACE; do :; done
36
	ip -6 rule add dev $INTERFACE table $LINK
37
	ip -6 ro replace $eui64/128 dev $INTERFACE table $LINK
38
	ip -6 neigh add proxy $eui64 dev $uplink
39

  
40
	# disable proxy NDP since we're handling this on userspace
41
	# this should be the default, but better safe than sorry
42
	echo 0 > /proc/sys/net/ipv6/conf/$INTERFACE/proxy_ndp
43
}
44

  
45
# pick a firewall profile per NIC, based on tags (and apply it)
46
function routed_setup_firewall {
47
	ifprefix="synnefo:network:$INTERFACE_INDEX:"
48
	for tag in $TAGS; do
49
		case ${tag#$ifprefix} in
50
		protected)
51
			chain=protected
52
		;;
53
		unprotected)
54
			chain=unprotected
55
		;;
56
		limited)
57
			chain=limited
58
		;;
59
		esac
60
	done
61

  
62
	# Flush any old rules. We have to consider all chains, since
63
	# we are not sure the instance was on the same chain, or had the same
64
	# tap interface.
65
	for oldchain in protected unprotected limited; do
66
		iptables  -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null
67
		ip6tables -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null
68
	done
69

  
70
	if [ "x$chain" != "x" ]; then
71
		iptables  -A FORWARD -o $INTERFACE -j $chain
72
		ip6tables -A FORWARD -o $INTERFACE -j $chain
73
	fi
74
}
75

  
76
function routed_setup_nfdhcpd {
77
	umask 022
78
	cat >$NFDHCPD_STATE_DIR/$INTERFACE <<EOF
79
IP=$IP
80
MAC=$MAC
81
LINK=$LINK
82
HOSTNAME=$INSTANCE
83
TAGS="$TAGS"
84
EOF
85
}
86

  
87
if [ "$MODE" = "routed" ]; then
88
	# special proxy-ARP/NDP routing mode
89

  
90
	# use a constant predefined MAC address for the tap
91
	ip link set $INTERFACE addr $TAP_CONSTANT_MAC
92
	# bring the tap up
93
	ifconfig $INTERFACE 0.0.0.0 up
94

  
95
	# Drop unicast BOOTP/DHCP packets
96
	iptables -D FORWARD -i $INTERFACE -p udp --dport 67 -j DROP 2>/dev/null
97
	iptables -A FORWARD -i $INTERFACE -p udp --dport 67 -j DROP
98

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

  
40
These are the individual Ganeti hooks for Synnefo.
41

  
42
"""
43

  
44
import sys
45
import os
46

  
47
import time
48
import json
49
import socket
50
import logging
51

  
52
from amqplib import client_0_8 as amqp
53

  
54
from synnefo.util.mac2eui64 import mac2eui64
55
import synnefo.settings as settings
56

  
57
def ganeti_net_status(logger, environ):
58
    """Produce notifications of type 'Ganeti-net-status'
59

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

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

  
68
    """
69
    nics = {}
70

  
71
    key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link' }
72

  
73
    for env in environ.keys():
74
        if env.startswith("GANETI_INSTANCE_NIC"):
75
            s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
76
            if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP', 'BRIDGE'):
77
                index = int(s[0])
78
                key = key_to_attr[s[1]]
79

  
80
                if nics.has_key(index):
81
                    nics[index][key] = environ[env]
82
                else:
83
                    nics[index] = { key: environ[env] }
84

  
85
                # IPv6 support:
86
                #
87
                # The IPv6 of NIC with index 0 [the public NIC]
88
                # is derived using an EUI64 scheme.
89
                if index == 0 and key == 'mac':
90
                    nics[0]['ipv6'] = mac2eui64(nics[0]['mac'],
91
                                                settings.PUBLIC_IPV6_PREFIX)
92

  
93
    # Amend notification with firewall settings
94
    tags = environ.get('GANETI_INSTANCE_TAGS', '')
95
    for tag in tags.split(' '):
96
        t = tag.split(':')
97
        if t[0:2] == ['synnefo', 'network']:
98
            if len(t) != 4:
99
                logger.error("Malformed synnefo tag %s", tag)
100
                continue
101
            try:
102
                index = int(t[2])
103
                nics[index]['firewall'] = t[3]
104
            except ValueError:
105
                logger.error("Malformed synnefo tag %s", tag)
106
            except KeyError:
107
                logger.error("Found tag %s for non-existent NIC %d",
108
                             tag, index)
109

  
110
    # Verify our findings are consistent with the Ganeti environment
111
    indexes = list(nics.keys())
112
    ganeti_nic_count = int(environ['GANETI_INSTANCE_NIC_COUNT'])
113
    if len(indexes) != ganeti_nic_count:
114
        logger.error("I have %d NICs, Ganeti says number of NICs is %d",
115
            len(indexes), ganeti_nic_count)
116
        raise Exception("Inconsistent number of NICs in Ganeti environment")
117

  
118
    if indexes != range(0, len(indexes)):
119
        logger.error("Ganeti NIC indexes are not consecutive starting at zero.");
120
        logger.error("NIC indexes are: %s. Environment is: %s", indexes, environ)
121
        raise Exception("Unexpected inconsistency in the Ganeti environment")
122

  
123
    # Construct the notification
124
    instance = environ['GANETI_INSTANCE_NAME']
125

  
126
    nics_list = []
127
    for i in indexes:
128
        nics_list.append(nics[i])
129

  
130
    msg = {
131
        "type": "ganeti-net-status",
132
        "instance": instance,
133
        "nics": nics_list
134
    }
135

  
136
    return msg
137

  
138

  
139
class GanetiHook():
140
    def __init__(self, logger, environ, instance, prefix):
141
        self.logger = logger
142
        self.environ = environ
143
        self.instance = instance
144
        self.prefix = prefix
145

  
146
    def on_master(self):
147
        """Return True if running on the Ganeti master"""
148
        return socket.getfqdn() == self.environ['GANETI_MASTER']
149

  
150
    def publish_msgs(self, msgs):
151
        for (msgtype, msg) in msgs:
152
            routekey = "ganeti.%s.event.%s" % (self.prefix, msgtype)
153
            self.logger.debug("Pushing message to RabbitMQ: %s (key = %s)",
154
                json.dumps(msg), routekey)
155
            msg = amqp.Message(json.dumps(msg))
156
            msg.properties["delivery_mode"] = 2  # Persistent
157

  
158
            # Retry up to five times to open a channel to RabbitMQ.
159
            # The hook needs to abort if this count is exceeded, because it
160
            # runs synchronously with VM creation inside Ganeti, and may only
161
            # run for a finite amount of time.
162
            #
163
            # FIXME: We need a reconciliation mechanism between the DB and
164
            #        Ganeti, for cases exactly like this.
165
            conn = None
166
            sent = False
167
            retry = 0
168
            while not sent and retry < 5:
169
                self.logger.debug("Attempting to publish to RabbitMQ at %s",
170
                    settings.RABBIT_HOST)
171
                try:
172
                    if not conn:
173
                        conn = amqp.Connection(host=settings.RABBIT_HOST,
174
                            userid=settings.RABBIT_USERNAME,
175
                            password=settings.RABBIT_PASSWORD,
176
                            virtual_host=settings.RABBIT_VHOST)
177
                        chann = conn.channel()
178
                        self.logger.debug("Successfully connected to RabbitMQ at %s",
179
                            settings.RABBIT_HOST)
180

  
181
                    chann.basic_publish(msg,
182
                        exchange=settings.EXCHANGE_GANETI,
183
                        routing_key=routekey)
184
                    sent = True
185
                    self.logger.debug("Successfully sent message to RabbitMQ")
186
                except socket.error:
187
                    conn = False
188
                    retry += 1
189
                    self.logger.exception("Publish to RabbitMQ failed, retry=%d in 1s",
190
                        retry)
191
                    time.sleep(1)
192

  
193
            if not sent:
194
                raise Exception("Publish to RabbitMQ failed after %d tries, aborting" % retry)
195

  
196

  
197
class PostStartHook(GanetiHook):
198
    """Post-instance-startup Ganeti Hook.
199

  
200
    Produce notifications to the rest of the Synnefo
201
    infrastructure in the post-instance-start phase of Ganeti.
202

  
203
    Currently, this list only contains a single message,
204
    detailing the net configuration of an instance.
205

  
206
    This hook only runs on the Ganeti master.
207

  
208
    """
209
    def run(self):
210
        if self.on_master():
211
            notifs = []
212
            notifs.append(("net", ganeti_net_status(self.logger, self.environ)))
213

  
214
            self.publish_msgs(notifs)
215

  
216
        return 0
217

  
218

  
219
class PostStopHook(GanetiHook):
220
    def run(self):
221
        return 0
222

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

  
40
A daemon to monitor the Ganeti job queue and publish job progress
41
and Ganeti VM state notifications to the ganeti exchange
42

  
43
"""
44

  
45
#from django.core.management import setup_environ
46

  
47
import sys
48
import os
49
path = os.path.normpath(os.path.join(os.getcwd(), '..'))
50
sys.path.append(path)
51
import synnefo.settings as settings
52

  
53
#setup_environ(settings)
54

  
55
import time
56
import json
57
import logging
58
import pyinotify
59
import daemon
60
import daemon.pidlockfile
61
import socket
62
from signal import signal, SIGINT, SIGTERM
63

  
64
from amqplib import client_0_8 as amqp
65

  
66
from ganeti import utils
67
from ganeti import jqueue
68
from ganeti import constants
69
from ganeti import serializer
70

  
71
class JobFileHandler(pyinotify.ProcessEvent):
72
    def __init__(self, logger):
73
        pyinotify.ProcessEvent.__init__(self)
74
        self.logger = logger
75
        self.chan = None
76

  
77
    def open_channel(self):
78
        conn = None
79
        while conn == None:
80
            handler_logger.info("Attempting to connect to %s",
81
                settings.RABBIT_HOST)
82
            try:
83
                conn = amqp.Connection(host=settings.RABBIT_HOST,
84
                     userid=settings.RABBIT_USERNAME,
85
                     password=settings.RABBIT_PASSWORD,
86
                     virtual_host=settings.RABBIT_VHOST)
87
            except socket.error:
88
                time.sleep(1)
89

  
90
        handler_logger.info("Connection succesful, opening channel")
91
        return conn.channel()
92

  
93
    def process_IN_CLOSE_WRITE(self, event):
94
        self.process_IN_MOVED_TO(event)
95

  
96
    def process_IN_MOVED_TO(self, event):
97
        if self.chan == None:
98
            self.chan = self.open_channel()
99

  
100
        jobfile = os.path.join(event.path, event.name)
101
        if not event.name.startswith("job-"):
102
            self.logger.debug("Not a job file: %s" % event.path)
103
            return
104

  
105
        try:
106
            data = utils.ReadFile(jobfile)
107
        except IOError:
108
            return
109

  
110
        data = serializer.LoadJson(data)
111
        job = jqueue._QueuedJob.Restore(None, data)
112

  
113
        for op in job.ops:
114
            instances = ""
115
            try:
116
                instances = " ".join(op.input.instances)
117
            except AttributeError:
118
                pass
119

  
120
            try:
121
                instances = op.input.instance_name
122
            except AttributeError:
123
                pass
124

  
125
            # Get the last line of the op log as message
126
            try:
127
                logmsg = op.log[-1][-1]
128
            except IndexError:
129
                logmsg = None
130

  
131
            self.logger.debug("Job: %d: %s(%s) %s %s",
132
                    int(job.id), op.input.OP_ID, instances, op.status, logmsg)
133

  
134
            # Construct message
135
            msg = {
136
                    "type": "ganeti-op-status",
137
                    "instance": instances,
138
                    "operation": op.input.OP_ID,
139
                    "jobId": int(job.id),
140
                    "status": op.status,
141
                    "logmsg": logmsg
142
                    }
143
            if logmsg:
144
                msg["message"] = logmsg
145

  
146
            instance = instances.split('-')[0]
147
            routekey = "ganeti.%s.event.op" % instance
148

  
149
            self.logger.debug("Delivering msg: %s (key=%s)",
150
                json.dumps(msg), routekey)
151
            msg = amqp.Message(json.dumps(msg))
152
            msg.properties["delivery_mode"] = 2  # Persistent
153

  
154
            while True:
155
                try:
156
                    self.chan.basic_publish(msg,
157
                            exchange=settings.EXCHANGE_GANETI,
158
                            routing_key=routekey)
159
                    return
160
                except socket.error:
161
                    self.logger.exception("Server went away, reconnecting...")
162
                    self.chan = self.open_channel()
163
                except Exception:
164
                    self.logger.exception("Caught unexpected exception, msg: ",
165
                                          msg)
166
                    raise
167

  
168
handler_logger = None
169
def fatal_signal_handler(signum, frame):
170
    global handler_logger
171

  
172
    handler_logger.info("Caught fatal signal %d, will raise SystemExit",
173
                        signum)
174
    raise SystemExit
175

  
176
def parse_arguments(args):
177
    from optparse import OptionParser
178

  
179
    parser = OptionParser()
180
    parser.add_option("-d", "--debug", action="store_true", dest="debug",
181
                      help="Enable debugging information")
182
    parser.add_option("-l", "--log", dest="log_file",
183
                      default=settings.GANETI_EVENTD_LOG_FILE,
184
                      metavar="FILE",
185
                      help="Write log to FILE instead of %s" %
186
                           settings.GANETI_EVENTD_LOG_FILE)
187
    parser.add_option('--pid-file', dest="pid_file",
188
                      default=settings.GANETI_EVENTD_PID_FILE,
189
                      metavar='PIDFILE',
190
                      help="Save PID to file (default: %s)" %
191
                           settings.GANETI_EVENTD_PID_FILE)
192

  
193
    return parser.parse_args(args)
194

  
195
def main():
196
    global handler_logger
197

  
198
    (opts, args) = parse_arguments(sys.argv[1:])
199

  
200
    # Create pidfile
201
    pidf = daemon.pidlockfile.TimeoutPIDLockFile(opts.pid_file, 10)
202

  
203
    # Initialize logger
204
    lvl = logging.DEBUG if opts.debug else logging.INFO
205
    logger = logging.getLogger("ganeti.eventd")
206
    logger.setLevel(lvl)
207
    formatter = logging.Formatter(
208
        "%(asctime)s %(module)s[%(process)d] %(levelname)s: %(message)s",
209
        "%Y-%m-%d %H:%M:%S")
210
    handler = logging.FileHandler(opts.log_file)
211
    handler.setFormatter(formatter)
212
    logger.addHandler(handler)
213
    handler_logger = logger
214

  
215
    # Become a daemon:
216
    # Redirect stdout and stderr to handler.stream to catch
217
    # early errors in the daemonization process [e.g., pidfile creation]
218
    # which will otherwise go to /dev/null.
219
    daemon_context = daemon.DaemonContext(
220
            pidfile=pidf,
221
            umask=022,
222
            stdout=handler.stream,
223
            stderr=handler.stream,
224
            files_preserve=[handler.stream])
225
    daemon_context.open()
226
    logger.info("Became a daemon")
227

  
228
    # Catch signals to ensure graceful shutdown
229
    signal(SIGINT, fatal_signal_handler)
230
    signal(SIGTERM, fatal_signal_handler)
231

  
232
    # Monitor the Ganeti job queue, create and push notifications
233
    wm = pyinotify.WatchManager()
234
    mask = pyinotify.EventsCodes.ALL_FLAGS["IN_MOVED_TO"] | \
235
           pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
236
    handler = JobFileHandler(logger)
237
    notifier = pyinotify.Notifier(wm, handler)
238

  
239
    try:
240
        # Fail if adding the inotify() watch fails for any reason
241
        res = wm.add_watch(constants.QUEUE_DIR, mask)
242
        if res[constants.QUEUE_DIR] < 0:
243
            raise Exception("pyinotify add_watch returned negative descriptor")
244

  
245
        logger.info("Now watching %s" % constants.QUEUE_DIR)
246

  
247
        while True:    # loop forever
248
            # process the queue of events as explained above
249
            notifier.process_events()
250
            if notifier.check_events():
251
                # read notified events and enqeue them
252
                notifier.read_events()
253
    except SystemExit:
254
        logger.info("SystemExit")
255
    except:
256
        logger.exception("Caught exception, terminating")
257
    finally:
258
        # destroy the inotify's instance on this interrupt (stop monitoring)
259
        notifier.stop()
260
        raise
261

  
262
if __name__ == "__main__":
263
    sys.exit(main())
264

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

  
37
# This must be set to the root of the the Django project for Synnefo
38
SYNNEFO_PROJECT_DIR=/home/devel/synnefo
39

  
40
# Include the parent of the Django project dir in the PYTHONPATH
41
export PYTHONPATH=$PYTHONPATH:$(dirname "$SYNNEFO_PROJECT_DIR")
42

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

  
40
This is the generic Synnefo Ganeti hook.
41

  
42
It uses the full path of the hook, as passed through sys.argv[0]
43
to discover the root of the Synnefo project, then passes
44
control to the function which implements this specific hook,
45
based on the GANETI_HOOKS_PATH and GANETI_HOOKS_PHASE env variables,
46
set by Ganeti.
47

  
48
"""
49
import logging
50

  
51
import sys
52
import os
53

  
54
# IMPORTANT: PYTHONPATH must contain the parent of the Synnefo project root.
55
try:
56
    import synnefo.settings as settings
57
except ImportError:
58
    raise Exception("Cannot import settings, make sure PYTHONPATH contains "
59
                    "the parent directory of the Synnefo Django project.")
60

  
61
# A hook runs either in the "pre" or "post" phase of a Ganeti operation.
62
# Possible values for the Ganeti operation are "instance-start",
63
# "instance-stop", "instance-reboot", "instance-modify". See the Ganeti
64
# documentation for a full list.
65

  
66
# The operation and phase for which the hook run are determined from the
67
# values of the GANETI_HOOK_PATH and GANETI_HOOK_PHASE environment variables.
68
# For each valid (operation, phase) pair control passes to the corresponding
69
# Python function, based on the following dictionary.
70

  
71
from synnefo.ganeti.hooks import \
72
    PostStartHook, PostStopHook
73

  
74
hooks = {
75
    ("instance-add", "post"): PostStartHook,
76
    ("instance-start", "post"): PostStartHook,
77
    ("instance-reboot", "post"): PostStartHook,
78
    ("instance-stop", "post"): PostStopHook,
79
    ("instance-modify", "post"): PostStartHook
80
}
81

  
82
def main():
83
    logging.basicConfig(level=logging.DEBUG)
84
    logger = logging.getLogger("synnefo.ganeti")
85

  
86
    try:
87
        instance = os.environ['GANETI_INSTANCE_NAME']
88
        op = os.environ['GANETI_HOOKS_PATH']
89
        phase = os.environ['GANETI_HOOKS_PHASE']
90
    except KeyError:
91
        raise Exception("Environment missing one of: " \
92
            "GANETI_INSTANCE_NAME, GANETI_HOOKS_PATH, GANETI_HOOKS_PHASE")
93

  
94
    prefix = instance.split('-')[0]
95

  
96
    # FIXME: The hooks should only run for Synnefo instances.
97
    # Uncomment the following lines for a shared Ganeti deployment.
98
    # Currently, the following code is commented out because multiple
99
    # backend prefixes are used in the same Ganeti installation during development.
100
    #if not instance.startswith(settings.BACKEND_PREFIX_ID):
101
    #    logger.warning("Ignoring non-Synnefo instance %s", instance)
102
    #    return 0
103

  
104
    try:
105
        hook = hooks[(op, phase)](logger, os.environ, instance, prefix)
106
    except KeyError:
107
        raise Exception("No hook found for operation = '%s', phase = '%s'" % (op, phase))
108
    return hook.run()
109

  
110

  
111
if __name__ == "__main__":
112
    sys.exit(main())
113

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

  
37
# This must be set to the root of the the Django project for Synnefo
38
SYNNEFO_PROJECT_DIR=/home/devel/synnefo
39

  
40
# Include the parent of the Django project dir in the PYTHONPATH
41
export PYTHONPATH=$PYTHONPATH:$(dirname "$SYNNEFO_PROJECT_DIR")
42

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

  
45
from amqplib import client_0_8 as amqp
46

  
47
try:
48
    import synnefo.settings as settings
49
except ImportError:
50
    raise Exception("Cannot import settings, make sure PYTHONPATH contains "
51
                    "the parent directory of the Synnefo Django project.")
52

  
53

  
54
class AMQPClient(object):
55
    def __init__(self, routekey):
56
        self.conn = None
57
        self.chan = None
58
        self.routekey = routekey
59
  
60
    def open_channel(self):
61
        if not self.conn:
62
            try:
63
                sys.stderr.write("Attempting to connect to %s\n" %
64
                                 settings.RABBIT_HOST)
65
                self.conn = amqp.Connection(host=settings.RABBIT_HOST,
66
                                            userid=settings.RABBIT_USERNAME,
67
                                            password=settings.RABBIT_PASSWORD,
68
                                            virtual_host=settings.RABBIT_VHOST)
69
            except socket.error:
70
                sys.stderr.write("Connection failed, will retry in 1s\n")
71
                time.sleep(1)
72

  
73
        if self.conn:
74
            sys.stderr.write("Connection succesful, opening channel\n")
75

  
76
        self.chan = self.conn.channel()
77

  
78
    def send_message(self, msg):
79
        sys.stderr.write("Delivering msg with key=%s:\n%s\n" %
80
                         (self.routekey, json.dumps(msg)))
81
        msg = amqp.Message(json.dumps(msg))
82
        msg.properties["delivery_mode"] = 2  # Persistent
83

  
84
        if not self.chan:
85
            self.open_channel()
86
        if not self.chan:
87
            return
88

  
89
        try:
90
            self.chan.basic_publish(msg,
91
                                    exchange=settings.EXCHANGE_GANETI,
92
                                    routing_key=self.routekey)
93
        except socket.error:
94
            sys.stderr.write("Server went away, reconnecting...\n")
95
            self.conn = None
96
            self.chan = None
97

  
98

  
99
def parse_arguments(args):
100
    from optparse import OptionParser
101

  
102
    kw = {}
103
    kw['usage'] = "%prog [options] command [args...]"
104
    kw['description'] = \
105
        "%prog runs 'command' with the specified arguments, monitoring the " \
106
        "number of bytes read and written by it. 'command' is assumed to be " \
107
        "A program used to install the OS for a Ganeti instance. %prog " \
108
        "periodically issues notifications of type 'ganeti-create-progress' " \
109
        "to the rest of the Synnefo infrastructure over AMQP."
110

  
111
    parser = OptionParser(**kw)
112
    parser.disable_interspersed_args()
113
    parser.add_option("-r", "--read-bytes",
114
                      action="store", type="int", dest="read_bytes",
115
                      metavar="BYTES_TO_READ",
116
                      help="The expected number of bytes to be read, " \
117
                           "used to compute input progress",
118
                      default=0)
119
    parser.add_option("-w", "--write-bytes",
120
                      action="store", type="int", dest="write_bytes",
121
                      metavar="BYTES_TO_WRITE",
122
                      help="The expected number of bytes to be written, " \
123
                           "used to compute output progress",
124
                      default=0)
125
    parser.add_option("-i", "--instance-name",
126
                      dest="instance_name",
127
                      metavar="GANETI_INSTANCE",
128
                      help="The Ganeti instance name to be used in AMQP " \
129
                           "notifications")
130

  
131
    (opts, args) = parser.parse_args(args)
132

  
133
    if opts.instance_name is None or (opts.read_bytes == 0 and
134
                                      opts.write_bytes == 0):
135
        sys.stderr.write("Fatal: Options '-i' and at least one of '-r' " \
136
                         "or '-w' are mandatory.\n")
137
        parser.print_help()
138
        sys.exit(1)
139

  
140
    if len(args) == 0:
141
        sys.stderr.write("Fatal: You need to specify the command to run.\n")
142
        parser.print_help()
143
        sys.exit(1)
144

  
145
    return (opts, args)
146

  
147

  
148
def report_wait_status(pid, status):
149
    if os.WIFEXITED(status):
150
        sys.stderr.write("Child PID = %d exited, status = %d\n" %
151
                         (pid, os.WEXITSTATUS(status)))
152
    elif os.WIFSIGNALED(status):
153
        sys.stderr.write("Child PID = %d died by signal, signal = %d\n" %
154
                         (pid, os.WTERMSIG(status)))
155
    elif os.WIFSTOPPED(status):
156
        sys.stderr.write("Child PID = %d stopped by signal, signal = %d\n" %
157
                         (pid, os.WSTOPSIG(status)))
158
    else:
159
        sys.stderr.write("Internal error: Unhandled case, " \
160
                         "PID = %d, status = %d\n" % (pid, status))
161
        sys.exit(1)
162
    sys.stderr.flush()
163

  
164

  
165
def main():
166
    (opts, args) = parse_arguments(sys.argv[1:])
167

  
168
    # WARNING: This assumes that instance names
169
    # are of the form prefix-id, and uses prefix to
170
    # determine the routekey for AMPQ
171
    prefix = opts.instance_name.split('-')[0]
172
    routekey = "ganeti.%s.event.progress" % prefix
173
    amqp = AMQPClient(routekey)
174

  
175
    pid = os.fork()
176
    if pid == 0:
177
        # In child process:
178

  
179
        # Make sure we die we the parent and are not left behind
180
        # WARNING: This uses the prctl(2) call and is Linux-specific.
181
        prctl.set_pdeathsig(signal.SIGHUP)
182
        
183
        # exec command specified in arguments,
184
        # searching the $PATH, keeping all environment
185
        os.execvpe(args[0], args, os.environ)
186
        sys.stderr.write("execvpe failed, exiting with non-zero status")
187
        os.exit(1)
188

  
189
    # In parent process:
190
    iofname = "/proc/%d/io" % pid
191
    iof = open(iofname, "r", 0)   # 0: unbuffered open
192
    sys.stderr.write("%s: created child PID = %d, monitoring file %s\n" %
193
                     (sys.argv[0], pid, iofname))
194

  
195
    while True:
196
        # check if the child process is still alive
197
        (wpid, status) = os.waitpid(pid, os.WNOHANG)
198
        if wpid == pid:
199
            report_wait_status(pid, status)
200
            if (os.WIFEXITED(status) or os.WIFSIGNALED(status)):
201
                if not (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0):
202
                    return 1
203
                else:
204
                    return 0
205

  
206
        # retrieve the current values of the read/write byte counters
207
        iof.seek(0)
208
        for l in iof.readlines():
209
            if l.startswith("rchar:"):
210
                rchar = int(l.split(': ')[1])
211
            if l.startswith("wchar:"):
212
                wchar = int(l.split(': ')[1])
213

  
214
        # Construct notification of type 'ganeti-create-progress'
215
        msg = dict(type="ganeti-create-progress",
216
                   instance=opts.instance_name)
217
        if opts.read_bytes:
218
            msg['rprogress'] =  float("%2.2f" %
219
                                      (rchar * 100.0 / opts.read_bytes))
220
        if opts.write_bytes:
221
            msg['wprogress'] =  float("%2.2f" %
222
                                      (wchar * 100.0 / opts.write_bytes))
223

  
224
        # and send it over AMQP
225
        amqp.send_message(msg)
226
    
227
        # Sleep for a while
228
        time.sleep(3)
229

  
230

  
231
if __name__ == "__main__":
232
    sys.exit(main())
/dev/null
1
#
2
# Unit Tests for the Ganeti-specific interfaces
3
#
4
# Provides unit tests for the code implementing
5
# the Ganeti notification daemon and the Ganeti hook in Synnefo.
6
#
7
# Copyright 2011 Greek Research and Technology Network
8
#
9
import logging
10

  
11
from django.test import TestCase
12
from django.conf import settings
13

  
14
from ganeti.hooks import ganeti_net_status
15

  
16
class GanetiHookTestCase(TestCase):
17
    def setUp(self):
18
        # Example Ganeti environment, based on from
19
        # http://docs.ganeti.org/ganeti/master/html/hooks.html?highlight=hooks#examples
20
        self.env = {
21
            'GANETI_CLUSTER': 'cluster1.example.com',
22
            'GANETI_DATA_DIR': '/var/lib/ganeti',
23
            'GANETI_FORCE': 'False',
24
            'GANETI_HOOKS_PATH': 'instance-start',
25
            'GANETI_HOOKS_PHASE': 'post',
26
            'GANETI_HOOKS_VERSION': '2',
27
            'GANETI_INSTANCE_DISK0_MODE': 'rw',
28
            'GANETI_INSTANCE_DISK0_SIZE': '128',
29
            'GANETI_INSTANCE_DISK_COUNT': '1',
30
            'GANETI_INSTANCE_DISK_TEMPLATE': 'drbd',
31
            'GANETI_INSTANCE_MEMORY': '128',
32
            'GANETI_INSTANCE_TAGS': 'tag1 synnefo:network:0:protected tag2',
33
            'GANETI_INSTANCE_NAME': 'instance2.example.com',
34
            'GANETI_INSTANCE_NIC0_BRIDGE': 'xen-br0',
35
            'GANETI_INSTANCE_NIC0_IP': '147.102.3.1',
36
            'GANETI_INSTANCE_NIC0_MAC': '00:01:de:ad:be:ef',
37
            'GANETI_INSTANCE_NIC1_MAC': '00:01:de:ad:ba:be',
38
            'GANETI_INSTANCE_NIC2_MAC': '00:01:02:03:04:05',
39
            'GANETI_INSTANCE_NIC2_IP': '147.102.3.98',
40
            'GANETI_INSTANCE_NIC_COUNT': '3',
41
            'GANETI_INSTANCE_OS_TYPE': 'debootstrap',
42
            'GANETI_INSTANCE_PRIMARY': 'node3.example.com',
43
            'GANETI_INSTANCE_SECONDARY': 'node5.example.com',
44
            'GANETI_INSTANCE_STATUS': 'down',
45
            'GANETI_INSTANCE_VCPUS': '1',
46
            'GANETI_MASTER': 'node1.example.com',
47
            'GANETI_OBJECT_TYPE': 'INSTANCE',
48
            'GANETI_OP_CODE': 'OP_INSTANCE_STARTUP',
49
            'GANETI_OP_TARGET': 'instance2.example.com'
50
        }
51

  
52
    def test_ganeti_net_status(self):
53
        e = self.env
54
        expected = {
55
            'type': 'ganeti-net-status',
56
            'instance': 'instance2.example.com',
57
            'nics': [
58
                {
59
                    'ip': '147.102.3.1', 'mac': '00:01:de:ad:be:ef',
60
                    'link': 'xen-br0', 'ipv6': '2001:db8::201:deff:fead:beef',
61
                    'firewall': 'protected'
62
                },
63
                { 'mac': '00:01:de:ad:ba:be' },
64
                { 'ip': '147.102.3.98', 'mac': '00:01:02:03:04:05' }
65
            ]
66
        }
67

  
68
        self.assertEqual(ganeti_net_status(logging, e), expected)
b/snf-ganeti-tools/MANIFEST.in
1
include kvm-vif-bridge snf-ganeti-eventd.py snf-ganeti-hook.py snf-progress-monitor.py
b/snf-ganeti-tools/debian/changelog
1
snf-ganeti-tools (0.5-1) UNRELEASED; urgency=low
2

  
3
  * Initial release.
4

  
5
 -- Faidon Liambotis <paravoid@debian.org>  Wed, 29 Jun 2011 18:41:49 +0300
b/snf-ganeti-tools/debian/clean
1
snf_ganeti_tools.egg-info/*
b/snf-ganeti-tools/debian/compat
1
8
b/snf-ganeti-tools/debian/control
1
Source: snf-ganeti-tools
2
Section: python
3
Priority: optional
4
Maintainer: Faidon Liambotis <paravoid@debian.org>
5
Build-Depends: debhelper (>= 8), python-all (>= 2.5)
6
Standards-Version: 3.9.2
7
XS-Python-Version: >= 2.6
8
Homepage: https://code.grnet.gr/projects/synnefo
9

  
10
Package: snf-ganeti-tools
11
Architecture: all
12
Depends: ${misc:Depends}, ${python:Depends}, nfdhcpd, ganeti2
13
Provides: ${python:Provides}
14
XB-Python-Version: ${python:Versions}
15
Description: Synnefo Ganeti supplementary tools
16
 This is a fork of the Ganeti supplementary tools shipped with the synnefo
17
 project.
18
 .
19
 Included are:
20
  * snf-ganeti-hook, a Ganeti hook that messages synnefo dispatcher
21
  * snf-ganeti-eventd, a daemon watching the Ganeti job queue
22
  * kvm-vif-bridge, a shell ifup script for KVM instances brought up by Ganeti
b/snf-ganeti-tools/debian/dirs
1
usr/sbin
2
etc/synnefo
3
etc/ganeti/hooks/instance-add-post.d
4
etc/ganeti/hooks/instance-modify-post.d
5
etc/ganeti/hooks/instance-reboot-post.d
6
etc/ganeti/hooks/instance-start-post.d
7
etc/ganeti/hooks/instance-stop-post.d
b/snf-ganeti-tools/debian/examples
1
example.conf
b/snf-ganeti-tools/debian/install
1
kvm-vif-bridge /etc/ganeti
b/snf-ganeti-tools/debian/links
1
usr/sbin/snf-ganeti-hook etc/ganeti/hooks/instance-add-post.d/snf-ganeti-hook
2
usr/sbin/snf-ganeti-hook etc/ganeti/hooks/instance-modify-post.d/snf-ganeti-hook
3
usr/sbin/snf-ganeti-hook etc/ganeti/hooks/instance-reboot-post.d/snf-ganeti-hook
4
usr/sbin/snf-ganeti-hook etc/ganeti/hooks/instance-start-post.d/snf-ganeti-hook
5
usr/sbin/snf-ganeti-hook etc/ganeti/hooks/instance-stop-post.d/snf-ganeti-hook
b/snf-ganeti-tools/debian/pydist-overrides
1
ganeti ganeti2
2
daemon python-daemon
b/snf-ganeti-tools/debian/rules
1
#!/usr/bin/make -f
2

  
3
%:
4
	dh $@ --with python2
5

  
6
override_dh_auto_install:
7
	dh_auto_install
8

  
9
	mkdir -p debian/snf-ganeti-tools/usr/sbin
10
	-mv \
11
	  debian/snf-ganeti-tools/usr/bin/snf-ganeti-eventd.py \
12
	  debian/snf-ganeti-tools/usr/sbin/snf-ganeti-eventd
13
	-mv \
14
	  debian/snf-ganeti-tools/usr/bin/snf-ganeti-hook.py \
15
	  debian/snf-ganeti-tools/usr/sbin/snf-ganeti-hook
16
	-mv \
17
	  debian/snf-ganeti-tools/usr/bin/snf-progress-monitor.py \
18
	  debian/snf-ganeti-tools/usr/sbin/snf-progress-monitor
19
	rmdir debian/snf-ganeti-tools/usr/bin
b/snf-ganeti-tools/debian/source/format
1
3.0 (native)
b/snf-ganeti-tools/example.conf
1
# Example configuration file for snf-ganeti-tools
2
#
3
# You should copy this to /etc/synnefo and change it accordingly
4

  
5
RABBIT_HOST = "62.217.120.67:5672"
6
RABBIT_USERNAME = "okeanos"
7
RABBIT_PASSWORD = "0k3@n0s"
8
RABBIT_VHOST = "/"
9
BACKEND_PREFIX_ID = "snf-"
10
EXCHANGE_GANETI = "ganeti"
11
PUBLIC_IPV6_PREFIX = "2001:db8::/64"
b/snf-ganeti-tools/kvm-vif-bridge
1
#!/bin/bash
2

  
3
# This is an example of a Ganeti kvm ifup script that configures network
4
# interfaces based on the initial deployment of the Okeanos project
5

  
6
TAP_CONSTANT_MAC=cc:47:52:4e:45:54 # GRNET in hex :-)
7
MAC2EUI64=/usr/bin/mac2eui64
8
NFDHCPD_STATE_DIR=/var/lib/nfdhcpd
9

  
10
function routed_setup_ipv4 {
11
	# get the link's default gateway
12
	gw=$(ip route list table $LINK | sed -n 's/default via \([^ ]\+\).*/\1/p' | head -1)
13

  
14
	# mangle ARPs to come from the gw's IP
15
	arptables -D OUTPUT -o $INTERFACE --opcode request -j mangle >/dev/null 2>&1
16
	arptables -A OUTPUT -o $INTERFACE --opcode request -j mangle --mangle-ip-s "$gw"
17

  
18
	# route interface to the proper routing table
19
	while ip rule del dev $INTERFACE; do :; done
20
	ip rule add dev $INTERFACE table $LINK
21

  
22
	# static route mapping IP -> INTERFACE
23
	ip route replace $IP table $LINK proto static dev $INTERFACE
24

  
25
	# Enable proxy ARP
26
	echo 1 > /proc/sys/net/ipv4/conf/$INTERFACE/proxy_arp
27
}
28

  
29
function routed_setup_ipv6 {
30
	# Add a routing entry for the eui-64
31
	prefix=$(ip -6 route list table $LINK | awk '/\/64/ {print $1; exit}')
32
	uplink=$(ip -6 route list table $LINK | sed -n 's/default via .* dev \([^ ]\+\).*/\1/p' | head -1)
33
	eui64=$($MAC2EUI64 $MAC $prefix)
34

  
35
	while ip -6 rule del dev $INTERFACE; do :; done
36
	ip -6 rule add dev $INTERFACE table $LINK
37
	ip -6 ro replace $eui64/128 dev $INTERFACE table $LINK
38
	ip -6 neigh add proxy $eui64 dev $uplink
39

  
40
	# disable proxy NDP since we're handling this on userspace
41
	# this should be the default, but better safe than sorry
42
	echo 0 > /proc/sys/net/ipv6/conf/$INTERFACE/proxy_ndp
43
}
44

  
45
# pick a firewall profile per NIC, based on tags (and apply it)
46
function routed_setup_firewall {
47
	ifprefix="synnefo:network:$INTERFACE_INDEX:"
48
	for tag in $TAGS; do
49
		case ${tag#$ifprefix} in
50
		protected)
51
			chain=protected
52
		;;
53
		unprotected)
54
			chain=unprotected
55
		;;
56
		limited)
57
			chain=limited
58
		;;
59
		esac
60
	done
61

  
62
	# Flush any old rules. We have to consider all chains, since
63
	# we are not sure the instance was on the same chain, or had the same
64
	# tap interface.
65
	for oldchain in protected unprotected limited; do
66
		iptables  -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null
67
		ip6tables -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null
68
	done
69

  
70
	if [ "x$chain" != "x" ]; then
71
		iptables  -A FORWARD -o $INTERFACE -j $chain
72
		ip6tables -A FORWARD -o $INTERFACE -j $chain
73
	fi
74
}
75

  
76
function routed_setup_nfdhcpd {
77
	umask 022
78
	cat >$NFDHCPD_STATE_DIR/$INTERFACE <<EOF
79
IP=$IP
80
MAC=$MAC
81
LINK=$LINK
82
HOSTNAME=$INSTANCE
83
TAGS="$TAGS"
84
EOF
85
}
86

  
87
if [ "$MODE" = "routed" ]; then
88
	# special proxy-ARP/NDP routing mode
89

  
90
	# use a constant predefined MAC address for the tap
91
	ip link set $INTERFACE addr $TAP_CONSTANT_MAC
92
	# bring the tap up
93
	ifconfig $INTERFACE 0.0.0.0 up
94

  
95
	# Drop unicast BOOTP/DHCP packets
96
	iptables -D FORWARD -i $INTERFACE -p udp --dport 67 -j DROP 2>/dev/null
97
	iptables -A FORWARD -i $INTERFACE -p udp --dport 67 -j DROP
98

  
99
	routed_setup_ipv4
100
	routed_setup_ipv6
101
	routed_setup_firewall
102
	routed_setup_nfdhcpd
103
elif [ "$MODE" = "bridged" ]; then
104
	ifconfig $INTERFACE 0.0.0.0 up
105
	brctl addif $BRIDGE $INTERFACE
106
	rm -f $NFDHCPD_STATE_DIR/$INTERFACE
107
fi
b/snf-ganeti-tools/setup.py
1
#!/usr/bin/env python
2

  
3
from setuptools import setup
4

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

  
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff