summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorSandy Walsh <sandy.walsh@rackspace.com>2011-05-16 19:29:44 -0700
committerSandy Walsh <sandy.walsh@rackspace.com>2011-05-16 19:29:44 -0700
commit6bec79c4e1f5999dd69134fe15bd799e0cba9761 (patch)
tree04e0a263dd707f3eda9a8d770bbe2eeb1587d5fe /nova
parent02bba6a8f49b924e9b5b0e69124afd953e8cc3ae (diff)
parent0946bac5fa210dc9172bbd6ab73ffd77495ebe58 (diff)
failure conditions are being sent back properly now
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/cloud.py6
-rw-r--r--nova/api/openstack/flavors.py3
-rw-r--r--nova/api/openstack/servers.py20
-rw-r--r--nova/api/openstack/views/limits.py9
-rw-r--r--nova/compute/api.py22
-rw-r--r--nova/compute/manager.py9
-rw-r--r--nova/db/api.py30
-rw-r--r--nova/db/sqlalchemy/api.py52
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py203
-rw-r--r--nova/db/sqlalchemy/models.py18
-rw-r--r--nova/ipv6/__init__.py17
-rw-r--r--nova/ipv6/account_identifier.py45
-rw-r--r--nova/ipv6/api.py41
-rw-r--r--nova/ipv6/rfc2462.py42
-rw-r--r--nova/quota.py44
-rw-r--r--nova/scheduler/host_filter.py5
-rw-r--r--nova/scheduler/manager.py6
-rw-r--r--nova/scheduler/zone_aware_scheduler.py12
-rw-r--r--nova/tests/api/openstack/test_servers.py111
-rw-r--r--nova/tests/db/fakes.py1
-rw-r--r--nova/tests/network/base.py9
-rw-r--r--nova/tests/test_cloud.py55
-rw-r--r--nova/tests/test_ipv6.py60
-rw-r--r--nova/tests/test_quota.py81
-rw-r--r--nova/tests/test_virt.py2
-rw-r--r--nova/tests/test_xenapi.py23
-rw-r--r--nova/tests/xenapi/stubs.py39
-rw-r--r--nova/utils.py22
-rw-r--r--nova/virt/libvirt_conn.py4
-rw-r--r--nova/virt/xenapi/vmops.py12
-rw-r--r--nova/virt/xenapi_conn.py86
31 files changed, 829 insertions, 260 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index be5dd38a0..1fa07d042 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -35,6 +35,7 @@ from nova import crypto
from nova import db
from nova import exception
from nova import flags
+from nova import ipv6
from nova import log as logging
from nova import network
from nova import utils
@@ -718,9 +719,10 @@ class CloudController(object):
fixed = instance['fixed_ip']
floating_addr = fixed['floating_ips'][0]['address']
if instance['fixed_ip']['network'] and 'use_v6' in kwargs:
- i['dnsNameV6'] = utils.to_global_ipv6(
+ i['dnsNameV6'] = ipv6.to_global(
instance['fixed_ip']['network']['cidr_v6'],
- instance['mac_address'])
+ instance['mac_address'],
+ instance['project_id'])
i['privateDnsName'] = fixed_addr
i['privateIpAddress'] = fixed_addr
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 40787bd17..4c5971cf6 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -45,6 +45,9 @@ class Controller(common.OpenstackController):
items = self._get_flavors(req, is_detail=True)
return dict(flavors=items)
+ def _get_view_builder(self, req):
+ raise NotImplementedError()
+
def _get_flavors(self, req, is_detail=True):
"""Helper function that returns a list of flavor dicts."""
ctxt = req.environ['nova.context']
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 547310613..8f2de2afe 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -75,6 +75,21 @@ class Controller(common.OpenstackController):
""" Returns a list of server details for a given user """
return self._items(req, is_detail=True)
+ def _image_id_from_req_data(self, data):
+ raise NotImplementedError()
+
+ def _flavor_id_from_req_data(self, data):
+ raise NotImplementedError()
+
+ def _get_view_builder(self, req):
+ raise NotImplementedError()
+
+ def _limit_items(self, items, req):
+ raise NotImplementedError()
+
+ def _action_rebuild(self, info, request, instance_id):
+ raise NotImplementedError()
+
def _items(self, req, is_detail):
"""Returns a list of servers for a given user.
@@ -743,8 +758,9 @@ class ServerCreateRequestXMLDeserializer(object):
"""Marshal the server attribute of a parsed request"""
server = {}
server_node = self._find_first_child_named(node, 'server')
- for attr in ["name", "imageId", "flavorId"]:
- server[attr] = server_node.getAttribute(attr)
+ for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
+ if server_node.getAttribute(attr):
+ server[attr] = server_node.getAttribute(attr)
metadata = self._extract_metadata(server_node)
if metadata is not None:
server["metadata"] = metadata
diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py
index 552db39ee..22d1c260d 100644
--- a/nova/api/openstack/views/limits.py
+++ b/nova/api/openstack/views/limits.py
@@ -23,6 +23,15 @@ from nova.api.openstack import common
class ViewBuilder(object):
"""Openstack API base limits view builder."""
+ def _build_rate_limits(self, rate_limits):
+ raise NotImplementedError()
+
+ def _build_rate_limit(self, rate_limit):
+ raise NotImplementedError()
+
+ def _build_absolute_limits(self, absolute_limit):
+ raise NotImplementedError()
+
def build(self, rate_limits, absolute_limits):
rate_limits = self._build_rate_limits(rate_limits)
absolute_limits = self._build_absolute_limits(absolute_limits)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index f57d5cd90..4ddbbd0e2 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -19,6 +19,7 @@
"""Handles all requests relating to instances (guest vms)."""
import datetime
+import eventlet
import re
import time
@@ -42,6 +43,8 @@ LOG = logging.getLogger('nova.compute.api')
FLAGS = flags.FLAGS
flags.DECLARE('vncproxy_topic', 'nova.vnc')
+flags.DEFINE_integer('find_host_timeout', 30,
+ 'Timeout after NN seconds when looking for a host.')
def generate_default_hostname(instance_id):
@@ -496,7 +499,7 @@ class API(base.Base):
def _find_host(self, context, instance_id):
"""Find the host associated with an instance."""
- for attempts in xrange(10):
+ for attempts in xrange(FLAGS.find_host_timeout):
instance = self.get(context, instance_id)
host = instance["host"]
if host:
@@ -505,6 +508,15 @@ class API(base.Base):
raise exception.Error(_("Unable to find host for Instance %s")
% instance_id)
+ def _set_admin_password(self, context, instance_id, password):
+ """Set the root/admin password for the given instance."""
+ host = self._find_host(context, instance_id)
+
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "set_admin_password",
+ "args": {"instance_id": instance_id, "new_pass": password}})
+
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance.
@@ -658,12 +670,8 @@ class API(base.Base):
def set_admin_password(self, context, instance_id, password=None):
"""Set the root/admin password for the given instance."""
- host = self._find_host(context, instance_id)
-
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "set_admin_password",
- "args": {"instance_id": instance_id, "new_pass": password}})
+ eventlet.spawn_n(self._set_admin_password(context, instance_id,
+ password))
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 556b3b3b9..923feaa59 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -77,7 +77,8 @@ flags.DEFINE_integer("rescue_timeout", 0,
" Set to 0 to disable.")
flags.DEFINE_bool('auto_assign_floating_ip', False,
'Autoassigning floating ip to VM')
-
+flags.DEFINE_integer('host_state_interval', 120,
+ 'Interval in seconds for querying the host status')
LOG = logging.getLogger('nova.compute.manager')
@@ -426,6 +427,12 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.audit(_("Instance %s: Root password set"),
instance_ref["name"])
break
+ except NotImplementedError:
+ # NOTE(dprince): if the driver doesn't implement
+ # set_admin_password we break to avoid a loop
+ LOG.warn(_('set_admin_password is not implemented '
+ 'by this driver.'))
+ break
except Exception, e:
# Catch all here because this could be anything.
LOG.exception(e)
diff --git a/nova/db/api.py b/nova/db/api.py
index f9a4b5b4b..ef8aa1143 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -756,24 +756,34 @@ def auth_token_create(context, token):
###################
-def quota_create(context, values):
- """Create a quota from the values dictionary."""
- return IMPL.quota_create(context, values)
+def quota_create(context, project_id, resource, limit):
+ """Create a quota for the given project and resource."""
+ return IMPL.quota_create(context, project_id, resource, limit)
-def quota_get(context, project_id):
+def quota_get(context, project_id, resource):
"""Retrieve a quota or raise if it does not exist."""
- return IMPL.quota_get(context, project_id)
+ return IMPL.quota_get(context, project_id, resource)
-def quota_update(context, project_id, values):
- """Update a quota from the values dictionary."""
- return IMPL.quota_update(context, project_id, values)
+def quota_get_all_by_project(context, project_id):
+ """Retrieve all quotas associated with a given project."""
+ return IMPL.quota_get_all_by_project(context, project_id)
-def quota_destroy(context, project_id):
+def quota_update(context, project_id, resource, limit):
+ """Update a quota or raise if it does not exist."""
+ return IMPL.quota_update(context, project_id, resource, limit)
+
+
+def quota_destroy(context, project_id, resource):
"""Destroy the quota or raise if it does not exist."""
- return IMPL.quota_destroy(context, project_id)
+ return IMPL.quota_destroy(context, project_id, resource)
+
+
+def quota_destroy_all_by_project(context, project_id):
+ """Destroy all quotas associated with a given project."""
+ return IMPL.quota_get_all_by_project(context, project_id)
###################
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 285b22a04..3681f30db 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -25,6 +25,7 @@ import warnings
from nova import db
from nova import exception
from nova import flags
+from nova import ipv6
from nova import utils
from nova.db.sqlalchemy import models
from nova.db.sqlalchemy.session import get_session
@@ -744,7 +745,7 @@ def fixed_ip_get_all_by_instance(context, instance_id):
@require_context
def fixed_ip_get_instance_v6(context, address):
session = get_session()
- mac = utils.to_mac(address)
+ mac = ipv6.to_mac(address)
result = session.query(models.Instance).\
filter_by(mac_address=mac).\
@@ -974,7 +975,8 @@ def instance_get_fixed_address_v6(context, instance_id):
network_ref = network_get_by_instance(context, instance_id)
prefix = network_ref.cidr_v6
mac = instance_ref.mac_address
- return utils.to_global_ipv6(prefix, mac)
+ project_id = instance_ref.project_id
+ return ipv6.to_global(prefix, mac, project_id)
@require_context
@@ -1496,45 +1498,71 @@ def auth_token_create(_context, token):
@require_admin_context
-def quota_get(context, project_id, session=None):
+def quota_get(context, project_id, resource, session=None):
if not session:
session = get_session()
-
result = session.query(models.Quota).\
filter_by(project_id=project_id).\
- filter_by(deleted=can_read_deleted(context)).\
+ filter_by(resource=resource).\
+ filter_by(deleted=False).\
first()
if not result:
raise exception.ProjectQuotaNotFound(project_id=project_id)
+ return result
+
+@require_admin_context
+def quota_get_all_by_project(context, project_id):
+ session = get_session()
+ result = {'project_id': project_id}
+ rows = session.query(models.Quota).\
+ filter_by(project_id=project_id).\
+ filter_by(deleted=False).\
+ all()
+ for row in rows:
+ result[row.resource] = row.hard_limit
return result
@require_admin_context
-def quota_create(context, values):
+def quota_create(context, project_id, resource, limit):
quota_ref = models.Quota()
- quota_ref.update(values)
+ quota_ref.project_id = project_id
+ quota_ref.resource = resource
+ quota_ref.hard_limit = limit
quota_ref.save()
return quota_ref
@require_admin_context
-def quota_update(context, project_id, values):
+def quota_update(context, project_id, resource, limit):
session = get_session()
with session.begin():
- quota_ref = quota_get(context, project_id, session=session)
- quota_ref.update(values)
+ quota_ref = quota_get(context, project_id, resource, session=session)
+ quota_ref.hard_limit = limit
quota_ref.save(session=session)
@require_admin_context
-def quota_destroy(context, project_id):
+def quota_destroy(context, project_id, resource):
session = get_session()
with session.begin():
- quota_ref = quota_get(context, project_id, session=session)
+ quota_ref = quota_get(context, project_id, resource, session=session)
quota_ref.delete(session=session)
+@require_admin_context
+def quota_destroy_all_by_project(context, project_id):
+ session = get_session()
+ with session.begin():
+ quotas = session.query(models.Quota).\
+ filter_by(project_id=project_id).\
+ filter_by(deleted=False).\
+ all()
+ for quota_ref in quotas:
+ quota_ref.delete(session=session)
+
+
###################
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
new file mode 100644
index 000000000..a2d8192ca
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
@@ -0,0 +1,203 @@
+# 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 Boolean, Column, DateTime, Integer
+from sqlalchemy import MetaData, String, Table
+
+import datetime
+
+meta = MetaData()
+
+resources = [
+ 'instances',
+ 'cores',
+ 'volumes',
+ 'gigabytes',
+ 'floating_ips',
+ 'metadata_items',
+]
+
+
+def old_style_quotas_table(name):
+ return Table(name, meta,
+ Column('id', Integer(), primary_key=True),
+ Column('created_at', DateTime(),
+ default=datetime.datetime.utcnow),
+ Column('updated_at', DateTime(),
+ onupdate=datetime.datetime.utcnow),
+ Column('deleted_at', DateTime()),
+ Column('deleted', Boolean(), default=False),
+ Column('project_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False)),
+ Column('instances', Integer()),
+ Column('cores', Integer()),
+ Column('volumes', Integer()),
+ Column('gigabytes', Integer()),
+ Column('floating_ips', Integer()),
+ Column('metadata_items', Integer()),
+ )
+
+
+def new_style_quotas_table(name):
+ return Table(name, meta,
+ Column('id', Integer(), primary_key=True),
+ Column('created_at', DateTime(),
+ default=datetime.datetime.utcnow),
+ Column('updated_at', DateTime(),
+ onupdate=datetime.datetime.utcnow),
+ Column('deleted_at', DateTime()),
+ Column('deleted', Boolean(), default=False),
+ Column('project_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False)),
+ Column('resource',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False),
+ nullable=False),
+ Column('hard_limit', Integer(), nullable=True),
+ )
+
+
+def existing_quotas_table(migrate_engine):
+ return Table('quotas', meta, autoload=True, autoload_with=migrate_engine)
+
+
+def _assert_no_duplicate_project_ids(quotas):
+ project_ids = set()
+ message = ('There are multiple active quotas for project "%s" '
+ '(among others, possibly). '
+ 'Please resolve all ambiguous quotas before '
+ 'reattempting the migration.')
+ for quota in quotas:
+ assert quota.project_id not in project_ids, message % quota.project_id
+ project_ids.add(quota.project_id)
+
+
+def assert_old_quotas_have_no_active_duplicates(migrate_engine, quotas):
+ """Ensure that there are no duplicate non-deleted quota entries."""
+ select = quotas.select().where(quotas.c.deleted == False)
+ results = migrate_engine.execute(select)
+ _assert_no_duplicate_project_ids(list(results))
+
+
+def assert_new_quotas_have_no_active_duplicates(migrate_engine, quotas):
+ """Ensure that there are no duplicate non-deleted quota entries."""
+ for resource in resources:
+ select = quotas.select().\
+ where(quotas.c.deleted == False).\
+ where(quotas.c.resource == resource)
+ results = migrate_engine.execute(select)
+ _assert_no_duplicate_project_ids(list(results))
+
+
+def convert_forward(migrate_engine, old_quotas, new_quotas):
+ quotas = list(migrate_engine.execute(old_quotas.select()))
+ for quota in quotas:
+ for resource in resources:
+ hard_limit = getattr(quota, resource)
+ if hard_limit is None:
+ continue
+ insert = new_quotas.insert().values(
+ created_at=quota.created_at,
+ updated_at=quota.updated_at,
+ deleted_at=quota.deleted_at,
+ deleted=quota.deleted,
+ project_id=quota.project_id,
+ resource=resource,
+ hard_limit=hard_limit)
+ migrate_engine.execute(insert)
+
+
+def earliest(date1, date2):
+ if date1 is None and date2 is None:
+ return None
+ if date1 is None:
+ return date2
+ if date2 is None:
+ return date1
+ if date1 < date2:
+ return date1
+ return date2
+
+
+def latest(date1, date2):
+ if date1 is None and date2 is None:
+ return None
+ if date1 is None:
+ return date2
+ if date2 is None:
+ return date1
+ if date1 > date2:
+ return date1
+ return date2
+
+
+def convert_backward(migrate_engine, old_quotas, new_quotas):
+ quotas = {}
+ for quota in migrate_engine.execute(new_quotas.select()):
+ if (quota.resource not in resources
+ or quota.hard_limit is None or quota.deleted):
+ continue
+ if not quota.project_id in quotas:
+ quotas[quota.project_id] = {
+ 'project_id': quota.project_id,
+ 'created_at': quota.created_at,
+ 'updated_at': quota.updated_at,
+ quota.resource: quota.hard_limit
+ }
+ else:
+ quotas[quota.project_id]['created_at'] = earliest(
+ quota.created_at, quotas[quota.project_id]['created_at'])
+ quotas[quota.project_id]['updated_at'] = latest(
+ quota.updated_at, quotas[quota.project_id]['updated_at'])
+ quotas[quota.project_id][quota.resource] = quota.hard_limit
+
+ for quota in quotas.itervalues():
+ insert = old_quotas.insert().values(**quota)
+ migrate_engine.execute(insert)
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine;
+ # bind migrate_engine to your metadata
+ meta.bind = migrate_engine
+
+ old_quotas = existing_quotas_table(migrate_engine)
+ assert_old_quotas_have_no_active_duplicates(migrate_engine, old_quotas)
+
+ new_quotas = new_style_quotas_table('quotas_new')
+ new_quotas.create()
+ convert_forward(migrate_engine, old_quotas, new_quotas)
+ old_quotas.drop()
+ new_quotas.rename('quotas')
+
+
+def downgrade(migrate_engine):
+ # Operations to reverse the above upgrade go here.
+ meta.bind = migrate_engine
+
+ new_quotas = existing_quotas_table(migrate_engine)
+ assert_new_quotas_have_no_active_duplicates(migrate_engine, new_quotas)
+
+ old_quotas = old_style_quotas_table('quotas_old')
+ old_quotas.create()
+ convert_backward(migrate_engine, old_quotas, new_quotas)
+ new_quotas.drop()
+ old_quotas.rename('quotas')
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 36a084a1d..0b46d5a05 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -313,18 +313,20 @@ class Volume(BASE, NovaBase):
class Quota(BASE, NovaBase):
- """Represents quota overrides for a project."""
+ """Represents a single quota override for a project.
+
+ If there is no row for a given project id and resource, then
+ the default for the deployment is used. If the row is present
+ but the hard limit is Null, then the resource is unlimited.
+ """
+
__tablename__ = 'quotas'
id = Column(Integer, primary_key=True)
- project_id = Column(String(255))
+ project_id = Column(String(255), index=True)
- instances = Column(Integer)
- cores = Column(Integer)
- volumes = Column(Integer)
- gigabytes = Column(Integer)
- floating_ips = Column(Integer)
- metadata_items = Column(Integer)
+ resource = Column(String(255))
+ hard_limit = Column(Integer, nullable=True)
class ExportDevice(BASE, NovaBase):
diff --git a/nova/ipv6/__init__.py b/nova/ipv6/__init__.py
new file mode 100644
index 000000000..da4567cfb
--- /dev/null
+++ b/nova/ipv6/__init__.py
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 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.ipv6.api import *
diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py
new file mode 100644
index 000000000..258678f0a
--- /dev/null
+++ b/nova/ipv6/account_identifier.py
@@ -0,0 +1,45 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# 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.
+
+"""IPv6 address generation with account identifier embedded"""
+
+import hashlib
+import netaddr
+
+
+def to_global(prefix, mac, project_id):
+ project_hash = netaddr.IPAddress(int(hashlib.sha1(project_id).\
+ hexdigest()[:8], 16) << 32)
+ static_num = netaddr.IPAddress(0xff << 24)
+
+ try:
+ mac_suffix = netaddr.EUI(mac).words[3:]
+ int_addr = int(''.join(['%02x' % i for i in mac_suffix]), 16)
+ mac_addr = netaddr.IPAddress(int_addr)
+ maskIP = netaddr.IPNetwork(prefix).ip
+ return (project_hash ^ static_num ^ mac_addr | maskIP).format()
+ except TypeError:
+ raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
+
+
+def to_mac(ipv6_address):
+ address = netaddr.IPAddress(ipv6_address)
+ mask1 = netaddr.IPAddress('::ff:ffff')
+ mac = netaddr.EUI(int(address & mask1)).words
+ return ':'.join(['02', '16', '3e'] + ['%02x' % i for i in mac[3:6]])
diff --git a/nova/ipv6/api.py b/nova/ipv6/api.py
new file mode 100644
index 000000000..da003645a
--- /dev/null
+++ b/nova/ipv6/api.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 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 import flags
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('ipv6_backend',
+ 'rfc2462',
+ 'Backend to use for IPv6 generation')
+
+
+def reset_backend():
+ global IMPL
+ IMPL = utils.LazyPluggable(FLAGS['ipv6_backend'],
+ rfc2462='nova.ipv6.rfc2462',
+ account_identifier='nova.ipv6.account_identifier')
+
+
+def to_global(prefix, mac, project_id):
+ return IMPL.to_global(prefix, mac, project_id)
+
+
+def to_mac(ipv6_address):
+ return IMPL.to_mac(ipv6_address)
+
+reset_backend()
diff --git a/nova/ipv6/rfc2462.py b/nova/ipv6/rfc2462.py
new file mode 100644
index 000000000..0074efe98
--- /dev/null
+++ b/nova/ipv6/rfc2462.py
@@ -0,0 +1,42 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# 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.
+
+"""RFC2462 style IPv6 address generation"""
+
+import netaddr
+
+
+def to_global(prefix, mac, project_id):
+ try:
+ mac64 = netaddr.EUI(mac).eui64().words
+ int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
+ mac64_addr = netaddr.IPAddress(int_addr)
+ maskIP = netaddr.IPNetwork(prefix).ip
+ return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
+ format()
+ except TypeError:
+ raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
+
+
+def to_mac(ipv6_address):
+ address = netaddr.IPAddress(ipv6_address)
+ mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff')
+ mask2 = netaddr.IPAddress('::0200:0:0:0')
+ mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
+ return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]])
diff --git a/nova/quota.py b/nova/quota.py
index d8b5d9a93..a93cd0766 100644
--- a/nova/quota.py
+++ b/nova/quota.py
@@ -52,26 +52,31 @@ def get_quota(context, project_id):
'floating_ips': FLAGS.quota_floating_ips,
'metadata_items': FLAGS.quota_metadata_items}
- try:
- quota = db.quota_get(context, project_id)
- for key in rval.keys():
- if quota[key] is not None:
- rval[key] = quota[key]
- except exception.NotFound:
- pass
+ quota = db.quota_get_all_by_project(context, project_id)
+ for key in rval.keys():
+ if key in quota:
+ rval[key] = quota[key]
return rval
+def _get_request_allotment(requested, used, quota):
+ if quota is None:
+ return requested
+ return quota - used
+
+
def allowed_instances(context, num_instances, instance_type):
"""Check quota and return min(num_instances, allowed_instances)."""
project_id = context.project_id
context = context.elevated()
+ num_cores = num_instances * instance_type['vcpus']
used_instances, used_cores = db.instance_data_get_for_project(context,
project_id)
quota = get_quota(context, project_id)
- allowed_instances = quota['instances'] - used_instances
- allowed_cores = quota['cores'] - used_cores
- num_cores = num_instances * instance_type['vcpus']
+ allowed_instances = _get_request_allotment(num_instances, used_instances,
+ quota['instances'])
+ allowed_cores = _get_request_allotment(num_cores, used_cores,
+ quota['cores'])
allowed_instances = min(allowed_instances,
int(allowed_cores // instance_type['vcpus']))
return min(num_instances, allowed_instances)
@@ -81,13 +86,15 @@ def allowed_volumes(context, num_volumes, size):
"""Check quota and return min(num_volumes, allowed_volumes)."""
project_id = context.project_id
context = context.elevated()
+ size = int(size)
+ num_gigabytes = num_volumes * size
used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
project_id)
quota = get_quota(context, project_id)
- allowed_volumes = quota['volumes'] - used_volumes
- allowed_gigabytes = quota['gigabytes'] - used_gigabytes
- size = int(size)
- num_gigabytes = num_volumes * size
+ allowed_volumes = _get_request_allotment(num_volumes, used_volumes,
+ quota['volumes'])
+ allowed_gigabytes = _get_request_allotment(num_gigabytes, used_gigabytes,
+ quota['gigabytes'])
allowed_volumes = min(allowed_volumes,
int(allowed_gigabytes // size))
return min(num_volumes, allowed_volumes)
@@ -99,7 +106,9 @@ def allowed_floating_ips(context, num_floating_ips):
context = context.elevated()
used_floating_ips = db.floating_ip_count_by_project(context, project_id)
quota = get_quota(context, project_id)
- allowed_floating_ips = quota['floating_ips'] - used_floating_ips
+ allowed_floating_ips = _get_request_allotment(num_floating_ips,
+ used_floating_ips,
+ quota['floating_ips'])
return min(num_floating_ips, allowed_floating_ips)
@@ -108,8 +117,9 @@ def allowed_metadata_items(context, num_metadata_items):
project_id = context.project_id
context = context.elevated()
quota = get_quota(context, project_id)
- num_allowed_metadata_items = quota['metadata_items']
- return min(num_metadata_items, num_allowed_metadata_items)
+ allowed_metadata_items = _get_request_allotment(num_metadata_items, 0,
+ quota['metadata_items'])
+ return min(num_metadata_items, allowed_metadata_items)
def allowed_injected_files(context):
diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py
index a47e41da4..8519b8b51 100644
--- a/nova/scheduler/host_filter.py
+++ b/nova/scheduler/host_filter.py
@@ -305,3 +305,8 @@ class HostFilterScheduler(zone_aware_scheduler.ZoneAwareScheduler):
instance_type = request_spec['instance_type']
query = driver.instance_type_to_filter(instance_type)
return driver.filter_hosts(self.zone_manager, query)
+
+ def weigh_hosts(self, num, request_spec, hosts):
+ """Derived classes must override this method and return
+ a lists of hosts in [{weight, hostname}] format."""
+ return []
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index 55cd7208b..60f98b082 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -83,11 +83,15 @@ class SchedulerManager(manager.Manager):
except AttributeError:
host = self.driver.schedule(elevated, topic, *args, **kwargs)
+ if not host:
+ LOG.debug(_("%(topic)s %(method)s handled in Scheduler") % locals())
+ return
+
rpc.cast(context,
db.queue_get_for(context, topic, host),
{"method": method,
"args": kwargs})
- LOG.debug(_("Casting to %(topic)s %(host)s for %(method)s") % locals())
+ LOG.debug(_("Casted to %(topic)s %(host)s for %(method)s") % locals())
# NOTE (masumotok) : This method should be moved to nova.api.ec2.admin.
# Based on bexar design summit discussion,
diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py
index f9c5f65f3..2fc5f1f87 100644
--- a/nova/scheduler/zone_aware_scheduler.py
+++ b/nova/scheduler/zone_aware_scheduler.py
@@ -53,6 +53,9 @@ class ZoneAwareScheduler(driver.Scheduler):
# Create build plan and provision ...
build_plan = self.select(context, request_spec)
+ if not build_plan:
+ raise driver.NoValidHost(_('No hosts were available'))
+
for item in build_plan:
self.provision_instance(context, topic, item)
@@ -67,15 +70,14 @@ class ZoneAwareScheduler(driver.Scheduler):
anything about the children."""
return self._schedule(context, "compute", request_spec, *args, **kwargs)
+ # TODO(sandy): We're only focused on compute instances right now,
+ # so we don't implement the default "schedule()" method required
+ # of Schedulers.
def schedule(self, context, topic, request_spec, *args, **kwargs):
"""The schedule() contract requires we return the one
best-suited host for this request.
"""
- res = self._schedule(context, topic, request_spec, *args, **kwargs)
- # TODO(sirp): should this be a host object rather than a weight-dict?
- if not res:
- raise driver.NoValidHost(_('No hosts were available'))
- return res[0]
+ raise driver.NoValidHost(_('No hosts were available'))
def _schedule(self, context, topic, request_spec, *args, **kwargs):
"""Returns a list of hosts that meet the required specs,
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 89edece42..e8182b6a9 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -183,7 +183,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res_dict['server']['id'], 1)
self.assertEqual(res_dict['server']['name'], 'server1')
- def test_get_server_by_id_v11(self):
+ def test_get_server_by_id_v1_1(self):
req = webob.Request.blank('/v1.1/servers/1')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
@@ -246,7 +246,7 @@ class ServersTest(test.TestCase):
self.assertEqual(len(addresses["private"]), 1)
self.assertEqual(addresses["private"][0], private)
- def test_get_server_addresses_V10(self):
+ def test_get_server_addresses_v1_0(self):
private = '192.168.0.3'
public = ['1.2.3.4']
new_return_server = return_server_with_addresses(private, public)
@@ -257,7 +257,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res_dict, {
'addresses': {'public': public, 'private': [private]}})
- def test_get_server_addresses_xml_V10(self):
+ def test_get_server_addresses_xml_v1_0(self):
private_expected = "192.168.0.3"
public_expected = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private_expected,
@@ -276,7 +276,7 @@ class ServersTest(test.TestCase):
(ip,) = private.getElementsByTagName('ip')
self.assertEquals(ip.getAttribute('addr'), private_expected)
- def test_get_server_addresses_public_V10(self):
+ def test_get_server_addresses_public_v1_0(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private, public)
@@ -286,7 +286,7 @@ class ServersTest(test.TestCase):
res_dict = json.loads(res.body)
self.assertEqual(res_dict, {'public': public})
- def test_get_server_addresses_private_V10(self):
+ def test_get_server_addresses_private_v1_0(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private, public)
@@ -296,7 +296,7 @@ class ServersTest(test.TestCase):
res_dict = json.loads(res.body)
self.assertEqual(res_dict, {'private': [private]})
- def test_get_server_addresses_public_xml_V10(self):
+ def test_get_server_addresses_public_xml_v1_0(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private, public)
@@ -310,7 +310,7 @@ class ServersTest(test.TestCase):
(ip,) = public_node.getElementsByTagName('ip')
self.assertEquals(ip.getAttribute('addr'), public[0])
- def test_get_server_addresses_private_xml_V10(self):
+ def test_get_server_addresses_private_xml_v1_0(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private, public)
@@ -324,7 +324,7 @@ class ServersTest(test.TestCase):
(ip,) = private_node.getElementsByTagName('ip')
self.assertEquals(ip.getAttribute('addr'), private)
- def test_get_server_by_id_with_addresses_v11(self):
+ def test_get_server_by_id_with_addresses_v1_1(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private, public)
@@ -354,7 +354,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s.get('imageId', None), None)
i += 1
- def test_get_server_list_v11(self):
+ def test_get_server_list_v1_1(self):
req = webob.Request.blank('/v1.1/servers')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
@@ -576,16 +576,16 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_create_instance_v11(self):
+ def test_create_instance_v1_1(self):
self._setup_for_create_instance()
- imageRef = 'http://localhost/v1.1/images/2'
- flavorRef = 'http://localhost/v1.1/flavors/3'
+ image_ref = 'http://localhost/v1.1/images/2'
+ flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': imageRef,
- 'flavorRef': flavorRef,
+ 'imageRef': image_ref,
+ 'flavorRef': flavor_ref,
'metadata': {
'hello': 'world',
'open': 'stack',
@@ -605,17 +605,17 @@ class ServersTest(test.TestCase):
self.assertEqual(16, len(server['adminPass']))
self.assertEqual('server_test', server['name'])
self.assertEqual(1, server['id'])
- self.assertEqual(flavorRef, server['flavorRef'])
- self.assertEqual(imageRef, server['imageRef'])
+ self.assertEqual(flavor_ref, server['flavorRef'])
+ self.assertEqual(image_ref, server['imageRef'])
self.assertEqual(res.status_int, 200)
- def test_create_instance_v11_bad_href(self):
+ def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
- imageRef = 'http://localhost/v1.1/images/asdf'
- flavorRef = 'http://localhost/v1.1/flavors/3'
+ image_ref = 'http://localhost/v1.1/images/asdf'
+ flavor_ref = 'http://localhost/v1.1/flavors/3'
body = dict(server=dict(
- name='server_test', imageRef=imageRef, flavorRef=flavorRef,
+ name='server_test', imageRef=image_ref, flavorRef=flavor_ref,
metadata={'hello': 'world', 'open': 'stack'},
personality={}))
req = webob.Request.blank('/v1.1/servers')
@@ -625,17 +625,17 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_create_instance_v11_local_href(self):
+ def test_create_instance_v1_1_local_href(self):
self._setup_for_create_instance()
- imageRef = 'http://localhost/v1.1/images/2'
- imageRefLocal = '2'
- flavorRef = 'http://localhost/v1.1/flavors/3'
+ image_ref = 'http://localhost/v1.1/images/2'
+ image_ref_local = '2'
+ flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': imageRefLocal,
- 'flavorRef': flavorRef,
+ 'imageRef': image_ref_local,
+ 'flavorRef': flavor_ref,
},
}
@@ -648,11 +648,11 @@ class ServersTest(test.TestCase):
server = json.loads(res.body)['server']
self.assertEqual(1, server['id'])
- self.assertEqual(flavorRef, server['flavorRef'])
- self.assertEqual(imageRef, server['imageRef'])
+ self.assertEqual(flavor_ref, server['flavorRef'])
+ self.assertEqual(image_ref, server['imageRef'])
self.assertEqual(res.status_int, 200)
- def test_create_instance_with_admin_pass_v10(self):
+ def test_create_instance_with_admin_pass_v1_0(self):
self._setup_for_create_instance()
body = {
@@ -673,16 +673,16 @@ class ServersTest(test.TestCase):
self.assertNotEqual(res['server']['adminPass'],
body['server']['adminPass'])
- def test_create_instance_with_admin_pass_v11(self):
+ def test_create_instance_with_admin_pass_v1_1(self):
self._setup_for_create_instance()
- imageRef = 'http://localhost/v1.1/images/2'
- flavorRef = 'http://localhost/v1.1/flavors/3'
+ image_ref = 'http://localhost/v1.1/images/2'
+ flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': imageRef,
- 'flavorRef': flavorRef,
+ 'imageRef': image_ref,
+ 'flavorRef': flavor_ref,
'adminPass': 'testpass',
},
}
@@ -695,16 +695,16 @@ class ServersTest(test.TestCase):
server = json.loads(res.body)['server']
self.assertEqual(server['adminPass'], body['server']['adminPass'])
- def test_create_instance_with_empty_admin_pass_v11(self):
+ def test_create_instance_with_empty_admin_pass_v1_1(self):
self._setup_for_create_instance()
- imageRef = 'http://localhost/v1.1/images/2'
- flavorRef = 'http://localhost/v1.1/flavors/3'
+ image_ref = 'http://localhost/v1.1/images/2'
+ flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': imageRef,
- 'flavorRef': flavorRef,
+ 'imageRef': image_ref,
+ 'flavorRef': flavor_ref,
'adminPass': '',
},
}
@@ -758,7 +758,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_update_server_v10(self):
+ def test_update_server_v1_0(self):
inst_dict = dict(name='server_test', adminPass='bacon')
self.body = json.dumps(dict(server=inst_dict))
@@ -781,7 +781,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 204)
- def test_update_server_adminPass_ignored_v11(self):
+ def test_update_server_adminPass_ignored_v1_1(self):
inst_dict = dict(name='server_test', adminPass='bacon')
self.body = json.dumps(dict(server=inst_dict))
@@ -822,7 +822,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 501)
- def test_server_backup_schedule_deprecated_v11(self):
+ def test_server_backup_schedule_deprecated_v1_1(self):
req = webob.Request.blank('/v1.1/servers/1/backup_schedule')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
@@ -1113,7 +1113,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_server_rebuild_accepted_minimum_v11(self):
+ def test_server_rebuild_accepted_minimum_v1_1(self):
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
@@ -1128,7 +1128,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
- def test_server_rebuild_rejected_when_building_v11(self):
+ def test_server_rebuild_rejected_when_building_v1_1(self):
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
@@ -1147,7 +1147,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 409)
- def test_server_rebuild_accepted_with_metadata_v11(self):
+ def test_server_rebuild_accepted_with_metadata_v1_1(self):
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
@@ -1165,7 +1165,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
- def test_server_rebuild_accepted_with_bad_metadata_v11(self):
+ def test_server_rebuild_accepted_with_bad_metadata_v1_1(self):
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
@@ -1181,7 +1181,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_server_rebuild_bad_entity_v11(self):
+ def test_server_rebuild_bad_entity_v1_1(self):
body = {
"rebuild": {
"imageId": 2,
@@ -1196,7 +1196,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_server_rebuild_bad_personality_v11(self):
+ def test_server_rebuild_bad_personality_v1_1(self):
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
@@ -1215,7 +1215,7 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
- def test_server_rebuild_personality_v11(self):
+ def test_server_rebuild_personality_v1_1(self):
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
@@ -1654,6 +1654,19 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
request = self.deserializer.deserialize(serial_request)
self.assertEqual(request, expected)
+ def test_request_xmlser_with_flavor_image_ref(self):
+ serial_request = """
+ <server xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test"
+ imageRef="http://localhost:8774/v1.1/images/1"
+ flavorRef="http://localhost:8774/v1.1/flavors/1">
+ </server>"""
+ request = self.deserializer.deserialize(serial_request)
+ self.assertEquals(request["server"]["flavorRef"],
+ "http://localhost:8774/v1.1/flavors/1")
+ self.assertEquals(request["server"]["imageRef"],
+ "http://localhost:8774/v1.1/images/1")
+
class TestServerInstanceCreation(test.TestCase):
diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py
index 58d251b1e..8bdea359a 100644
--- a/nova/tests/db/fakes.py
+++ b/nova/tests/db/fakes.py
@@ -124,7 +124,6 @@ def stub_out_db_instance_api(stubs, injected=True):
return FakeModel(vlan_network_fields)
else:
return FakeModel(flat_network_fields)
- return FakeModel(network_fields)
def fake_network_get_all_by_instance(context, instance_id):
# Even instance numbers are on vlan networks
diff --git a/nova/tests/network/base.py b/nova/tests/network/base.py
index 988a1de72..b06271c99 100644
--- a/nova/tests/network/base.py
+++ b/nova/tests/network/base.py
@@ -25,6 +25,7 @@ from nova import context
from nova import db
from nova import exception
from nova import flags
+from nova import ipv6
from nova import log as logging
from nova import test
from nova import utils
@@ -117,15 +118,15 @@ class NetworkTestCase(test.TestCase):
context.get_admin_context(),
instance_ref['id'])
self.assertEqual(instance_ref['mac_address'],
- utils.to_mac(address_v6))
+ ipv6.to_mac(address_v6))
instance_ref2 = db.fixed_ip_get_instance_v6(
context.get_admin_context(),
address_v6)
self.assertEqual(instance_ref['id'], instance_ref2['id'])
self.assertEqual(address_v6,
- utils.to_global_ipv6(
- network_ref['cidr_v6'],
- instance_ref['mac_address']))
+ ipv6.to_global(network_ref['cidr_v6'],
+ instance_ref['mac_address'],
+ 'test'))
self._deallocate_address(0, address)
db.instance_destroy(context.get_admin_context(),
instance_ref['id'])
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index f271c03f2..c8559615a 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -279,6 +279,26 @@ class CloudTestCase(test.TestCase):
user_group=['all'])
self.assertEqual(True, result['is_public'])
+ def test_deregister_image(self):
+ deregister_image = self.cloud.deregister_image
+
+ def fake_delete(self, context, id):
+ return None
+
+ self.stubs.Set(local.LocalImageService, 'delete', fake_delete)
+ # valid image
+ result = deregister_image(self.context, 'ami-00000001')
+ self.assertEqual(result['imageId'], 'ami-00000001')
+ # invalid image
+ self.stubs.UnsetAll()
+
+ def fake_detail_empty(self, context):
+ return []
+
+ self.stubs.Set(local.LocalImageService, 'detail', fake_detail_empty)
+ self.assertRaises(exception.ImageNotFound, deregister_image,
+ self.context, 'ami-bad001')
+
def test_console_output(self):
instance_type = FLAGS.default_instance_type
max_count = 1
@@ -338,41 +358,6 @@ class CloudTestCase(test.TestCase):
self._create_key('test')
self.cloud.delete_key_pair(self.context, 'test')
- def test_run_instances(self):
- if FLAGS.connection_type == 'fake':
- LOG.debug(_("Can't test instances without a real virtual env."))
- return
- image_id = FLAGS.default_image
- instance_type = FLAGS.default_instance_type
- max_count = 1
- kwargs = {'image_id': image_id,
- 'instance_type': instance_type,
- 'max_count': max_count}
- rv = self.cloud.run_instances(self.context, **kwargs)
- # TODO: check for proper response
- instance_id = rv['reservationSet'][0].keys()[0]
- instance = rv['reservationSet'][0][instance_id][0]
- LOG.debug(_("Need to watch instance %s until it's running..."),
- instance['instance_id'])
- while True:
- greenthread.sleep(1)
- info = self.cloud._get_instance(instance['instance_id'])
- LOG.debug(info['state'])
- if info['state'] == power_state.RUNNING:
- break
- self.assert_(rv)
-
- if FLAGS.connection_type != 'fake':
- time.sleep(45) # Should use boto for polling here
- for reservations in rv['reservationSet']:
- # for res_id in reservations.keys():
- # LOG.debug(reservations[res_id])
- # for instance in reservations[res_id]:
- for instance in reservations[reservations.keys()[0]]:
- instance_id = instance['instance_id']
- LOG.debug(_("Terminating instance %s"), instance_id)
- rv = self.compute.terminate_instance(instance_id)
-
def test_terminate_instances(self):
inst1 = db.instance_create(self.context, {'reservation_id': 'a',
'image_id': 1,
diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py
new file mode 100644
index 000000000..11dc2ec98
--- /dev/null
+++ b/nova/tests/test_ipv6.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 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.
+
+"""Test suite for IPv6."""
+
+from nova import flags
+from nova import ipv6
+from nova import log as logging
+from nova import test
+
+LOG = logging.getLogger('nova.tests.test_ipv6')
+
+FLAGS = flags.FLAGS
+
+import sys
+
+
+class IPv6RFC2462TestCase(test.TestCase):
+ """Unit tests for IPv6 rfc2462 backend operations."""
+ def setUp(self):
+ super(IPv6RFC2462TestCase, self).setUp()
+ self.flags(ipv6_backend='rfc2462')
+ ipv6.reset_backend()
+
+ def test_to_global(self):
+ addr = ipv6.to_global('2001:db8::', '02:16:3e:33:44:55', 'test')
+ self.assertEquals(addr, '2001:db8::16:3eff:fe33:4455')
+
+ def test_to_mac(self):
+ mac = ipv6.to_mac('2001:db8::216:3eff:fe33:4455')
+ self.assertEquals(mac, '00:16:3e:33:44:55')
+
+
+class IPv6AccountIdentiferTestCase(test.TestCase):
+ """Unit tests for IPv6 account_identifier backend operations."""
+ def setUp(self):
+ super(IPv6AccountIdentiferTestCase, self).setUp()
+ self.flags(ipv6_backend='account_identifier')
+ ipv6.reset_backend()
+
+ def test_to_global(self):
+ addr = ipv6.to_global('2001:db8::', '02:16:3e:33:44:55', 'test')
+ self.assertEquals(addr, '2001:db8::a94a:8fe5:ff33:4455')
+
+ def test_to_mac(self):
+ mac = ipv6.to_mac('2001:db8::a94a:8fe5:ff33:4455')
+ self.assertEquals(mac, '02:16:3e:33:44:55')
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index 39a123158..7ace2ad7d 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -96,12 +96,11 @@ class QuotaTestCase(test.TestCase):
num_instances = quota.allowed_instances(self.context, 100,
self._get_instance_type('m1.small'))
self.assertEqual(num_instances, 2)
- db.quota_create(self.context, {'project_id': self.project.id,
- 'instances': 10})
+ db.quota_create(self.context, self.project.id, 'instances', 10)
num_instances = quota.allowed_instances(self.context, 100,
self._get_instance_type('m1.small'))
self.assertEqual(num_instances, 4)
- db.quota_update(self.context, self.project.id, {'cores': 100})
+ db.quota_create(self.context, self.project.id, 'cores', 100)
num_instances = quota.allowed_instances(self.context, 100,
self._get_instance_type('m1.small'))
self.assertEqual(num_instances, 10)
@@ -111,13 +110,85 @@ class QuotaTestCase(test.TestCase):
num_metadata_items = quota.allowed_metadata_items(self.context,
too_many_items)
self.assertEqual(num_metadata_items, FLAGS.quota_metadata_items)
- db.quota_update(self.context, self.project.id, {'metadata_items': 5})
+ db.quota_create(self.context, self.project.id, 'metadata_items', 5)
num_metadata_items = quota.allowed_metadata_items(self.context,
too_many_items)
self.assertEqual(num_metadata_items, 5)
# Cleanup
- db.quota_destroy(self.context, self.project.id)
+ db.quota_destroy_all_by_project(self.context, self.project.id)
+
+ def test_unlimited_instances(self):
+ FLAGS.quota_instances = 2
+ FLAGS.quota_cores = 1000
+ instance_type = self._get_instance_type('m1.small')
+ num_instances = quota.allowed_instances(self.context, 100,
+ instance_type)
+ self.assertEqual(num_instances, 2)
+ db.quota_create(self.context, self.project.id, 'instances', None)
+ num_instances = quota.allowed_instances(self.context, 100,
+ instance_type)
+ self.assertEqual(num_instances, 100)
+ num_instances = quota.allowed_instances(self.context, 101,
+ instance_type)
+ self.assertEqual(num_instances, 101)
+
+ def test_unlimited_cores(self):
+ FLAGS.quota_instances = 1000
+ FLAGS.quota_cores = 2
+ instance_type = self._get_instance_type('m1.small')
+ num_instances = quota.allowed_instances(self.context, 100,
+ instance_type)
+ self.assertEqual(num_instances, 2)
+ db.quota_create(self.context, self.project.id, 'cores', None)
+ num_instances = quota.allowed_instances(self.context, 100,
+ instance_type)
+ self.assertEqual(num_instances, 100)
+ num_instances = quota.allowed_instances(self.context, 101,
+ instance_type)
+ self.assertEqual(num_instances, 101)
+
+ def test_unlimited_volumes(self):
+ FLAGS.quota_volumes = 10
+ FLAGS.quota_gigabytes = 1000
+ volumes = quota.allowed_volumes(self.context, 100, 1)
+ self.assertEqual(volumes, 10)
+ db.quota_create(self.context, self.project.id, 'volumes', None)
+ volumes = quota.allowed_volumes(self.context, 100, 1)
+ self.assertEqual(volumes, 100)
+ volumes = quota.allowed_volumes(self.context, 101, 1)
+ self.assertEqual(volumes, 101)
+
+ def test_unlimited_gigabytes(self):
+ FLAGS.quota_volumes = 1000
+ FLAGS.quota_gigabytes = 10
+ volumes = quota.allowed_volumes(self.context, 100, 1)
+ self.assertEqual(volumes, 10)
+ db.quota_create(self.context, self.project.id, 'gigabytes', None)
+ volumes = quota.allowed_volumes(self.context, 100, 1)
+ self.assertEqual(volumes, 100)
+ volumes = quota.allowed_volumes(self.context, 101, 1)
+ self.assertEqual(volumes, 101)
+
+ def test_unlimited_floating_ips(self):
+ FLAGS.quota_floating_ips = 10
+ floating_ips = quota.allowed_floating_ips(self.context, 100)
+ self.assertEqual(floating_ips, 10)
+ db.quota_create(self.context, self.project.id, 'floating_ips', None)
+ floating_ips = quota.allowed_floating_ips(self.context, 100)
+ self.assertEqual(floating_ips, 100)
+ floating_ips = quota.allowed_floating_ips(self.context, 101)
+ self.assertEqual(floating_ips, 101)
+
+ def test_unlimited_metadata_items(self):
+ FLAGS.quota_metadata_items = 10
+ items = quota.allowed_metadata_items(self.context, 100)
+ self.assertEqual(items, 10)
+ db.quota_create(self.context, self.project.id, 'metadata_items', None)
+ items = quota.allowed_metadata_items(self.context, 100)
+ self.assertEqual(items, 100)
+ items = quota.allowed_metadata_items(self.context, 101)
+ self.assertEqual(items, 101)
def test_too_many_instances(self):
instance_ids = []
diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
index 1311ba361..d743f94f7 100644
--- a/nova/tests/test_virt.py
+++ b/nova/tests/test_virt.py
@@ -642,7 +642,7 @@ class LibvirtConnTestCase(test.TestCase):
try:
conn.spawn(instance, network_info)
except Exception, e:
- count = (0 <= e.message.find('Unexpected method call'))
+ count = (0 <= str(e.message).find('Unexpected method call'))
self.assertTrue(count)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 6072f5455..be1e35697 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -16,6 +16,7 @@
"""Test suite for XenAPI."""
+import eventlet
import functools
import json
import os
@@ -198,6 +199,28 @@ class XenAPIVMTestCase(test.TestCase):
self.context = context.RequestContext('fake', 'fake', False)
self.conn = xenapi_conn.get_connection(False)
+ def test_parallel_builds(self):
+ stubs.stubout_loopingcall_delay(self.stubs)
+
+ def _do_build(id, proj, user, *args):
+ values = {
+ 'id': id,
+ 'project_id': proj,
+ 'user_id': user,
+ 'image_id': 1,
+ 'kernel_id': 2,
+ 'ramdisk_id': 3,
+ 'instance_type_id': '3', # m1.large
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'os_type': 'linux'}
+ instance = db.instance_create(self.context, values)
+ self.conn.spawn(instance)
+
+ gt1 = eventlet.spawn(_do_build, 1, self.project.id, self.user.id)
+ gt2 = eventlet.spawn(_do_build, 2, self.project.id, self.user.id)
+ gt1.wait()
+ gt2.wait()
+
def test_list_instances_0(self):
instances = self.conn.list_instances()
self.assertEquals(instances, [])
diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py
index 205f6c902..4833ccb07 100644
--- a/nova/tests/xenapi/stubs.py
+++ b/nova/tests/xenapi/stubs.py
@@ -16,6 +16,7 @@
"""Stubouts, mocks and fixtures for the test suite"""
+import eventlet
from nova.virt import xenapi_conn
from nova.virt.xenapi import fake
from nova.virt.xenapi import volume_utils
@@ -28,29 +29,6 @@ def stubout_instance_snapshot(stubs):
@classmethod
def fake_fetch_image(cls, session, instance_id, image, user, project,
type):
- # Stubout wait_for_task
- def fake_wait_for_task(self, task, id):
- class FakeEvent:
-
- def send(self, value):
- self.rv = value
-
- def wait(self):
- return self.rv
-
- done = FakeEvent()
- self._poll_task(id, task, done)
- rv = done.wait()
- return rv
-
- def fake_loop(self):
- pass
-
- stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task',
- fake_wait_for_task)
-
- stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop)
-
from nova.virt.xenapi.fake import create_vdi
name_label = "instance-%s" % instance_id
#TODO: create fake SR record
@@ -63,11 +41,6 @@ def stubout_instance_snapshot(stubs):
stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image)
- def fake_parse_xmlrpc_value(val):
- return val
-
- stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value)
-
def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,
original_parent_uuid):
from nova.virt.xenapi.fake import create_vdi
@@ -144,6 +117,16 @@ def stubout_loopingcall_start(stubs):
stubs.Set(utils.LoopingCall, 'start', fake_start)
+def stubout_loopingcall_delay(stubs):
+ def fake_start(self, interval, now=True):
+ self._running = True
+ eventlet.sleep(1)
+ self.f(*self.args, **self.kw)
+ # This would fail before parallel xenapi calls were fixed
+ assert self._running == False
+ stubs.Set(utils.LoopingCall, 'start', fake_start)
+
+
class FakeSessionForVMTests(fake.SessionBase):
""" Stubs out a XenAPISession for VM tests """
def __init__(self, uri):
diff --git a/nova/utils.py b/nova/utils.py
index b55e83e5a..361fc9873 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -306,26 +306,6 @@ def get_my_linklocal(interface):
" :%(ex)s") % locals())
-def to_global_ipv6(prefix, mac):
- try:
- mac64 = netaddr.EUI(mac).eui64().words
- int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
- mac64_addr = netaddr.IPAddress(int_addr)
- maskIP = netaddr.IPNetwork(prefix).ip
- return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
- format()
- except TypeError:
- raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
-
-
-def to_mac(ipv6_address):
- address = netaddr.IPAddress(ipv6_address)
- mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff')
- mask2 = netaddr.IPAddress('::0200:0:0:0')
- mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
- return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]])
-
-
def utcnow():
"""Overridable version of datetime.datetime.utcnow."""
if utcnow.override_time:
@@ -462,6 +442,8 @@ class LoopingCall(object):
try:
while self._running:
self.f(*self.args, **self.kw)
+ if not self._running:
+ break
greenthread.sleep(interval)
except LoopingCallDone, e:
self.stop()
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 555e44ce2..6ee23d1df 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -57,6 +57,7 @@ from nova import context
from nova import db
from nova import exception
from nova import flags
+from nova import ipv6
from nova import log as logging
from nova import utils
from nova import vnc
@@ -184,8 +185,9 @@ def _get_network_info(instance):
def ip6_dict():
prefix = network['cidr_v6']
mac = instance['mac_address']
+ project_id = instance['project_id']
return {
- 'ip': utils.to_global_ipv6(prefix, mac),
+ 'ip': ipv6.to_global(prefix, mac, project_id),
'netmask': network['netmask_v6'],
'enabled': '1'}
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index fe9a74dd6..13d7d215b 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -28,12 +28,13 @@ import subprocess
import tempfile
import uuid
-from nova import db
from nova import context
-from nova import log as logging
+from nova import db
from nova import exception
-from nova import utils
from nova import flags
+from nova import ipv6
+from nova import log as logging
+from nova import utils
from nova.auth.manager import AuthManager
from nova.compute import power_state
@@ -808,8 +809,9 @@ class VMOps(object):
def ip6_dict():
return {
- "ip": utils.to_global_ipv6(network['cidr_v6'],
- instance['mac_address']),
+ "ip": ipv6.to_global(network['cidr_v6'],
+ instance['mac_address'],
+ instance['project_id']),
"netmask": network['netmask_v6'],
"enabled": "1"}
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 8e9085277..eb572f295 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -347,7 +347,6 @@ class XenAPISession(object):
"(is the Dom0 disk full?)"))
with timeout.Timeout(FLAGS.xenapi_login_timeout, exception):
self._session.login_with_password(user, pw)
- self.loop = None
def get_imported_xenapi(self):
"""Stubout point. This can be replaced with a mock xenapi module."""
@@ -384,57 +383,52 @@ class XenAPISession(object):
def wait_for_task(self, task, id=None):
"""Return the result of the given task. The task is polled
- until it completes. Not re-entrant."""
+ until it completes."""
done = event.Event()
- self.loop = utils.LoopingCall(self._poll_task, id, task, done)
- self.loop.start(FLAGS.xenapi_task_poll_interval, now=True)
- rv = done.wait()
- self.loop.stop()
- return rv
-
- def _stop_loop(self):
- """Stop polling for task to finish."""
- #NOTE(sandy-walsh) Had to break this call out to support unit tests.
- if self.loop:
- self.loop.stop()
+ loop = utils.LoopingCall(f=None)
+
+ def _poll_task():
+ """Poll the given XenAPI task, and return the result if the
+ action was completed successfully or not.
+ """
+ try:
+ name = self._session.xenapi.task.get_name_label(task)
+ status = self._session.xenapi.task.get_status(task)
+ if id:
+ action = dict(
+ instance_id=int(id),
+ action=name[0:255], # Ensure action is never > 255
+ error=None)
+ if status == "pending":
+ return
+ elif status == "success":
+ result = self._session.xenapi.task.get_result(task)
+ LOG.info(_("Task [%(name)s] %(task)s status:"
+ " success %(result)s") % locals())
+ done.send(_parse_xmlrpc_value(result))
+ else:
+ error_info = self._session.xenapi.task.get_error_info(task)
+ action["error"] = str(error_info)
+ LOG.warn(_("Task [%(name)s] %(task)s status:"
+ " %(status)s %(error_info)s") % locals())
+ done.send_exception(self.XenAPI.Failure(error_info))
+
+ if id:
+ db.instance_action_create(context.get_admin_context(),
+ action)
+ except self.XenAPI.Failure, exc:
+ LOG.warn(exc)
+ done.send_exception(*sys.exc_info())
+ loop.stop()
+
+ loop.f = _poll_task
+ loop.start(FLAGS.xenapi_task_poll_interval, now=True)
+ return done.wait()
def _create_session(self, url):
"""Stubout point. This can be replaced with a mock session."""
return self.XenAPI.Session(url)
- def _poll_task(self, id, task, done):
- """Poll the given XenAPI task, and fire the given action if we
- get a result.
- """
- try:
- name = self._session.xenapi.task.get_name_label(task)
- status = self._session.xenapi.task.get_status(task)
- if id:
- action = dict(
- instance_id=int(id),
- action=name[0:255], # Ensure action is never > 255
- error=None)
- if status == "pending":
- return
- elif status == "success":
- result = self._session.xenapi.task.get_result(task)
- LOG.info(_("Task [%(name)s] %(task)s status:"
- " success %(result)s") % locals())
- done.send(_parse_xmlrpc_value(result))
- else:
- error_info = self._session.xenapi.task.get_error_info(task)
- action["error"] = str(error_info)
- LOG.warn(_("Task [%(name)s] %(task)s status:"
- " %(status)s %(error_info)s") % locals())
- done.send_exception(self.XenAPI.Failure(error_info))
-
- if id:
- db.instance_action_create(context.get_admin_context(), action)
- except self.XenAPI.Failure, exc:
- LOG.warn(exc)
- done.send_exception(*sys.exc_info())
- self._stop_loop()
-
def _unwrap_plugin_exceptions(self, func, *args, **kwargs):
"""Parse exception details"""
try: