summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorTim Simpson <tim.simpson@rackspace.com>2011-08-23 13:16:04 -0500
committerTim Simpson <tim.simpson@rackspace.com>2011-08-23 13:16:04 -0500
commit83713359429263154cd4e0f8c9de0ee3f8f0636f (patch)
tree148def760f96a1a3d61adf8f9eff4b46cbf09193 /nova
parentb75f90e0d83e50b6699a8e6efc60cc97a00c0678 (diff)
parente23eb5aa5c9810f68f3818cd1119e4993b99a297 (diff)
Merged from upstream.
Diffstat (limited to 'nova')
-rw-r--r--nova/api/auth.py1
-rw-r--r--nova/api/ec2/__init__.py21
-rw-r--r--nova/api/ec2/admin.py4
-rw-r--r--nova/api/openstack/auth.py52
-rw-r--r--nova/api/openstack/contrib/createserverext.py66
-rw-r--r--nova/api/openstack/create_instance_helper.py79
-rw-r--r--nova/api/openstack/views/servers.py5
-rw-r--r--nova/auth/manager.py3
-rw-r--r--nova/cloudpipe/pipelib.py9
-rw-r--r--nova/compute/api.py61
-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/migrate_repo/versions/041_add_config_drive_to_instances.py38
-rw-r--r--nova/db/sqlalchemy/models.py3
-rw-r--r--nova/exception.py22
-rw-r--r--nova/flags.py2
-rw-r--r--nova/network/api.py9
-rw-r--r--nova/network/manager.py123
-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_server_actions.py8
-rw-r--r--nova/tests/api/openstack/test_servers.py323
-rw-r--r--nova/tests/integrated/integrated_helpers.py109
-rw-r--r--nova/tests/integrated/test_login.py39
-rw-r--r--nova/tests/integrated/test_servers.py2
-rw-r--r--nova/tests/test_compute.py15
-rw-r--r--nova/tests/test_network.py130
-rw-r--r--nova/tests/test_nova_manage.py52
-rw-r--r--nova/utils.py16
-rw-r--r--nova/virt/disk.py32
-rw-r--r--nova/virt/driver.py291
-rw-r--r--nova/virt/fake.py276
-rw-r--r--nova/virt/libvirt.xml.template7
-rw-r--r--nova/virt/libvirt/connection.py72
-rw-r--r--nova/virt/xenapi/vm_utils.py15
-rw-r--r--nova/virt/xenapi/vmops.py5
38 files changed, 1749 insertions, 571 deletions
diff --git a/nova/api/auth.py b/nova/api/auth.py
index cd3e3e8a0..cd0d38b3f 100644
--- a/nova/api/auth.py
+++ b/nova/api/auth.py
@@ -62,6 +62,7 @@ class KeystoneContext(wsgi.Middleware):
req.headers.get('X_STORAGE_TOKEN'))
# Build a context, including the auth_token...
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
remote_address = req.remote_addr
if FLAGS.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 17969099d..5430f443d 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -183,6 +183,27 @@ class ToToken(wsgi.Middleware):
return self.application
+class NoAuth(wsgi.Middleware):
+ """Add user:project as 'nova.context' to WSGI environ."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ if 'AWSAccessKeyId' not in req.params:
+ raise webob.exc.HTTPBadRequest()
+ user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
+ project_id = project_id or user_id
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=True,
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctx
+ return self.application
+
+
class Authenticate(wsgi.Middleware):
"""Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index df7876b9d..dfbbc0a2b 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -283,8 +283,10 @@ class AdminController(object):
# NOTE(vish) import delayed because of __init__.py
from nova.cloudpipe import pipelib
pipe = pipelib.CloudPipe()
+ proj = manager.AuthManager().get_project(project)
+ user_id = proj.project_manager_id
try:
- pipe.launch_vpn_instance(project)
+ pipe.launch_vpn_instance(project, user_id)
except db.NoMoreNetworks:
raise exception.ApiError("Unable to claim IP for VPN instance"
", ensure it isn't running, and try "
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index b6ff1126b..6754fea27 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -33,6 +33,46 @@ from nova.api.openstack import faults
LOG = logging.getLogger('nova.api.openstack')
FLAGS = flags.FLAGS
+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
+
+
+class NoAuthMiddleware(wsgi.Middleware):
+ """Return a fake token if one isn't specified."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ if 'X-Auth-Token' not in req.headers:
+ os_url = req.url
+ version = common.get_version_from_href(os_url)
+ user_id = req.headers.get('X-Auth-User', 'admin')
+ project_id = req.headers.get('X-Auth-Project-Id', 'admin')
+ if version == '1.1':
+ os_url += '/' + project_id
+ res = webob.Response()
+ # NOTE(vish): This is expecting and returning Auth(1.1), whereas
+ # keystone uses 2.0 auth. We should probably allow
+ # 2.0 auth here as well.
+ res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id)
+ res.headers['X-Server-Management-Url'] = os_url
+ res.headers['X-Storage-Url'] = ''
+ res.headers['X-CDN-Management-Url'] = ''
+ res.content_type = 'text/plain'
+ res.status = '204'
+ return res
+
+ token = req.headers['X-Auth-Token']
+ user_id, _sep, project_id = token.partition(':')
+ project_id = project_id or user_id
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=True,
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctx
+ return self.application
class AuthMiddleware(wsgi.Middleware):
@@ -85,9 +125,15 @@ class AuthMiddleware(wsgi.Middleware):
project_id = projects[0].id
is_admin = self.auth.is_admin(user_id)
- req.environ['nova.context'] = context.RequestContext(user_id,
- project_id,
- is_admin)
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=is_admin,
+ remote_address=remote_address)
+ req.environ['nova.context'] = ctx
+
if not is_admin and not self.auth.is_project_member(user_id,
project_id):
msg = _("%(user_id)s must be an admin or a "
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..483ff4985 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -1,4 +1,5 @@
# Copyright 2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -29,7 +30,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
@@ -106,6 +107,7 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
personality = server_dict.get('personality')
+ config_drive = server_dict.get('config_drive')
injected_files = []
if personality:
@@ -120,6 +122,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:
@@ -154,6 +161,7 @@ class CreateInstanceHelper(object):
extra_values = {
'instance_type': inst_type,
'image_ref': image_href,
+ 'config_drive': config_drive,
'password': password}
return (extra_values,
@@ -175,9 +183,11 @@ 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))
+ availability_zone=availability_zone,
+ config_drive=config_drive,))
except quota.QuotaError as error:
self._handle_quota_error(error)
except exception.ImageNotFound as error:
@@ -188,6 +198,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 +330,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 +534,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 +559,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/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 465287adc..0ec98591e 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -187,6 +188,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
response['uuid'] = inst['uuid']
+ self._build_config_drive(response, inst)
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -205,6 +207,9 @@ class ViewBuilderV11(ViewBuilder):
response["links"] = links
+ def _build_config_drive(self, response, inst):
+ response['config_drive'] = inst.get('config_drive')
+
def generate_href(self, server_id):
"""Create an url that refers to a specific server id."""
return os.path.join(self.base_url, self.project_id,
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 6205cfb56..85227bea0 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -17,6 +17,9 @@
# under the License.
"""
+WARNING: This code is deprecated and will be removed.
+Keystone is the recommended solution for auth management.
+
Nova authentication management
"""
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index 2c4673f9e..3eb372844 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -34,7 +34,6 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
-from nova.auth import manager
# TODO(eday): Eventually changes these to something not ec2-specific
from nova.api.ec2 import cloud
@@ -57,7 +56,6 @@ LOG = logging.getLogger('nova.cloudpipe')
class CloudPipe(object):
def __init__(self):
self.controller = cloud.CloudController()
- self.manager = manager.AuthManager()
def get_encoded_zip(self, project_id):
# Make a payload.zip
@@ -93,11 +91,10 @@ class CloudPipe(object):
zippy.close()
return encoded
- def launch_vpn_instance(self, project_id):
+ def launch_vpn_instance(self, project_id, user_id):
LOG.debug(_("Launching VPN for %s") % (project_id))
- project = self.manager.get_project(project_id)
- ctxt = context.RequestContext(user=project.project_manager_id,
- project=project.id)
+ ctxt = context.RequestContext(user_id=user_id,
+ project_id=project_id)
key_name = self.setup_key_pair(ctxt)
group_name = self.setup_security_group(ctxt)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index d3cce8568..69f76bf40 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -146,6 +147,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 +164,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, config_drive=None,):
"""Verify all the input parameters regardless of the provisioning
strategy being performed."""
@@ -182,10 +194,16 @@ 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)
+ config_drive_id = None
+ if config_drive and config_drive is not True:
+ # config_drive is volume id
+ config_drive, config_drive_id = None, config_drive
+
os_type = None
if 'properties' in image and 'os_type' in image['properties']:
os_type = image['properties']['os_type']
@@ -213,6 +231,8 @@ class API(base.Base):
image_service.show(context, kernel_id)
if ramdisk_id:
image_service.show(context, ramdisk_id)
+ if config_drive_id:
+ image_service.show(context, config_drive_id)
self.ensure_default_security_group(context)
@@ -231,6 +251,8 @@ class API(base.Base):
'image_ref': image_href,
'kernel_id': kernel_id or '',
'ramdisk_id': ramdisk_id or '',
+ 'config_drive_id': config_drive_id or '',
+ 'config_drive': config_drive or '',
'state': 0,
'state_description': 'scheduling',
'user_id': context.user_id,
@@ -400,9 +422,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 +452,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 +463,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, config_drive=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 +480,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, config_drive)
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 +500,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, config_drive=None,):
"""
Provision the instances by sending off a series of single
instance requests to the Schedulers. This is fine for trival
@@ -499,7 +525,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, config_drive)
block_device_mapping = block_device_mapping or []
instances = []
@@ -513,11 +540,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/migrate_repo/versions/041_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py
new file mode 100644
index 000000000..d3058f00d
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 Piston Cloud Computing, Inc.
+#
+# 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()
+
+instances = Table("instances", meta,
+ Column("id", Integer(), primary_key=True, nullable=False))
+
+# matches the size of an image_ref
+config_drive_column = Column("config_drive", String(255), nullable=True)
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances.create_column(config_drive_column)
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances.drop_column(config_drive_column)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index a487ab28d..0680501e9 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -230,6 +231,7 @@ class Instance(BASE, NovaBase):
uuid = Column(String(36))
root_device_name = Column(String(255))
+ config_drive = Column(String(255))
# User editable field meant to represent what ip should be used
# to connect to the instance
@@ -561,6 +563,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/flags.py b/nova/flags.py
index 48d5e8168..ce5356723 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -402,3 +402,5 @@ DEFINE_bool('resume_guests_state_on_host_boot', False,
DEFINE_string('root_helper', 'sudo',
'Command prefix to use for running commands as root')
+
+DEFINE_bool('use_ipv6', False, 'use ipv6')
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..404a3180e 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -106,8 +106,6 @@ flags.DEFINE_integer('create_unique_mac_address_attempts', 5,
'Number of attempts to create unique mac address')
flags.DEFINE_bool('auto_assign_floating_ip', False,
'Autoassigning floating ip to VM')
-flags.DEFINE_bool('use_ipv6', False,
- 'use the ipv6')
flags.DEFINE_string('network_host', socket.gethostname(),
'Network host to use for ip allocation in flat modes')
flags.DEFINE_bool('fake_call', False,
@@ -131,7 +129,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 +154,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 +162,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 +207,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 +389,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 +417,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 +586,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 +820,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 +883,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 +986,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 +1010,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 +1070,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_server_actions.py b/nova/tests/api/openstack/test_server_actions.py
index 90fe2f0b3..bdd6824e7 100644
--- a/nova/tests/api/openstack/test_server_actions.py
+++ b/nova/tests/api/openstack/test_server_actions.py
@@ -1,17 +1,13 @@
import base64
import json
-import unittest
-from xml.dom import minidom
import stubout
import webob
from nova import context
-from nova import db
from nova import utils
from nova import flags
from nova.api.openstack import create_instance_helper
-from nova.compute import instance_types
from nova.compute import power_state
import nova.db.api
from nova import test
@@ -103,8 +99,6 @@ class ServerActionsTest(test.TestCase):
super(ServerActionsTest, self).setUp()
self.flags(verbose=True)
self.stubs = stubout.StubOutForTesting()
- fakes.FakeAuthManager.reset_fake_data()
- fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id)
self.stubs.Set(nova.db.api, 'instance_update', instance_update)
@@ -468,8 +462,6 @@ class ServerActionsTestV11(test.TestCase):
self.maxDiff = None
super(ServerActionsTestV11, self).setUp()
self.stubs = stubout.StubOutForTesting()
- fakes.FakeAuthManager.reset_fake_data()
- fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id)
self.stubs.Set(nova.db.api, 'instance_update', instance_update)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 800a2e229..aec2ad947 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -233,7 +234,6 @@ class MockSetAdminPassword(object):
class ServersTest(test.TestCase):
-
def setUp(self):
self.maxDiff = None
super(ServersTest, self).setUp()
@@ -265,6 +265,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.compute.API, "get_actions", fake_compute_api)
self.webreq = common.webob_factory('/v1.0/servers')
+ self.config_drive = None
def test_get_server_by_id(self):
req = webob.Request.blank('/v1.0/servers/1')
@@ -379,6 +380,7 @@ class ServersTest(test.TestCase):
"metadata": {
"seq": "1",
},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -545,6 +547,7 @@ class ServersTest(test.TestCase):
"metadata": {
"seq": "1",
},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -638,6 +641,7 @@ class ServersTest(test.TestCase):
"metadata": {
"seq": "1",
},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -1399,6 +1403,7 @@ class ServersTest(test.TestCase):
'image_ref': image_ref,
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
+ "config_drive": self.config_drive,
}
def server_update(context, id, params):
@@ -1424,8 +1429,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'instance_create', instance_create)
self.stubs.Set(nova.rpc, 'cast', fake_method)
self.stubs.Set(nova.rpc, 'call', fake_method)
- self.stubs.Set(nova.db.api, 'instance_update',
- server_update)
+ self.stubs.Set(nova.db.api, 'instance_update', server_update)
self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for)
self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip',
fake_method)
@@ -1768,6 +1772,129 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_create_instance_with_config_drive_v1_1(self):
+ self.config_drive = True
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': True,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/123/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(fakes.wsgi_app())
+ print res
+ self.assertEqual(res.status_int, 202)
+ server = json.loads(res.body)['server']
+ self.assertEqual(1, server['id'])
+ self.assertTrue(server['config_drive'])
+
+ def test_create_instance_with_config_drive_as_id_v1_1(self):
+ self.config_drive = 2
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': 2,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/123/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, 202)
+ server = json.loads(res.body)['server']
+ self.assertEqual(1, server['id'])
+ self.assertTrue(server['config_drive'])
+ self.assertEqual(2, server['config_drive'])
+
+ def test_create_instance_with_bad_config_drive_v1_1(self):
+ self.config_drive = "asdf"
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': 'asdf',
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/123/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_create_instance_without_config_drive_v1_1(self):
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': True,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/123/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, 202)
+ server = json.loads(res.body)['server']
+ self.assertEqual(1, server['id'])
+ self.assertFalse(server['config_drive'])
+
def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
@@ -1890,6 +2017,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 +2979,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 +3207,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}]
@@ -3266,6 +3576,7 @@ class ServersViewBuilderV11Test(test.TestCase):
"href": "http://localhost/servers/1",
},
],
+ "config_drive": None,
}
}
@@ -3278,6 +3589,7 @@ class ServersViewBuilderV11Test(test.TestCase):
"id": 1,
"uuid": self.instance['uuid'],
"name": "test_server",
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -3330,6 +3642,7 @@ class ServersViewBuilderV11Test(test.TestCase):
},
"addresses": {},
"metadata": {},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -3383,6 +3696,7 @@ class ServersViewBuilderV11Test(test.TestCase):
},
"addresses": {},
"metadata": {},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -3435,6 +3749,7 @@ class ServersViewBuilderV11Test(test.TestCase):
},
"addresses": {},
"metadata": {},
+ "config_drive": None,
"accessIPv4": "1.2.3.4",
"accessIPv6": "",
"links": [
@@ -3489,6 +3804,7 @@ class ServersViewBuilderV11Test(test.TestCase):
},
"addresses": {},
"metadata": {},
+ "config_drive": None,
"accessIPv4": "",
"accessIPv6": "fead::1234",
"links": [
@@ -3551,6 +3867,7 @@ class ServersViewBuilderV11Test(test.TestCase):
"Open": "Stack",
"Number": "1",
},
+ "config_drive": None,
"links": [
{
"rel": "self",
diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py
index fb2f88502..343190427 100644
--- a/nova/tests/integrated/integrated_helpers.py
+++ b/nova/tests/integrated/integrated_helpers.py
@@ -22,10 +22,8 @@ Provides common functionality for integrated unit tests
import random
import string
-from nova import exception
from nova import service
from nova import test # For the flags
-from nova.auth import manager
import nova.image.glance
from nova.log import logging
from nova.tests.integrated.api import client
@@ -58,90 +56,6 @@ def generate_new_element(items, prefix, numeric=False):
LOG.debug("Random collision on %s" % candidate)
-class TestUser(object):
- def __init__(self, name, secret, auth_url):
- self.name = name
- self.secret = secret
- self.auth_url = auth_url
-
- if not auth_url:
- raise exception.Error("auth_url is required")
- self.openstack_api = client.TestOpenStackClient(self.name,
- self.secret,
- self.auth_url)
-
- def get_unused_server_name(self):
- servers = self.openstack_api.get_servers()
- server_names = [server['name'] for server in servers]
- return generate_new_element(server_names, 'server')
-
- def get_invalid_image(self):
- images = self.openstack_api.get_images()
- image_ids = [image['id'] for image in images]
- return generate_new_element(image_ids, '', numeric=True)
-
- def get_valid_image(self, create=False):
- images = self.openstack_api.get_images()
- if create and not images:
- # TODO(justinsb): No way currently to create an image through API
- #created_image = self.openstack_api.post_image(image)
- #images.append(created_image)
- raise exception.Error("No way to create an image through API")
-
- if images:
- return images[0]
- return None
-
-
-class IntegratedUnitTestContext(object):
- def __init__(self, auth_url):
- self.auth_manager = manager.AuthManager()
-
- self.auth_url = auth_url
- self.project_name = None
-
- self.test_user = None
-
- self.setup()
-
- def setup(self):
- self._create_test_user()
-
- def _create_test_user(self):
- self.test_user = self._create_unittest_user()
-
- # No way to currently pass this through the OpenStack API
- self.project_name = 'openstack'
- self._configure_project(self.project_name, self.test_user)
-
- def cleanup(self):
- self.test_user = None
-
- def _create_unittest_user(self):
- users = self.auth_manager.get_users()
- user_names = [user.name for user in users]
- auth_name = generate_new_element(user_names, 'unittest_user_')
- auth_key = generate_random_alphanumeric(16)
-
- # Right now there's a bug where auth_name and auth_key are reversed
- # bug732907
- auth_key = auth_name
-
- self.auth_manager.create_user(auth_name, auth_name, auth_key, False)
- return TestUser(auth_name, auth_key, self.auth_url)
-
- def _configure_project(self, project_name, user):
- projects = self.auth_manager.get_projects()
- project_names = [project.name for project in projects]
- if not project_name in project_names:
- project = self.auth_manager.create_project(project_name,
- user.name,
- description=None,
- member_users=None)
- else:
- self.auth_manager.add_to_project(user.name, project_name)
-
-
class _IntegratedTestBase(test.TestCase):
def setUp(self):
super(_IntegratedTestBase, self).setUp()
@@ -163,10 +77,7 @@ class _IntegratedTestBase(test.TestCase):
self._start_api_service()
- self.context = IntegratedUnitTestContext(self.auth_url)
-
- self.user = self.context.test_user
- self.api = self.user.openstack_api
+ self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url)
def _start_api_service(self):
osapi = service.WSGIService("osapi")
@@ -174,10 +85,6 @@ class _IntegratedTestBase(test.TestCase):
self.auth_url = 'http://%s:%s/v1.1' % (osapi.host, osapi.port)
LOG.warn(self.auth_url)
- def tearDown(self):
- self.context.cleanup()
- super(_IntegratedTestBase, self).tearDown()
-
def _get_flags(self):
"""An opportunity to setup flags, before the services are started."""
f = {}
@@ -190,10 +97,20 @@ class _IntegratedTestBase(test.TestCase):
f['fake_network'] = True
return f
+ def get_unused_server_name(self):
+ servers = self.api.get_servers()
+ server_names = [server['name'] for server in servers]
+ return generate_new_element(server_names, 'server')
+
+ def get_invalid_image(self):
+ images = self.api.get_images()
+ image_ids = [image['id'] for image in images]
+ return generate_new_element(image_ids, '', numeric=True)
+
def _build_minimal_create_server_request(self):
server = {}
- image = self.user.get_valid_image(create=True)
+ image = self.api.get_images()[0]
LOG.debug("Image: %s" % image)
if 'imageRef' in image:
@@ -211,7 +128,7 @@ class _IntegratedTestBase(test.TestCase):
server['flavorRef'] = 'http://fake.server/%s' % flavor['id']
# Set a valid server name
- server_name = self.user.get_unused_server_name()
+ server_name = self.get_unused_server_name()
server['name'] = server_name
return server
diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py
index 9d1925bc0..3a863d0f9 100644
--- a/nova/tests/integrated/test_login.py
+++ b/nova/tests/integrated/test_login.py
@@ -15,11 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import unittest
from nova.log import logging
from nova.tests.integrated import integrated_helpers
-from nova.tests.integrated.api import client
LOG = logging.getLogger('nova.tests.integrated')
@@ -31,40 +29,3 @@ class LoginTest(integrated_helpers._IntegratedTestBase):
flavors = self.api.get_flavors()
for flavor in flavors:
LOG.debug(_("flavor: %s") % flavor)
-
- def test_bad_login_password(self):
- """Test that I get a 401 with a bad username."""
- bad_credentials_api = client.TestOpenStackClient(self.user.name,
- "notso_password",
- self.user.auth_url)
-
- self.assertRaises(client.OpenStackApiAuthenticationException,
- bad_credentials_api.get_flavors)
-
- def test_bad_login_username(self):
- """Test that I get a 401 with a bad password."""
- bad_credentials_api = client.TestOpenStackClient("notso_username",
- self.user.secret,
- self.user.auth_url)
-
- self.assertRaises(client.OpenStackApiAuthenticationException,
- bad_credentials_api.get_flavors)
-
- def test_bad_login_both_bad(self):
- """Test that I get a 401 with both bad username and bad password."""
- bad_credentials_api = client.TestOpenStackClient("notso_username",
- "notso_password",
- self.user.auth_url)
-
- self.assertRaises(client.OpenStackApiAuthenticationException,
- bad_credentials_api.get_flavors)
-
- def test_good_login_bad_project(self):
- """Test that I get a 401 with valid user/pass but bad project"""
- self.api.project_id = 'openstackBAD'
-
- self.assertRaises(client.OpenStackApiAuthorizationException,
- self.api.get_flavors)
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py
index 725f6d529..c2f800689 100644
--- a/nova/tests/integrated/test_servers.py
+++ b/nova/tests/integrated/test_servers.py
@@ -51,7 +51,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase):
self.api.post_server, post)
# With an invalid imageRef, this throws 500.
- server['imageRef'] = self.user.get_invalid_image()
+ server['imageRef'] = self.get_invalid_image()
# TODO(justinsb): Check whatever the spec says should be thrown here
self.assertRaises(client.OpenStackApiException,
self.api.post_server, post)
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 993a87f23..0523d73b6 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -159,6 +160,20 @@ class ComputeTestCase(test.TestCase):
db.security_group_destroy(self.context, group['id'])
db.instance_destroy(self.context, ref[0]['id'])
+ def test_create_instance_associates_config_drive(self):
+ """Make sure create associates a config drive."""
+
+ instance_id = self._create_instance(params={'config_drive': True, })
+
+ try:
+ self.compute.run_instance(self.context, instance_id)
+ instances = db.instance_get_all(context.get_admin_context())
+ instance = instances[0]
+
+ self.assertTrue(instance.config_drive)
+ finally:
+ db.instance_destroy(self.context, instance_id)
+
def test_default_hostname_generator(self):
cases = [(None, 'server_1'), ('Hello, Server!', 'hello_server'),
('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello')]
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/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py
index 9c6563f14..f5ea68a03 100644
--- a/nova/tests/test_nova_manage.py
+++ b/nova/tests/test_nova_manage.py
@@ -28,55 +28,45 @@ sys.dont_write_bytecode = True
import imp
nova_manage = imp.load_source('nova_manage.py', NOVA_MANAGE_PATH)
sys.dont_write_bytecode = False
+import mox
+import stubout
-import netaddr
from nova import context
from nova import db
-from nova import flags
+from nova import exception
from nova import test
-
-FLAGS = flags.FLAGS
+from nova.tests.db import fakes as db_fakes
class FixedIpCommandsTestCase(test.TestCase):
def setUp(self):
super(FixedIpCommandsTestCase, self).setUp()
- cidr = '10.0.0.0/24'
- net = netaddr.IPNetwork(cidr)
- net_info = {'bridge': 'fakebr',
- 'bridge_interface': 'fakeeth',
- 'dns': FLAGS.flat_network_dns,
- 'cidr': cidr,
- 'netmask': str(net.netmask),
- 'gateway': str(net[1]),
- 'broadcast': str(net.broadcast),
- 'dhcp_start': str(net[2])}
- self.network = db.network_create_safe(context.get_admin_context(),
- net_info)
- num_ips = len(net)
- for index in range(num_ips):
- address = str(net[index])
- reserved = (index == 1 or index == 2)
- db.fixed_ip_create(context.get_admin_context(),
- {'network_id': self.network['id'],
- 'address': address,
- 'reserved': reserved})
+ self.stubs = stubout.StubOutForTesting()
+ db_fakes.stub_out_db_network_api(self.stubs)
self.commands = nova_manage.FixedIpCommands()
def tearDown(self):
- db.network_delete_safe(context.get_admin_context(), self.network['id'])
super(FixedIpCommandsTestCase, self).tearDown()
+ self.stubs.UnsetAll()
def test_reserve(self):
- self.commands.reserve('10.0.0.100')
+ self.commands.reserve('192.168.0.100')
address = db.fixed_ip_get_by_address(context.get_admin_context(),
- '10.0.0.100')
+ '192.168.0.100')
self.assertEqual(address['reserved'], True)
+ def test_reserve_nonexistent_address(self):
+ self.assertRaises(SystemExit,
+ self.commands.reserve,
+ '55.55.55.55')
+
def test_unreserve(self):
- db.fixed_ip_update(context.get_admin_context(), '10.0.0.100',
- {'reserved': True})
- self.commands.unreserve('10.0.0.100')
+ self.commands.unreserve('192.168.0.100')
address = db.fixed_ip_get_by_address(context.get_admin_context(),
- '10.0.0.100')
+ '192.168.0.100')
self.assertEqual(address['reserved'], False)
+
+ def test_unreserve_nonexistent_address(self):
+ self.assertRaises(SystemExit,
+ self.commands.unreserve,
+ '55.55.55.55')
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
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
index 19f3ec185..52b2881e8 100644
--- a/nova/virt/disk.py
+++ b/nova/virt/disk.py
@@ -2,6 +2,9 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+#
+# Copyright 2011, Piston Cloud Computing, Inc.
+#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -22,6 +25,7 @@ Includes injection of SSH PGP keys into authorized_keys file.
"""
+import json
import os
import tempfile
import time
@@ -60,7 +64,8 @@ def extend(image, size):
utils.execute('resize2fs', image, check_exit_code=False)
-def inject_data(image, key=None, net=None, partition=None, nbd=False):
+def inject_data(image, key=None, net=None, metadata=None,
+ partition=None, nbd=False, tune2fs=True):
"""Injects a ssh key and optionally net data into a disk image.
it will mount the image as a fully partitioned disk and attempt to inject
@@ -89,10 +94,10 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False):
' only inject raw disk images): %s' %
mapped_device)
- # Configure ext2fs so that it doesn't auto-check every N boots
- out, err = utils.execute('tune2fs', '-c', 0, '-i', 0,
- mapped_device, run_as_root=True)
-
+ if tune2fs:
+ # Configure ext2fs so that it doesn't auto-check every N boots
+ out, err = utils.execute('tune2fs', '-c', 0, '-i', 0,
+ mapped_device, run_as_root=True)
tmpdir = tempfile.mkdtemp()
try:
# mount loopback to dir
@@ -103,7 +108,8 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False):
% err)
try:
- inject_data_into_fs(tmpdir, key, net, utils.execute)
+ inject_data_into_fs(tmpdir, key, net, metadata,
+ utils.execute)
finally:
# unmount device
utils.execute('umount', mapped_device, run_as_root=True)
@@ -155,6 +161,7 @@ def destroy_container(target, instance, nbd=False):
def _link_device(image, nbd):
"""Link image to device using loopback or nbd"""
+
if nbd:
device = _allocate_device()
utils.execute('qemu-nbd', '-c', device, image, run_as_root=True)
@@ -190,6 +197,7 @@ def _allocate_device():
# NOTE(vish): This assumes no other processes are allocating nbd devices.
# It may race cause a race condition if multiple
# workers are running on a given machine.
+
while True:
if not _DEVICES:
raise exception.Error(_('No free nbd devices'))
@@ -203,7 +211,7 @@ def _free_device(device):
_DEVICES.append(device)
-def inject_data_into_fs(fs, key, net, execute):
+def inject_data_into_fs(fs, key, net, metadata, execute):
"""Injects data into a filesystem already mounted by the caller.
Virt connections can call this directly if they mount their fs
in a different way to inject_data
@@ -212,6 +220,16 @@ def inject_data_into_fs(fs, key, net, execute):
_inject_key_into_fs(key, fs, execute=execute)
if net:
_inject_net_into_fs(net, fs, execute=execute)
+ if metadata:
+ _inject_metadata_into_fs(metadata, fs, execute=execute)
+
+
+def _inject_metadata_into_fs(metadata, fs, execute=None):
+ metadata_path = os.path.join(fs, "meta.js")
+ metadata = dict([(m.key, m.value) for m in metadata])
+
+ utils.execute('sudo', 'tee', metadata_path,
+ process_input=json.dumps(metadata))
def _inject_key_into_fs(key, fs, execute=None):
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 20af2666d..93290aba7 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -62,11 +62,41 @@ def block_device_info_get_mapping(block_device_info):
class ComputeDriver(object):
"""Base class for compute drivers.
- Lots of documentation is currently on fake.py.
+ The interface to this class talks in terms of 'instances' (Amazon EC2 and
+ internal Nova terminology), by which we mean 'running virtual machine'
+ (XenAPI terminology) or domain (Xen or libvirt terminology).
+
+ An instance has an ID, which is the identifier chosen by Nova to represent
+ the instance further up the stack. This is unfortunately also called a
+ 'name' elsewhere. As far as this layer is concerned, 'instance ID' and
+ 'instance name' are synonyms.
+
+ Note that the instance ID or name is not human-readable or
+ customer-controlled -- it's an internal ID chosen by Nova. At the
+ nova.virt layer, instances do not have human-readable names at all -- such
+ things are only known higher up the stack.
+
+ Most virtualization platforms will also have their own identity schemes,
+ to uniquely identify a VM or domain. These IDs must stay internal to the
+ platform-specific layer, and never escape the connection interface. The
+ platform-specific layer is responsible for keeping track of which instance
+ ID maps to which platform-specific ID, and vice versa.
+
+ In contrast, the list_disks and list_interfaces calls may return
+ platform-specific IDs. These identify a specific virtual disk or specific
+ virtual network interface, and these IDs are opaque to the rest of Nova.
+
+ Some methods here take an instance of nova.compute.service.Instance. This
+ is the datastructure used by nova.compute to store details regarding an
+ instance, and pass them into this layer. This layer is responsible for
+ translating that generic datastructure into terms that are specific to the
+ virtualization platform.
+
"""
def init_host(self, host):
- """Adopt existing VM's running here"""
+ """Initialize anything that is necessary for the driver to function,
+ including catching up with currently running VM's on the given host."""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
@@ -74,6 +104,7 @@ class ComputeDriver(object):
"""Get the current status of an instance, by name (not ID!)
Returns a dict containing:
+
:state: the running state, one of the power_state codes
:max_mem: (int) the maximum memory in KBytes allowed
:mem: (int) the memory in KBytes used by the domain
@@ -84,6 +115,10 @@ class ComputeDriver(object):
raise NotImplementedError()
def list_instances(self):
+ """
+ Return the names of all the instances known to the virtualization
+ layer, as a list.
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
@@ -94,28 +129,53 @@ class ComputeDriver(object):
def spawn(self, context, instance,
network_info=None, block_device_info=None):
- """Launch a VM for the specified instance"""
+ """
+ Create a new instance/VM/domain on the virtualization platform.
+
+ Once this successfully completes, the instance should be
+ running (power_state.RUNNING).
+
+ If this fails, any partial instance should be completely
+ cleaned up, and the virtualization platform should be in the state
+ that it was before this call began.
+
+ :param context: security context
+ :param instance: Instance of {nova.compute.service.Instance}.
+ This function should use the data there to guide
+ the creation of the new instance.
+ :param network_info:
+ :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ :param block_device_info:
+ """
raise NotImplementedError()
def destroy(self, instance, network_info, cleanup=True):
"""Destroy (shutdown and delete) the specified instance.
The given parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
If the instance is not found (for example if networking failed), this
function should still succeed. It's probably a good idea to log a
warning in that case.
+ :param instance: Instance of {nova.compute.service.Instance} and so
+ the instance is being specified as instance.name.
+ :param network_info:
+ :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ :param cleanup:
+
"""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def reboot(self, instance, network_info):
- """Reboot specified VM"""
+ """Reboot the specified instance.
+
+ :param instance: Instance of {nova.compute.service.Instance} and so
+ the instance is being specified as instance.name.
+ :param network_info:
+ :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
@@ -140,31 +200,60 @@ class ComputeDriver(object):
raise NotImplementedError()
def get_host_ip_addr(self):
+ """
+ Retrieves the IP address of the dom0
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def attach_volume(self, context, instance_id, volume_id, mountpoint):
+ """Attach the disk at device_path to the instance at mountpoint"""
raise NotImplementedError()
def detach_volume(self, context, instance_id, volume_id):
+ """Detach the disk attached to the instance at mountpoint"""
raise NotImplementedError()
- def compare_cpu(self, context, cpu_info):
+ def compare_cpu(self, cpu_info):
+ """Compares given cpu info against host
+
+ Before attempting to migrate a VM to this host,
+ compare_cpu is called to ensure that the VM will
+ actually run here.
+
+ :param cpu_info: (str) JSON structure describing the source CPU.
+ :returns: None if migration is acceptable
+ :raises: :py:class:`~nova.exception.InvalidCPUInfo` if migration
+ is not acceptable.
+ """
raise NotImplementedError()
def migrate_disk_and_power_off(self, instance, dest):
- """Transfers the VHD of a running instance to another host, then shuts
- off the instance copies over the COW disk"""
+ """
+ Transfers the disk of a running instance in multiple phases, turning
+ off the instance before the end.
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def snapshot(self, context, instance, image_id):
- """Create snapshot from a running VM instance."""
+ """
+ Snapshots the specified instance.
+
+ The given parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name.
+
+ The second parameter is the name of the snapshot.
+ """
raise NotImplementedError()
def finish_migration(self, context, instance, disk_info, network_info,
resize_instance):
- """Completes a resize, turning on the migrated instance"""
+ """Completes a resize, turning on the migrated instance
+
+ :param network_info:
+ :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ """
raise NotImplementedError()
def revert_migration(self, instance):
@@ -173,7 +262,7 @@ class ComputeDriver(object):
raise NotImplementedError()
def pause(self, instance, callback):
- """Pause VM instance"""
+ """Pause the specified instance."""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
@@ -218,15 +307,15 @@ class ComputeDriver(object):
post_method, recover_method):
"""Spawning live_migration operation for distributing high-load.
- :params ctxt: security context
- :params instance_ref:
+ :param ctxt: security context
+ :param instance_ref:
nova.db.sqlalchemy.models.Instance object
instance object that is migrated.
- :params dest: destination host
- :params post_method:
+ :param dest: destination host
+ :param post_method:
post operation method.
expected nova.compute.manager.post_live_migration.
- :params recover_method:
+ :param recover_method:
recovery method when any exception occurs.
expected nova.compute.manager.recover_live_migration.
@@ -235,15 +324,69 @@ class ComputeDriver(object):
raise NotImplementedError()
def refresh_security_group_rules(self, security_group_id):
+ """This method is called after a change to security groups.
+
+ All security groups and their associated rules live in the datastore,
+ and calling this method should apply the updated rules to instances
+ running the specified security group.
+
+ An error should be raised if the operation cannot complete.
+
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def refresh_security_group_members(self, security_group_id):
+ """This method is called when a security group is added to an instance.
+
+ This message is sent to the virtualization drivers on hosts that are
+ running an instance that belongs to a security group that has a rule
+ that references the security group identified by `security_group_id`.
+ It is the responsiblity of this method to make sure any rules
+ that authorize traffic flow with members of the security group are
+ updated and any new members can communicate, and any removed members
+ cannot.
+
+ Scenario:
+ * we are running on host 'H0' and we have an instance 'i-0'.
+ * instance 'i-0' is a member of security group 'speaks-b'
+ * group 'speaks-b' has an ingress rule that authorizes group 'b'
+ * another host 'H1' runs an instance 'i-1'
+ * instance 'i-1' is a member of security group 'b'
+
+ When 'i-1' launches or terminates we will recieve the message
+ to update members of group 'b', at which time we will make
+ any changes needed to the rules for instance 'i-0' to allow
+ or deny traffic coming from 'i-1', depending on if it is being
+ added or removed from the group.
+
+ In this scenario, 'i-1' could just as easily have been running on our
+ host 'H0' and this method would still have been called. The point was
+ that this method isn't called on the host where instances of that
+ group are running (as is the case with
+ :method:`refresh_security_group_rules`) but is called where references
+ are made to authorizing those instances.
+
+ An error should be raised if the operation cannot complete.
+
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def refresh_provider_fw_rules(self, security_group_id):
- """See: nova/virt/fake.py for docs."""
+ """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.
+
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
@@ -284,18 +427,38 @@ class ComputeDriver(object):
raise NotImplementedError()
def set_admin_password(self, context, instance_id, new_pass=None):
- """Set the root/admin password for an instance on this server."""
+ """
+ Set the root password on the specified instance.
+
+ The first parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name. The second
+ parameter is the value of the new password.
+ """
raise NotImplementedError()
def inject_file(self, instance, b64_path, b64_contents):
- """Create a file on the VM instance. The file path and contents
- should be base64-encoded.
+ """
+ Writes a file on the specified instance.
+
+ The first parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name. The second
+ parameter is the base64-encoded path to which the file is to be
+ written on the instance; the third is the contents of the file, also
+ base64-encoded.
"""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def agent_update(self, instance, url, md5hash):
- """Update agent on the VM instance."""
+ """
+ Update agent on the specified instance.
+
+ The first parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name. The second
+ parameter is the URL of the agent to be fetched and updated on the
+ instance; the third is the md5 hash of the file for verification
+ purposes.
+ """
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
@@ -322,3 +485,83 @@ class ComputeDriver(object):
"""Plugs in VIFs to networks."""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
+
+ def update_host_status(self):
+ """Refresh host stats"""
+ raise NotImplementedError()
+
+ def get_host_stats(self, refresh=False):
+ """Return currently known host stats"""
+ raise NotImplementedError()
+
+ def list_disks(self, instance_name):
+ """
+ Return the IDs of all the virtual disks attached to the specified
+ instance, as a list. These IDs are opaque to the caller (they are
+ only useful for giving back to this layer as a parameter to
+ disk_stats). These IDs only need to be unique for a given instance.
+
+ Note that this function takes an instance ID.
+ """
+ raise NotImplementedError()
+
+ def list_interfaces(self, instance_name):
+ """
+ Return the IDs of all the virtual network interfaces attached to the
+ specified instance, as a list. These IDs are opaque to the caller
+ (they are only useful for giving back to this layer as a parameter to
+ interface_stats). These IDs only need to be unique for a given
+ instance.
+
+ Note that this function takes an instance ID.
+ """
+ raise NotImplementedError()
+
+ def resize(self, instance, flavor):
+ """
+ Resizes/Migrates the specified instance.
+
+ The flavor parameter determines whether or not the instance RAM and
+ disk space are modified, and if so, to what size.
+ """
+ raise NotImplementedError()
+
+ def block_stats(self, instance_name, disk_id):
+ """
+ Return performance counters associated with the given disk_id on the
+ given instance_name. These are returned as [rd_req, rd_bytes, wr_req,
+ wr_bytes, errs], where rd indicates read, wr indicates write, req is
+ the total number of I/O requests made, bytes is the total number of
+ bytes transferred, and errs is the number of requests held up due to a
+ full pipeline.
+
+ All counters are long integers.
+
+ This method is optional. On some platforms (e.g. XenAPI) performance
+ statistics can be retrieved directly in aggregate form, without Nova
+ having to do the aggregation. On those platforms, this method is
+ unused.
+
+ Note that this function takes an instance ID.
+ """
+ raise NotImplementedError()
+
+ def interface_stats(self, instance_name, iface_id):
+ """
+ Return performance counters associated with the given iface_id on the
+ given instance_id. These are returned as [rx_bytes, rx_packets,
+ rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx
+ indicates receive, tx indicates transmit, bytes and packets indicate
+ the total number of bytes or packets transferred, and errs and dropped
+ is the total number of packets failed / dropped.
+
+ All counters are long integers.
+
+ This method is optional. On some platforms (e.g. XenAPI) performance
+ statistics can be retrieved directly in aggregate form, without Nova
+ having to do the aggregation. On those platforms, this method is
+ unused.
+
+ Note that this function takes an instance ID.
+ """
+ raise NotImplementedError()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index dc0628772..13b7aeab5 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -48,37 +48,7 @@ class FakeInstance(object):
class FakeConnection(driver.ComputeDriver):
- """
- The interface to this class talks in terms of 'instances' (Amazon EC2 and
- internal Nova terminology), by which we mean 'running virtual machine'
- (XenAPI terminology) or domain (Xen or libvirt terminology).
-
- An instance has an ID, which is the identifier chosen by Nova to represent
- the instance further up the stack. This is unfortunately also called a
- 'name' elsewhere. As far as this layer is concerned, 'instance ID' and
- 'instance name' are synonyms.
-
- Note that the instance ID or name is not human-readable or
- customer-controlled -- it's an internal ID chosen by Nova. At the
- nova.virt layer, instances do not have human-readable names at all -- such
- things are only known higher up the stack.
-
- Most virtualization platforms will also have their own identity schemes,
- to uniquely identify a VM or domain. These IDs must stay internal to the
- platform-specific layer, and never escape the connection interface. The
- platform-specific layer is responsible for keeping track of which instance
- ID maps to which platform-specific ID, and vice versa.
-
- In contrast, the list_disks and list_interfaces calls may return
- platform-specific IDs. These identify a specific virtual disk or specific
- virtual network interface, and these IDs are opaque to the rest of Nova.
-
- Some methods here take an instance of nova.compute.service.Instance. This
- is the datastructure used by nova.compute to store details regarding an
- instance, and pass them into this layer. This layer is responsible for
- translating that generic datastructure into terms that are specific to the
- virtualization platform.
- """
+ """Fake hypervisor driver"""
def __init__(self):
self.instances = {}
@@ -105,17 +75,9 @@ class FakeConnection(driver.ComputeDriver):
return cls._instance
def init_host(self, host):
- """
- Initialize anything that is necessary for the driver to function,
- including catching up with currently running VM's on the given host.
- """
return
def list_instances(self):
- """
- Return the names of all the instances known to the virtualization
- layer, as a list.
- """
return self.instances.keys()
def _map_to_instance_info(self, instance):
@@ -131,167 +93,54 @@ class FakeConnection(driver.ComputeDriver):
def spawn(self, context, instance,
network_info=None, block_device_info=None):
- """
- Create a new instance/VM/domain on the virtualization platform.
-
- The given parameter is an instance of nova.compute.service.Instance.
- This function should use the data there to guide the creation of
- the new instance.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
-
- Once this successfully completes, the instance should be
- running (power_state.RUNNING).
-
- If this fails, any partial instance should be completely
- cleaned up, and the virtualization platform should be in the state
- that it was before this call began.
- """
-
name = instance.name
state = power_state.RUNNING
fake_instance = FakeInstance(name, state)
self.instances[name] = fake_instance
def snapshot(self, context, instance, name):
- """
- Snapshots the specified instance.
-
- The given parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name.
-
- The second parameter is the name of the snapshot.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
- """
pass
def reboot(self, instance, network_info):
- """
- Reboot the specified instance.
-
- The given parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
- """
pass
def get_host_ip_addr(self):
- """
- Retrieves the IP address of the dom0
- """
- pass
+ return '192.168.0.1'
def resize(self, instance, flavor):
- """
- Resizes/Migrates the specified instance.
-
- The flavor parameter determines whether or not the instance RAM and
- disk space are modified, and if so, to what size.
-
- The work will be done asynchronously. This function returns a task
- that allows the caller to detect when it is complete.
- """
pass
def set_admin_password(self, instance, new_pass):
- """
- Set the root password on the specified instance.
-
- The first parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name. The second
- parameter is the value of the new password.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
- """
pass
def inject_file(self, instance, b64_path, b64_contents):
- """
- Writes a file on the specified instance.
-
- The first parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name. The second
- parameter is the base64-encoded path to which the file is to be
- written on the instance; the third is the contents of the file, also
- base64-encoded.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
- """
pass
def agent_update(self, instance, url, md5hash):
- """
- Update agent on the specified instance.
-
- The first parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name. The second
- parameter is the URL of the agent to be fetched and updated on the
- instance; the third is the md5 hash of the file for verification
- purposes.
-
- The work will be done asynchronously. This function returns a
- task that allows the caller to detect when it is complete.
- """
pass
def rescue(self, context, instance, callback, network_info):
- """
- Rescue the specified instance.
- """
pass
def unrescue(self, instance, callback, network_info):
- """
- Unrescue the specified instance.
- """
pass
def poll_rescued_instances(self, timeout):
- """Poll for rescued instances"""
pass
def migrate_disk_and_power_off(self, instance, dest):
- """
- Transfers the disk of a running instance in multiple phases, turning
- off the instance before the end.
- """
- pass
-
- def attach_disk(self, instance, disk_info):
- """
- Attaches the disk to an instance given the metadata disk_info
- """
pass
def pause(self, instance, callback):
- """
- Pause the specified instance.
- """
pass
def unpause(self, instance, callback):
- """
- Unpause the specified instance.
- """
pass
def suspend(self, instance, callback):
- """
- suspend the specified instance
- """
pass
def resume(self, instance, callback):
- """
- resume the specified instance
- """
pass
def destroy(self, instance, network_info, cleanup=True):
@@ -303,25 +152,12 @@ class FakeConnection(driver.ComputeDriver):
(key, self.instances))
def attach_volume(self, instance_name, device_path, mountpoint):
- """Attach the disk at device_path to the instance at mountpoint"""
return True
def detach_volume(self, instance_name, mountpoint):
- """Detach the disk attached to the instance at mountpoint"""
return True
def get_info(self, instance_name):
- """
- Get a block of information about the given instance. This is returned
- as a dictionary containing 'state': The power_state of the instance,
- 'max_mem': The maximum memory for the instance, in KiB, 'mem': The
- current memory the instance has, in KiB, 'num_cpu': The current number
- of virtual CPUs the instance has, 'cpu_time': The total CPU time used
- by the instance, in nanoseconds.
-
- This method should raise exception.NotFound if the hypervisor has no
- knowledge of the instance
- """
if instance_name not in self.instances:
raise exception.InstanceNotFound(instance_id=instance_name)
i = self.instances[instance_name]
@@ -332,69 +168,18 @@ class FakeConnection(driver.ComputeDriver):
'cpu_time': 0}
def get_diagnostics(self, instance_name):
- pass
+ return {}
def list_disks(self, instance_name):
- """
- Return the IDs of all the virtual disks attached to the specified
- instance, as a list. These IDs are opaque to the caller (they are
- only useful for giving back to this layer as a parameter to
- disk_stats). These IDs only need to be unique for a given instance.
-
- Note that this function takes an instance ID.
- """
return ['A_DISK']
def list_interfaces(self, instance_name):
- """
- Return the IDs of all the virtual network interfaces attached to the
- specified instance, as a list. These IDs are opaque to the caller
- (they are only useful for giving back to this layer as a parameter to
- interface_stats). These IDs only need to be unique for a given
- instance.
-
- Note that this function takes an instance ID.
- """
return ['A_VIF']
def block_stats(self, instance_name, disk_id):
- """
- Return performance counters associated with the given disk_id on the
- given instance_name. These are returned as [rd_req, rd_bytes, wr_req,
- wr_bytes, errs], where rd indicates read, wr indicates write, req is
- the total number of I/O requests made, bytes is the total number of
- bytes transferred, and errs is the number of requests held up due to a
- full pipeline.
-
- All counters are long integers.
-
- This method is optional. On some platforms (e.g. XenAPI) performance
- statistics can be retrieved directly in aggregate form, without Nova
- having to do the aggregation. On those platforms, this method is
- unused.
-
- Note that this function takes an instance ID.
- """
return [0L, 0L, 0L, 0L, None]
def interface_stats(self, instance_name, iface_id):
- """
- Return performance counters associated with the given iface_id on the
- given instance_id. These are returned as [rx_bytes, rx_packets,
- rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx
- indicates receive, tx indicates transmit, bytes and packets indicate
- the total number of bytes or packets transferred, and errs and dropped
- is the total number of packets failed / dropped.
-
- All counters are long integers.
-
- This method is optional. On some platforms (e.g. XenAPI) performance
- statistics can be retrieved directly in aggregate form, without Nova
- having to do the aggregation. On those platforms, this method is
- unused.
-
- Note that this function takes an instance ID.
- """
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
def get_console_output(self, instance):
@@ -416,67 +201,12 @@ class FakeConnection(driver.ComputeDriver):
'password': 'fakepassword'}
def refresh_security_group_rules(self, security_group_id):
- """This method is called after a change to security groups.
-
- All security groups and their associated rules live in the datastore,
- and calling this method should apply the updated rules to instances
- running the specified security group.
-
- An error should be raised if the operation cannot complete.
-
- """
return True
def refresh_security_group_members(self, security_group_id):
- """This method is called when a security group is added to an instance.
-
- This message is sent to the virtualization drivers on hosts that are
- running an instance that belongs to a security group that has a rule
- that references the security group identified by `security_group_id`.
- It is the responsiblity of this method to make sure any rules
- that authorize traffic flow with members of the security group are
- updated and any new members can communicate, and any removed members
- cannot.
-
- Scenario:
- * we are running on host 'H0' and we have an instance 'i-0'.
- * instance 'i-0' is a member of security group 'speaks-b'
- * group 'speaks-b' has an ingress rule that authorizes group 'b'
- * another host 'H1' runs an instance 'i-1'
- * instance 'i-1' is a member of security group 'b'
-
- When 'i-1' launches or terminates we will recieve the message
- to update members of group 'b', at which time we will make
- any changes needed to the rules for instance 'i-0' to allow
- or deny traffic coming from 'i-1', depending on if it is being
- added or removed from the group.
-
- In this scenario, 'i-1' could just as easily have been running on our
- host 'H0' and this method would still have been called. The point was
- that this method isn't called on the host where instances of that
- group are running (as is the case with
- :method:`refresh_security_group_rules`) but is called where references
- are made to authorizing those instances.
-
- An error should be raised if the operation cannot complete.
-
- """
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):
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template
index 210e2b0fb..6a02cfa24 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -106,6 +106,13 @@
</disk>
#end for
#end if
+ #if $getVar('config_drive', False)
+ <disk type='file'>
+ <driver type='raw' />
+ <source file='${basepath}/disk.config' />
+ <target dev='${disk_prefix}z' bus='${disk_bus}' />
+ </disk>
+ #end if
#end if
#for $nic in $nics
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index e8a657bac..4388291db 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -4,6 +4,7 @@
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
# Copyright (c) 2010 Citrix Systems, Inc.
+# Copyright (c) 2011 Piston Cloud Computing, Inc
#
# 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
@@ -130,6 +131,10 @@ flags.DEFINE_string('libvirt_vif_type', 'bridge',
flags.DEFINE_string('libvirt_vif_driver',
'nova.virt.libvirt.vif.LibvirtBridgeDriver',
'The libvirt VIF driver to configure the VIFs.')
+flags.DEFINE_string('default_local_format',
+ None,
+ 'The default format a local_volume will be formatted with '
+ 'on creation.')
def get_connection(read_only):
@@ -586,6 +591,7 @@ class LibvirtConnection(driver.ComputeDriver):
self.firewall_driver.prepare_instance_filter(instance, network_info)
self._create_image(context, instance, xml, network_info=network_info,
block_device_info=block_device_info)
+
domain = self._create_new_domain(xml)
LOG.debug(_("instance %s: is running"), instance['name'])
self.firewall_driver.apply_instance_filter(instance, network_info)
@@ -759,10 +765,15 @@ class LibvirtConnection(driver.ComputeDriver):
if size:
disk.extend(target, size)
- def _create_local(self, target, local_gb):
+ def _create_local(self, target, local_size, prefix='G', fs_format=None):
"""Create a blank image of specified size"""
- utils.execute('truncate', target, '-s', "%dG" % local_gb)
- # TODO(vish): should we format disk by default?
+
+ if not fs_format:
+ fs_format = FLAGS.default_local_format
+
+ utils.execute('truncate', target, '-s', "%d%c" % (local_size, prefix))
+ if fs_format:
+ utils.execute('mkfs', '-t', fs_format, target)
def _create_swap(self, target, swap_gb):
"""Create a swap file of specified size"""
@@ -849,14 +860,14 @@ class LibvirtConnection(driver.ComputeDriver):
target=basepath('disk.local'),
fname="local_%s" % local_gb,
cow=FLAGS.use_cow_images,
- local_gb=local_gb)
+ local_size=local_gb)
for eph in driver.block_device_info_get_ephemerals(block_device_info):
self._cache_image(fn=self._create_local,
target=basepath(_get_eph_disk(eph)),
fname="local_%s" % eph['size'],
cow=FLAGS.use_cow_images,
- local_gb=eph['size'])
+ local_size=eph['size'])
swap_gb = 0
@@ -882,9 +893,24 @@ class LibvirtConnection(driver.ComputeDriver):
if not inst['kernel_id']:
target_partition = "1"
- if FLAGS.libvirt_type == 'lxc':
+ config_drive_id = inst.get('config_drive_id')
+ config_drive = inst.get('config_drive')
+
+ if any((FLAGS.libvirt_type == 'lxc', config_drive, config_drive_id)):
target_partition = None
+ if config_drive_id:
+ fname = '%08x' % int(config_drive_id)
+ self._cache_image(fn=self._fetch_image,
+ target=basepath('disk.config'),
+ fname=fname,
+ image_id=config_drive_id,
+ user=user,
+ project=project)
+ elif config_drive:
+ self._create_local(basepath('disk.config'), 64, prefix="M",
+ fs_format='msdos') # 64MB
+
if inst['key_data']:
key = str(inst['key_data'])
else:
@@ -928,19 +954,29 @@ class LibvirtConnection(driver.ComputeDriver):
searchList=[{'interfaces': nets,
'use_ipv6': FLAGS.use_ipv6}]))
- if key or net:
+ metadata = inst.get('metadata')
+ if any((key, net, metadata)):
inst_name = inst['name']
- img_id = inst.image_ref
- if key:
- LOG.info(_('instance %(inst_name)s: injecting key into'
- ' image %(img_id)s') % locals())
- if net:
- LOG.info(_('instance %(inst_name)s: injecting net into'
- ' image %(img_id)s') % locals())
+
+ if config_drive: # Should be True or None by now.
+ injection_path = basepath('disk.config')
+ img_id = 'config-drive'
+ tune2fs = False
+ else:
+ injection_path = basepath('disk')
+ img_id = inst.image_ref
+ tune2fs = True
+
+ for injection in ('metadata', 'key', 'net'):
+ if locals()[injection]:
+ LOG.info(_('instance %(inst_name)s: injecting '
+ '%(injection)s into image %(img_id)s'
+ % locals()))
try:
- disk.inject_data(basepath('disk'), key, net,
+ disk.inject_data(injection_path, key, net, metadata,
partition=target_partition,
- nbd=FLAGS.use_cow_images)
+ nbd=FLAGS.use_cow_images,
+ tune2fs=tune2fs)
if FLAGS.libvirt_type == 'lxc':
disk.setup_container(basepath('disk'),
@@ -1070,6 +1106,10 @@ class LibvirtConnection(driver.ComputeDriver):
block_device_info)):
xml_info['swap_device'] = self.default_swap_device
+ config_drive = False
+ if instance.get('config_drive') or instance.get('config_drive_id'):
+ xml_info['config_drive'] = xml_info['basepath'] + "/disk.config"
+
if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'):
xml_info['vncserver_host'] = FLAGS.vncserver_host
xml_info['vnc_keymap'] = FLAGS.vnc_keymap
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 4a1f07bb1..efbea7076 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010 Citrix Systems, Inc.
+# Copyright 2011 Piston Cloud Computing, Inc.
#
# 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
@@ -740,13 +741,14 @@ class VMHelper(HelperBase):
# if at all, so determine whether it's required first, and then do
# everything
mount_required = False
- key, net = _prepare_injectables(instance, network_info)
- mount_required = key or net
+ key, net, metadata = _prepare_injectables(instance, network_info)
+ mount_required = key or net or metadata
if not mount_required:
return
with_vdi_attached_here(session, vdi_ref, False,
- lambda dev: _mounted_processing(dev, key, net))
+ lambda dev: _mounted_processing(dev, key, net,
+ metadata))
@classmethod
def lookup_kernel_ramdisk(cls, session, vm):
@@ -1198,7 +1200,7 @@ def _find_guest_agent(base_dir, agent_rel_path):
return False
-def _mounted_processing(device, key, net):
+def _mounted_processing(device, key, net, metadata):
"""Callback which runs with the image VDI attached"""
dev_path = '/dev/' + device + '1' # NB: Partition 1 hardcoded
@@ -1212,7 +1214,7 @@ def _mounted_processing(device, key, net):
if not _find_guest_agent(tmpdir, FLAGS.xenapi_agent_path):
LOG.info(_('Manipulating interface files '
'directly'))
- disk.inject_data_into_fs(tmpdir, key, net,
+ disk.inject_data_into_fs(tmpdir, key, net, metadata,
utils.execute)
finally:
utils.execute('umount', dev_path, run_as_root=True)
@@ -1235,6 +1237,7 @@ def _prepare_injectables(inst, networks_info):
template = t.Template
template_data = open(FLAGS.injected_network_template).read()
+ metadata = inst['metadata']
key = str(inst['key_data'])
net = None
if networks_info:
@@ -1272,4 +1275,4 @@ def _prepare_injectables(inst, networks_info):
net = str(template(template_data,
searchList=[{'interfaces': interfaces_info,
'use_ipv6': FLAGS.use_ipv6}]))
- return key, net
+ return key, net, metadata
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 9a6215f88..64c106f47 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -239,8 +239,9 @@ class VMOps(object):
self._attach_disks(instance, disk_image_type, vm_ref, first_vdi_ref,
vdis)
- # Alter the image before VM start for, e.g. network injection
- if FLAGS.flat_injected:
+ # Alter the image before VM start for, e.g. network injection also
+ # alter the image if there's metadata.
+ if FLAGS.flat_injected or instance['metadata']:
VMHelper.preconfigure_instance(self._session, instance,
first_vdi_ref, network_info)