From f835c01f41fdba5791190b9275775ae7fcfcafc6 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 18 Mar 2011 20:35:44 -0400 Subject: * committing ovs scripts --- .../xensource/scripts/ovs_configure_base_flows.py | 68 ++++++++ .../xensource/scripts/ovs_configure_vif_flows.py | 194 +++++++++++++++++++++ .../networking/etc/xensource/scripts/vif_rules.py | 9 +- 3 files changed, 267 insertions(+), 4 deletions(-) create mode 100755 plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py create mode 100755 plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py new file mode 100755 index 000000000..c46fb4b60 --- /dev/null +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 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 base openvswitch flows for XenServer hosts. +""" + +import os +import subprocess +import sys + + +PNIC_NAME="eth1" +XEN_BRIDGE="xenbr1" + +def main(dom_id, command, only_this_vif=None): + pnic_ofport = execute('/usr/bin/ovs-ofctl', 'get', 'Interface', PNIC_NAME, + 'ofport', return_stdout=True) + ovs_ofctl = lambda *rule: execute('/usr/bin/ovs-ofctl', *rule) + + # clear all flows + ovs_ofctl('del-flows', XEN_BRIDGE) + + # these flows are lower priority than all VM-specific flows. + + # allow all traffic from the physical NIC, as it is trusted (i.e., from a + # filtered vif, or from the physical infrastructure + ovs_ofctl('add-flow', XEN_BRIDGE, + "priority=2,in_port=%s,action=normal" % pnic_ofport) + + # default drop + ovs_ofctl('add-flow', XEN_BRIDGE, 'priority=1,action=drop') + + +def execute(*command, return_stdout=False): + devnull = open(os.devnull, 'w') + command = map(str, command) + proc = subprocess.Popen(command, close_fds=True, + stdout=subprocess.PIPE, stderr=devnull) + devnull.close() + if return_stdout: + return proc.stdout.read() + else: + return None + + +if __name__ == "__main__": + if sys.argv: + print "This script configures base ovs flows." + print "usage: %s" % os.path.basename(sys.argv[0]) + sys.exit(1) + else: + main() diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py new file mode 100755 index 000000000..a77bbbf4b --- /dev/null +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 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 openvswitch flows on XenServer hosts. +""" + +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 + + +XEN_BRIDGE = 'xenbr1' +OVS_OFCTL = '/usr/bin/ovs-ofctl' + + +def execute(*command, return_stdout=False): + devnull = open(os.devnull, 'w') + command = map(str, command) + proc = subprocess.Popen(command, close_fds=True, + stdout=subprocess.PIPE, stderr=devnull) + devnull.close() + if return_stdout: + return proc.stdout.read() + else: + return None + + +class OvsFlow(): + def __init__(self, command, params, bridge=None): + self.command = command + self.params = params + self.bridge = bridge or XEN_BRIDGE + + def add(self, rule): + execute(OVS_OFCTL, 'add-flow', self.bridge, rule) + + def delete(self, rule): + execute(OVS_OFCTL, 'del-flow', self.bridge, rule) + + def apply(self, rule): + self.delete(rule % self.params) + if self.command == 'online': + self.add(rule % params) + + +def main(dom_id, command, net, only_this_vif=None): + vif_ofport = execute('/usr/bin/ovs-ofctl', 'get', 'Interface', + only_this_vif, 'ofport', return_stdout=True) + + xsls = execute('/usr/bin/xenstore-ls', + '/local/domain/%s/vm-data/networking' % dom_id, + return_stdout=True) + macs = [line.split("=")[0].strip() for line in xsls.splitlines()] + + for mac in macs: + xsread = execute('/usr/bin/enstore-read', + '/local/domain/%s/vm-data/networking/%s' % + (dom_id, mac), True) + data = json.loads(xsread) + if data["label"] == "public": + vif = "vif%s.0" % dom_id + else: + vif = "vif%s.1" % dom_id + + if (only_this_vif is None) or (vif == only_this_vif): + params = dict(VIF=vif, MAC=data['mac']) + if net in ('ipv4', 'all'): + for ip4 in data['ips']: + params.update({'IP': ip4['ip']}) + apply_ovs_ipv4_flows(command, params) + if net in ('ipv6', 'all'): + for ip6 in data['ip6s']: + params.update({'IP': ip6['ip']}) + apply_ovs_ipv6_flows(command, params) + + +# usage: +# XEN_BRIDGE=xenbr1 +# VIF_NAME=$1 +# VIF_MAC=$2 +# VIF_IPv4=$3 +# VIF_GLOBAL_IPv6=$4 +# VIF_LOCAL_IPv6=$5 + +# # find the openflow port number associated with the vif interface +# VIF_OFPORT=`ovs-vsctl get Interface $VIF_NAME ofport` + +def apply_ovs_ipv4_flows(command, params): + flow = OvsFlow(command, params) + + # allow valid ARP outbound (both request / reply) + flow.apply("priority=3,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,arp," + "arp_sha=$VIF_MAC,nw_src=$VIF_IPv4,action=normal") + + flow.apply("priority=3,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,arp," + "arp_sha=$VIF_MAC,nw_src=0.0.0.0,action=normal") + + # allow valid IPv4 outbound + flow.apply("priority=3,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,ip," + "nw_src=$VIF_IPv4,action=normal") + + +def apply_ovs_ipv6_flows(command, params): + flow = OvsFlow(command, params) + + # allow valid IPv6 ND outbound (are both global and local IPs needed?) + # Neighbor Solicitation + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=135,nd_sll=$VIF_MAC," + "action=normal") + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=135,action=normal") + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=135,nd_sll=$VIF_MAC," + "action=normal") + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=135,action=normal") + + # Neighbor Advertisement + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=136," + "nd_target=$VIF_LOCAL_IPv6,action=normal") + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=136,action=normal") + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=136," + "nd_target=$VIF_GLOBAL_IPv6,action=normal") + flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," + "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=136,action=normal") + + # drop all other neighbor discovery (required because we permit all icmp6 below) + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=135,action=drop") + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=136,action=drop") + + # do not allow sending specifc ICMPv6 types + # Router Advertisement + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=134,action=drop") + # Redirect Gateway + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=137,action=drop") + # Mobile Prefix Solicitation + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=146,action=drop") + # Mobile Prefix Advertisement + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=147,action=drop") + # Multicast Router Advertisement + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=151,action=drop") + # Multicast Router Solicitation + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=152,action=drop") + # Multicast Router Termination + flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=153,action=drop") + + # allow valid IPv6 outbound, by type + flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," + "ipv6_src=$VIF_GLOBAL_IPv6,icmp6,action=normal") + flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," + "ipv6_src=$VIF_LOCAL_IPv6,icmp6,action=normal") + flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," + "ipv6_src=$VIF_GLOBAL_IPv6,tcp6,action=normal") + flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," + "ipv6_src=$VIF_LOCAL_IPv6,tcp6,action=normal") + flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," + "ipv6_src=$VIF_GLOBAL_IPv6,udp6,action=normal") + flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," + "ipv6_src=$VIF_LOCAL_IPv6,udp6,action=normal") + # all else will be dropped ... + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print "usage: %s dom_id online|offline ipv4|ipv6|all [vif]" % \ + os.path.basename(sys.argv[0]) + sys.exit(1) + else: + dom_id, command, net = sys.argv[1:4] + vif = len(sys.argv) == 5 and sys.argv[4] or None + main(dom_id, command, net, vif) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py index 48122e6d6..500e055d8 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 OpenStack LLC. +# Copyright 2010-2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -31,7 +31,8 @@ import simplejson as json def main(dom_id, command, only_this_vif=None): xsls = execute('/usr/bin/xenstore-ls', - '/local/domain/%s/vm-data/networking' % dom_id, True) + '/local/domain/%s/vm-data/networking' % dom_id, + return_stdout=True) macs = [line.split("=")[0].strip() for line in xsls.splitlines()] for mac in macs: @@ -113,8 +114,8 @@ def apply_ebtables_rules(command, params): ebtables('-D', 'FORWARD', '-p', '0806', '-o', params['VIF'], '--arp-ip-dst', params['IP'], '-j', 'ACCEPT') - ebtables('-D', 'FORWARD', '-p', '0800', '-o', - params['VIF'], '--ip-dst', params['IP'], + ebtables('-D', 'FORWARD', '-p', '0800', '-o', params['VIF'], + '--ip-dst', params['IP'], '-j', 'ACCEPT') if command == 'online': ebtables('-A', 'FORWARD', '-p', '0806', -- cgit From ab1bf7c0c12e205cd17b80be31226055cc90ef20 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Mon, 28 Mar 2011 21:00:44 +0000 Subject: minor fix and comment --- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index a77bbbf4b..553811ab6 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -59,10 +59,11 @@ class OvsFlow(): def apply(self, rule): self.delete(rule % self.params) if self.command == 'online': - self.add(rule % params) + self.add(rule % self.params) def main(dom_id, command, net, only_this_vif=None): + # FIXME(dubs) what to do when only_this_vif is None vif_ofport = execute('/usr/bin/ovs-ofctl', 'get', 'Interface', only_this_vif, 'ofport', return_stdout=True) -- cgit From d7c51db418d554094c341639a0540ecfec8ddb19 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 1 Apr 2011 14:43:04 +0000 Subject: lots of updates to ovs scripts --- .../xensource/scripts/ovs_configure_base_flows.py | 26 ++-- .../xensource/scripts/ovs_configure_vif_flows.py | 139 ++++++++++----------- .../networking/etc/xensource/scripts/vif_rules.py | 2 +- 3 files changed, 80 insertions(+), 87 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py index c46fb4b60..1f3182e68 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010-2011 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -25,26 +25,23 @@ import subprocess import sys -PNIC_NAME="eth1" -XEN_BRIDGE="xenbr1" - -def main(dom_id, command, only_this_vif=None): - pnic_ofport = execute('/usr/bin/ovs-ofctl', 'get', 'Interface', PNIC_NAME, - 'ofport', return_stdout=True) +def main(phys_dev_name, bridge_name): + pnic_ofport = execute('/usr/bin/ovs-vsctl', 'get', 'Interface', + phys_dev_name, 'ofport', return_stdout=True) ovs_ofctl = lambda *rule: execute('/usr/bin/ovs-ofctl', *rule) # clear all flows - ovs_ofctl('del-flows', XEN_BRIDGE) + ovs_ofctl('del-flows', bridge_name) # these flows are lower priority than all VM-specific flows. # allow all traffic from the physical NIC, as it is trusted (i.e., from a # filtered vif, or from the physical infrastructure - ovs_ofctl('add-flow', XEN_BRIDGE, + ovs_ofctl('add-flow', bridge_name, "priority=2,in_port=%s,action=normal" % pnic_ofport) # default drop - ovs_ofctl('add-flow', XEN_BRIDGE, 'priority=1,action=drop') + ovs_ofctl('add-flow', bridge_name, 'priority=1,action=drop') def execute(*command, return_stdout=False): @@ -60,9 +57,12 @@ def execute(*command, return_stdout=False): if __name__ == "__main__": - if sys.argv: + if len(sys.argv) != 3: + script_name = os.path.basename(sys.argv[0]) print "This script configures base ovs flows." - print "usage: %s" % os.path.basename(sys.argv[0]) + print "usage: %s phys-dev-name bridge-name" % script_name + print " ex: %s eth2 xenbr2" % script_name sys.exit(1) else: - main() + phys_dev_name, bridge_name = sys.argv[1:3] + main(phys_dev_name, bridge_name) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 553811ab6..7bad39830 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010-2011 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -28,6 +28,7 @@ import sys import simplejson as json +# FIXME(dubs) this needs to be able to be passed in, check xen vif script XEN_BRIDGE = 'xenbr1' OVS_OFCTL = '/usr/bin/ovs-ofctl' @@ -62,18 +63,14 @@ class OvsFlow(): self.add(rule % self.params) -def main(dom_id, command, net, only_this_vif=None): - # FIXME(dubs) what to do when only_this_vif is None - vif_ofport = execute('/usr/bin/ovs-ofctl', 'get', 'Interface', - only_this_vif, 'ofport', return_stdout=True) - +def main(dom_id, command, net_type, only_this_vif=None): xsls = execute('/usr/bin/xenstore-ls', '/local/domain/%s/vm-data/networking' % dom_id, return_stdout=True) macs = [line.split("=")[0].strip() for line in xsls.splitlines()] for mac in macs: - xsread = execute('/usr/bin/enstore-read', + xsread = execute('/usr/bin/xenstore-read', '/local/domain/%s/vm-data/networking/%s' % (dom_id, mac), True) data = json.loads(xsread) @@ -83,113 +80,109 @@ def main(dom_id, command, net, only_this_vif=None): vif = "vif%s.1" % dom_id if (only_this_vif is None) or (vif == only_this_vif): - params = dict(VIF=vif, MAC=data['mac']) - if net in ('ipv4', 'all'): + vif_ofport = execute('/usr/bin/ovs-vsctl', 'get', 'Interface', + vif, 'ofport', return_stdout=True) + + params = dict(VIF_NAME=vif, + VIF_MAC=data['mac'], + VIF_OFPORT=vif_ofport) + if net_type in ('ipv4', 'all'): for ip4 in data['ips']: - params.update({'IP': ip4['ip']}) + params.update({'VIF_IPv4': ip4['ip']}) apply_ovs_ipv4_flows(command, params) - if net in ('ipv6', 'all'): + if net_type in ('ipv6', 'all'): for ip6 in data['ip6s']: - params.update({'IP': ip6['ip']}) + params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) + # TODO(dubs) calculate v6 link local addr + #params.update({'VIF_LOCAL_IPv6': XXX}) apply_ovs_ipv6_flows(command, params) -# usage: -# XEN_BRIDGE=xenbr1 -# VIF_NAME=$1 -# VIF_MAC=$2 -# VIF_IPv4=$3 -# VIF_GLOBAL_IPv6=$4 -# VIF_LOCAL_IPv6=$5 - -# # find the openflow port number associated with the vif interface -# VIF_OFPORT=`ovs-vsctl get Interface $VIF_NAME ofport` - def apply_ovs_ipv4_flows(command, params): flow = OvsFlow(command, params) # allow valid ARP outbound (both request / reply) - flow.apply("priority=3,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,arp," - "arp_sha=$VIF_MAC,nw_src=$VIF_IPv4,action=normal") + flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," + "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,action=normal") - flow.apply("priority=3,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,arp," - "arp_sha=$VIF_MAC,nw_src=0.0.0.0,action=normal") + flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," + "arp_sha=%(VIF_MAC)s,nw_src=0.0.0.0,action=normal") # allow valid IPv4 outbound - flow.apply("priority=3,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,ip," - "nw_src=$VIF_IPv4,action=normal") + flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,ip," + "nw_src=%(VIF_IPv4)s,action=normal") def apply_ovs_ipv6_flows(command, params): flow = OvsFlow(command, params) # allow valid IPv6 ND outbound (are both global and local IPs needed?) - # Neighbor Solicitation - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=135,nd_sll=$VIF_MAC," + # Neighbor Solicitation + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," "action=normal") - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=135,action=normal") - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=135,nd_sll=$VIF_MAC," + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,action=normal") + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," "action=normal") - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=135,action=normal") + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,action=normal") # Neighbor Advertisement - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=136," - "nd_target=$VIF_LOCAL_IPv6,action=normal") - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_LOCAL_IPv6,icmp_type=136,action=normal") - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=136," - "nd_target=$VIF_GLOBAL_IPv6,action=normal") - flow.apply("priority=6,in_port=$VIF_OFPORT,dl_src=$VIF_MAC,icmp6," - "ipv6_src=$VIF_GLOBAL_IPv6,icmp_type=136,action=normal") + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136," + "nd_target=%(VIF_LOCAL_IPv6)s,action=normal") + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136,action=normal") + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136," + "nd_target=%(VIF_GLOBAL_IPv6)s,action=normal") + flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136,action=normal") # drop all other neighbor discovery (required because we permit all icmp6 below) - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=135,action=drop") - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=136,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=135,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=136,action=drop") # do not allow sending specifc ICMPv6 types # Router Advertisement - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=134,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=134,action=drop") # Redirect Gateway - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=137,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=137,action=drop") # Mobile Prefix Solicitation - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=146,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=146,action=drop") # Mobile Prefix Advertisement - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=147,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=147,action=drop") # Multicast Router Advertisement - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=151,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=151,action=drop") # Multicast Router Solicitation - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=152,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=152,action=drop") # Multicast Router Termination - flow.apply("priority=5,in_port=$VIF_OFPORT,icmp6,icmp_type=153,action=drop") + flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=153,action=drop") # allow valid IPv6 outbound, by type - flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," - "ipv6_src=$VIF_GLOBAL_IPv6,icmp6,action=normal") - flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," - "ipv6_src=$VIF_LOCAL_IPv6,icmp6,action=normal") - flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," - "ipv6_src=$VIF_GLOBAL_IPv6,tcp6,action=normal") - flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," - "ipv6_src=$VIF_LOCAL_IPv6,tcp6,action=normal") - flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," - "ipv6_src=$VIF_GLOBAL_IPv6,udp6,action=normal") - flow.apply("priority=4,in_port=$VIF_OFPORT,dl_src=$VIF_MAC," - "ipv6_src=$VIF_LOCAL_IPv6,udp6,action=normal") + flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp6,action=normal") + flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp6,action=normal") + flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,tcp6,action=normal") + flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_LOCAL_IPv6)s,tcp6,action=normal") + flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,udp6,action=normal") + flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_LOCAL_IPv6)s,udp6,action=normal") # all else will be dropped ... if __name__ == "__main__": if len(sys.argv) < 3: - print "usage: %s dom_id online|offline ipv4|ipv6|all [vif]" % \ + print "usage: %s dom_id online|offline ipv4|ipv6|all [vif_name]" % \ os.path.basename(sys.argv[0]) sys.exit(1) else: - dom_id, command, net = sys.argv[1:4] - vif = len(sys.argv) == 5 and sys.argv[4] or None - main(dom_id, command, net, vif) + dom_id, command, net_type = sys.argv[1:4] + vif_name = len(sys.argv) == 5 and sys.argv[4] or None + main(dom_id, command, net_type, vif_name) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py index 500e055d8..4e13bad9d 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py @@ -36,7 +36,7 @@ def main(dom_id, command, only_this_vif=None): macs = [line.split("=")[0].strip() for line in xsls.splitlines()] for mac in macs: - xsread = execute('/usr/bin/enstore-read', + xsread = execute('/usr/bin/xenstore-read', '/local/domain/%s/vm-data/networking/%s' % (dom_id, mac), True) data = json.loads(xsread) -- cgit From 367581e63d4eb0018db293034dc1b096d2584720 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 1 Apr 2011 15:28:21 +0000 Subject: change bridge --- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 7bad39830..2faf4e5c0 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -29,7 +29,7 @@ import simplejson as json # FIXME(dubs) this needs to be able to be passed in, check xen vif script -XEN_BRIDGE = 'xenbr1' +XEN_BRIDGE = 'xenbr0' OVS_OFCTL = '/usr/bin/ovs-ofctl' @@ -86,6 +86,7 @@ def main(dom_id, command, net_type, only_this_vif=None): params = dict(VIF_NAME=vif, VIF_MAC=data['mac'], VIF_OFPORT=vif_ofport) + if net_type in ('ipv4', 'all'): for ip4 in data['ips']: params.update({'VIF_IPv4': ip4['ip']}) -- cgit From 74b9f240c7e8c62e68011691488be9e63758e980 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 1 Apr 2011 19:54:55 +0000 Subject: extract execute methods to a library for reuse --- .../networking/etc/xensource/scripts/novalib.py | 41 ++++++++++++++++++++++ .../xensource/scripts/ovs_configure_base_flows.py | 21 ++++------- .../xensource/scripts/ovs_configure_vif_flows.py | 30 ++++++---------- 3 files changed, 57 insertions(+), 35 deletions(-) create mode 100644 plugins/xenserver/networking/etc/xensource/scripts/novalib.py diff --git a/plugins/xenserver/networking/etc/xensource/scripts/novalib.py b/plugins/xenserver/networking/etc/xensource/scripts/novalib.py new file mode 100644 index 000000000..5366c385d --- /dev/null +++ b/plugins/xenserver/networking/etc/xensource/scripts/novalib.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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. + + +import os +import subprocess +import sys + + +def execute_get_output(*command): + """Execute and return stdout""" + devnull = open(os.devnull, 'w') + command = map(str, command) + proc = subprocess.Popen(command, close_fds=True, + stdout=subprocess.PIPE, stderr=devnull) + devnull.close() + return proc.stdout.read() + + +def execute(*command): + """Execute without returning stdout""" + devnull = open(os.devnull, 'w') + command = map(str, command) + proc = subprocess.Popen(command, close_fds=True, + stdout=subprocess.PIPE, stderr=devnull) + devnull.close() diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py index 1f3182e68..d036cf517 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -25,9 +25,12 @@ import subprocess import sys +from novalib import execute, execute_get_output + + def main(phys_dev_name, bridge_name): - pnic_ofport = execute('/usr/bin/ovs-vsctl', 'get', 'Interface', - phys_dev_name, 'ofport', return_stdout=True) + pnic_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', 'Interface', + phys_dev_name, 'ofport') ovs_ofctl = lambda *rule: execute('/usr/bin/ovs-ofctl', *rule) # clear all flows @@ -44,24 +47,12 @@ def main(phys_dev_name, bridge_name): ovs_ofctl('add-flow', bridge_name, 'priority=1,action=drop') -def execute(*command, return_stdout=False): - devnull = open(os.devnull, 'w') - command = map(str, command) - proc = subprocess.Popen(command, close_fds=True, - stdout=subprocess.PIPE, stderr=devnull) - devnull.close() - if return_stdout: - return proc.stdout.read() - else: - return None - - if __name__ == "__main__": if len(sys.argv) != 3: script_name = os.path.basename(sys.argv[0]) print "This script configures base ovs flows." print "usage: %s phys-dev-name bridge-name" % script_name - print " ex: %s eth2 xenbr2" % script_name + print " ex: %s eth0 xenbr0" % script_name sys.exit(1) else: phys_dev_name, bridge_name = sys.argv[1:3] diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 2faf4e5c0..82e79c2d8 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -28,23 +28,14 @@ import sys import simplejson as json +from novalib import execute, execute_get_output + + # FIXME(dubs) this needs to be able to be passed in, check xen vif script XEN_BRIDGE = 'xenbr0' OVS_OFCTL = '/usr/bin/ovs-ofctl' -def execute(*command, return_stdout=False): - devnull = open(os.devnull, 'w') - command = map(str, command) - proc = subprocess.Popen(command, close_fds=True, - stdout=subprocess.PIPE, stderr=devnull) - devnull.close() - if return_stdout: - return proc.stdout.read() - else: - return None - - class OvsFlow(): def __init__(self, command, params, bridge=None): self.command = command @@ -64,15 +55,14 @@ class OvsFlow(): def main(dom_id, command, net_type, only_this_vif=None): - xsls = execute('/usr/bin/xenstore-ls', - '/local/domain/%s/vm-data/networking' % dom_id, - return_stdout=True) + xsls = execute_get_output('/usr/bin/xenstore-ls', + '/local/domain/%s/vm-data/networking' % dom_id) macs = [line.split("=")[0].strip() for line in xsls.splitlines()] for mac in macs: - xsread = execute('/usr/bin/xenstore-read', - '/local/domain/%s/vm-data/networking/%s' % - (dom_id, mac), True) + xsread = execute_get_output('/usr/bin/xenstore-read', + '/local/domain/%s/vm-data/networking/%s' % + (dom_id, mac)) data = json.loads(xsread) if data["label"] == "public": vif = "vif%s.0" % dom_id @@ -80,8 +70,8 @@ def main(dom_id, command, net_type, only_this_vif=None): vif = "vif%s.1" % dom_id if (only_this_vif is None) or (vif == only_this_vif): - vif_ofport = execute('/usr/bin/ovs-vsctl', 'get', 'Interface', - vif, 'ofport', return_stdout=True) + vif_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', + 'Interface', vif, 'ofport') params = dict(VIF_NAME=vif, VIF_MAC=data['mac'], -- cgit From a4e1db03a2c61648588d9adb703a385f49d82fc0 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 1 Apr 2011 20:26:59 +0000 Subject: use novalib for vif_rules.py, fix OvsFlow class --- .../xensource/scripts/ovs_configure_vif_flows.py | 2 +- .../networking/etc/xensource/scripts/vif_rules.py | 25 +++++++--------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 82e79c2d8..23b6d85c9 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -36,7 +36,7 @@ XEN_BRIDGE = 'xenbr0' OVS_OFCTL = '/usr/bin/ovs-ofctl' -class OvsFlow(): +class OvsFlow(object): def __init__(self, command, params, bridge=None): self.command = command self.params = params diff --git a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py index 4e13bad9d..662def205 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py @@ -29,16 +29,18 @@ import sys import simplejson as json +from novalib import execute, execute_get_output + + def main(dom_id, command, only_this_vif=None): - xsls = execute('/usr/bin/xenstore-ls', - '/local/domain/%s/vm-data/networking' % dom_id, - return_stdout=True) + xsls = execute_get_output('/usr/bin/xenstore-ls', + '/local/domain/%s/vm-data/networking' % dom_id) macs = [line.split("=")[0].strip() for line in xsls.splitlines()] for mac in macs: - xsread = execute('/usr/bin/xenstore-read', - '/local/domain/%s/vm-data/networking/%s' % - (dom_id, mac), True) + xsread = execute_get_output('/usr/bin/xenstore-read', + '/local/domain/%s/vm-data/networking/%s' % + (dom_id, mac)) data = json.loads(xsread) for ip in data['ips']: if data["label"] == "public": @@ -53,17 +55,6 @@ def main(dom_id, command, only_this_vif=None): apply_iptables_rules(command, params) -def execute(*command, return_stdout=False): - devnull = open(os.devnull, 'w') - command = map(str, command) - proc = subprocess.Popen(command, close_fds=True, - stdout=subprocess.PIPE, stderr=devnull) - devnull.close() - if return_stdout: - return proc.stdout.read() - else: - return None - # 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. -- cgit From 1c13695a2c5e5d14ead3f5459d0b40bb875ecdf6 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 11 Apr 2011 14:16:17 -0400 Subject: Sudo chown the vbd device to the nova user before streaming data to it. This resolves an issue where nova-compute required 'root' privs to successfully create nodes with connection_type=xenapi. --- nova/virt/xenapi/vm_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d2045a557..50fdf3e30 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1012,6 +1012,8 @@ def _stream_disk(dev, image_type, virtual_size, image_file): offset = MBR_SIZE_BYTES _write_partition(virtual_size, dev) + utils.execute('sudo', 'chown', os.getuid(), '/dev/%s' % dev) + with open('/dev/%s' % dev, 'wb') as f: f.seek(offset) for chunk in image_file: -- cgit From 9d2513ea3a6d586e1fe3deae778a02bb089b9a5e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 12 Apr 2011 10:25:07 -0400 Subject: Updated to use setfacl instead of chown. --- nova/virt/xenapi/vm_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 50fdf3e30..5cdd29057 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1012,7 +1012,8 @@ def _stream_disk(dev, image_type, virtual_size, image_file): offset = MBR_SIZE_BYTES _write_partition(virtual_size, dev) - utils.execute('sudo', 'chown', os.getuid(), '/dev/%s' % dev) + utils.execute('sudo', 'setfacl', '-m', 'u:%s:rw' % os.getuid(), + '/dev/%s' % dev) with open('/dev/%s' % dev, 'wb') as f: f.seek(offset) -- cgit From f2e9d4120ed0495d9c810a0d27d530e280f325c6 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 14 Apr 2011 14:35:42 -0400 Subject: set the bridge on each OvsFlow --- .../xensource/scripts/ovs_configure_vif_flows.py | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 23b6d85c9..b59cc4d0b 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -27,30 +27,28 @@ import sys # This is written to Python 2.4, since that is what is available on XenServer import simplejson as json - from novalib import execute, execute_get_output -# FIXME(dubs) this needs to be able to be passed in, check xen vif script -XEN_BRIDGE = 'xenbr0' OVS_OFCTL = '/usr/bin/ovs-ofctl' class OvsFlow(object): - def __init__(self, command, params, bridge=None): + def __init__(self, command, bridge, params): self.command = command + self.bridge = bridge self.params = params - self.bridge = bridge or XEN_BRIDGE def add(self, rule): execute(OVS_OFCTL, 'add-flow', self.bridge, rule) def delete(self, rule): - execute(OVS_OFCTL, 'del-flow', self.bridge, rule) + execute(OVS_OFCTL, 'del-flows', self.bridge, rule) def apply(self, rule): - self.delete(rule % self.params) - if self.command == 'online': + if self.command in ('offline', 'reset'): + self.delete(rule % self.params) + if self.command in ('online', 'reset'): self.add(rule % self.params) @@ -66,8 +64,10 @@ def main(dom_id, command, net_type, only_this_vif=None): data = json.loads(xsread) if data["label"] == "public": vif = "vif%s.0" % dom_id + bridge = "xenbr0" else: vif = "vif%s.1" % dom_id + bridge = "xenbr1" if (only_this_vif is None) or (vif == only_this_vif): vif_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', @@ -80,17 +80,17 @@ def main(dom_id, command, net_type, only_this_vif=None): if net_type in ('ipv4', 'all'): for ip4 in data['ips']: params.update({'VIF_IPv4': ip4['ip']}) - apply_ovs_ipv4_flows(command, params) + apply_ovs_ipv4_flows(command, bridge, params) if net_type in ('ipv6', 'all'): for ip6 in data['ip6s']: params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) # TODO(dubs) calculate v6 link local addr #params.update({'VIF_LOCAL_IPv6': XXX}) - apply_ovs_ipv6_flows(command, params) + apply_ovs_ipv6_flows(command, bridge, params) -def apply_ovs_ipv4_flows(command, params): - flow = OvsFlow(command, params) +def apply_ovs_ipv4_flows(command, bridge, params): + flow = OvsFlow(command, bridge, params) # allow valid ARP outbound (both request / reply) flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," @@ -104,8 +104,8 @@ def apply_ovs_ipv4_flows(command, params): "nw_src=%(VIF_IPv4)s,action=normal") -def apply_ovs_ipv6_flows(command, params): - flow = OvsFlow(command, params) +def apply_ovs_ipv6_flows(command, bridge, params): + flow = OvsFlow(command, bridge, params) # allow valid IPv6 ND outbound (are both global and local IPs needed?) # Neighbor Solicitation @@ -170,7 +170,7 @@ def apply_ovs_ipv6_flows(command, params): if __name__ == "__main__": if len(sys.argv) < 3: - print "usage: %s dom_id online|offline ipv4|ipv6|all [vif_name]" % \ + print "usage: %s dom_id online|offline|reset ipv4|ipv6|all [vif_name]" % \ os.path.basename(sys.argv[0]) sys.exit(1) else: -- cgit From c134d3c9bfb5a9d789776b243b8d6e4283fb3f80 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Mon, 18 Apr 2011 13:30:54 -0400 Subject: calc link local --- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index b59cc4d0b..08d7a3859 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -25,6 +25,7 @@ import subprocess import sys # This is written to Python 2.4, since that is what is available on XenServer +import netaddr import simplejson as json from novalib import execute, execute_get_output @@ -84,8 +85,8 @@ def main(dom_id, command, net_type, only_this_vif=None): if net_type in ('ipv6', 'all'): for ip6 in data['ip6s']: params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) - # TODO(dubs) calculate v6 link local addr - #params.update({'VIF_LOCAL_IPv6': XXX}) + mac64 = netaddr.EUI(mac).eui64() + params.update({'VIF_LOCAL_IPv6': mac64.ipv6_link_local()}) apply_ovs_ipv6_flows(command, bridge, params) -- cgit From 4e11c04a34b3237853c0b4be90ce6362237bcbe0 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Tue, 19 Apr 2011 20:10:57 +0000 Subject: strip output, str() link local --- plugins/xenserver/networking/etc/xensource/scripts/novalib.py | 2 +- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/novalib.py b/plugins/xenserver/networking/etc/xensource/scripts/novalib.py index 5366c385d..9fc4b2310 100644 --- a/plugins/xenserver/networking/etc/xensource/scripts/novalib.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/novalib.py @@ -29,7 +29,7 @@ def execute_get_output(*command): proc = subprocess.Popen(command, close_fds=True, stdout=subprocess.PIPE, stderr=devnull) devnull.close() - return proc.stdout.read() + return proc.stdout.read().strip() def execute(*command): diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 08d7a3859..d1d646b99 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -86,7 +86,7 @@ def main(dom_id, command, net_type, only_this_vif=None): for ip6 in data['ip6s']: params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) mac64 = netaddr.EUI(mac).eui64() - params.update({'VIF_LOCAL_IPv6': mac64.ipv6_link_local()}) + params.update({'VIF_LOCAL_IPv6': str(mac64.ipv6_link_local())}) apply_ovs_ipv6_flows(command, bridge, params) -- cgit From a46bd9fb6483959e210f25276ff70c76767e509d Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Tue, 19 Apr 2011 22:13:40 +0000 Subject: only apply ipv6 if the data exists in xenstore --- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index d1d646b99..e1a151476 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -82,10 +82,10 @@ def main(dom_id, command, net_type, only_this_vif=None): for ip4 in data['ips']: params.update({'VIF_IPv4': ip4['ip']}) apply_ovs_ipv4_flows(command, bridge, params) - if net_type in ('ipv6', 'all'): + if net_type in ('ipv6', 'all') and 'ip6s' in data: for ip6 in data['ip6s']: params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) - mac64 = netaddr.EUI(mac).eui64() + mac64 = netaddr.EUI(data['mac']).eui64() params.update({'VIF_LOCAL_IPv6': str(mac64.ipv6_link_local())}) apply_ovs_ipv6_flows(command, bridge, params) -- cgit From 169496af390caa4035db2fefabffd71c95a57fbf Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Wed, 20 Apr 2011 14:11:25 -0400 Subject: refactor the way flows are deleted/reset --- .../xensource/scripts/ovs_configure_vif_flows.py | 143 ++++++++++----------- 1 file changed, 70 insertions(+), 73 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index e1a151476..37ff07e33 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -35,22 +35,15 @@ OVS_OFCTL = '/usr/bin/ovs-ofctl' class OvsFlow(object): - def __init__(self, command, bridge, params): - self.command = command + def __init__(self, bridge, params): self.bridge = bridge self.params = params def add(self, rule): - execute(OVS_OFCTL, 'add-flow', self.bridge, rule) + execute(OVS_OFCTL, 'add-flow', self.bridge, rule % self.params) - def delete(self, rule): - execute(OVS_OFCTL, 'del-flows', self.bridge, rule) - - def apply(self, rule): - if self.command in ('offline', 'reset'): - self.delete(rule % self.params) - if self.command in ('online', 'reset'): - self.add(rule % self.params) + def clear_flows(self, ofport): + execute(OVS_OFCTL, 'del-flows', self.bridge, "in_port=%s" % ofport) def main(dom_id, command, net_type, only_this_vif=None): @@ -78,94 +71,98 @@ def main(dom_id, command, net_type, only_this_vif=None): VIF_MAC=data['mac'], VIF_OFPORT=vif_ofport) - if net_type in ('ipv4', 'all'): - for ip4 in data['ips']: - params.update({'VIF_IPv4': ip4['ip']}) - apply_ovs_ipv4_flows(command, bridge, params) - if net_type in ('ipv6', 'all') and 'ip6s' in data: - for ip6 in data['ip6s']: - params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) - mac64 = netaddr.EUI(data['mac']).eui64() - params.update({'VIF_LOCAL_IPv6': str(mac64.ipv6_link_local())}) - apply_ovs_ipv6_flows(command, bridge, params) + ovs = OvsFlow(bridge, params) + + if command in ('offline', 'reset'): + # I haven't found a way to clear only IPv4 or IPv6 rules. + ovs.clear_flows(vif_ofport) + if command in ('online', 'reset'): + if net_type in ('ipv4', 'all') and 'ips' in data: + for ip4 in data['ips']: + ovs.params.update({'VIF_IPv4': ip4['ip']}) + apply_ovs_ipv4_flows(ovs, bridge, params) + if net_type in ('ipv6', 'all') and 'ip6s' in data: + for ip6 in data['ip6s']: + link_local = str(netaddr.EUI(data['mac']).eui64()\ + .ipv6_link_local()) + ovs.params.update({'VIF_LOCAL_IPv6': link_local}) + ovs.params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) + apply_ovs_ipv6_flows(ovs, bridge, params) -def apply_ovs_ipv4_flows(command, bridge, params): - flow = OvsFlow(command, bridge, params) +def apply_ovs_ipv4_flows(ovs, command, bridge, params): # allow valid ARP outbound (both request / reply) - flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," - "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,action=normal") + ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," + "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,action=normal") - flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," - "arp_sha=%(VIF_MAC)s,nw_src=0.0.0.0,action=normal") + ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," + "arp_sha=%(VIF_MAC)s,nw_src=0.0.0.0,action=normal") # allow valid IPv4 outbound - flow.apply("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,ip," - "nw_src=%(VIF_IPv4)s,action=normal") - + ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,ip," + "nw_src=%(VIF_IPv4)s,action=normal") -def apply_ovs_ipv6_flows(command, bridge, params): - flow = OvsFlow(command, bridge, params) +def apply_ovs_ipv6_flows(ovs, command, bridge, params): # allow valid IPv6 ND outbound (are both global and local IPs needed?) # Neighbor Solicitation - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," - "action=normal") - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,action=normal") - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," - "action=normal") - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," + "action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," + "action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,action=normal") # Neighbor Advertisement - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136," - "nd_target=%(VIF_LOCAL_IPv6)s,action=normal") - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136,action=normal") - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136," - "nd_target=%(VIF_GLOBAL_IPv6)s,action=normal") - flow.apply("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136,action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136," + "nd_target=%(VIF_LOCAL_IPv6)s,action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136,action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136," + "nd_target=%(VIF_GLOBAL_IPv6)s,action=normal") + ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136,action=normal") # drop all other neighbor discovery (required because we permit all icmp6 below) - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=135,action=drop") - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=136,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=135,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=136,action=drop") # do not allow sending specifc ICMPv6 types # Router Advertisement - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=134,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=134,action=drop") # Redirect Gateway - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=137,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=137,action=drop") # Mobile Prefix Solicitation - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=146,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=146,action=drop") # Mobile Prefix Advertisement - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=147,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=147,action=drop") # Multicast Router Advertisement - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=151,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=151,action=drop") # Multicast Router Solicitation - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=152,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=152,action=drop") # Multicast Router Termination - flow.apply("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=153,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=153,action=drop") # allow valid IPv6 outbound, by type - flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp6,action=normal") - flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp6,action=normal") - flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,tcp6,action=normal") - flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,tcp6,action=normal") - flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,udp6,action=normal") - flow.apply("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,udp6,action=normal") + ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp6,action=normal") + ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp6,action=normal") + ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,tcp6,action=normal") + ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_LOCAL_IPv6)s,tcp6,action=normal") + ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_GLOBAL_IPv6)s,udp6,action=normal") + ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," + "ipv6_src=%(VIF_LOCAL_IPv6)s,udp6,action=normal") # all else will be dropped ... -- cgit From 7c53dc7a792dfcda0862178725adbe585c4fed21 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Wed, 20 Apr 2011 14:24:29 -0400 Subject: bugfix signature --- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 37ff07e33..9557eb3e2 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -91,7 +91,7 @@ def main(dom_id, command, net_type, only_this_vif=None): apply_ovs_ipv6_flows(ovs, bridge, params) -def apply_ovs_ipv4_flows(ovs, command, bridge, params): +def apply_ovs_ipv4_flows(ovs, bridge, params): # allow valid ARP outbound (both request / reply) ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,action=normal") @@ -104,7 +104,7 @@ def apply_ovs_ipv4_flows(ovs, command, bridge, params): "nw_src=%(VIF_IPv4)s,action=normal") -def apply_ovs_ipv6_flows(ovs, command, bridge, params): +def apply_ovs_ipv6_flows(ovs, bridge, params): # allow valid IPv6 ND outbound (are both global and local IPs needed?) # Neighbor Solicitation ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," -- cgit From bbcc2304167c3331f4c54898200f01fd66c0a20c Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 21 Apr 2011 14:53:03 -0400 Subject: change action= to actions= --- .../xensource/scripts/ovs_configure_base_flows.py | 4 +- .../xensource/scripts/ovs_configure_vif_flows.py | 52 +++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py index d036cf517..555dad71a 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -41,10 +41,10 @@ def main(phys_dev_name, bridge_name): # allow all traffic from the physical NIC, as it is trusted (i.e., from a # filtered vif, or from the physical infrastructure ovs_ofctl('add-flow', bridge_name, - "priority=2,in_port=%s,action=normal" % pnic_ofport) + "priority=2,in_port=%s,actions=normal" % pnic_ofport) # default drop - ovs_ofctl('add-flow', bridge_name, 'priority=1,action=drop') + ovs_ofctl('add-flow', bridge_name, 'priority=1,actions=drop') if __name__ == "__main__": diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 9557eb3e2..aba8487f6 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -94,14 +94,14 @@ def main(dom_id, command, net_type, only_this_vif=None): def apply_ovs_ipv4_flows(ovs, bridge, params): # allow valid ARP outbound (both request / reply) ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," - "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,action=normal") + "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,actions=normal") ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," - "arp_sha=%(VIF_MAC)s,nw_src=0.0.0.0,action=normal") + "arp_sha=%(VIF_MAC)s,nw_src=0.0.0.0,actions=normal") # allow valid IPv4 outbound ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,ip," - "nw_src=%(VIF_IPv4)s,action=normal") + "nw_src=%(VIF_IPv4)s,actions=normal") def apply_ovs_ipv6_flows(ovs, bridge, params): @@ -109,60 +109,60 @@ def apply_ovs_ipv6_flows(ovs, bridge, params): # Neighbor Solicitation ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," - "action=normal") + "actions=normal") ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,action=normal") + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,actions=normal") ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," - "action=normal") + "actions=normal") ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,action=normal") + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,actions=normal") # Neighbor Advertisement ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136," - "nd_target=%(VIF_LOCAL_IPv6)s,action=normal") + "nd_target=%(VIF_LOCAL_IPv6)s,actions=normal") ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136,action=normal") + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136,actions=normal") ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136," - "nd_target=%(VIF_GLOBAL_IPv6)s,action=normal") + "nd_target=%(VIF_GLOBAL_IPv6)s,actions=normal") ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136,action=normal") + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136,actions=normal") # drop all other neighbor discovery (required because we permit all icmp6 below) - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=135,action=drop") - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=136,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=135,actions=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=136,actions=drop") # do not allow sending specifc ICMPv6 types # Router Advertisement - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=134,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=134,actions=drop") # Redirect Gateway - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=137,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=137,actions=drop") # Mobile Prefix Solicitation - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=146,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=146,actions=drop") # Mobile Prefix Advertisement - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=147,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=147,actions=drop") # Multicast Router Advertisement - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=151,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=151,actions=drop") # Multicast Router Solicitation - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=152,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=152,actions=drop") # Multicast Router Termination - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=153,action=drop") + ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=153,actions=drop") # allow valid IPv6 outbound, by type ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp6,action=normal") + "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp6,actions=normal") ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp6,action=normal") + "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp6,actions=normal") ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,tcp6,action=normal") + "ipv6_src=%(VIF_GLOBAL_IPv6)s,tcp6,actions=normal") ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,tcp6,action=normal") + "ipv6_src=%(VIF_LOCAL_IPv6)s,tcp6,actions=normal") ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,udp6,action=normal") + "ipv6_src=%(VIF_GLOBAL_IPv6)s,udp6,actions=normal") ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,udp6,action=normal") + "ipv6_src=%(VIF_LOCAL_IPv6)s,udp6,actions=normal") # all else will be dropped ... -- cgit From a13616c2deae4ae90bb69ce87bda28576e194426 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 22 Apr 2011 16:35:26 -0400 Subject: removed unused imports and renamed template variables --- .../networking/etc/xensource/scripts/novalib.py | 1 - .../xensource/scripts/ovs_configure_base_flows.py | 1 - .../xensource/scripts/ovs_configure_vif_flows.py | 107 ++++++++++----------- 3 files changed, 53 insertions(+), 56 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/novalib.py b/plugins/xenserver/networking/etc/xensource/scripts/novalib.py index 9fc4b2310..dcbee3ded 100644 --- a/plugins/xenserver/networking/etc/xensource/scripts/novalib.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/novalib.py @@ -19,7 +19,6 @@ import os import subprocess -import sys def execute_get_output(*command): diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py index 555dad71a..82d0b9e31 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -21,7 +21,6 @@ This script is used to configure base openvswitch flows for XenServer hosts. """ import os -import subprocess import sys diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index aba8487f6..f91a5f49d 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -21,7 +21,6 @@ This script is used to configure openvswitch flows on XenServer hosts. """ import os -import subprocess import sys # This is written to Python 2.4, since that is what is available on XenServer @@ -68,8 +67,8 @@ def main(dom_id, command, net_type, only_this_vif=None): 'Interface', vif, 'ofport') params = dict(VIF_NAME=vif, - VIF_MAC=data['mac'], - VIF_OFPORT=vif_ofport) + MAC=data['mac'], + OF_PORT=vif_ofport) ovs = OvsFlow(bridge, params) @@ -80,95 +79,95 @@ def main(dom_id, command, net_type, only_this_vif=None): if command in ('online', 'reset'): if net_type in ('ipv4', 'all') and 'ips' in data: for ip4 in data['ips']: - ovs.params.update({'VIF_IPv4': ip4['ip']}) + ovs.params.update({'IPV4_ADDR': ip4['ip']}) apply_ovs_ipv4_flows(ovs, bridge, params) if net_type in ('ipv6', 'all') and 'ip6s' in data: for ip6 in data['ip6s']: link_local = str(netaddr.EUI(data['mac']).eui64()\ .ipv6_link_local()) - ovs.params.update({'VIF_LOCAL_IPv6': link_local}) - ovs.params.update({'VIF_GLOBAL_IPv6': ip6['ip']}) + ovs.params.update({'IPV6_LINK_LOCAL_ADDR': link_local}) + ovs.params.update({'IPV6_GLOBAL_ADDR': ip6['ip']}) apply_ovs_ipv6_flows(ovs, bridge, params) def apply_ovs_ipv4_flows(ovs, bridge, params): # allow valid ARP outbound (both request / reply) - ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," - "arp_sha=%(VIF_MAC)s,nw_src=%(VIF_IPv4)s,actions=normal") + ovs.add("priority=3,in_port=%(OF_PORT)s,dl_src=%(MAC)s,arp," + "arp_sha=%(MAC)s,nw_src=%(IPV4_ADDR)s,actions=normal") - ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,arp," - "arp_sha=%(VIF_MAC)s,nw_src=0.0.0.0,actions=normal") + ovs.add("priority=3,in_port=%(OF_PORT)s,dl_src=%(MAC)s,arp," + "arp_sha=%(MAC)s,nw_src=0.0.0.0,actions=normal") # allow valid IPv4 outbound - ovs.add("priority=3,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,ip," - "nw_src=%(VIF_IPv4)s,actions=normal") + ovs.add("priority=3,in_port=%(OF_PORT)s,dl_src=%(MAC)s,ip," + "nw_src=%(IPV4_ADDR)s,actions=normal") def apply_ovs_ipv6_flows(ovs, bridge, params): # allow valid IPv6 ND outbound (are both global and local IPs needed?) # Neighbor Solicitation - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=135,nd_sll=%(MAC)s," "actions=normal") - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=135,actions=normal") - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,nd_sll=%(VIF_MAC)s," + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=135,actions=normal") + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=135,nd_sll=%(MAC)s," "actions=normal") - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=135,actions=normal") + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=135,actions=normal") # Neighbor Advertisement - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136," - "nd_target=%(VIF_LOCAL_IPv6)s,actions=normal") - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp_type=136,actions=normal") - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136," - "nd_target=%(VIF_GLOBAL_IPv6)s,actions=normal") - ovs.add("priority=6,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s,icmp6," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp_type=136,actions=normal") - - # drop all other neighbor discovery (required because we permit all icmp6 below) - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=135,actions=drop") - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=136,actions=drop") + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=136," + "nd_target=%(IPV6_LINK_LOCAL_ADDR)s,actions=normal") + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp_type=136,actions=normal") + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=136," + "nd_target=%(IPV6_GLOBAL_ADDR)s,actions=normal") + ovs.add("priority=6,in_port=%(OF_PORT)s,dl_src=%(MAC)s,icmp6," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp_type=136,actions=normal") + + # drop all other neighbor discovery (req b/c we permit all icmp6 below) + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=135,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=136,actions=drop") # do not allow sending specifc ICMPv6 types # Router Advertisement - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=134,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=134,actions=drop") # Redirect Gateway - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=137,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=137,actions=drop") # Mobile Prefix Solicitation - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=146,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=146,actions=drop") # Mobile Prefix Advertisement - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=147,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=147,actions=drop") # Multicast Router Advertisement - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=151,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=151,actions=drop") # Multicast Router Solicitation - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=152,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=152,actions=drop") # Multicast Router Termination - ovs.add("priority=5,in_port=%(VIF_OFPORT)s,icmp6,icmp_type=153,actions=drop") + ovs.add("priority=5,in_port=%(OF_PORT)s,icmp6,icmp_type=153,actions=drop") # allow valid IPv6 outbound, by type - ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,icmp6,actions=normal") - ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,icmp6,actions=normal") - ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,tcp6,actions=normal") - ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,tcp6,actions=normal") - ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_GLOBAL_IPv6)s,udp6,actions=normal") - ovs.add("priority=4,in_port=%(VIF_OFPORT)s,dl_src=%(VIF_MAC)s," - "ipv6_src=%(VIF_LOCAL_IPv6)s,udp6,actions=normal") + ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,icmp6,actions=normal") + ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,icmp6,actions=normal") + ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,tcp6,actions=normal") + ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,tcp6,actions=normal") + ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s," + "ipv6_src=%(IPV6_GLOBAL_ADDR)s,udp6,actions=normal") + ovs.add("priority=4,in_port=%(OF_PORT)s,dl_src=%(MAC)s," + "ipv6_src=%(IPV6_LINK_LOCAL_ADDR)s,udp6,actions=normal") # all else will be dropped ... if __name__ == "__main__": if len(sys.argv) < 3: - print "usage: %s dom_id online|offline|reset ipv4|ipv6|all [vif_name]" % \ + print "usage: %s dom_id online|offline|reset ipv4|ipv6|all [vif]" % \ os.path.basename(sys.argv[0]) sys.exit(1) else: -- cgit From 3ee0507ddc6bb7e15834144acc47c354396fbc70 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 5 May 2011 23:14:46 -0400 Subject: Publish errors via nova.notifier --- nova/log.py | 9 +++++++++ nova/tests/test_notifier.py | 21 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/nova/log.py b/nova/log.py index 096279f7c..3e587891a 100644 --- a/nova/log.py +++ b/nova/log.py @@ -35,6 +35,7 @@ import os import sys import traceback +import nova from nova import flags from nova import version @@ -63,6 +64,7 @@ flags.DEFINE_list('default_log_levels', 'eventlet.wsgi.server=WARN'], 'list of logger=LEVEL pairs') flags.DEFINE_bool('use_syslog', False, 'output to syslog') +flags.DEFINE_bool('publish_errors', True, 'publish error events') flags.DEFINE_string('logfile', None, 'output to named file') @@ -258,12 +260,19 @@ class NovaRootLogger(NovaLogger): else: self.removeHandler(self.filelog) self.addHandler(self.streamlog) + if FLAGS.publish_errors: + self.addHandler(PublishErrorsHandler(ERROR)) if FLAGS.verbose: self.setLevel(DEBUG) else: self.setLevel(INFO) +class PublishErrorsHandler(logging.Handler): + def emit(self, record): + nova.notifier.notify('error', record) + + def handle_exception(type, value, tb): extra = {} if FLAGS.verbose: diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 4d6289e6a..d18d3bc05 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,14 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. -import nova +import json + +import stubout +import nova +from nova import log as logging from nova import flags from nova import notifier from nova.notifier import no_op_notifier from nova import test -import stubout +LOG = logging.getLogger('nova.compute.api') class NotifierTestCase(test.TestCase): """Test case for notifications""" @@ -58,3 +62,16 @@ class NotifierTestCase(test.TestCase): notifier.notify('derp', Mock()) self.assertEqual(self.mock_cast, True) + + def test_error_notification(self): + self.stubs.Set(nova.flags.FLAGS, 'notification_driver', + 'nova.notifier.rabbit_notifier.RabbitNotifier') + msgs = [] + def mock_cast(context, topic, msg): + data = json.loads(msg) + msgs.append(data) + self.stubs.Set(nova.rpc, 'cast', mock_cast) + LOG.error('foo'); + msg = msgs[0] + self.assertEqual(msg['event_name'], 'error') + self.assertEqual(msg['model']['msg'], 'foo') -- cgit From 65595766706631a5c65193cfc0fa2ac9de1aeffc Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 6 May 2011 20:15:06 -0400 Subject: Set publish_errors default to False. --- nova/log.py | 2 +- nova/tests/test_notifier.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/log.py b/nova/log.py index 3e587891a..d2ed82c6c 100644 --- a/nova/log.py +++ b/nova/log.py @@ -64,7 +64,7 @@ flags.DEFINE_list('default_log_levels', 'eventlet.wsgi.server=WARN'], 'list of logger=LEVEL pairs') flags.DEFINE_bool('use_syslog', False, 'output to syslog') -flags.DEFINE_bool('publish_errors', True, 'publish error events') +flags.DEFINE_bool('publish_errors', False, 'publish error events') flags.DEFINE_string('logfile', None, 'output to named file') diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index d18d3bc05..c9c4ddde8 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -18,14 +18,12 @@ import json import stubout import nova -from nova import log as logging +from nova import log from nova import flags from nova import notifier from nova.notifier import no_op_notifier from nova import test -LOG = logging.getLogger('nova.compute.api') - class NotifierTestCase(test.TestCase): """Test case for notifications""" def setUp(self): @@ -66,12 +64,17 @@ class NotifierTestCase(test.TestCase): def test_error_notification(self): self.stubs.Set(nova.flags.FLAGS, 'notification_driver', 'nova.notifier.rabbit_notifier.RabbitNotifier') + self.stubs.Set(nova.flags.FLAGS, 'publish_errors', True) + LOG = log.getLogger('nova') + LOG.setup_from_flags() + msgs = [] def mock_cast(context, topic, msg): data = json.loads(msg) msgs.append(data) self.stubs.Set(nova.rpc, 'cast', mock_cast) LOG.error('foo'); + self.assertEqual(1, len(msgs)) msg = msgs[0] self.assertEqual(msg['event_name'], 'error') self.assertEqual(msg['model']['msg'], 'foo') -- cgit From 6c4059f20c85e9bc013a340de167151e7b5fa8c4 Mon Sep 17 00:00:00 2001 From: Mike Scherbakov Date: Wed, 11 May 2011 03:24:02 +0400 Subject: Bugfix #780784. KeyError when creating custom image. --- Authors | 1 + nova/virt/libvirt_conn.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Authors b/Authors index 60e1d2dad..72eb0b6ae 100644 --- a/Authors +++ b/Authors @@ -54,6 +54,7 @@ Mark Washenberger Masanori Itoh Matt Dietz Michael Gundlach +Mike Scherbakov Monsyne Dragon Monty Taylor MORITA Kazutaka diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9780c69a6..71cedae54 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -456,7 +456,8 @@ class LibvirtConnection(driver.ComputeDriver): 'container_format': base['container_format'], 'is_public': False, 'name': '%s.%s' % (base['name'], image_id), - 'properties': {'architecture': base['architecture'], + 'properties': {'architecture': + base['properties']['architecture'], 'kernel_id': instance['kernel_id'], 'image_location': 'snapshot', 'image_state': 'available', -- cgit From a7c25a19a9a2fdf89fc9ecd3992ded936923af18 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 13 May 2011 14:21:55 +0000 Subject: Add init script and sysconfig file for openvswitch-nova --- .../networking/etc/init.d/openvswitch-nova | 96 ++++++++++++++++++++++ .../networking/etc/sysconfig/openvswitch-nova | 1 + .../xensource/scripts/ovs_configure_base_flows.py | 35 ++++---- 3 files changed, 116 insertions(+), 16 deletions(-) create mode 100755 plugins/xenserver/networking/etc/init.d/openvswitch-nova create mode 100644 plugins/xenserver/networking/etc/sysconfig/openvswitch-nova diff --git a/plugins/xenserver/networking/etc/init.d/openvswitch-nova b/plugins/xenserver/networking/etc/init.d/openvswitch-nova new file mode 100755 index 000000000..e4dbdf4af --- /dev/null +++ b/plugins/xenserver/networking/etc/init.d/openvswitch-nova @@ -0,0 +1,96 @@ +#!/bin/bash +# +# openvswitch-nova +# +# chkconfig: 2345 10 89 +# description: Apply initial OVS flows for Nova + +# Copyright 2011 OpenStack LLC. +# Copyright (C) 2009, 2010, 2011 Nicira Networks, Inc. +# 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. + +# source function library +if [ -f /etc/init.d/functions ]; then + . /etc/init.d/functions +elif [ -f /etc/rc.d/init.d/functions ]; then + . /etc/rc.d/init.d/functions +elif [ -f /lib/lsb/init-functions ]; then + . /lib/lsb/init-functions +else + echo "$0: missing LSB shell function library" >&2 + exit 1 +fi + +OVS_CONFIGURE_BASE_FLOWS=/etc/xensource/scripts/ovs_configure_base_flows.py + +if test -e /etc/sysconfig/openvswitch-nova; then + . /etc/sysconfig/openvswitch-nova +else + echo "$0: missing configuration file: /etc/sysconfig/openvswitch-nova" + exit 1 +fi + +if test -e /etc/xensource/network.conf; then + NETWORK_MODE=$(cat /etc/xensource/network.conf) +fi + +case ${NETWORK_MODE:=openvswitch} in + vswitch|openvswitch) + ;; + bridge) + exit 0 + ;; + *) + echo "Open vSwitch disabled (/etc/xensource/network.conf is invalid)" >&2 + exit 0 + ;; +esac + +function run_ovs_conf_base_flows { + # expected format: DEVICE_BRIDGES="eth0:xenbr0 eth1:xenbr1" + for pair in $DEVICE_BRIDGES; do + # below in $info, physical device is [0], bridge name is [1] + info=${pair//:/ } + /usr/bin/python $OVS_CONFIGURE_BASE_FLOWS $1 ${info[0]} ${info[1]} + done +} + +function start { + run_ovs_conf_base_flows online +} + +function stop { + run_ovs_conf_base_flows offline +} + +function restart { + run_ovs_conf_base_flows reset +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + *) + echo "usage: openvswitch-nova [start|stop|restart]" + exit 1 + ;; +esac diff --git a/plugins/xenserver/networking/etc/sysconfig/openvswitch-nova b/plugins/xenserver/networking/etc/sysconfig/openvswitch-nova new file mode 100644 index 000000000..829782fb6 --- /dev/null +++ b/plugins/xenserver/networking/etc/sysconfig/openvswitch-nova @@ -0,0 +1 @@ +#DEVICE_BRIDGES="eth0:xenbr0 eth1:xenbr1" diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py index 82d0b9e31..0186a3c8b 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -27,32 +27,35 @@ import sys from novalib import execute, execute_get_output -def main(phys_dev_name, bridge_name): - pnic_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', 'Interface', - phys_dev_name, 'ofport') +def main(command, phys_dev_name, bridge_name): ovs_ofctl = lambda *rule: execute('/usr/bin/ovs-ofctl', *rule) - # clear all flows + # always clear all flows first ovs_ofctl('del-flows', bridge_name) - # these flows are lower priority than all VM-specific flows. + if command in ('online', 'reset'): + pnic_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', 'Interface', + phys_dev_name, 'ofport') - # allow all traffic from the physical NIC, as it is trusted (i.e., from a - # filtered vif, or from the physical infrastructure - ovs_ofctl('add-flow', bridge_name, - "priority=2,in_port=%s,actions=normal" % pnic_ofport) + # these flows are lower priority than all VM-specific flows. - # default drop - ovs_ofctl('add-flow', bridge_name, 'priority=1,actions=drop') + # allow all traffic from the physical NIC, as it is trusted (i.e., from a + # filtered vif, or from the physical infrastructure + ovs_ofctl('add-flow', bridge_name, + "priority=2,in_port=%s,actions=normal" % pnic_ofport) + + # default drop + ovs_ofctl('add-flow', bridge_name, 'priority=1,actions=drop') if __name__ == "__main__": - if len(sys.argv) != 3: + if len(sys.argv) != 4 or sys.argv[1] not in ('online', 'offline', 'reset'): + print sys.argv script_name = os.path.basename(sys.argv[0]) print "This script configures base ovs flows." - print "usage: %s phys-dev-name bridge-name" % script_name - print " ex: %s eth0 xenbr0" % script_name + print "usage: %s [online|offline|reset] phys-dev-name bridge-name" % script_name + print " ex: %s online eth0 xenbr0" % script_name sys.exit(1) else: - phys_dev_name, bridge_name = sys.argv[1:3] - main(phys_dev_name, bridge_name) + command, phys_dev_name, bridge_name = sys.argv[1:4] + main(command, phys_dev_name, bridge_name) -- cgit From bccbe3f845e9e7661efefbe456bfa56144de8136 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 13 May 2011 19:29:10 +0000 Subject: add udev rules and modified ovs_configure_vif_flows.py to work with udev rules --- .../etc/udev/rules.d/openvswitch-nova.rules | 3 +++ .../xensource/scripts/ovs_configure_vif_flows.py | 28 ++++++++++++---------- 2 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules diff --git a/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules b/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules new file mode 100644 index 000000000..0dfb029eb --- /dev/null +++ b/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules @@ -0,0 +1,3 @@ +SUBSYSTEM=="xen-backend", KERNEL=="vif*", RUN+="/etc/xensource/scripts/ovs_configure_base_flows.py $env{ACTION} %k all" +# is this one needed? +#SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/etc/xensource/scripts/ovs_configure_base_flows.py $env{ACTION} %k all" diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index f91a5f49d..95a944a2b 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -45,7 +45,14 @@ class OvsFlow(object): execute(OVS_OFCTL, 'del-flows', self.bridge, "in_port=%s" % ofport) -def main(dom_id, command, net_type, only_this_vif=None): +def main(command, vif_raw, net_type): + if command not in ('online', 'offline'): + return + + vif_name, dom_id, vif_index = vif_raw.split('-') + vif = "%s%s.%s" % (vif_name, dom_id, vif_index) + bridge = "xenbr%s" % vif_index + xsls = execute_get_output('/usr/bin/xenstore-ls', '/local/domain/%s/vm-data/networking' % dom_id) macs = [line.split("=")[0].strip() for line in xsls.splitlines()] @@ -56,13 +63,11 @@ def main(dom_id, command, net_type, only_this_vif=None): (dom_id, mac)) data = json.loads(xsread) if data["label"] == "public": - vif = "vif%s.0" % dom_id - bridge = "xenbr0" + this_vif = "vif%s.0" % dom_id else: - vif = "vif%s.1" % dom_id - bridge = "xenbr1" + this_vif = "vif%s.1" % dom_id - if (only_this_vif is None) or (vif == only_this_vif): + if vif == this_vif: vif_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', 'Interface', vif, 'ofport') @@ -72,11 +77,11 @@ def main(dom_id, command, net_type, only_this_vif=None): ovs = OvsFlow(bridge, params) - if command in ('offline', 'reset'): + if command == 'offline': # I haven't found a way to clear only IPv4 or IPv6 rules. ovs.clear_flows(vif_ofport) - if command in ('online', 'reset'): + if command == 'online': if net_type in ('ipv4', 'all') and 'ips' in data: for ip4 in data['ips']: ovs.params.update({'IPV4_ADDR': ip4['ip']}) @@ -167,10 +172,9 @@ def apply_ovs_ipv6_flows(ovs, bridge, params): if __name__ == "__main__": if len(sys.argv) < 3: - print "usage: %s dom_id online|offline|reset ipv4|ipv6|all [vif]" % \ + print "usage: %s [online|offline] vif-domid-idx ipv4|ipv6|all " % \ os.path.basename(sys.argv[0]) sys.exit(1) else: - dom_id, command, net_type = sys.argv[1:4] - vif_name = len(sys.argv) == 5 and sys.argv[4] or None - main(dom_id, command, net_type, vif_name) + command, vif_raw, net_type = sys.argv[1:4] + main(command, vif_raw, net_type) -- cgit From fd2861fdcdae0d7a3f13dac7b54d4d8f106f2b3e Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 13 May 2011 21:05:12 +0000 Subject: fix sys.argv requirement --- .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 95a944a2b..2ebc4dd8c 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -171,7 +171,7 @@ def apply_ovs_ipv6_flows(ovs, bridge, params): if __name__ == "__main__": - if len(sys.argv) < 3: + if len(sys.argv) != 4: print "usage: %s [online|offline] vif-domid-idx ipv4|ipv6|all " % \ os.path.basename(sys.argv[0]) sys.exit(1) -- cgit From f889f6c8a430d6411a81270a68025a27781b09a2 Mon Sep 17 00:00:00 2001 From: Mike Scherbakov Date: Sun, 15 May 2011 14:15:37 +0400 Subject: Unit test for snapshotting (creating custom image). --- nova/image/fake.py | 33 +++++++++++++++++++++------- nova/tests/test_virt.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/nova/image/fake.py b/nova/image/fake.py index b400b2adb..4bf25d9af 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -19,6 +19,7 @@ import copy import datetime +import random from nova import exception from nova import flags @@ -32,7 +33,7 @@ LOG = logging.getLogger('nova.image.fake') FLAGS = flags.FLAGS -class FakeImageService(service.BaseImageService): +class _FakeImageService(service.BaseImageService): """Mock (fake) image service for unit testing.""" def __init__(self): @@ -48,9 +49,10 @@ class FakeImageService(service.BaseImageService): 'container_format': 'ami', 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, - 'ramdisk_id': FLAGS.null_kernel}} + 'ramdisk_id': FLAGS.null_kernel, + 'architecture': 'x86_64'}} self.create(None, image) - super(FakeImageService, self).__init__() + super(_FakeImageService, self).__init__() def index(self, context): """Returns list of images.""" @@ -74,19 +76,28 @@ class FakeImageService(service.BaseImageService): image_id, self.images) raise exception.ImageNotFound(image_id=image_id) - def create(self, context, data): + def create(self, context, metadata, data=None): """Store the image data and return the new image id. :raises: Duplicate if the image already exist. """ - image_id = int(data['id']) + #image_id = int(metadata['id']) + # metadata['id'] may not exists, and since image_id is + # randomly generated in local.py, let us do the same here + try: + image_id = int(metadata['id']) + except: + image_id = random.randint(0, 2 ** 31 - 1) + if self.images.get(image_id): raise exception.Duplicate() - self.images[image_id] = copy.deepcopy(data) + metadata['id'] = image_id + self.images[image_id] = copy.deepcopy(metadata) + return self.images[image_id] - def update(self, context, image_id, data): + def update(self, context, image_id, metadata, data=None): """Replace the contents of the given image with the new data. :raises: ImageNotFound if the image does not exist. @@ -95,7 +106,7 @@ class FakeImageService(service.BaseImageService): image_id = int(image_id) if not self.images.get(image_id): raise exception.ImageNotFound(image_id=image_id) - self.images[image_id] = copy.deepcopy(data) + self.images[image_id] = copy.deepcopy(metadata) def delete(self, context, image_id): """Delete the given image. @@ -111,3 +122,9 @@ class FakeImageService(service.BaseImageService): def delete_all(self): """Clears out all images.""" self.images.clear() + +_fakeImageService = _FakeImageService() + + +def FakeImageService(): + return _fakeImageService diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1311ba361..eb238e871 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -159,6 +159,7 @@ class LibvirtConnTestCase(test.TestCase): 'vcpus': 2, 'project_id': 'fake', 'bridge': 'br101', + 'image_id': '123456', 'instance_type_id': '5'} # m1.small def lazy_load_library_exists(self): @@ -279,6 +280,62 @@ class LibvirtConnTestCase(test.TestCase): instance_data = dict(self.test_instance) self._check_xml_and_container(instance_data) + def test_snapshot(self): + FLAGS.image_service = 'nova.image.fake.FakeImageService' + + # Only file-based instance storages are supported at the moment + test_xml = """ + + + + + + + + """ + + class FakeVirtDomain(object): + + def __init__(self): + pass + + def snapshotCreateXML(self, *args): + return None + + def XMLDesc(self, *args): + return test_xml + + def fake_lookup(instance_name): + if instance_name == instance_ref.name: + return FakeVirtDomain() + + def fake_execute(*args): + # Touch filename to pass 'with open(out_path)' + open(args[-1], "a").close() + + # Start test + image_service = utils.import_object(FLAGS.image_service) + + # Assuming that base image already exists in image_service + instance_ref = db.instance_create(self.context, self.test_instance) + properties = {'instance_id': instance_ref['id'], + 'user_id': str(self.context.user_id)} + sent_meta = {'name': 'test-snap', 'is_public': False, + 'properties': properties} + # Create new image. It will be updated in snapshot method + # To work with it from snapshot, the single image_service is needed + recv_meta = image_service.create(context, sent_meta) + + self.mox.StubOutWithMock(libvirt_conn.LibvirtConnection, '_conn') + libvirt_conn.LibvirtConnection._conn.lookupByName = fake_lookup + self.mox.StubOutWithMock(libvirt_conn.utils, 'execute') + libvirt_conn.utils.execute = fake_execute + + self.mox.ReplayAll() + + conn = libvirt_conn.LibvirtConnection(False) + conn.snapshot(instance_ref, recv_meta['id']) + def test_multi_nic(self): instance_data = dict(self.test_instance) network_info = _create_network_info(2) -- cgit From 818c2424a0547882fe6bdfe6613ee66a248d91db Mon Sep 17 00:00:00 2001 From: Mike Scherbakov Date: Sun, 15 May 2011 15:11:54 +0400 Subject: Define image state during snapshotting. Name snapshot to the name provided, not generate. --- nova/compute/api.py | 5 +++-- nova/tests/test_virt.py | 10 ++++++++-- nova/virt/libvirt_conn.py | 4 +++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 63884be97..971c0732f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -500,9 +500,10 @@ class API(base.Base): """ properties = {'instance_id': str(instance_id), - 'user_id': str(context.user_id)} + 'user_id': str(context.user_id), + 'image_state': 'creating'} sent_meta = {'name': name, 'is_public': False, - 'properties': properties} + 'status': 'creating', 'properties': properties} recv_meta = self.image_service.create(context, sent_meta) params = {'image_id': recv_meta['id']} self._cast_compute_message('snapshot_instance', context, instance_id, diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index eb238e871..c4fcc21cc 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -320,8 +320,9 @@ class LibvirtConnTestCase(test.TestCase): instance_ref = db.instance_create(self.context, self.test_instance) properties = {'instance_id': instance_ref['id'], 'user_id': str(self.context.user_id)} - sent_meta = {'name': 'test-snap', 'is_public': False, - 'properties': properties} + snapshot_name = 'test-snap' + sent_meta = {'name': snapshot_name, 'is_public': False, + 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed recv_meta = image_service.create(context, sent_meta) @@ -336,6 +337,11 @@ class LibvirtConnTestCase(test.TestCase): conn = libvirt_conn.LibvirtConnection(False) conn.snapshot(instance_ref, recv_meta['id']) + snapshot = image_service.show(context, recv_meta['id']) + self.assertEquals(snapshot['properties']['image_state'], 'available') + self.assertEquals(snapshot['status'], 'active') + self.assertEquals(snapshot['name'], snapshot_name) + def test_multi_nic(self): instance_data = dict(self.test_instance) network_info = _create_network_info(2) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 71cedae54..92d580314 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -451,11 +451,13 @@ class LibvirtConnection(driver.ComputeDriver): elevated = context.get_admin_context() base = image_service.show(elevated, instance['image_id']) + snapshot = image_service.show(elevated, image_id) metadata = {'disk_format': base['disk_format'], 'container_format': base['container_format'], 'is_public': False, - 'name': '%s.%s' % (base['name'], image_id), + 'status': 'active', + 'name': snapshot['name'], 'properties': {'architecture': base['properties']['architecture'], 'kernel_id': instance['kernel_id'], -- cgit From e6fc2fc58d2c98f4322e92b26b1031ca362c8724 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 17 May 2011 15:14:17 -0700 Subject: add more statuses for ec2 image registration --- nova/image/s3.py | 84 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/nova/image/s3.py b/nova/image/s3.py index c38c58d95..673cbf56f 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -161,43 +161,75 @@ class S3ImageService(service.BaseImageService): def delayed_create(): """This handles the fetching and decrypting of the part files.""" - parts = [] - for fn_element in manifest.find('image').getiterator('filename'): - part = self._download_file(bucket, fn_element.text, image_path) - parts.append(part) - - # NOTE(vish): this may be suboptimal, should we use cat? - encrypted_filename = os.path.join(image_path, 'image.encrypted') - with open(encrypted_filename, 'w') as combined: - for filename in parts: - with open(filename) as part: - shutil.copyfileobj(part, combined) - - metadata['properties']['image_state'] = 'decrypting' + metadata['properties']['image_state'] = 'downloading' self.service.update(context, image_id, metadata) - hex_key = manifest.find('image/ec2_encrypted_key').text - encrypted_key = binascii.a2b_hex(hex_key) - hex_iv = manifest.find('image/ec2_encrypted_iv').text - encrypted_iv = binascii.a2b_hex(hex_iv) + try: + parts = [] + elements = manifest.find('image').getiterator('filename') + for fn_element in elements: + part = self._download_file(bucket, + fn_element.text, + image_path) + parts.append(part) + + # NOTE(vish): this may be suboptimal, should we use cat? + enc_filename = os.path.join(image_path, 'image.encrypted') + with open(enc_filename, 'w') as combined: + for filename in parts: + with open(filename) as part: + shutil.copyfileobj(part, combined) + + except Exception: + metadata['properties']['image_state'] = 'failed_download' + self.service.update(context, image_id, metadata) + raise - # FIXME(vish): grab key from common service so this can run on - # any host. - cloud_pk = crypto.key_path(context.project_id) + metadata['properties']['image_state'] = 'decrypting' + self.service.update(context, image_id, metadata) - decrypted_filename = os.path.join(image_path, 'image.tar.gz') - self._decrypt_image(encrypted_filename, encrypted_key, - encrypted_iv, cloud_pk, decrypted_filename) + try: + hex_key = manifest.find('image/ec2_encrypted_key').text + encrypted_key = binascii.a2b_hex(hex_key) + hex_iv = manifest.find('image/ec2_encrypted_iv').text + encrypted_iv = binascii.a2b_hex(hex_iv) + + # FIXME(vish): grab key from common service so this can run on + # any host. + cloud_pk = crypto.key_path(context.project_id) + + dec_filename = os.path.join(image_path, 'image.tar.gz') + self._decrypt_image(enc_filename, encrypted_key, + encrypted_iv, cloud_pk, + dec_filename) + except Exception: + metadata['properties']['image_state'] = 'failed_decrypt' + self.service.update(context, image_id, metadata) + raise metadata['properties']['image_state'] = 'untarring' self.service.update(context, image_id, metadata) - unz_filename = self._untarzip_image(image_path, decrypted_filename) + try: + unz_filename = self._untarzip_image(image_path, dec_filename) + except Exception: + metadata['properties']['image_state'] = 'failed_untar' + self.service.update(context, image_id, metadata) + raise metadata['properties']['image_state'] = 'uploading' - with open(unz_filename) as image_file: - self.service.update(context, image_id, metadata, image_file) + self.service.update(context, image_id, metadata) + try: + with open(unz_filename) as image_file: + self.service.update(context, image_id, + metadata, image_file) + except Exception: + metadata['properties']['image_state'] = 'failed_upload' + self.service.update(context, image_id, metadata) + raise + metadata['properties']['image_state'] = 'available' + metadata['status'] = 'active' self.service.update(context, image_id, metadata) shutil.rmtree(image_path) -- cgit From 2c6c184138b0d8c650496e0e8d033c85a2e2dec1 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Wed, 18 May 2011 20:46:21 +0000 Subject: fix typo in udev rule --- plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules b/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules index 0dfb029eb..b179f0847 100644 --- a/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules +++ b/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules @@ -1,3 +1,3 @@ -SUBSYSTEM=="xen-backend", KERNEL=="vif*", RUN+="/etc/xensource/scripts/ovs_configure_base_flows.py $env{ACTION} %k all" +SUBSYSTEM=="xen-backend", KERNEL=="vif*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all" # is this one needed? -#SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/etc/xensource/scripts/ovs_configure_base_flows.py $env{ACTION} %k all" +#SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all" -- cgit From ef42fa95197e7b0f73e04322456bbbdedaf3e2b3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 18 May 2011 14:15:36 -0700 Subject: log any exceptions that get thrown trying to retrieve metadata --- nova/api/ec2/metadatarequesthandler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 28f99b0ef..481e34e12 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -71,7 +71,11 @@ class MetadataRequestHandler(wsgi.Application): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) - meta_data = cc.get_metadata(remote_address) + try: + meta_data = cc.get_metadata(remote_address) + except Exception: + LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) + raise if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() -- cgit From 38ba122d9eb67c699ea0c10eab5961c3b4c25d81 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 18 May 2011 14:23:09 -0700 Subject: use a manual 500 with error text instead of traceback for failure --- nova/api/ec2/metadatarequesthandler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 481e34e12..720f264a4 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -23,6 +23,7 @@ import webob.exc from nova import log as logging from nova import flags +from nova import utils from nova import wsgi from nova.api.ec2 import cloud @@ -75,7 +76,12 @@ class MetadataRequestHandler(wsgi.Application): meta_data = cc.get_metadata(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) - raise + resp = webob.Response() + resp.status = 500 + message = _('An unknown error has occurred. ' + 'Please try your request again.') + resp.body = str(utils.utf8(message)) + return resp if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() -- cgit From 5e722ea7b912f189c0a3b9434e9a38d08095ad00 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 18 May 2011 19:13:22 -0400 Subject: refactoring wsgi to separate controller/serialization/deserialization logic; creating osapi-specific module --- nova/api/openstack/__init__.py | 43 ++--- nova/api/openstack/accounts.py | 33 ++-- nova/api/openstack/backup_schedules.py | 27 ++- nova/api/openstack/consoles.py | 26 ++- nova/api/openstack/flavors.py | 34 ++-- nova/api/openstack/image_metadata.py | 19 +- nova/api/openstack/images.py | 40 +++-- nova/api/openstack/ips.py | 33 ++-- nova/api/openstack/limits.py | 50 ++++-- nova/api/openstack/server_metadata.py | 21 ++- nova/api/openstack/servers.py | 124 +++++++------ nova/api/openstack/shared_ip_groups.py | 28 +-- nova/api/openstack/users.py | 43 +++-- nova/api/openstack/wsgi.py | 291 +++++++++++++++++++++++++++++++ nova/api/openstack/zones.py | 33 ++-- nova/tests/api/openstack/test_limits.py | 4 +- nova/tests/api/openstack/test_servers.py | 2 - nova/tests/api/test_wsgi.py | 135 -------------- nova/tests/integrated/test_xml.py | 4 +- 19 files changed, 624 insertions(+), 366 deletions(-) create mode 100644 nova/api/openstack/wsgi.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 348b70d5b..fbbd99cb9 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -26,7 +26,7 @@ import webob.exc from nova import flags from nova import log as logging -from nova import wsgi +from nova import wsgi as base_wsgi from nova.api.openstack import accounts from nova.api.openstack import faults from nova.api.openstack import backup_schedules @@ -40,6 +40,7 @@ from nova.api.openstack import servers from nova.api.openstack import server_metadata from nova.api.openstack import shared_ip_groups from nova.api.openstack import users +from nova.api.openstack import wsgi from nova.api.openstack import zones @@ -50,7 +51,7 @@ flags.DEFINE_bool('allow_admin_api', 'When True, this API service will accept admin operations.') -class FaultWrapper(wsgi.Middleware): +class FaultWrapper(base_wsgi.Middleware): """Calls down the middleware stack, making exceptions into faults.""" @webob.dec.wsgify(RequestClass=wsgi.Request) @@ -63,7 +64,7 @@ class FaultWrapper(wsgi.Middleware): return faults.Fault(exc) -class APIRouter(wsgi.Router): +class APIRouter(base_wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller and method. @@ -97,18 +98,20 @@ class APIRouter(wsgi.Router): server_members['reset_network'] = 'POST' server_members['inject_network_info'] = 'POST' - mapper.resource("zone", "zones", controller=zones.Controller(), + mapper.resource("zone", "zones", + controller=zones.resource_factory(), collection={'detail': 'GET', 'info': 'GET'}), - mapper.resource("user", "users", controller=users.Controller(), + mapper.resource("user", "users", + controller=users.resource_factory(), collection={'detail': 'GET'}) mapper.resource("account", "accounts", - controller=accounts.Controller(), + controller=accounts.resource_factory(), collection={'detail': 'GET'}) mapper.resource("console", "consoles", - controller=consoles.Controller(), + controller=consoles.resource_factory(), parent_resource=dict(member_name='server', collection_name='servers')) @@ -121,31 +124,31 @@ class APIRouterV10(APIRouter): def _setup_routes(self, mapper): super(APIRouterV10, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.ControllerV10(), + controller=servers.resource_factory('1.0'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.ControllerV10(), + controller=images.resource_factory('1.0'), collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", - controller=flavors.ControllerV10(), + controller=flavors.resource_factory('1.0'), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, - controller=shared_ip_groups.Controller()) + controller=shared_ip_groups.resource_factory()) mapper.resource("backup_schedule", "backup_schedule", - controller=backup_schedules.Controller(), + controller=backup_schedules.resource_factory(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("limit", "limits", - controller=limits.LimitsControllerV10()) + controller=limits.resource_factory('1.0')) - mapper.resource("ip", "ips", controller=ips.Controller(), + mapper.resource("ip", "ips", controller=ips.resource_factory(), collection=dict(public='GET', private='GET'), parent_resource=dict(member_name='server', collection_name='servers')) @@ -157,27 +160,27 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.ControllerV11(), + controller=servers.resource_factory('1.1'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.ControllerV11(), + controller=images.resource_factory('1.1'), collection={'detail': 'GET'}) mapper.resource("image_meta", "meta", - controller=image_metadata.Controller(), + controller=image_metadata.resource_factory(), parent_resource=dict(member_name='image', collection_name='images')) mapper.resource("server_meta", "meta", - controller=server_metadata.Controller(), + controller=server_metadata.resource_factory(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("flavor", "flavors", - controller=flavors.ControllerV11(), + controller=flavors.resource_factory('1.1'), collection={'detail': 'GET'}) mapper.resource("limit", "limits", - controller=limits.LimitsControllerV11()) + controller=limits.resource_factory('1.1')) diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 00fdd4540..d8a9d1909 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -20,8 +20,9 @@ from nova import flags from nova import log as logging from nova.auth import manager -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi + FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack') @@ -34,12 +35,7 @@ def _translate_keys(account): manager=account.project_manager_id) -class Controller(common.OpenstackController): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "account": ["id", "name", "description", "manager"]}}} +class Controller(object): def __init__(self): self.manager = manager.AuthManager() @@ -66,20 +62,33 @@ class Controller(common.OpenstackController): self.manager.delete_project(id) return {} - def create(self, req): + def create(self, req, body): """We use update with create-or-update semantics because the id comes from an external source""" raise faults.Fault(webob.exc.HTTPNotImplemented()) - def update(self, req, id): + def update(self, req, id, body): """This is really create or update.""" self._check_admin(req.environ['nova.context']) - env = self._deserialize(req.body, req.get_content_type()) - description = env['account'].get('description') - manager = env['account'].get('manager') + description = body['account'].get('description') + manager = body['account'].get('manager') try: account = self.manager.get_project(id) self.manager.modify_project(id, manager, description) except exception.NotFound: account = self.manager.create_project(id, manager, description) return dict(account=_translate_keys(account)) + + +def resource_factory(): + metadata = { + "attributes": { + "account": ["id", "name", "description", "manager"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 4bf744046..4153c90c1 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,9 +19,8 @@ import time from webob import exc -from nova.api.openstack import common from nova.api.openstack import faults -import nova.image.service +from nova.api.openstack import wsgi def _translate_keys(inst): @@ -29,14 +28,9 @@ def _translate_keys(inst): return dict(backupSchedule=inst) -class Controller(common.OpenstackController): +class Controller(object): """ The backup schedule API controller for the Openstack API """ - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'backupSchedule': []}}} - def __init__(self): pass @@ -48,7 +42,7 @@ class Controller(common.OpenstackController): """ Returns a single backup schedule for a given instance """ return faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, server_id): + def create(self, req, server_id, body): """ No actual update method required, since the existing API allows both create and update through a POST """ return faults.Fault(exc.HTTPNotImplemented()) @@ -56,3 +50,18 @@ class Controller(common.OpenstackController): def delete(self, req, server_id, id): """ Deletes an existing backup schedule """ return faults.Fault(exc.HTTPNotImplemented()) + + +def resource_factory(): + metadata = { + 'attributes': { + 'backupSchedule': [], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 1a77f25d7..36d570803 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -19,8 +19,8 @@ from webob import exc from nova import console from nova import exception -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi def _translate_keys(cons): @@ -43,14 +43,9 @@ def _translate_detail_keys(cons): return dict(console=info) -class Controller(common.OpenstackController): +class Controller(object): """The Consoles Controller for the Openstack API""" - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'console': []}}} - def __init__(self): self.console_api = console.API() super(Controller, self).__init__() @@ -63,9 +58,8 @@ class Controller(common.OpenstackController): return dict(consoles=[_translate_keys(console) for console in consoles]) - def create(self, req, server_id): + def create(self, req, server_id, body): """Creates a new console""" - #info = self._deserialize(req.body, req.get_content_type()) self.console_api.create_console( req.environ['nova.context'], int(server_id)) @@ -94,3 +88,17 @@ class Controller(common.OpenstackController): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + + +def resource_factory(): + metadata = { + 'attributes': { + 'console': [], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 4c5971cf6..46056a27a 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -19,22 +19,13 @@ import webob from nova import db from nova import exception -from nova.api.openstack import common from nova.api.openstack import views +from nova.api.openstack import wsgi -class Controller(common.OpenstackController): +class Controller(object): """Flavor controller for the OpenStack API.""" - _serialization_metadata = { - 'application/xml': { - "attributes": { - "flavor": ["id", "name", "ram", "disk"], - "link": ["rel", "type", "href"], - } - } - } - def index(self, req): """Return all flavors in brief.""" items = self._get_flavors(req, is_detail=False) @@ -71,14 +62,31 @@ class Controller(common.OpenstackController): class ControllerV10(Controller): + def _get_view_builder(self, req): return views.flavors.ViewBuilder() class ControllerV11(Controller): + def _get_view_builder(self, req): base_url = req.application_url return views.flavors.ViewBuilderV11(base_url) - def get_default_xmlns(self, req): - return common.XML_NS_V11 + +def resource_factory(version='1.0'): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=xmlns), + } + + return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 1eccc0174..ce0140265 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -21,19 +21,18 @@ from nova import flags from nova import quota from nova import utils from nova import wsgi -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(object): """The image metadata API controller for the Openstack API""" def __init__(self): self.image_service = utils.import_object(FLAGS.image_service) - super(Controller, self).__init__() def _get_metadata(self, context, image_id, image=None): if not image: @@ -64,9 +63,8 @@ class Controller(common.OpenstackController): else: return faults.Fault(exc.HTTPNotFound()) - def create(self, req, image_id): + def create(self, req, image_id, body): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) if 'metadata' in body: @@ -77,9 +75,8 @@ class Controller(common.OpenstackController): self.image_service.update(context, image_id, img, None) return dict(metadata=metadata) - def update(self, req, image_id, id): + def update(self, req, image_id, id, body): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -104,3 +101,11 @@ class Controller(common.OpenstackController): metadata.pop(id) img['properties'] = metadata self.image_service.update(context, image_id, img, None) + + +def resource_factory(): + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 34d4c27fc..e22854ebf 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -23,25 +23,16 @@ from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import images as images_view +from nova.api.openstack import wsgi LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(object): """Base `wsgi.Controller` for retrieving/displaying images.""" - _serialization_metadata = { - 'application/xml': { - "attributes": { - "image": ["id", "name", "updated", "created", "status", - "serverId", "progress"], - "link": ["rel", "type", "href"], - }, - }, - } - def __init__(self, image_service=None, compute_service=None): """Initialize new `ImageController`. @@ -153,3 +144,30 @@ class ControllerV11(Controller): def get_default_xmlns(self, req): return common.XML_NS_V11 + + +def resource_factory(version='1.0'): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + metadata = { + "attributes": { + "image": ["id", "name", "updated", "created", "status", + "serverId", "progress"], + "link": ["rel", "type", "href"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, + metadata=metadata), + } + + return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 778e9ba1a..24612eafb 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -20,23 +20,14 @@ import time from webob import exc import nova -import nova.api.openstack.views.addresses -from nova.api.openstack import common from nova.api.openstack import faults +import nova.api.openstack.views.addresses +from nova.api.openstack import wsgi -class Controller(common.OpenstackController): +class Controller(object): """The servers addresses API controller for the Openstack API.""" - _serialization_metadata = { - 'application/xml': { - 'list_collections': { - 'public': {'item_name': 'ip', 'item_key': 'addr'}, - 'private': {'item_name': 'ip', 'item_key': 'addr'}, - }, - }, - } - def __init__(self): self.compute_api = nova.compute.API() self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() @@ -65,8 +56,24 @@ class Controller(common.OpenstackController): def show(self, req, server_id, id): return faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, server_id): + def create(self, req, server_id, body): return faults.Fault(exc.HTTPNotImplemented()) def delete(self, req, server_id, id): return faults.Fault(exc.HTTPNotImplemented()) + + +def resource_factory(): + metadata = { + 'list_collections': { + 'public': {'item_name': 'ip', 'item_key': 'addr'}, + 'private': {'item_name': 'ip', 'item_key': 'addr'}, + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 47bc238f1..306048d8f 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -30,10 +30,11 @@ from collections import defaultdict from webob.dec import wsgify -from nova import wsgi +from nova import wsgi as base_wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import limits as limits_views +from nova.api.openstack import wsgi # Convenience constants for the limits dictionary passed to Limiter(). @@ -43,23 +44,11 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -class LimitsController(common.OpenstackController): +class LimitsController(object): """ Controller for accessing limits in the OpenStack API. """ - _serialization_metadata = { - "application/xml": { - "attributes": { - "limit": ["verb", "URI", "uri", "regex", "value", "unit", - "resetTime", "next-available", "remaining", "name"], - }, - "plurals": { - "rate": "limit", - }, - }, - } - def index(self, req): """ Return all global and rate limit information. @@ -84,6 +73,35 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() +def resource_factory(version='1.0'): + controller = { + '1.0': LimitsControllerV10, + '1.1': LimitsControllerV11, + }[version]() + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + metadata = { + "attributes": { + "limit": ["verb", "URI", "uri", "regex", "value", "unit", + "resetTime", "next-available", "remaining", "name"], + }, + "plurals": { + "rate": "limit", + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, + metadata=metadata) + } + + return wsgi.Resource(controller, serializers=serializers) + + class Limit(object): """ Stores information about a limit for HTTP requets. @@ -195,7 +213,7 @@ DEFAULT_LIMITS = [ ] -class RateLimitingMiddleware(wsgi.Middleware): +class RateLimitingMiddleware(base_wsgi.Middleware): """ Rate-limits requests passing through this middleware. All limit information is stored in memory for this implementation. @@ -209,7 +227,7 @@ class RateLimitingMiddleware(wsgi.Middleware): @param application: WSGI application to wrap @param limits: List of dictionaries describing limits """ - wsgi.Middleware.__init__(self, application) + base_wsgi.Middleware.__init__(self, application) self._limiter = Limiter(limits or DEFAULT_LIMITS) @wsgify(RequestClass=wsgi.Request) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index fd64ee4fb..fb9449b4c 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -19,12 +19,11 @@ from webob import exc from nova import compute from nova import quota -from nova import wsgi -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi -class Controller(common.OpenstackController): +class Controller(object): """ The server metadata API controller for the Openstack API """ def __init__(self): @@ -43,10 +42,9 @@ class Controller(common.OpenstackController): context = req.environ['nova.context'] return self._get_metadata(context, server_id) - def create(self, req, server_id): + def create(self, req, server_id, body): context = req.environ['nova.context'] - data = self._deserialize(req.body, req.get_content_type()) - metadata = data.get('metadata') + metadata = body.get('metadata') try: self.compute_api.update_or_create_instance_metadata(context, server_id, @@ -55,9 +53,8 @@ class Controller(common.OpenstackController): self._handle_quota_error(error) return req.body - def update(self, req, server_id, id): + def update(self, req, server_id, id, body): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -92,3 +89,11 @@ class Controller(common.OpenstackController): if error.code == "MetadataLimitExceeded": raise exc.HTTPBadRequest(explanation=error.message) raise error + + +def resource_factory(): + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8f2de2afe..78f8bb1b7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -31,6 +31,7 @@ import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images import nova.api.openstack.views.servers +from nova.api.openstack import wsgi from nova.auth import manager as auth_manager from nova.compute import instance_types import nova.api.openstack @@ -41,31 +42,12 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(object): """ The Server API controller for the OpenStack API """ - _serialization_metadata = { - "application/xml": { - "attributes": { - "server": ["id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "adminPass", "flavorRef", - "imageRef"], - "link": ["rel", "type", "href"], - }, - "dict_collections": { - "metadata": {"item_name": "meta", "item_key": "key"}, - }, - "list_collections": { - "public": {"item_name": "ip", "item_key": "addr"}, - "private": {"item_name": "ip", "item_key": "addr"}, - }, - }, - } - def __init__(self): self.compute_api = compute.API() self._image_service = utils.import_object(FLAGS.image_service) - super(Controller, self).__init__() def index(self, req): """ Returns a list of server names and ids for a given user """ @@ -122,15 +104,14 @@ class Controller(common.OpenstackController): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() - def create(self, req): + def create(self, req, body): """ Creates a new server for a given user """ - env = self._deserialize_create(req) - if not env: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) context = req.environ['nova.context'] - password = self._get_server_admin_password(env['server']) + password = self._get_server_admin_password(body['server']) key_name = None key_data = None @@ -140,7 +121,7 @@ class Controller(common.OpenstackController): key_name = key_pair['name'] key_data = key_pair['public_key'] - requested_image_id = self._image_id_from_req_data(env) + requested_image_id = self._image_id_from_req_data(body) try: image_id = common.get_image_id_from_image_hash(self._image_service, context, requested_image_id) @@ -151,18 +132,18 @@ class Controller(common.OpenstackController): kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) - personality = env['server'].get('personality') + personality = body['server'].get('personality') injected_files = [] if personality: injected_files = self._get_injected_files(personality) - flavor_id = self._flavor_id_from_req_data(env) + flavor_id = self._flavor_id_from_req_data(body) - if not 'name' in env['server']: + if not 'name' in body['server']: msg = _("Server name is not defined") return exc.HTTPBadRequest(msg) - name = env['server']['name'] + name = body['server']['name'] self._validate_server_name(name) name = name.strip() @@ -179,7 +160,7 @@ class Controller(common.OpenstackController): display_description=name, key_name=key_name, key_data=key_data, - metadata=env['server'].get('metadata', {}), + metadata=body['server'].get('metadata', {}), injected_files=injected_files) except quota.QuotaError as error: self._handle_quota_error(error) @@ -194,18 +175,6 @@ class Controller(common.OpenstackController): password) return server - def _deserialize_create(self, request): - """ - Deserialize a create request - - Overrides normal behavior in the case of xml content - """ - if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() - return deserializer.deserialize(request.body) - else: - return self._deserialize(request.body, request.get_content_type()) - def _get_injected_files(self, personality): """ Create a list of injected files from the personality attribute @@ -255,24 +224,23 @@ class Controller(common.OpenstackController): return utils.generate_password(16) @scheduler_api.redirect_handler - def update(self, req, id): + def update(self, req, id, body): """ Updates the server name or password """ if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() - inst_dict = self._deserialize(req.body, req.get_content_type()) - if not inst_dict: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) ctxt = req.environ['nova.context'] update_dict = {} - if 'name' in inst_dict['server']: - name = inst_dict['server']['name'] + if 'name' in body['server']: + name = body['server']['name'] self._validate_server_name(name) update_dict['display_name'] = name.strip() - self._parse_update(ctxt, id, inst_dict, update_dict) + self._parse_update(ctxt, id, body, update_dict) try: self.compute_api.update(ctxt, id, **update_dict) @@ -294,7 +262,7 @@ class Controller(common.OpenstackController): pass @scheduler_api.redirect_handler - def action(self, req, id): + def action(self, req, id, body): """Multi-purpose method used to reboot, rebuild, or resize a server""" @@ -307,10 +275,9 @@ class Controller(common.OpenstackController): 'rebuild': self._action_rebuild, } - input_dict = self._deserialize(req.body, req.get_content_type()) for key in actions.keys(): - if key in input_dict: - return actions[key](input_dict, req, id) + if key in body: + return actions[key](body, req, id) return faults.Fault(exc.HTTPNotImplemented()) def _action_change_password(self, input_dict, req, id): @@ -410,7 +377,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def reset_network(self, req, id): + def reset_network(self, req, id, body): """ Reset networking on an instance (admin only). @@ -425,7 +392,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def inject_network_info(self, req, id): + def inject_network_info(self, req, id, body): """ Inject network info for an instance (admin only). @@ -440,7 +407,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def pause(self, req, id): + def pause(self, req, id, body): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: @@ -452,7 +419,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def unpause(self, req, id): + def unpause(self, req, id, body): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] try: @@ -464,7 +431,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def suspend(self, req, id): + def suspend(self, req, id, body): """permit admins to suspend the server""" context = req.environ['nova.context'] try: @@ -476,7 +443,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def resume(self, req, id): + def resume(self, req, id, body): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] try: @@ -815,3 +782,44 @@ class ServerCreateRequestXMLDeserializer(object): if child.nodeType == child.TEXT_NODE: return child.nodeValue return "" + + +def resource_factory(version='1.0'): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + + metadata = { + "attributes": { + "server": ["id", "imageId", "name", "flavorId", "hostId", + "status", "progress", "adminPass", "flavorRef", + "imageRef"], + "link": ["rel", "type", "href"], + }, + "dict_collections": { + "metadata": {"item_name": "meta", "item_key": "key"}, + }, + "list_collections": { + "public": {"item_name": "ip", "item_key": "addr"}, + "private": {"item_name": "ip", "item_key": "addr"}, + }, + } + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=xmlns), + } + + deserializers = { + 'application/xml': ServerCreateRequestXMLDeserializer(), + } + + return wsgi.Resource(controller, serializers=serializers, + deserializers=deserializers) + diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index 996db3648..db178f2a2 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,29 +17,13 @@ from webob import exc -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi -def _translate_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format """ - return dict(sharedIpGroup=inst) - - -def _translate_detail_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format with - correctly mapped attributes """ - return dict(sharedIpGroups=inst) - - -class Controller(common.OpenstackController): +class Controller(object): """ 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 """ raise faults.Fault(exc.HTTPNotImplemented()) @@ -48,7 +32,7 @@ class Controller(common.OpenstackController): """ Shows in-depth information on a specific Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def update(self, req, id): + def update(self, req, id, body): """ You can't update a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) @@ -60,6 +44,10 @@ class Controller(common.OpenstackController): """ Returns a complete list of Shared IP Groups """ raise faults.Fault(exc.HTTPNotImplemented()) - def create(self, req): + def create(self, req, body): """ Creates a new Shared IP group """ raise faults.Fault(exc.HTTPNotImplemented()) + + +def resource_factory(): + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 7ae4c3232..35b6a502e 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -20,8 +20,10 @@ from nova import flags from nova import log as logging from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi from nova.auth import manager + FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack') @@ -34,12 +36,7 @@ def _translate_keys(user): admin=user.admin) -class Controller(common.OpenstackController): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "user": ["id", "name", "access", "secret", "admin"]}}} +class Controller(object): def __init__(self): self.manager = manager.AuthManager() @@ -81,23 +78,35 @@ class Controller(common.OpenstackController): self.manager.delete_user(id) return {} - def create(self, req): + def create(self, req, body): self._check_admin(req.environ['nova.context']) - env = self._deserialize(req.body, req.get_content_type()) - is_admin = env['user'].get('admin') in ('T', 'True', True) - name = env['user'].get('name') - access = env['user'].get('access') - secret = env['user'].get('secret') + is_admin = body['user'].get('admin') in ('T', 'True', True) + name = body['user'].get('name') + access = body['user'].get('access') + secret = body['user'].get('secret') user = self.manager.create_user(name, access, secret, is_admin) return dict(user=_translate_keys(user)) - def update(self, req, id): + def update(self, req, id, body): self._check_admin(req.environ['nova.context']) - env = self._deserialize(req.body, req.get_content_type()) - is_admin = env['user'].get('admin') + is_admin = body['user'].get('admin') if is_admin is not None: is_admin = is_admin in ('T', 'True', True) - access = env['user'].get('access') - secret = env['user'].get('secret') + access = body['user'].get('access') + secret = body['user'].get('secret') self.manager.modify_user(id, access, secret, is_admin) return dict(user=_translate_keys(self.manager.get_user(id))) + + +def resource_factory(): + metadata = { + "attributes": { + "user": ["id", "name", "access", "secret", "admin"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py new file mode 100644 index 000000000..9e0077932 --- /dev/null +++ b/nova/api/openstack/wsgi.py @@ -0,0 +1,291 @@ + +import json +import webob +from xml.dom import minidom + +from nova import exception +from nova import log as logging +from nova import utils + + +XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' +XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' + +LOG = logging.getLogger('nova.api.openstack.wsgi') + + +class Request(webob.Request): + def best_match_content_type(self, supported=None): + """Determine the requested content-type. + + Based on the query extension then the Accept header. + + :param supported: list of content-types to override defaults + + """ + supported = supported or ['application/json', 'application/xml'] + parts = self.path.rsplit('.', 1) + + if len(parts) > 1: + ctype = 'application/{0}'.format(parts[1]) + if ctype in supported: + return ctype + + bm = self.accept.best_match(supported) + + return bm or 'application/json' + + def get_content_type(self): + if not "Content-Type" in self.headers: + raise exception.InvalidContentType(content_type=None) + + allowed_types = ("application/xml", "application/json") + type = self.content_type + + if type not in allowed_types: + raise exception.InvalidContentType(content_type=type) + else: + return type + + +class JSONDeserializer(object): + def deserialize(self, datastring): + return utils.loads(datastring) + + +class JSONSerializer(object): + def serialize(self, data): + return utils.dumps(data) + + +class XMLDeserializer(object): + def __init__(self, metadata=None): + """ + :param metadata: information needed to deserialize xml into + a dictionary. + """ + super(XMLDeserializer, self).__init__() + self.metadata = metadata or {} + + def deserialize(self, datastring): + """XML deserialization entry point.""" + plurals = set(self.metadata.get('plurals', {})) + node = minidom.parseString(datastring).childNodes[0] + return {node.nodeName: self._from_xml_node(node, plurals)} + + def _from_xml_node(self, node, listnames): + """Convert a minidom node to a simple Python type. + + :param listnames: list of XML node names whose subnodes should + be considered list items. + + """ + if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: + return node.childNodes[0].nodeValue + elif node.nodeName in listnames: + return [self._from_xml_node(n, listnames) for n in node.childNodes] + else: + result = dict() + for attr in node.attributes.keys(): + result[attr] = node.attributes[attr].nodeValue + for child in node.childNodes: + if child.nodeType != node.TEXT_NODE: + result[child.nodeName] = self._from_xml_node(child, + listnames) + return result + + +class XMLSerializer(object): + def __init__(self, metadata=None, xmlns=None): + """ + :param metadata: information needed to deserialize xml into + a dictionary. + :param xmlns: XML namespace to include with serialized xml + """ + super(XMLSerializer, self).__init__() + self.metadata = metadata or {} + self.xmlns = xmlns + + def serialize(self, data): + # We expect data to contain a single key which is the XML root. + root_key = data.keys()[0] + doc = minidom.Document() + node = self._to_xml_node(doc, self.metadata, root_key, data[root_key]) + + xmlns = node.getAttribute('xmlns') + if not xmlns and self.xmlns: + node.setAttribute('xmlns', self.xmlns) + + return node.toprettyxml(indent=' ') + + def _to_xml_node(self, doc, metadata, nodename, data): + """Recursive method to convert data members to XML nodes.""" + result = doc.createElement(nodename) + + # Set the xml namespace if one is specified + # TODO(justinsb): We could also use prefixes on the keys + xmlns = metadata.get('xmlns', None) + if xmlns: + result.setAttribute('xmlns', xmlns) + + if type(data) is list: + collections = metadata.get('list_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for item in data: + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(item)) + result.appendChild(node) + return result + singular = metadata.get('plurals', {}).get(nodename, None) + if singular is None: + if nodename.endswith('s'): + singular = nodename[:-1] + else: + singular = 'item' + for item in data: + node = self._to_xml_node(doc, metadata, singular, item) + result.appendChild(node) + elif type(data) is dict: + collections = metadata.get('dict_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for k, v in data.items(): + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(k)) + text = doc.createTextNode(str(v)) + node.appendChild(text) + result.appendChild(node) + return result + attrs = metadata.get('attributes', {}).get(nodename, {}) + for k, v in data.items(): + if k in attrs: + result.setAttribute(k, str(v)) + else: + node = self._to_xml_node(doc, metadata, k, v) + result.appendChild(node) + else: + # Type is atom + node = doc.createTextNode(str(data)) + result.appendChild(node) + return result + + +class Resource(object): + """WSGI app that dispatched to methods. + + WSGI app that reads routing information supplied by RoutesMiddleware + and calls the requested action method upon itself. All action methods + must, in addition to their normal parameters, accept a 'req' argument + which is the incoming wsgi.Request. They raise a webob.exc exception, + or return a dict which will be serialized by requested content type. + + """ + def __init__(self, controller, serializers=None, deserializers=None): + self.serializers = { + 'application/xml': XMLSerializer(), + 'application/json': JSONSerializer(), + } + self.serializers.update(serializers or {}) + + self.deserializers = { + 'application/xml': XMLDeserializer(), + 'application/json': JSONDeserializer(), + } + self.deserializers.update(deserializers or {}) + + self.controller = controller + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, request): + """Call the method specified in req.environ by RoutesMiddleware.""" + LOG.debug("%s %s" % (request.method, request.url)) + + try: + action, action_args, accept = self.deserialize_request(request) + except exception.InvalidContentType: + return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) + + controller_method = getattr(self.controller, action) + result = controller_method(req=request, **action_args) + + response = self.serialize_response(accept, result) + + try: + msg_dict = dict(url=request.url, status=response.status_int) + msg = _("%(url)s returned with HTTP %(status)d") % msg_dict + except AttributeError: + msg_dict = dict(url=request.url) + msg = _("%(url)s returned a fault") + + LOG.debug(msg) + + return response + + def serialize_response(self, content_type, response_body): + """Serialize a dict into a string and wrap in a wsgi.Request object. + + :param content_type: expected mimetype of serialized response body + :param response_body: dict produced by the Controller + + """ + if not type(response_body) is dict: + return response_body + + response = webob.Response() + response.headers['Content-Type'] = content_type + + serializer = self.get_serializer(content_type) + response.body = serializer.serialize(response_body) + + return response + + def get_serializer(self, content_type): + try: + return self.serializers[content_type] + except Exception: + raise exception.InvalidContentType(content_type=content_type) + + def deserialize_request(self, request): + """Parse a wsgi request into a set of params we care about. + + :param request: wsgi.Request object + + """ + action_args = self.get_action_args(request.environ) + action = action_args.pop('action') + + if request.method.lower() in ('post', 'put'): + if len(request.body) == 0: + action_args['body'] = None + else: + content_type = request.get_content_type() + deserializer = self.get_deserializer(content_type) + + try: + action_args['body'] = deserializer.deserialize(request.body) + except exception.InvalidContentType: + action_args['body'] = None + + accept = self.get_expected_content_type(request) + + return (action, action_args, accept) + + def get_expected_content_type(self, request): + return request.best_match_content_type() + + def get_action_args(self, request_environment): + args = request_environment['wsgiorg.routing_args'][1].copy() + + del args['controller'] + + if 'format' in args: + del args['format'] + + return args + + def get_deserializer(self, content_type): + try: + return self.deserializers[content_type] + except Exception: + raise exception.InvalidContentType(content_type=content_type) diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 227ffecdc..d17ab7a9b 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -17,6 +17,7 @@ from nova import db from nova import flags from nova import log as logging from nova.api.openstack import common +from nova.api.openstack import wsgi from nova.scheduler import api @@ -41,12 +42,7 @@ def _scrub_zone(zone): 'deleted', 'deleted_at', 'updated_at')) -class Controller(common.OpenstackController): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "zone": ["id", "api_url", "name", "capabilities"]}}} +class Controller(object): def index(self, req): """Return all zones in brief""" @@ -85,15 +81,28 @@ class Controller(common.OpenstackController): api.zone_delete(req.environ['nova.context'], zone_id) return {} - def create(self, req): + def create(self, req, body): context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - zone = api.zone_create(context, env["zone"]) + zone = api.zone_create(context, body["zone"]) return dict(zone=_scrub_zone(zone)) - def update(self, req, id): + def update(self, req, id, body): context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) zone_id = int(id) - zone = api.zone_update(context, zone_id, env["zone"]) + zone = api.zone_update(context, zone_id, body["zone"]) return dict(zone=_scrub_zone(zone)) + + +def resource_factory(): + metadata = { + "attributes": { + "zone": ["id", "api_url", "name", "capabilities"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 45bd4d501..db859c2f8 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -65,7 +65,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite): def setUp(self): """Run before each test.""" BaseLimitTestSuite.setUp(self) - self.controller = limits.LimitsControllerV10() + self.controller = limits.resource_factory('1.0') def _get_index_request(self, accept_header="application/json"): """Helper to set routing arguments.""" @@ -178,7 +178,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): def setUp(self): """Run before each test.""" BaseLimitTestSuite.setUp(self) - self.controller = limits.LimitsControllerV11() + self.controller = limits.resource_factory('1.1') def _get_index_request(self, accept_header="application/json"): """Helper to set routing arguments.""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e8182b6a9..15f376f74 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -207,7 +207,6 @@ class ServersTest(test.TestCase): }, ] - print res_dict['server'] self.assertEqual(res_dict['server']['links'], expected_links) def test_get_server_by_id_with_addresses_xml(self): @@ -831,7 +830,6 @@ class ServersTest(test.TestCase): req = webob.Request.blank('/v1.0/servers/detail') req.headers['Accept'] = 'application/xml' res = req.get_response(fakes.wsgi_app()) - print res.body dom = minidom.parseString(res.body) for i, server in enumerate(dom.getElementsByTagName('server')): self.assertEqual(server.getAttribute('id'), str(i)) diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index 5820ecdc2..0be3aecf1 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -121,138 +121,3 @@ class ControllerTest(test.TestCase): result = request.get_response(self.TestRouter()) self.assertEqual(result.status_int, 200) self.assertEqual(result.headers["Content-Type"], "application/json") - - -class RequestTest(test.TestCase): - - def test_request_content_type_missing(self): - request = wsgi.Request.blank('/tests/123') - request.body = "" - self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type) - - def test_request_content_type_unsupported(self): - request = wsgi.Request.blank('/tests/123') - request.headers["Content-Type"] = "text/html" - request.body = "asdf
" - self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type) - - def test_request_content_type_with_charset(self): - request = wsgi.Request.blank('/tests/123') - request.headers["Content-Type"] = "application/json; charset=UTF-8" - result = request.get_content_type() - self.assertEqual(result, "application/json") - - def test_content_type_from_accept_xml(self): - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = "application/xml" - result = request.best_match_content_type() - self.assertEqual(result, "application/xml") - - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = "application/json" - result = request.best_match_content_type() - self.assertEqual(result, "application/json") - - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = "application/xml, application/json" - result = request.best_match_content_type() - self.assertEqual(result, "application/json") - - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = \ - "application/json; q=0.3, application/xml; q=0.9" - result = request.best_match_content_type() - self.assertEqual(result, "application/xml") - - def test_content_type_from_query_extension(self): - request = wsgi.Request.blank('/tests/123.xml') - result = request.best_match_content_type() - self.assertEqual(result, "application/xml") - - request = wsgi.Request.blank('/tests/123.json') - result = request.best_match_content_type() - self.assertEqual(result, "application/json") - - request = wsgi.Request.blank('/tests/123.invalid') - result = request.best_match_content_type() - self.assertEqual(result, "application/json") - - def test_content_type_accept_and_query_extension(self): - request = wsgi.Request.blank('/tests/123.xml') - request.headers["Accept"] = "application/json" - result = request.best_match_content_type() - self.assertEqual(result, "application/xml") - - def test_content_type_accept_default(self): - request = wsgi.Request.blank('/tests/123.unsupported') - request.headers["Accept"] = "application/unsupported1" - result = request.best_match_content_type() - self.assertEqual(result, "application/json") - - -class SerializerTest(test.TestCase): - - def test_xml(self): - input_dict = dict(servers=dict(a=(2, 3))) - expected_xml = '(2,3)' - serializer = wsgi.Serializer() - result = serializer.serialize(input_dict, "application/xml") - result = result.replace('\n', '').replace(' ', '') - self.assertEqual(result, expected_xml) - - def test_json(self): - input_dict = dict(servers=dict(a=(2, 3))) - expected_json = '{"servers":{"a":[2,3]}}' - serializer = wsgi.Serializer() - result = serializer.serialize(input_dict, "application/json") - result = result.replace('\n', '').replace(' ', '') - self.assertEqual(result, expected_json) - - def test_unsupported_content_type(self): - serializer = wsgi.Serializer() - self.assertRaises(exception.InvalidContentType, serializer.serialize, - {}, "text/null") - - def test_deserialize_json(self): - data = """{"a": { - "a1": "1", - "a2": "2", - "bs": ["1", "2", "3", {"c": {"c1": "1"}}], - "d": {"e": "1"}, - "f": "1"}}""" - as_dict = dict(a={ - 'a1': '1', - 'a2': '2', - 'bs': ['1', '2', '3', {'c': dict(c1='1')}], - 'd': {'e': '1'}, - 'f': '1'}) - metadata = {} - serializer = wsgi.Serializer(metadata) - self.assertEqual(serializer.deserialize(data, "application/json"), - as_dict) - - def test_deserialize_xml(self): - xml = """ - - 123 - 1 - 1 - - """.strip() - as_dict = dict(a={ - 'a1': '1', - 'a2': '2', - 'bs': ['1', '2', '3', {'c': dict(c1='1')}], - 'd': {'e': '1'}, - 'f': '1'}) - metadata = {'application/xml': dict(plurals={'bs': 'b', 'ts': 't'})} - serializer = wsgi.Serializer(metadata) - self.assertEqual(serializer.deserialize(xml, "application/xml"), - as_dict) - - def test_deserialize_empty_xml(self): - xml = """""" - as_dict = {"a": {}} - serializer = wsgi.Serializer() - self.assertEqual(serializer.deserialize(xml, "application/xml"), - as_dict) diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py index 8a9754777..fde32f797 100644 --- a/nova/tests/integrated/test_xml.py +++ b/nova/tests/integrated/test_xml.py @@ -32,7 +32,7 @@ class XmlTests(integrated_helpers._IntegratedTestBase): """"Some basic XML sanity checks.""" def test_namespace_limits(self): - """/limits should have v1.0 namespace (hasn't changed in 1.1).""" + """/limits should have v1.1 namespace (has changed in 1.1).""" headers = {} headers['Accept'] = 'application/xml' @@ -40,7 +40,7 @@ class XmlTests(integrated_helpers._IntegratedTestBase): data = response.read() LOG.debug("data: %s" % data) - prefix = ' Date: Wed, 18 May 2011 20:33:25 -0400 Subject: removing controller/serializer code from wsgi.py; updating other code to use new modules --- nova/api/direct.py | 12 +- nova/api/openstack/common.py | 7 - nova/api/openstack/consoles.py | 3 +- nova/api/openstack/contrib/volumes.py | 23 +- nova/api/openstack/extensions.py | 97 +++++---- nova/api/openstack/faults.py | 39 ++-- nova/api/openstack/image_metadata.py | 1 - nova/api/openstack/images.py | 11 +- nova/api/openstack/versions.py | 44 ++-- nova/api/openstack/wsgi.py | 23 +- nova/objectstore/s3server.py | 2 +- nova/tests/api/openstack/extensions/foxinsocks.py | 4 +- nova/tests/api/openstack/test_extensions.py | 4 +- nova/tests/api/test_wsgi.py | 54 ----- nova/wsgi.py | 250 +--------------------- 15 files changed, 147 insertions(+), 427 deletions(-) diff --git a/nova/api/direct.py b/nova/api/direct.py index 8ceae299c..5e6c7c882 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -42,6 +42,7 @@ from nova import exception from nova import flags from nova import utils from nova import wsgi +import nova.api.openstack.wsgi # Global storage for registering modules. @@ -251,7 +252,7 @@ class Reflection(object): return self._methods[method] -class ServiceWrapper(wsgi.Controller): +class ServiceWrapper(object): """Wrapper to dynamically povide a WSGI controller for arbitrary objects. With lightweight introspection allows public methods on the object to @@ -265,7 +266,7 @@ class ServiceWrapper(wsgi.Controller): def __init__(self, service_handle): self.service_handle = service_handle - @webob.dec.wsgify(RequestClass=wsgi.Request) + @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request) def __call__(self, req): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] @@ -289,8 +290,11 @@ class ServiceWrapper(wsgi.Controller): try: content_type = req.best_match_content_type() - default_xmlns = self.get_default_xmlns(req) - return self._serialize(result, content_type, default_xmlns) + serializer = { + 'application/xml': nova.api.openstack.wsgi.XMLSerializer(), + 'application/json': nova.api.openstack.wsgi.JSONSerializer(), + }[content_type] + return serializer.serialize(result) except: raise exception.Error("returned non-serializable type: %s" % result) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 32cd689ca..bb1a96812 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -23,7 +23,6 @@ import webob from nova import exception from nova import flags from nova import log as logging -from nova import wsgi LOG = logging.getLogger('nova.api.openstack.common') @@ -146,9 +145,3 @@ def get_id_from_href(href): except: LOG.debug(_("Error extracting id from href: %s") % href) raise webob.exc.HTTPBadRequest(_('could not parse id from href')) - - -class OpenstackController(wsgi.Controller): - def get_default_xmlns(self, req): - # Use V10 by default - return XML_NS_V10 diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 36d570803..97304affe 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -44,11 +44,10 @@ def _translate_detail_keys(cons): class Controller(object): - """The Consoles Controller for the Openstack API""" + """The Consoles controller for the Openstack API""" def __init__(self): self.console_api = console.API() - super(Controller, self).__init__() def index(self, req, server_id): """Returns a list of consoles for this instance""" diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 18de2ec71..b00790b7f 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -22,7 +22,6 @@ from nova import exception from nova import flags from nova import log as logging from nova import volume -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import faults @@ -64,7 +63,7 @@ def _translate_volume_summary_view(context, vol): return d -class VolumeController(wsgi.Controller): +class VolumeController(object): """The Volumes API controller for the OpenStack API.""" _serialization_metadata = { @@ -124,15 +123,14 @@ class VolumeController(wsgi.Controller): res = [entity_maker(context, vol) for vol in limited_list] return {'volumes': res} - def create(self, req): + def create(self, req, body): """Creates a new volume.""" context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - if not env: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) - vol = env['volume'] + vol = body['volume'] size = vol['size'] LOG.audit(_("Create volume of %s GB"), size, context=context) new_volume = self.volume_api.create(context, size, @@ -175,7 +173,7 @@ def _translate_attachment_summary_view(_context, vol): return d -class VolumeAttachmentController(wsgi.Controller): +class VolumeAttachmentController(object): """The volume attachment API controller for the Openstack API. A child resource of the server. Note that we use the volume id @@ -219,17 +217,16 @@ class VolumeAttachmentController(wsgi.Controller): return {'volumeAttachment': _translate_attachment_detail_view(context, vol)} - def create(self, req, server_id): + def create(self, req, server_id, body): """Attach a volume to an instance.""" context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - if not env: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) instance_id = server_id - volume_id = env['volumeAttachment']['volumeId'] - device = env['volumeAttachment']['device'] + volume_id = body['volumeAttachment']['volumeId'] + device = body['volumeAttachment']['device'] msg = _("Attach volume %(volume_id)s to instance %(server_id)s" " at %(device)s") % locals() @@ -259,7 +256,7 @@ class VolumeAttachmentController(wsgi.Controller): # TODO(justinsb): How do I return "accepted" here? return {'volumeAttachment': attachment} - def update(self, _req, _server_id, _id): + def update(self, req, server_id, id, body): """Update a volume attachment. We don't currently support this.""" return faults.Fault(exc.HTTPBadRequest()) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 7ea7afef6..73f174e07 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -27,9 +27,10 @@ import webob.exc from nova import exception from nova import flags from nova import log as logging -from nova import wsgi +from nova import wsgi as base_wsgi from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi LOG = logging.getLogger('extensions') @@ -116,28 +117,34 @@ class ExtensionDescriptor(object): return response_exts -class ActionExtensionController(common.OpenstackController): - +class ActionExtensionController(object): def __init__(self, application): - self.application = application self.action_handlers = {} def add_action(self, action_name, handler): self.action_handlers[action_name] = handler - def action(self, req, id): - - input_dict = self._deserialize(req.body, req.get_content_type()) + def action(self, req, id, body): for action_name, handler in self.action_handlers.iteritems(): - if action_name in input_dict: - return handler(input_dict, req, id) + if action_name in body: + return handler(body, req, id) # no action handler found (bump to downstream application) res = self.application return res -class ResponseExtensionController(common.OpenstackController): +class ActionExtensionResource(wsgi.Resource): + + def __init__(self, application): + controller = ActionExtensionController(application) + super(ActionExtensionResource, self).__init__(controller) + + def add_action(self, action_name, handler): + self.controller.add_action(action_name, handler) + + +class ResponseExtensionController(object): def __init__(self, application): self.application = application @@ -157,7 +164,11 @@ class ResponseExtensionController(common.OpenstackController): headers = res.headers except AttributeError: default_xmlns = None - body = self._serialize(res, content_type, default_xmlns) + serializer = { + 'application/xml': wsgi.XMLSerializer(), + 'application/json': wsgi.JSONSerializer(), + }[content_type] + body = serializer.serialize(res) headers = {"Content-Type": content_type} res = webob.Response() res.body = body @@ -165,7 +176,17 @@ class ResponseExtensionController(common.OpenstackController): return res -class ExtensionController(common.OpenstackController): +class ResponseExtensionResource(wsgi.Resource): + + def __init__(self, application): + controller = ResponseExtensionController(application) + super(ResponseExtensionResource, self).__init__(controller) + + def add_handler(self, handler): + self.controller.add_handler(handler) + + +class ExtensionController(object): def __init__(self, extension_manager): self.extension_manager = extension_manager @@ -198,7 +219,7 @@ class ExtensionController(common.OpenstackController): raise faults.Fault(webob.exc.HTTPNotFound()) -class ExtensionMiddleware(wsgi.Middleware): +class ExtensionMiddleware(base_wsgi.Middleware): """Extensions middleware for WSGI.""" @classmethod def factory(cls, global_config, **local_config): @@ -207,43 +228,43 @@ class ExtensionMiddleware(wsgi.Middleware): return cls(app, **local_config) return _factory - def _action_ext_controllers(self, application, ext_mgr, mapper): - """Return a dict of ActionExtensionController-s by collection.""" - action_controllers = {} + def _action_ext_resources(self, application, ext_mgr, mapper): + """Return a dict of ActionExtensionResource objects by collection.""" + action_resources = {} for action in ext_mgr.get_actions(): - if not action.collection in action_controllers.keys(): - controller = ActionExtensionController(application) + if not action.collection in action_resources.keys(): + resource = ActionExtensionResource(application) mapper.connect("/%s/:(id)/action.:(format)" % action.collection, action='action', - controller=controller, + controller=resource, conditions=dict(method=['POST'])) mapper.connect("/%s/:(id)/action" % action.collection, action='action', - controller=controller, + controller=resource, conditions=dict(method=['POST'])) - action_controllers[action.collection] = controller + action_resources[action.collection] = resource - return action_controllers + return action_resources - def _response_ext_controllers(self, application, ext_mgr, mapper): - """Returns a dict of ResponseExtensionController-s by collection.""" - response_ext_controllers = {} + def _response_ext_resources(self, application, ext_mgr, mapper): + """Returns a dict of ResponseExtensionResource objects by collection.""" + response_ext_resources = {} for resp_ext in ext_mgr.get_response_extensions(): - if not resp_ext.key in response_ext_controllers.keys(): - controller = ResponseExtensionController(application) + if not resp_ext.key in response_ext_resources.keys(): + resource = ResponseExtensionResource(application) mapper.connect(resp_ext.url_route + '.:(format)', action='process', - controller=controller, + controller=resource, conditions=resp_ext.conditions) mapper.connect(resp_ext.url_route, action='process', - controller=controller, + controller=resource, conditions=resp_ext.conditions) - response_ext_controllers[resp_ext.key] = controller + response_ext_resources[resp_ext.key] = resource - return response_ext_controllers + return response_ext_resources def __init__(self, application, ext_mgr=None): @@ -258,21 +279,21 @@ class ExtensionMiddleware(wsgi.Middleware): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=resource.controller, + controller=wsgi.Resource(resource.controller), collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) # extended actions - action_controllers = self._action_ext_controllers(application, ext_mgr, + action_resources = self._action_ext_resources(application, ext_mgr, mapper) for action in ext_mgr.get_actions(): LOG.debug(_('Extended action: %s'), action.action_name) - controller = action_controllers[action.collection] - controller.add_action(action.action_name, action.handler) + resource = action_resources[action.collection] + resource.add_action(action.action_name, action.handler) # extended responses - resp_controllers = self._response_ext_controllers(application, ext_mgr, + resp_controllers = self._response_ext_resources(application, ext_mgr, mapper) for response_ext in ext_mgr.get_response_extensions(): LOG.debug(_('Extended response: %s'), response_ext.key) @@ -422,7 +443,7 @@ class ExtensionManager(object): class ResponseExtension(object): - """Add data to responses from core nova OpenStack API controllers.""" + """Add data to responses from core nova OpenStack API resources.""" def __init__(self, method, url_route, handler): self.url_route = url_route @@ -432,7 +453,7 @@ class ResponseExtension(object): class ActionExtension(object): - """Add custom actions to core nova OpenStack API controllers.""" + """Add custom actions to core nova OpenStack API resources.""" def __init__(self, collection, action_name, handler): self.collection = collection diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 87118ce19..fd36f8f17 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -19,8 +19,7 @@ import webob.dec import webob.exc -from nova import wsgi -from nova.api.openstack import common +from nova.api.openstack import wsgi class Fault(webob.exc.HTTPException): @@ -55,13 +54,21 @@ class Fault(webob.exc.HTTPException): if code == 413: retry = self.wrapped_exc.headers['Retry-After'] fault_data[fault_name]['retryAfter'] = retry + # 'code' is an attribute on the fault tag itself - metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} - default_xmlns = common.XML_NS_V10 - serializer = wsgi.Serializer(metadata, default_xmlns) + metadata = {'attributes': {fault_name: 'code'}} + content_type = req.best_match_content_type() - self.wrapped_exc.body = serializer.serialize(fault_data, content_type) + + serializer = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONSerializer(), + }[content_type] + + self.wrapped_exc.body = serializer.serialize(fault_data) self.wrapped_exc.content_type = content_type + return self.wrapped_exc @@ -70,14 +77,6 @@ class OverLimitFault(webob.exc.HTTPException): Rate-limited request response. """ - _serialization_metadata = { - "application/xml": { - "attributes": { - "overLimitFault": "code", - }, - }, - } - def __init__(self, message, details, retry_time): """ Initialize new `OverLimitFault` with relevant information. @@ -97,8 +96,16 @@ class OverLimitFault(webob.exc.HTTPException): Return the wrapped exception with a serialized body conforming to our error format. """ - serializer = wsgi.Serializer(self._serialization_metadata) content_type = request.best_match_content_type() - content = serializer.serialize(self.content, content_type) + metadata = {"attributes": {"overLimitFault": "code"}} + + serializer = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONSerializer(), + }[content_type] + + content = serializer.serialize(self.content) self.wrapped_exc.body = content + return self.wrapped_exc diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index ce0140265..506b63acf 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -20,7 +20,6 @@ from webob import exc from nova import flags from nova import quota from nova import utils -from nova import wsgi from nova.api.openstack import faults from nova.api.openstack import wsgi diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index e22854ebf..5a03573d8 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -31,7 +31,7 @@ FLAGS = flags.FLAGS class Controller(object): - """Base `wsgi.Controller` for retrieving/displaying images.""" + """Base controller for retrieving/displaying images.""" def __init__(self, image_service=None, compute_service=None): """Initialize new `ImageController`. @@ -99,21 +99,20 @@ class Controller(object): self._image_service.delete(context, image_id) return webob.exc.HTTPNoContent() - def create(self, req): + def create(self, req, body): """Snapshot a server instance and save the image. :param req: `wsgi.Request` object """ context = req.environ['nova.context'] content_type = req.get_content_type() - image = self._deserialize(req.body, content_type) - if not image: + if not body: raise webob.exc.HTTPBadRequest() try: - server_id = image["image"]["serverId"] - image_name = image["image"]["name"] + server_id = body["image"]["serverId"] + image_name = body["image"]["name"] except KeyError: raise webob.exc.HTTPBadRequest() diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 3f9d91934..a8d785b52 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -18,13 +18,27 @@ import webob import webob.dec -from nova import wsgi +from nova import wsgi as base_wsgi import nova.api.openstack.views.versions +from nova.api.openstack import wsgi -class Versions(wsgi.Application): - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): +class Versions(wsgi.Resource, base_wsgi.Application): + def __init__(self): + metadata = { + "attributes": { + "version": ["status", "id"], + "link": ["rel", "href"], + } + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + super(Versions, self).__init__(None, serializers=serializers) + + def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" version_objs = [ { @@ -37,24 +51,6 @@ class Versions(wsgi.Application): }, ] - builder = nova.api.openstack.views.versions.get_view_builder(req) + builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] - response = dict(versions=versions) - - metadata = { - "application/xml": { - "attributes": { - "version": ["status", "id"], - "link": ["rel", "href"], - } - } - } - - content_type = req.best_match_content_type() - body = wsgi.Serializer(metadata).serialize(response, content_type) - - response = webob.Response() - response.content_type = content_type - response.body = body - - return response + return dict(versions=versions) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 9e0077932..97280c365 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -206,8 +206,7 @@ class Resource(object): except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - controller_method = getattr(self.controller, action) - result = controller_method(req=request, **action_args) + result = self.dispatch(request, action, action_args) response = self.serialize_response(accept, result) @@ -222,6 +221,10 @@ class Resource(object): return response + def dispatch(self, request, action, action_args): + controller_method = getattr(self.controller, action) + return controller_method(req=request, **action_args) + def serialize_response(self, content_type, response_body): """Serialize a dict into a string and wrap in a wsgi.Request object. @@ -253,7 +256,7 @@ class Resource(object): """ action_args = self.get_action_args(request.environ) - action = action_args.pop('action') + action = action_args.pop('action', None) if request.method.lower() in ('post', 'put'): if len(request.body) == 0: @@ -275,14 +278,18 @@ class Resource(object): return request.best_match_content_type() def get_action_args(self, request_environment): - args = request_environment['wsgiorg.routing_args'][1].copy() + try: + args = request_environment['wsgiorg.routing_args'][1].copy() + + del args['controller'] - del args['controller'] + if 'format' in args: + del args['format'] - if 'format' in args: - del args['format'] + return args - return args + except KeyError: + return {} def get_deserializer(self, content_type): try: diff --git a/nova/objectstore/s3server.py b/nova/objectstore/s3server.py index dd6327c8f..76025a1e3 100644 --- a/nova/objectstore/s3server.py +++ b/nova/objectstore/s3server.py @@ -81,7 +81,7 @@ class S3Application(wsgi.Router): super(S3Application, self).__init__(mapper) -class BaseRequestHandler(wsgi.Controller): +class BaseRequestHandler(object): """Base class emulating Tornado's web framework pattern in WSGI. This is a direct port of Tornado's implementation, so some key decisions diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 0860b51ac..a64552af1 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -17,12 +17,10 @@ import json -from nova import wsgi - from nova.api.openstack import extensions -class FoxInSocksController(wsgi.Controller): +class FoxInSocksController(object): def index(self, req): return "Try to say this Mr. Knox, sir..." diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 481d34ed1..a8168f88a 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -26,15 +26,15 @@ from nova import flags from nova.api import openstack from nova.api.openstack import extensions from nova.api.openstack import flavors +from nova.api.openstack import wsgi from nova.tests.api.openstack import fakes -import nova.wsgi FLAGS = flags.FLAGS response_body = "Try to say this Mr. Knox, sir..." -class StubController(nova.wsgi.Controller): +class StubController(object): def __init__(self, body): self.body = body diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index 0be3aecf1..d33268296 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -67,57 +67,3 @@ class Test(test.TestCase): self.assertEqual(result.body, "Router result") result = webob.Request.blank('/bad').get_response(Router()) self.assertNotEqual(result.body, "Router result") - - -class ControllerTest(test.TestCase): - - class TestRouter(wsgi.Router): - - class TestController(wsgi.Controller): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "test": ["id"]}}} - - def show(self, req, id): # pylint: disable=W0622,C0103 - return {"test": {"id": id}} - - def __init__(self): - mapper = routes.Mapper() - mapper.resource("test", "tests", controller=self.TestController()) - wsgi.Router.__init__(self, mapper) - - def test_show(self): - request = wsgi.Request.blank('/tests/123') - result = request.get_response(self.TestRouter()) - self.assertEqual(json.loads(result.body), {"test": {"id": "123"}}) - - def test_response_content_type_from_accept_xml(self): - request = webob.Request.blank('/tests/123') - request.headers["Accept"] = "application/xml" - result = request.get_response(self.TestRouter()) - self.assertEqual(result.headers["Content-Type"], "application/xml") - - def test_response_content_type_from_accept_json(self): - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = "application/json" - result = request.get_response(self.TestRouter()) - self.assertEqual(result.headers["Content-Type"], "application/json") - - def test_response_content_type_from_query_extension_xml(self): - request = wsgi.Request.blank('/tests/123.xml') - result = request.get_response(self.TestRouter()) - self.assertEqual(result.headers["Content-Type"], "application/xml") - - def test_response_content_type_from_query_extension_json(self): - request = wsgi.Request.blank('/tests/123.json') - result = request.get_response(self.TestRouter()) - self.assertEqual(result.headers["Content-Type"], "application/json") - - def test_response_content_type_default_when_unsupported(self): - request = wsgi.Request.blank('/tests/123.unsupported') - request.headers["Accept"] = "application/unsupported1" - result = request.get_response(self.TestRouter()) - self.assertEqual(result.status_int, 200) - self.assertEqual(result.headers["Content-Type"], "application/json") diff --git a/nova/wsgi.py b/nova/wsgi.py index e60a8820d..3a292073b 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -82,36 +82,7 @@ class Server(object): class Request(webob.Request): - - def best_match_content_type(self): - """Determine the most acceptable content-type. - - Based on the query extension then the Accept header. - - """ - parts = self.path.rsplit('.', 1) - - if len(parts) > 1: - format = parts[1] - if format in ['json', 'xml']: - return 'application/{0}'.format(parts[1]) - - ctypes = ['application/json', 'application/xml'] - bm = self.accept.best_match(ctypes) - - return bm or 'application/json' - - def get_content_type(self): - allowed_types = ("application/xml", "application/json") - if not "Content-Type" in self.headers: - msg = _("Missing Content-Type") - LOG.debug(msg) - raise webob.exc.HTTPBadRequest(msg) - type = self.content_type - if type in allowed_types: - return type - LOG.debug(_("Wrong Content-Type: %s") % type) - raise webob.exc.HTTPBadRequest("Invalid content type") + pass class Application(object): @@ -286,7 +257,7 @@ class Router(object): Each route in `mapper` must specify a 'controller', which is a WSGI app to call. You'll probably want to specify an 'action' as - well and have your controller be a wsgi.Controller, who will route + well and have your controller be a controller, who will route the request to the action method. Examples: @@ -335,223 +306,6 @@ class Router(object): return app -class Controller(object): - """WSGI app that dispatched to methods. - - WSGI app that reads routing information supplied by RoutesMiddleware - and calls the requested action method upon itself. All action methods - must, in addition to their normal parameters, accept a 'req' argument - which is the incoming wsgi.Request. They raise a webob.exc exception, - or return a dict which will be serialized by requested content type. - - """ - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - """Call the method specified in req.environ by RoutesMiddleware.""" - arg_dict = req.environ['wsgiorg.routing_args'][1] - action = arg_dict['action'] - method = getattr(self, action) - LOG.debug("%s %s" % (req.method, req.url)) - del arg_dict['controller'] - del arg_dict['action'] - if 'format' in arg_dict: - del arg_dict['format'] - arg_dict['req'] = req - result = method(**arg_dict) - - if type(result) is dict: - content_type = req.best_match_content_type() - default_xmlns = self.get_default_xmlns(req) - body = self._serialize(result, content_type, default_xmlns) - - response = webob.Response() - response.headers['Content-Type'] = content_type - response.body = body - msg_dict = dict(url=req.url, status=response.status_int) - msg = _("%(url)s returned with HTTP %(status)d") % msg_dict - LOG.debug(msg) - return response - else: - return result - - def _serialize(self, data, content_type, default_xmlns): - """Serialize the given dict to the provided content_type. - - Uses self._serialization_metadata if it exists, which is a dict mapping - MIME types to information needed to serialize to that type. - - """ - _metadata = getattr(type(self), '_serialization_metadata', {}) - - serializer = Serializer(_metadata, default_xmlns) - try: - return serializer.serialize(data, content_type) - except exception.InvalidContentType: - raise webob.exc.HTTPNotAcceptable() - - def _deserialize(self, data, content_type): - """Deserialize the request body to the specefied content type. - - Uses self._serialization_metadata if it exists, which is a dict mapping - MIME types to information needed to serialize to that type. - - """ - _metadata = getattr(type(self), '_serialization_metadata', {}) - serializer = Serializer(_metadata) - return serializer.deserialize(data, content_type) - - def get_default_xmlns(self, req): - """Provide the XML namespace to use if none is otherwise specified.""" - return None - - -class Serializer(object): - """Serializes and deserializes dictionaries to certain MIME types.""" - - def __init__(self, metadata=None, default_xmlns=None): - """Create a serializer based on the given WSGI environment. - - 'metadata' is an optional dict mapping MIME types to information - needed to serialize a dictionary to that type. - - """ - self.metadata = metadata or {} - self.default_xmlns = default_xmlns - - def _get_serialize_handler(self, content_type): - handlers = { - 'application/json': self._to_json, - 'application/xml': self._to_xml, - } - - try: - return handlers[content_type] - except Exception: - raise exception.InvalidContentType(content_type=content_type) - - def serialize(self, data, content_type): - """Serialize a dictionary into the specified content type.""" - return self._get_serialize_handler(content_type)(data) - - def deserialize(self, datastring, content_type): - """Deserialize a string to a dictionary. - - The string must be in the format of a supported MIME type. - - """ - return self.get_deserialize_handler(content_type)(datastring) - - def get_deserialize_handler(self, content_type): - handlers = { - 'application/json': self._from_json, - 'application/xml': self._from_xml, - } - - try: - return handlers[content_type] - except Exception: - raise exception.InvalidContentType(content_type=content_type) - - def _from_json(self, datastring): - return utils.loads(datastring) - - def _from_xml(self, datastring): - xmldata = self.metadata.get('application/xml', {}) - plurals = set(xmldata.get('plurals', {})) - node = minidom.parseString(datastring).childNodes[0] - return {node.nodeName: self._from_xml_node(node, plurals)} - - def _from_xml_node(self, node, listnames): - """Convert a minidom node to a simple Python type. - - listnames is a collection of names of XML nodes whose subnodes should - be considered list items. - - """ - if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: - return node.childNodes[0].nodeValue - elif node.nodeName in listnames: - return [self._from_xml_node(n, listnames) for n in node.childNodes] - else: - result = dict() - for attr in node.attributes.keys(): - result[attr] = node.attributes[attr].nodeValue - for child in node.childNodes: - if child.nodeType != node.TEXT_NODE: - result[child.nodeName] = self._from_xml_node(child, - listnames) - return result - - def _to_json(self, data): - return utils.dumps(data) - - def _to_xml(self, data): - metadata = self.metadata.get('application/xml', {}) - # We expect data to contain a single key which is the XML root. - root_key = data.keys()[0] - doc = minidom.Document() - node = self._to_xml_node(doc, metadata, root_key, data[root_key]) - - xmlns = node.getAttribute('xmlns') - if not xmlns and self.default_xmlns: - node.setAttribute('xmlns', self.default_xmlns) - - return node.toprettyxml(indent=' ') - - def _to_xml_node(self, doc, metadata, nodename, data): - """Recursive method to convert data members to XML nodes.""" - result = doc.createElement(nodename) - - # Set the xml namespace if one is specified - # TODO(justinsb): We could also use prefixes on the keys - xmlns = metadata.get('xmlns', None) - if xmlns: - result.setAttribute('xmlns', xmlns) - - if type(data) is list: - collections = metadata.get('list_collections', {}) - if nodename in collections: - metadata = collections[nodename] - for item in data: - node = doc.createElement(metadata['item_name']) - node.setAttribute(metadata['item_key'], str(item)) - result.appendChild(node) - return result - singular = metadata.get('plurals', {}).get(nodename, None) - if singular is None: - if nodename.endswith('s'): - singular = nodename[:-1] - else: - singular = 'item' - for item in data: - node = self._to_xml_node(doc, metadata, singular, item) - result.appendChild(node) - elif type(data) is dict: - collections = metadata.get('dict_collections', {}) - if nodename in collections: - metadata = collections[nodename] - for k, v in data.items(): - node = doc.createElement(metadata['item_name']) - node.setAttribute(metadata['item_key'], str(k)) - text = doc.createTextNode(str(v)) - node.appendChild(text) - result.appendChild(node) - return result - attrs = metadata.get('attributes', {}).get(nodename, {}) - for k, v in data.items(): - if k in attrs: - result.setAttribute(k, str(v)) - else: - node = self._to_xml_node(doc, metadata, k, v) - result.appendChild(node) - else: - # Type is atom - node = doc.createTextNode(str(data)) - result.appendChild(node) - return result - - def paste_config_file(basename): """Find the best location in the system for a paste config file. -- cgit From 0aefdc6da92b8db8b15a3e8a0bef8fc5c4b46450 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 18 May 2011 20:33:52 -0400 Subject: missed the new wsgi test file --- nova/tests/api/openstack/test_wsgi.py | 248 ++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 nova/tests/api/openstack/test_wsgi.py diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py new file mode 100644 index 000000000..430dafe77 --- /dev/null +++ b/nova/tests/api/openstack/test_wsgi.py @@ -0,0 +1,248 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import json +import webob + +from nova import exception +from nova import test +from nova.api.openstack import wsgi + + +class RequestTest(test.TestCase): + def test_content_type_missing(self): + request = wsgi.Request.blank('/tests/123') + request.body = "" + self.assertRaises(exception.InvalidContentType, + request.get_content_type) + + def test_content_type_unsupported(self): + request = wsgi.Request.blank('/tests/123') + request.headers["Content-Type"] = "text/html" + request.body = "asdf
" + self.assertRaises(exception.InvalidContentType, + request.get_content_type) + + def test_content_type_with_charset(self): + request = wsgi.Request.blank('/tests/123') + request.headers["Content-Type"] = "application/json; charset=UTF-8" + result = request.get_content_type() + self.assertEqual(result, "application/json") + + def test_content_type_from_accept_xml(self): + request = wsgi.Request.blank('/tests/123') + request.headers["Accept"] = "application/xml" + result = request.best_match_content_type() + self.assertEqual(result, "application/xml") + + request = wsgi.Request.blank('/tests/123') + request.headers["Accept"] = "application/json" + result = request.best_match_content_type() + self.assertEqual(result, "application/json") + + request = wsgi.Request.blank('/tests/123') + request.headers["Accept"] = "application/xml, application/json" + result = request.best_match_content_type() + self.assertEqual(result, "application/json") + + request = wsgi.Request.blank('/tests/123') + request.headers["Accept"] = \ + "application/json; q=0.3, application/xml; q=0.9" + result = request.best_match_content_type() + self.assertEqual(result, "application/xml") + + def test_content_type_from_query_extension(self): + request = wsgi.Request.blank('/tests/123.xml') + result = request.best_match_content_type() + self.assertEqual(result, "application/xml") + + request = wsgi.Request.blank('/tests/123.json') + result = request.best_match_content_type() + self.assertEqual(result, "application/json") + + request = wsgi.Request.blank('/tests/123.invalid') + result = request.best_match_content_type() + self.assertEqual(result, "application/json") + + def test_content_type_accept_and_query_extension(self): + request = wsgi.Request.blank('/tests/123.xml') + request.headers["Accept"] = "application/json" + result = request.best_match_content_type() + self.assertEqual(result, "application/xml") + + def test_content_type_accept_default(self): + request = wsgi.Request.blank('/tests/123.unsupported') + request.headers["Accept"] = "application/unsupported1" + result = request.best_match_content_type() + self.assertEqual(result, "application/json") + + +class SerializationTest(test.TestCase): + def test_xml(self): + input_dict = dict(servers=dict(a=(2, 3))) + expected_xml = '(2,3)' + xmlns = "testing xmlns" + serializer = wsgi.XMLSerializer(xmlns="asdf") + result = serializer.serialize(input_dict) + result = result.replace('\n', '').replace(' ', '') + self.assertEqual(result, expected_xml) + + def test_json(self): + input_dict = dict(servers=dict(a=(2, 3))) + expected_json = '{"servers":{"a":[2,3]}}' + serializer = wsgi.JSONSerializer() + result = serializer.serialize(input_dict) + result = result.replace('\n', '').replace(' ', '') + self.assertEqual(result, expected_json) + + +class DeserializationTest(test.TestCase): + def test_json(self): + data = """{"a": { + "a1": "1", + "a2": "2", + "bs": ["1", "2", "3", {"c": {"c1": "1"}}], + "d": {"e": "1"}, + "f": "1"}}""" + as_dict = dict(a={ + 'a1': '1', + 'a2': '2', + 'bs': ['1', '2', '3', {'c': dict(c1='1')}], + 'd': {'e': '1'}, + 'f': '1'}) + deserializer = wsgi.JSONDeserializer() + self.assertEqual(deserializer.deserialize(data), as_dict) + + def test_xml(self): + xml = """ + + 123 + 1 + 1 + + """.strip() + as_dict = dict(a={ + 'a1': '1', + 'a2': '2', + 'bs': ['1', '2', '3', {'c': dict(c1='1')}], + 'd': {'e': '1'}, + 'f': '1'}) + metadata = {'plurals': {'bs': 'b', 'ts': 't'}} + deserializer = wsgi.XMLDeserializer(metadata=metadata) + self.assertEqual(deserializer.deserialize(xml), as_dict) + + def test_xml_empty(self): + xml = """""" + as_dict = {"a": {}} + deserializer = wsgi.XMLDeserializer() + self.assertEqual(deserializer.deserialize(xml), as_dict) + + +class ResourceSerializerTest(test.TestCase): + def setUp(self): + class JSONSerializer(object): + def serialize(self, data): + return 'pew_json' + + class XMLSerializer(object): + def serialize(self, data): + return 'pew_xml' + + self.serializers = { + 'application/json': JSONSerializer(), + 'application/XML': XMLSerializer(), + } + + self.resource = wsgi.Resource(None, serializers=self.serializers) + + def tearDown(self): + pass + + def test_get_serializer(self): + self.assertEqual(self.resource.get_serializer('application/json'), + self.serializers['application/json']) + + def test_get_serializer_unknown_content_type(self): + self.assertRaises(exception.InvalidContentType, + self.resource.get_serializer, + 'application/unknown') + + def test_serialize_response_dict(self): + response = self.resource.serialize_response('application/json', {}) + self.assertEqual(response.headers['Content-Type'], 'application/json') + self.assertEqual(response.body, 'pew_json') + + def test_serialize_response_non_dict(self): + response = self.resource.serialize_response('application/json', 'a') + self.assertEqual(response, 'a') + + def test_serialize_response_dict_to_unknown_content_type(self): + self.assertRaises(exception.InvalidContentType, + self.resource.serialize_response, + 'application/unknown', {}) + + def test_serialize_response_non_dict_to_unknown_content_type(self): + response = self.resource.serialize_response('application/unknown', 'a') + self.assertEqual(response, 'a') + + +class ResourceDeserializerTest(test.TestCase): + def setUp(self): + class JSONDeserializer(object): + def deserialize(self, data): + return 'pew_json' + + class XMLDeserializer(object): + def deserialize(self, data): + return 'pew_xml' + + self.deserializers = { + 'application/json': JSONDeserializer(), + 'application/XML': XMLDeserializer(), + } + + self.resource = wsgi.Resource(None, deserializers=self.deserializers) + + def tearDown(self): + pass + + def test_get_deserializer(self): + self.assertEqual(self.resource.get_deserializer('application/json'), + self.deserializers['application/json']) + + def test_get_deserializer_unknown_content_type(self): + self.assertRaises(exception.InvalidContentType, + self.resource.get_deserializer, + 'application/unknown') + + def test_get_expected_content_type(self): + request = wsgi.Request.blank('/') + request.headers['Accept'] = 'application/json' + self.assertEqual(self.resource.get_expected_content_type(request), + 'application/json') + + def test_get_action_args(self): + env = { + 'wsgiorg.routing_args': [None, { + 'controller': None, + 'format': None, + 'action': 'update', + 'id': 12, + }], + } + + expected = {'action': 'update', 'id': 12} + + self.assertEqual(self.resource.get_action_args(env), expected) + + def test_deserialize_request(self): + def fake_get_routing_args(request): + return {'action': 'create'} + self.resource.get_action_args = fake_get_routing_args + + request = wsgi.Request.blank('/') + request.headers['Accept'] = 'application/xml' + + deserialized = self.resource.deserialize_request(request) + expected = ('create', {}, 'application/xml') + + self.assertEqual(expected, deserialized) -- cgit From 68426df2287c24efc3d327d12371911ac29d117e Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 19 May 2011 16:16:06 -0400 Subject: further refactoring of wsgi module; adding documentation and tests --- nova/api/direct.py | 4 +- nova/api/openstack/accounts.py | 2 +- nova/api/openstack/backup_schedules.py | 4 +- nova/api/openstack/consoles.py | 2 +- nova/api/openstack/extensions.py | 8 +- nova/api/openstack/faults.py | 12 +- nova/api/openstack/flavors.py | 2 +- nova/api/openstack/image_metadata.py | 2 +- nova/api/openstack/images.py | 4 +- nova/api/openstack/ips.py | 4 +- nova/api/openstack/limits.py | 4 +- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 11 +- nova/api/openstack/users.py | 2 +- nova/api/openstack/versions.py | 5 +- nova/api/openstack/wsgi.py | 301 ++++++++++++++++++++----------- nova/api/openstack/zones.py | 4 +- nova/tests/api/openstack/test_servers.py | 50 ++--- nova/tests/api/openstack/test_wsgi.py | 104 ++++++++--- 19 files changed, 331 insertions(+), 196 deletions(-) diff --git a/nova/api/direct.py b/nova/api/direct.py index 5e6c7c882..ea20042a7 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -291,8 +291,8 @@ class ServiceWrapper(object): try: content_type = req.best_match_content_type() serializer = { - 'application/xml': nova.api.openstack.wsgi.XMLSerializer(), - 'application/json': nova.api.openstack.wsgi.JSONSerializer(), + 'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(), + 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(), }[content_type] return serializer.serialize(result) except: diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index d8a9d1909..faff8bb2c 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -88,7 +88,7 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 4153c90c1..d08a4799c 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -60,8 +60,8 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, - metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 97304affe..56f79db60 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -97,7 +97,7 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 73f174e07..19147bbea 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -165,8 +165,8 @@ class ResponseExtensionController(object): except AttributeError: default_xmlns = None serializer = { - 'application/xml': wsgi.XMLSerializer(), - 'application/json': wsgi.JSONSerializer(), + 'application/xml': wsgi.XMLDictSerializer(), + 'application/json': wsgi.JSONDictSerializer(), }[content_type] body = serializer.serialize(res) headers = {"Content-Type": content_type} @@ -229,7 +229,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): return _factory def _action_ext_resources(self, application, ext_mgr, mapper): - """Return a dict of ActionExtensionResource objects by collection.""" + """Return a dict of ActionExtensionResource-s by collection.""" action_resources = {} for action in ext_mgr.get_actions(): if not action.collection in action_resources.keys(): @@ -248,7 +248,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): return action_resources def _response_ext_resources(self, application, ext_mgr, mapper): - """Returns a dict of ResponseExtensionResource objects by collection.""" + """Returns a dict of ResponseExtensionResource-s by collection.""" response_ext_resources = {} for resp_ext in ext_mgr.get_response_extensions(): if not resp_ext.key in response_ext_resources.keys(): diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index fd36f8f17..b9a23c126 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -61,9 +61,9 @@ class Fault(webob.exc.HTTPException): content_type = req.best_match_content_type() serializer = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), - 'application/json': wsgi.JSONSerializer(), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONDictSerializer(), }[content_type] self.wrapped_exc.body = serializer.serialize(fault_data) @@ -100,9 +100,9 @@ class OverLimitFault(webob.exc.HTTPException): metadata = {"attributes": {"overLimitFault": "code"}} serializer = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), - 'application/json': wsgi.JSONSerializer(), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONDictSerializer(), }[content_type] content = serializer.serialize(self.content) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 46056a27a..9e98e6c27 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -86,7 +86,7 @@ def resource_factory(version='1.0'): }[version] serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=xmlns), + 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 506b63acf..8acde9fe8 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -104,7 +104,7 @@ class Controller(object): def resource_factory(): serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5a03573d8..a9071ed8a 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -165,8 +165,8 @@ def resource_factory(version='1.0'): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, - metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, + metadata=metadata), } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 24612eafb..87c8c997a 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -72,8 +72,8 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 306048d8f..b0e093702 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -95,8 +95,8 @@ def resource_factory(version='1.0'): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, - metadata=metadata) + 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, + metadata=metadata) } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index fb9449b4c..eff98c060 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -93,7 +93,7 @@ class Controller(object): def resource_factory(): serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 78f8bb1b7..8f39bd256 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -707,7 +707,7 @@ class ControllerV11(Controller): return common.XML_NS_V11 -class ServerCreateRequestXMLDeserializer(object): +class ServerXMLDeserializer(wsgi.XMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -715,7 +715,7 @@ class ServerCreateRequestXMLDeserializer(object): and personality attributes """ - def deserialize(self, string): + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) server = self._extract_server(dom) @@ -812,14 +812,13 @@ def resource_factory(version='1.0'): }[version] serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=xmlns), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=xmlns), } deserializers = { - 'application/xml': ServerCreateRequestXMLDeserializer(), + 'application/xml': ServerXMLDeserializer(), } return wsgi.Resource(controller, serializers=serializers, deserializers=deserializers) - diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 35b6a502e..e14616349 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -106,7 +106,7 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a8d785b52..9db160102 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -18,12 +18,11 @@ import webob import webob.dec -from nova import wsgi as base_wsgi import nova.api.openstack.views.versions from nova.api.openstack import wsgi -class Versions(wsgi.Resource, base_wsgi.Application): +class Versions(wsgi.Resource): def __init__(self): metadata = { "attributes": { @@ -33,7 +32,7 @@ class Versions(wsgi.Resource, base_wsgi.Application): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } super(Versions, self).__init__(None, serializers=serializers) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 97280c365..bd840a6f7 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -6,6 +6,7 @@ from xml.dom import minidom from nova import exception from nova import log as logging from nova import utils +from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' @@ -15,17 +16,17 @@ LOG = logging.getLogger('nova.api.openstack.wsgi') class Request(webob.Request): - def best_match_content_type(self, supported=None): - """Determine the requested content-type. + """Add some Openstack API-specific logic to the base webob.Request.""" - Based on the query extension then the Accept header. + def best_match_content_type(self): + """Determine the requested response content-type. - :param supported: list of content-types to override defaults + Based on the query extension then the Accept header. """ - supported = supported or ['application/json', 'application/xml'] - parts = self.path.rsplit('.', 1) + supported = ('application/json', 'application/xml') + parts = self.path.rsplit('.', 1) if len(parts) > 1: ctype = 'application/{0}'.format(parts[1]) if ctype in supported: @@ -33,32 +34,52 @@ class Request(webob.Request): bm = self.accept.best_match(supported) + # default to application/json if we don't find a preference return bm or 'application/json' def get_content_type(self): + """Determine content type of the request body. + + Does not do any body introspection, only checks header + + """ if not "Content-Type" in self.headers: raise exception.InvalidContentType(content_type=None) allowed_types = ("application/xml", "application/json") - type = self.content_type + content_type = self.content_type - if type not in allowed_types: - raise exception.InvalidContentType(content_type=type) + if content_type not in allowed_types: + raise exception.InvalidContentType(content_type=content_type) else: - return type + return content_type -class JSONDeserializer(object): - def deserialize(self, datastring): - return utils.loads(datastring) +class TextDeserializer(object): + """Custom request body deserialization based on controller action name.""" + def deserialize(self, datastring, action=None): + """Find local deserialization method and parse request body.""" + try: + action_method = getattr(self, action) + except Exception: + action_method = self.default -class JSONSerializer(object): - def serialize(self, data): - return utils.dumps(data) + return action_method(datastring) + def default(self, datastring): + """Default deserialization code should live here""" + raise NotImplementedError() + + +class JSONDeserializer(TextDeserializer): + + def default(self, datastring): + return utils.loads(datastring) + + +class XMLDeserializer(TextDeserializer): -class XMLDeserializer(object): def __init__(self, metadata=None): """ :param metadata: information needed to deserialize xml into @@ -67,8 +88,7 @@ class XMLDeserializer(object): super(XMLDeserializer, self).__init__() self.metadata = metadata or {} - def deserialize(self, datastring): - """XML deserialization entry point.""" + def default(self, datastring): plurals = set(self.metadata.get('plurals', {})) node = minidom.parseString(datastring).childNodes[0] return {node.nodeName: self._from_xml_node(node, plurals)} @@ -95,18 +115,111 @@ class XMLDeserializer(object): return result -class XMLSerializer(object): +class RequestDeserializer(object): + """Break up a Request object into more useful pieces.""" + + def __init__(self, deserializers=None): + """ + :param deserializers: dictionary of content-type-specific deserializers + + """ + self.deserializers = { + 'application/xml': XMLDeserializer(), + 'application/json': JSONDeserializer(), + } + + self.deserializers.update(deserializers or {}) + + def deserialize(self, request): + """Extract necessary pieces of the request. + + :param request: Request object + :returns tuple of expected controller action name, dictionary of + keyword arguments to pass to the controller, the expected + content type of the response + + """ + action_args = self.get_action_args(request.environ) + action = action_args.pop('action', None) + + if request.method.lower() in ('post', 'put'): + if len(request.body) == 0: + action_args['body'] = None + else: + content_type = request.get_content_type() + deserializer = self.get_deserializer(content_type) + + try: + body = deserializer.deserialize(request.body, action) + action_args['body'] = body + except exception.InvalidContentType: + action_args['body'] = None + + accept = self.get_expected_content_type(request) + + return (action, action_args, accept) + + def get_deserializer(self, content_type): + try: + return self.deserializers[content_type] + except Exception: + raise exception.InvalidContentType(content_type=content_type) + + def get_expected_content_type(self, request): + return request.best_match_content_type() + + def get_action_args(self, request_environment): + """Parse dictionary created by routes library.""" + try: + args = request_environment['wsgiorg.routing_args'][1].copy() + + del args['controller'] + + if 'format' in args: + del args['format'] + + return args + + except KeyError: + return {} + + +class DictSerializer(object): + """Custom response body serialization based on controller action name.""" + + def serialize(self, data, action=None): + """Find local serialization method and encode response body.""" + try: + action_method = getattr(self, action) + except Exception: + action_method = self.default + + return action_method(data) + + def default(self, data): + """Default serialization code should live here""" + raise NotImplementedError() + + +class JSONDictSerializer(DictSerializer): + + def default(self, data): + return utils.dumps(data) + + +class XMLDictSerializer(DictSerializer): + def __init__(self, metadata=None, xmlns=None): """ :param metadata: information needed to deserialize xml into a dictionary. :param xmlns: XML namespace to include with serialized xml """ - super(XMLSerializer, self).__init__() + super(XMLDictSerializer, self).__init__() self.metadata = metadata or {} self.xmlns = xmlns - def serialize(self, data): + def default(self, data): # We expect data to contain a single key which is the XML root. root_key = data.keys()[0] doc = minidom.Document() @@ -171,75 +284,32 @@ class XMLSerializer(object): return result -class Resource(object): - """WSGI app that dispatched to methods. +class ResponseSerializer(object): + """Encode the necessary pieces into a response object""" - WSGI app that reads routing information supplied by RoutesMiddleware - and calls the requested action method upon itself. All action methods - must, in addition to their normal parameters, accept a 'req' argument - which is the incoming wsgi.Request. They raise a webob.exc exception, - or return a dict which will be serialized by requested content type. + def __init__(self, serializers=None): + """ + :param serializers: dictionary of content-type-specific serializers - """ - def __init__(self, controller, serializers=None, deserializers=None): + """ self.serializers = { - 'application/xml': XMLSerializer(), - 'application/json': JSONSerializer(), + 'application/xml': XMLDictSerializer(), + 'application/json': JSONDictSerializer(), } self.serializers.update(serializers or {}) - self.deserializers = { - 'application/xml': XMLDeserializer(), - 'application/json': JSONDeserializer(), - } - self.deserializers.update(deserializers or {}) - - self.controller = controller - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, request): - """Call the method specified in req.environ by RoutesMiddleware.""" - LOG.debug("%s %s" % (request.method, request.url)) - - try: - action, action_args, accept = self.deserialize_request(request) - except exception.InvalidContentType: - return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - - result = self.dispatch(request, action, action_args) - - response = self.serialize_response(accept, result) - - try: - msg_dict = dict(url=request.url, status=response.status_int) - msg = _("%(url)s returned with HTTP %(status)d") % msg_dict - except AttributeError: - msg_dict = dict(url=request.url) - msg = _("%(url)s returned a fault") - - LOG.debug(msg) - - return response - - def dispatch(self, request, action, action_args): - controller_method = getattr(self.controller, action) - return controller_method(req=request, **action_args) - - def serialize_response(self, content_type, response_body): + def serialize(self, response_data, content_type): """Serialize a dict into a string and wrap in a wsgi.Request object. + :param response_data: dict produced by the Controller :param content_type: expected mimetype of serialized response body - :param response_body: dict produced by the Controller """ - if not type(response_body) is dict: - return response_body - response = webob.Response() response.headers['Content-Type'] = content_type serializer = self.get_serializer(content_type) - response.body = serializer.serialize(response_body) + response.body = serializer.serialize(response_data) return response @@ -249,50 +319,63 @@ class Resource(object): except Exception: raise exception.InvalidContentType(content_type=content_type) - def deserialize_request(self, request): - """Parse a wsgi request into a set of params we care about. - :param request: wsgi.Request object +class Resource(wsgi.Application): + """WSGI app that handles (de)serialization and controller dispatch. + + WSGI app that reads routing information supplied by RoutesMiddleware + and calls the requested action method upon its controller. All + controller action methods must accept a 'req' argument, which is the + incoming wsgi.Request. If the operation is a PUT or POST, the controller + method must also accept a 'body' argument (the deserialized request body). + They may raise a webob.exc exception or return a dict, which will be + serialized by requested content type. + """ + def __init__(self, controller, serializers=None, deserializers=None): """ - action_args = self.get_action_args(request.environ) - action = action_args.pop('action', None) + :param controller: object that implement methods created by routes lib + :param serializers: dict of content-type specific text serializers + :param deserializers: dict of content-type specific text deserializers - if request.method.lower() in ('post', 'put'): - if len(request.body) == 0: - action_args['body'] = None - else: - content_type = request.get_content_type() - deserializer = self.get_deserializer(content_type) + """ + self.controller = controller + self.serializer = ResponseSerializer(serializers) + self.deserializer = RequestDeserializer(deserializers) - try: - action_args['body'] = deserializer.deserialize(request.body) - except exception.InvalidContentType: - action_args['body'] = None + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, request): + """WSGI method that controls (de)serialization and method dispatch.""" - accept = self.get_expected_content_type(request) + LOG.debug("%s %s" % (request.method, request.url)) - return (action, action_args, accept) + try: + action, action_args, accept = self.deserializer.deserialize( + request) + except exception.InvalidContentType: + return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - def get_expected_content_type(self, request): - return request.best_match_content_type() + action_result = self.dispatch(request, action, action_args) - def get_action_args(self, request_environment): - try: - args = request_environment['wsgiorg.routing_args'][1].copy() + #TODO(bcwaldon): find a more elegant way to pass through non-dict types + if type(action_result) is dict: + response = self.serializer.serialize(action_result, accept) + else: + response = action_result - del args['controller'] + try: + msg_dict = dict(url=request.url, status=response.status_int) + msg = _("%(url)s returned with HTTP %(status)d") % msg_dict + except AttributeError: + msg_dict = dict(url=request.url) + msg = _("%(url)s returned a fault") - if 'format' in args: - del args['format'] + LOG.debug(msg) - return args + return response - except KeyError: - return {} + def dispatch(self, request, action, action_args): + """Find action-spefic method on controller and call it.""" - def get_deserializer(self, content_type): - try: - return self.deserializers[content_type] - except Exception: - raise exception.InvalidContentType(content_type=content_type) + controller_method = getattr(self.controller, action) + return controller_method(req=request, **action_args) diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index d17ab7a9b..e750fc230 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -101,8 +101,8 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, - metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 15f376f74..31571fc46 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -993,6 +993,14 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 501) + def test_server_change_password_xml(self): + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/xml' + req.body = '' +# res = req.get_response(fakes.wsgi_app()) +# self.assertEqual(res.status_int, 501) + def test_server_change_password_v1_1(self): class MockSetAdminPassword(object): @@ -1375,13 +1383,13 @@ class ServersTest(test.TestCase): class TestServerCreateRequestXMLDeserializer(unittest.TestCase): def setUp(self): - self.deserializer = servers.ServerCreateRequestXMLDeserializer() + self.deserializer = servers.ServerXMLDeserializer() def test_minimal_request(self): serial_request = """ """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"server": { "name": "new-server-test", "imageId": "1", @@ -1395,7 +1403,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): name="new-server-test" imageId="1" flavorId="1"> """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"server": { "name": "new-server-test", "imageId": "1", @@ -1410,7 +1418,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): name="new-server-test" imageId="1" flavorId="1"> """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"server": { "name": "new-server-test", "imageId": "1", @@ -1426,7 +1434,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"server": { "name": "new-server-test", "imageId": "1", @@ -1443,7 +1451,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"server": { "name": "new-server-test", "imageId": "1", @@ -1461,7 +1469,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): aabbccdd """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = [{"path": "/etc/conf", "contents": "aabbccdd"}] self.assertEquals(request["server"]["personality"], expected) @@ -1471,7 +1479,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): name="new-server-test" imageId="1" flavorId="1"> aabbccdd abcd""" - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = [{"path": "/etc/conf", "contents": "aabbccdd"}, {"path": "/etc/sudoers", "contents": "abcd"}] self.assertEquals(request["server"]["personality"], expected) @@ -1487,7 +1495,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): anything """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = [{"path": "/etc/conf", "contents": "aabbccdd"}] self.assertEquals(request["server"]["personality"], expected) @@ -1496,7 +1504,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): aabbccdd""" - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = [{"contents": "aabbccdd"}] self.assertEquals(request["server"]["personality"], expected) @@ -1505,7 +1513,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = [{"path": "/etc/conf", "contents": ""}] self.assertEquals(request["server"]["personality"], expected) @@ -1514,7 +1522,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = [{"path": "/etc/conf", "contents": ""}] self.assertEquals(request["server"]["personality"], expected) @@ -1526,7 +1534,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): beta """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"alpha": "beta"} self.assertEquals(request["server"]["metadata"], expected) @@ -1539,7 +1547,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): bar """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"alpha": "beta", "foo": "bar"} self.assertEquals(request["server"]["metadata"], expected) @@ -1551,7 +1559,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"alpha": ""} self.assertEquals(request["server"]["metadata"], expected) @@ -1564,7 +1572,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"alpha": "", "delta": ""} self.assertEquals(request["server"]["metadata"], expected) @@ -1576,7 +1584,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): beta """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"": "beta"} self.assertEquals(request["server"]["metadata"], expected) @@ -1589,7 +1597,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): gamma """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"": "gamma"} self.assertEquals(request["server"]["metadata"], expected) @@ -1602,7 +1610,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase): baz """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') expected = {"foo": "baz"} self.assertEquals(request["server"]["metadata"], expected) @@ -1649,7 +1657,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", }, ], }} - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') self.assertEqual(request, expected) def test_request_xmlser_with_flavor_image_ref(self): @@ -1659,7 +1667,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", imageRef="http://localhost:8774/v1.1/images/1" flavorRef="http://localhost:8774/v1.1/flavors/1"> """ - request = self.deserializer.deserialize(serial_request) + request = self.deserializer.deserialize(serial_request, 'create') self.assertEquals(request["server"]["flavorRef"], "http://localhost:8774/v1.1/flavors/1") self.assertEquals(request["server"]["imageRef"], diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 430dafe77..6c57d3e4f 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -76,26 +76,56 @@ class RequestTest(test.TestCase): self.assertEqual(result, "application/json") -class SerializationTest(test.TestCase): +class DictSerializerTest(test.TestCase): + def test_dispatch(self): + serializer = wsgi.DictSerializer() + serializer.create = lambda x: 'pants' + serializer.default = lambda x: 'trousers' + self.assertEqual(serializer.serialize({}, 'create'), 'pants') + + def test_dispatch_default(self): + serializer = wsgi.DictSerializer() + serializer.create = lambda x: 'pants' + serializer.default = lambda x: 'trousers' + self.assertEqual(serializer.serialize({}, 'update'), 'trousers') + + +class XMLDictSerializerTest(test.TestCase): def test_xml(self): input_dict = dict(servers=dict(a=(2, 3))) expected_xml = '(2,3)' xmlns = "testing xmlns" - serializer = wsgi.XMLSerializer(xmlns="asdf") + serializer = wsgi.XMLDictSerializer(xmlns="asdf") result = serializer.serialize(input_dict) result = result.replace('\n', '').replace(' ', '') self.assertEqual(result, expected_xml) + +class JSONDictSerializerTest(test.TestCase): def test_json(self): input_dict = dict(servers=dict(a=(2, 3))) expected_json = '{"servers":{"a":[2,3]}}' - serializer = wsgi.JSONSerializer() + serializer = wsgi.JSONDictSerializer() result = serializer.serialize(input_dict) result = result.replace('\n', '').replace(' ', '') self.assertEqual(result, expected_json) -class DeserializationTest(test.TestCase): +class TextDeserializerTest(test.TestCase): + def test_dispatch(self): + deserializer = wsgi.TextDeserializer() + deserializer.create = lambda x: 'pants' + deserializer.default = lambda x: 'trousers' + self.assertEqual(deserializer.deserialize({}, 'create'), 'pants') + + def test_dispatch_default(self): + deserializer = wsgi.TextDeserializer() + deserializer.create = lambda x: 'pants' + deserializer.default = lambda x: 'trousers' + self.assertEqual(deserializer.deserialize({}, 'update'), 'trousers') + + +class JSONDeserializerTest(test.TestCase): def test_json(self): data = """{"a": { "a1": "1", @@ -112,6 +142,8 @@ class DeserializationTest(test.TestCase): deserializer = wsgi.JSONDeserializer() self.assertEqual(deserializer.deserialize(data), as_dict) + +class XMLDeserializerTest(test.TestCase): def test_xml(self): xml = """ @@ -137,7 +169,7 @@ class DeserializationTest(test.TestCase): self.assertEqual(deserializer.deserialize(xml), as_dict) -class ResourceSerializerTest(test.TestCase): +class ResponseSerializerTest(test.TestCase): def setUp(self): class JSONSerializer(object): def serialize(self, data): @@ -152,40 +184,32 @@ class ResourceSerializerTest(test.TestCase): 'application/XML': XMLSerializer(), } - self.resource = wsgi.Resource(None, serializers=self.serializers) + self.serializer = wsgi.ResponseSerializer(serializers=self.serializers) def tearDown(self): pass def test_get_serializer(self): - self.assertEqual(self.resource.get_serializer('application/json'), + self.assertEqual(self.serializer.get_serializer('application/json'), self.serializers['application/json']) def test_get_serializer_unknown_content_type(self): self.assertRaises(exception.InvalidContentType, - self.resource.get_serializer, + self.serializer.get_serializer, 'application/unknown') - def test_serialize_response_dict(self): - response = self.resource.serialize_response('application/json', {}) + def test_serialize_response(self): + response = self.serializer.serialize({}, 'application/json') self.assertEqual(response.headers['Content-Type'], 'application/json') self.assertEqual(response.body, 'pew_json') - def test_serialize_response_non_dict(self): - response = self.resource.serialize_response('application/json', 'a') - self.assertEqual(response, 'a') - def test_serialize_response_dict_to_unknown_content_type(self): self.assertRaises(exception.InvalidContentType, - self.resource.serialize_response, + self.serializer.serialize, 'application/unknown', {}) - def test_serialize_response_non_dict_to_unknown_content_type(self): - response = self.resource.serialize_response('application/unknown', 'a') - self.assertEqual(response, 'a') - -class ResourceDeserializerTest(test.TestCase): +class RequestDeserializerTest(test.TestCase): def setUp(self): class JSONDeserializer(object): def deserialize(self, data): @@ -200,24 +224,25 @@ class ResourceDeserializerTest(test.TestCase): 'application/XML': XMLDeserializer(), } - self.resource = wsgi.Resource(None, deserializers=self.deserializers) + self.deserializer = wsgi.RequestDeserializer( + deserializers=self.deserializers) def tearDown(self): pass def test_get_deserializer(self): - self.assertEqual(self.resource.get_deserializer('application/json'), - self.deserializers['application/json']) + expected = self.deserializer.get_deserializer('application/json') + self.assertEqual(expected, self.deserializers['application/json']) def test_get_deserializer_unknown_content_type(self): self.assertRaises(exception.InvalidContentType, - self.resource.get_deserializer, + self.deserializer.get_deserializer, 'application/unknown') def test_get_expected_content_type(self): request = wsgi.Request.blank('/') request.headers['Accept'] = 'application/json' - self.assertEqual(self.resource.get_expected_content_type(request), + self.assertEqual(self.deserializer.get_expected_content_type(request), 'application/json') def test_get_action_args(self): @@ -232,17 +257,38 @@ class ResourceDeserializerTest(test.TestCase): expected = {'action': 'update', 'id': 12} - self.assertEqual(self.resource.get_action_args(env), expected) + self.assertEqual(self.deserializer.get_action_args(env), expected) - def test_deserialize_request(self): + def test_deserialize(self): def fake_get_routing_args(request): return {'action': 'create'} - self.resource.get_action_args = fake_get_routing_args + self.deserializer.get_action_args = fake_get_routing_args request = wsgi.Request.blank('/') request.headers['Accept'] = 'application/xml' - deserialized = self.resource.deserialize_request(request) + deserialized = self.deserializer.deserialize(request) expected = ('create', {}, 'application/xml') self.assertEqual(expected, deserialized) + + +class ResourceTest(test.TestCase): + def test_dispatch(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + resource = wsgi.Resource(Controller()) + actual = resource.dispatch(None, 'index', {'pants': 'off'}) + expected = 'off' + self.assertEqual(actual, expected) + + def test_dispatch_unknown_controller_action(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + resource = wsgi.Resource(Controller()) + self.assertRaises(AttributeError, resource.dispatch, + None, 'create', {}) -- cgit From 3fc3b7537cc1af2783829a2caaca272e83d6d3e8 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 20 May 2011 14:42:19 -0400 Subject: renaming resource_factory to create_resource --- nova/api/openstack/__init__.py | 34 ++++++++++++++++----------------- nova/api/openstack/accounts.py | 2 +- nova/api/openstack/backup_schedules.py | 2 +- nova/api/openstack/consoles.py | 2 +- nova/api/openstack/flavors.py | 2 +- nova/api/openstack/image_metadata.py | 2 +- nova/api/openstack/images.py | 2 +- nova/api/openstack/ips.py | 2 +- nova/api/openstack/limits.py | 2 +- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 2 +- nova/api/openstack/shared_ip_groups.py | 2 +- nova/api/openstack/users.py | 2 +- nova/api/openstack/zones.py | 2 +- nova/tests/api/openstack/test_limits.py | 4 ++-- 15 files changed, 32 insertions(+), 32 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index fbbd99cb9..4419d0748 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -99,19 +99,19 @@ class APIRouter(base_wsgi.Router): server_members['inject_network_info'] = 'POST' mapper.resource("zone", "zones", - controller=zones.resource_factory(), + controller=zones.create_resource(), collection={'detail': 'GET', 'info': 'GET'}), mapper.resource("user", "users", - controller=users.resource_factory(), + controller=users.create_resource(), collection={'detail': 'GET'}) mapper.resource("account", "accounts", - controller=accounts.resource_factory(), + controller=accounts.create_resource(), collection={'detail': 'GET'}) mapper.resource("console", "consoles", - controller=consoles.resource_factory(), + controller=consoles.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) @@ -124,31 +124,31 @@ class APIRouterV10(APIRouter): def _setup_routes(self, mapper): super(APIRouterV10, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.resource_factory('1.0'), + controller=servers.create_resource('1.0'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.resource_factory('1.0'), + controller=images.create_resource('1.0'), collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", - controller=flavors.resource_factory('1.0'), + controller=flavors.create_resource('1.0'), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, - controller=shared_ip_groups.resource_factory()) + controller=shared_ip_groups.create_resource()) mapper.resource("backup_schedule", "backup_schedule", - controller=backup_schedules.resource_factory(), + controller=backup_schedules.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("limit", "limits", - controller=limits.resource_factory('1.0')) + controller=limits.create_resource('1.0')) - mapper.resource("ip", "ips", controller=ips.resource_factory(), + mapper.resource("ip", "ips", controller=ips.create_resource(), collection=dict(public='GET', private='GET'), parent_resource=dict(member_name='server', collection_name='servers')) @@ -160,27 +160,27 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.resource_factory('1.1'), + controller=servers.create_resource('1.1'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.resource_factory('1.1'), + controller=images.create_resource('1.1'), collection={'detail': 'GET'}) mapper.resource("image_meta", "meta", - controller=image_metadata.resource_factory(), + controller=image_metadata.create_resource(), parent_resource=dict(member_name='image', collection_name='images')) mapper.resource("server_meta", "meta", - controller=server_metadata.resource_factory(), + controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("flavor", "flavors", - controller=flavors.resource_factory('1.1'), + controller=flavors.create_resource('1.1'), collection={'detail': 'GET'}) mapper.resource("limit", "limits", - controller=limits.resource_factory('1.1')) + controller=limits.create_resource('1.1')) diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index faff8bb2c..0dcd37217 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -80,7 +80,7 @@ class Controller(object): return dict(account=_translate_keys(account)) -def resource_factory(): +def create_resource(): metadata = { "attributes": { "account": ["id", "name", "description", "manager"], diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index d08a4799c..71a14d4ce 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -52,7 +52,7 @@ class Controller(object): return faults.Fault(exc.HTTPNotImplemented()) -def resource_factory(): +def create_resource(): metadata = { 'attributes': { 'backupSchedule': [], diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 56f79db60..bccf04d8f 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -89,7 +89,7 @@ class Controller(object): return exc.HTTPAccepted() -def resource_factory(): +def create_resource(): metadata = { 'attributes': { 'console': [], diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 9e98e6c27..a21ff6cb2 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -74,7 +74,7 @@ class ControllerV11(Controller): return views.flavors.ViewBuilderV11(base_url) -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 8acde9fe8..88e10168d 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -102,7 +102,7 @@ class Controller(object): self.image_service.update(context, image_id, img, None) -def resource_factory(): +def create_resource(): serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index a9071ed8a..3376f358a 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -145,7 +145,7 @@ class ControllerV11(Controller): return common.XML_NS_V11 -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 87c8c997a..abea71830 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -63,7 +63,7 @@ class Controller(object): return faults.Fault(exc.HTTPNotImplemented()) -def resource_factory(): +def create_resource(): metadata = { 'list_collections': { 'public': {'item_name': 'ip', 'item_key': 'addr'}, diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index b0e093702..2d9fe356f 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -73,7 +73,7 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': LimitsControllerV10, '1.1': LimitsControllerV11, diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index eff98c060..b38b84a2a 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -91,7 +91,7 @@ class Controller(object): raise error -def resource_factory(): +def create_resource(): serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8f39bd256..bdd2960d9 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -784,7 +784,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): return "" -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index db178f2a2..4f11f8dfb 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -49,5 +49,5 @@ class Controller(object): raise faults.Fault(exc.HTTPNotImplemented()) -def resource_factory(): +def create_resource(): return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index e14616349..50975fc1f 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -98,7 +98,7 @@ class Controller(object): return dict(user=_translate_keys(self.manager.get_user(id))) -def resource_factory(): +def create_resource(): metadata = { "attributes": { "user": ["id", "name", "access", "secret", "admin"], diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index e750fc230..0475deb52 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -93,7 +93,7 @@ class Controller(object): return dict(zone=_scrub_zone(zone)) -def resource_factory(): +def create_resource(): metadata = { "attributes": { "zone": ["id", "api_url", "name", "capabilities"], diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index db859c2f8..4cf857507 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -65,7 +65,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite): def setUp(self): """Run before each test.""" BaseLimitTestSuite.setUp(self) - self.controller = limits.resource_factory('1.0') + self.controller = limits.create_resource('1.0') def _get_index_request(self, accept_header="application/json"): """Helper to set routing arguments.""" @@ -178,7 +178,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite): def setUp(self): """Run before each test.""" BaseLimitTestSuite.setUp(self) - self.controller = limits.resource_factory('1.1') + self.controller = limits.create_resource('1.1') def _get_index_request(self, accept_header="application/json"): """Helper to set routing arguments.""" -- cgit From 2c16eb37822b3ebdb14ac36df26362636d0f5078 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 20 May 2011 16:36:10 -0400 Subject: minor cleanup --- nova/api/openstack/images.py | 3 --- nova/api/openstack/servers.py | 3 --- nova/tests/api/openstack/test_wsgi.py | 1 - 3 files changed, 7 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 3376f358a..7f5551664 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -141,9 +141,6 @@ class ControllerV11(Controller): base_url = request.application_url return images_view.ViewBuilderV11(base_url) - def get_default_xmlns(self, req): - return common.XML_NS_V11 - def create_resource(version='1.0'): controller = { diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bdd2960d9..313321d7d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -703,9 +703,6 @@ class ControllerV11(Controller): raise exc.HTTPBadRequest(msg) return password - def get_default_xmlns(self, req): - return common.XML_NS_V11 - class ServerXMLDeserializer(wsgi.XMLDeserializer): """ diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 6c57d3e4f..89603d82b 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -94,7 +94,6 @@ class XMLDictSerializerTest(test.TestCase): def test_xml(self): input_dict = dict(servers=dict(a=(2, 3))) expected_xml = '(2,3)' - xmlns = "testing xmlns" serializer = wsgi.XMLDictSerializer(xmlns="asdf") result = serializer.serialize(input_dict) result = result.replace('\n', '').replace(' ', '') -- cgit From 0850945efd0c5d7341590acd109572b9caf89e18 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Fri, 20 May 2011 21:30:04 +0000 Subject: move init start position to 96 to allow openvswitch time to fully start --- plugins/xenserver/networking/etc/init.d/openvswitch-nova | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/xenserver/networking/etc/init.d/openvswitch-nova b/plugins/xenserver/networking/etc/init.d/openvswitch-nova index e4dbdf4af..8672a69b8 100755 --- a/plugins/xenserver/networking/etc/init.d/openvswitch-nova +++ b/plugins/xenserver/networking/etc/init.d/openvswitch-nova @@ -2,7 +2,7 @@ # # openvswitch-nova # -# chkconfig: 2345 10 89 +# chkconfig: 2345 96 89 # description: Apply initial OVS flows for Nova # Copyright 2011 OpenStack LLC. -- cgit From 46ddecc177830ea0ccef82e84d72c48261450b40 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 25 May 2011 03:29:16 -0400 Subject: Don't need to import json. --- nova/tests/test_notifier.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 14bef79b8..523f38f24 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import json - import stubout import nova -- cgit From ac3348ae282b218a941b33a2d17b7d5ddaeebab6 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 26 May 2011 09:16:02 -0400 Subject: Switching back to chown. I'm fine w/ setfacl too but nova already has 'chown' via sudoers so this seems reasonable for now. --- nova/virt/xenapi/vm_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 931f8e2d4..fdf51ff74 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1013,8 +1013,7 @@ def _stream_disk(dev, image_type, virtual_size, image_file): offset = MBR_SIZE_BYTES _write_partition(virtual_size, dev) - utils.execute('sudo', 'setfacl', '-m', 'u:%s:rw' % os.getuid(), - '/dev/%s' % dev) + utils.execute('sudo', 'chown', os.getuid(), '/dev/%s' % dev) with open('/dev/%s' % dev, 'wb') as f: f.seek(offset) -- cgit From 7c0564baf72cbb5c3693ab72c72684a5c6b333c5 Mon Sep 17 00:00:00 2001 From: John Tran Date: Thu, 26 May 2011 10:22:45 -0700 Subject: instance obj returned is not a hash, instead is sqlalchemy obj and hostname attr is what the logic is looking for --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 26c0d776c..51373d282 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -536,7 +536,7 @@ class FloatingIpCommands(object): for floating_ip in floating_ips: instance = None if floating_ip['fixed_ip']: - instance = floating_ip['fixed_ip']['instance']['ec2_id'] + instance = floating_ip['fixed_ip']['instance'].hostname print "%s\t%s\t%s" % (floating_ip['host'], floating_ip['address'], instance) -- cgit From ff75e808eef06a72c0198fe976c19c60256c6b74 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 26 May 2011 10:28:22 -0700 Subject: log upload errors --- nova/image/s3.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nova/image/s3.py b/nova/image/s3.py index 673cbf56f..bad04e5c0 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -31,12 +31,14 @@ import eventlet from nova import crypto from nova import exception from nova import flags +from nova import log as logging from nova import utils from nova.auth import manager from nova.image import service from nova.api.ec2 import ec2utils +LOG = logging.getLogger("nova.image.s3") FLAGS = flags.FLAGS flags.DEFINE_string('image_decryption_dir', '/tmp', 'parent dir for tempdir used for image decryption') @@ -181,6 +183,8 @@ class S3ImageService(service.BaseImageService): shutil.copyfileobj(part, combined) except Exception: + LOG.exception(_("Failed to download %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_download' self.service.update(context, image_id, metadata) raise @@ -203,6 +207,9 @@ class S3ImageService(service.BaseImageService): encrypted_iv, cloud_pk, dec_filename) except Exception: + LOG.exception(_("Failed to decrypt %(image_location)s " + "to %(image_path)s"), locals()) + LOG.exception(_("Failed to decrypt %s"), enc_filename) metadata['properties']['image_state'] = 'failed_decrypt' self.service.update(context, image_id, metadata) raise @@ -213,6 +220,8 @@ class S3ImageService(service.BaseImageService): try: unz_filename = self._untarzip_image(image_path, dec_filename) except Exception: + LOG.exception(_("Failed to untar %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_untar' self.service.update(context, image_id, metadata) raise @@ -224,6 +233,8 @@ class S3ImageService(service.BaseImageService): self.service.update(context, image_id, metadata, image_file) except Exception: + LOG.exception(_("Failed to upload %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_upload' self.service.update(context, image_id, metadata) raise -- cgit From 75ec3d77d3fa4078bbe7d647377f987d87d97651 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 26 May 2011 10:30:27 -0700 Subject: exceptions are logged via the raise, so just log an error message --- nova/image/s3.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/nova/image/s3.py b/nova/image/s3.py index bad04e5c0..ec8852f09 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -183,8 +183,8 @@ class S3ImageService(service.BaseImageService): shutil.copyfileobj(part, combined) except Exception: - LOG.exception(_("Failed to download %(image_location)s " - "to %(image_path)s"), locals()) + LOG.error(_("Failed to download %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_download' self.service.update(context, image_id, metadata) raise @@ -207,9 +207,8 @@ class S3ImageService(service.BaseImageService): encrypted_iv, cloud_pk, dec_filename) except Exception: - LOG.exception(_("Failed to decrypt %(image_location)s " - "to %(image_path)s"), locals()) - LOG.exception(_("Failed to decrypt %s"), enc_filename) + LOG.error(_("Failed to decrypt %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_decrypt' self.service.update(context, image_id, metadata) raise @@ -220,8 +219,8 @@ class S3ImageService(service.BaseImageService): try: unz_filename = self._untarzip_image(image_path, dec_filename) except Exception: - LOG.exception(_("Failed to untar %(image_location)s " - "to %(image_path)s"), locals()) + LOG.error(_("Failed to untar %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_untar' self.service.update(context, image_id, metadata) raise @@ -233,8 +232,8 @@ class S3ImageService(service.BaseImageService): self.service.update(context, image_id, metadata, image_file) except Exception: - LOG.exception(_("Failed to upload %(image_location)s " - "to %(image_path)s"), locals()) + LOG.error(_("Failed to upload %(image_location)s " + "to %(image_path)s"), locals()) metadata['properties']['image_state'] = 'failed_upload' self.service.update(context, image_id, metadata) raise -- cgit From 2d834fa19078c645e3c36001b5dd34fb8e708f0a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 26 May 2011 14:09:59 -0400 Subject: review fixups --- nova/api/openstack/wsgi.py | 27 ++++++++++++++++----------- nova/tests/api/openstack/test_wsgi.py | 2 +- nova/wsgi.py | 4 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index bd840a6f7..5577d326f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -62,7 +62,7 @@ class TextDeserializer(object): """Find local deserialization method and parse request body.""" try: action_method = getattr(self, action) - except Exception: + except (AttributeError, TypeError): action_method = self.default return action_method(datastring) @@ -162,7 +162,7 @@ class RequestDeserializer(object): def get_deserializer(self, content_type): try: return self.deserializers[content_type] - except Exception: + except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) def get_expected_content_type(self, request): @@ -172,16 +172,20 @@ class RequestDeserializer(object): """Parse dictionary created by routes library.""" try: args = request_environment['wsgiorg.routing_args'][1].copy() + except Exception: + return {} + try: del args['controller'] + except KeyError: + pass - if 'format' in args: - del args['format'] - - return args - + try: + del args['format'] except KeyError: - return {} + pass + + return args class DictSerializer(object): @@ -191,7 +195,7 @@ class DictSerializer(object): """Find local serialization method and encode response body.""" try: action_method = getattr(self, action) - except Exception: + except (AttributeError, TypeError): action_method = self.default return action_method(data) @@ -316,7 +320,7 @@ class ResponseSerializer(object): def get_serializer(self, content_type): try: return self.serializers[content_type] - except Exception: + except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -347,7 +351,8 @@ class Resource(wsgi.Application): def __call__(self, request): """WSGI method that controls (de)serialization and method dispatch.""" - LOG.debug("%s %s" % (request.method, request.url)) + LOG.debug("%(method)s %(url)s" % {"method": request.method, + "url": request.url}) try: action, action_args, accept = self.deserializer.deserialize( diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 89603d82b..ebbdc9409 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -205,7 +205,7 @@ class ResponseSerializerTest(test.TestCase): def test_serialize_response_dict_to_unknown_content_type(self): self.assertRaises(exception.InvalidContentType, self.serializer.serialize, - 'application/unknown', {}) + {}, 'application/unknown') class RequestDeserializerTest(test.TestCase): diff --git a/nova/wsgi.py b/nova/wsgi.py index d59d2ee13..33ba852bc 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -260,8 +260,8 @@ class Router(object): Each route in `mapper` must specify a 'controller', which is a WSGI app to call. You'll probably want to specify an 'action' as - well and have your controller be a controller, who will route - the request to the action method. + well and have your controller be an object that can route + the request to the action-specific method. Examples: mapper = routes.Mapper() -- cgit From 3264c18fffa26b1288fc253f2526d9a78fdc9dd4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 26 May 2011 15:01:24 -0400 Subject: cleaning up getattr calls with default param --- nova/api/openstack/wsgi.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 5577d326f..7a747842e 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -58,13 +58,9 @@ class Request(webob.Request): class TextDeserializer(object): """Custom request body deserialization based on controller action name.""" - def deserialize(self, datastring, action=None): + def deserialize(self, datastring, action='default'): """Find local deserialization method and parse request body.""" - try: - action_method = getattr(self, action) - except (AttributeError, TypeError): - action_method = self.default - + action_method = getattr(self, action, self.default) return action_method(datastring) def default(self, datastring): @@ -191,13 +187,9 @@ class RequestDeserializer(object): class DictSerializer(object): """Custom response body serialization based on controller action name.""" - def serialize(self, data, action=None): + def serialize(self, data, action='default'): """Find local serialization method and encode response body.""" - try: - action_method = getattr(self, action) - except (AttributeError, TypeError): - action_method = self.default - + action_method = getattr(self, action, self.default) return action_method(data) def default(self, data): -- cgit From 613aee2dd146957cb0c040d7a7a1a661b487efbc Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 26 May 2011 16:58:06 -0400 Subject: move udev file so it follows the xen-backend.rules --- plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules | 3 --- .../xenserver/networking/etc/udev/rules.d/xen-openvswitch-nova.rules | 3 +++ .../networking/etc/xensource/scripts/ovs_configure_vif_flows.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules create mode 100644 plugins/xenserver/networking/etc/udev/rules.d/xen-openvswitch-nova.rules diff --git a/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules b/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules deleted file mode 100644 index b179f0847..000000000 --- a/plugins/xenserver/networking/etc/udev/rules.d/openvswitch-nova.rules +++ /dev/null @@ -1,3 +0,0 @@ -SUBSYSTEM=="xen-backend", KERNEL=="vif*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all" -# is this one needed? -#SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all" diff --git a/plugins/xenserver/networking/etc/udev/rules.d/xen-openvswitch-nova.rules b/plugins/xenserver/networking/etc/udev/rules.d/xen-openvswitch-nova.rules new file mode 100644 index 000000000..b179f0847 --- /dev/null +++ b/plugins/xenserver/networking/etc/udev/rules.d/xen-openvswitch-nova.rules @@ -0,0 +1,3 @@ +SUBSYSTEM=="xen-backend", KERNEL=="vif*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all" +# is this one needed? +#SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/etc/xensource/scripts/ovs_configure_vif_flows.py $env{ACTION} %k all" diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 2ebc4dd8c..9fde69377 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -172,7 +172,7 @@ def apply_ovs_ipv6_flows(ovs, bridge, params): if __name__ == "__main__": if len(sys.argv) != 4: - print "usage: %s [online|offline] vif-domid-idx ipv4|ipv6|all " % \ + print "usage: %s [online|offline] vif-domid-idx [ipv4|ipv6|all] " % \ os.path.basename(sys.argv[0]) sys.exit(1) else: -- cgit From a79f01fcea81bb6be233a65670c6a79af8534a10 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 26 May 2011 17:27:48 -0400 Subject: adding TODOs per dabo's review --- nova/api/openstack/wsgi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7a747842e..ddf4e6fa9 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -237,6 +237,7 @@ class XMLDictSerializer(DictSerializer): if xmlns: result.setAttribute('xmlns', xmlns) + #TODO(bcwaldon): accomplish this without a type-check if type(data) is list: collections = metadata.get('list_collections', {}) if nodename in collections: @@ -255,6 +256,7 @@ class XMLDictSerializer(DictSerializer): for item in data: node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) + #TODO(bcwaldon): accomplish this without a type-check elif type(data) is dict: collections = metadata.get('dict_collections', {}) if nodename in collections: -- cgit From 5976b50299b31292d578dcdd8576607e175fca44 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sat, 28 May 2011 21:10:57 -0400 Subject: Cleanup instances_path in test_libvirt test_spawn_with_network_info test. --- nova/tests/test_libvirt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 4efdd6ae9..1fac4e4e6 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -18,6 +18,7 @@ import eventlet import mox import os import re +import shutil import sys from xml.etree.ElementTree import fromstring as xml_to_tree @@ -645,6 +646,8 @@ class LibvirtConnTestCase(test.TestCase): except Exception, e: count = (0 <= str(e.message).find('Unexpected method call')) + shutil.rmtree(os.path.join(FLAGS.instances_path, instance.name)) + self.assertTrue(count) def test_get_host_ip_addr(self): -- cgit From 394b37f8c944fbd3ca683d7752cd751bc69cce51 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 29 May 2011 00:00:02 -0400 Subject: Implement the v1.1 style resize action with support for flavorRef. --- nova/api/openstack/servers.py | 32 ++++++++++++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 19 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c10fc916..a3066e578 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -332,6 +332,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() def _action_resize(self, input_dict, req, id): + return exc.HTTPNotImplemented() """ Resizes a given instance to the flavor size requested """ try: if 'resize' in input_dict and 'flavorId' in input_dict['resize']: @@ -610,6 +611,21 @@ class ControllerV10(Controller): self.compute_api.set_admin_password(context, server_id, inst_dict['server']['adminPass']) + def _action_resize(self, input_dict, req, id): + """ Resizes a given instance to the flavor size requested """ + try: + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing arguments for resize")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + except Exception, e: + LOG.exception(_("Error in resize %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] instance_id = int(instance_id) @@ -695,6 +711,22 @@ class ControllerV11(Controller): LOG.info(msg) raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + def _action_resize(self, input_dict, req, id): + """ Resizes a given instance to the flavor size requested """ + try: + if 'resize' in input_dict and 'flavorRef' in input_dict['resize']: + flavor_ref = input_dict['resize']['flavorRef'] + flavor_id = common.get_id_from_href(flavor_ref) + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing arguments for resize")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + except Exception, e: + LOG.exception(_("Error in resize %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] instance_id = int(instance_id) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index fbde5c9ce..e0910fed6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1267,6 +1267,25 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 202) self.assertEqual(self.resize_called, True) + def test_resize_server_v11(self): + + req = webob.Request.blank('/v1.1/servers/1/action') + req.content_type = 'application/json' + req.method = 'POST' + body_dict = dict(resize=dict(flavorRef="http://localhost/3")) + req.body = json.dumps(body_dict) + + self.resize_called = False + + def resize_mock(*args): + self.resize_called = True + + self.stubs.Set(nova.compute.api.API, 'resize', resize_mock) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertEqual(self.resize_called, True) + def test_resize_bad_flavor_fails(self): req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3))) -- cgit From d6cd02a07ab3b66a53689fb8edbf55db03b4bff2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 31 May 2011 08:20:40 -0400 Subject: Actually remove the _action_resize code from the base Servers controller. The V11 and V10 controllers implement these now. --- nova/api/openstack/servers.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a3066e578..4bd7ddb14 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -333,19 +333,6 @@ class Controller(common.OpenstackController): def _action_resize(self, input_dict, req, id): return exc.HTTPNotImplemented() - """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing arguments for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: -- cgit From 2bd6e5561339a6755709461dab9aa6cad4a1cf81 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Tue, 31 May 2011 09:51:20 -0400 Subject: pep8 fixes --- .../etc/xensource/scripts/ovs_configure_base_flows.py | 11 ++++++----- .../etc/xensource/scripts/ovs_configure_vif_flows.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py index 0186a3c8b..514a43a2d 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py @@ -34,13 +34,13 @@ def main(command, phys_dev_name, bridge_name): ovs_ofctl('del-flows', bridge_name) if command in ('online', 'reset'): - pnic_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', 'Interface', - phys_dev_name, 'ofport') + pnic_ofport = execute_get_output('/usr/bin/ovs-vsctl', 'get', + 'Interface', phys_dev_name, 'ofport') # these flows are lower priority than all VM-specific flows. - # allow all traffic from the physical NIC, as it is trusted (i.e., from a - # filtered vif, or from the physical infrastructure + # allow all traffic from the physical NIC, as it is trusted (i.e., + # from a filtered vif, or from the physical infrastructure) ovs_ofctl('add-flow', bridge_name, "priority=2,in_port=%s,actions=normal" % pnic_ofport) @@ -53,7 +53,8 @@ if __name__ == "__main__": print sys.argv script_name = os.path.basename(sys.argv[0]) print "This script configures base ovs flows." - print "usage: %s [online|offline|reset] phys-dev-name bridge-name" % script_name + print "usage: %s [online|offline|reset] phys-dev-name bridge-name" \ + % script_name print " ex: %s online eth0 xenbr0" % script_name sys.exit(1) else: diff --git a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py index 9fde69377..accd08b91 100755 --- a/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py +++ b/plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py @@ -52,7 +52,7 @@ def main(command, vif_raw, net_type): vif_name, dom_id, vif_index = vif_raw.split('-') vif = "%s%s.%s" % (vif_name, dom_id, vif_index) bridge = "xenbr%s" % vif_index - + xsls = execute_get_output('/usr/bin/xenstore-ls', '/local/domain/%s/vm-data/networking' % dom_id) macs = [line.split("=")[0].strip() for line in xsls.splitlines()] -- cgit From 95f103f276f6eb7decd6ebd17ff4ac106bc7222f Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 31 May 2011 11:17:35 -0400 Subject: More specific error messages for resize requests. --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4bd7ddb14..1ec74bc2e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -606,7 +606,7 @@ class ControllerV10(Controller): self.compute_api.resize(req.environ['nova.context'], id, flavor_id) else: - LOG.exception(_("Missing arguments for resize")) + LOG.exception(_("Missing 'flavorId' argument for resize")) return faults.Fault(exc.HTTPUnprocessableEntity()) except Exception, e: LOG.exception(_("Error in resize %s"), e) @@ -707,7 +707,7 @@ class ControllerV11(Controller): self.compute_api.resize(req.environ['nova.context'], id, flavor_id) else: - LOG.exception(_("Missing arguments for resize")) + LOG.exception(_("Missing 'flavorRef' argument for resize")) return faults.Fault(exc.HTTPUnprocessableEntity()) except Exception, e: LOG.exception(_("Error in resize %s"), e) -- cgit From b0c43e57ad6a7e5be8a749e70da39b7f7ba547bd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 31 May 2011 14:49:47 -0700 Subject: switch to using webob exception --- nova/api/ec2/metadatarequesthandler.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 720f264a4..9c8e52270 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -76,12 +76,10 @@ class MetadataRequestHandler(wsgi.Application): meta_data = cc.get_metadata(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) - resp = webob.Response() - resp.status = 500 - message = _('An unknown error has occurred. ' - 'Please try your request again.') - resp.body = str(utils.utf8(message)) - return resp + msg = _('An unknown error has occurred. ' + 'Please try your request again.') + exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg)) + return exc if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() -- cgit From 81f40ed1ca284bc9a8ee948ae23fdff93d632cb0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 31 May 2011 15:50:33 -0700 Subject: pep8 --- nova/api/ec2/metadatarequesthandler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 9c8e52270..b70266a20 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -75,7 +75,8 @@ class MetadataRequestHandler(wsgi.Application): try: meta_data = cc.get_metadata(remote_address) except Exception: - LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) + LOG.exception(_('Failed to get metadata for ip: %s'), + remote_address) msg = _('An unknown error has occurred. ' 'Please try your request again.') exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg)) -- cgit From 9fc8e71f1b201adc0a5e49ac3a94e22bf47596fb Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 1 Jun 2011 10:17:00 -0400 Subject: pep8 fixes --- nova/log.py | 2 +- nova/tests/test_notifier.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nova/log.py b/nova/log.py index 960598b14..6909916a1 100644 --- a/nova/log.py +++ b/nova/log.py @@ -272,7 +272,7 @@ class PublishErrorsHandler(logging.Handler): def emit(self, record): nova.notifier.api.notify('nova.error.publisher', 'error_notification', nova.notifier.api.ERROR, dict(error=record.msg)) - + def handle_exception(type, value, tb): extra = {} diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 523f38f24..64b799a2c 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -122,12 +122,13 @@ class NotifierTestCase(test.TestCase): self.stubs.Set(nova.flags.FLAGS, 'publish_errors', True) LOG = log.getLogger('nova') LOG.setup_from_flags() - msgs = [] + def mock_cast(context, topic, data): msgs.append(data) - self.stubs.Set(nova.rpc, 'cast', mock_cast) - LOG.error('foo'); + + self.stubs.Set(nova.rpc, 'cast', mock_cast) + LOG.error('foo') self.assertEqual(1, len(msgs)) msg = msgs[0] self.assertEqual(msg['event_type'], 'error_notification') -- cgit From 8b716bc23ac4e5e5398db9557757621fccb08204 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 1 Jun 2011 10:37:54 -0400 Subject: fix pep8 issues --- nova/compute/api.py | 3 +-- .../migrate_repo/versions/016_make_quotas_key_and_value.py | 3 +-- nova/scheduler/host_filter.py | 3 +-- nova/tests/api/openstack/test_servers.py | 3 +-- nova/tests/integrated/test_servers.py | 9 +++------ nova/tests/test_host_filter.py | 14 +++++--------- nova/tests/test_zone_aware_scheduler.py | 10 +++------- tools/install_venv.py | 2 +- 8 files changed, 16 insertions(+), 31 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index de774e807..3e991e68a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -270,8 +270,7 @@ class API(base.Base): 'instance_type': instance_type, 'filter': 'nova.scheduler.host_filter.' - 'InstanceTypeFilter' - }, + 'InstanceTypeFilter'}, "availability_zone": availability_zone, "injected_files": injected_files, "admin_password": admin_password}}) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index a2d8192ca..1a2a6d7ce 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -160,8 +160,7 @@ def convert_backward(migrate_engine, old_quotas, new_quotas): 'project_id': quota.project_id, 'created_at': quota.created_at, 'updated_at': quota.updated_at, - quota.resource: quota.hard_limit - } + quota.resource: quota.hard_limit} else: quotas[quota.project_id]['created_at'] = earliest( quota.created_at, quotas[quota.project_id]['created_at']) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 4260cbf42..8827db4d4 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -226,8 +226,7 @@ class JsonFilter(HostFilter): required_disk = instance_type['local_gb'] query = ['and', ['>=', '$compute.host_memory_free', required_ram], - ['>=', '$compute.disk_available', required_disk] - ] + ['>=', '$compute.disk_available', required_disk]] return (self._full_name(), json.dumps(query)) def _parse_string(self, string, host, services): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index fbde5c9ce..20379e2bd 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -774,8 +774,7 @@ class ServersTest(test.TestCase): def server_update(context, id, params): filtered_dict = dict( - display_name='server_test' - ) + display_name='server_test') self.assertEqual(params, filtered_dict) return filtered_dict diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index a67fa1bb5..35c6bb34f 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -194,8 +194,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {} post['rebuild'] = { "imageRef": "https://localhost/v1.1/32278/images/2", - "name": "blah" - } + "name": "blah"} self.api.post_server_action(created_server_id, post) LOG.debug("rebuilt server: %s" % created_server) @@ -224,8 +223,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {} post['rebuild'] = { "imageRef": "https://localhost/v1.1/32278/images/2", - "name": "blah" - } + "name": "blah"} metadata = {} for i in range(30): @@ -267,8 +265,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {} post['rebuild'] = { "imageRef": "https://localhost/v1.1/32278/images/2", - "name": "blah" - } + "name": "blah"} metadata = {} post['rebuild']['metadata'] = metadata diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 07817cc5a..098ebff3d 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -133,13 +133,11 @@ class HostFilterTestCase(test.TestCase): raw = ['or', ['and', ['<', '$compute.host_memory_free', 30], - ['<', '$compute.disk_available', 300] - ], + ['<', '$compute.disk_available', 300]], ['and', ['>', '$compute.host_memory_free', 70], - ['>', '$compute.disk_available', 700] - ] - ] + ['>', '$compute.disk_available', 700]]] + cooked = json.dumps(raw) hosts = hf.filter_hosts(self.zone_manager, cooked) @@ -183,13 +181,11 @@ class HostFilterTestCase(test.TestCase): self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps([]))) self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps({}))) self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps( - ['not', True, False, True, False] - ))) + ['not', True, False, True, False]))) try: hf.filter_hosts(self.zone_manager, json.dumps( - 'not', True, False, True, False - )) + 'not', True, False, True, False)) self.fail("Should give KeyError") except KeyError, e: pass diff --git a/nova/tests/test_zone_aware_scheduler.py b/nova/tests/test_zone_aware_scheduler.py index 37169fb97..90ae427e3 100644 --- a/nova/tests/test_zone_aware_scheduler.py +++ b/nova/tests/test_zone_aware_scheduler.py @@ -39,15 +39,11 @@ class FakeZoneManager(zone_manager.ZoneManager): def __init__(self): self.service_states = { 'host1': { - 'compute': {'ram': 1000} - }, + 'compute': {'ram': 1000}}, 'host2': { - 'compute': {'ram': 2000} - }, + 'compute': {'ram': 2000}}, 'host3': { - 'compute': {'ram': 3000} - } - } + 'compute': {'ram': 3000}}} class FakeEmptyZoneManager(zone_manager.ZoneManager): diff --git a/tools/install_venv.py b/tools/install_venv.py index 812b1dd0f..f4b6583ed 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -36,7 +36,7 @@ PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) def die(message, *args): - print >>sys.stderr, message % args + print >> sys.stderr, message % args sys.exit(1) -- cgit From c80fedead72456c18c3a0e63348e1a4d40c7e7c5 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 1 Jun 2011 10:58:17 -0400 Subject: updates to keep things looking better --- nova/compute/api.py | 7 +++++-- .../versions/016_make_quotas_key_and_value.py | 3 ++- nova/scheduler/host_filter.py | 3 ++- nova/tests/api/openstack/test_servers.py | 3 +-- nova/tests/integrated/test_servers.py | 9 ++++++--- nova/tests/test_host_filter.py | 7 +++++-- nova/tests/test_zone_aware_scheduler.py | 16 ++++++++++------ 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 3e991e68a..263e44bab 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -270,10 +270,13 @@ class API(base.Base): 'instance_type': instance_type, 'filter': 'nova.scheduler.host_filter.' - 'InstanceTypeFilter'}, + 'InstanceTypeFilter', + }, "availability_zone": availability_zone, "injected_files": injected_files, - "admin_password": admin_password}}) + "admin_password": admin_password, + }, + }) for group_id in security_groups: self.trigger_security_group_members_refresh(elevated, group_id) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py index 1a2a6d7ce..5d0593f2e 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py @@ -160,7 +160,8 @@ def convert_backward(migrate_engine, old_quotas, new_quotas): 'project_id': quota.project_id, 'created_at': quota.created_at, 'updated_at': quota.updated_at, - quota.resource: quota.hard_limit} + quota.resource: quota.hard_limit, + } else: quotas[quota.project_id]['created_at'] = earliest( quota.created_at, quotas[quota.project_id]['created_at']) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 8827db4d4..7d6ee0ee3 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -226,7 +226,8 @@ class JsonFilter(HostFilter): required_disk = instance_type['local_gb'] query = ['and', ['>=', '$compute.host_memory_free', required_ram], - ['>=', '$compute.disk_available', required_disk]] + ['>=', '$compute.disk_available', required_disk], + ] return (self._full_name(), json.dumps(query)) def _parse_string(self, string, host, services): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 20379e2bd..ee27d24eb 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -773,8 +773,7 @@ class ServersTest(test.TestCase): self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - filtered_dict = dict( - display_name='server_test') + filtered_dict = dict(display_name='server_test') self.assertEqual(params, filtered_dict) return filtered_dict diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 35c6bb34f..fcb517cf5 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -194,7 +194,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {} post['rebuild'] = { "imageRef": "https://localhost/v1.1/32278/images/2", - "name": "blah"} + "name": "blah", + } self.api.post_server_action(created_server_id, post) LOG.debug("rebuilt server: %s" % created_server) @@ -223,7 +224,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {} post['rebuild'] = { "imageRef": "https://localhost/v1.1/32278/images/2", - "name": "blah"} + "name": "blah", + } metadata = {} for i in range(30): @@ -265,7 +267,8 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {} post['rebuild'] = { "imageRef": "https://localhost/v1.1/32278/images/2", - "name": "blah"} + "name": "blah", + } metadata = {} post['rebuild']['metadata'] = metadata diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py index 098ebff3d..3361c7b73 100644 --- a/nova/tests/test_host_filter.py +++ b/nova/tests/test_host_filter.py @@ -133,10 +133,13 @@ class HostFilterTestCase(test.TestCase): raw = ['or', ['and', ['<', '$compute.host_memory_free', 30], - ['<', '$compute.disk_available', 300]], + ['<', '$compute.disk_available', 300], + ], ['and', ['>', '$compute.host_memory_free', 70], - ['>', '$compute.disk_available', 700]]] + ['>', '$compute.disk_available', 700], + ], + ] cooked = json.dumps(raw) hosts = hf.filter_hosts(self.zone_manager, cooked) diff --git a/nova/tests/test_zone_aware_scheduler.py b/nova/tests/test_zone_aware_scheduler.py index 90ae427e3..72b74be20 100644 --- a/nova/tests/test_zone_aware_scheduler.py +++ b/nova/tests/test_zone_aware_scheduler.py @@ -38,12 +38,16 @@ class FakeZoneAwareScheduler(zone_aware_scheduler.ZoneAwareScheduler): class FakeZoneManager(zone_manager.ZoneManager): def __init__(self): self.service_states = { - 'host1': { - 'compute': {'ram': 1000}}, - 'host2': { - 'compute': {'ram': 2000}}, - 'host3': { - 'compute': {'ram': 3000}}} + 'host1': { + 'compute': {'ram': 1000}, + }, + 'host2': { + 'compute': {'ram': 2000}, + }, + 'host3': { + 'compute': {'ram': 3000}, + }, + } class FakeEmptyZoneManager(zone_manager.ZoneManager): -- cgit From ad964ef8934a14329a9100946bed26bcf37b1d52 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 1 Jun 2011 14:56:06 -0400 Subject: Updates to the 018_rename_server_management_url to avoid adding and dropping a column. Just simply rename the column. --- .../versions/018_rename_server_management_url.py | 29 ++++------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py b/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py index a169afb40..73c76f666 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py @@ -14,23 +14,10 @@ # License for the specific language governing permissions and limitations # under the License. -from sqlalchemy import Column, Integer, MetaData, String, Table -#from nova import log as logging +from sqlalchemy import MetaData, Table meta = MetaData() -c_manageent = Column('server_manageent_url', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - -c_management = Column('server_management_url', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - def upgrade(migrate_engine): # Upgrade operations go here. Don't create your own engine; @@ -40,11 +27,8 @@ def upgrade(migrate_engine): tokens = Table('auth_tokens', meta, autoload=True, autoload_with=migrate_engine) - tokens.create_column(c_management) - migrate_engine.execute(tokens.update() - .values(server_management_url=tokens.c.server_manageent_url)) - - tokens.c.server_manageent_url.drop() + c_manageent = tokens.c.server_manageent_url + c_manageent.alter(name='server_management_url') def downgrade(migrate_engine): @@ -53,8 +37,5 @@ def downgrade(migrate_engine): tokens = Table('auth_tokens', meta, autoload=True, autoload_with=migrate_engine) - tokens.create_column(c_manageent) - migrate_engine.execute(tokens.update() - .values(server_manageent_url=tokens.c.server_management_url)) - - tokens.c.server_management_url.drop() + c_management = tokens.c.server_management_url + c_management.alter(name='server_manageent_url') -- cgit From d77aa5862762bc6efda46d92940143a1b9cbccf5 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 1 Jun 2011 14:46:05 -0500 Subject: Allow SSL AMQP connections. --- nova/flags.py | 1 + nova/rpc.py | 1 + 2 files changed, 2 insertions(+) diff --git a/nova/flags.py b/nova/flags.py index 9eaac5596..d5090edba 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -296,6 +296,7 @@ DEFINE_bool('fake_network', False, 'should we use fake network devices and addresses') DEFINE_string('rabbit_host', 'localhost', 'rabbit host') DEFINE_integer('rabbit_port', 5672, 'rabbit port') +DEFINE_bool('rabbit_use_ssl', False, 'connect over SSL') DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') diff --git a/nova/rpc.py b/nova/rpc.py index c5277c6a9..2e78a31e7 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -65,6 +65,7 @@ class Connection(carrot_connection.BrokerConnection): if new or not hasattr(cls, '_instance'): params = dict(hostname=FLAGS.rabbit_host, port=FLAGS.rabbit_port, + ssl=FLAGS.rabbit_use_ssl, userid=FLAGS.rabbit_userid, password=FLAGS.rabbit_password, virtual_host=FLAGS.rabbit_virtual_host) -- cgit From ced79009e6555eb75f3862184834a883d37b2062 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 1 Jun 2011 16:01:41 -0700 Subject: fixed as per peer review to make more consistent --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 51373d282..5de4d9e81 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -536,7 +536,7 @@ class FloatingIpCommands(object): for floating_ip in floating_ips: instance = None if floating_ip['fixed_ip']: - instance = floating_ip['fixed_ip']['instance'].hostname + instance = floating_ip['fixed_ip']['instance']['hostname'] print "%s\t%s\t%s" % (floating_ip['host'], floating_ip['address'], instance) -- cgit From ef1f5b3aadde2fedb4b2d197af0f1c0f07375714 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 1 Jun 2011 16:51:26 -0700 Subject: fix novarc to work on mac and zsh --- nova/auth/novarc.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index 8170fcafe..4a1f41802 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -1,4 +1,5 @@ -NOVA_KEY_DIR=$(dirname $(readlink -f ${BASH_SOURCE})) +NOVARC=$(python -c 'import os,sys; print os.path.abspath(os.path.realpath(sys.argv[1]))' ${BASH_SOURCE-0}) +NOVA_KEY_DIR=$(dirname ${NOVARC}) export EC2_ACCESS_KEY="%(access)s:%(project)s" export EC2_SECRET_KEY="%(secret)s" export EC2_URL="%(ec2)s" -- cgit From 8ee41f679bd72af6aab098f9d9735e342b281635 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 1 Jun 2011 18:55:41 -0700 Subject: missed a couple chars --- nova/auth/novarc.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index 4a1f41802..92eed3520 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -1,4 +1,4 @@ -NOVARC=$(python -c 'import os,sys; print os.path.abspath(os.path.realpath(sys.argv[1]))' ${BASH_SOURCE-0}) +NOVARC=$(python -c 'import os,sys; print os.path.abspath(os.path.realpath(sys.argv[1]))' ${BASH_SOURCE:-$0}) NOVA_KEY_DIR=$(dirname ${NOVARC}) export EC2_ACCESS_KEY="%(access)s:%(project)s" export EC2_SECRET_KEY="%(secret)s" -- cgit From b39b0e66f16d49890189c63fba528734ef476068 Mon Sep 17 00:00:00 2001 From: Mike Scherbakov Date: Thu, 2 Jun 2011 10:29:58 +0400 Subject: Refactored after review, fixed merge. --- nova/image/fake.py | 5 +---- nova/tests/test_libvirt.py | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/nova/image/fake.py b/nova/image/fake.py index 4bf25d9af..019d683f4 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -82,12 +82,9 @@ class _FakeImageService(service.BaseImageService): :raises: Duplicate if the image already exist. """ - #image_id = int(metadata['id']) - # metadata['id'] may not exists, and since image_id is - # randomly generated in local.py, let us do the same here try: image_id = int(metadata['id']) - except: + except KeyError: image_id = random.randint(0, 2 ** 31 - 1) if self.images.get(image_id): diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index d9316ab4f..d008a149e 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -328,14 +328,14 @@ class LibvirtConnTestCase(test.TestCase): # To work with it from snapshot, the single image_service is needed recv_meta = image_service.create(context, sent_meta) - self.mox.StubOutWithMock(libvirt_conn.LibvirtConnection, '_conn') - libvirt_conn.LibvirtConnection._conn.lookupByName = fake_lookup - self.mox.StubOutWithMock(libvirt_conn.utils, 'execute') - libvirt_conn.utils.execute = fake_execute + self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn') + connection.LibvirtConnection._conn.lookupByName = fake_lookup + self.mox.StubOutWithMock(connection.utils, 'execute') + connection.utils.execute = fake_execute self.mox.ReplayAll() - conn = libvirt_conn.LibvirtConnection(False) + conn = connection.LibvirtConnection(False) conn.snapshot(instance_ref, recv_meta['id']) snapshot = image_service.show(context, recv_meta['id']) -- cgit From be2f5e986e41f8f8d63c0ef7a5c03916c70ba455 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 2 Jun 2011 10:20:26 -0700 Subject: don't use python if readlink is available --- nova/auth/novarc.template | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index 92eed3520..d30bd849c 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -1,5 +1,6 @@ -NOVARC=$(python -c 'import os,sys; print os.path.abspath(os.path.realpath(sys.argv[1]))' ${BASH_SOURCE:-$0}) -NOVA_KEY_DIR=$(dirname ${NOVARC}) +NOVARC=$(readlink -f "${BASH_SOURCE:-${0}}" 2>/dev/null) || + NOVARC=$(python -c 'import os,sys; print os.path.abspath(os.path.realpath(sys.argv[1]))' "${BASH_SOURCE:-${0}}") +NOVA_KEY_DIR=${NOVARC%/*} export EC2_ACCESS_KEY="%(access)s:%(project)s" export EC2_SECRET_KEY="%(secret)s" export EC2_URL="%(ec2)s" -- cgit