From 2462eab6f02105b15f4686a6183eb043d7b1a5e3 Mon Sep 17 00:00:00 2001 From: Antony Messerli Date: Mon, 20 Dec 2010 10:56:10 -0600 Subject: [PATCH 01/20] initial commit of xenserver host protections --- .../networking/etc/init.d/host-rules | 83 +++++++++++++++++++ .../etc/xensource/scripts/vif_5.6-fp1.patch | 22 +++++ .../etc/xensource/scripts/vif_rules.py | 72 ++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100755 plugins/xenserver/networking/etc/init.d/host-rules create mode 100644 plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch create mode 100755 plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py diff --git a/plugins/xenserver/networking/etc/init.d/host-rules b/plugins/xenserver/networking/etc/init.d/host-rules new file mode 100755 index 0000000000..980396bae4 --- /dev/null +++ b/plugins/xenserver/networking/etc/init.d/host-rules @@ -0,0 +1,83 @@ +#!/bin/bash +# +# host-rules Start/Stop the networking host rules +# +# chkconfig: 2345 85 15 +# description: Networking Host Rules for Multi Tenancy Protections + +iptables-up() +{ + iptables -P FORWARD DROP + iptables -A FORWARD -m physdev --physdev-in eth0 -j ACCEPT + iptables -A FORWARD -m physdev --physdev-in eth1 -j ACCEPT +} + +ebtables-up() +{ + ebtables -P FORWARD DROP + ebtables -A FORWARD -o eth0 -j ACCEPT + ebtables -A FORWARD -o eth1 -j ACCEPT +} + +arptables-up() +{ + arptables -P FORWARD DROP + arptables -A FORWARD --opcode Request --in-interface eth0 -j ACCEPT + arptables -A FORWARD --opcode Reply --in-interface eth0 -j ACCEPT + arptables -A FORWARD --opcode Request --in-interface eth1 -j ACCEPT + arptables -A FORWARD --opcode Reply --in-interface eth1 -j ACCEPT +} + +iptables-down() +{ + iptables -P FORWARD ACCEPT + iptables -D FORWARD -m physdev --physdev-in eth0 -j ACCEPT + iptables -D FORWARD -m physdev --physdev-in eth1 -j ACCEPT +} + +ebtables-down() +{ + ebtables -P FORWARD ACCEPT + ebtables -D FORWARD -o eth0 -j ACCEPT + ebtables -D FORWARD -o eth1 -j ACCEPT +} + +arptables-down() +{ + arptables -P FORWARD ACCEPT + arptables -D FORWARD --opcode Request --in-interface eth0 -j ACCEPT + arptables -D FORWARD --opcode Reply --in-interface eth0 -j ACCEPT + arptables -D FORWARD --opcode Request --in-interface eth1 -j ACCEPT + arptables -D FORWARD --opcode Reply --in-interface eth1 -j ACCEPT +} + +start() +{ + iptables-up + ebtables-up + arptables-up +} + +stop() +{ + iptables-down + ebtables-down + arptables-down +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + start + ;; + *) + echo $"Usage: $0 {start|stop|restart}" + exit 1 +esac +exit 0 diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch b/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch new file mode 100644 index 0000000000..142096ff1d --- /dev/null +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch @@ -0,0 +1,22 @@ +--- vif 2010-12-20 16:39:46.000000000 +0000 ++++ vif_modified 2010-11-19 23:24:37.000000000 +0000 +@@ -213,6 +213,7 @@ + + # xs-xen.pq.hq:91e986b8e49f netback-wait-for-hotplug + xenstore-write "/local/domain/0/backend/vif/${DOMID}/${DEVID}/hotplug-status" "connected" ++ python /etc/xensource/scripts/vif_rules.py ${DOMID} online 2>&1 >> /dev/null + fi + ;; + +@@ -224,9 +225,11 @@ + + remove) + if [ "${TYPE}" = "vif" ] ;then ++ python /etc/xensource/scripts/vif_rules.py ${DOMID} offline 2>&1 >> /dev/null + xenstore-rm "${HOTPLUG}/hotplug" + fi + logger -t scripts-vif "${dev} has been removed" + remove_from_bridge + ;; + esac ++ diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py new file mode 100755 index 0000000000..05141630b3 --- /dev/null +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +from os import system, popen4 +import sys +import simplejson as json +from itertools import chain + +# order is important, mmmkay? 1 is domid, 2 command, 3 is vif +# when we add rules, we delete first, to make sure we only keep the one rule we need + +def main(): + fin,fout = popen4("/usr/bin/xenstore-ls /local/domain/%s/vm-data/networking" % sys.argv[1] ) + macs = fout.read().split("\n")[0:-1] + + for mac in macs: + m = mac.split("=")[0].strip() + fin,fout = popen4("/usr/bin/xenstore-read /local/domain/%s/vm-data/networking/%s" % (sys.argv[1],m)) + mjson = json.loads(fout.read()) + for ip in mjson['ips']: + if mjson["label"] == "public": + label = 0 + else: + label = 1 + + VIF = "vif%s.%s" % (sys.argv[1],label) + + if (len(sys.argv) == 4 and sys.argv[3] == VIF) or (len(sys.argv) == 3): + run_rules( + IP = ip['ip'], + VIF = VIF, + MAC = mjson['mac'], + STATUS = (sys.argv[2] == 'online') and '-A' or '-D' + ) + +def run_rules(**kwargs): + map(system, chain(ebtables(**kwargs), arptables(**kwargs), iptables(**kwargs) )) + +def iptables(**kwargs): + return [ + "/sbin/iptables -D FORWARD -m physdev --physdev-in %s -s %s -j ACCEPT 2>&1 > /dev/null" % ( kwargs['VIF'], kwargs['IP']), + "/sbin/iptables %s FORWARD -m physdev --physdev-in %s -s %s -j ACCEPT" % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP']) + ] + +def arptables(**kwargs): + return [ + "/sbin/arptables -D FORWARD --opcode Request --in-interface %s --source-ip %s --source-mac %s -j ACCEPT 2>&1 > /dev/null" % (kwargs['VIF'], kwargs['IP'], kwargs['MAC']), + "/sbin/arptables -D FORWARD --opcode Reply --in-interface %s --source-ip %s --source-mac %s -j ACCEPT 2>&1 > /dev/null" % (kwargs['VIF'], kwargs['IP'], kwargs['MAC']), + "/sbin/arptables %s FORWARD --opcode Request --in-interface %s --source-ip %s --source-mac %s -j ACCEPT" % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP'], kwargs['MAC']), + "/sbin/arptables %s FORWARD --opcode Reply --in-interface %s --source-ip %s --source-mac %s -j ACCEPT" % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP'], kwargs['MAC']) + ] + +def ebtables(**kwargs): + cmds = [ + "/sbin/ebtables -D FORWARD -p 0806 -o %s --arp-ip-dst %s -j ACCEPT 2>&1 >> /dev/null" % (kwargs['VIF'], kwargs['IP']), + "/sbin/ebtables -D FORWARD -p 0800 -o %s --ip-dst %s -j ACCEPT 2>&1 >> /dev/null" % (kwargs['VIF'], kwargs['IP']), + "/sbin/ebtables %s FORWARD -p 0806 -o %s --arp-ip-dst %s -j ACCEPT 2>&1 " % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP']), + "/sbin/ebtables %s FORWARD -p 0800 -o %s --ip-dst %s -j ACCEPT 2>&1 " % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP']) + ] + if kwargs['STATUS'] == "-A": + cmds.append("/sbin/ebtables -D FORWARD -s ! %s -i %s -j DROP 2>&1 > /dev/null" % (kwargs['MAC'], kwargs['VIF'])) + cmds.append("/sbin/ebtables -I FORWARD 1 -s ! %s -i %s -j DROP" % (kwargs['MAC'], kwargs['VIF'])) + else: + cmds.append("/sbin/ebtables %s FORWARD -s ! %s -i %s -j DROP" % (kwargs['STATUS'], kwargs['MAC'], kwargs['VIF'])) + return cmds + +def usage(): + print "Usage: slice_vifs.py optional: " + +if __name__ == "__main__": + if len(sys.argv) < 3: + usage() + else: + main() From ab0cba603d96e25ee151222bb5fcf550459cfc7a Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Mon, 20 Dec 2010 17:24:08 -0500 Subject: [PATCH 02/20] Rewrite of vif_rules.py to meet coding standards and be more pythonic in general. Use absolute paths for iptables/ebtables/arptables in host-rules. --- .../networking/etc/init.d/host-rules | 54 +++--- .../etc/xensource/scripts/vif_5.6-fp1.patch | 4 +- .../etc/xensource/scripts/vif_rules.py | 164 +++++++++++------- 3 files changed, 138 insertions(+), 84 deletions(-) diff --git a/plugins/xenserver/networking/etc/init.d/host-rules b/plugins/xenserver/networking/etc/init.d/host-rules index 980396bae4..385c596290 100755 --- a/plugins/xenserver/networking/etc/init.d/host-rules +++ b/plugins/xenserver/networking/etc/init.d/host-rules @@ -5,50 +5,54 @@ # chkconfig: 2345 85 15 # description: Networking Host Rules for Multi Tenancy Protections +IPTABLES=/sbin/iptables +EBTABLES=/sbin/ebtables +ARPTABLES=/sbin/arptables + iptables-up() { - iptables -P FORWARD DROP - iptables -A FORWARD -m physdev --physdev-in eth0 -j ACCEPT - iptables -A FORWARD -m physdev --physdev-in eth1 -j ACCEPT + $IPTABLES -P FORWARD DROP + $IPTABLES -A FORWARD -m physdev --physdev-in eth0 -j ACCEPT + $IPTABLES -A FORWARD -m physdev --physdev-in eth1 -j ACCEPT } ebtables-up() { - ebtables -P FORWARD DROP - ebtables -A FORWARD -o eth0 -j ACCEPT - ebtables -A FORWARD -o eth1 -j ACCEPT + $EBTABLES -P FORWARD DROP + $EBTABLES -A FORWARD -o eth0 -j ACCEPT + $EBTABLES -A FORWARD -o eth1 -j ACCEPT } arptables-up() { - arptables -P FORWARD DROP - arptables -A FORWARD --opcode Request --in-interface eth0 -j ACCEPT - arptables -A FORWARD --opcode Reply --in-interface eth0 -j ACCEPT - arptables -A FORWARD --opcode Request --in-interface eth1 -j ACCEPT - arptables -A FORWARD --opcode Reply --in-interface eth1 -j ACCEPT + $ARPTABLES -P FORWARD DROP + $ARPTABLES -A FORWARD --opcode Request --in-interface eth0 -j ACCEPT + $ARPTABLES -A FORWARD --opcode Reply --in-interface eth0 -j ACCEPT + $ARPTABLES -A FORWARD --opcode Request --in-interface eth1 -j ACCEPT + $ARPTABLES -A FORWARD --opcode Reply --in-interface eth1 -j ACCEPT } iptables-down() { - iptables -P FORWARD ACCEPT - iptables -D FORWARD -m physdev --physdev-in eth0 -j ACCEPT - iptables -D FORWARD -m physdev --physdev-in eth1 -j ACCEPT + $IPTABLES -P FORWARD ACCEPT + $IPTABLES -D FORWARD -m physdev --physdev-in eth0 -j ACCEPT + $IPTABLES -D FORWARD -m physdev --physdev-in eth1 -j ACCEPT } ebtables-down() { - ebtables -P FORWARD ACCEPT - ebtables -D FORWARD -o eth0 -j ACCEPT - ebtables -D FORWARD -o eth1 -j ACCEPT + $EBTABLES -P FORWARD ACCEPT + $EBTABLES -D FORWARD -o eth0 -j ACCEPT + $EBTABLES -D FORWARD -o eth1 -j ACCEPT } arptables-down() { - arptables -P FORWARD ACCEPT - arptables -D FORWARD --opcode Request --in-interface eth0 -j ACCEPT - arptables -D FORWARD --opcode Reply --in-interface eth0 -j ACCEPT - arptables -D FORWARD --opcode Request --in-interface eth1 -j ACCEPT - arptables -D FORWARD --opcode Reply --in-interface eth1 -j ACCEPT + $ARPTABLES -P FORWARD ACCEPT + $ARPTABLES -D FORWARD --opcode Request --in-interface eth0 -j ACCEPT + $ARPTABLES -D FORWARD --opcode Reply --in-interface eth0 -j ACCEPT + $ARPTABLES -D FORWARD --opcode Request --in-interface eth1 -j ACCEPT + $ARPTABLES -D FORWARD --opcode Reply --in-interface eth1 -j ACCEPT } start() @@ -68,16 +72,20 @@ stop() case "$1" in start) start + RETVAL=$? ;; stop) stop + RETVAL=$? ;; restart) stop start + RETVAL=$? ;; *) echo $"Usage: $0 {start|stop|restart}" exit 1 + ;; esac -exit 0 +exit $RETVAL diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch b/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch index 142096ff1d..feaf1312dd 100644 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch @@ -4,7 +4,7 @@ # xs-xen.pq.hq:91e986b8e49f netback-wait-for-hotplug xenstore-write "/local/domain/0/backend/vif/${DOMID}/${DEVID}/hotplug-status" "connected" -+ python /etc/xensource/scripts/vif_rules.py ${DOMID} online 2>&1 >> /dev/null ++ python /etc/xensource/scripts/vif_rules.py ${DOMID} online 2>&1 > /dev/null fi ;; @@ -12,7 +12,7 @@ remove) if [ "${TYPE}" = "vif" ] ;then -+ python /etc/xensource/scripts/vif_rules.py ${DOMID} offline 2>&1 >> /dev/null ++ python /etc/xensource/scripts/vif_rules.py ${DOMID} offline 2>&1 > /dev/null xenstore-rm "${HOTPLUG}/hotplug" fi logger -t scripts-vif "${dev} has been removed" diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py index 05141630b3..dd27d3c6b7 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py @@ -1,72 +1,118 @@ #!/usr/bin/env python -from os import system, popen4 +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +This script is used to configure iptables, ebtables, and arptables rules for +XenServer instances. +""" + +import os +import subprocess import sys + +# This is written to Python 2.4, since that is what is available on XenServer import simplejson as json -from itertools import chain -# order is important, mmmkay? 1 is domid, 2 command, 3 is vif -# when we add rules, we delete first, to make sure we only keep the one rule we need -def main(): - fin,fout = popen4("/usr/bin/xenstore-ls /local/domain/%s/vm-data/networking" % sys.argv[1] ) - macs = fout.read().split("\n")[0:-1] +def main(dom_id, command, only_this_vif=None): + xsls = execute("/usr/bin/xenstore-ls /local/domain/%s/vm-data/networking" \ + % dom_id, True) + macs = [line.split("=")[0].strip() for line in xsls.splitlines()] - for mac in macs: - m = mac.split("=")[0].strip() - fin,fout = popen4("/usr/bin/xenstore-read /local/domain/%s/vm-data/networking/%s" % (sys.argv[1],m)) - mjson = json.loads(fout.read()) - for ip in mjson['ips']: - if mjson["label"] == "public": - label = 0 - else: - label = 1 + for mac in macs: + xsr = "/usr/bin/xenstore-read /local/domain/%s/vm-data/networking/%s" + xsread = execute(xsr % (dom_id, mac), True) + data = json.loads(xsread) + for ip in data['ips']: + if data["label"] == "public": + vif = "vif%s.0" % dom_id + else: + vif = "vif%s.1" % dom_id - VIF = "vif%s.%s" % (sys.argv[1],label) - - if (len(sys.argv) == 4 and sys.argv[3] == VIF) or (len(sys.argv) == 3): - run_rules( - IP = ip['ip'], - VIF = VIF, - MAC = mjson['mac'], - STATUS = (sys.argv[2] == 'online') and '-A' or '-D' - ) + if (only_this_vif is None) or (vif == only_this_vif): + params = dict(IP=ip['ip'], VIF=vif, MAC=data['mac']) + apply_ebtables_rules(command, params) + apply_arptables_rules(command, params) + apply_iptables_rules(command, params) -def run_rules(**kwargs): - map(system, chain(ebtables(**kwargs), arptables(**kwargs), iptables(**kwargs) )) -def iptables(**kwargs): - return [ - "/sbin/iptables -D FORWARD -m physdev --physdev-in %s -s %s -j ACCEPT 2>&1 > /dev/null" % ( kwargs['VIF'], kwargs['IP']), - "/sbin/iptables %s FORWARD -m physdev --physdev-in %s -s %s -j ACCEPT" % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP']) - ] - -def arptables(**kwargs): - return [ - "/sbin/arptables -D FORWARD --opcode Request --in-interface %s --source-ip %s --source-mac %s -j ACCEPT 2>&1 > /dev/null" % (kwargs['VIF'], kwargs['IP'], kwargs['MAC']), - "/sbin/arptables -D FORWARD --opcode Reply --in-interface %s --source-ip %s --source-mac %s -j ACCEPT 2>&1 > /dev/null" % (kwargs['VIF'], kwargs['IP'], kwargs['MAC']), - "/sbin/arptables %s FORWARD --opcode Request --in-interface %s --source-ip %s --source-mac %s -j ACCEPT" % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP'], kwargs['MAC']), - "/sbin/arptables %s FORWARD --opcode Reply --in-interface %s --source-ip %s --source-mac %s -j ACCEPT" % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP'], kwargs['MAC']) - ] +def execute(command, return_stdout=False): + devnull = open(os.devnull, 'w') + proc = subprocess.Popen(command, shell=True, close_fds=True, + stdout=subprocess.PIPE, stderr=devnull) + if return_stdout: + return proc.stdout.read() + else: + return None -def ebtables(**kwargs): - cmds = [ - "/sbin/ebtables -D FORWARD -p 0806 -o %s --arp-ip-dst %s -j ACCEPT 2>&1 >> /dev/null" % (kwargs['VIF'], kwargs['IP']), - "/sbin/ebtables -D FORWARD -p 0800 -o %s --ip-dst %s -j ACCEPT 2>&1 >> /dev/null" % (kwargs['VIF'], kwargs['IP']), - "/sbin/ebtables %s FORWARD -p 0806 -o %s --arp-ip-dst %s -j ACCEPT 2>&1 " % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP']), - "/sbin/ebtables %s FORWARD -p 0800 -o %s --ip-dst %s -j ACCEPT 2>&1 " % (kwargs['STATUS'], kwargs['VIF'], kwargs['IP']) - ] - if kwargs['STATUS'] == "-A": - cmds.append("/sbin/ebtables -D FORWARD -s ! %s -i %s -j DROP 2>&1 > /dev/null" % (kwargs['MAC'], kwargs['VIF'])) - cmds.append("/sbin/ebtables -I FORWARD 1 -s ! %s -i %s -j DROP" % (kwargs['MAC'], kwargs['VIF'])) - else: - cmds.append("/sbin/ebtables %s FORWARD -s ! %s -i %s -j DROP" % (kwargs['STATUS'], kwargs['MAC'], kwargs['VIF'])) - return cmds +# A note about adding rules: +# Whenever we add any rule to iptables, arptables or ebtables we first +# delete the same rule to ensure the rule only exists once. + + +def apply_iptables_rules(command, params): + iptables = lambda rule: execute("/sbin/iptables %s" % rule) + + iptables("-D FORWARD -m physdev --physdev-in %(VIF)s -s %(IP)s \ + -j ACCEPT" % params) + if command == 'online': + iptables("-A FORWARD -m physdev --physdev-in %(VIF)s -s %(IP)s \ + -j ACCEPT" % params) + + +def apply_arptables_rules(command, params): + arptables = lambda rule: execute("/sbin/arptables %s" % rule) + + arptables("-D FORWARD --opcode Request --in-interface %(VIF)s \ + --source-ip %(IP)s --source-mac %(MAC)s -j ACCEPT" % params) + arptables("-D FORWARD --opcode Reply --in-interface %(VIF)s \ + --source-ip %(IP)s --source-mac %(MAC)s -j ACCEPT" % params) + if command == 'online': + arptables("-A FORWARD --opcode Request --in-interface %(VIF)s \ + --source-ip %(IP)s --source-mac %(MAC)s -j ACCEPT" % params) + arptables("-A FORWARD --opcode Reply --in-interface %(VIF)s \ + --source-ip %(IP)s --source-mac %(MAC)s -j ACCEPT" % params) + + +def apply_ebtables_rules(command, params): + ebtables = lambda rule: execute("/sbin/ebtables %s" % rule) + + ebtables("-D FORWARD -p 0806 -o %(VIF)s --arp-ip-dst %(IP)s -j ACCEPT" % + params) + ebtables("-D FORWARD -p 0800 -o %(VIF)s --ip-dst %(IP)s -j ACCEPT" % + params) + if command == 'online': + ebtables("-A FORWARD -p 0806 -o %(VIF)s --arp-ip-dst %(IP)s \ + -j ACCEPT" % params) + ebtables("-A FORWARD -p 0800 -o %(VIF)s --ip-dst %(IP)s \ + -j ACCEPT" % params) + + ebtables("-D FORWARD -s ! %(MAC)s -i %(VIF)s -j DROP" % params) + if command == 'online': + ebtables("-I FORWARD 1 -s ! %(MAC)s -i %(VIF)s -j DROP" % params) -def usage(): - print "Usage: slice_vifs.py optional: " if __name__ == "__main__": - if len(sys.argv) < 3: - usage() - else: - main() + if len(sys.argv) < 3: + print "usage: %s dom_id online|offline [vif]" % \ + os.path.basename(sys.argv[0]) + sys.exit(1) + else: + dom_id, command = sys.argv[1:3] + vif = len(sys.argv) == 4 and sys.argv[3] or None + main(dom_id, command, vif) From 1b47ef95fff4d8419e27a7cc247178806cc065ff Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Mon, 20 Dec 2010 18:15:40 -0500 Subject: [PATCH 03/20] Close devnull filehandle --- .../xenserver/networking/etc/xensource/scripts/vif_rules.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py index dd27d3c6b7..d60816ce76 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py @@ -17,8 +17,8 @@ # under the License. """ -This script is used to configure iptables, ebtables, and arptables rules for -XenServer instances. +This script is used to configure iptables, ebtables, and arptables rules on +XenServer hosts. """ import os @@ -55,6 +55,7 @@ def execute(command, return_stdout=False): devnull = open(os.devnull, 'w') proc = subprocess.Popen(command, shell=True, close_fds=True, stdout=subprocess.PIPE, stderr=devnull) + devnull.close() if return_stdout: return proc.stdout.read() else: From 130d75a8b240068a6251188da68296496c2c1564 Mon Sep 17 00:00:00 2001 From: Antony Messerli Date: Wed, 22 Dec 2010 11:27:23 -0600 Subject: [PATCH 04/20] Moved xenapi into xenserver specific directory --- plugins/{ => xenserver}/xenapi/README | 0 plugins/{ => xenserver}/xenapi/etc/xapi.d/plugins/objectstore | 0 .../{ => xenserver}/xenapi/etc/xapi.d/plugins/pluginlib_nova.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename plugins/{ => xenserver}/xenapi/README (100%) rename plugins/{ => xenserver}/xenapi/etc/xapi.d/plugins/objectstore (100%) rename plugins/{ => xenserver}/xenapi/etc/xapi.d/plugins/pluginlib_nova.py (100%) diff --git a/plugins/xenapi/README b/plugins/xenserver/xenapi/README similarity index 100% rename from plugins/xenapi/README rename to plugins/xenserver/xenapi/README diff --git a/plugins/xenapi/etc/xapi.d/plugins/objectstore b/plugins/xenserver/xenapi/etc/xapi.d/plugins/objectstore similarity index 100% rename from plugins/xenapi/etc/xapi.d/plugins/objectstore rename to plugins/xenserver/xenapi/etc/xapi.d/plugins/objectstore diff --git a/plugins/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py similarity index 100% rename from plugins/xenapi/etc/xapi.d/plugins/pluginlib_nova.py rename to plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py From a653173c75fdd3810ce75c3d5de5ea491d5d6922 Mon Sep 17 00:00:00 2001 From: Antony Messerli Date: Wed, 22 Dec 2010 11:28:08 -0600 Subject: [PATCH 05/20] Added networking protections readme --- plugins/xenserver/networking/README | 126 ++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 plugins/xenserver/networking/README diff --git a/plugins/xenserver/networking/README b/plugins/xenserver/networking/README new file mode 100644 index 0000000000..b59de16b8b --- /dev/null +++ b/plugins/xenserver/networking/README @@ -0,0 +1,126 @@ +Multi Tenancy Networking Protections in XenServer +================================================= + +The purpose of the vif_rules script is to allow multi-tenancy on a XenServer +host. In a multi-tenant cloud environment a host machine needs to be able to +enforce network isolation amongst guest instances, at both layer two and layer +three. The rules prevent guests from taking and using unauthorized IP addresses, +sniffing other guests traffic, and prevents ARP poisoning attacks. This current +revision only supports IPv4, but will support IPv6 in the future. + +Kernel Requirements +=================== + +- physdev module +- arptables support +- ebtables support +- iptables support + +If the kernel doesn't support these, you will need to obtain the Source RPMS for +the proper version of XenServer to recompile the dom0 kernel. + +XenServer Requirements (32-bit dom0) +==================================== + +- arptables 32-bit rpm +- ebtables 32-bit rpm +- python-simplejson + +XenServer Environment Specific Notes +==================================== + +- XenServer 5.5 U1 based on the 2.6.18 kernel didn't include physdev module + support. Support for this had to be recompiled into the kernel. +- XenServer 5.6 based on the 2.6.27 kernel didn't include physdev, ebtables, or + arptables. +- XenServer 5.6 FP1 didn't include physdev, ebtables, or arptables but they do + have a Cloud Supplemental pack available to partners which swaps out the + kernels for kernels that support the networking rules. + +How it works - tl;dr +==================== + +iptables, ebtables, and arptables drop rules are applied to all forward chains +on the host. These are applied at boot time with an init script. They ensure +all forwarded packets are dropped by default. Allow rules are then applied to +the instances to ensure they have permission to talk on the internet. + +How it works - Long +=================== + +Any time an underprivileged domain or domU is started or stopped, it gets a +unique domain id (dom_id). This dom_id is utilized in a number of places, one +of which is it's assigned to the virtual interface (vif). The vifs are attached +to the bridge that is attached to the physical network. For instance, if you +had a public bridge attached to eth0 and your domain id was 5, your vif would be +vif5.0. + +The networking rules are applied to the VIF directly so they apply at the lowest +level of the networking stack. Because the VIF changes along with the domain id +on any start, stop, or reboot of the instance, the rules need to be removed and +re-added any time that occurs. + +Because the dom_id can change often, the vif_rules script is hooked into the +/etc/xensource/scripts/vif script that gets called anytime an instance is +started, or stopped, which includes pauses and resumes. + +Examples of the rules ran for the host on boot: + +iptables -P FORWARD DROP iptables -A FORWARD -m physdev --physdev-in eth0 -j +ACCEPT ebtables -P FORWARD DROP ebtables -A FORWARD -o eth0 -j ACCEPT arptables +-P FORWARD DROP arptables -A FORWARD --opcode Request --in-interface eth0 -j +ACCEPT arptables -A FORWARD --opcode Reply --in-interface eth0 -j ACCEPT + +Examples of the rules that are ran per instance state change: + +iptables -A FORWARD -m physdev --physdev-in vif1.0 -s 10.1.135.22/32 -j ACCEPT +arptables -A FORWARD --opcode Request --in-interface "vif1.0" --source-ip +10.1.135.22 -j ACCEPT arptables -A FORWARD --opcode Reply --in-interface +"vif1.0" --source-ip 10.1.135.22 --source-mac 9e:6e:cc:19:7f:fe -j ACCEPT +ebtables -A FORWARD -p 0806 -o vif1.0 --arp-ip-dst 10.1.135.22 -j ACCEPT +ebtables -A FORWARD -p 0800 -o vif1.0 --ip-dst 10.1.135.22 -j ACCEPT ebtables -I +FORWARD 1 -s ! 9e:6e:cc:19:7f:fe -i vif1.0 -j DROP + +Typically when you see a vif, it'll look like vif.. +vif2.1 for example would be domain 2 on the second interface. + +The vif_rules.py script needs to pull information about the IPs and MAC +addresses assigned to the instance. The current implementation assumes that +information is put into the VM Record into the xenstore-data key in a JSON +string. The vif_rules.py script reads out of the JSON string to determine the +IPs, and MAC addresses to protect. + +An example format is given below: + +xe vm-param-get uuid= param-name=xenstore-data xenstore-data (MRW): +vm-data/networking/4040fa7292e4: +{"label":"public","ips":[{"netmask":"255.255.255.0", "enabled":"1", +"ip":"173.200.100.10"}], "mac":"40:40:fa:72:92:e4", "gateway":"173.200.100.1", +"vm_id":"123456", "dns":["72.3.128.240","72.3.128.241"]}; +vm-data/networking/40402321c9b8: +{"label":"private","ips":[{"netmask":"255.255.224.0","enabled":"1", +"ip":"10.177.10.10"}], "routes":[{"route":"10.176.0.0","netmask":"255.248.0.0", +"gateway":"10.177.10.1"}, {"route":"10.191.192.0", "netmask":"255.255.192.0", +"gateway":"10.177.10.1"}], "mac":"40:40:23:21:c9:b8"} + +The key is used for two purposes. One, the vif_rules.py script will read from +it to apply the rules needed after parsing the JSON. The second is that because +it's put into the xenstore-data field, the xenstore will be populated with this +data on boot. This allows a guest agent the ability to read out data about the +instance and apply configurations as needed. + +Installation +============ + +- Copy host-rules into /etc/init.d/ and make sure to chmod +x host-rules. +- Run 'chkconfig host-rules on' to add the init script to start up. +- Copy vif_rules.py into /etc/xensource/scripts +- Patch /etc/xensource/scripts/vif using the supplied patch file. It may vary + for different versions of XenServer but it should be pretty self explanitory. + It calls the vif_rules.py script on domain creation and tear down. +- Run '/etc/init.d/host-rules start' to start up the host based rules. +- The instance rules will then fire on creation of the VM as long as the correct + JSON is in place. +- You can check to see if the rules are in place with: iptables --list, + arptables --list, or ebtables --list + From 1f9f997ae342ea16d0640c3e406402950b1d2a9b Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Wed, 22 Dec 2010 13:27:51 -0500 Subject: [PATCH 06/20] Adding myself and Antony Messerli to the Authors file --- Authors | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Authors b/Authors index fa38ef0b1b..6e873acf31 100644 --- a/Authors +++ b/Authors @@ -1,9 +1,11 @@ Andy Smith Anne Gentle Anthony Young +Antony Messerli Armando Migliaccio Chris Behrens Chmouel Boudjnah +Cory Wright Dean Troyer Devin Carlen Ed Leafe From 59c3e5bf0dda0c0c1b77307a339f3102c7179885 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 23 Dec 2010 18:09:52 -0600 Subject: [PATCH 07/20] Faked out handling for shared ip groups so they return something --- nova/api/openstack/ratelimiting/__init__.py | 4 ++-- nova/api/openstack/sharedipgroups.py | 22 ++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 91a8b2e558..cbb4b897eb 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -64,9 +64,9 @@ class RateLimitingMiddleware(wsgi.Middleware): If the request should be rate limited, return a 413 status with a Retry-After header giving the time when the request would succeed. """ - return self.limited_request(req, self.application) + return self.rate_limited_request(req, self.application) - def limited_request(self, req, application): + def rate_limited_request(self, req, application): """Rate limit the request. If the request should be rate limited, return a 413 status with a diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 75d02905c2..4f4bb10163 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +from webob import exc + from nova import wsgi @@ -22,19 +24,25 @@ class Controller(wsgi.Controller): """ The Shared IP Groups Controller for the Openstack API """ def index(self, req): - raise NotImplementedError + """ Returns a list of Shared IP Groups for the user """ + return dict(sharedipgroups=[]) def show(self, req, id): - raise NotImplementedError + """ Shows in-depth information on a specific Shared IP Group """ + return dict(sharedipgroup={}) def update(self, req, id): - raise NotImplementedError + """ You can't update a Shared IP Group """ + raise faults.Fault(exc.HTTPNotImplemented()) def delete(self, req, id): - raise NotImplementedError + """ Deletes a Shared IP Group """ + raise faults.Fault(exc.HTTPNotFound()) - def detail(self, req): - raise NotImplementedError + def detail(self, req, id): + """ Returns a complete list of Shared IP Groups """ + return dict(sharedipgroups=[]) def create(self, req): - raise NotImplementedError + """ Creates a new Shared IP group """ + raise faults.Fault(exc.HTTPNotFound()) From 002bbfa7a648a1117e14713eab3ee3ee4b2b6d8e Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Mon, 27 Dec 2010 12:06:36 -0500 Subject: [PATCH 08/20] Moving README to doc/networking.rst per recommendation from Jay Pipes --- plugins/xenserver/{networking/README => doc/networking.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/xenserver/{networking/README => doc/networking.rst} (100%) diff --git a/plugins/xenserver/networking/README b/plugins/xenserver/doc/networking.rst similarity index 100% rename from plugins/xenserver/networking/README rename to plugins/xenserver/doc/networking.rst From 3490fde00fd8bfb00834b1085de62d86c9c9d061 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 12:08:22 -0600 Subject: [PATCH 09/20] A few fixes --- nova/api/openstack/backup_schedules.py | 5 ++++- nova/api/openstack/servers.py | 3 ++- nova/api/openstack/sharedipgroups.py | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index fc70b5c6c8..c484023e08 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -24,12 +24,14 @@ import nova.image.service class Controller(wsgi.Controller): + """ The backup schedule API controller for the Openstack API """ def __init__(self): pass def index(self, req, server_id): - return faults.Fault(exc.HTTPNotFound()) + """ Returns the list of backup schedules for a given instance """ + return dict(backup_schedules=[]) def create(self, req, server_id): """ No actual update method required, since the existing API allows @@ -37,4 +39,5 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) def delete(self, req, server_id, id): + """ Deletes an existing backup schedule """ return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d60e2cabb..c8851387d7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -43,6 +43,7 @@ def _entity_list(entities): def _entity_detail(inst): """ Maps everything to Rackspace-like attributes for return""" power_mapping = { + None: 'build', power_state.NOSTATE: 'build', power_state.RUNNING: 'active', power_state.BLOCKED: 'active', @@ -153,7 +154,7 @@ class Controller(wsgi.Controller): try: self.compute_api.update_instance(req.environ['nova.context'], - instance['id'], + inst_dict['id'], **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 4f4bb10163..7e98b07127 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -18,6 +18,7 @@ from webob import exc from nova import wsgi +from nova.api.openstack import faults class Controller(wsgi.Controller): From b879c746049241837af3785adc3068fbe35f199d Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 13:20:37 -0600 Subject: [PATCH 10/20] backup schedule changes --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/backup_schedules.py | 10 +++++++++- nova/api/openstack/servers.py | 6 ++++-- nova/api/openstack/sharedipgroups.py | 27 +++++++++++++++++++++++--- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c49399f281..1f78820c8d 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -98,7 +98,7 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, member=server_members) - mapper.resource("backup_schedule", "backup_schedules", + mapper.resource("backup_schedule", "backup_schedule", controller=backup_schedules.Controller(), parent_resource=dict(member_name='server', collection_name='servers')) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index c484023e08..a8b0fbbb91 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -22,16 +22,24 @@ from nova import wsgi from nova.api.openstack import faults import nova.image.service +def _entity_inst(inst): + """ Coerces the backup schedule into proper dictionary format """ + return dict(backupSchedule=inst) class Controller(wsgi.Controller): """ The backup schedule API controller for the Openstack API """ + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'backupSchedule': []}}} + def __init__(self): pass def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ - return dict(backup_schedules=[]) + return _entity_inst({}) def create(self, req, server_id): """ No actual update method required, since the existing API allows diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c8851387d7..7abf5ec531 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -153,8 +153,10 @@ class Controller(wsgi.Controller): update_dict['display_name'] = inst_dict['server']['name'] try: - self.compute_api.update_instance(req.environ['nova.context'], - inst_dict['id'], + ctxt = req.environ['nova.context'] + inst_ref = self.compute_api.get_instance(ctxt, id) + self.compute_api.update_instance(ctxt, + id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 7e98b07127..32ebfa45af 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -21,16 +21,37 @@ from nova import wsgi from nova.api.openstack import faults +def _entity_list(entities): + """ Coerces a list of shared IP groups into proper dictionary format """ + return dict(sharedIpGroups=entities) + + +def _entity_inst(inst): + """ Coerces a shared IP group instance into proper dictionary format """ + return dict(sharedIpGroup=inst) + + +def _entity_detail(inst): + """ Coerces a shared IP group instance into proper dictionary format with + correctly mapped attributes """ + return dict(sharedIpGroup=inst) + + class Controller(wsgi.Controller): """ The Shared IP Groups Controller for the Openstack API """ + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'sharedIpGroup': []}}} + def index(self, req): """ Returns a list of Shared IP Groups for the user """ - return dict(sharedipgroups=[]) + return _entity_list([]) def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - return dict(sharedipgroup={}) + return _entity_inst({}) def update(self, req, id): """ You can't update a Shared IP Group """ @@ -42,7 +63,7 @@ class Controller(wsgi.Controller): def detail(self, req, id): """ Returns a complete list of Shared IP Groups """ - return dict(sharedipgroups=[]) + return _entity_detail({}) def create(self, req): """ Creates a new Shared IP group """ From 431c54ba76a2a85ff55658c571f68378b47ce39d Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 17:29:45 -0600 Subject: [PATCH 11/20] Renamed based on feedback from another branch --- nova/api/openstack/backup_schedules.py | 2 ++ nova/api/openstack/servers.py | 27 ++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index a8b0fbbb91..9b8e605f69 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -22,10 +22,12 @@ from nova import wsgi from nova.api.openstack import faults import nova.image.service + def _entity_inst(inst): """ Coerces the backup schedule into proper dictionary format """ return dict(backupSchedule=inst) + class Controller(wsgi.Controller): """ The backup schedule API controller for the Openstack API """ diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7abf5ec531..9369f6516d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -35,13 +35,9 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) -def _entity_list(entities): - """ Coerces a list of servers into proper dictionary format """ - return dict(servers=entities) - - -def _entity_detail(inst): - """ Maps everything to Rackspace-like attributes for return""" +def _translate_detail_keys(inst): + """ Coerces into dictionary format, mapping everything to Rackspace-like + attributes for return""" power_mapping = { None: 'build', power_state.NOSTATE: 'build', @@ -67,8 +63,9 @@ def _entity_detail(inst): return dict(server=inst_dict) -def _entity_inst(inst): - """ Filters all model attributes save for id and name """ +def _translate_keys(inst): + """ Coerces into dictionary format, excluding all model attributes + save for id and name """ return dict(server=dict(id=inst['internal_id'], name=inst['display_name'])) @@ -87,29 +84,29 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - return self._items(req, entity_maker=_entity_inst) + return self._items(req, entity_maker=_translte_keys) def detail(self, req): """ Returns a list of server details for a given user """ - return self._items(req, entity_maker=_entity_detail) + return self._items(req, entity_maker=_translate_detail_keys) def _items(self, req, entity_maker): """Returns a list of servers for a given user. - entity_maker - either _entity_detail or _entity_inst + entity_maker - either _translate_detail_keys or _translate_keys """ instance_list = self.compute_api.get_instances( req.environ['nova.context']) limited_list = common.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] - return _entity_list(res) + return dict(servers=res) def show(self, req, id): """ Returns server details by server id """ try: instance = self.compute_api.get_instance( req.environ['nova.context'], int(id)) - return _entity_detail(instance) + return _translate_detail_keys(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -138,7 +135,7 @@ class Controller(wsgi.Controller): description=env['server']['name'], key_name=key_pair['name'], key_data=key_pair['public_key']) - return _entity_inst(instances[0]) + return _translate_keys(instances[0]) def update(self, req, id): """ Updates the server name or password """ From 31d3aed581302e73b3f155b1dd72586324433e91 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Mon, 27 Dec 2010 17:35:59 -0600 Subject: [PATCH 12/20] Typo fix --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9369f6516d..d188895639 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -84,7 +84,7 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - return self._items(req, entity_maker=_translte_keys) + return self._items(req, entity_maker=_translate_keys) def detail(self, req): """ Returns a list of server details for a given user """ From eab0ce934e6296910b26c087e4268a65dc233a55 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 28 Dec 2010 01:37:04 +0000 Subject: [PATCH 13/20] Bug #694880: nova-compute now depends upon Cheetah even when not using libvirt Only import Cheetah when needed, as we do already with libvirt and libxml2. This ensures that users of other virt backends don't need Cheetah to run nova-compute. --- nova/tests/test_virt.py | 1 + nova/virt/libvirt_conn.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1c155abe4f..4aa489d08f 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -33,6 +33,7 @@ flags.DECLARE('instances_path', 'nova.compute.manager') class LibvirtConnTestCase(test.TestCase): def setUp(self): super(LibvirtConnTestCase, self).setUp() + libvirt_conn._late_load_cheetah() self.flags(fake_call=True) self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake', diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 67b9fc47e5..8b67b937f4 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -58,10 +58,9 @@ from nova.compute import instance_types from nova.compute import power_state from nova.virt import images -from Cheetah.Template import Template - libvirt = None libxml2 = None +Template = None FLAGS = flags.FLAGS @@ -88,14 +87,23 @@ flags.DEFINE_bool('allow_project_net_traffic', def get_connection(read_only): # These are loaded late so that there's no need to install these # libraries when not using libvirt. + # Cheetah is separate because the unit tests want to load Cheetah, + # but not libvirt. global libvirt global libxml2 if libvirt is None: libvirt = __import__('libvirt') if libxml2 is None: libxml2 = __import__('libxml2') + _late_load_cheetah() return LibvirtConnection(read_only) +def _late_load_cheetah(): + global Template + if Template is None: + t = __import__('Cheetah.Template', globals(), locals(), ['Template'], + -1) + Template = t.Template def _get_net_and_mask(cidr): net = IPy.IP(cidr) From 8d03539e15bf4ac168af550e27a81353a896df54 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Tue, 28 Dec 2010 16:35:56 -0500 Subject: [PATCH 14/20] Update .mailmap with both email addresses for Ant and myself --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 9ab7db743f..9e7fb1ec0f 100644 --- a/.mailmap +++ b/.mailmap @@ -27,3 +27,5 @@ + + From 902df6eb4968743dd451e54cde27ce88fc83ddaa Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Dec 2010 15:48:48 -0600 Subject: [PATCH 15/20] Whoops --- nova/api/openstack/backup_schedules.py | 4 ++-- nova/api/openstack/sharedipgroups.py | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 9b8e605f69..fcc07bdd37 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -23,7 +23,7 @@ from nova.api.openstack import faults import nova.image.service -def _entity_inst(inst): +def _translate_keys(inst): """ Coerces the backup schedule into proper dictionary format """ return dict(backupSchedule=inst) @@ -41,7 +41,7 @@ class Controller(wsgi.Controller): def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ - return _entity_inst({}) + return _translate_keys({}) def create(self, req, server_id): """ No actual update method required, since the existing API allows diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 32ebfa45af..845f5beada 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -21,17 +21,12 @@ from nova import wsgi from nova.api.openstack import faults -def _entity_list(entities): - """ Coerces a list of shared IP groups into proper dictionary format """ - return dict(sharedIpGroups=entities) - - -def _entity_inst(inst): +def _translate_keys(inst): """ Coerces a shared IP group instance into proper dictionary format """ return dict(sharedIpGroup=inst) -def _entity_detail(inst): +def _translate_detail_keys(inst): """ Coerces a shared IP group instance into proper dictionary format with correctly mapped attributes """ return dict(sharedIpGroup=inst) @@ -47,11 +42,11 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of Shared IP Groups for the user """ - return _entity_list([]) + return dict(sharedIpGroups=[]) def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - return _entity_inst({}) + return _translate_keys({}) def update(self, req, id): """ You can't update a Shared IP Group """ @@ -63,7 +58,7 @@ class Controller(wsgi.Controller): def detail(self, req, id): """ Returns a complete list of Shared IP Groups """ - return _entity_detail({}) + return _translate_detail_keys({}) def create(self, req): """ Creates a new Shared IP group """ From 8ee15b6e93b5666b4645f2cefef357b0af3d26d0 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 28 Dec 2010 23:25:50 +0100 Subject: [PATCH 16/20] Address bug #695157 by using a blank request class and setting an empty request path. --- nova/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/wsgi.py b/nova/wsgi.py index c7ee9ed146..b5d6b96c15 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -270,7 +270,7 @@ class Serializer(object): needed to serialize a dictionary to that type. """ self.metadata = metadata or {} - req = webob.Request(environ) + req = webob.Request.blank('', environ) suffix = req.path_info.split('.')[-1].lower() if suffix == 'json': self.handler = self._to_json From 99a228a8ef3ee2760774fbafd136f137bd578dba Mon Sep 17 00:00:00 2001 From: Cerberus Date: Tue, 28 Dec 2010 17:34:51 -0600 Subject: [PATCH 17/20] removed superfluous line --- nova/api/openstack/servers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2232d24b2b..8451832582 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -152,7 +152,6 @@ class Controller(wsgi.Controller): try: ctxt = req.environ['nova.context'] - inst_ref = self.compute_api.get_instance(ctxt, id) self.compute_api.update_instance(ctxt, id, **update_dict) From 380b28f89481c52dbcda0b54fd7409b6bc72bb56 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Wed, 29 Dec 2010 01:58:04 +0000 Subject: [PATCH 18/20] Fix pep8 violations. --- nova/virt/libvirt_conn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8b67b937f4..e9c6c27131 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -98,6 +98,7 @@ def get_connection(read_only): _late_load_cheetah() return LibvirtConnection(read_only) + def _late_load_cheetah(): global Template if Template is None: @@ -105,6 +106,7 @@ def _late_load_cheetah(): -1) Template = t.Template + def _get_net_and_mask(cidr): net = IPy.IP(cidr) return str(net.net()), str(net.netmask()) From 34f5bed4d9c99af58c83b82b499f898c270124a8 Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Wed, 29 Dec 2010 11:24:42 -0800 Subject: [PATCH 19/20] Re-added flag definition for injected_network_template. Tested & verified fix in the same env as the original bug. --- nova/virt/libvirt_conn.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f6a218fa43..39215c4e18 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -68,6 +68,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image') flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image') +flags.DEFINE_string('injected_network_template', + utils.abspath('virt/interfaces.template'), + 'Template file for injected network') flags.DEFINE_string('libvirt_xml_template', utils.abspath('virt/libvirt.xml.template'), 'Libvirt XML Template') From e7be0b485e2d6c7cd95d1f5b7e6a401032f437e6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Dec 2010 17:01:34 -0500 Subject: [PATCH 20/20] Fix scheduler testcase so it knows all flags and can run in isolation. --- nova/tests/test_scheduler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 91517cc5db..78e4a1c77d 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -33,6 +33,7 @@ from nova.scheduler import driver FLAGS = flags.FLAGS flags.DECLARE('max_cores', 'nova.scheduler.simple') +flags.DECLARE('stub_network', 'nova.compute.manager') class TestDriver(driver.Scheduler):