trunk merge

This commit is contained in:
Trey Morris
2011-06-23 18:10:30 -05:00
26 changed files with 833 additions and 106 deletions

View File

@@ -263,6 +263,11 @@ class RoleCommands(object):
"""adds role to user
if project is specified, adds project specific role
arguments: user, role [project]"""
if project:
projobj = self.manager.get_project(project)
if not projobj.has_member(user):
print "%s not a member of %s" % (user, project)
return
self.manager.add_role(user, role, project)
def has(self, user, role, project=None):
@@ -900,7 +905,7 @@ class InstanceTypeCommands(object):
try:
instance_types.create(name, memory, vcpus, local_gb,
flavorid, swap, rxtx_quota, rxtx_cap)
except exception.InvalidInputException:
except exception.InvalidInput:
print "Must supply valid parameters to create instance_type"
print e
sys.exit(1)

View File

@@ -21,7 +21,11 @@ Admin API controller, exposed through http via the api worker.
"""
import base64
import datetime
import netaddr
import urllib
from nova import compute
from nova import db
from nova import exception
from nova import flags
@@ -117,6 +121,9 @@ class AdminController(object):
def __str__(self):
return 'AdminController'
def __init__(self):
self.compute_api = compute.API()
def describe_instance_types(self, context, **_kwargs):
"""Returns all active instance types data (vcpus, memory, etc.)"""
return {'instanceTypeSet': [instance_dict(v) for v in
@@ -324,3 +331,41 @@ class AdminController(object):
rv.append(host_dict(host, compute, instances, volume, volumes,
now))
return {'hosts': rv}
def _provider_fw_rule_exists(self, context, rule):
# TODO(todd): we call this repeatedly, can we filter by protocol?
for old_rule in db.provider_fw_rule_get_all(context):
if all([rule[k] == old_rule[k] for k in ('cidr', 'from_port',
'to_port', 'protocol')]):
return True
return False
def block_external_addresses(self, context, cidr):
"""Add provider-level firewall rules to block incoming traffic."""
LOG.audit(_('Blocking traffic to all projects incoming from %s'),
cidr, context=context)
cidr = urllib.unquote(cidr).decode()
# raise if invalid
netaddr.IPNetwork(cidr)
rule = {'cidr': cidr}
tcp_rule = rule.copy()
tcp_rule.update({'protocol': 'tcp', 'from_port': 1, 'to_port': 65535})
udp_rule = rule.copy()
udp_rule.update({'protocol': 'udp', 'from_port': 1, 'to_port': 65535})
icmp_rule = rule.copy()
icmp_rule.update({'protocol': 'icmp', 'from_port': -1,
'to_port': None})
rules_added = 0
if not self._provider_fw_rule_exists(context, tcp_rule):
db.provider_fw_rule_create(context, tcp_rule)
rules_added += 1
if not self._provider_fw_rule_exists(context, udp_rule):
db.provider_fw_rule_create(context, udp_rule)
rules_added += 1
if not self._provider_fw_rule_exists(context, icmp_rule):
db.provider_fw_rule_create(context, icmp_rule)
rules_added += 1
if not rules_added:
raise exception.ApiError(_('Duplicate rule'))
self.compute_api.trigger_provider_fw_rules_refresh(context)
return {'status': 'OK', 'message': 'Added %s rules' % rules_added}

View File

@@ -16,6 +16,7 @@
# under the License.
from webob import exc
from xml.dom import minidom
from nova import flags
from nova import image
@@ -59,7 +60,7 @@ class Controller(object):
context = req.environ['nova.context']
metadata = self._get_metadata(context, image_id)
if id in metadata:
return {id: metadata[id]}
return {'meta': {id: metadata[id]}}
else:
return faults.Fault(exc.HTTPNotFound())
@@ -77,15 +78,22 @@ class Controller(object):
def update(self, req, image_id, id, body):
context = req.environ['nova.context']
if not id in body:
try:
meta = body['meta']
except KeyError:
expl = _('Incorrect request body format')
raise exc.HTTPBadRequest(explanation=expl)
if not id in meta:
expl = _('Request body and URI mismatch')
raise exc.HTTPBadRequest(explanation=expl)
if len(body) > 1:
if len(meta) > 1:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id, img)
metadata[id] = body[id]
metadata[id] = meta[id]
self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
@@ -103,9 +111,55 @@ class Controller(object):
self.image_service.update(context, image_id, img, None)
class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer):
def __init__(self):
xmlns = wsgi.XMLNS_V11
super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns)
def _meta_item_to_xml(self, doc, key, value):
node = doc.createElement('meta')
node.setAttribute('key', key)
text = doc.createTextNode(value)
node.appendChild(text)
return node
def _meta_list_to_xml(self, xml_doc, meta_items):
container_node = xml_doc.createElement('metadata')
for (key, value) in meta_items:
item_node = self._meta_item_to_xml(xml_doc, key, value)
container_node.appendChild(item_node)
return container_node
def _meta_list_to_xml_string(self, metadata_dict):
xml_doc = minidom.Document()
items = metadata_dict['metadata'].items()
container_node = self._meta_list_to_xml(xml_doc, items)
self._add_xmlns(container_node)
return container_node.toprettyxml(indent=' ')
def index(self, metadata_dict):
return self._meta_list_to_xml_string(metadata_dict)
def create(self, metadata_dict):
return self._meta_list_to_xml_string(metadata_dict)
def _meta_item_to_xml_string(self, meta_item_dict):
xml_doc = minidom.Document()
item_key, item_value = meta_item_dict.items()[0]
item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
self._add_xmlns(item_node)
return item_node.toprettyxml(indent=' ')
def show(self, meta_item_dict):
return self._meta_item_to_xml_string(meta_item_dict['meta'])
def update(self, meta_item_dict):
return self._meta_item_to_xml_string(meta_item_dict['meta'])
def create_resource():
serializers = {
'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
'application/xml': ImageMetadataXMLSerializer(),
}
return wsgi.Resource(Controller(), serializers=serializers)

View File

@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import os.path
import webob.exc
from nova import compute
@@ -99,21 +101,27 @@ class Controller(object):
raise webob.exc.HTTPBadRequest()
try:
server_id = self._server_id_from_req_data(body)
server_id = self._server_id_from_req(req, body)
image_name = body["image"]["name"]
except KeyError:
raise webob.exc.HTTPBadRequest()
image = self._compute_service.snapshot(context, server_id, image_name)
props = self._get_extra_properties(req, body)
image = self._compute_service.snapshot(context, server_id,
image_name, props)
return dict(image=self.get_builder(req).build(image, detail=True))
def get_builder(self, request):
"""Indicates that you must use a Controller subclass."""
raise NotImplementedError
def _server_id_from_req_data(self, data):
def _server_id_from_req(self, req, data):
raise NotImplementedError()
def _get_extra_properties(self, req, data):
return {}
class ControllerV10(Controller):
"""Version 1.0 specific controller logic."""
@@ -149,8 +157,12 @@ class ControllerV10(Controller):
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=True) for image in images])
def _server_id_from_req_data(self, data):
def _server_id_from_req(self, req, data):
try:
return data['image']['serverId']
except KeyError:
msg = _("Expected serverId attribute on server entity.")
raise webob.exc.HTTPBadRequest(explanation=msg)
class ControllerV11(Controller):
@@ -189,8 +201,27 @@ class ControllerV11(Controller):
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=True) for image in images])
def _server_id_from_req_data(self, data):
return data['image']['serverRef']
def _server_id_from_req(self, req, data):
try:
server_ref = data['image']['serverRef']
except KeyError:
msg = _("Expected serverRef attribute on server entity.")
raise webob.exc.HTTPBadRequest(explanation=msg)
head, tail = os.path.split(server_ref)
if head and head != os.path.join(req.application_url, 'servers'):
msg = _("serverRef must match request url")
raise webob.exc.HTTPBadRequest(explanation=msg)
return tail
def _get_extra_properties(self, req, data):
server_ref = data['image']['serverRef']
if not server_ref.startswith('http'):
server_ref = os.path.join(req.application_url, 'servers',
server_ref)
return {'instance_ref': server_ref}
def create_resource(version='1.0'):

View File

@@ -46,13 +46,9 @@ class ViewBuilder(object):
except KeyError:
image['status'] = image['status'].upper()
def _build_server(self, image, instance_id):
def _build_server(self, image, image_obj):
"""Indicates that you must use a ViewBuilder subclass."""
raise NotImplementedError
def generate_server_ref(self, server_id):
"""Return an href string pointing to this server."""
return os.path.join(self._url, "servers", str(server_id))
raise NotImplementedError()
def generate_href(self, image_id):
"""Return an href string pointing to this object."""
@@ -60,8 +56,6 @@ class ViewBuilder(object):
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""
properties = image_obj.get("properties", {})
self._format_dates(image_obj)
if "status" in image_obj:
@@ -72,11 +66,7 @@ class ViewBuilder(object):
"name": image_obj.get("name"),
}
if "instance_id" in properties:
try:
self._build_server(image, int(properties["instance_id"]))
except ValueError:
pass
self._build_server(image, image_obj)
if detail:
image.update({
@@ -94,15 +84,21 @@ class ViewBuilder(object):
class ViewBuilderV10(ViewBuilder):
"""OpenStack API v1.0 Image Builder"""
def _build_server(self, image, instance_id):
image["serverId"] = instance_id
def _build_server(self, image, image_obj):
try:
image['serverId'] = int(image_obj['properties']['instance_id'])
except (KeyError, ValueError):
pass
class ViewBuilderV11(ViewBuilder):
"""OpenStack API v1.1 Image Builder"""
def _build_server(self, image, instance_id):
image["serverRef"] = self.generate_server_ref(instance_id)
def _build_server(self, image, image_obj):
try:
image['serverRef'] = image_obj['properties']['instance_ref']
except KeyError:
return
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""

View File

@@ -232,12 +232,14 @@ class XMLDictSerializer(DictSerializer):
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)
self._add_xmlns(node)
return node.toprettyxml(indent=' ', encoding='utf-8')
def _add_xmlns(self, node):
if self.xmlns is not None:
node.setAttribute('xmlns', self.xmlns)
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
result = doc.createElement(nodename)

View File

@@ -478,6 +478,16 @@ class API(base.Base):
{"method": "refresh_security_group_members",
"args": {"security_group_id": group_id}})
def trigger_provider_fw_rules_refresh(self, context):
"""Called when a rule is added to or removed from a security_group"""
hosts = [x['host'] for (x, idx)
in db.service_get_all_compute_sorted(context)]
for host in hosts:
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.compute_topic, host),
{'method': 'refresh_provider_fw_rules', 'args': {}})
def update(self, context, instance_id, **kwargs):
"""Updates the instance in the datastore.
@@ -683,7 +693,7 @@ class API(base.Base):
raise exception.Error(_("Unable to find host for Instance %s")
% instance_id)
def snapshot(self, context, instance_id, name):
def snapshot(self, context, instance_id, name, extra_properties=None):
"""Snapshot the given instance.
:returns: A dict containing image metadata
@@ -691,6 +701,7 @@ class API(base.Base):
properties = {'instance_id': str(instance_id),
'user_id': str(context.user_id),
'image_state': 'creating'}
properties.update(extra_properties or {})
sent_meta = {'name': name, 'is_public': False,
'status': 'creating', 'properties': properties}
recv_meta = self.image_service.create(context, sent_meta)

View File

@@ -201,6 +201,11 @@ class ComputeManager(manager.SchedulerDependentManager):
"""
return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception
def refresh_provider_fw_rules(self, context, **_kwargs):
"""This call passes straight through to the virtualization driver."""
return self.driver.refresh_provider_fw_rules()
def _setup_block_device_mapping(self, context, instance_id):
"""setup volumes for block device mapping"""
self.db.instance_set_state(context,
@@ -277,16 +282,17 @@ class ComputeManager(manager.SchedulerDependentManager):
'networking')
is_vpn = instance['image_ref'] == str(FLAGS.vpn_image_id)
try:
# NOTE(vish): This could be a cast because we don't do anything
# with the address currently, but I'm leaving it as
# a call to ensure that network setup completes. We
# will eventually also need to save the address here.
if not FLAGS.stub_network:
network_info = self.network_api.allocate_for_instance(context,
instance,
vpn=is_vpn)
instance, vpn=is_vpn)
LOG.debug(_("instance network_info: |%s|"), network_info)
self.network_manager.setup_compute_network(context, instance_id)
self.network_manager.setup_compute_network(context,
instance_id)
else:
# TODO(tr3buchet) not really sure how this should be handled.
# virt requires network_info to be passed in but stub_network
@@ -294,14 +300,13 @@ class ComputeManager(manager.SchedulerDependentManager):
# all vif creation and network injection, maybe this is correct
network_info = []
block_device_mapping = self._setup_block_device_mapping(context,
instance_id)
bd_mapping = self._setup_block_device_mapping(context, instance_id)
# TODO(vish) check to make sure the availability zone matches
self._update_state(context, instance_id, power_state.BUILDING)
try:
self.driver.spawn(instance, network_info, block_device_mapping)
self.driver.spawn(instance, network_info, bd_mapping)
except Exception as ex: # pylint: disable=W0702
msg = _("Instance '%(instance_id)s' failed to spawn. Is "
"virtualization enabled in the BIOS? Details: "
@@ -310,6 +315,12 @@ class ComputeManager(manager.SchedulerDependentManager):
self._update_launched_at(context, instance_id)
self._update_state(context, instance_id)
except exception.InstanceNotFound:
# FIXME(wwolf): We are just ignoring InstanceNotFound
# exceptions here in case the instance was immediately
# deleted before it actually got created. This should
# be fixed once we have no-db-messaging
pass
@exception.wrap_exception
def run_instance(self, context, instance_id, **kwargs):

View File

@@ -1084,6 +1084,19 @@ def security_group_rule_destroy(context, security_group_rule_id):
###################
def provider_fw_rule_create(context, rule):
"""Add a firewall rule at the provider level (all hosts & instances)."""
return IMPL.provider_fw_rule_create(context, rule)
def provider_fw_rule_get_all(context):
"""Get all provider-level firewall rules."""
return IMPL.provider_fw_rule_get_all(context)
###################
def user_get(context, id):
"""Get user by id."""
return IMPL.user_get(context, id)

View File

@@ -2413,6 +2413,24 @@ def security_group_rule_destroy(context, security_group_rule_id):
###################
@require_admin_context
def provider_fw_rule_create(context, rule):
fw_rule_ref = models.ProviderFirewallRule()
fw_rule_ref.update(rule)
fw_rule_ref.save()
return fw_rule_ref
def provider_fw_rule_get_all(context):
session = get_session()
return session.query(models.ProviderFirewallRule).\
filter_by(deleted=can_read_deleted(context)).\
all()
###################
@require_admin_context
def user_get(context, id, session=None):
if not session:

View File

@@ -0,0 +1,75 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
from sqlalchemy import *
from migrate import *
from nova import log as logging
meta = MetaData()
# Just for the ForeignKey and column creation to succeed, these are not the
# actual definitions of instances or services.
instances = Table('instances', meta,
Column('id', Integer(), primary_key=True, nullable=False),
)
services = Table('services', meta,
Column('id', Integer(), primary_key=True, nullable=False),
)
networks = Table('networks', meta,
Column('id', Integer(), primary_key=True, nullable=False),
)
#
# New Tables
#
provider_fw_rules = Table('provider_fw_rules', meta,
Column('created_at', DateTime(timezone=False)),
Column('updated_at', DateTime(timezone=False)),
Column('deleted_at', DateTime(timezone=False)),
Column('deleted', Boolean(create_constraint=True, name=None)),
Column('id', Integer(), primary_key=True, nullable=False),
Column('protocol',
String(length=5, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('from_port', Integer()),
Column('to_port', Integer()),
Column('cidr',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False))
)
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
for table in (provider_fw_rules,):
try:
table.create()
except Exception:
logging.info(repr(table))
logging.exception('Exception while creating table')
raise

View File

@@ -493,6 +493,17 @@ class SecurityGroupIngressRule(BASE, NovaBase):
group_id = Column(Integer, ForeignKey('security_groups.id'))
class ProviderFirewallRule(BASE, NovaBase):
"""Represents a rule in a security group."""
__tablename__ = 'provider_fw_rules'
id = Column(Integer, primary_key=True)
protocol = Column(String(5)) # "tcp", "udp", or "icmp"
from_port = Column(Integer)
to_port = Column(Integer)
cidr = Column(String(255))
class KeyPair(BASE, NovaBase):
"""Represents a public key pair for ssh."""
__tablename__ = 'key_pairs'

View File

@@ -191,6 +191,13 @@ class IptablesTable(object):
{'chain': chain, 'rule': rule,
'top': top, 'wrap': wrap})
def empty_chain(self, chain, wrap=True):
"""Remove all rules from a chain."""
chained_rules = [rule for rule in self.rules
if rule.chain == chain and rule.wrap == wrap]
for rule in chained_rules:
self.rules.remove(rule)
class IptablesManager(object):
"""Wrapper for iptables.

View File

@@ -140,9 +140,10 @@ def stub_out_networking(stubs):
def stub_out_compute_api_snapshot(stubs):
def snapshot(self, context, instance_id, name):
return dict(id='123', status='ACTIVE',
properties=dict(instance_id='123'))
def snapshot(self, context, instance_id, name, extra_properties=None):
props = dict(instance_id=instance_id, instance_ref=instance_id)
props.update(extra_properties or {})
return dict(id='123', status='ACTIVE', name=name, properties=props)
stubs.Set(nova.compute.API, 'snapshot', snapshot)

View File

@@ -19,6 +19,7 @@ import json
import stubout
import unittest
import webob
import xml.dom.minidom as minidom
from nova import flags
@@ -105,19 +106,62 @@ class ImageMetaDataTest(unittest.TestCase):
self.assertEqual(200, res.status_int)
self.assertEqual('value1', res_dict['metadata']['key1'])
def test_index_xml(self):
serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
fixture = {
'metadata': {
'one': 'two',
'three': 'four',
},
}
output = serializer.index(fixture)
actual = minidom.parseString(output.replace(" ", ""))
expected = minidom.parseString("""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
<meta key="three">
four
</meta>
<meta key="one">
two
</meta>
</metadata>
""".replace(" ", ""))
self.assertEqual(expected.toxml(), actual.toxml())
def test_show(self):
req = webob.Request.blank('/v1.1/images/1/meta/key1')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(200, res.status_int)
self.assertEqual('value1', res_dict['key1'])
self.assertTrue('meta' in res_dict)
self.assertEqual(len(res_dict['meta']), 1)
self.assertEqual('value1', res_dict['meta']['key1'])
def test_show_xml(self):
serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
fixture = {
'meta': {
'one': 'two',
},
}
output = serializer.show(fixture)
actual = minidom.parseString(output.replace(" ", ""))
expected = minidom.parseString("""
<meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
two
</meta>
""".replace(" ", ""))
self.assertEqual(expected.toxml(), actual.toxml())
def test_show_not_found(self):
req = webob.Request.blank('/v1.1/images/1/meta/key9')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(404, res.status_int)
def test_create(self):
@@ -135,22 +179,79 @@ class ImageMetaDataTest(unittest.TestCase):
self.assertEqual('value2', res_dict['metadata']['key2'])
self.assertEqual(1, len(res_dict))
def test_create_xml(self):
serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
fixture = {
'metadata': {
'key9': 'value9',
'key2': 'value2',
'key1': 'value1',
},
}
output = serializer.create(fixture)
actual = minidom.parseString(output.replace(" ", ""))
expected = minidom.parseString("""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
<meta key="key2">
value2
</meta>
<meta key="key9">
value9
</meta>
<meta key="key1">
value1
</meta>
</metadata>
""".replace(" ", ""))
self.assertEqual(expected.toxml(), actual.toxml())
def test_update_item(self):
req = webob.Request.blank('/v1.1/images/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
req.body = '{"meta": {"key1": "zz"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int)
res_dict = json.loads(res.body)
self.assertTrue('meta' in res_dict)
self.assertEqual(len(res_dict['meta']), 1)
self.assertEqual('zz', res_dict['meta']['key1'])
def test_update_item_bad_body(self):
req = webob.Request.blank('/v1.1/images/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
req.body = '{"key1": "zz"}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int)
res_dict = json.loads(res.body)
self.assertEqual('zz', res_dict['key1'])
self.assertEqual(400, res.status_int)
def test_update_item_xml(self):
serializer = openstack.image_metadata.ImageMetadataXMLSerializer()
fixture = {
'meta': {
'one': 'two',
},
}
output = serializer.update(fixture)
actual = minidom.parseString(output.replace(" ", ""))
expected = minidom.parseString("""
<meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
two
</meta>
""".replace(" ", ""))
self.assertEqual(expected.toxml(), actual.toxml())
def test_update_item_too_many_keys(self):
req = webob.Request.blank('/v1.1/images/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
req.body = '{"key1": "value1", "key2": "value2"}'
req.body = '{"meta": {"key1": "value1", "key2": "value2"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
@@ -159,7 +260,7 @@ class ImageMetaDataTest(unittest.TestCase):
req = webob.Request.blank('/v1.1/images/1/meta/bad')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
req.body = '{"key1": "value1"}'
req.body = '{"meta": {"key1": "value1"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
@@ -195,7 +296,7 @@ class ImageMetaDataTest(unittest.TestCase):
req = webob.Request.blank('/v1.1/images/3/meta/blah')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
req.body = '{"blah": "blah"}'
req.body = '{"meta": {"blah": "blah"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)

View File

@@ -618,7 +618,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 124,
'name': 'queued backup',
'serverId': 42,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'QUEUED',
@@ -626,7 +625,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 125,
'name': 'saving backup',
'serverId': 42,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'SAVING',
@@ -635,7 +633,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 126,
'name': 'active backup',
'serverId': 42,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE'
@@ -643,7 +640,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 127,
'name': 'killed backup',
'serverId': 42,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
@@ -689,7 +685,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 124,
'name': 'queued backup',
'serverRef': "http://localhost/v1.1/servers/42",
'serverRef': "http://localhost:8774/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'QUEUED',
@@ -711,7 +707,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 125,
'name': 'saving backup',
'serverRef': "http://localhost/v1.1/servers/42",
'serverRef': "http://localhost:8774/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'SAVING',
@@ -734,7 +730,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 126,
'name': 'active backup',
'serverRef': "http://localhost/v1.1/servers/42",
'serverRef': "http://localhost:8774/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
@@ -756,7 +752,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 127,
'name': 'killed backup',
'serverRef': "http://localhost/v1.1/servers/42",
'serverRef': "http://localhost:8774/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
@@ -1002,6 +998,30 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = req.get_response(fakes.wsgi_app())
self.assertEqual(200, response.status_int)
def test_create_image_v1_1_actual_server_ref(self):
serverRef = 'http://localhost/v1.1/servers/1'
body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
req = webob.Request.blank('/v1.1/images')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
response = req.get_response(fakes.wsgi_app())
self.assertEqual(200, response.status_int)
result = json.loads(response.body)
self.assertEqual(result['image']['serverRef'], serverRef)
def test_create_image_v1_1_server_ref_bad_hostname(self):
serverRef = 'http://asdf/v1.1/servers/1'
body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
req = webob.Request.blank('/v1.1/images')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
response = req.get_response(fakes.wsgi_app())
self.assertEqual(400, response.status_int)
def test_create_image_v1_1_xml_serialization(self):
body = dict(image=dict(serverRef='123', name='Backup 1'))
@@ -1018,7 +1038,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
<image
created="None"
id="123"
name="None"
name="Backup 1"
serverRef="http://localhost/v1.1/servers/123"
status="ACTIVE"
updated="None"
@@ -1065,7 +1085,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
image_id += 1
# Backup for User 1
backup_properties = {'instance_id': '42', 'user_id': '1'}
server_ref = 'http://localhost:8774/v1.1/servers/42'
backup_properties = {'instance_ref': server_ref, 'user_id': '1'}
for status in ('queued', 'saving', 'active', 'killed'):
add_fixture(id=image_id, name='%s backup' % status,
is_public=False, status=status,

View File

@@ -672,8 +672,7 @@ class WsgiLimiterTest(BaseLimitTestSuite):
"""Only POSTs should work."""
requests = []
for method in ["GET", "PUT", "DELETE", "HEAD", "OPTIONS"]:
request = webob.Request.blank("/")
request.body = self._request_data("GET", "/something")
request = webob.Request.blank("/", method=method)
response = request.get_response(self.app)
self.assertEqual(response.status_int, 405)

View File

@@ -1504,7 +1504,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 400)
def test_resized_server_has_correct_status(self):
req = self.webreq('/1', 'GET', dict(resize=dict(flavorId=3)))
req = self.webreq('/1', 'GET')
def fake_migration_get(*args):
return {}

View File

@@ -10,13 +10,13 @@ from nova.api.openstack import wsgi
class RequestTest(test.TestCase):
def test_content_type_missing(self):
request = wsgi.Request.blank('/tests/123')
request = wsgi.Request.blank('/tests/123', method='POST')
request.body = "<body />"
self.assertRaises(exception.InvalidContentType,
request.get_content_type)
def test_content_type_unsupported(self):
request = wsgi.Request.blank('/tests/123')
request = wsgi.Request.blank('/tests/123', method='POST')
request.headers["Content-Type"] = "text/html"
request.body = "asdf<br />"
self.assertRaises(exception.InvalidContentType,

View File

@@ -0,0 +1,89 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
from eventlet import greenthread
from nova import context
from nova import db
from nova import flags
from nova import log as logging
from nova import rpc
from nova import test
from nova import utils
from nova.auth import manager
from nova.api.ec2 import admin
from nova.image import fake
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.adminapi')
class AdminApiTestCase(test.TestCase):
def setUp(self):
super(AdminApiTestCase, self).setUp()
self.flags(connection_type='fake')
self.conn = rpc.Connection.instance()
# set up our cloud
self.api = admin.AdminController()
# set up services
self.compute = self.start_service('compute')
self.scheduter = self.start_service('scheduler')
self.network = self.start_service('network')
self.volume = self.start_service('volume')
self.image_service = utils.import_object(FLAGS.image_service)
self.manager = manager.AuthManager()
self.user = self.manager.create_user('admin', 'admin', 'admin', True)
self.project = self.manager.create_project('proj', 'admin', 'proj')
self.context = context.RequestContext(user=self.user,
project=self.project)
host = self.network.get_network_host(self.context.elevated())
def fake_show(meh, context, id):
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
'type': 'machine', 'image_state': 'available'}}
self.stubs.Set(fake._FakeImageService, 'show', fake_show)
self.stubs.Set(fake._FakeImageService, 'show_by_name', fake_show)
# NOTE(vish): set up a manual wait so rpc.cast has a chance to finish
rpc_cast = rpc.cast
def finish_cast(*args, **kwargs):
rpc_cast(*args, **kwargs)
greenthread.sleep(0.2)
self.stubs.Set(rpc, 'cast', finish_cast)
def tearDown(self):
network_ref = db.project_get_network(self.context,
self.project.id)
db.network_disassociate(self.context, network_ref['id'])
self.manager.delete_project(self.project)
self.manager.delete_user(self.user)
super(AdminApiTestCase, self).tearDown()
def test_block_external_ips(self):
"""Make sure provider firewall rules are created."""
result = self.api.block_external_addresses(self.context, '1.1.1.1/32')
self.assertEqual('OK', result['status'])
self.assertEqual('Added 3 rules', result['message'])

View File

@@ -821,6 +821,8 @@ class IptablesFirewallTestCase(test.TestCase):
self.network = utils.import_object(FLAGS.network_manager)
class FakeLibvirtConnection(object):
def nwfilterDefineXML(*args, **kwargs):
"""setup_basic_rules in nwfilter calls this."""
pass
self.fake_libvirt_connection = FakeLibvirtConnection()
self.fw = firewall.IptablesFirewallDriver(
@@ -1063,7 +1065,6 @@ class IptablesFirewallTestCase(test.TestCase):
fakefilter.filterDefineXMLMock
self.fw.nwfilter._conn.nwfilterLookupByName =\
fakefilter.nwfilterLookupByName
instance_ref = self._create_instance_ref()
inst_id = instance_ref['id']
instance = db.instance_get(self.context, inst_id)
@@ -1085,6 +1086,63 @@ class IptablesFirewallTestCase(test.TestCase):
db.instance_destroy(admin_ctxt, instance_ref['id'])
def test_provider_firewall_rules(self):
# setup basic instance data
instance_ref = self._create_instance_ref()
nw_info = _create_network_info(1)
ip = '10.11.12.13'
network_ref = db.project_get_network(self.context, 'fake')
admin_ctxt = context.get_admin_context()
fixed_ip = {'address': ip, 'network_id': network_ref['id']}
db.fixed_ip_create(admin_ctxt, fixed_ip)
db.fixed_ip_update(admin_ctxt, ip, {'allocated': True,
'instance_id': instance_ref['id']})
# FRAGILE: peeks at how the firewall names chains
chain_name = 'inst-%s' % instance_ref['id']
# create a firewall via setup_basic_filtering like libvirt_conn.spawn
# should have a chain with 0 rules
self.fw.setup_basic_filtering(instance_ref, network_info=nw_info)
self.assertTrue('provider' in self.fw.iptables.ipv4['filter'].chains)
rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules
if rule.chain == 'provider']
self.assertEqual(0, len(rules))
# add a rule and send the update message, check for 1 rule
provider_fw0 = db.provider_fw_rule_create(admin_ctxt,
{'protocol': 'tcp',
'cidr': '10.99.99.99/32',
'from_port': 1,
'to_port': 65535})
self.fw.refresh_provider_fw_rules()
rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules
if rule.chain == 'provider']
self.assertEqual(1, len(rules))
# Add another, refresh, and make sure number of rules goes to two
provider_fw1 = db.provider_fw_rule_create(admin_ctxt,
{'protocol': 'udp',
'cidr': '10.99.99.99/32',
'from_port': 1,
'to_port': 65535})
self.fw.refresh_provider_fw_rules()
rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules
if rule.chain == 'provider']
self.assertEqual(2, len(rules))
# create the instance filter and make sure it has a jump rule
self.fw.prepare_instance_filter(instance_ref, network_info=nw_info)
self.fw.apply_instance_filter(instance_ref)
inst_rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules
if rule.chain == chain_name]
jump_rules = [rule for rule in inst_rules if '-j' in rule.rule]
provjump_rules = []
# IptablesTable doesn't make rules unique internally
for rule in jump_rules:
if 'provider' in rule.rule and rule not in provjump_rules:
provjump_rules.append(rule)
self.assertEqual(1, len(provjump_rules))
class NWFilterTestCase(test.TestCase):
def setUp(self):

View File

@@ -191,6 +191,10 @@ class ComputeDriver(object):
def refresh_security_group_members(self, security_group_id):
raise NotImplementedError()
def refresh_provider_fw_rules(self, security_group_id):
"""See: nova/virt/fake.py for docs."""
raise NotImplementedError()
def reset_network(self, instance):
"""reset networking for specified instance"""
raise NotImplementedError()

View File

@@ -466,6 +466,22 @@ class FakeConnection(driver.ComputeDriver):
"""
return True
def refresh_provider_fw_rules(self):
"""This triggers a firewall update based on database changes.
When this is called, rules have either been added or removed from the
datastore. You can retrieve rules with
:method:`nova.db.api.provider_fw_rule_get_all`.
Provider rules take precedence over security group rules. If an IP
would be allowed by a security group ingress rule, but blocked by
a provider rule, then packets from the IP are dropped. This includes
intra-project traffic in the case of the allow_project_net_traffic
flag for the libvirt-derived classes.
"""
pass
def update_available_resource(self, ctxt, host):
"""This method is supported only by libvirt."""
return

View File

@@ -1383,6 +1383,9 @@ class LibvirtConnection(driver.ComputeDriver):
def refresh_security_group_members(self, security_group_id):
self.firewall_driver.refresh_security_group_members(security_group_id)
def refresh_provider_fw_rules(self):
self.firewall_driver.refresh_provider_fw_rules()
def update_available_resource(self, ctxt, host):
"""Updates compute manager resource info on ComputeNode table.

View File

@@ -76,6 +76,15 @@ class FirewallDriver(object):
the security group."""
raise NotImplementedError()
def refresh_provider_fw_rules(self):
"""Refresh common rules for all hosts/instances from data store.
Gets called when a rule has been added to or removed from
the list of rules (via admin api).
"""
raise NotImplementedError()
def setup_basic_filtering(self, instance, network_info=None):
"""Create rules to block spoofing and allow dhcp.
@@ -207,6 +216,13 @@ class NWFilterFirewall(FirewallDriver):
[base_filter]))
def _ensure_static_filters(self):
"""Static filters are filters that have no need to be IP aware.
There is no configuration or tuneability of these filters, so they
can be set up once and forgotten about.
"""
if self.static_filters_configured:
return
@@ -310,19 +326,21 @@ class NWFilterFirewall(FirewallDriver):
'for %(instance_name)s is not found.') % locals())
def prepare_instance_filter(self, instance, network_info=None):
"""
Creates an NWFilter for the given instance. In the process,
it makes sure the filters for the security groups as well as
the base filter are all in place.
"""Creates an NWFilter for the given instance.
In the process, it makes sure the filters for the provider blocks,
security groups, and base filter are all in place.
"""
if not network_info:
network_info = netutils.get_network_info(instance)
self.refresh_provider_fw_rules()
ctxt = context.get_admin_context()
instance_secgroup_filter_name = \
'%s-secgroup' % (self._instance_filter_name(instance))
#% (instance_filter_name,)
instance_secgroup_filter_children = ['nova-base-ipv4',
'nova-base-ipv6',
@@ -366,7 +384,7 @@ class NWFilterFirewall(FirewallDriver):
for (_n, mapping) in network_info:
nic_id = mapping['mac'].replace(':', '')
instance_filter_name = self._instance_filter_name(instance, nic_id)
instance_filter_children = [base_filter,
instance_filter_children = [base_filter, 'nova-provider-rules',
instance_secgroup_filter_name]
if FLAGS.allow_project_net_traffic:
@@ -388,6 +406,19 @@ class NWFilterFirewall(FirewallDriver):
return self._define_filter(
self.security_group_to_nwfilter_xml(security_group_id))
def refresh_provider_fw_rules(self):
"""Update rules for all instances.
This is part of the FirewallDriver API and is called when the
provider firewall rules change in the database. In the
`prepare_instance_filter` we add a reference to the
'nova-provider-rules' filter for each instance's firewall, and
by changing that filter we update them all.
"""
xml = self.provider_fw_to_nwfilter_xml()
return self._define_filter(xml)
def security_group_to_nwfilter_xml(self, security_group_id):
security_group = db.security_group_get(context.get_admin_context(),
security_group_id)
@@ -426,6 +457,43 @@ class NWFilterFirewall(FirewallDriver):
xml += "chain='ipv4'>%s</filter>" % rule_xml
return xml
def provider_fw_to_nwfilter_xml(self):
"""Compose a filter of drop rules from specified cidrs."""
rule_xml = ""
v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
rules = db.provider_fw_rule_get_all(context.get_admin_context())
for rule in rules:
rule_xml += "<rule action='block' direction='in' priority='150'>"
version = netutils.get_ip_version(rule.cidr)
if(FLAGS.use_ipv6 and version == 6):
net, prefixlen = netutils.get_net_and_prefixlen(rule.cidr)
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
(v6protocol[rule.protocol], net, prefixlen)
else:
net, mask = netutils.get_net_and_mask(rule.cidr)
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
(rule.protocol, net, mask)
if rule.protocol in ['tcp', 'udp']:
rule_xml += "dstportstart='%s' dstportend='%s' " % \
(rule.from_port, rule.to_port)
elif rule.protocol == 'icmp':
LOG.info('rule.protocol: %r, rule.from_port: %r, '
'rule.to_port: %r', rule.protocol,
rule.from_port, rule.to_port)
if rule.from_port != -1:
rule_xml += "type='%s' " % rule.from_port
if rule.to_port != -1:
rule_xml += "code='%s' " % rule.to_port
rule_xml += '/>\n'
rule_xml += "</rule>\n"
xml = "<filter name='nova-provider-rules' "
if(FLAGS.use_ipv6):
xml += "chain='root'>%s</filter>" % rule_xml
else:
xml += "chain='ipv4'>%s</filter>" % rule_xml
return xml
def _instance_filter_name(self, instance, nic_id=None):
if not nic_id:
return 'nova-instance-%s' % (instance['name'])
@@ -453,6 +521,7 @@ class IptablesFirewallDriver(FirewallDriver):
self.iptables = linux_net.iptables_manager
self.instances = {}
self.nwfilter = NWFilterFirewall(kwargs['get_connection'])
self.basicly_filtered = False
self.iptables.ipv4['filter'].add_chain('sg-fallback')
self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
@@ -460,10 +529,14 @@ class IptablesFirewallDriver(FirewallDriver):
self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
def setup_basic_filtering(self, instance, network_info=None):
"""Use NWFilter from libvirt for this."""
"""Set up provider rules and basic NWFilter."""
if not network_info:
network_info = netutils.get_network_info(instance)
return self.nwfilter.setup_basic_filtering(instance, network_info)
self.nwfilter.setup_basic_filtering(instance, network_info)
if not self.basicly_filtered:
LOG.debug(_('iptables firewall: Setup Basic Filtering'))
self.refresh_provider_fw_rules()
self.basicly_filtered = True
def apply_instance_filter(self, instance):
"""No-op. Everything is done in prepare_instance_filter"""
@@ -543,6 +616,10 @@ class IptablesFirewallDriver(FirewallDriver):
ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
# Pass through provider-wide drops
ipv4_rules += ['-j $provider']
ipv6_rules += ['-j $provider']
dhcp_servers = [info['gateway'] for (_n, info) in network_info]
for dhcp_server in dhcp_servers:
@@ -583,7 +660,7 @@ class IptablesFirewallDriver(FirewallDriver):
security_group['id'])
for rule in rules:
logging.info('%r', rule)
LOG.debug(_('Adding security group rule: %r'), rule)
if not rule.cidr:
# Eventually, a mechanism to grant access for security
@@ -592,9 +669,9 @@ class IptablesFirewallDriver(FirewallDriver):
version = netutils.get_ip_version(rule.cidr)
if version == 4:
rules = ipv4_rules
fw_rules = ipv4_rules
else:
rules = ipv6_rules
fw_rules = ipv6_rules
protocol = rule.protocol
if version == 6 and rule.protocol == 'icmp':
@@ -629,7 +706,7 @@ class IptablesFirewallDriver(FirewallDriver):
icmp_type_arg]
args += ['-j ACCEPT']
rules += [' '.join(args)]
fw_rules += [' '.join(args)]
ipv4_rules += ['-j $sg-fallback']
ipv6_rules += ['-j $sg-fallback']
@@ -657,6 +734,85 @@ class IptablesFirewallDriver(FirewallDriver):
network_info = netutils.get_network_info(instance)
self.add_filters_for_instance(instance, network_info)
def refresh_provider_fw_rules(self):
"""See class:FirewallDriver: docs."""
self._do_refresh_provider_fw_rules()
self.iptables.apply()
@utils.synchronized('iptables', external=True)
def _do_refresh_provider_fw_rules(self):
"""Internal, synchronized version of refresh_provider_fw_rules."""
self._purge_provider_fw_rules()
self._build_provider_fw_rules()
def _purge_provider_fw_rules(self):
"""Remove all rules from the provider chains."""
self.iptables.ipv4['filter'].empty_chain('provider')
if FLAGS.use_ipv6:
self.iptables.ipv6['filter'].empty_chain('provider')
def _build_provider_fw_rules(self):
"""Create all rules for the provider IP DROPs."""
self.iptables.ipv4['filter'].add_chain('provider')
if FLAGS.use_ipv6:
self.iptables.ipv6['filter'].add_chain('provider')
ipv4_rules, ipv6_rules = self._provider_rules()
for rule in ipv4_rules:
self.iptables.ipv4['filter'].add_rule('provider', rule)
if FLAGS.use_ipv6:
for rule in ipv6_rules:
self.iptables.ipv6['filter'].add_rule('provider', rule)
def _provider_rules(self):
"""Generate a list of rules from provider for IP4 & IP6."""
ctxt = context.get_admin_context()
ipv4_rules = []
ipv6_rules = []
rules = db.provider_fw_rule_get_all(ctxt)
for rule in rules:
LOG.debug(_('Adding provider rule: %s'), rule['cidr'])
version = netutils.get_ip_version(rule['cidr'])
if version == 4:
fw_rules = ipv4_rules
else:
fw_rules = ipv6_rules
protocol = rule['protocol']
if version == 6 and protocol == 'icmp':
protocol = 'icmpv6'
args = ['-p', protocol, '-s', rule['cidr']]
if protocol in ['udp', 'tcp']:
if rule['from_port'] == rule['to_port']:
args += ['--dport', '%s' % (rule['from_port'],)]
else:
args += ['-m', 'multiport',
'--dports', '%s:%s' % (rule['from_port'],
rule['to_port'])]
elif protocol == 'icmp':
icmp_type = rule['from_port']
icmp_code = rule['to_port']
if icmp_type == -1:
icmp_type_arg = None
else:
icmp_type_arg = '%s' % icmp_type
if not icmp_code == -1:
icmp_type_arg += '/%s' % icmp_code
if icmp_type_arg:
if version == 4:
args += ['-m', 'icmp', '--icmp-type',
icmp_type_arg]
elif version == 6:
args += ['-m', 'icmp6', '--icmpv6-type',
icmp_type_arg]
args += ['-j DROP']
fw_rules += [' '.join(args)]
return ipv4_rules, ipv6_rules
def _security_group_chain_name(self, security_group_id):
return 'nova-sg-%s' % (security_group_id,)

View File

@@ -15,7 +15,7 @@ python-daemon==1.5.5
python-gflags==1.3
redis==2.0.0
routes==1.12.3
WebOb==0.9.8
WebOb==1.0.8
wsgiref==0.1.2
mox==0.5.3
greenlet==0.3.1