Merge lp:~justin-fathomdb/nova/justinsb-metadata3 into lp:~hudson-openstack/nova/trunk

Proposed by justinsb
Status: Superseded
Proposed branch: lp:~justin-fathomdb/nova/justinsb-metadata3
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 426 lines (+199/-12)
10 files modified
nova/api/ec2/cloud.py (+5/-1)
nova/api/openstack/servers.py (+22/-5)
nova/compute/api.py (+26/-1)
nova/db/sqlalchemy/api.py (+2/-0)
nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py (+78/-0)
nova/db/sqlalchemy/models.py (+17/-1)
nova/quota.py (+13/-1)
nova/tests/api/openstack/test_servers.py (+9/-2)
nova/tests/test_quota.py (+24/-0)
run_tests.sh (+3/-1)
To merge this branch: bzr merge lp:~justin-fathomdb/nova/justinsb-metadata3
Reviewer Review Type Date Requested Status
Eric Day (community) Approve
Dan Prince (community) Approve
Jay Pipes (community) Approve
Review via email: mp+50249@code.launchpad.net

This proposal supersedes a proposal from 2011-02-17.

This proposal has been superseded by a proposal from 2011-02-21.

Description of the change

Initial support for per-instance metadata, though the OpenStack API. Key/value pairs can be specified at instance creation time and are returned in the details view. Support limits based on quota system.

To post a comment you must log in.
Revision history for this message
Dan Prince (dan-prince) wrote : Posted in a previous version of this proposal

Justin,

Does this branch work with Glance? I'm pretty sure Glance stores kernel ID as image['properties']['kernel_id']. Additionally ramdisk ID is stored as image['properties']['ramdisk_id'].

Perhaps you were developing/testing against S3ImageStore?

Dan

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

To answer Dan's question, no, this patch won't work if FLAGS.image_service == 'glance'. But, that's not necessarily Justin's fault of course, and the OpenStack servers.py controller shouldn't be relying on the Glance image service being set, either...

Devin and Dan have been working on fixing the inconsistencies in the keys returned from the S3ImageService and the GlanceImageService when it comes to the kernel_id (kernelId) and ramdisk_id (ramdiskId) keys. Please see this bug and patch here for a few more details: https://bugs.launchpad.net/nova/+bug/712782

As for the specifics of this patch, Justin, I think it would be clearer if you renamed the Metadata model to InstanceProperty and the table name from metadata to instance_properties, to make it clear what the metadata properties refer to. Since there is already the concept of Metadata in SQLAlchemy, it would make reading the code a tad easier.

Cheers!
jay

Revision history for this message
justinsb (justin-fathomdb) wrote : Posted in a previous version of this proposal

I think I'll probably revert the lines that touched the kernel_id & ramdisk_id - I had to put them in there to get this to work, but it probably doesn't really belong in this patch.

I agree that 'metadata', 'key' and 'value' are potentially problematic names. Two issues though: First, there will be metadata for more than just instances - I can either have one table 'item_properties', or I can go with InstanceProperties, VolumeProperties etc and have multiple tables. And second, "properties" may be a bit overloaded already (e.g. the images already has 'properties' in Dan's code snippet!) Although I'd like the special flags (kernel_id, ramdisk_id, zone, image_type, volume_size etc) to just be treated as well-known metadata, I don't see this happening any time soon, so I don't think we can use 'properties'. Maybe 'Tags'? What do you think Jay?

Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

> I think I'll probably revert the lines that touched the kernel_id & ramdisk_id
> - I had to put them in there to get this to work, but it probably doesn't
> really belong in this patch.

k, agreed.

> I agree that 'metadata', 'key' and 'value' are potentially problematic names.
> Two issues though: First, there will be metadata for more than just instances
> - I can either have one table 'item_properties', or I can go with
> InstanceProperties, VolumeProperties etc and have multiple tables. And
> second, "properties" may be a bit overloaded already (e.g. the images already
> has 'properties' in Dan's code snippet!)

Heh, yes it does, because Glance's registry database has a table image_properties, with a model, yep, you guessed it, ImageProperty :)

So, I think that InstanceProperties is perfectly fine in this case :)

> Although I'd like the special flags
> (kernel_id, ramdisk_id, zone, image_type, volume_size etc) to just be treated
> as well-known metadata, I don't see this happening any time soon, so I don't
> think we can use 'properties'. Maybe 'Tags'? What do you think Jay?

This should be a mailing list question, I believe. If it's an attribute that *every* instance has, or possibly that *most* instances have, I think it should be in the Instance model directly, but this is just my opinion. Best to ask the ML I think...

-jay

Revision history for this message
Dan Prince (dan-prince) wrote : Posted in a previous version of this proposal

This branch currently doesn't appear to restrict the number of metadata items per instances(server) to 5.

According to the Cloud Servers API docs:

"The maximum size of the metadata key and value is each 255 bytes and the maximum number of key-value pairs that can be supplied per server is 5."

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

<diplomacy-hat>
OpenStack != Cloud Servers, of course, but I would slightly modify what Dan said to:

Could we add a FLAG option that would allow the deployer to set a limit to the number of metadata items per object? ;)
</diplomacy-hat>

Revision history for this message
Devin Carlen (devcamcar) wrote : Posted in a previous version of this proposal

Agree, make the number of metadata items configurable

Revision history for this message
Dan Prince (dan-prince) wrote : Posted in a previous version of this proposal

Absolutely. Making this a configuration flag would be great.

I might suggest we not make the FLAG option default to being 'unlimited'. The ability to specify an unlimited number of items seems like kind of a security concern.

10 items maybe?

Revision history for this message
justinsb (justin-fathomdb) wrote : Posted in a previous version of this proposal

Fixed per suggestions. I'm a bit uneasy about calling the metadata items properties, as I don't think it's particularly clear. Support a limit on the number of items, set through the quota system. Check key/value sizes vs. max length 255.

Revision history for this message
Eric Day (eday) wrote : Posted in a previous version of this proposal

I think we need to decide on using a single term, either 'metadata' or 'properties'. Using one name in the API and another in the DB layer is confusing. Also, it's all metadata (or properties), and this should not be confused with the format (list of key/values pairs or dict of key => value mappings). Using different names for different formats is also going to confuse folks.

Line 8: Nothing in nova/api should be importing db directly, especially implementation specific db modules. We need to encapsulate anything needing this with a proper nova.compute API and then a nova.db API call.

review: Needs Fixing
Revision history for this message
justinsb (justin-fathomdb) wrote : Posted in a previous version of this proposal

Eric: The suggestion from the mailing list was 'properties' because that's what Glance chose. The API uses 'metadata' (which I personally prefer), but was potentially confusing because SQLAlchemy has a Metadata object also (though it didn't cause any code problems). Are you voting that I change the API to 'properties' or that I go back to 'metadata'? If the former, am I allowed to just change the API?

Revision history for this message
Vish Ishaya (vishvananda) wrote : Posted in a previous version of this proposal

Jorge was saying in the api thread that he didn't think metadata should be used for tagging, it is supposed to be client only metadata. We should probably create something new ("tags? properties?"), although it seems fine to use metadata to test it out initially.

Vish

On Feb 16, 2011, at 1:18 PM, justinsb wrote:

> Eric: The suggestion from the mailing list was 'properties' because that's what Glance chose. The API uses 'metadata' (which I personally prefer), but was potentially confusing because SQLAlchemy has a Metadata object also (though it didn't cause any code problems). Are you voting that I change the API to 'properties' or that I go back to 'metadata'? If the former, am I allowed to just change the API?
> --
> https://code.launchpad.net/~justin-fathomdb/nova/justinsb-metadata/+merge/49902
> You are subscribed to branch lp:nova.

Revision history for this message
Eric Day (eday) wrote : Posted in a previous version of this proposal

I have no opinion on what we call it, I just prefer it be the same end-to-end. I defer to others to decide which rename is possible, and if there are still multiple choices, which is most appropriate.

Revision history for this message
justinsb (justin-fathomdb) wrote : Posted in a previous version of this proposal

Vish: I don't necessarily agree with Jorge, but this patch is just about implementing Metadata as defined in the blueprint/CloudServers API. Whether we create a second metadata collection or share this one is a matter for another day/another patch :-)

Revision history for this message
Dan Prince (dan-prince) wrote : Posted in a previous version of this proposal

Good work.

How hard would it be to add a test case that tested for the upper limit of the metadata quota?

699. By justinsb

Merged with trunk

Revision history for this message
justinsb (justin-fathomdb) wrote :

Went with InstanceMetadata and 'metadata', for consistency with the API, as agreed on IRC.
Dan: Added the unit test for the max metadata item count.
Eric: Removed DB model class reference from API. Using a simple dictionary instead

Ready for merge, I hope!

Revision history for this message
Jay Pipes (jaypipes) wrote :

Thanks for your patience and updates, Justin. Looks good to me now.

review: Approve
Revision history for this message
Dan Prince (dan-prince) wrote :

Looks good to me. Thanks for adding the tests.

review: Approve
Revision history for this message
justinsb (justin-fathomdb) wrote :

Jay, Dan: Thanks + no worries about the debate on the naming. I think the patch is much better for it :-)

700. By justinsb

Rename migration 004 => 005

Revision history for this message
Eric Day (eday) wrote :

lgtm

review: Approve
Revision history for this message
Todd Willey (xtoddx) wrote : Posted in a previous version of this proposal

lgtm

review: Approve
701. By justinsb

Merged with trunk

702. By justinsb

Changed unit test to refer to compute API, per Todd's suggestion.
Avoids needing to extend our implementation of the EC2 API.

703. By justinsb

Merged with trunk

704. By justinsb

Merged with trunk (a criss-cross merge...)

Unmerged revisions

704. By justinsb

Merged with trunk (a criss-cross merge...)

703. By justinsb

Merged with trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/ec2/cloud.py'
2--- nova/api/ec2/cloud.py 2011-02-19 07:15:42 +0000
3+++ nova/api/ec2/cloud.py 2011-02-21 22:49:18 +0000
4@@ -783,6 +783,9 @@
5
6 def run_instances(self, context, **kwargs):
7 max_count = int(kwargs.get('max_count', 1))
8+ # NOTE(justinsb): the EC2 API doesn't support metadata here, but this
9+ # is needed for the unit tests. Maybe the unit tests shouldn't be
10+ # calling the EC2 code
11 instances = self.compute_api.create(context,
12 instance_type=instance_types.get_by_type(
13 kwargs.get('instance_type', None)),
14@@ -797,7 +800,8 @@
15 user_data=kwargs.get('user_data'),
16 security_group=kwargs.get('security_group'),
17 availability_zone=kwargs.get('placement', {}).get(
18- 'AvailabilityZone'))
19+ 'AvailabilityZone'),
20+ metadata=kwargs.get('metadata', []))
21 return self._format_run_instances(context,
22 instances[0]['reservation_id'])
23
24
25=== modified file 'nova/api/openstack/servers.py'
26--- nova/api/openstack/servers.py 2011-02-18 22:36:01 +0000
27+++ nova/api/openstack/servers.py 2011-02-21 22:49:18 +0000
28@@ -78,9 +78,14 @@
29 except KeyError:
30 LOG.debug(_("Failed to read public ip(s)"))
31
32- inst_dict['metadata'] = {}
33 inst_dict['hostId'] = ''
34
35+ # Return the metadata as a dictionary
36+ metadata = {}
37+ for item in inst['metadata']:
38+ metadata[item['key']] = item['value']
39+ inst_dict['metadata'] = metadata
40+
41 return dict(server=inst_dict)
42
43
44@@ -162,18 +167,29 @@
45 if not env:
46 return faults.Fault(exc.HTTPUnprocessableEntity())
47
48- key_pairs = auth_manager.AuthManager.get_key_pairs(
49- req.environ['nova.context'])
50+ context = req.environ['nova.context']
51+ key_pairs = auth_manager.AuthManager.get_key_pairs(context)
52 if not key_pairs:
53 raise exception.NotFound(_("No keypairs defined"))
54 key_pair = key_pairs[0]
55
56 image_id = common.get_image_id_from_image_hash(self._image_service,
57- req.environ['nova.context'], env['server']['imageId'])
58+ context, env['server']['imageId'])
59 kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
60 req, image_id)
61+
62+ # Metadata is a list, not a Dictionary, because we allow duplicate keys
63+ # (even though JSON can't encode this)
64+ # In future, we may not allow duplicate keys.
65+ # However, the CloudServers API is not definitive on this front,
66+ # and we want to be compatible.
67+ metadata = []
68+ if env['server']['metadata']:
69+ for k, v in env['server']['metadata'].items():
70+ metadata.append({'key': k, 'value': v})
71+
72 instances = self.compute_api.create(
73- req.environ['nova.context'],
74+ context,
75 instance_types.get_by_flavor_id(env['server']['flavorId']),
76 image_id,
77 kernel_id=kernel_id,
78@@ -182,6 +198,7 @@
79 display_description=env['server']['name'],
80 key_name=key_pair['name'],
81 key_data=key_pair['public_key'],
82+ metadata=metadata,
83 onset_files=env.get('onset_files', []))
84 return _translate_keys(instances[0])
85
86
87=== modified file 'nova/compute/api.py'
88--- nova/compute/api.py 2011-02-19 05:00:58 +0000
89+++ nova/compute/api.py 2011-02-21 22:49:18 +0000
90@@ -85,7 +85,7 @@
91 min_count=1, max_count=1,
92 display_name='', display_description='',
93 key_name=None, key_data=None, security_group='default',
94- availability_zone=None, user_data=None,
95+ availability_zone=None, user_data=None, metadata=[],
96 onset_files=None):
97 """Create the number of instances requested if quota and
98 other arguments check out ok.
99@@ -100,6 +100,30 @@
100 "run %s more instances of this type.") %
101 num_instances, "InstanceLimitExceeded")
102
103+ num_metadata = len(metadata)
104+ quota_metadata = quota.allowed_metadata_items(context, num_metadata)
105+ if quota_metadata < num_metadata:
106+ pid = context.project_id
107+ msg = (_("Quota exceeeded for %(pid)s,"
108+ " tried to set %(num_metadata)s metadata properties")
109+ % locals())
110+ LOG.warn(msg)
111+ raise quota.QuotaError(msg, "MetadataLimitExceeded")
112+
113+ # Because metadata is stored in the DB, we hard-code the size limits
114+ # In future, we may support more variable length strings, so we act
115+ # as if this is quota-controlled for forwards compatibility
116+ for metadata_item in metadata:
117+ k = metadata_item['key']
118+ v = metadata_item['value']
119+ if len(k) > 255 or len(v) > 255:
120+ pid = context.project_id
121+ msg = (_("Quota exceeeded for %(pid)s,"
122+ " metadata property key or value too long")
123+ % locals())
124+ LOG.warn(msg)
125+ raise quota.QuotaError(msg, "MetadataLimitExceeded")
126+
127 image = self.image_service.show(context, image_id)
128 if kernel_id is None:
129 kernel_id = image.get('kernel_id', None)
130@@ -154,6 +178,7 @@
131 'key_name': key_name,
132 'key_data': key_data,
133 'locked': False,
134+ 'metadata': metadata,
135 'availability_zone': availability_zone}
136 elevated = context.elevated()
137 instances = []
138
139=== modified file 'nova/db/sqlalchemy/api.py'
140--- nova/db/sqlalchemy/api.py 2011-02-17 21:39:03 +0000
141+++ nova/db/sqlalchemy/api.py 2011-02-21 22:49:18 +0000
142@@ -715,6 +715,7 @@
143 options(joinedload_all('security_groups.rules')).\
144 options(joinedload('volumes')).\
145 options(joinedload_all('fixed_ip.network')).\
146+ options(joinedload('metadata')).\
147 filter_by(id=instance_id).\
148 filter_by(deleted=can_read_deleted(context)).\
149 first()
150@@ -723,6 +724,7 @@
151 options(joinedload_all('fixed_ip.floating_ips')).\
152 options(joinedload_all('security_groups.rules')).\
153 options(joinedload('volumes')).\
154+ options(joinedload('metadata')).\
155 filter_by(project_id=context.project_id).\
156 filter_by(id=instance_id).\
157 filter_by(deleted=False).\
158
159=== added file 'nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py'
160--- nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py 1970-01-01 00:00:00 +0000
161+++ nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py 2011-02-21 22:49:18 +0000
162@@ -0,0 +1,78 @@
163+# vim: tabstop=4 shiftwidth=4 softtabstop=4
164+
165+# Copyright 2011 Justin Santa Barbara
166+# All Rights Reserved.
167+#
168+# Licensed under the Apache License, Version 2.0 (the "License"); you may
169+# not use this file except in compliance with the License. You may obtain
170+# a copy of the License at
171+#
172+# http://www.apache.org/licenses/LICENSE-2.0
173+#
174+# Unless required by applicable law or agreed to in writing, software
175+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
176+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
177+# License for the specific language governing permissions and limitations
178+# under the License.
179+
180+from sqlalchemy import *
181+from migrate import *
182+
183+from nova import log as logging
184+
185+
186+meta = MetaData()
187+
188+
189+# Just for the ForeignKey and column creation to succeed, these are not the
190+# actual definitions of instances or services.
191+instances = Table('instances', meta,
192+ Column('id', Integer(), primary_key=True, nullable=False),
193+ )
194+
195+quotas = Table('quotas', meta,
196+ Column('id', Integer(), primary_key=True, nullable=False),
197+ )
198+
199+
200+#
201+# New Tables
202+#
203+
204+instance_metadata_table = Table('instance_metadata', meta,
205+ Column('created_at', DateTime(timezone=False)),
206+ Column('updated_at', DateTime(timezone=False)),
207+ Column('deleted_at', DateTime(timezone=False)),
208+ Column('deleted', Boolean(create_constraint=True, name=None)),
209+ Column('id', Integer(), primary_key=True, nullable=False),
210+ Column('instance_id',
211+ Integer(),
212+ ForeignKey('instances.id'),
213+ nullable=False),
214+ Column('key',
215+ String(length=255, convert_unicode=False, assert_unicode=None,
216+ unicode_error=None, _warn_on_bytestring=False)),
217+ Column('value',
218+ String(length=255, convert_unicode=False, assert_unicode=None,
219+ unicode_error=None, _warn_on_bytestring=False)))
220+
221+
222+#
223+# New columns
224+#
225+quota_metadata_items = Column('metadata_items', Integer())
226+
227+
228+def upgrade(migrate_engine):
229+ # Upgrade operations go here. Don't create your own engine;
230+ # bind migrate_engine to your metadata
231+ meta.bind = migrate_engine
232+ for table in (instance_metadata_table, ):
233+ try:
234+ table.create()
235+ except Exception:
236+ logging.info(repr(table))
237+ logging.exception('Exception while creating table')
238+ raise
239+
240+ quotas.create_column(quota_metadata_items)
241
242=== modified file 'nova/db/sqlalchemy/models.py'
243--- nova/db/sqlalchemy/models.py 2011-02-17 21:39:03 +0000
244+++ nova/db/sqlalchemy/models.py 2011-02-21 22:49:18 +0000
245@@ -256,6 +256,7 @@
246 volumes = Column(Integer)
247 gigabytes = Column(Integer)
248 floating_ips = Column(Integer)
249+ metadata_items = Column(Integer)
250
251
252 class ExportDevice(BASE, NovaBase):
253@@ -536,6 +537,20 @@
254 pool = relationship(ConsolePool, backref=backref('consoles'))
255
256
257+class InstanceMetadata(BASE, NovaBase):
258+ """Represents a metadata key/value pair for an instance"""
259+ __tablename__ = 'instance_metadata'
260+ id = Column(Integer, primary_key=True)
261+ key = Column(String(255))
262+ value = Column(String(255))
263+ instance_id = Column(Integer, ForeignKey('instances.id'), nullable=False)
264+ instance = relationship(Instance, backref="metadata",
265+ foreign_keys=instance_id,
266+ primaryjoin='and_('
267+ 'InstanceMetadata.instance_id == Instance.id,'
268+ 'InstanceMetadata.deleted == False)')
269+
270+
271 class Zone(BASE, NovaBase):
272 """Represents a child zone of this zone."""
273 __tablename__ = 'zones'
274@@ -557,7 +572,8 @@
275 Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,
276 Network, SecurityGroup, SecurityGroupIngressRule,
277 SecurityGroupInstanceAssociation, AuthToken, User,
278- Project, Certificate, ConsolePool, Console, Zone)
279+ Project, Certificate, ConsolePool, Console, Zone,
280+ InstanceMetadata)
281 engine = create_engine(FLAGS.sql_connection, echo=False)
282 for model in models:
283 model.metadata.create_all(engine)
284
285=== modified file 'nova/quota.py'
286--- nova/quota.py 2011-01-04 21:56:36 +0000
287+++ nova/quota.py 2011-02-21 22:49:18 +0000
288@@ -35,6 +35,8 @@
289 'number of volume gigabytes allowed per project')
290 flags.DEFINE_integer('quota_floating_ips', 10,
291 'number of floating ips allowed per project')
292+flags.DEFINE_integer('quota_metadata_items', 128,
293+ 'number of metadata items allowed per instance')
294
295
296 def get_quota(context, project_id):
297@@ -42,7 +44,8 @@
298 'cores': FLAGS.quota_cores,
299 'volumes': FLAGS.quota_volumes,
300 'gigabytes': FLAGS.quota_gigabytes,
301- 'floating_ips': FLAGS.quota_floating_ips}
302+ 'floating_ips': FLAGS.quota_floating_ips,
303+ 'metadata_items': FLAGS.quota_metadata_items}
304 try:
305 quota = db.quota_get(context, project_id)
306 for key in rval.keys():
307@@ -94,6 +97,15 @@
308 return min(num_floating_ips, allowed_floating_ips)
309
310
311+def allowed_metadata_items(context, num_metadata_items):
312+ """Check quota; return min(num_metadata_items,allowed_metadata_items)"""
313+ project_id = context.project_id
314+ context = context.elevated()
315+ quota = get_quota(context, project_id)
316+ num_allowed_metadata_items = quota['metadata_items']
317+ return min(num_metadata_items, num_allowed_metadata_items)
318+
319+
320 class QuotaError(exception.ApiError):
321 """Quota Exceeeded"""
322 pass
323
324=== modified file 'nova/tests/api/openstack/test_servers.py'
325--- nova/tests/api/openstack/test_servers.py 2011-02-17 19:38:11 +0000
326+++ nova/tests/api/openstack/test_servers.py 2011-02-21 22:49:18 +0000
327@@ -28,6 +28,7 @@
328 from nova.api.openstack import servers
329 import nova.db.api
330 from nova.db.sqlalchemy.models import Instance
331+from nova.db.sqlalchemy.models import InstanceMetadata
332 import nova.rpc
333 from nova.tests.api.openstack import fakes
334
335@@ -64,6 +65,9 @@
336
337
338 def stub_instance(id, user_id=1, private_address=None, public_addresses=None):
339+ metadata = []
340+ metadata.append(InstanceMetadata(key='seq', value=id))
341+
342 if public_addresses == None:
343 public_addresses = list()
344
345@@ -95,7 +99,8 @@
346 "availability_zone": "",
347 "display_name": "server%s" % id,
348 "display_description": "",
349- "locked": False}
350+ "locked": False,
351+ "metadata": metadata}
352
353 instance["fixed_ip"] = {
354 "address": private_address,
355@@ -214,7 +219,8 @@
356 "get_image_id_from_image_hash", image_id_from_hash)
357
358 body = dict(server=dict(
359- name='server_test', imageId=2, flavorId=2, metadata={},
360+ name='server_test', imageId=2, flavorId=2,
361+ metadata={'hello': 'world', 'open': 'stack'},
362 personality={}))
363 req = webob.Request.blank('/v1.0/servers')
364 req.method = 'POST'
365@@ -291,6 +297,7 @@
366 self.assertEqual(s['id'], i)
367 self.assertEqual(s['name'], 'server%d' % i)
368 self.assertEqual(s['imageId'], 10)
369+ self.assertEqual(s['metadata']['seq'], i)
370 i += 1
371
372 def test_server_pause(self):
373
374=== modified file 'nova/tests/test_quota.py'
375--- nova/tests/test_quota.py 2011-01-07 14:46:17 +0000
376+++ nova/tests/test_quota.py 2011-02-21 22:49:18 +0000
377@@ -87,6 +87,18 @@
378 num_instances = quota.allowed_instances(self.context, 100,
379 instance_types.INSTANCE_TYPES['m1.small'])
380 self.assertEqual(num_instances, 10)
381+
382+ # metadata_items
383+ too_many_items = FLAGS.quota_metadata_items + 1000
384+ num_metadata_items = quota.allowed_metadata_items(self.context,
385+ too_many_items)
386+ self.assertEqual(num_metadata_items, FLAGS.quota_metadata_items)
387+ db.quota_update(self.context, self.project.id, {'metadata_items': 5})
388+ num_metadata_items = quota.allowed_metadata_items(self.context,
389+ too_many_items)
390+ self.assertEqual(num_metadata_items, 5)
391+
392+ # Cleanup
393 db.quota_destroy(self.context, self.project.id)
394
395 def test_too_many_instances(self):
396@@ -151,3 +163,15 @@
397 self.assertRaises(quota.QuotaError, self.cloud.allocate_address,
398 self.context)
399 db.floating_ip_destroy(context.get_admin_context(), address)
400+
401+ def test_too_many_metadata_items(self):
402+ metadata = {}
403+ for i in range(FLAGS.quota_metadata_items + 1):
404+ metadata['key%s' % i] = 'value%s' % i
405+ self.assertRaises(quota.QuotaError, self.cloud.run_instances,
406+ self.context,
407+ min_count=1,
408+ max_count=1,
409+ instance_type='m1.small',
410+ image_id='fake',
411+ metadata=metadata)
412
413=== modified file 'run_tests.sh'
414--- run_tests.sh 2011-01-31 05:55:32 +0000
415+++ run_tests.sh 2011-02-21 22:49:18 +0000
416@@ -73,7 +73,9 @@
417
418 if [ -z "$noseargs" ];
419 then
420- run_tests && pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py bin/* nova setup.py || exit 1
421+ srcfiles=`find bin -type f ! -name "nova.conf*"`
422+ srcfiles+=" nova setup.py"
423+ run_tests && pep8 --repeat --show-pep8 --show-source --exclude=vcsversion.py ${srcfiles} || exit 1
424 else
425 run_tests
426 fi