Merge lp:~gundlach/nova/controllers-in-api into lp:~hudson-openstack/nova/trunk

Proposed by Michael Gundlach
Status: Merged
Approved by: Michael Gundlach
Approved revision: no longer in the revision history of the source branch.
Merged at revision: 259
Proposed branch: lp:~gundlach/nova/controllers-in-api
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 237 lines (+103/-70)
5 files modified
nova/api/rackspace/__init__.py (+4/-2)
nova/api/rackspace/_id_translator.py (+42/-0)
nova/api/rackspace/flavors.py (+37/-1)
nova/api/rackspace/images.py (+13/-60)
nova/compute/instance_types.py (+7/-7)
To merge this branch: bzr merge lp:~gundlach/nova/controllers-in-api
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Review via email: mp+33698@code.launchpad.net

Description of the change

Add Flavors controller supporting

GET /flavors

GET /flavors/detail

GET /flavors/<id>

Also add GET /images/detail

Turn the RackspaceAPIImageIdTranslator into a RackspaceAPIIdTranslator, so that it can be used to translate IDs for other rackspace API components as well (servers, backup schedules.) I thought I'd need it for flavors but it turns out flavors are so simple I could hard code their ids into compute.instance_types.INSTANCE_TYPES.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

Yep, looks great.

Wondering why you chose to make _id_translator.py prefixed with an underscore...is that file meant to be a private utility? Is there some reason you didn't want it to be part of the set of "public" Python classes?

review: Approve
Revision history for this message
Michael Gundlach (gundlach) wrote :

Yes, I didn't want api.rackspace.id_translator to exist along with "servers|images|flavors|sharedipgroups" which are all controllers.

I could have put that class into api.rackspace.__init__ I guess with a _ prepended, but, whatever :)

lp:~gundlach/nova/controllers-in-api updated
266. By Michael Gundlach

get to look like trunk

267. By Michael Gundlach

work endpoint/images.py into an S3ImageService. The translation isn't perfect, but it's a start.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/rackspace/__init__.py'
2--- nova/api/rackspace/__init__.py 2010-08-18 15:56:33 +0000
3+++ nova/api/rackspace/__init__.py 2010-08-25 21:50:55 +0000
4@@ -74,8 +74,10 @@
5 def __init__(self):
6 mapper = routes.Mapper()
7 mapper.resource("server", "servers", controller=servers.Controller())
8- mapper.resource("image", "images", controller=images.Controller())
9- mapper.resource("flavor", "flavors", controller=flavors.Controller())
10+ mapper.resource("image", "images", controller=images.Controller(),
11+ collection={'detail': 'GET'})
12+ mapper.resource("flavor", "flavors", controller=flavors.Controller(),
13+ collection={'detail': 'GET'})
14 mapper.resource("sharedipgroup", "sharedipgroups",
15 controller=sharedipgroups.Controller())
16 super(APIRouter, self).__init__(mapper)
17
18=== added file 'nova/api/rackspace/_id_translator.py'
19--- nova/api/rackspace/_id_translator.py 1970-01-01 00:00:00 +0000
20+++ nova/api/rackspace/_id_translator.py 2010-08-25 21:50:55 +0000
21@@ -0,0 +1,42 @@
22+from nova import datastore
23+
24+class RackspaceAPIIdTranslator(object):
25+ """
26+ Converts Rackspace API ids to and from the id format for a given
27+ strategy.
28+ """
29+
30+ def __init__(self, id_type, service_name):
31+ """
32+ Creates a translator for ids of the given type (e.g. 'flavor'), for the
33+ given storage service backend class name (e.g. 'LocalFlavorService').
34+ """
35+
36+ self._store = datastore.Redis.instance()
37+ key_prefix = "rsapi.idtranslator.%s.%s" % (id_type, service_name)
38+ # Forward (strategy format -> RS format) and reverse translation keys
39+ self._fwd_key = "%s.fwd" % key_prefix
40+ self._rev_key = "%s.rev" % key_prefix
41+
42+ def to_rs_id(self, opaque_id):
43+ """Convert an id from a strategy-specific one to a Rackspace one."""
44+ result = self._store.hget(self._fwd_key, str(opaque_id))
45+ if result: # we have a mapping from opaque to RS for this strategy
46+ return int(result)
47+ else:
48+ # Store the mapping.
49+ nextid = self._store.incr("%s.lastid" % self._fwd_key)
50+ if self._store.hsetnx(self._fwd_key, str(opaque_id), nextid):
51+ # If someone else didn't beat us to it, store the reverse
52+ # mapping as well.
53+ self._store.hset(self._rev_key, nextid, str(opaque_id))
54+ return nextid
55+ else:
56+ # Someone beat us to it; use their number instead, and
57+ # discard nextid (which is OK -- we don't require that
58+ # every int id be used.)
59+ return int(self._store.hget(self._fwd_key, str(opaque_id)))
60+
61+ def from_rs_id(self, strategy_name, rs_id):
62+ """Convert a Rackspace id to a strategy-specific one."""
63+ return self._store.hget(self._rev_key, rs_id)
64
65=== modified file 'nova/api/rackspace/flavors.py'
66--- nova/api/rackspace/flavors.py 2010-08-18 15:56:33 +0000
67+++ nova/api/rackspace/flavors.py 2010-08-25 21:50:55 +0000
68@@ -15,4 +15,40 @@
69 # License for the specific language governing permissions and limitations
70 # under the License.
71
72-class Controller(object): pass
73+from nova.api.rackspace import base
74+from nova.compute import instance_types
75+from webob import exc
76+
77+class Controller(base.Controller):
78+ """Flavor controller for the Rackspace API."""
79+
80+ _serialization_metadata = {
81+ 'application/xml': {
82+ "attributes": {
83+ "flavor": [ "id", "name", "ram", "disk" ]
84+ }
85+ }
86+ }
87+
88+ def index(self, req):
89+ """Return all flavors in brief."""
90+ return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
91+ for flavor in self.detail(req)['flavors']])
92+
93+ def detail(self, req):
94+ """Return all flavors in detail."""
95+ items = [self.show(req, id)['flavor'] for id in self._all_ids()]
96+ return dict(flavors=items)
97+
98+ def show(self, req, id):
99+ """Return data about the given flavor id."""
100+ for name, val in instance_types.INSTANCE_TYPES.iteritems():
101+ if val['flavorid'] == int(id):
102+ item = dict(ram=val['memory_mb'], disk=val['local_gb'],
103+ id=val['flavorid'], name=name)
104+ return dict(flavor=item)
105+ raise exc.HTTPNotFound()
106+
107+ def _all_ids(self):
108+ """Return the list of all flavorids."""
109+ return [i['flavorid'] for i in instance_types.INSTANCE_TYPES.values()]
110
111=== modified file 'nova/api/rackspace/images.py'
112--- nova/api/rackspace/images.py 2010-08-24 20:24:24 +0000
113+++ nova/api/rackspace/images.py 2010-08-25 21:50:55 +0000
114@@ -15,9 +15,9 @@
115 # License for the specific language governing permissions and limitations
116 # under the License.
117
118-from nova import datastore
119-from nova import image
120+import nova.image.service
121 from nova.api.rackspace import base
122+from nova.api.rackspace import _id_translator
123 from webob import exc
124
125 class Controller(base.Controller):
126@@ -32,35 +32,25 @@
127 }
128
129 def __init__(self):
130- self._service = image.service.ImageService.load()
131- self._id_translator = RackspaceAPIImageIdTranslator()
132-
133- def _to_rs_id(self, image_id):
134- """
135- Convert an image id from the format of our ImageService strategy
136- to the Rackspace API format (an int).
137- """
138- strategy = self._service.__class__.__name__
139- return self._id_translator.to_rs_id(strategy, image_id)
140-
141- def _from_rs_id(self, rs_image_id):
142- """
143- Convert an image id from the Rackspace API format (an int) to the
144- format of our ImageService strategy.
145- """
146- strategy = self._service.__class__.__name__
147- return self._id_translator.from_rs_id(strategy, rs_image_id)
148+ self._service = nova.image.service.ImageService.load()
149+ self._id_translator = _id_translator.RackspaceAPIIdTranslator(
150+ "image", self._service.__class__.__name__)
151
152 def index(self, req):
153- """Return all public images."""
154+ """Return all public images in brief."""
155+ return dict(images=[dict(id=img['id'], name=img['name'])
156+ for img in self.detail(req)['images']])
157+
158+ def detail(self, req):
159+ """Return all public images in detail."""
160 data = self._service.index()
161 for img in data:
162- img['id'] = self._to_rs_id(img['id'])
163+ img['id'] = self._id_translator.to_rs_id(img['id'])
164 return dict(images=data)
165
166 def show(self, req, id):
167 """Return data about the given image id."""
168- opaque_id = self._from_rs_id(id)
169+ opaque_id = self._id_translator.from_rs_id(id)
170 img = self._service.show(opaque_id)
171 img['id'] = id
172 return dict(image=img)
173@@ -78,40 +68,3 @@
174 # Users may not modify public images, and that's all that
175 # we support for now.
176 raise exc.HTTPNotFound()
177-
178-
179-class RackspaceAPIImageIdTranslator(object):
180- """
181- Converts Rackspace API image ids to and from the id format for a given
182- strategy.
183- """
184-
185- def __init__(self):
186- self._store = datastore.Redis.instance()
187- self._key_template = "rsapi.idstrategies.image.%s.%s"
188-
189- def to_rs_id(self, strategy_name, opaque_id):
190- """Convert an id from a strategy-specific one to a Rackspace one."""
191- key = self._key_template % (strategy_name, "fwd")
192- result = self._store.hget(key, str(opaque_id))
193- if result: # we have a mapping from opaque to RS for this strategy
194- return int(result)
195- else:
196- # Store the mapping.
197- nextid = self._store.incr("%s.lastid" % key)
198- if self._store.hsetnx(key, str(opaque_id), nextid):
199- # If someone else didn't beat us to it, store the reverse
200- # mapping as well.
201- key = self._key_template % (strategy_name, "rev")
202- self._store.hset(key, nextid, str(opaque_id))
203- return nextid
204- else:
205- # Someone beat us to it; use their number instead, and
206- # discard nextid (which is OK -- we don't require that
207- # every int id be used.)
208- return int(self._store.hget(key, str(opaque_id)))
209-
210- def from_rs_id(self, strategy_name, rs_id):
211- """Convert a Rackspace id to a strategy-specific one."""
212- key = self._key_template % (strategy_name, "rev")
213- return self._store.hget(key, rs_id)
214
215=== modified file 'nova/compute/instance_types.py'
216--- nova/compute/instance_types.py 2010-07-18 17:15:12 +0000
217+++ nova/compute/instance_types.py 2010-08-25 21:50:55 +0000
218@@ -21,10 +21,10 @@
219 The built-in instance properties.
220 """
221
222-INSTANCE_TYPES = {}
223-INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0}
224-INSTANCE_TYPES['m1.small'] = {'memory_mb': 1024, 'vcpus': 1, 'local_gb': 10}
225-INSTANCE_TYPES['m1.medium'] = {'memory_mb': 2048, 'vcpus': 2, 'local_gb': 10}
226-INSTANCE_TYPES['m1.large'] = {'memory_mb': 4096, 'vcpus': 4, 'local_gb': 10}
227-INSTANCE_TYPES['m1.xlarge'] = {'memory_mb': 8192, 'vcpus': 4, 'local_gb': 10}
228-INSTANCE_TYPES['c1.medium'] = {'memory_mb': 2048, 'vcpus': 4, 'local_gb': 10}
229+INSTANCE_TYPES = {
230+ 'm1.tiny': dict(memory_mb=512, vcpus=1, local_gb=0, flavorid=1),
231+ 'm1.small': dict(memory_mb=1024, vcpus=1, local_gb=10, flavorid=2),
232+ 'm1.medium': dict(memory_mb=2048, vcpus=2, local_gb=10, flavorid=3),
233+ 'm1.large': dict(memory_mb=4096, vcpus=4, local_gb=10, flavorid=4),
234+ 'm1.xlarge': dict(memory_mb=8192, vcpus=4, local_gb=10, flavorid=5),
235+ 'c1.medium': dict(memory_mb=2048, vcpus=4, local_gb=10, flavorid=6)}
236
237=== added file 'nova/image/__init__.py'