summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
Diffstat (limited to 'nova')
-rw-r--r--nova/api/openstack/contrib/createserverext.py66
-rw-r--r--nova/api/openstack/create_instance_helper.py73
-rw-r--r--nova/compute/api.py51
-rw-r--r--nova/compute/manager.py5
-rw-r--r--nova/db/api.py12
-rw-r--r--nova/db/sqlalchemy/api.py63
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py43
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/exception.py22
-rw-r--r--nova/network/api.py9
-rw-r--r--nova/network/manager.py121
-rw-r--r--nova/tests/api/openstack/contrib/test_createserverext.py306
-rw-r--r--nova/tests/api/openstack/test_extensions.py1
-rw-r--r--nova/tests/api/openstack/test_servers.py183
-rw-r--r--nova/tests/test_network.py130
-rw-r--r--nova/utils.py16
16 files changed, 1050 insertions, 52 deletions
diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py
new file mode 100644
index 000000000..ba72fdb0b
--- /dev/null
+++ b/nova/api/openstack/contrib/createserverext.py
@@ -0,0 +1,66 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+#
+# 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 nova.api.openstack import create_instance_helper as helper
+from nova.api.openstack import extensions
+from nova.api.openstack import servers
+from nova.api.openstack import wsgi
+
+
+class Createserverext(extensions.ExtensionDescriptor):
+ """The servers create ext
+
+ Exposes addFixedIp and removeFixedIp actions on servers.
+
+ """
+ def get_name(self):
+ return "Createserverext"
+
+ def get_alias(self):
+ return "os-create-server-ext"
+
+ def get_description(self):
+ return "Extended support to the Create Server v1.1 API"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/createserverext/api/v1.1"
+
+ def get_updated(self):
+ return "2011-07-19T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ headers_serializer = servers.HeadersSerializer()
+ body_serializers = {
+ 'application/xml': servers.ServerXMLSerializer(),
+ }
+
+ body_deserializers = {
+ 'application/xml': helper.ServerXMLDeserializerV11(),
+ }
+
+ serializer = wsgi.ResponseSerializer(body_serializers,
+ headers_serializer)
+ deserializer = wsgi.RequestDeserializer(body_deserializers)
+
+ res = extensions.ResourceExtension('os-create-server-ext',
+ controller=servers.ControllerV11(),
+ deserializer=deserializer,
+ serializer=serializer)
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 339f260b9..4b4a1b0c3 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -29,7 +29,7 @@ from nova import utils
from nova.compute import instance_types
from nova.api.openstack import common
from nova.api.openstack import wsgi
-
+from nova.rpc.common import RemoteError
LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
FLAGS = flags.FLAGS
@@ -120,6 +120,11 @@ class CreateInstanceHelper(object):
sg_names = list(set(sg_names))
+ requested_networks = server_dict.get('networks')
+ if requested_networks is not None:
+ requested_networks = self._get_requested_networks(
+ requested_networks)
+
try:
flavor_id = self.controller._flavor_id_from_req_data(body)
except ValueError as error:
@@ -175,6 +180,7 @@ class CreateInstanceHelper(object):
reservation_id=reservation_id,
min_count=min_count,
max_count=max_count,
+ requested_networks=requested_networks,
security_group=sg_names,
user_data=user_data,
availability_zone=availability_zone))
@@ -188,6 +194,10 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
except exception.SecurityGroupNotFound as error:
raise exc.HTTPBadRequest(explanation=unicode(error))
+ except RemoteError as err:
+ msg = "%(err_type)s: %(err_msg)s" % \
+ {'err_type': err.exc_type, 'err_msg': err.value}
+ raise exc.HTTPBadRequest(explanation=msg)
# Let the caller deal with unhandled exceptions.
def _handle_quota_error(self, error):
@@ -316,6 +326,46 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
return password
+ def _get_requested_networks(self, requested_networks):
+ """
+ Create a list of requested networks from the networks attribute
+ """
+ networks = []
+ for network in requested_networks:
+ try:
+ network_uuid = network['uuid']
+
+ if not utils.is_uuid_like(network_uuid):
+ msg = _("Bad networks format: network uuid is not in"
+ " proper format (%s)") % network_uuid
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ #fixed IP address is optional
+ #if the fixed IP address is not provided then
+ #it will use one of the available IP address from the network
+ address = network.get('fixed_ip', None)
+ if address is not None and not utils.is_valid_ipv4(address):
+ msg = _("Invalid fixed IP address (%s)") % address
+ raise exc.HTTPBadRequest(explanation=msg)
+ # check if the network id is already present in the list,
+ # we don't want duplicate networks to be passed
+ # at the boot time
+ for id, ip in networks:
+ if id == network_uuid:
+ expl = _("Duplicate networks (%s) are not allowed")\
+ % network_uuid
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ networks.append((network_uuid, address))
+ except KeyError as key:
+ expl = _('Bad network format: missing %s') % key
+ raise exc.HTTPBadRequest(explanation=expl)
+ except TypeError:
+ expl = _('Bad networks format')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ return networks
+
class ServerXMLDeserializer(wsgi.XMLDeserializer):
"""
@@ -480,6 +530,10 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
if personality is not None:
server["personality"] = personality
+ networks = self._extract_networks(server_node)
+ if networks is not None:
+ server["networks"] = networks
+
security_groups = self._extract_security_groups(server_node)
if security_groups is not None:
server["security_groups"] = security_groups
@@ -501,6 +555,23 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
else:
return None
+ def _extract_networks(self, server_node):
+ """Marshal the networks attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "networks")
+ if node is not None:
+ networks = []
+ for network_node in self.find_children_named(node,
+ "network"):
+ item = {}
+ if network_node.hasAttribute("uuid"):
+ item["uuid"] = network_node.getAttribute("uuid")
+ if network_node.hasAttribute("fixed_ip"):
+ item["fixed_ip"] = network_node.getAttribute("fixed_ip")
+ networks.append(item)
+ return networks
+ else:
+ return None
+
def _extract_security_groups(self, server_node):
"""Marshal the security_groups attribute of a parsed request"""
node = self.find_first_child_named(server_node, "security_groups")
diff --git a/nova/compute/api.py b/nova/compute/api.py
index d3cce8568..7de91584f 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -146,6 +146,16 @@ class API(base.Base):
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
+ def _check_requested_networks(self, context, requested_networks):
+ """ Check if the networks requested belongs to the project
+ and the fixed IP address for each network provided is within
+ same the network block
+ """
+ if requested_networks is None:
+ return
+
+ self.network_api.validate_networks(context, requested_networks)
+
def _check_create_parameters(self, context, instance_type,
image_href, kernel_id=None, ramdisk_id=None,
min_count=None, max_count=None,
@@ -153,7 +163,8 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata=None,
injected_files=None, admin_password=None, zone_blob=None,
- reservation_id=None, access_ip_v4=None, access_ip_v6=None):
+ reservation_id=None, access_ip_v4=None, access_ip_v6=None,
+ requested_networks=None):
"""Verify all the input parameters regardless of the provisioning
strategy being performed."""
@@ -182,6 +193,7 @@ class API(base.Base):
self._check_metadata_properties_quota(context, metadata)
self._check_injected_file_quota(context, injected_files)
+ self._check_requested_networks(context, requested_networks)
(image_service, image_id) = nova.image.get_image_service(image_href)
image = image_service.show(context, image_id)
@@ -400,9 +412,9 @@ class API(base.Base):
def _ask_scheduler_to_create_instance(self, context, base_options,
instance_type, zone_blob,
availability_zone, injected_files,
- admin_password,
- image,
- instance_id=None, num_instances=1):
+ admin_password, image,
+ instance_id=None, num_instances=1,
+ requested_networks=None):
"""Send the run_instance request to the schedulers for processing."""
pid = context.project_id
uid = context.user_id
@@ -430,7 +442,8 @@ class API(base.Base):
"request_spec": request_spec,
"availability_zone": availability_zone,
"admin_password": admin_password,
- "injected_files": injected_files}})
+ "injected_files": injected_files,
+ "requested_networks": requested_networks}})
def create_all_at_once(self, context, instance_type,
image_href, kernel_id=None, ramdisk_id=None,
@@ -440,7 +453,8 @@ class API(base.Base):
availability_zone=None, user_data=None, metadata=None,
injected_files=None, admin_password=None, zone_blob=None,
reservation_id=None, block_device_mapping=None,
- access_ip_v4=None, access_ip_v6=None):
+ access_ip_v4=None, access_ip_v6=None,
+ requested_networks=None):
"""Provision the instances by passing the whole request to
the Scheduler for execution. Returns a Reservation ID
related to the creation of all of these instances."""
@@ -456,14 +470,15 @@ class API(base.Base):
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, admin_password, zone_blob,
- reservation_id, access_ip_v4, access_ip_v6)
+ reservation_id, access_ip_v4, access_ip_v6,
+ requested_networks)
self._ask_scheduler_to_create_instance(context, base_options,
instance_type, zone_blob,
availability_zone, injected_files,
- admin_password,
- image,
- num_instances=num_instances)
+ admin_password, image,
+ num_instances=num_instances,
+ requested_networks=requested_networks)
return base_options['reservation_id']
@@ -475,7 +490,8 @@ class API(base.Base):
availability_zone=None, user_data=None, metadata=None,
injected_files=None, admin_password=None, zone_blob=None,
reservation_id=None, block_device_mapping=None,
- access_ip_v4=None, access_ip_v6=None):
+ access_ip_v4=None, access_ip_v6=None,
+ requested_networks=None):
"""
Provision the instances by sending off a series of single
instance requests to the Schedulers. This is fine for trival
@@ -499,7 +515,8 @@ class API(base.Base):
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, admin_password, zone_blob,
- reservation_id, access_ip_v4, access_ip_v6)
+ reservation_id, access_ip_v4, access_ip_v6,
+ requested_networks)
block_device_mapping = block_device_mapping or []
instances = []
@@ -513,11 +530,11 @@ class API(base.Base):
instance_id = instance['id']
self._ask_scheduler_to_create_instance(context, base_options,
- instance_type, zone_blob,
- availability_zone, injected_files,
- admin_password,
- image,
- instance_id=instance_id)
+ instance_type, zone_blob,
+ availability_zone, injected_files,
+ admin_password, image,
+ instance_id=instance_id,
+ requested_networks=requested_networks)
return [dict(x.iteritems()) for x in instances]
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 091b3b6b2..c207eccbb 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -382,6 +382,8 @@ class ComputeManager(manager.SchedulerDependentManager):
context = context.elevated()
instance = self.db.instance_get(context, instance_id)
+ requested_networks = kwargs.get('requested_networks', None)
+
if instance['name'] in self.driver.list_instances():
raise exception.Error(_("Instance has already been created"))
@@ -411,7 +413,8 @@ class ComputeManager(manager.SchedulerDependentManager):
# 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,
+ requested_networks=requested_networks)
LOG.debug(_("instance network_info: |%s|"), network_info)
else:
# TODO(tr3buchet) not really sure how this should be handled.
diff --git a/nova/db/api.py b/nova/db/api.py
index e946e8436..2d854f24c 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -323,13 +323,13 @@ def migration_get_by_instance_and_status(context, instance_uuid, status):
####################
-def fixed_ip_associate(context, address, instance_id):
+def fixed_ip_associate(context, address, instance_id, network_id=None):
"""Associate fixed ip to instance.
Raises if fixed ip is not available.
"""
- return IMPL.fixed_ip_associate(context, address, instance_id)
+ return IMPL.fixed_ip_associate(context, address, instance_id, network_id)
def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None):
@@ -396,7 +396,6 @@ def fixed_ip_update(context, address, values):
"""Create a fixed ip from the values dictionary."""
return IMPL.fixed_ip_update(context, address, values)
-
####################
@@ -686,7 +685,14 @@ def network_get_all(context):
return IMPL.network_get_all(context)
+def network_get_all_by_uuids(context, network_uuids, project_id=None):
+ """Return networks by ids."""
+ return IMPL.network_get_all_by_uuids(context, network_uuids, project_id)
+
+
# pylint: disable=C0103
+
+
def network_get_associated_fixed_ips(context, network_id):
"""Get all network's ips that have been associated."""
return IMPL.network_get_associated_fixed_ips(context, network_id)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 0f747c602..04b5405f6 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -652,23 +652,36 @@ def floating_ip_update(context, address, values):
###################
-@require_context
-def fixed_ip_associate(context, address, instance_id):
+@require_admin_context
+def fixed_ip_associate(context, address, instance_id, network_id=None):
session = get_session()
with session.begin():
- instance = instance_get(context, instance_id, session=session)
+ network_or_none = or_(models.FixedIp.network_id == network_id,
+ models.FixedIp.network_id == None)
fixed_ip_ref = session.query(models.FixedIp).\
- filter_by(address=address).\
+ filter(network_or_none).\
+ filter_by(reserved=False).\
filter_by(deleted=False).\
- filter_by(instance=None).\
+ filter_by(address=address).\
with_lockmode('update').\
first()
# NOTE(vish): if with_lockmode isn't supported, as in sqlite,
# then this has concurrency issues
- if not fixed_ip_ref:
- raise exception.NoMoreFixedIps()
- fixed_ip_ref.instance = instance
+ if fixed_ip_ref is None:
+ raise exception.FixedIpNotFoundForNetwork(address=address,
+ network_id=network_id)
+ if fixed_ip_ref.instance is not None:
+ raise exception.FixedIpAlreadyInUse(address=address)
+
+ if not fixed_ip_ref.network:
+ fixed_ip_ref.network = network_get(context,
+ network_id,
+ session=session)
+ fixed_ip_ref.instance = instance_get(context,
+ instance_id,
+ session=session)
session.add(fixed_ip_ref)
+ return fixed_ip_ref['address']
@require_admin_context
@@ -1755,6 +1768,40 @@ def network_get_all(context):
return result
+@require_admin_context
+def network_get_all_by_uuids(context, network_uuids, project_id=None):
+ session = get_session()
+ project_or_none = or_(models.Network.project_id == project_id,
+ models.Network.project_id == None)
+ result = session.query(models.Network).\
+ filter(models.Network.uuid.in_(network_uuids)).\
+ filter(project_or_none).\
+ filter_by(deleted=False).all()
+ if not result:
+ raise exception.NoNetworksFound()
+
+ #check if host is set to all of the networks
+ # returned in the result
+ for network in result:
+ if network['host'] is None:
+ raise exception.NetworkHostNotSet(network_id=network['id'])
+
+ #check if the result contains all the networks
+ #we are looking for
+ for network_uuid in network_uuids:
+ found = False
+ for network in result:
+ if network['uuid'] == network_uuid:
+ found = True
+ break
+ if not found:
+ if project_id:
+ raise exception.NetworkNotFoundForProject(network_uuid=uuid,
+ project_id=context.project_id)
+ raise exception.NetworkNotFound(network_id=network_uuid)
+
+ return result
+
# NOTE(vish): pylint complains because of the long method name, but
# it fits with the names of the rest of the methods
# pylint: disable=C0103
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py
new file mode 100644
index 000000000..38c543d51
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+#
+# 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 Column, Integer, MetaData, String, Table
+
+from nova import utils
+
+
+meta = MetaData()
+
+networks = Table("networks", meta,
+ Column("id", Integer(), primary_key=True, nullable=False))
+uuid_column = Column("uuid", String(36))
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ networks.create_column(uuid_column)
+
+ rows = migrate_engine.execute(networks.select())
+ for row in rows:
+ networks_uuid = str(utils.gen_uuid())
+ migrate_engine.execute(networks.update()\
+ .where(networks.c.id == row[0])\
+ .values(uuid=networks_uuid))
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ networks.drop_column(uuid_column)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index a487ab28d..19dc3302e 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -561,6 +561,7 @@ class Network(BASE, NovaBase):
project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
+ uuid = Column(String(36))
class VirtualInterface(BASE, NovaBase):
diff --git a/nova/exception.py b/nova/exception.py
index e8cb7bcb5..66740019b 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -423,6 +423,15 @@ class NoNetworksFound(NotFound):
message = _("No networks defined.")
+class NetworkNotFoundForProject(NotFound):
+ message = _("Either Network uuid %(network_uuid)s is not present or "
+ "is not assigned to the project %(project_id)s.")
+
+
+class NetworkHostNotSet(NovaException):
+ message = _("Host is not set to the network (%(network_id)s).")
+
+
class DatastoreNotFound(NotFound):
message = _("Could not find the datastore reference(s) which the VM uses.")
@@ -456,6 +465,19 @@ class FixedIpNotFoundForHost(FixedIpNotFound):
message = _("Host %(host)s has zero fixed ips.")
+class FixedIpNotFoundForNetwork(FixedIpNotFound):
+ message = _("Fixed IP address (%(address)s) does not exist in "
+ "network (%(network_uuid)s).")
+
+
+class FixedIpAlreadyInUse(NovaException):
+ message = _("Fixed IP address %(address)s is already in use.")
+
+
+class FixedIpInvalid(Invalid):
+ message = _("Fixed IP address %(address)s is invalid.")
+
+
class NoMoreFixedIps(Error):
message = _("Zero fixed ips available.")
diff --git a/nova/network/api.py b/nova/network/api.py
index 247768722..d04474df3 100644
--- a/nova/network/api.py
+++ b/nova/network/api.py
@@ -195,3 +195,12 @@ class API(base.Base):
return rpc.call(context, FLAGS.network_topic,
{'method': 'get_instance_nw_info',
'args': args})
+
+ def validate_networks(self, context, requested_networks):
+ """validate the networks passed at the time of creating
+ the server
+ """
+ args = {'networks': requested_networks}
+ return rpc.call(context, FLAGS.network_topic,
+ {'method': 'validate_networks',
+ 'args': args})
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 921c27e45..aa2a3700c 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -131,7 +131,15 @@ class RPCAllocateFixedIP(object):
green_pool = greenpool.GreenPool()
vpn = kwargs.pop('vpn')
+ requested_networks = kwargs.pop('requested_networks')
+
for network in networks:
+ address = None
+ if requested_networks is not None:
+ for address in (fixed_ip for (uuid, fixed_ip) in \
+ requested_networks if network['uuid'] == uuid):
+ break
+
# NOTE(vish): if we are not multi_host pass to the network host
if not network['multi_host']:
host = network['host']
@@ -148,6 +156,7 @@ class RPCAllocateFixedIP(object):
args = {}
args['instance_id'] = instance_id
args['network_id'] = network['id']
+ args['address'] = address
args['vpn'] = vpn
green_pool.spawn_n(rpc.call, context, topic,
@@ -155,7 +164,8 @@ class RPCAllocateFixedIP(object):
'args': args})
else:
# i am the correct host, run here
- self.allocate_fixed_ip(context, instance_id, network, vpn=vpn)
+ self.allocate_fixed_ip(context, instance_id, network,
+ vpn=vpn, address=address)
# wait for all of the allocates (if any) to finish
green_pool.waitall()
@@ -199,6 +209,7 @@ class FloatingIP(object):
"""
instance_id = kwargs.get('instance_id')
project_id = kwargs.get('project_id')
+ requested_networks = kwargs.get('requested_networks')
LOG.debug(_("floating IP allocation for instance |%s|"), instance_id,
context=context)
# call the next inherited class's allocate_for_instance()
@@ -380,16 +391,21 @@ class NetworkManager(manager.SchedulerDependentManager):
self.compute_api.trigger_security_group_members_refresh(admin_context,
group_ids)
- def _get_networks_for_instance(self, context, instance_id, project_id):
+ def _get_networks_for_instance(self, context, instance_id, project_id,
+ requested_networks=None):
"""Determine & return which networks an instance should connect to."""
# TODO(tr3buchet) maybe this needs to be updated in the future if
# there is a better way to determine which networks
# a non-vlan instance should connect to
- try:
- networks = self.db.network_get_all(context)
- except exception.NoNetworksFound:
- return []
-
+ if requested_networks is not None and len(requested_networks) != 0:
+ network_uuids = [uuid for (uuid, fixed_ip) in requested_networks]
+ networks = self.db.network_get_all_by_uuids(context,
+ network_uuids)
+ else:
+ try:
+ networks = self.db.network_get_all(context)
+ except exception.NoNetworksFound:
+ return []
# return only networks which are not vlan networks
return [network for network in networks if
not network['vlan']]
@@ -403,16 +419,18 @@ class NetworkManager(manager.SchedulerDependentManager):
host = kwargs.pop('host')
project_id = kwargs.pop('project_id')
type_id = kwargs.pop('instance_type_id')
+ requested_networks = kwargs.get('requested_networks')
vpn = kwargs.pop('vpn')
admin_context = context.elevated()
LOG.debug(_("network allocations for instance %s"), instance_id,
context=context)
- networks = self._get_networks_for_instance(admin_context, instance_id,
- project_id)
- LOG.warn(networks)
+ networks = self._get_networks_for_instance(admin_context,
+ instance_id, project_id,
+ requested_networks=requested_networks)
self._allocate_mac_addresses(context, instance_id, networks)
- self._allocate_fixed_ips(admin_context, instance_id, host, networks,
- vpn=vpn)
+ self._allocate_fixed_ips(admin_context, instance_id,
+ host, networks, vpn=vpn,
+ requested_networks=requested_networks)
return self.get_instance_nw_info(context, instance_id, type_id, host)
def deallocate_for_instance(self, context, **kwargs):
@@ -570,9 +588,15 @@ class NetworkManager(manager.SchedulerDependentManager):
# network_get_by_compute_host
address = None
if network['cidr']:
- address = self.db.fixed_ip_associate_pool(context.elevated(),
- network['id'],
- instance_id)
+ address = kwargs.get('address', None)
+ if address:
+ address = self.db.fixed_ip_associate(context,
+ address, instance_id,
+ network['id'])
+ else:
+ address = self.db.fixed_ip_associate_pool(context.elevated(),
+ network['id'],
+ instance_id)
self._do_trigger_security_group_members_refresh_for_instance(
instance_id)
get_vif = self.db.virtual_interface_get_by_instance_and_network
@@ -798,6 +822,35 @@ class NetworkManager(manager.SchedulerDependentManager):
"""Sets up network on this host."""
raise NotImplementedError()
+ def validate_networks(self, context, networks):
+ """check if the networks exists and host
+ is set to each network.
+ """
+ if networks is None or len(networks) == 0:
+ return
+
+ network_uuids = [uuid for (uuid, fixed_ip) in networks]
+
+ self._get_networks_by_uuids(context, network_uuids)
+
+ for network_uuid, address in networks:
+ # check if the fixed IP address is valid and
+ # it actually belongs to the network
+ if address is not None:
+ if not utils.is_valid_ipv4(address):
+ raise exception.FixedIpInvalid(address=address)
+
+ fixed_ip_ref = self.db.fixed_ip_get_by_address(context,
+ address)
+ if fixed_ip_ref['network']['uuid'] != network_uuid:
+ raise exception.FixedIpNotFoundForNetwork(address=address,
+ network_uuid=network_uuid)
+ if fixed_ip_ref['instance'] is not None:
+ raise exception.FixedIpAlreadyInUse(address=address)
+
+ def _get_networks_by_uuids(self, context, network_uuids):
+ return self.db.network_get_all_by_uuids(context, network_uuids)
+
class FlatManager(NetworkManager):
"""Basic network where no vlans are used.
@@ -832,8 +885,16 @@ class FlatManager(NetworkManager):
def _allocate_fixed_ips(self, context, instance_id, host, networks,
**kwargs):
"""Calls allocate_fixed_ip once for each network."""
+ requested_networks = kwargs.pop('requested_networks')
for network in networks:
- self.allocate_fixed_ip(context, instance_id, network)
+ address = None
+ if requested_networks is not None:
+ for address in (fixed_ip for (uuid, fixed_ip) in \
+ requested_networks if network['uuid'] == uuid):
+ break
+
+ self.allocate_fixed_ip(context, instance_id,
+ network, address=address)
def deallocate_fixed_ip(self, context, address, **kwargs):
"""Returns a fixed ip to the pool."""
@@ -927,9 +988,15 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
address,
instance_id)
else:
- address = self.db.fixed_ip_associate_pool(context,
- network['id'],
- instance_id)
+ address = kwargs.get('address', None)
+ if address:
+ address = self.db.fixed_ip_associate(context, address,
+ instance_id,
+ network['id'])
+ else:
+ address = self.db.fixed_ip_associate_pool(context,
+ network['id'],
+ instance_id)
self._do_trigger_security_group_members_refresh_for_instance(
instance_id)
vif = self.db.virtual_interface_get_by_instance_and_network(context,
@@ -945,10 +1012,18 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
"""Force adds another network to a project."""
self.db.network_associate(context, project_id, force=True)
- def _get_networks_for_instance(self, context, instance_id, project_id):
+ def _get_networks_for_instance(self, context, instance_id, project_id,
+ requested_networks=None):
"""Determine which networks an instance should connect to."""
# get networks associated with project
- return self.db.project_get_networks(context, project_id)
+ if requested_networks is not None and len(requested_networks) != 0:
+ network_uuids = [uuid for (uuid, fixed_ip) in requested_networks]
+ networks = self.db.network_get_all_by_uuids(context,
+ network_uuids,
+ project_id)
+ else:
+ networks = self.db.project_get_networks(context, project_id)
+ return networks
def create_networks(self, context, **kwargs):
"""Create networks based on parameters."""
@@ -997,6 +1072,10 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
self.db.network_update(context, network_ref['id'],
{'gateway_v6': gateway})
+ def _get_networks_by_uuids(self, context, network_uuids):
+ return self.db.network_get_all_by_uuids(context, network_uuids,
+ context.project_id)
+
@property
def _bottom_reserved_ips(self):
"""Number of reserved ips at the bottom of the range."""
diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py
new file mode 100644
index 000000000..e5eed14fe
--- /dev/null
+++ b/nova/tests/api/openstack/contrib/test_createserverext.py
@@ -0,0 +1,306 @@
+# 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.
+
+import base64
+import json
+import unittest
+from xml.dom import minidom
+
+import stubout
+import webob
+
+from nova import exception
+from nova import flags
+from nova import test
+from nova import utils
+import nova.api.openstack
+from nova.api.openstack import servers
+from nova.api.openstack.contrib import createserverext
+import nova.compute.api
+
+import nova.scheduler.api
+import nova.image.fake
+import nova.rpc
+from nova.tests.api.openstack import fakes
+
+
+FLAGS = flags.FLAGS
+FLAGS.verbose = True
+
+FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
+
+FAKE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'),
+ ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '10.0.2.12')]
+
+DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'),
+ ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12')]
+
+INVALID_NETWORKS = [('invalid', 'invalid-ip-address')]
+
+
+class CreateserverextTest(test.TestCase):
+
+ def setUp(self):
+ super(CreateserverextTest, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.auth_data = {}
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_auth(self.stubs)
+ fakes.stub_out_image_service(self.stubs)
+ fakes.stub_out_key_pair_funcs(self.stubs)
+ self.allow_admin = FLAGS.allow_admin_api
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ FLAGS.allow_admin_api = self.allow_admin
+ super(CreateserverextTest, self).tearDown()
+
+ def _setup_mock_compute_api(self):
+
+ class MockComputeAPI(nova.compute.API):
+
+ def __init__(self):
+ self.injected_files = None
+ self.networks = None
+
+ def create(self, *args, **kwargs):
+ if 'injected_files' in kwargs:
+ self.injected_files = kwargs['injected_files']
+ else:
+ self.injected_files = None
+
+ if 'requested_networks' in kwargs:
+ self.networks = kwargs['requested_networks']
+ else:
+ self.networks = None
+ return [{'id': '1234', 'display_name': 'fakeinstance',
+ 'uuid': FAKE_UUID,
+ 'created_at': "",
+ 'updated_at': ""}]
+
+ def set_admin_password(self, *args, **kwargs):
+ pass
+
+ def make_stub_method(canned_return):
+ def stub_method(*args, **kwargs):
+ return canned_return
+ return stub_method
+
+ compute_api = MockComputeAPI()
+ self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api))
+ self.stubs.Set(
+ nova.api.openstack.create_instance_helper.CreateInstanceHelper,
+ '_get_kernel_ramdisk_from_image', make_stub_method((1, 1)))
+ return compute_api
+
+ def _create_networks_request_dict(self, networks):
+ server = {}
+ server['name'] = 'new-server-test'
+ server['imageRef'] = 1
+ server['flavorRef'] = 1
+ if networks is not None:
+ network_list = []
+ for uuid, fixed_ip in networks:
+ network_list.append({'uuid': uuid, 'fixed_ip': fixed_ip})
+ server['networks'] = network_list
+ return {'server': server}
+
+ def _get_create_request_json(self, body_dict):
+ req = webob.Request.blank('/v1.1/123/os-create-server-ext')
+ req.headers['Content-Type'] = 'application/json'
+ req.method = 'POST'
+ req.body = json.dumps(body_dict)
+ return req
+
+ def _run_create_instance_with_mock_compute_api(self, request):
+ compute_api = self._setup_mock_compute_api()
+ response = request.get_response(fakes.wsgi_app())
+ return compute_api, response
+
+ def _format_xml_request_body(self, body_dict):
+ server = body_dict['server']
+ body_parts = []
+ body_parts.extend([
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.1"',
+ ' name="%s" imageRef="%s" flavorRef="%s">' % (
+ server['name'], server['imageRef'], server['flavorRef'])])
+ if 'metadata' in server:
+ metadata = server['metadata']
+ body_parts.append('<metadata>')
+ for item in metadata.iteritems():
+ body_parts.append('<meta key="%s">%s</meta>' % item)
+ body_parts.append('</metadata>')
+ if 'personality' in server:
+ personalities = server['personality']
+ body_parts.append('<personality>')
+ for file in personalities:
+ item = (file['path'], file['contents'])
+ body_parts.append('<file path="%s">%s</file>' % item)
+ body_parts.append('</personality>')
+ if 'networks' in server:
+ networks = server['networks']
+ body_parts.append('<networks>')
+ for network in networks:
+ item = (network['uuid'], network['fixed_ip'])
+ body_parts.append('<network uuid="%s" fixed_ip="%s"></network>'
+ % item)
+ body_parts.append('</networks>')
+ body_parts.append('</server>')
+ return ''.join(body_parts)
+
+ def _get_create_request_xml(self, body_dict):
+ req = webob.Request.blank('/v1.1/123/os-create-server-ext')
+ req.content_type = 'application/xml'
+ req.accept = 'application/xml'
+ req.method = 'POST'
+ req.body = self._format_xml_request_body(body_dict)
+ return req
+
+ def _create_instance_with_networks_json(self, networks):
+ body_dict = self._create_networks_request_dict(networks)
+ request = self._get_create_request_json(body_dict)
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
+ return request, response, compute_api.networks
+
+ def _create_instance_with_networks_xml(self, networks):
+ body_dict = self._create_networks_request_dict(networks)
+ request = self._get_create_request_xml(body_dict)
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
+ return request, response, compute_api.networks
+
+ def test_create_instance_with_no_networks(self):
+ request, response, networks = \
+ self._create_instance_with_networks_json(networks=None)
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_no_networks_xml(self):
+ request, response, networks = \
+ self._create_instance_with_networks_xml(networks=None)
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_one_network(self):
+ request, response, networks = \
+ self._create_instance_with_networks_json([FAKE_NETWORKS[0]])
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(networks, [FAKE_NETWORKS[0]])
+
+ def test_create_instance_with_one_network_xml(self):
+ request, response, networks = \
+ self._create_instance_with_networks_xml([FAKE_NETWORKS[0]])
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(networks, [FAKE_NETWORKS[0]])
+
+ def test_create_instance_with_two_networks(self):
+ request, response, networks = \
+ self._create_instance_with_networks_json(FAKE_NETWORKS)
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(networks, FAKE_NETWORKS)
+
+ def test_create_instance_with_two_networks_xml(self):
+ request, response, networks = \
+ self._create_instance_with_networks_xml(FAKE_NETWORKS)
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(networks, FAKE_NETWORKS)
+
+ def test_create_instance_with_duplicate_networks(self):
+ request, response, networks = \
+ self._create_instance_with_networks_json(DUPLICATE_NETWORKS)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_duplicate_networks_xml(self):
+ request, response, networks = \
+ self._create_instance_with_networks_xml(DUPLICATE_NETWORKS)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_network_no_id(self):
+ body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
+ del body_dict['server']['networks'][0]['uuid']
+ request = self._get_create_request_json(body_dict)
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(compute_api.networks, None)
+
+ def test_create_instance_with_network_no_id_xml(self):
+ body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
+ request = self._get_create_request_xml(body_dict)
+ uuid = ' uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"'
+ request.body = request.body.replace(uuid, '')
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(compute_api.networks, None)
+
+ def test_create_instance_with_network_invalid_id(self):
+ request, response, networks = \
+ self._create_instance_with_networks_json(INVALID_NETWORKS)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_network_invalid_id_xml(self):
+ request, response, networks = \
+ self._create_instance_with_networks_xml(INVALID_NETWORKS)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_network_empty_fixed_ip(self):
+ networks = [('1', '')]
+ request, response, networks = \
+ self._create_instance_with_networks_json(networks)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_network_non_string_fixed_ip(self):
+ networks = [('1', 12345)]
+ request, response, networks = \
+ self._create_instance_with_networks_json(networks)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_network_empty_fixed_ip_xml(self):
+ networks = [('1', '')]
+ request, response, networks = \
+ self._create_instance_with_networks_xml(networks)
+ self.assertEquals(response.status_int, 400)
+ self.assertEquals(networks, None)
+
+ def test_create_instance_with_network_no_fixed_ip(self):
+ body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
+ del body_dict['server']['networks'][0]['fixed_ip']
+ request = self._get_create_request_json(body_dict)
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(compute_api.networks,
+ [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)])
+
+ def test_create_instance_with_network_no_fixed_ip_xml(self):
+ body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
+ request = self._get_create_request_xml(body_dict)
+ request.body = request.body.replace(' fixed_ip="10.0.1.12"', '')
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
+ self.assertEquals(response.status_int, 202)
+ self.assertEquals(compute_api.networks,
+ [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)])
diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py
index 4060763fc..9f923852d 100644
--- a/nova/tests/api/openstack/test_extensions.py
+++ b/nova/tests/api/openstack/test_extensions.py
@@ -85,6 +85,7 @@ class ExtensionControllerTest(test.TestCase):
ext_path = os.path.join(os.path.dirname(__file__), "extensions")
self.flags(osapi_extensions_path=ext_path)
self.ext_list = [
+ "Createserverext",
"FlavorExtraSpecs",
"Floating_ips",
"Fox In Socks",
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 800a2e229..dd4b63a2c 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -1890,6 +1890,29 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_create_instance_whitespace_name(self):
+ self._setup_for_create_instance()
+
+ body = {
+ 'server': {
+ 'name': ' ',
+ 'imageId': 3,
+ 'flavorId': 1,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
def test_update_server_no_body(self):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
@@ -2829,6 +2852,164 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
}
self.assertEquals(request['body'], expected)
+ def test_request_with_empty_networks(self):
+ serial_request = """
+<server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks/>
+</server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": []
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_one_network(self):
+ serial_request = """
+<server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="1" fixed_ip="10.0.1.12"/>
+ </networks>
+</server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_two_networks(self):
+ serial_request = """
+<server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="1" fixed_ip="10.0.1.12"/>
+ <network uuid="2" fixed_ip="10.0.2.12"/>
+ </networks>
+</server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"},
+ {"uuid": "2", "fixed_ip": "10.0.2.12"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_second_network_node_ignored(self):
+ serial_request = """
+<server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="1" fixed_ip="10.0.1.12"/>
+ </networks>
+ <networks>
+ <network uuid="2" fixed_ip="10.0.2.12"/>
+ </networks>
+</server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_one_network_missing_id(self):
+ serial_request = """
+<server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network fixed_ip="10.0.1.12"/>
+ </networks>
+</server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"fixed_ip": "10.0.1.12"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_one_network_missing_fixed_ip(self):
+ serial_request = """
+<server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="1"/>
+ </networks>
+</server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "1"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_one_network_empty_id(self):
+ serial_request = """
+ <server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="" fixed_ip="10.0.1.12"/>
+ </networks>
+ </server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "", "fixed_ip": "10.0.1.12"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_one_network_empty_fixed_ip(self):
+ serial_request = """
+ <server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="1" fixed_ip=""/>
+ </networks>
+ </server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "1", "fixed_ip": ""}],
+ }}
+ self.assertEquals(request['body'], expected)
+
+ def test_request_with_networks_duplicate_ids(self):
+ serial_request = """
+ <server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test" imageRef="1" flavorRef="1">
+ <networks>
+ <network uuid="1" fixed_ip="10.0.1.12"/>
+ <network uuid="1" fixed_ip="10.0.2.12"/>
+ </networks>
+ </server>"""
+ request = self.deserializer.deserialize(serial_request, 'create')
+ expected = {"server": {
+ "name": "new-server-test",
+ "imageRef": "1",
+ "flavorRef": "1",
+ "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"},
+ {"uuid": "1", "fixed_ip": "10.0.2.12"}],
+ }}
+ self.assertEquals(request['body'], expected)
+
class TestAddressesXMLSerialization(test.TestCase):
@@ -2899,12 +3080,14 @@ class TestServerInstanceCreation(test.TestCase):
def __init__(self):
self.injected_files = None
+ self.networks = None
def create(self, *args, **kwargs):
if 'injected_files' in kwargs:
self.injected_files = kwargs['injected_files']
else:
self.injected_files = None
+
return [{'id': '1234', 'display_name': 'fakeinstance',
'uuid': FAKE_UUID}]
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index e5c80b6f6..0b8539442 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from nova import context
from nova import db
from nova import exception
from nova import log as logging
@@ -41,6 +42,7 @@ class FakeModel(dict):
networks = [{'id': 0,
+ 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
'label': 'test0',
'injected': False,
'multi_host': False,
@@ -60,6 +62,7 @@ networks = [{'id': 0,
'project_id': 'fake_project',
'vpn_public_address': '192.168.0.2'},
{'id': 1,
+ 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
'label': 'test1',
'injected': False,
'multi_host': False,
@@ -126,6 +129,8 @@ class FlatNetworkTestCase(test.TestCase):
super(FlatNetworkTestCase, self).setUp()
self.network = network_manager.FlatManager(host=HOST)
self.network.db = db
+ self.context = context.RequestContext('testuser', 'testproject',
+ is_admin=False)
def test_get_instance_nw_info(self):
self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance')
@@ -183,12 +188,73 @@ class FlatNetworkTestCase(test.TestCase):
'netmask': '255.255.255.0'}]
self.assertDictListMatch(nw[1]['ips'], check)
+ def test_validate_networks(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+ self.mox.StubOutWithMock(db, "fixed_ip_get_by_address")
+
+ requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
+ "192.168.1.100")]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+
+ fixed_ips[1]['network'] = FakeModel(**networks[1])
+ fixed_ips[1]['instance'] = None
+ db.fixed_ip_get_by_address(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(fixed_ips[1])
+
+ self.mox.ReplayAll()
+ self.network.validate_networks(self.context, requested_networks)
+
+ def test_validate_networks_none_requested_networks(self):
+ self.network.validate_networks(self.context, None)
+
+ def test_validate_networks_empty_requested_networks(self):
+ requested_networks = []
+ self.mox.ReplayAll()
+
+ self.network.validate_networks(self.context, requested_networks)
+
+ def test_validate_networks_invalid_fixed_ip(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+ requested_networks = [(1, "192.168.0.100.1")]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+ self.mox.ReplayAll()
+
+ self.assertRaises(exception.FixedIpInvalid,
+ self.network.validate_networks, None,
+ requested_networks)
+
+ def test_validate_networks_empty_fixed_ip(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+
+ requested_networks = [(1, "")]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+ self.mox.ReplayAll()
+
+ self.assertRaises(exception.FixedIpInvalid,
+ self.network.validate_networks,
+ None, requested_networks)
+
+ def test_validate_networks_none_fixed_ip(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+
+ requested_networks = [(1, None)]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+ self.mox.ReplayAll()
+
+ self.network.validate_networks(None, requested_networks)
+
class VlanNetworkTestCase(test.TestCase):
def setUp(self):
super(VlanNetworkTestCase, self).setUp()
self.network = network_manager.VlanManager(host=HOST)
self.network.db = db
+ self.context = context.RequestContext('testuser', 'testproject',
+ is_admin=False)
def test_vpn_allocate_fixed_ip(self):
self.mox.StubOutWithMock(db, 'fixed_ip_associate')
@@ -232,7 +298,7 @@ class VlanNetworkTestCase(test.TestCase):
network = dict(networks[0])
network['vpn_private_address'] = '192.168.0.2'
- self.network.allocate_fixed_ip(None, 0, network)
+ self.network.allocate_fixed_ip(self.context, 0, network)
def test_create_networks_too_big(self):
self.assertRaises(ValueError, self.network.create_networks, None,
@@ -243,6 +309,68 @@ class VlanNetworkTestCase(test.TestCase):
num_networks=100, vlan_start=1,
cidr='192.168.0.1/24', network_size=100)
+ def test_validate_networks(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+ self.mox.StubOutWithMock(db, "fixed_ip_get_by_address")
+
+ requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
+ "192.168.1.100")]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+
+ fixed_ips[1]['network'] = FakeModel(**networks[1])
+ fixed_ips[1]['instance'] = None
+ db.fixed_ip_get_by_address(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(fixed_ips[1])
+
+ self.mox.ReplayAll()
+ self.network.validate_networks(self.context, requested_networks)
+
+ def test_validate_networks_none_requested_networks(self):
+ self.network.validate_networks(self.context, None)
+
+ def test_validate_networks_empty_requested_networks(self):
+ requested_networks = []
+ self.mox.ReplayAll()
+
+ self.network.validate_networks(self.context, requested_networks)
+
+ def test_validate_networks_invalid_fixed_ip(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+ requested_networks = [(1, "192.168.0.100.1")]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+ self.mox.ReplayAll()
+
+ self.assertRaises(exception.FixedIpInvalid,
+ self.network.validate_networks, self.context,
+ requested_networks)
+
+ def test_validate_networks_empty_fixed_ip(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+
+ requested_networks = [(1, "")]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+ self.mox.ReplayAll()
+
+ self.assertRaises(exception.FixedIpInvalid,
+ self.network.validate_networks,
+ self.context, requested_networks)
+
+ def test_validate_networks_none_fixed_ip(self):
+ self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
+
+ requested_networks = [(1, None)]
+ db.network_get_all_by_uuids(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks)
+ self.mox.ReplayAll()
+ self.network.validate_networks(self.context, requested_networks)
+
class CommonNetworkTestCase(test.TestCase):
diff --git a/nova/utils.py b/nova/utils.py
index f6e98c2eb..fc4bbd53b 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -844,3 +844,19 @@ def bool_from_str(val):
return True if int(val) else False
except ValueError:
return val.lower() == 'true'
+
+
+def is_valid_ipv4(address):
+ """valid the address strictly as per format xxx.xxx.xxx.xxx.
+ where xxx is a value between 0 and 255.
+ """
+ parts = address.split(".")
+ if len(parts) != 4:
+ return False
+ for item in parts:
+ try:
+ if not 0 <= int(item) <= 255:
+ return False
+ except ValueError:
+ return False
+ return True