Merge lp:~sandy-walsh/nova/zones into lp:~hudson-openstack/nova/trunk

Proposed by Sandy Walsh
Status: Merged
Approved by: Matt Dietz
Approved revision: 651
Merged at revision: 696
Proposed branch: lp:~sandy-walsh/nova/zones
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 487 lines (+374/-9)
11 files modified
nova/api/openstack/__init__.py (+4/-0)
nova/api/openstack/auth.py (+1/-0)
nova/api/openstack/servers.py (+0/-2)
nova/api/openstack/zones.py (+80/-0)
nova/auth/novarc.template (+3/-4)
nova/db/api.py (+28/-0)
nova/db/sqlalchemy/api.py (+44/-0)
nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py (+61/-0)
nova/db/sqlalchemy/migration.py (+2/-2)
nova/db/sqlalchemy/models.py (+10/-1)
nova/tests/api/openstack/test_zones.py (+141/-0)
To merge this branch: bzr merge lp:~sandy-walsh/nova/zones
Reviewer Review Type Date Requested Status
Matt Dietz (community) Approve
Eric Day (community) Approve
Jay Pipes (community) Needs Fixing
Review via email: mp+49730@code.launchpad.net

Commit message

Added http://mynova/v1.0/zones/ api options for add/remove/update/delete zones. child_zones table added to database and migration. Changed novarc vars from CLOUD_SERVERS_* to NOVA_* to work with novatools. See python-novatools on github for help testing this.

Description of the change

This is the first phase of the multi-zone clustering. Right now it's just the API/DB stuff for adding/listing/deleting zones.

Zones are just URL + username/password credentials of child zones.

python-novatools (on github) was also updated to support these changes.

Next we'll get these zones talking to each other.

To post a comment you must log in.
Revision history for this message
Todd Willey (xtoddx) wrote :

was the bin/nova-combined change accidental?

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

I would s/ChildZone/Zone/ for consistent naming. Having the API deal with 'zones' and the DB deal with 'child zones' may make someone think they are not the same.

review: Needs Fixing
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Thanks for the catch todd. Missed that on my diff.

Eric, yeah, I debated that one. I agree & will change. Thanks.

lp:~sandy-walsh/nova/zones updated
646. By Sandy Walsh

fixed nova-combined debug hack and renamed ChildZone to Zone

647. By Sandy Walsh

better filtering

648. By Sandy Walsh

trunk merge

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

Hi! Great work so far, Sandy.

A couple notes:

1) 223 === added file 'nova/db/sqlalchemy/migrate_repo/versions/003_cactus.py'

I think we wanted to go with >1 migrate scripts per release, with a migration script for each patch that makes changes to the underlying database schema.

So, I'd recommend s/003_cactus.py/003_add_zone_tables.py/.

2) Looks like you are still calling things child zones in the migration script and in the nova.db.api docstrings...

Other than those little things, lgtm.

review: Needs Fixing
lp:~sandy-walsh/nova/zones updated
649. By Sandy Walsh

fixed / renamed migration scripts

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

lgtm

review: Approve
lp:~sandy-walsh/nova/zones updated
650. By Sandy Walsh

changed from 003-004 migration

Revision history for this message
Matt Dietz (cerberus) wrote :

lgtm.

review: Approve
Revision history for this message
Matt Dietz (cerberus) wrote :

Whoops, missed the NASA copyright in the migration file. Should be the Openstack one

review: Needs Fixing
lp:~sandy-walsh/nova/zones updated
651. By Sandy Walsh

copyright notice

Revision history for this message
Matt Dietz (cerberus) wrote :

again.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-02-16 20:12:54 +0000
+++ nova/api/openstack/__init__.py 2011-02-17 21:54:13 +0000
@@ -34,6 +34,7 @@
34from nova.api.openstack import images34from nova.api.openstack import images
35from nova.api.openstack import servers35from nova.api.openstack import servers
36from nova.api.openstack import shared_ip_groups36from nova.api.openstack import shared_ip_groups
37from nova.api.openstack import zones
3738
3839
39LOG = logging.getLogger('nova.api.openstack')40LOG = logging.getLogger('nova.api.openstack')
@@ -81,6 +82,9 @@
81 server_members['resume'] = 'POST'82 server_members['resume'] = 'POST'
82 server_members['reset_network'] = 'POST'83 server_members['reset_network'] = 'POST'
8384
85 mapper.resource("zone", "zones", controller=zones.Controller(),
86 collection={'detail': 'GET'})
87
84 mapper.resource("server", "servers", controller=servers.Controller(),88 mapper.resource("server", "servers", controller=servers.Controller(),
85 collection={'detail': 'GET'},89 collection={'detail': 'GET'},
86 member=server_members)90 member=server_members)
8791
=== modified file 'nova/api/openstack/auth.py'
--- nova/api/openstack/auth.py 2011-01-06 18:57:48 +0000
+++ nova/api/openstack/auth.py 2011-02-17 21:54:13 +0000
@@ -19,6 +19,7 @@
19import hashlib19import hashlib
20import json20import json
21import time21import time
22import logging
2223
23import webob.exc24import webob.exc
24import webob.dec25import webob.dec
2526
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-02-17 19:38:11 +0000
+++ nova/api/openstack/servers.py 2011-02-17 21:54:13 +0000
@@ -1,5 +1,3 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 OpenStack LLC.1# Copyright 2010 OpenStack LLC.
4# All Rights Reserved.2# All Rights Reserved.
5#3#
64
=== added file 'nova/api/openstack/zones.py'
--- nova/api/openstack/zones.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/zones.py 2011-02-17 21:54:13 +0000
@@ -0,0 +1,80 @@
1# Copyright 2010 OpenStack LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import common
17import logging
18
19from nova import flags
20from nova import wsgi
21from nova import db
22
23
24FLAGS = flags.FLAGS
25
26
27def _filter_keys(item, keys):
28 """
29 Filters all model attributes except for keys
30 item is a dict
31
32 """
33 return dict((k, v) for k, v in item.iteritems() if k in keys)
34
35
36def _scrub_zone(zone):
37 return _filter_keys(zone, ('id', 'api_url'))
38
39
40class Controller(wsgi.Controller):
41
42 _serialization_metadata = {
43 'application/xml': {
44 "attributes": {
45 "zone": ["id", "api_url"]}}}
46
47 def index(self, req):
48 """Return all zones in brief"""
49 items = db.zone_get_all(req.environ['nova.context'])
50 items = common.limited(items, req)
51 items = [_scrub_zone(item) for item in items]
52 return dict(zones=items)
53
54 def detail(self, req):
55 """Return all zones in detail"""
56 return self.index(req)
57
58 def show(self, req, id):
59 """Return data about the given zone id"""
60 zone_id = int(id)
61 zone = db.zone_get(req.environ['nova.context'], zone_id)
62 return dict(zone=_scrub_zone(zone))
63
64 def delete(self, req, id):
65 zone_id = int(id)
66 db.zone_delete(req.environ['nova.context'], zone_id)
67 return {}
68
69 def create(self, req):
70 context = req.environ['nova.context']
71 env = self._deserialize(req.body, req)
72 zone = db.zone_create(context, env["zone"])
73 return dict(zone=_scrub_zone(zone))
74
75 def update(self, req, id):
76 context = req.environ['nova.context']
77 env = self._deserialize(req.body, req)
78 zone_id = int(id)
79 zone = db.zone_update(context, zone_id, env["zone"])
80 return dict(zone=_scrub_zone(zone))
081
=== modified file 'nova/auth/novarc.template'
--- nova/auth/novarc.template 2011-01-04 01:06:50 +0000
+++ nova/auth/novarc.template 2011-02-17 21:54:13 +0000
@@ -10,7 +10,6 @@
10export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set10export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
11alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"11alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
12alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"12alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
13export CLOUD_SERVERS_API_KEY="%(access)s"13export NOVA_API_KEY="%(access)s"
14export CLOUD_SERVERS_USERNAME="%(user)s"14export NOVA_USERNAME="%(user)s"
15export CLOUD_SERVERS_URL="%(os)s"15export NOVA_URL="%(os)s"
16
1716
=== modified file 'nova/db/api.py'
--- nova/db/api.py 2011-02-17 19:32:30 +0000
+++ nova/db/api.py 2011-02-17 21:54:13 +0000
@@ -1000,3 +1000,31 @@
1000def console_get(context, console_id, instance_id=None):1000def console_get(context, console_id, instance_id=None):
1001 """Get a specific console (possibly on a given instance)."""1001 """Get a specific console (possibly on a given instance)."""
1002 return IMPL.console_get(context, console_id, instance_id)1002 return IMPL.console_get(context, console_id, instance_id)
1003
1004
1005####################
1006
1007
1008def zone_create(context, values):
1009 """Create a new child Zone entry."""
1010 return IMPL.zone_create(context, values)
1011
1012
1013def zone_update(context, zone_id, values):
1014 """Update a child Zone entry."""
1015 return IMPL.zone_update(context, values)
1016
1017
1018def zone_delete(context, zone_id):
1019 """Delete a child Zone."""
1020 return IMPL.zone_delete(context, zone_id)
1021
1022
1023def zone_get(context, zone_id):
1024 """Get a specific child Zone."""
1025 return IMPL.zone_get(context, zone_id)
1026
1027
1028def zone_get_all(context):
1029 """Get all child Zones."""
1030 return IMPL.zone_get_all(context)
10031031
=== modified file 'nova/db/sqlalchemy/api.py'
--- nova/db/sqlalchemy/api.py 2011-02-17 19:32:30 +0000
+++ nova/db/sqlalchemy/api.py 2011-02-17 21:54:13 +0000
@@ -2058,3 +2058,47 @@
2058 raise exception.NotFound(_("No console with id %(console_id)s"2058 raise exception.NotFound(_("No console with id %(console_id)s"
2059 " %(idesc)s") % locals())2059 " %(idesc)s") % locals())
2060 return result2060 return result
2061
2062
2063####################
2064
2065
2066@require_admin_context
2067def zone_create(context, values):
2068 zone = models.Zone()
2069 zone.update(values)
2070 zone.save()
2071 return zone
2072
2073
2074@require_admin_context
2075def zone_update(context, zone_id, values):
2076 zone = session.query(models.Zone).filter_by(id=zone_id).first()
2077 if not zone:
2078 raise exception.NotFound(_("No zone with id %(zone_id)s") % locals())
2079 zone.update(values)
2080 zone.save()
2081 return zone
2082
2083
2084@require_admin_context
2085def zone_delete(context, zone_id):
2086 session = get_session()
2087 with session.begin():
2088 session.execute('delete from zones '
2089 'where id=:id', {'id': zone_id})
2090
2091
2092@require_admin_context
2093def zone_get(context, zone_id):
2094 session = get_session()
2095 result = session.query(models.Zone).filter_by(id=zone_id).first()
2096 if not result:
2097 raise exception.NotFound(_("No zone with id %(zone_id)s") % locals())
2098 return result
2099
2100
2101@require_admin_context
2102def zone_get_all(context):
2103 session = get_session()
2104 return session.query(models.Zone).all()
20612105
=== added file 'nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py'
--- nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py 1970-01-01 00:00:00 +0000
+++ nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py 2011-02-17 21:54:13 +0000
@@ -0,0 +1,61 @@
1# Copyright 2010 OpenStack LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from sqlalchemy import *
17from migrate import *
18
19from nova import log as logging
20
21
22meta = MetaData()
23
24
25#
26# New Tables
27#
28zones = Table('zones', meta,
29 Column('created_at', DateTime(timezone=False)),
30 Column('updated_at', DateTime(timezone=False)),
31 Column('deleted_at', DateTime(timezone=False)),
32 Column('deleted', Boolean(create_constraint=True, name=None)),
33 Column('id', Integer(), primary_key=True, nullable=False),
34 Column('api_url',
35 String(length=255, convert_unicode=False, assert_unicode=None,
36 unicode_error=None, _warn_on_bytestring=False)),
37 Column('username',
38 String(length=255, convert_unicode=False, assert_unicode=None,
39 unicode_error=None, _warn_on_bytestring=False)),
40 Column('password',
41 String(length=255, convert_unicode=False, assert_unicode=None,
42 unicode_error=None, _warn_on_bytestring=False)),
43 )
44
45
46#
47# Tables to alter
48#
49
50# (none currently)
51
52
53def upgrade(migrate_engine):
54 # Upgrade operations go here. Don't create your own engine;
55 # bind migrate_engine to your metadata
56 meta.bind = migrate_engine
57 for table in (zones, ):
58 try:
59 table.create()
60 except Exception:
61 logging.info(repr(table))
062
=== modified file 'nova/db/sqlalchemy/migration.py'
--- nova/db/sqlalchemy/migration.py 2011-02-16 18:24:46 +0000
+++ nova/db/sqlalchemy/migration.py 2011-02-17 21:54:13 +0000
@@ -55,8 +55,8 @@
55 engine = sqlalchemy.create_engine(FLAGS.sql_connection, echo=False)55 engine = sqlalchemy.create_engine(FLAGS.sql_connection, echo=False)
56 meta.reflect(bind=engine)56 meta.reflect(bind=engine)
57 try:57 try:
58 for table in ('auth_tokens', 'export_devices', 'fixed_ips',58 for table in ('auth_tokens', 'zones', 'export_devices',
59 'floating_ips', 'instances',59 'fixed_ips', 'floating_ips', 'instances',
60 'key_pairs', 'networks', 'projects', 'quotas',60 'key_pairs', 'networks', 'projects', 'quotas',
61 'security_group_instance_association',61 'security_group_instance_association',
62 'security_group_rules', 'security_groups',62 'security_group_rules', 'security_groups',
6363
=== modified file 'nova/db/sqlalchemy/models.py'
--- nova/db/sqlalchemy/models.py 2011-02-14 23:18:59 +0000
+++ nova/db/sqlalchemy/models.py 2011-02-17 21:54:13 +0000
@@ -536,6 +536,15 @@
536 pool = relationship(ConsolePool, backref=backref('consoles'))536 pool = relationship(ConsolePool, backref=backref('consoles'))
537537
538538
539class Zone(BASE, NovaBase):
540 """Represents a child zone of this zone."""
541 __tablename__ = 'zones'
542 id = Column(Integer, primary_key=True)
543 api_url = Column(String(255))
544 username = Column(String(255))
545 password = Column(String(255))
546
547
539def register_models():548def register_models():
540 """Register Models and create metadata.549 """Register Models and create metadata.
541550
@@ -548,7 +557,7 @@
548 Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,557 Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,
549 Network, SecurityGroup, SecurityGroupIngressRule,558 Network, SecurityGroup, SecurityGroupIngressRule,
550 SecurityGroupInstanceAssociation, AuthToken, User,559 SecurityGroupInstanceAssociation, AuthToken, User,
551 Project, Certificate, ConsolePool, Console) # , Image, Host560 Project, Certificate, ConsolePool, Console, Zone)
552 engine = create_engine(FLAGS.sql_connection, echo=False)561 engine = create_engine(FLAGS.sql_connection, echo=False)
553 for model in models:562 for model in models:
554 model.metadata.create_all(engine)563 model.metadata.create_all(engine)
555564
=== added file 'nova/tests/api/openstack/test_zones.py'
--- nova/tests/api/openstack/test_zones.py 1970-01-01 00:00:00 +0000
+++ nova/tests/api/openstack/test_zones.py 2011-02-17 21:54:13 +0000
@@ -0,0 +1,141 @@
1# Copyright 2010 OpenStack LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import unittest
17
18import stubout
19import webob
20import json
21
22import nova.db
23from nova import context
24from nova import flags
25from nova.api.openstack import zones
26from nova.tests.api.openstack import fakes
27
28
29FLAGS = flags.FLAGS
30FLAGS.verbose = True
31
32
33def zone_get(context, zone_id):
34 return dict(id=1, api_url='http://foo.com', username='bob',
35 password='xxx')
36
37
38def zone_create(context, values):
39 zone = dict(id=1)
40 zone.update(values)
41 return zone
42
43
44def zone_update(context, zone_id, values):
45 zone = dict(id=zone_id, api_url='http://foo.com', username='bob',
46 password='xxx')
47 zone.update(values)
48 return zone
49
50
51def zone_delete(context, zone_id):
52 pass
53
54
55def zone_get_all(context):
56 return [
57 dict(id=1, api_url='http://foo.com', username='bob',
58 password='xxx'),
59 dict(id=2, api_url='http://blah.com', username='alice',
60 password='qwerty')
61 ]
62
63
64class ZonesTest(unittest.TestCase):
65 def setUp(self):
66 self.stubs = stubout.StubOutForTesting()
67 fakes.FakeAuthManager.auth_data = {}
68 fakes.FakeAuthDatabase.data = {}
69 fakes.stub_out_networking(self.stubs)
70 fakes.stub_out_rate_limiting(self.stubs)
71 fakes.stub_out_auth(self.stubs)
72
73 self.allow_admin = FLAGS.allow_admin_api
74 FLAGS.allow_admin_api = True
75
76 self.stubs.Set(nova.db, 'zone_get', zone_get)
77 self.stubs.Set(nova.db, 'zone_get_all', zone_get_all)
78 self.stubs.Set(nova.db, 'zone_update', zone_update)
79 self.stubs.Set(nova.db, 'zone_create', zone_create)
80 self.stubs.Set(nova.db, 'zone_delete', zone_delete)
81
82 def tearDown(self):
83 self.stubs.UnsetAll()
84 FLAGS.allow_admin_api = self.allow_admin
85
86 def test_get_zone_list(self):
87 req = webob.Request.blank('/v1.0/zones')
88 res = req.get_response(fakes.wsgi_app())
89 res_dict = json.loads(res.body)
90
91 self.assertEqual(res.status_int, 200)
92 self.assertEqual(len(res_dict['zones']), 2)
93
94 def test_get_zone_by_id(self):
95 req = webob.Request.blank('/v1.0/zones/1')
96 res = req.get_response(fakes.wsgi_app())
97 res_dict = json.loads(res.body)
98
99 self.assertEqual(res_dict['zone']['id'], 1)
100 self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
101 self.assertFalse('password' in res_dict['zone'])
102 self.assertEqual(res.status_int, 200)
103
104 def test_zone_delete(self):
105 req = webob.Request.blank('/v1.0/zones/1')
106 res = req.get_response(fakes.wsgi_app())
107
108 self.assertEqual(res.status_int, 200)
109
110 def test_zone_create(self):
111 body = dict(zone=dict(api_url='http://blah.zoo', username='fred',
112 password='fubar'))
113 req = webob.Request.blank('/v1.0/zones')
114 req.method = 'POST'
115 req.body = json.dumps(body)
116
117 res = req.get_response(fakes.wsgi_app())
118 res_dict = json.loads(res.body)
119
120 self.assertEqual(res.status_int, 200)
121 self.assertEqual(res_dict['zone']['id'], 1)
122 self.assertEqual(res_dict['zone']['api_url'], 'http://blah.zoo')
123 self.assertFalse('username' in res_dict['zone'])
124
125 def test_zone_update(self):
126 body = dict(zone=dict(username='zeb', password='sneaky'))
127 req = webob.Request.blank('/v1.0/zones/1')
128 req.method = 'PUT'
129 req.body = json.dumps(body)
130
131 res = req.get_response(fakes.wsgi_app())
132 res_dict = json.loads(res.body)
133
134 self.assertEqual(res.status_int, 200)
135 self.assertEqual(res_dict['zone']['id'], 1)
136 self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
137 self.assertFalse('username' in res_dict['zone'])
138
139
140if __name__ == '__main__':
141 unittest.main()