Merge lp:~vladimir.p/nova/admin-vm into lp:~hudson-openstack/nova/trunk
- admin-vm
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~vladimir.p/nova/admin-vm |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
2176 lines (+1311/-96) 20 files modified
Authors (+1/-0) bin/nova-manage (+120/-11) nova/api/ec2/admin.py (+16/-1) nova/api/ec2/cloud.py (+134/-18) nova/api/openstack/create_instance_helper.py (+15/-1) nova/api/openstack/servers.py (+9/-1) nova/cloudpipe/pipelib.py (+1/-0) nova/compute/api.py (+95/-47) nova/compute/instance_categories.py (+196/-0) nova/compute/manager.py (+3/-1) nova/db/api.py (+58/-2) nova/db/sqlalchemy/api.py (+160/-10) nova/db/sqlalchemy/migrate_repo/versions/029_add_instance_categories.py (+152/-0) nova/db/sqlalchemy/migration.py (+1/-1) nova/db/sqlalchemy/models.py (+19/-1) nova/exception.py (+17/-0) nova/flags.py (+10/-0) nova/tests/api/openstack/test_servers.py (+4/-0) nova/tests/test_instance_categories.py (+295/-0) nova/virt/libvirt/firewall.py (+5/-2) |
To merge this branch: | bzr merge lp:~vladimir.p/nova/admin-vm |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dan Prince (community) | Needs Fixing | ||
Brian Lamar (community) | Needs Fixing | ||
Devin Carlen (community) | Needs Fixing | ||
Josh Kearney (community) | Needs Fixing | ||
Brian Waldon (community) | Needs Information | ||
Review via email: mp+63771@code.launchpad.net |
This proposal supersedes a proposal from 2011-06-07.
Commit message
Description of the change
First cut of Admin-VM changes:
- added instance categories. By default, all instances have 'regular' category assigned
- each category has default image_id
- new param "category" in ec2/openstack APIs
- filter admin instances/
Devin Carlen (devcamcar) wrote : Posted in a previous version of this proposal | # |
Vladimir Popovski (vladimir.p) wrote : Posted in a previous version of this proposal | # |
Hi Devin,
Thanks. Working on merging trunk back to our code. Will be ready soon.
Brian Waldon (bcwaldon) wrote : | # |
Since this is a relatively major feature, I would like to see the blueprint expanded to describe the implementation. In particular, I want to hear why adding a 'category' to an instance is the right path to take. Why not use the metadata container?
This could also be considered an admin-only feature that doesn't belong in the end-user apis. I'm not crazy about seeing undocumented changes to the OpenStack API.
I'm pretty excited about this feature, I just want to make sure we are doing it right :)
Vladimir Popovski (vladimir.p) wrote : | # |
Hi Brian,
Please take a look at short design description we put into Blueprint.
Thanks,
-Vladimir
-----Original Message-----
From: <email address hidden> [mailto:<email address hidden>] On Behalf Of
Brian Waldon
Sent: Wednesday, June 08, 2011 12:58 PM
To: <email address hidden>
Subject: Re: [Merge] lp:~vladimir.p/nova/admin-vm into lp:nova
Review: Needs Information
Since this is a relatively major feature, I would like to see the blueprint
expanded to describe the implementation. In particular, I want to hear why
adding a 'category' to an instance is the right path to take. Why not use
the metadata container?
This could also be considered an admin-only feature that doesn't belong in
the end-user apis. I'm not crazy about seeing undocumented changes to the
OpenStack API.
I'm pretty excited about this feature, I just want to make sure we are doing
it right :)
--
https:/
You are the owner of lp:~vladimir.p/nova/admin-vm.
Brian Waldon (bcwaldon) wrote : | # |
Sorry it took me so long to get back to you Vladimir! Hopefully we can get the ball rolling again...
First off, there are some merge conflicts that needs to be resolved.
I'm still not quite sold on the design here. I'd like some other nova-core members to take a look at the blueprint/diff and share their thoughts.
Like I said before, I don't think we should make changes to the core OSAPI. It's looking like you will have to implement the api code as an extension unless you can convince somebody to add it to the admin api. I'd be more than happy to talk to you about this off the MP if you need some guidance here.
Josh Kearney (jk0) wrote : | # |
Looks like you still have a few merge conflicts to handle.
Vladimir Popovski (vladimir.p) wrote : | # |
Merged with latest nova revision (#1187)
- 1112. By Vladimir Popovski
-
Pep8 normalization
- 1113. By Vladimir Popovski
-
changed unit_test properties (otherwise was skipped)
Devin Carlen (devcamcar) wrote : | # |
Hi Vladimir, I agree with Brian that you need to implement the apis as extensions.
Vladimir Popovski (vladimir.p) wrote : | # |
ok, I'm fine with API extensions, but how about the general direction/idea to have instance categories (wanted to use name type, but it is already taken for describing config types/flavors)?
If having instance categories will not be approved by community there is no point in continuing in this direction.
Any feedback on categories?
Brian Waldon (bcwaldon) wrote : | # |
> ok, I'm fine with API extensions, but how about the general direction/idea to
> have instance categories (wanted to use name type, but it is already taken for
> describing config types/flavors)?
>
> If having instance categories will not be approved by community there is no
> point in continuing in this direction.
>
> Any feedback on categories?
Personally, I feel this could be solved through the use of system-level metadata on an instance. We don't have that capability right now, but I think it would be a good fit for this. It would allow a category to be set on an instance when it needed to be, and totally ignore it when it doesn't matter. What do you think?
Vladimir Popovski (vladimir.p) wrote : | # |
I suppose there are 3 general aspects for categories:
- APIs for setting categories per instance (and everything related to APIs)
- having a separate table where different category-wise settings might be stored
- distinguishing if instance belongs to category or not (where to store this field)
Having a separate table for categories helps to organize things, support different types (categories) of instances and establish better relations between tables. Potentially, we could link categories table to instance_types and set up special configurations/
It is definitely possible to store this category field in the instance_metadata table as a key/value pair and we were thinking about it at the beginning. But it complicates SQL queries and you can't build normal SQL relations between key/value & categories table.
It will require first to retrieve a value and only after that to perform an additional query to retrieve properties of this category ...
Another use-case - filtering of non-regular instances. Now for each DescribeInstances call we will need to look at meta-data key/value pairs and find out if it should be hidden or not.
But I guess the main question about categories - if we think that it might be one of basic instance parameters that will benefit every installation/
Brian Waldon (bcwaldon) wrote : | # |
Here's how understand the problem you are trying to solve with this merge prop: We need the ability for a deployer to create an instance that a tenant cannot access. This instance needs to be able to access the tenant's network. Please help me understand if this isn't 100% correct.
My first thoughts when solving the problem (as I defined it) are to allow an admin to change the network associated with an instance. Therefore, any instance he creates can be plugged into any project network. Do you think this is a viable solution?
I just want to make sure there isn't a simpler solution to this problem. This merge prop adds a lot of code to solve what seems to be a rather simple problem.
Vladimir Popovski (vladimir.p) wrote : | # |
Actually, we are trying to do something bigger than that - we would like to
be able to spawn special instances with their special properties.
Different types of instances may perform different tasks. For example,
"virtual storage controllers / arrays" providing HA block storage to
project's tenants. Another example might be a dedicated billing or IO
statistics server per project.
If instance will spawn multiple projects ... probably, but t will require
changes on networking level and we decided to wait for completion of
multi-nic implementation for that.
In general, tenants should not be able to access those instances in the same
manner as they access regular instances, but this is a slightly different
issue. They might be able to access such instances using REST APIs for
retrieving some data.
Does it make sense?
Vish Ishaya (vishvananda) wrote : | # |
Brian: I think if we have plugin networking + admin managed metadata we have the components that we need. I'm a fan of putting the metadata into a generalized extra_data table. Other than that a standard way of defining an admin vm would be very valuable. It seems like a "category" definition could be defined by a tenant, networks to plug into (and whether to plug into one or more users network), base image, flavor, injected user data, and some small amount of metadata. Then you could have a command that would launch an image based on a category definition.
The special scheduler would have enough information based on the metadata to make any necessary decisions.
Vish Ishaya (vishvananda) wrote : | # |
> In general, tenants should not be able to access those instances in the same
> manner as they access regular instances, but this is a slightly different
> issue.
This can be responsible by running the instances as a different tenant
> They might be able to access such instances using REST APIs for
> retrieving some data.
A simple command for retrieving data could get data from the flavor or metadata to return to the user.
A complicated command would require a guest agent. As long as you can specify a specific (administration) network to plug into then your REST API should be able to communicate to the guest agent to run commands.
So a complicated category def would say: run the instance as this tenant, using this image id (which has a guest agent), this special metadata, with flavor x, plugged into the admin network and the network for tenant x. Alternatively, in flatdhcp you could specify to allow access from certain tenant security groups in place of arbitrary networks.
Vish
Brian Lamar (blamar) wrote : | # |
=== modified file 'nova/virt/
=== modified file 'nova/virt/
Should these files have been made executable?
Also it seems you may have added an import to connection.py which is unused.
Lines 107-109, 132-134, 150-151, 171-172, 197-198, and potentially others needs docstring/sphinx argument standardization:
"""One line summary.
Optional extra information which might be multiple lines.
:param param_one: This is a description of param one.
:param param_two: This is a description of param two.
:returns: This is a description of the return value.
"""
OR
"""This is a description of this method."""
Vladimir Popovski (vladimir.p) wrote : | # |
Thanks.
Re: properties changed from -x to +x - we missed it. Seems like samba on one of dev servers was incorrectly configured. will fix it.
Re: probably a leftover from the merge with one of latest nova versions (when connection was removed). Will fix it
Re: comments standardization - fine. will fix it.
- 1114. By Vladimir Popovski
-
merged with 1221
- 1115. By Vladimir Popovski
-
merged with 1222
- 1116. By Vladimir Popovski
-
replaced my fix for nova-api with community's
Vladimir Popovski (vladimir.p) wrote : | # |
- fixed all conflicts related to recent merges
- comments in nova-manage are complied with other comments in this exec module
Brian Waldon (bcwaldon) wrote : | # |
Would you mind calling this something other than 'category'? To me, a category is a grouping of similar things. I think what we are going for here is more of a 'class': a predefined configuration of an instance. Does that seem reasonable?
Vladimir Popovski (vladimir.p) wrote : | # |
I suppose the best term for it is "type", but it is taken by
flavor/
-----Original Message-----
From: <email address hidden> [mailto:<email address hidden>] On Behalf Of
Brian Waldon
Sent: Wednesday, June 29, 2011 7:17 AM
To: <email address hidden>
Subject: Re: [Merge] lp:~vladimir.p/nova/admin-vm into lp:nova
Would you mind calling this something other than 'category'? To me, a
category is a grouping of similar things. I think what we are going for here
is more of a 'class': a predefined configuration of an instance. Does that
seem reasonable?
--
https:/
You are the owner of lp:~vladimir.p/nova/admin-vm.
Brian Waldon (bcwaldon) wrote : | # |
The term 'class' may not be the best option, either, as it is a well-defined keyword in python.
Brian Waldon (bcwaldon) wrote : | # |
What about 'InstancePlan' or 'InstanceConfig?
Vladimir Popovski (vladimir.p) wrote : | # |
Definitely not InstanceConfig :-)
I suppose the main purpose is to distinguish between regular compute
instances and administrative instances (instances created for other needs):
statistics/billing VMs, some special DBs, instances for hosting Virtual
Storage arrays/controllers, even VPN instances, etc. Most likely such
instances will have different configuration description / templates, but it
is probably a different question.
I'm open to any suggestions, but for me InstanceType or InstanceCategory are
winners... meanwhile :-)
Dan Prince (dan-prince) wrote : | # |
Hi Vladimir,
A couple more merge conflicts for this one:
Text conflict in bin/nova-manage
Text conflict in nova/api/
Text conflict in nova/compute/api.py
3 conflicts encountered.
Vladimir Popovski (vladimir.p) wrote : | # |
I'm missing it. The Launchpad doesn't show any conflict.
Anyway, I suppose there might be conflicts related to added new parameters
or changed prints, but how about general feedback about direction?
Thanks.
Brian Waldon (bcwaldon) wrote : | # |
> I'm missing it. The Launchpad doesn't show any conflict.
Launchpad isn't great at showing conflicts that develop after a merge prop is submitted. On your local machine, try to merge trunk into your branch. That will uncover what Dan pointed out.
Unmerged revisions
- 1116. By Vladimir Popovski
-
replaced my fix for nova-api with community's
- 1115. By Vladimir Popovski
-
merged with 1222
- 1114. By Vladimir Popovski
-
merged with 1221
- 1113. By Vladimir Popovski
-
changed unit_test properties (otherwise was skipped)
- 1112. By Vladimir Popovski
-
Pep8 normalization
- 1111. By Vladimir Popovski
-
Admin-VM: merged with 1187
- 1110. By Vladimir Popovski
-
Admin-VM: merged with 1186
- 1109. By Vladimir Popovski
-
pep8 code normalization
- 1108. By Vladimir Popovski
-
Admin-VM: removed nova.conf (accidentally added)
- 1107. By Vladimir Popovski
-
Admin-VM: merged with nova trunk-1159
Preview Diff
1 | === modified file 'Authors' |
2 | --- Authors 2011-06-28 00:23:19 +0000 |
3 | +++ Authors 2011-06-28 22:54:37 +0000 |
4 | @@ -88,6 +88,7 @@ |
5 | Vasiliy Shlykov <vash@vasiliyshlykov.org> |
6 | Vishvananda Ishaya <vishvananda@gmail.com> |
7 | Vivek Y S <vivek.ys@gmail.com> |
8 | +Vladimir Popovski <vladimir@zadarastorage.com> |
9 | William Wolf <throughnothing@gmail.com> |
10 | Yoshiaki Tamura <yoshi@midokura.jp> |
11 | Youcef Laribi <Youcef.Laribi@eu.citrix.com> |
12 | |
13 | === modified file 'bin/nova-manage' |
14 | --- bin/nova-manage 2011-06-25 19:38:07 +0000 |
15 | +++ bin/nova-manage 2011-06-28 22:54:37 +0000 |
16 | @@ -87,6 +87,7 @@ |
17 | from nova.auth import manager |
18 | from nova.cloudpipe import pipelib |
19 | from nova.compute import instance_types |
20 | +from nova.compute import instance_categories |
21 | from nova.db import migration |
22 | |
23 | FLAGS = flags.FLAGS |
24 | @@ -140,7 +141,9 @@ |
25 | ipport = "%s:%s" % (project.vpn_ip, project.vpn_port) |
26 | print "%-20s\t" % ipport, |
27 | ctxt = context.get_admin_context() |
28 | - vpn = db.instance_get_project_vpn(ctxt, project.id) |
29 | + cat_id = instance_categories.get_instance_cat_id_by_name( |
30 | + FLAGS.instance_cat_vpn) |
31 | + vpn = db.instance_get_project_admin(ctxt, project.id, cat_id) |
32 | if vpn: |
33 | address = None |
34 | state = 'down' |
35 | @@ -611,14 +614,14 @@ |
36 | class VmCommands(object): |
37 | """Class for mangaging VM instances.""" |
38 | |
39 | - def list(self, host=None): |
40 | + def list(self, host=None, cat_id=None): |
41 | """Show a list of all instances |
42 | |
43 | :param host: show all instance on specified host. |
44 | :param instance: show specificed instance. |
45 | """ |
46 | print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ |
47 | - " %-10s %-10s %-10s %-5s" % ( |
48 | + " %-10s %-10s %-10s %-5s %-8s" % ( |
49 | _('instance'), |
50 | _('node'), |
51 | _('type'), |
52 | @@ -630,20 +633,23 @@ |
53 | _('project'), |
54 | _('user'), |
55 | _('zone'), |
56 | - _('index')) |
57 | + _('index'), |
58 | + _('category')) |
59 | |
60 | - if host is None: |
61 | - instances = db.instance_get_all(context.get_admin_context()) |
62 | + ctxt = context.get_admin_context() |
63 | + if cat_id is not None: |
64 | + instances = db.instance_get_all_by_cat(ctxt, cat_id) |
65 | + elif host is None: |
66 | + instances = db.instance_get_all(ctxt) |
67 | else: |
68 | - instances = db.instance_get_all_by_host( |
69 | - context.get_admin_context(), host) |
70 | + instances = db.instance_get_all_by_host(ctxt, host) |
71 | |
72 | for instance in instances: |
73 | print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ |
74 | - " %-10s %-10s %-10s %-5d" % ( |
75 | + " %-10s %-10s %-10s %-5d %-8s" % ( |
76 | instance['hostname'], |
77 | instance['host'], |
78 | - instance['instance_type'], |
79 | + instance['instance_type']['name'], |
80 | instance['state_description'], |
81 | instance['launched_at'], |
82 | instance['image_id'], |
83 | @@ -652,7 +658,8 @@ |
84 | instance['project_id'], |
85 | instance['user_id'], |
86 | instance['availability_zone'], |
87 | - instance['launch_index']) |
88 | + instance['launch_index'], |
89 | + instance['instance_cat']['name']) |
90 | |
91 | def live_migration(self, ec2_id, dest): |
92 | """Migrates a running instance to a new machine. |
93 | @@ -937,6 +944,98 @@ |
94 | self._print_instance_types(name, inst_types) |
95 | |
96 | |
97 | +class InstanceCatCommands(object): |
98 | + """Class for managing instance categories.""" |
99 | + |
100 | + def _print_instance_categories(self, name, val): |
101 | + deleted = ('', ', inactive')[val["deleted"] == 1] |
102 | + print ("%-16s: id: %2d, Desc: %-30s, image_id %-10s, Specs: %s%s") % \ |
103 | + (name, val["id"], val["description"], val["image_id"], |
104 | + val["specs"], deleted) |
105 | + |
106 | + def create(self, name, description, image_id=None, specs=None): |
107 | + """Creates instance category |
108 | + arguments: name description [image_id] [specs] |
109 | + """ |
110 | + try: |
111 | + instance_categories.create(name, description, image_id, specs) |
112 | + except exception.InvalidInputException: |
113 | + print "Must supply valid parameters to create instance_category" |
114 | + print e |
115 | + sys.exit(1) |
116 | + except exception.ApiError, e: |
117 | + print "\n\n" |
118 | + print "\n%s" % e |
119 | + print "Please ensure category name is unique." |
120 | + print "To complete remove a category, use the --purge flag:" |
121 | + print "\n # nova-manage instance_category delete <name> --purge" |
122 | + print "\nCurrently defined category names:" |
123 | + self.list("--all") |
124 | + sys.exit(2) |
125 | + except: |
126 | + print "Unknown error" |
127 | + sys.exit(3) |
128 | + else: |
129 | + print "Category %s created" % name |
130 | + |
131 | + def update(self, name, description=None, image_id=None, specs=None): |
132 | + """Updates instance category |
133 | + arguments: name [description] [image_id] [specs] |
134 | + """ |
135 | + try: |
136 | + instance_categories.update(name, description, image_id, specs) |
137 | + except exception.ApiError: |
138 | + print "Valid instance category name is required" |
139 | + sys.exit(1) |
140 | + except exception.DBError, e: |
141 | + print "DB Error: %s" % e |
142 | + sys.exit(2) |
143 | + except: |
144 | + print "Unknown error" |
145 | + sys.exit(3) |
146 | + else: |
147 | + print "Category %s updated" % (name) |
148 | + |
149 | + def delete(self, name, purge=None): |
150 | + """Marks instance category as deleted |
151 | + arguments: name""" |
152 | + try: |
153 | + if purge == "--purge": |
154 | + instance_categories.purge(name) |
155 | + verb = "purged" |
156 | + else: |
157 | + instance_categories.destroy(name) |
158 | + verb = "deleted" |
159 | + except exception.ApiError: |
160 | + print "Valid instance category name is required" |
161 | + sys.exit(1) |
162 | + except exception.DBError, e: |
163 | + print "DB Error: %s" % e |
164 | + sys.exit(2) |
165 | + except: |
166 | + sys.exit(3) |
167 | + else: |
168 | + print "Category %s %s" % (name, verb) |
169 | + |
170 | + def list(self, name=None): |
171 | + """Lists all active or specific instance categories |
172 | + arguments: [name]""" |
173 | + try: |
174 | + if name is None: |
175 | + inst_cats = instance_categories.get_all_categories() |
176 | + elif name == "--all": |
177 | + inst_cats = instance_categories.get_all_categories(True) |
178 | + else: |
179 | + inst_cats = instance_categories.get_instance_cat_by_name(name) |
180 | + except exception.DBError, e: |
181 | + _db_error(e) |
182 | + if isinstance(inst_cats.values()[0], dict): |
183 | + for k, v in inst_cats.iteritems(): |
184 | + self._print_instance_categories(k, v) |
185 | + else: |
186 | + self._print_instance_categories(name, inst_cats) |
187 | + |
188 | + |
189 | class ImageCommands(object): |
190 | """Methods for dealing with a cloud in an odd state""" |
191 | |
192 | @@ -1075,6 +1174,15 @@ |
193 | self._convert_images(other_images) |
194 | self._convert_images(machine_images) |
195 | |
196 | + def show_admin_images(self, category=None): |
197 | + """Show registered admin images [per category] |
198 | + arguments: [category]""" |
199 | + |
200 | + if category == "--all": |
201 | + category = None |
202 | + image_names = instance_categories.get_admin_image_names(category) |
203 | + print image_names |
204 | + |
205 | |
206 | class AgentBuildCommands(object): |
207 | """Class for managing agent builds.""" |
208 | @@ -1158,6 +1266,7 @@ |
209 | ('fixed', FixedIpCommands), |
210 | ('flavor', InstanceTypeCommands), |
211 | ('floating', FloatingIpCommands), |
212 | + ('instance_category', InstanceCatCommands), |
213 | ('instance_type', InstanceTypeCommands), |
214 | ('image', ImageCommands), |
215 | ('network', NetworkCommands), |
216 | |
217 | === modified file 'nova/api/ec2/admin.py' |
218 | --- nova/api/ec2/admin.py 2011-06-25 19:38:07 +0000 |
219 | +++ nova/api/ec2/admin.py 2011-06-28 22:54:37 +0000 |
220 | @@ -33,6 +33,7 @@ |
221 | from nova import utils |
222 | from nova.api.ec2 import ec2utils |
223 | from nova.auth import manager |
224 | +from nova.compute import instance_categories |
225 | |
226 | |
227 | FLAGS = flags.FLAGS |
228 | @@ -91,6 +92,13 @@ |
229 | 'flavor_id': inst['flavorid']} |
230 | |
231 | |
232 | +def instance_cat_dict(inst): |
233 | + return {'name': inst['name'], |
234 | + 'description': inst['description'], |
235 | + 'image_id': inst['image_id'], |
236 | + 'specs': inst['specs']} |
237 | + |
238 | + |
239 | def vpn_dict(project, vpn_instance): |
240 | rv = {'project_id': project.id, |
241 | 'public_ip': project.vpn_ip, |
242 | @@ -129,6 +137,11 @@ |
243 | return {'instanceTypeSet': [instance_dict(v) for v in |
244 | db.instance_type_get_all(context).values()]} |
245 | |
246 | + def describe_instance_categories(self, context, **_kwargs): |
247 | + """Returns all active instance categories data""" |
248 | + return {'instanceCatSet': [instance_cat_dict(v) for v in |
249 | + db.instance_cat_get_all(context).values()]} |
250 | + |
251 | def describe_user(self, _context, name, **_kwargs): |
252 | """Returns user data, including access and secret keys.""" |
253 | return user_dict(manager.AuthManager().get_user(name)) |
254 | @@ -272,7 +285,9 @@ |
255 | def _vpn_for(self, context, project_id): |
256 | """Get the VPN instance for a project ID.""" |
257 | for instance in db.instance_get_all_by_project(context, project_id): |
258 | - if (instance['image_id'] == str(FLAGS.vpn_image_id) |
259 | + if (instance_categories.is_admin_instance( |
260 | + instance, |
261 | + FLAGS.instance_cat_vpn) \ |
262 | and not instance['state_description'] in |
263 | ['shutting_down', 'shutdown']): |
264 | return instance |
265 | |
266 | === modified file 'nova/api/ec2/cloud.py' |
267 | --- nova/api/ec2/cloud.py 2011-06-25 19:38:07 +0000 |
268 | +++ nova/api/ec2/cloud.py 2011-06-28 22:54:37 +0000 |
269 | @@ -44,6 +44,7 @@ |
270 | from nova import volume |
271 | from nova.api.ec2 import ec2utils |
272 | from nova.compute import instance_types |
273 | +from nova.compute import instance_categories |
274 | from nova.image import s3 |
275 | |
276 | |
277 | @@ -620,6 +621,40 @@ |
278 | return self.compute_api.get_vnc_console(context, |
279 | instance_id=instance_id) |
280 | |
281 | + def _filter_volumes(self, context, volumes, category): |
282 | + result = [] |
283 | + for volume in volumes: |
284 | + add_image = False |
285 | + if volume.get('instance', None): |
286 | + instance_id = volume['instance']['id'] |
287 | + instance = db.instance_get(context, instance_id) |
288 | + is_admin_inst = \ |
289 | + instance_categories.is_admin_instance(instance, category) |
290 | + else: |
291 | + is_admin_inst = False |
292 | + |
293 | + if category is None: |
294 | + if context.is_admin \ |
295 | + or not is_admin_inst: |
296 | + add_image = True |
297 | + else: |
298 | + if is_admin_inst: |
299 | + add_image = True |
300 | + |
301 | + if add_image: |
302 | + LOG.debug(("filter_volumes: +++ add volume %d, " |
303 | + "is_admin_inst %s, cat %s, is_admin=%s"), |
304 | + volume['id'], str(is_admin_inst), category, |
305 | + str(context.is_admin)) |
306 | + result.append(volume) |
307 | + else: |
308 | + LOG.debug(("filter_volumes: --- skip volume %d, " |
309 | + "is_admin_inst %s, cat %s, is_admin=%s"), |
310 | + volume['id'], str(is_admin_inst), category, |
311 | + str(context.is_admin)) |
312 | + |
313 | + return result |
314 | + |
315 | def describe_volumes(self, context, volume_id=None, **kwargs): |
316 | if volume_id: |
317 | volumes = [] |
318 | @@ -629,7 +664,13 @@ |
319 | volumes.append(volume) |
320 | else: |
321 | volumes = self.volume_api.get_all(context) |
322 | - volumes = [self._format_volume(context, v) for v in volumes] |
323 | + |
324 | + category = self._get_category_name(**kwargs) |
325 | + filtered_volumes = self._filter_volumes(context, |
326 | + volumes, |
327 | + category=category) |
328 | + |
329 | + volumes = [self._format_volume(context, v) for v in filtered_volumes] |
330 | return {'volumeSet': volumes} |
331 | |
332 | def _format_volume(self, context, volume): |
333 | @@ -748,6 +789,16 @@ |
334 | lst = [lst] |
335 | return [{label: x} for x in lst] |
336 | |
337 | + def _get_category_name(self, **kwargs): |
338 | + return kwargs.get('category', None) |
339 | + |
340 | + def _get_category(self, **kwargs): |
341 | + cat_name = self._get_category_name(**kwargs) |
342 | + if cat_name is None: |
343 | + return instance_categories.get_default_instance_cat() |
344 | + else: |
345 | + return instance_categories.get_instance_cat_by_name(cat_name) |
346 | + |
347 | def describe_instances(self, context, **kwargs): |
348 | return self._format_describe_instances(context, **kwargs) |
349 | |
350 | @@ -758,8 +809,10 @@ |
351 | def _format_describe_instances(self, context, **kwargs): |
352 | return {'reservationSet': self._format_instances(context, **kwargs)} |
353 | |
354 | - def _format_run_instances(self, context, reservation_id): |
355 | - i = self._format_instances(context, reservation_id=reservation_id) |
356 | + def _format_run_instances(self, context, reservation_id, category): |
357 | + i = self._format_instances(context, |
358 | + reservation_id=reservation_id, |
359 | + category=category) |
360 | assert len(i) == 1 |
361 | return i[0] |
362 | |
363 | @@ -772,16 +825,18 @@ |
364 | # NOTE(vish): instance_id is an optional list of ids to filter by |
365 | if instance_id: |
366 | instances = [] |
367 | + instance_cat = self._get_category(**kwargs) |
368 | for ec2_id in instance_id: |
369 | internal_id = ec2utils.ec2_id_to_id(ec2_id) |
370 | instance = self.compute_api.get(context, |
371 | - instance_id=internal_id) |
372 | + instance_id=internal_id, |
373 | + instance_cat=instance_cat) |
374 | instances.append(instance) |
375 | else: |
376 | instances = self.compute_api.get_all(context, **kwargs) |
377 | for instance in instances: |
378 | if not context.is_admin: |
379 | - if instance['image_ref'] == str(FLAGS.vpn_image_id): |
380 | + if instance_categories.is_admin_instance(instance): |
381 | continue |
382 | i = {} |
383 | instance_id = instance['id'] |
384 | @@ -820,6 +875,10 @@ |
385 | i['instanceType'] = instance['instance_type'].get('name') |
386 | else: |
387 | i['instanceType'] = None |
388 | + if instance['instance_cat']: |
389 | + i['category'] = instance['instance_cat'].get('name') |
390 | + else: |
391 | + i['category'] = None |
392 | i['launchTime'] = instance['created_at'] |
393 | i['amiLaunchIndex'] = instance['launch_index'] |
394 | i['displayName'] = instance['display_name'] |
395 | @@ -938,6 +997,12 @@ |
396 | if image_state != 'available': |
397 | raise exception.ApiError(_('Image must be available')) |
398 | |
399 | + category = self._get_category_name(**kwargs) |
400 | + instance_cat = self._get_category(**kwargs) |
401 | + if instance_categories.is_admin_category(instance_cat) \ |
402 | + and not context.is_admin: |
403 | + raise exception.AdminRequired() |
404 | + |
405 | instances = self.compute_api.create(context, |
406 | instance_type=instance_types.get_instance_type_by_name( |
407 | kwargs.get('instance_type', None)), |
408 | @@ -953,53 +1018,67 @@ |
409 | security_group=kwargs.get('security_group'), |
410 | availability_zone=kwargs.get('placement', {}).get( |
411 | 'AvailabilityZone'), |
412 | - block_device_mapping=kwargs.get('block_device_mapping', {})) |
413 | + block_device_mapping=kwargs.get('block_device_mapping', {}), |
414 | + instance_cat=instance_cat) |
415 | return self._format_run_instances(context, |
416 | - instances[0]['reservation_id']) |
417 | + instances[0]['reservation_id'], |
418 | + category=category) |
419 | |
420 | - def _do_instance(self, action, context, ec2_id): |
421 | + def _do_instance(self, action, context, ec2_id, instance_cat=None): |
422 | instance_id = ec2utils.ec2_id_to_id(ec2_id) |
423 | - action(context, instance_id=instance_id) |
424 | + action(context, instance_id=instance_id, instance_cat=instance_cat) |
425 | |
426 | - def _do_instances(self, action, context, instance_id): |
427 | + def _do_instances(self, action, context, instance_id, instance_cat=None): |
428 | for ec2_id in instance_id: |
429 | - self._do_instance(action, context, ec2_id) |
430 | + self._do_instance(action, context, ec2_id, instance_cat) |
431 | |
432 | def terminate_instances(self, context, instance_id, **kwargs): |
433 | """Terminate each instance in instance_id, which is a list of ec2 ids. |
434 | instance_id is a kwarg so its name cannot be modified.""" |
435 | LOG.debug(_("Going to start terminating instances")) |
436 | - self._do_instances(self.compute_api.delete, context, instance_id) |
437 | + instance_cat = self._get_category(**kwargs) |
438 | + self._do_instances(self.compute_api.delete, context, instance_id, |
439 | + instance_cat) |
440 | return True |
441 | |
442 | def reboot_instances(self, context, instance_id, **kwargs): |
443 | """instance_id is a list of instance ids""" |
444 | LOG.audit(_("Reboot instance %r"), instance_id, context=context) |
445 | - self._do_instances(self.compute_api.reboot, context, instance_id) |
446 | + instance_cat = self._get_category(**kwargs) |
447 | + self._do_instances(self.compute_api.reboot, context, instance_id, |
448 | + instance_cat) |
449 | return True |
450 | |
451 | def stop_instances(self, context, instance_id, **kwargs): |
452 | """Stop each instances in instance_id. |
453 | Here instance_id is a list of instance ids""" |
454 | LOG.debug(_("Going to stop instances")) |
455 | - self._do_instances(self.compute_api.stop, context, instance_id) |
456 | + instance_cat = self._get_category(**kwargs) |
457 | + self._do_instances(self.compute_api.stop, context, instance_id, |
458 | + instance_cat) |
459 | return True |
460 | |
461 | def start_instances(self, context, instance_id, **kwargs): |
462 | """Start each instances in instance_id. |
463 | Here instance_id is a list of instance ids""" |
464 | LOG.debug(_("Going to start instances")) |
465 | - self._do_instances(self.compute_api.start, context, instance_id) |
466 | + instance_cat = self._get_category(**kwargs) |
467 | + self._do_instances(self.compute_api.start, context, instance_id, |
468 | + instance_cat) |
469 | return True |
470 | |
471 | def rescue_instance(self, context, instance_id, **kwargs): |
472 | """This is an extension to the normal ec2_api""" |
473 | - self._do_instance(self.compute_api.rescue, contect, instnace_id) |
474 | + instance_cat = self._get_category(**kwargs) |
475 | + self._do_instance(self.compute_api.rescue, context, instance_id, |
476 | + instance_cat) |
477 | return True |
478 | |
479 | def unrescue_instance(self, context, instance_id, **kwargs): |
480 | """This is an extension to the normal ec2_api""" |
481 | - self._do_instance(self.compute_api.unrescue, context, instance_id) |
482 | + instance_cat = self._get_category(**kwargs) |
483 | + self._do_instance(self.compute_api.unrescue, context, instance_id, |
484 | + instance_cat) |
485 | return True |
486 | |
487 | def update_instance(self, context, instance_id, **kwargs): |
488 | @@ -1083,6 +1162,38 @@ |
489 | i['architecture'] = image['properties'].get('architecture') |
490 | return i |
491 | |
492 | + def _filter_images(self, context, images, category): |
493 | + result = [] |
494 | + admin_image_names = instance_categories.get_admin_image_names(category) |
495 | + for i in admin_image_names: |
496 | + LOG.debug(_("admin_image_names: %s"), i) |
497 | + |
498 | + for image in images: |
499 | + name = str(image.get('id')) |
500 | + add_name = False |
501 | + if category is None: |
502 | + # if no category set - filter all admin images |
503 | + #if name not in admin_image_names: |
504 | + if context.is_admin \ |
505 | + or name not in admin_image_names: |
506 | + add_name = True |
507 | + else: |
508 | + # if category is set - return images for this category only |
509 | + if name in admin_image_names: |
510 | + add_name = True |
511 | + |
512 | + if add_name: |
513 | + LOG.debug(("filter_images: +++ add name %s, " |
514 | + "category %s, is_admin=%s"), |
515 | + name, category, str(context.is_admin)) |
516 | + result.append(image) |
517 | + else: |
518 | + LOG.debug(("filter_images: --- skip name %s, " |
519 | + "category %s, is_admin=%s"), |
520 | + name, category, str(context.is_admin)) |
521 | + |
522 | + return result |
523 | + |
524 | def describe_images(self, context, image_id=None, **kwargs): |
525 | # NOTE: image_id is a list! |
526 | if image_id: |
527 | @@ -1095,7 +1206,12 @@ |
528 | images.append(image) |
529 | else: |
530 | images = self.image_service.detail(context) |
531 | - images = [self._format_image(i) for i in images] |
532 | + |
533 | + category = self._get_category_name(**kwargs) |
534 | + filtered_images = self._filter_images(context, |
535 | + images, category=category) |
536 | + |
537 | + images = [self._format_image(i) for i in filtered_images] |
538 | return {'imagesSet': images} |
539 | |
540 | def deregister_image(self, context, image_id, **kwargs): |
541 | |
542 | === modified file 'nova/api/openstack/create_instance_helper.py' |
543 | --- nova/api/openstack/create_instance_helper.py 2011-06-17 17:17:14 +0000 |
544 | +++ nova/api/openstack/create_instance_helper.py 2011-06-28 22:54:37 +0000 |
545 | @@ -28,6 +28,7 @@ |
546 | from nova import utils |
547 | |
548 | from nova.compute import instance_types |
549 | +from nova.compute import instance_categories |
550 | from nova.api.openstack import faults |
551 | from nova.api.openstack import wsgi |
552 | from nova.auth import manager as auth_manager |
553 | @@ -104,6 +105,17 @@ |
554 | |
555 | flavor_id = self.controller._flavor_id_from_req_data(body) |
556 | |
557 | + if 'category' in body['server']: |
558 | + category = self.controller._instance_cat_from_req_data(body) |
559 | + try: |
560 | + instance_cat = \ |
561 | + instance_categories.get_instance_cat_by_name(category) |
562 | + except exception.NoFound: |
563 | + raise exc.HTTPBadRequest(_("Category %(category)s not found")) |
564 | + else: |
565 | + instance_cat = None |
566 | + category = instance_categories.default_category_name() |
567 | + |
568 | if not 'name' in body['server']: |
569 | msg = _("Server name is not defined") |
570 | raise exc.HTTPBadRequest(explanation=msg) |
571 | @@ -120,6 +132,7 @@ |
572 | instance_types.get_instance_type_by_flavor_id(flavor_id) |
573 | extra_values = { |
574 | 'instance_type': inst_type, |
575 | + 'category': category, |
576 | 'image_ref': image_href, |
577 | 'password': password} |
578 | |
579 | @@ -137,7 +150,8 @@ |
580 | injected_files=injected_files, |
581 | admin_password=password, |
582 | zone_blob=zone_blob, |
583 | - reservation_id=reservation_id)) |
584 | + reservation_id=reservation_id, |
585 | + instance_cat=instance_cat)) |
586 | except quota.QuotaError as error: |
587 | self._handle_quota_error(error) |
588 | except exception.ImageNotFound as error: |
589 | |
590 | === modified file 'nova/api/openstack/servers.py' |
591 | --- nova/api/openstack/servers.py 2011-06-15 20:58:55 +0000 |
592 | +++ nova/api/openstack/servers.py 2011-06-28 22:54:37 +0000 |
593 | @@ -124,6 +124,7 @@ |
594 | |
595 | builder = self._get_view_builder(req) |
596 | server = builder.build(inst, is_detail=True) |
597 | + server['server']['category'] = extra_values['category'] |
598 | server['server']['adminPass'] = extra_values['password'] |
599 | return server |
600 | |
601 | @@ -410,6 +411,9 @@ |
602 | def _flavor_id_from_req_data(self, data): |
603 | return data['server']['flavorId'] |
604 | |
605 | + def _instance_cat_from_req_data(self, data): |
606 | + return data['server']['category'] |
607 | + |
608 | def _get_view_builder(self, req): |
609 | addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10() |
610 | return nova.api.openstack.views.servers.ViewBuilderV10( |
611 | @@ -473,6 +477,10 @@ |
612 | href = data['server']['flavorRef'] |
613 | return common.get_id_from_href(href) |
614 | |
615 | + def _instance_cat_from_req_data(self, data): |
616 | + href = data['server']['category'] |
617 | + return common.get_id_from_href(href) |
618 | + |
619 | def _get_view_builder(self, req): |
620 | base_url = req.application_url |
621 | flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11( |
622 | @@ -591,7 +599,7 @@ |
623 | "attributes": { |
624 | "server": ["id", "imageId", "name", "flavorId", "hostId", |
625 | "status", "progress", "adminPass", "flavorRef", |
626 | - "imageRef"], |
627 | + "imageRef", "category"], |
628 | "link": ["rel", "type", "href"], |
629 | }, |
630 | "dict_collections": { |
631 | |
632 | === modified file 'nova/cloudpipe/pipelib.py' |
633 | --- nova/cloudpipe/pipelib.py 2011-04-19 16:32:33 +0000 |
634 | +++ nova/cloudpipe/pipelib.py 2011-06-28 22:54:37 +0000 |
635 | @@ -107,6 +107,7 @@ |
636 | max_count=1, |
637 | min_count=1, |
638 | instance_type='m1.tiny', |
639 | + instance_cat=FLAGS.instance_cat_vpn, |
640 | image_id=ec2_id, |
641 | key_name=key_name, |
642 | security_group=[group_name]) |
643 | |
644 | === modified file 'nova/compute/api.py' |
645 | --- nova/compute/api.py 2011-06-24 12:07:33 +0000 |
646 | +++ nova/compute/api.py 2011-06-28 22:54:37 +0000 |
647 | @@ -33,6 +33,7 @@ |
648 | from nova import utils |
649 | from nova import volume |
650 | from nova.compute import instance_types |
651 | +from nova.compute import instance_categories |
652 | from nova.compute import power_state |
653 | from nova.compute.utils import terminate_volumes |
654 | from nova.scheduler import api as scheduler_api |
655 | @@ -148,13 +149,17 @@ |
656 | key_name=None, key_data=None, security_group='default', |
657 | availability_zone=None, user_data=None, metadata={}, |
658 | injected_files=None, admin_password=None, zone_blob=None, |
659 | - reservation_id=None): |
660 | + reservation_id=None, |
661 | + instance_cat=None): |
662 | """Verify all the input parameters regardless of the provisioning |
663 | strategy being performed.""" |
664 | |
665 | if not instance_type: |
666 | instance_type = instance_types.get_default_instance_type() |
667 | |
668 | + if not instance_cat: |
669 | + instance_cat = instance_categories.get_default_instance_cat() |
670 | + |
671 | num_instances = quota.allowed_instances(context, max_count, |
672 | instance_type) |
673 | if num_instances < min_count: |
674 | @@ -234,6 +239,7 @@ |
675 | 'project_id': context.project_id, |
676 | 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), |
677 | 'instance_type_id': instance_type['id'], |
678 | + 'instance_cat_id': instance_cat['id'], |
679 | 'memory_mb': instance_type['memory_mb'], |
680 | 'vcpus': instance_type['vcpus'], |
681 | 'local_gb': instance_type['local_gb'], |
682 | @@ -303,7 +309,8 @@ |
683 | return instance |
684 | |
685 | def _ask_scheduler_to_create_instance(self, context, base_options, |
686 | - instance_type, zone_blob, |
687 | + instance_type, instance_cat, |
688 | + zone_blob, |
689 | availability_zone, injected_files, |
690 | admin_password, |
691 | instance_id=None, num_instances=1): |
692 | @@ -317,10 +324,14 @@ |
693 | LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's" |
694 | " (all-at-once)") % locals()) |
695 | |
696 | + if not instance_cat: |
697 | + instance_cat = instance_categories.get_default_instance_cat() |
698 | + |
699 | filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter' |
700 | request_spec = { |
701 | 'instance_properties': base_options, |
702 | 'instance_type': instance_type, |
703 | + 'category': instance_cat['name'], |
704 | 'filter': filter_class, |
705 | 'blob': zone_blob, |
706 | 'num_instances': num_instances, |
707 | @@ -343,7 +354,8 @@ |
708 | key_name=None, key_data=None, security_group='default', |
709 | availability_zone=None, user_data=None, metadata={}, |
710 | injected_files=None, admin_password=None, zone_blob=None, |
711 | - reservation_id=None): |
712 | + reservation_id=None, |
713 | + instance_cat=None): |
714 | """Provision the instances by passing the whole request to |
715 | the Scheduler for execution. Returns a Reservation ID |
716 | related to the creation of all of these instances.""" |
717 | @@ -356,10 +368,10 @@ |
718 | key_name, key_data, security_group, |
719 | availability_zone, user_data, metadata, |
720 | injected_files, admin_password, zone_blob, |
721 | - reservation_id) |
722 | + reservation_id, instance_cat) |
723 | |
724 | self._ask_scheduler_to_create_instance(context, base_options, |
725 | - instance_type, zone_blob, |
726 | + instance_type, instance_cat, zone_blob, |
727 | availability_zone, injected_files, |
728 | admin_password, |
729 | num_instances=num_instances) |
730 | @@ -373,7 +385,8 @@ |
731 | key_name=None, key_data=None, security_group='default', |
732 | availability_zone=None, user_data=None, metadata={}, |
733 | injected_files=None, admin_password=None, zone_blob=None, |
734 | - reservation_id=None, block_device_mapping=None): |
735 | + reservation_id=None, block_device_mapping=None, |
736 | + instance_cat=None): |
737 | """ |
738 | Provision the instances by sending off a series of single |
739 | instance requests to the Schedulers. This is fine for trival |
740 | @@ -392,7 +405,7 @@ |
741 | key_name, key_data, security_group, |
742 | availability_zone, user_data, metadata, |
743 | injected_files, admin_password, zone_blob, |
744 | - reservation_id) |
745 | + reservation_id, instance_cat) |
746 | |
747 | block_device_mapping = block_device_mapping or [] |
748 | instances = [] |
749 | @@ -405,7 +418,8 @@ |
750 | instance_id = instance['id'] |
751 | |
752 | self._ask_scheduler_to_create_instance(context, base_options, |
753 | - instance_type, zone_blob, |
754 | + instance_type, instance_cat, |
755 | + zone_blob, |
756 | availability_zone, injected_files, |
757 | admin_password, |
758 | instance_id=instance_id) |
759 | @@ -520,9 +534,9 @@ |
760 | rv = self.db.instance_update(context, instance_id, kwargs) |
761 | return dict(rv.iteritems()) |
762 | |
763 | - def _get_instance(self, context, instance_id, action_str): |
764 | + def _get_instance(self, context, instance_id, instance_cat, action_str): |
765 | try: |
766 | - return self.get(context, instance_id) |
767 | + return self.get(context, instance_id, instance_cat) |
768 | except exception.NotFound: |
769 | LOG.warning(_("Instance %(instance_id)s was not found during " |
770 | "%(action_str)s") % |
771 | @@ -530,10 +544,11 @@ |
772 | raise |
773 | |
774 | @scheduler_api.reroute_compute("delete") |
775 | - def delete(self, context, instance_id): |
776 | + def delete(self, context, instance_id, instance_cat=None): |
777 | """Terminate an instance.""" |
778 | LOG.debug(_("Going to try to terminate %s"), instance_id) |
779 | - instance = self._get_instance(context, instance_id, 'terminating') |
780 | + instance = self._get_instance(context, instance_id, instance_cat, |
781 | + 'terminating') |
782 | |
783 | if not _is_able_to_shutdown(instance, instance_id): |
784 | return |
785 | @@ -553,11 +568,12 @@ |
786 | self.db.instance_destroy(context, instance_id) |
787 | |
788 | @scheduler_api.reroute_compute("stop") |
789 | - def stop(self, context, instance_id): |
790 | + def stop(self, context, instance_id, instance_cat=None): |
791 | """Stop an instance.""" |
792 | LOG.debug(_("Going to try to stop %s"), instance_id) |
793 | |
794 | - instance = self._get_instance(context, instance_id, 'stopping') |
795 | + instance = self._get_instance(context, instance_id, instance_cat, |
796 | + 'stopping') |
797 | if not _is_able_to_shutdown(instance, instance_id): |
798 | return |
799 | |
800 | @@ -572,10 +588,11 @@ |
801 | self._cast_compute_message('stop_instance', context, |
802 | instance_id, host) |
803 | |
804 | - def start(self, context, instance_id): |
805 | + def start(self, context, instance_id, instance_cat=None): |
806 | """Start an instance.""" |
807 | LOG.debug(_("Going to try to start %s"), instance_id) |
808 | - instance = self._get_instance(context, instance_id, 'starting') |
809 | + instance = self._get_instance(context, instance_id, instance_cat, |
810 | + 'starting') |
811 | if instance['state_description'] != 'stopped': |
812 | _state_description = instance['state_description'] |
813 | LOG.warning(_("Instance %(instance_id)s is not " |
814 | @@ -591,7 +608,7 @@ |
815 | "args": {"topic": FLAGS.compute_topic, |
816 | "instance_id": instance_id}}) |
817 | |
818 | - def get(self, context, instance_id): |
819 | + def get(self, context, instance_id, instance_cat=None): |
820 | """Get a single instance with the given instance_id.""" |
821 | # NOTE(sirp): id used to be exclusively integer IDs; now we're |
822 | # accepting both UUIDs and integer IDs. The handling of this |
823 | @@ -601,17 +618,22 @@ |
824 | instance = self.db.instance_get_by_uuid(context, uuid) |
825 | else: |
826 | instance = self.db.instance_get(context, instance_id) |
827 | - return dict(instance.iteritems()) |
828 | + instance = dict(instance.iteritems()) |
829 | + if instance_cat is not None: |
830 | + if not instance_categories.is_access_allowed(context, instance, |
831 | + instance_cat): |
832 | + raise exception.AdminRequired() |
833 | + return instance |
834 | |
835 | @scheduler_api.reroute_compute("get") |
836 | - def routing_get(self, context, instance_id): |
837 | + def routing_get(self, context, instance_id, instance_cat=None): |
838 | """A version of get with special routing characteristics. |
839 | |
840 | Use this method instead of get() if this is the only operation you |
841 | intend to to. It will route to novaclient.get if the instance is not |
842 | found. |
843 | """ |
844 | - return self.get(context, instance_id) |
845 | + return self.get(context, instance_id, instance_cat) |
846 | |
847 | def get_all_across_zones(self, context, reservation_id): |
848 | """Get all instances with this reservation_id, across |
849 | @@ -633,33 +655,47 @@ |
850 | return instances |
851 | |
852 | def get_all(self, context, project_id=None, reservation_id=None, |
853 | - fixed_ip=None): |
854 | + fixed_ip=None, category=None): |
855 | """Get all instances filtered by one of the given parameters. |
856 | |
857 | If there is no filter and the context is an admin, it will retreive |
858 | all instances in the system. |
859 | """ |
860 | + instance_cat = instance_categories.get_instance_cat_by_name(category) |
861 | + |
862 | if reservation_id is not None: |
863 | - return self.get_all_across_zones(context, reservation_id) |
864 | - |
865 | - if fixed_ip is not None: |
866 | - return self.db.fixed_ip_get_instance(context, fixed_ip) |
867 | - |
868 | - if project_id or not context.is_admin: |
869 | + instances = self.get_all_across_zones(context, reservation_id) |
870 | + |
871 | + elif fixed_ip is not None: |
872 | + instances = \ |
873 | + self.db.fixed_ip_get_instance(context, fixed_ip) |
874 | + |
875 | + elif project_id or not context.is_admin: |
876 | if not context.project: |
877 | - return self.db.instance_get_all_by_user( |
878 | - context, context.user_id) |
879 | - |
880 | - if project_id is None: |
881 | - project_id = context.project_id |
882 | - |
883 | - return self.db.instance_get_all_by_project( |
884 | - context, project_id) |
885 | - |
886 | - return self.db.instance_get_all(context) |
887 | + instances = self.db.instance_get_all_by_user( |
888 | + context, context.user_id) |
889 | + else: |
890 | + if project_id is None: |
891 | + project_id = context.project_id |
892 | + |
893 | + instances = self.db.instance_get_all_by_project( |
894 | + context, project_id) |
895 | + |
896 | + elif category is not None: |
897 | + instances = self.db.instance_get_all_by_cat(context, |
898 | + instance_cat['id']) |
899 | + else: |
900 | + instances = self.db.instance_get_all(context) |
901 | + |
902 | + return self._filter_instances(context, instances, instance_cat) |
903 | + |
904 | + def _filter_instances(self, context, instances, instance_cat): |
905 | + return [instance for instance in instances \ |
906 | + if instance_categories.is_access_allowed(context, instance, |
907 | + instance_cat)] |
908 | |
909 | def _cast_compute_message(self, method, context, instance_id, host=None, |
910 | - params=None): |
911 | + params=None, instance_cat=None): |
912 | """Generic handler for RPC casts to compute. |
913 | |
914 | :param params: Optional dictionary of arguments to be passed to the |
915 | @@ -667,18 +703,23 @@ |
916 | |
917 | :returns: None |
918 | """ |
919 | + instance = self.get(context, instance_id) |
920 | + if not instance_categories.is_access_allowed(context, instance, |
921 | + instance_cat): |
922 | + raise exception.AdminRequired() |
923 | + |
924 | if not params: |
925 | params = {} |
926 | if not host: |
927 | - instance = self.get(context, instance_id) |
928 | host = instance['host'] |
929 | + |
930 | queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) |
931 | params['instance_id'] = instance_id |
932 | kwargs = {'method': method, 'args': params} |
933 | rpc.cast(context, queue, kwargs) |
934 | |
935 | def _call_compute_message(self, method, context, instance_id, host=None, |
936 | - params=None): |
937 | + params=None, instance_cat=None): |
938 | """Generic handler for RPC calls to compute. |
939 | |
940 | :param params: Optional dictionary of arguments to be passed to the |
941 | @@ -686,10 +727,14 @@ |
942 | |
943 | :returns: Result returned by compute worker |
944 | """ |
945 | + instance = self.get(context, instance_id) |
946 | + if not instance_categories.is_access_allowed(context, instance, |
947 | + instance_cat): |
948 | + raise exception.AdminRequired() |
949 | + |
950 | if not params: |
951 | params = {} |
952 | if not host: |
953 | - instance = self.get(context, instance_id) |
954 | host = instance["host"] |
955 | queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) |
956 | params['instance_id'] = instance_id |
957 | @@ -728,9 +773,10 @@ |
958 | params=params) |
959 | return recv_meta |
960 | |
961 | - def reboot(self, context, instance_id): |
962 | + def reboot(self, context, instance_id, instance_cat=None): |
963 | """Reboot the given instance.""" |
964 | - self._cast_compute_message('reboot_instance', context, instance_id) |
965 | + self._cast_compute_message('reboot_instance', context, instance_id, |
966 | + instance_cat=instance_cat) |
967 | |
968 | def rebuild(self, context, instance_id, image_href, name=None, |
969 | metadata=None, files_to_inject=None): |
970 | @@ -857,14 +903,16 @@ |
971 | self._cast_compute_message('resume_instance', context, instance_id) |
972 | |
973 | @scheduler_api.reroute_compute("rescue") |
974 | - def rescue(self, context, instance_id): |
975 | + def rescue(self, context, instance_id, instance_cat=None): |
976 | """Rescue the given instance.""" |
977 | - self._cast_compute_message('rescue_instance', context, instance_id) |
978 | + self._cast_compute_message('rescue_instance', context, instance_id, |
979 | + instance_cat=instance_cat) |
980 | |
981 | @scheduler_api.reroute_compute("unrescue") |
982 | - def unrescue(self, context, instance_id): |
983 | + def unrescue(self, context, instance_id, instance_cat=None): |
984 | """Unrescue the given instance.""" |
985 | - self._cast_compute_message('unrescue_instance', context, instance_id) |
986 | + self._cast_compute_message('unrescue_instance', context, instance_id, |
987 | + instance_cat=instance_cat) |
988 | |
989 | def set_admin_password(self, context, instance_id, password=None): |
990 | """Set the root/admin password for the given instance.""" |
991 | |
992 | === added file 'nova/compute/instance_categories.py' |
993 | --- nova/compute/instance_categories.py 1970-01-01 00:00:00 +0000 |
994 | +++ nova/compute/instance_categories.py 2011-06-28 22:54:37 +0000 |
995 | @@ -0,0 +1,196 @@ |
996 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
997 | + |
998 | +# Copyright 2010 United States Government as represented by the |
999 | +# Administrator of the National Aeronautics and Space Administration. |
1000 | +# All Rights Reserved. |
1001 | +# Copyright (c) 2010 Citrix Systems, Inc. |
1002 | +# Copyright 2011 Ken Pepple |
1003 | +# |
1004 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1005 | +# not use this file except in compliance with the License. You may obtain |
1006 | +# a copy of the License at |
1007 | +# |
1008 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1009 | +# |
1010 | +# Unless required by applicable law or agreed to in writing, software |
1011 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1012 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1013 | +# License for the specific language governing permissions and limitations |
1014 | +# under the License. |
1015 | + |
1016 | +"""Built-in instance categories properties.""" |
1017 | + |
1018 | +from nova import context |
1019 | +from nova import db |
1020 | +from nova import exception |
1021 | +from nova import flags |
1022 | +from nova import log as logging |
1023 | + |
1024 | +FLAGS = flags.FLAGS |
1025 | +LOG = logging.getLogger('nova.instance_categories') |
1026 | + |
1027 | + |
1028 | +def create(name, description, image_id=None, specs=None): |
1029 | + """Creates instance category.""" |
1030 | + try: |
1031 | + db.instance_cat_create( |
1032 | + context.get_admin_context(), |
1033 | + dict(name=name, |
1034 | + description=description, |
1035 | + image_id=image_id, |
1036 | + specs=specs)) |
1037 | + except exception.DBError, e: |
1038 | + LOG.exception(_('DB error: %s') % e) |
1039 | + raise exception.ApiError(_("Cannot create instance category with name" |
1040 | + " %(name)s, description %(description)s and" |
1041 | + " specs %(specs)s") % locals()) |
1042 | + |
1043 | + |
1044 | +def update(name, description=None, image_id=None, specs=None): |
1045 | + """Updates instance category the instance in the datastore. """ |
1046 | + rv = db.instance_cat_update( |
1047 | + context.get_admin_context(), |
1048 | + name, |
1049 | + dict(description=description, |
1050 | + image_id=image_id, |
1051 | + specs=specs)) |
1052 | + return dict(rv.iteritems()) |
1053 | + |
1054 | + |
1055 | +def destroy(name): |
1056 | + """Marks instance categories as deleted.""" |
1057 | + if name is None: |
1058 | + raise exception.InvalidInstanceCategory(instance_cat=name) |
1059 | + else: |
1060 | + try: |
1061 | + db.instance_cat_destroy(context.get_admin_context(), name) |
1062 | + except exception.NotFound: |
1063 | + LOG.exception(_('Category %s not found for deletion') % name) |
1064 | + raise exception.ApiError(_("Unknown instance category: %s") % name) |
1065 | + |
1066 | + |
1067 | +def purge(name): |
1068 | + """Removes instance categories from database.""" |
1069 | + if name is None: |
1070 | + raise exception.InvalidInstanceCategory(instance_cat=name) |
1071 | + else: |
1072 | + try: |
1073 | + db.instance_cat_purge(context.get_admin_context(), name) |
1074 | + except exception.NotFound: |
1075 | + LOG.exception(_('Instance category %s not found for purge') % name) |
1076 | + raise exception.ApiError(_("Unknown instance category: %s") % name) |
1077 | + |
1078 | + |
1079 | +def get_all_categories(inactive=0): |
1080 | + """Get all non-deleted instance categories. |
1081 | + |
1082 | + Pass true as argument if you want deleted categories returned also. |
1083 | + |
1084 | + """ |
1085 | + return db.instance_cat_get_all(context.get_admin_context(), inactive) |
1086 | + |
1087 | + |
1088 | +def default_category_name(): |
1089 | + return FLAGS.instance_cat_regular |
1090 | + |
1091 | + |
1092 | +def get_default_instance_cat(): |
1093 | + """Get the default instance category.""" |
1094 | + return get_instance_cat_by_name(default_category_name()) |
1095 | + |
1096 | + |
1097 | +def get_instance_cat(id): |
1098 | + """Retrieves single instance category by id.""" |
1099 | + if id is None: |
1100 | + return get_default_instance_cat() |
1101 | + try: |
1102 | + ctxt = context.get_admin_context() |
1103 | + return db.instance_cat_get_by_id(ctxt, id) |
1104 | + except exception.NotFound: |
1105 | + LOG.exception(_('Instance category with id %d not found') % id) |
1106 | + raise |
1107 | + |
1108 | + |
1109 | +def get_instance_cat_by_name(name): |
1110 | + """Retrieves single instance category by name.""" |
1111 | + if name is None: |
1112 | + return get_default_instance_cat() |
1113 | + try: |
1114 | + ctxt = context.get_admin_context() |
1115 | + return db.instance_cat_get_by_name(ctxt, name) |
1116 | + except exception.NotFound: |
1117 | + LOG.exception(_('Instance category with name %s not found') % name) |
1118 | + raise |
1119 | + |
1120 | + |
1121 | +def get_instance_cat_id_by_name(name): |
1122 | + """Retrieves single instance category ID by name.""" |
1123 | + return get_instance_cat_by_name(name)['id'] |
1124 | + |
1125 | + |
1126 | +def get_instance_cat_name(instance): |
1127 | + """Returns instance category name by instance_ref""" |
1128 | + if 'instance_cat' in instance \ |
1129 | + and 'name' in instance['instance_cat']: |
1130 | + return instance['instance_cat']['name'] |
1131 | + else: |
1132 | + return get_instance_cat(instance['instance_cat_id'])['name'] |
1133 | + |
1134 | + |
1135 | +def is_admin_cat_name(name): |
1136 | + return False if (name is None) or \ |
1137 | + (name == FLAGS.instance_cat_regular) else True |
1138 | + |
1139 | + |
1140 | +def is_access_allowed(context, instance, instance_cat=None): |
1141 | + """Determines if access to instance is allowed |
1142 | + |
1143 | + If admin context - allow everything, otherwise: |
1144 | + if regular instance - allow |
1145 | + otherwise (one of categories) |
1146 | + if instance's cat matches instance_cat - allow |
1147 | + if not matches: - restrict |
1148 | + """ |
1149 | + |
1150 | + if context.is_admin: |
1151 | + return True |
1152 | + if not is_admin_instance(instance): |
1153 | + return True |
1154 | + if instance_cat is not None \ |
1155 | + and get_instance_cat_name(instance) == instance_cat['name']: |
1156 | + return True |
1157 | + else: |
1158 | + return False |
1159 | + |
1160 | + |
1161 | +def is_admin_category(instance_cat): |
1162 | + return is_admin_cat_name(instance_cat['name']) |
1163 | + |
1164 | + |
1165 | +def is_admin_instance(instance, category=None): |
1166 | + """ Verifies if instance is one of admin instances. |
1167 | + |
1168 | + If category specified - returns True only if category matches |
1169 | + """ |
1170 | + if instance['instance_cat_id'] is None: |
1171 | + return False |
1172 | + |
1173 | + try: |
1174 | + inst_cat_name = get_instance_cat_name(instance) |
1175 | + except exception.NotFound: |
1176 | + raise |
1177 | + |
1178 | + if inst_cat_name == FLAGS.instance_cat_regular: |
1179 | + return False |
1180 | + |
1181 | + if category is not None \ |
1182 | + and category != inst_cat_name: |
1183 | + return False |
1184 | + |
1185 | + return True |
1186 | + |
1187 | + |
1188 | +def get_admin_image_names(category=None): |
1189 | + """Return list of image_ids for all or specified categories""" |
1190 | + return db.instance_cat_get_all_images(context.get_admin_context(), |
1191 | + category) |
1192 | |
1193 | === modified file 'nova/compute/manager.py' |
1194 | --- nova/compute/manager.py 2011-06-28 20:37:05 +0000 |
1195 | +++ nova/compute/manager.py 2011-06-28 22:54:37 +0000 |
1196 | @@ -54,6 +54,7 @@ |
1197 | from nova import utils |
1198 | from nova import volume |
1199 | from nova.compute import power_state |
1200 | +from nova.compute import instance_categories |
1201 | from nova.notifier import api as notifier_api |
1202 | from nova.compute.utils import terminate_volumes |
1203 | from nova.virt import driver |
1204 | @@ -297,7 +298,8 @@ |
1205 | power_state.NOSTATE, |
1206 | 'networking') |
1207 | |
1208 | - is_vpn = instance_ref['image_ref'] == str(FLAGS.vpn_image_id) |
1209 | + is_vpn = instance_categories.is_admin_instance(instance_ref, |
1210 | + FLAGS.instance_cat_vpn) |
1211 | try: |
1212 | # NOTE(vish): This could be a cast because we don't do anything |
1213 | # with the address currently, but I'm leaving it as |
1214 | |
1215 | === modified file 'nova/db/api.py' |
1216 | --- nova/db/api.py 2011-06-28 20:53:45 +0000 |
1217 | +++ nova/db/api.py 2011-06-28 22:54:37 +0000 |
1218 | @@ -467,6 +467,11 @@ |
1219 | return IMPL.instance_get_all_by_reservation(context, reservation_id) |
1220 | |
1221 | |
1222 | +def instance_get_all_by_cat(context, instance_cat_id): |
1223 | + """Get all instance belonging to a category.""" |
1224 | + return IMPL.instance_get_all_by_cat(context, instance_cat_id) |
1225 | + |
1226 | + |
1227 | def instance_get_fixed_address(context, instance_id): |
1228 | """Get the fixed ip address of an instance.""" |
1229 | return IMPL.instance_get_fixed_address(context, instance_id) |
1230 | @@ -481,9 +486,9 @@ |
1231 | return IMPL.instance_get_floating_address(context, instance_id) |
1232 | |
1233 | |
1234 | -def instance_get_project_vpn(context, project_id): |
1235 | +def instance_get_project_admin(context, project_id, cat_id): |
1236 | """Get a vpn instance by project or return None.""" |
1237 | - return IMPL.instance_get_project_vpn(context, project_id) |
1238 | + return IMPL.instance_get_project_admin(context, project_id, cat_id) |
1239 | |
1240 | |
1241 | def instance_set_state(context, instance_id, state, description=None): |
1242 | @@ -1279,6 +1284,57 @@ |
1243 | return IMPL.instance_type_purge(context, name) |
1244 | |
1245 | |
1246 | +################## |
1247 | + |
1248 | + |
1249 | +def instance_cat_create(context, values): |
1250 | + """Create a new instance category.""" |
1251 | + return IMPL.instance_cat_create(context, values) |
1252 | + |
1253 | + |
1254 | +def instance_cat_update(context, name, values): |
1255 | + """Set the given properties on an instance category and update it. |
1256 | + |
1257 | + Raises NotFound if category does not exist. |
1258 | + |
1259 | + """ |
1260 | + return IMPL.instance_cat_update(context, name, values) |
1261 | + |
1262 | + |
1263 | +def instance_cat_get_all(context, inactive=False): |
1264 | + """Get all instance categories.""" |
1265 | + return IMPL.instance_cat_get_all(context, inactive) |
1266 | + |
1267 | + |
1268 | +def instance_cat_get_by_id(context, id): |
1269 | + """Get instance category by id.""" |
1270 | + return IMPL.instance_cat_get_by_id(context, id) |
1271 | + |
1272 | + |
1273 | +def instance_cat_get_by_name(context, name): |
1274 | + """Get instance category by name.""" |
1275 | + return IMPL.instance_cat_get_by_name(context, name) |
1276 | + |
1277 | + |
1278 | +def instance_cat_get_all_images(context, category=None): |
1279 | + """Get registered images for all or selected categories.""" |
1280 | + return IMPL.instance_cat_get_all_images(context, category) |
1281 | + |
1282 | + |
1283 | +def instance_cat_destroy(context, name): |
1284 | + """Delete an instance category.""" |
1285 | + return IMPL.instance_cat_destroy(context, name) |
1286 | + |
1287 | + |
1288 | +def instance_cat_purge(context, name): |
1289 | + """Purges (removes) an instance category from DB. |
1290 | + |
1291 | + Use instance_type_destroy for most cases |
1292 | + |
1293 | + """ |
1294 | + return IMPL.instance_cat_purge(context, name) |
1295 | + |
1296 | + |
1297 | #################### |
1298 | |
1299 | |
1300 | |
1301 | === modified file 'nova/db/sqlalchemy/api.py' |
1302 | --- nova/db/sqlalchemy/api.py 2011-06-28 20:53:45 +0000 |
1303 | +++ nova/db/sqlalchemy/api.py 2011-06-28 22:54:37 +0000 |
1304 | @@ -932,6 +932,7 @@ |
1305 | options(joinedload('volumes')).\ |
1306 | options(joinedload_all('fixed_ip.network')).\ |
1307 | options(joinedload('metadata')).\ |
1308 | + options(joinedload('instance_cat')).\ |
1309 | options(joinedload('instance_type')) |
1310 | |
1311 | if is_admin_context(context): |
1312 | @@ -951,6 +952,7 @@ |
1313 | options(joinedload_all('fixed_ip.network')).\ |
1314 | options(joinedload('metadata')).\ |
1315 | options(joinedload('instance_type')).\ |
1316 | + options(joinedload('instance_cat')).\ |
1317 | filter_by(deleted=can_read_deleted(context)).\ |
1318 | all() |
1319 | |
1320 | @@ -982,6 +984,7 @@ |
1321 | options(joinedload_all('fixed_ip.network')).\ |
1322 | options(joinedload('metadata')).\ |
1323 | options(joinedload('instance_type')).\ |
1324 | + options(joinedload('instance_cat')).\ |
1325 | filter_by(deleted=can_read_deleted(context)).\ |
1326 | filter_by(user_id=user_id).\ |
1327 | all() |
1328 | @@ -996,6 +999,7 @@ |
1329 | options(joinedload_all('fixed_ip.network')).\ |
1330 | options(joinedload('metadata')).\ |
1331 | options(joinedload('instance_type')).\ |
1332 | + options(joinedload('instance_cat')).\ |
1333 | filter_by(host=host).\ |
1334 | filter_by(deleted=can_read_deleted(context)).\ |
1335 | all() |
1336 | @@ -1012,6 +1016,7 @@ |
1337 | options(joinedload_all('fixed_ip.network')).\ |
1338 | options(joinedload('metadata')).\ |
1339 | options(joinedload('instance_type')).\ |
1340 | + options(joinedload('instance_cat')).\ |
1341 | filter_by(project_id=project_id).\ |
1342 | filter_by(deleted=can_read_deleted(context)).\ |
1343 | all() |
1344 | @@ -1028,6 +1033,7 @@ |
1345 | options(joinedload_all('fixed_ip.network')).\ |
1346 | options(joinedload('metadata')).\ |
1347 | options(joinedload('instance_type')).\ |
1348 | + options(joinedload('instance_cat')).\ |
1349 | filter_by(reservation_id=reservation_id).\ |
1350 | filter_by(deleted=can_read_deleted(context)).\ |
1351 | all() |
1352 | @@ -1038,6 +1044,7 @@ |
1353 | options(joinedload_all('fixed_ip.network')).\ |
1354 | options(joinedload('metadata')).\ |
1355 | options(joinedload('instance_type')).\ |
1356 | + options(joinedload('instance_cat')).\ |
1357 | filter_by(project_id=context.project_id).\ |
1358 | filter_by(reservation_id=reservation_id).\ |
1359 | filter_by(deleted=False).\ |
1360 | @@ -1045,16 +1052,32 @@ |
1361 | |
1362 | |
1363 | @require_admin_context |
1364 | -def instance_get_project_vpn(context, project_id): |
1365 | - session = get_session() |
1366 | - return session.query(models.Instance).\ |
1367 | - options(joinedload_all('fixed_ip.floating_ips')).\ |
1368 | - options(joinedload('security_groups')).\ |
1369 | - options(joinedload_all('fixed_ip.network')).\ |
1370 | - options(joinedload('metadata')).\ |
1371 | - options(joinedload('instance_type')).\ |
1372 | +def instance_get_all_by_cat(context, instance_cat_id): |
1373 | + session = get_session() |
1374 | + return session.query(models.Instance).\ |
1375 | + options(joinedload_all('fixed_ip.floating_ips')).\ |
1376 | + options(joinedload('security_groups')).\ |
1377 | + options(joinedload_all('fixed_ip.network')).\ |
1378 | + options(joinedload('metadata')).\ |
1379 | + options(joinedload('instance_type')).\ |
1380 | + options(joinedload('instance_cat')).\ |
1381 | + filter_by(instance_cat_id=instance_cat_id).\ |
1382 | + filter_by(deleted=can_read_deleted(context)).\ |
1383 | + all() |
1384 | + |
1385 | + |
1386 | +@require_admin_context |
1387 | +def instance_get_project_admin(context, project_id, cat_id): |
1388 | + session = get_session() |
1389 | + return session.query(models.Instance).\ |
1390 | + options(joinedload_all('fixed_ip.floating_ips')).\ |
1391 | + options(joinedload('security_groups')).\ |
1392 | + options(joinedload_all('fixed_ip.network')).\ |
1393 | + options(joinedload('metadata')).\ |
1394 | + options(joinedload('instance_type')).\ |
1395 | + options(joinedload('instance_cat')).\ |
1396 | filter_by(project_id=project_id).\ |
1397 | - filter_by(image_ref=str(FLAGS.vpn_image_id)).\ |
1398 | + filter_by(instance_cat_id=cat_id).\ |
1399 | filter_by(deleted=can_read_deleted(context)).\ |
1400 | first() |
1401 | |
1402 | @@ -2733,7 +2756,7 @@ |
1403 | first() |
1404 | |
1405 | if not inst_type: |
1406 | - raise exception.InstanceTypeNotFound(instance_type=id) |
1407 | + raise exception.InstanceTypeNotFound(instance_type_id=id) |
1408 | else: |
1409 | return _dict_with_extra_specs(inst_type) |
1410 | |
1411 | @@ -2794,6 +2817,133 @@ |
1412 | return instance_type_ref |
1413 | |
1414 | |
1415 | + ################## |
1416 | + |
1417 | + |
1418 | +@require_admin_context |
1419 | +def instance_cat_create(_context, values): |
1420 | + try: |
1421 | + instance_cat_ref = models.InstanceCategories() |
1422 | + instance_cat_ref.update(values) |
1423 | + instance_cat_ref.save() |
1424 | + except Exception, e: |
1425 | + raise exception.DBError(e) |
1426 | + return instance_cat_ref |
1427 | + |
1428 | + |
1429 | +@require_admin_context |
1430 | +def instance_cat_update(context, name, values): |
1431 | + session = get_session() |
1432 | + with session.begin(): |
1433 | + instance_cat_ref = session.query(models.InstanceCategories).\ |
1434 | + filter_by(name=name).\ |
1435 | + first() |
1436 | + instance_cat_ref.update(values) |
1437 | + instance_cat_ref.save(session=session) |
1438 | + return instance_cat_ref |
1439 | + |
1440 | + |
1441 | +@require_context |
1442 | +def instance_cat_get_all(context, inactive=False): |
1443 | + """ |
1444 | + Returns a dict describing all instance_categories with name as key. |
1445 | + """ |
1446 | + session = get_session() |
1447 | + if inactive: |
1448 | + inst_cats = session.query(models.InstanceCategories).\ |
1449 | + order_by("name").\ |
1450 | + all() |
1451 | + else: |
1452 | + inst_cats = session.query(models.InstanceCategories).\ |
1453 | + filter_by(deleted=False).\ |
1454 | + order_by("name").\ |
1455 | + all() |
1456 | + if inst_cats: |
1457 | + inst_cat_dict = {} |
1458 | + for i in inst_cats: |
1459 | + inst_cat_dict[i['name']] = dict(i) |
1460 | + return inst_cat_dict |
1461 | + else: |
1462 | + raise exception.NoInstanceCategoriesFound() |
1463 | + |
1464 | + |
1465 | +@require_context |
1466 | +def instance_cat_get_by_id(context, id): |
1467 | + """Returns a dict describing specific instance category""" |
1468 | + session = get_session() |
1469 | + inst_cat = session.query(models.InstanceCategories).\ |
1470 | + filter_by(id=id).\ |
1471 | + first() |
1472 | + if not inst_cat: |
1473 | + raise exception.InstanceCatNotFound(instance_cat_id=id) |
1474 | + else: |
1475 | + return dict(inst_cat) |
1476 | + |
1477 | + |
1478 | +@require_context |
1479 | +def instance_cat_get_by_name(context, name): |
1480 | + """Returns a dict describing specific instance category""" |
1481 | + session = get_session() |
1482 | + inst_cat = session.query(models.InstanceCategories).\ |
1483 | + filter_by(name=name).\ |
1484 | + first() |
1485 | + if not inst_cat: |
1486 | + raise exception.InstanceCatNotFoundByName(instance_cat_name=name) |
1487 | + else: |
1488 | + return dict(inst_cat) |
1489 | + |
1490 | + |
1491 | +@require_admin_context |
1492 | +def instance_cat_destroy(context, name): |
1493 | + """ Marks specific instance category as deleted""" |
1494 | + session = get_session() |
1495 | + instance_cat_ref = session.query(models.InstanceCategories).\ |
1496 | + filter_by(name=name) |
1497 | + records = instance_cat_ref.update(dict(deleted=True)) |
1498 | + if records == 0: |
1499 | + raise exception.InstanceCatNotFoundByName(instance_cat_name=name) |
1500 | + else: |
1501 | + return instance_cat_ref |
1502 | + |
1503 | + |
1504 | +@require_admin_context |
1505 | +def instance_cat_purge(context, name): |
1506 | + """ Removes specific instance category from DB |
1507 | + Usually instance_cat_destroy should be used |
1508 | + """ |
1509 | + session = get_session() |
1510 | + instance_cat_ref = session.query(models.InstanceCategories).\ |
1511 | + filter_by(name=name) |
1512 | + records = instance_cat_ref.delete() |
1513 | + if records == 0: |
1514 | + raise exception.InstanceCatNotFoundByName(instance_cat_name=name) |
1515 | + else: |
1516 | + return instance_cat_ref |
1517 | + |
1518 | + |
1519 | +@require_context |
1520 | +def instance_cat_get_all_images(context, category=None): |
1521 | + """ |
1522 | + Returns a dict describing all instance_categories with name as key. |
1523 | + """ |
1524 | + session = get_session() |
1525 | + if category is None: |
1526 | + images = session.query(models.InstanceCategories).\ |
1527 | + filter(models.InstanceCategories.image_id != None).\ |
1528 | + filter(models.InstanceCategories.name != \ |
1529 | + FLAGS.instance_cat_regular).\ |
1530 | + filter_by(deleted=False).\ |
1531 | + all() |
1532 | + else: |
1533 | + images = session.query(models.InstanceCategories).\ |
1534 | + filter(models.InstanceCategories.image_id != None).\ |
1535 | + filter(models.InstanceCategories.name != \ |
1536 | + FLAGS.instance_cat_regular).\ |
1537 | + filter_by(deleted=False).\ |
1538 | + filter_by(name=category).\ |
1539 | + all() |
1540 | + return [image['image_id'] for image in images] |
1541 | + |
1542 | #################### |
1543 | |
1544 | |
1545 | |
1546 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/029_add_instance_categories.py' |
1547 | --- nova/db/sqlalchemy/migrate_repo/versions/029_add_instance_categories.py 1970-01-01 00:00:00 +0000 |
1548 | +++ nova/db/sqlalchemy/migrate_repo/versions/029_add_instance_categories.py 2011-06-28 22:54:37 +0000 |
1549 | @@ -0,0 +1,152 @@ |
1550 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
1551 | + |
1552 | +# Copyright 2010 OpenStack LLC. |
1553 | +# |
1554 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1555 | +# not use this file except in compliance with the License. You may obtain |
1556 | +# a copy of the License at |
1557 | +# |
1558 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1559 | +# |
1560 | +# Unless required by applicable law or agreed to in writing, software |
1561 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1562 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1563 | +# License for the specific language governing permissions and limitations |
1564 | +# under the License. |
1565 | + |
1566 | +from sqlalchemy import Column, DateTime, Integer, MetaData, String, Table |
1567 | +from sqlalchemy import Text, Boolean |
1568 | +# ForeignKey |
1569 | + |
1570 | +from nova import log as logging |
1571 | + |
1572 | +meta = MetaData() |
1573 | + |
1574 | +instances = Table('instances', meta, |
1575 | + Column('id', Integer(), primary_key=True, nullable=False), |
1576 | + Column('image_ref', String()), |
1577 | + ) |
1578 | + |
1579 | +instance_cat_id = Column('instance_cat_id', Integer(), nullable=True) |
1580 | + |
1581 | +# |
1582 | +# New Tables |
1583 | +# |
1584 | +instance_categories = Table('instance_categories', meta, |
1585 | + Column('created_at', DateTime(timezone=False)), |
1586 | + Column('updated_at', DateTime(timezone=False)), |
1587 | + Column('deleted_at', DateTime(timezone=False)), |
1588 | + Column('deleted', Boolean(create_constraint=True, name=None)), |
1589 | + Column('id', Integer(), primary_key=True, nullable=False), |
1590 | + Column('name', |
1591 | + String(length=255, convert_unicode=False, assert_unicode=None, |
1592 | + unicode_error=None, _warn_on_bytestring=False), |
1593 | + unique=True), |
1594 | + Column('description', |
1595 | + String(length=255, convert_unicode=False, assert_unicode=None, |
1596 | + unicode_error=None, _warn_on_bytestring=False), |
1597 | + unique=False, nullable=True), |
1598 | + Column('image_id', |
1599 | + String(length=255, convert_unicode=False, assert_unicode=None, |
1600 | + unicode_error=None, _warn_on_bytestring=False), |
1601 | + unique=False, nullable=True), |
1602 | + Column('specs', |
1603 | + Text(length=None, convert_unicode=False, assert_unicode=None, |
1604 | + unicode_error=None, _warn_on_bytestring=False), |
1605 | + nullable=True)) |
1606 | + |
1607 | + |
1608 | +def upgrade(migrate_engine): |
1609 | + |
1610 | + from nova import context |
1611 | + from nova import db |
1612 | + from nova import flags |
1613 | + |
1614 | + FLAGS = flags.FLAGS |
1615 | + |
1616 | + # Upgrade operations go here. Don't create your own engine; |
1617 | + # bind migrate_engine to your metadata |
1618 | + meta.bind = migrate_engine |
1619 | + |
1620 | + # Create instance_categories table |
1621 | + try: |
1622 | + instance_categories.create() |
1623 | + except Exception: |
1624 | + logging.info(repr(instance_categories)) |
1625 | + logging.exception('Exception while creating instance_categories table') |
1626 | + raise |
1627 | + |
1628 | + # Fill it up with default Categories |
1629 | + INSTANCE_CATEGORIES = { |
1630 | + FLAGS.instance_cat_regular: \ |
1631 | + dict(description='Regular Compute Instance', |
1632 | + image_id=None, |
1633 | + specs=None), |
1634 | + FLAGS.instance_cat_vc: \ |
1635 | + dict(description='Virtual Storage Controller', |
1636 | + image_id=None, |
1637 | + specs=None), |
1638 | + FLAGS.instance_cat_vpn: \ |
1639 | + dict(description='VPN Instance', |
1640 | + image_id=str(FLAGS.vpn_image_id), |
1641 | + specs=None), |
1642 | + FLAGS.instance_cat_admin: \ |
1643 | + dict(description='Generic Admin Instance', |
1644 | + image_id=None, |
1645 | + specs=None)} |
1646 | + |
1647 | + try: |
1648 | + for name, values in INSTANCE_CATEGORIES.iteritems(): |
1649 | + db.instance_cat_create(context.get_admin_context(), |
1650 | + {'name': name, |
1651 | + 'description': values["description"], |
1652 | + 'image_id': values["image_id"], |
1653 | + 'specs': values["specs"]}) |
1654 | + except Exception: |
1655 | + logging.exception('Exception while adding default values to ' \ |
1656 | + 'instance_categories table') |
1657 | + instance_categories.drop() |
1658 | + raise |
1659 | + |
1660 | + # Add appropriate column to instances and fill it up with default values |
1661 | + instances.create_column(instance_cat_id) |
1662 | + |
1663 | + try: |
1664 | + reg_recs = migrate_engine.execute( |
1665 | + instance_categories.select().\ |
1666 | + where(instance_categories.c.name == \ |
1667 | + FLAGS.instance_cat_regular)).\ |
1668 | + fetchone() |
1669 | + vpn_recs = migrate_engine.execute( |
1670 | + instance_categories.select().\ |
1671 | + where(instance_categories.c.name == \ |
1672 | + FLAGS.instance_cat_vpn)).\ |
1673 | + fetchone() |
1674 | + |
1675 | + reg_cat_id = reg_recs['id'] |
1676 | + vpn_cat_id = vpn_recs['id'] |
1677 | + |
1678 | + migrate_engine.execute(instances.update().\ |
1679 | + where(instances.c.instance_cat_id == None).\ |
1680 | + where(instances.c.image_ref != \ |
1681 | + str(FLAGS.vpn_image_id)).\ |
1682 | + values(instance_cat_id=reg_cat_id)) |
1683 | + |
1684 | + migrate_engine.execute(instances.update().\ |
1685 | + where(instances.c.instance_cat_id == None).\ |
1686 | + where(instances.c.image_ref == \ |
1687 | + str(FLAGS.vpn_image_id)).\ |
1688 | + values(instance_cat_id=vpn_cat_id)) |
1689 | + except Exception: |
1690 | + logging.info(repr(instances)) |
1691 | + logging.exception('Exception while setting default categories ' \ |
1692 | + 'in instances table') |
1693 | + raise |
1694 | + |
1695 | + |
1696 | +def downgrade(migrate_engine): |
1697 | + meta.bind = migrate_engine |
1698 | + |
1699 | + instances.drop_column(instance_cat_id) |
1700 | + |
1701 | + instance_categories.drop() |
1702 | |
1703 | === modified file 'nova/db/sqlalchemy/migration.py' |
1704 | --- nova/db/sqlalchemy/migration.py 2011-06-24 12:01:51 +0000 |
1705 | +++ nova/db/sqlalchemy/migration.py 2011-06-28 22:54:37 +0000 |
1706 | @@ -64,7 +64,7 @@ |
1707 | 'users', 'user_project_association', |
1708 | 'user_project_role_association', |
1709 | 'user_role_association', |
1710 | - 'volumes'): |
1711 | + 'volumes', 'instance_categories'): |
1712 | assert table in meta.tables |
1713 | return db_version_control(1) |
1714 | except AssertionError: |
1715 | |
1716 | === modified file 'nova/db/sqlalchemy/models.py' |
1717 | --- nova/db/sqlalchemy/models.py 2011-06-27 00:02:18 +0000 |
1718 | +++ nova/db/sqlalchemy/models.py 2011-06-28 22:54:37 +0000 |
1719 | @@ -210,6 +210,7 @@ |
1720 | host = Column(String(255)) # , ForeignKey('hosts.id')) |
1721 | |
1722 | instance_type_id = Column(Integer) |
1723 | + instance_cat_id = Column(Integer) |
1724 | |
1725 | user_data = Column(Text) |
1726 | |
1727 | @@ -278,6 +279,22 @@ |
1728 | 'InstanceTypes.id)') |
1729 | |
1730 | |
1731 | +class InstanceCategories(BASE, NovaBase): |
1732 | + """Represent possible instance_categories of VM offered""" |
1733 | + __tablename__ = "instance_categories" |
1734 | + id = Column(Integer, primary_key=True) |
1735 | + name = Column(String(255), unique=True) |
1736 | + description = Column(String(255), nullable=True) |
1737 | + image_id = Column(String(255), nullable=True) |
1738 | + specs = Column(Text, nullable=True) |
1739 | + |
1740 | + instances = relationship(Instance, |
1741 | + backref=backref('instance_cat', uselist=False), |
1742 | + foreign_keys=id, |
1743 | + primaryjoin='and_(Instance.instance_cat_id == ' |
1744 | + 'InstanceCategories.id)') |
1745 | + |
1746 | + |
1747 | class Volume(BASE, NovaBase): |
1748 | """Represents a block storage device that can be attached to a vm.""" |
1749 | __tablename__ = 'volumes' |
1750 | @@ -765,7 +782,8 @@ |
1751 | Network, SecurityGroup, SecurityGroupIngressRule, |
1752 | SecurityGroupInstanceAssociation, AuthToken, User, |
1753 | Project, Certificate, ConsolePool, Console, Zone, |
1754 | - AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration) |
1755 | + AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration, |
1756 | + InstanceCategories) |
1757 | engine = create_engine(FLAGS.sql_connection, echo=False) |
1758 | for model in models: |
1759 | model.metadata.create_all(engine) |
1760 | |
1761 | === modified file 'nova/exception.py' |
1762 | --- nova/exception.py 2011-06-28 15:13:52 +0000 |
1763 | +++ nova/exception.py 2011-06-28 22:54:37 +0000 |
1764 | @@ -145,6 +145,10 @@ |
1765 | message = _("Invalid instance type %(instance_type)s.") |
1766 | |
1767 | |
1768 | +class InvalidInstanceCategory(Invalid): |
1769 | + message = _("Invalid instance category %(instance_cat)s.") |
1770 | + |
1771 | + |
1772 | class InvalidPortRange(Invalid): |
1773 | message = _("Invalid port range %(from_port)s:%(to_port)s.") |
1774 | |
1775 | @@ -482,6 +486,19 @@ |
1776 | "could not be found.") |
1777 | |
1778 | |
1779 | +class NoInstanceCategoriesFound(NotFound): |
1780 | + message = _("Zero instance categories found.") |
1781 | + |
1782 | + |
1783 | +class InstanceCatNotFound(NotFound): |
1784 | + message = _("Instance category %(instance_cat_id)d could not be found.") |
1785 | + |
1786 | + |
1787 | +class InstanceCatNotFoundByName(InstanceCatNotFound): |
1788 | + message = _("Instance category with name %(instance_cat_name)s " |
1789 | + "could not be found.") |
1790 | + |
1791 | + |
1792 | class FlavorNotFound(NotFound): |
1793 | message = _("Flavor %(flavor_id)s could not be found.") |
1794 | |
1795 | |
1796 | === modified file 'nova/flags.py' |
1797 | --- nova/flags.py 2011-06-24 12:01:51 +0000 |
1798 | +++ nova/flags.py 2011-06-28 22:54:37 +0000 |
1799 | @@ -324,6 +324,16 @@ |
1800 | 'default image to use, testing only') |
1801 | DEFINE_string('default_instance_type', 'm1.small', |
1802 | 'default instance type to use, testing only') |
1803 | + |
1804 | +DEFINE_string('instance_cat_regular', 'regular', |
1805 | + 'default instance category for regular compute instances') |
1806 | +DEFINE_string('instance_cat_vc', 'vc', |
1807 | + 'instance category for VC instances') |
1808 | +DEFINE_string('instance_cat_vpn', 'vpn', |
1809 | + 'instance category for VPN instances') |
1810 | +DEFINE_string('instance_cat_admin', 'admin', |
1811 | + 'instance category for generic Admin instances') |
1812 | + |
1813 | DEFINE_string('null_kernel', 'nokernel', |
1814 | 'kernel image that indicates not to use a kernel,' |
1815 | ' but to use a raw disk image instead') |
1816 | |
1817 | === modified file 'nova/tests/api/openstack/test_servers.py' |
1818 | --- nova/tests/api/openstack/test_servers.py 2011-06-24 12:01:51 +0000 |
1819 | +++ nova/tests/api/openstack/test_servers.py 2011-06-28 22:54:37 +0000 |
1820 | @@ -34,6 +34,7 @@ |
1821 | from nova.api.openstack import create_instance_helper |
1822 | import nova.compute.api |
1823 | from nova.compute import instance_types |
1824 | +from nova.compute import instance_categories |
1825 | from nova.compute import power_state |
1826 | import nova.db.api |
1827 | import nova.scheduler.api |
1828 | @@ -129,6 +130,7 @@ |
1829 | metadata.append(InstanceMetadata(key='seq', value=id)) |
1830 | |
1831 | inst_type = instance_types.get_instance_type_by_flavor_id(1) |
1832 | + inst_cat = instance_categories.get_default_instance_cat() |
1833 | |
1834 | if public_addresses is None: |
1835 | public_addresses = list() |
1836 | @@ -160,6 +162,8 @@ |
1837 | "hostname": "", |
1838 | "host": host, |
1839 | "instance_type": dict(inst_type), |
1840 | + "instance_cat": dict(inst_cat), |
1841 | + "instance_cat_id": dict(inst_cat)['id'], |
1842 | "user_data": "", |
1843 | "reservation_id": reservation_id, |
1844 | "mac_address": "", |
1845 | |
1846 | === added file 'nova/tests/test_instance_categories.py' |
1847 | --- nova/tests/test_instance_categories.py 1970-01-01 00:00:00 +0000 |
1848 | +++ nova/tests/test_instance_categories.py 2011-06-28 22:54:37 +0000 |
1849 | @@ -0,0 +1,295 @@ |
1850 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
1851 | + |
1852 | +# Copyright 2011 Ken Pepple |
1853 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1854 | +# not use this file except in compliance with the License. You may obtain |
1855 | +# a copy of the License at |
1856 | +# |
1857 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1858 | +# |
1859 | +# Unless required by applicable law or agreed to in writing, software |
1860 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1861 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1862 | +# License for the specific language governing permissions and limitations |
1863 | +# under the License. |
1864 | +""" |
1865 | +Unit Tests for instance categories code |
1866 | +""" |
1867 | +import time |
1868 | + |
1869 | +from nova import context |
1870 | +from nova import db |
1871 | +from nova import exception |
1872 | +from nova import flags |
1873 | +from nova import log as logging |
1874 | +from nova import test |
1875 | +from nova import utils |
1876 | +from nova.compute import instance_categories |
1877 | +from nova.db.sqlalchemy.session import get_session |
1878 | +from nova.db.sqlalchemy import models |
1879 | + |
1880 | +FLAGS = flags.FLAGS |
1881 | +LOG = logging.getLogger('nova.tests.compute') |
1882 | + |
1883 | + |
1884 | +class InstanceCatTestCase(test.TestCase): |
1885 | + """Test cases for instance categories code""" |
1886 | + def setUp(self): |
1887 | + super(InstanceCatTestCase, self).setUp() |
1888 | + self.cntx = context.RequestContext(None, None) |
1889 | + self.cntx_admin = context.get_admin_context() |
1890 | + session = get_session() |
1891 | + max_id = session.query(models.InstanceCategories).\ |
1892 | + order_by("id desc").\ |
1893 | + first() |
1894 | + self.name = str(int(time.time())) |
1895 | + self.description = "Description_for_" + self.name |
1896 | + self.image_id = "Image_str_for_" + self.name |
1897 | + |
1898 | + def test_instance_cat_create_then_delete(self): |
1899 | + """Ensure instance types can be created""" |
1900 | + starting_list_active = instance_categories.get_all_categories() |
1901 | + starting_list_inactive = instance_categories.get_all_categories(1) |
1902 | + instance_categories.create(self.name, self.description) |
1903 | + new = instance_categories.get_all_categories() |
1904 | + self.assertNotEqual(len(starting_list_active), |
1905 | + len(new), |
1906 | + 'instance category was not created') |
1907 | + self.assertEqual(self.name, |
1908 | + instance_categories.\ |
1909 | + get_instance_cat_by_name(self.name)['name'], |
1910 | + 'instance category with expected name not found '\ |
1911 | + 'or doesnt match') |
1912 | + |
1913 | + cat_id = instance_categories.get_instance_cat_id_by_name(self.name) |
1914 | + |
1915 | + self.assertEqual(self.name, |
1916 | + instance_categories.get_instance_cat(cat_id)["name"], |
1917 | + 'instance category with expected ID not found '\ |
1918 | + 'or doesnt match') |
1919 | + self.assertEqual(self.description, |
1920 | + instance_categories.\ |
1921 | + get_instance_cat(cat_id)["description"], |
1922 | + 'instance category description with expected ID '\ |
1923 | + 'not found or doesnt match') |
1924 | + |
1925 | + instance_categories.destroy(self.name) |
1926 | + self.assertNotEqual(instance_categories.get_all_categories(), |
1927 | + instance_categories.get_all_categories(1), |
1928 | + 'active and inactive should be different') |
1929 | + self.assertEqual(1, |
1930 | + instance_categories.\ |
1931 | + get_instance_cat(cat_id)["deleted"], |
1932 | + 'instance category is not marked as deleted') |
1933 | + self.assertEqual(starting_list_active, |
1934 | + instance_categories.get_all_categories(), |
1935 | + 'instance category was not deleted') |
1936 | + |
1937 | + instance_categories.purge(self.name) |
1938 | + self.assertEqual(starting_list_inactive, |
1939 | + instance_categories.get_all_categories(1), |
1940 | + 'instance category not purged') |
1941 | + |
1942 | + def test_instance_cat_update(self): |
1943 | + """Ensure instance types can be created""" |
1944 | + instance_categories.create(self.name, self.description, self.image_id) |
1945 | + cat_id = instance_categories.get_instance_cat_id_by_name(self.name) |
1946 | + |
1947 | + self.assertEqual(self.name, |
1948 | + instance_categories.get_instance_cat(cat_id)["name"], |
1949 | + 'instance category with expected ID not found '\ |
1950 | + 'or doesnt match') |
1951 | + self.assertEqual(self.description, |
1952 | + instance_categories.\ |
1953 | + get_instance_cat(cat_id)["description"], |
1954 | + 'description not found or doesnt match') |
1955 | + self.assertEqual(self.image_id, |
1956 | + instance_categories.\ |
1957 | + get_instance_cat(cat_id)["image_id"], |
1958 | + 'image_id not found or doesnt match') |
1959 | + |
1960 | + new_desc = 'New desc' |
1961 | + new_image_id = 'New image id' |
1962 | + instance_categories.update(self.name, new_desc, new_image_id) |
1963 | + |
1964 | + self.assertEqual(new_desc, |
1965 | + instance_categories.\ |
1966 | + get_instance_cat(cat_id)["description"], |
1967 | + 'description not found or doesnt match') |
1968 | + self.assertEqual(new_image_id, |
1969 | + instance_categories.\ |
1970 | + get_instance_cat(cat_id)["image_id"], |
1971 | + 'image_id not found or doesnt match') |
1972 | + |
1973 | + instance_categories.destroy(self.name) |
1974 | + instance_categories.purge(self.name) |
1975 | + |
1976 | + def test_instance_cat_non_existant_shouldnt_delete_purge(self): |
1977 | + """ |
1978 | + Ensures that instance category deletion/purge fails with invalid args |
1979 | + """ |
1980 | + self.assertRaises(exception.InvalidInstanceCategory, |
1981 | + instance_categories.destroy, None) |
1982 | + self.assertRaises(exception.ApiError, |
1983 | + instance_categories.destroy, "sfsfsdfdfs") |
1984 | + |
1985 | + self.assertRaises(exception.InvalidInstanceCategory, |
1986 | + instance_categories.purge, None) |
1987 | + self.assertRaises(exception.ApiError, |
1988 | + instance_categories.purge, "qweqwe") |
1989 | + |
1990 | + def test_instance_cat_get_all_cats(self): |
1991 | + """Ensures that all instance categories can be retrieved""" |
1992 | + session = get_session() |
1993 | + total_instance_cats = session.query(models.InstanceCategories).\ |
1994 | + count() |
1995 | + instance_cats = len(instance_categories.get_all_categories(1)) |
1996 | + self.assertEqual(total_instance_cats, instance_cats) |
1997 | + |
1998 | + def test_instance_cat_dup_name_should_raise_api_error(self): |
1999 | + """Ensures that duplicate instance category raises ApiError""" |
2000 | + new_name = self.name + "dup" |
2001 | + instance_categories.create(new_name, "desc - duplicate test") |
2002 | + instance_categories.destroy(new_name) |
2003 | + self.assertRaises(exception.ApiError, |
2004 | + instance_categories.create, new_name, |
2005 | + "desc - duplicate category") |
2006 | + instance_categories.purge(new_name) |
2007 | + |
2008 | + def test_instance_cat_get_by_incorrect_id_name(self): |
2009 | + """Ensures that get cat fails with incorrect args |
2010 | + or returns default values""" |
2011 | + self.assertEqual(instance_categories.get_instance_cat(None), |
2012 | + instance_categories.get_default_instance_cat(), |
2013 | + 'get instance category without id should return '\ |
2014 | + 'default category') |
2015 | + |
2016 | + self.assertEqual(instance_categories.get_instance_cat_by_name(None), |
2017 | + instance_categories.get_default_instance_cat(), |
2018 | + 'get instance category without name should return '\ |
2019 | + 'default category') |
2020 | + |
2021 | + session = get_session() |
2022 | + max_id = session.query(models.InstanceCategories).\ |
2023 | + order_by("id desc").\ |
2024 | + first() |
2025 | + wrong_id = max_id["id"] + 100 |
2026 | + self.assertRaises(exception.NotFound, |
2027 | + instance_categories.get_instance_cat, wrong_id) |
2028 | + |
2029 | + self.assertRaises(exception.NotFound, |
2030 | + instance_categories.get_instance_cat_by_name, |
2031 | + "invalid_cat_name_qweqwe") |
2032 | + |
2033 | + def test_instance_cat_is_admin_name(self): |
2034 | + """Ensures that is_admin_cat_name returns True for exerything except |
2035 | + empty or regular cat names""" |
2036 | + self.assertFalse(instance_categories.is_admin_cat_name(None)) |
2037 | + self.assertFalse(instance_categories.\ |
2038 | + is_admin_cat_name(FLAGS.instance_cat_regular)) |
2039 | + self.assertTrue(instance_categories.\ |
2040 | + is_admin_cat_name(FLAGS.instance_cat_vpn)) |
2041 | + self.assertTrue(instance_categories.\ |
2042 | + is_admin_cat_name(FLAGS.instance_cat_vc)) |
2043 | + self.assertTrue(instance_categories.\ |
2044 | + is_admin_cat_name(FLAGS.instance_cat_admin)) |
2045 | + self.assertTrue(instance_categories.\ |
2046 | + is_admin_cat_name("any_cat_name_asdqwe")) |
2047 | + |
2048 | + def test_instance_cat_is_admin_category(self): |
2049 | + """Ensures that is_admin_category returns True for exerything |
2050 | + except empty or regular cat names""" |
2051 | + cat = {} |
2052 | + cat['name'] = FLAGS.instance_cat_regular |
2053 | + self.assertFalse(instance_categories.is_admin_category(cat)) |
2054 | + cat['name'] = FLAGS.instance_cat_admin |
2055 | + self.assertTrue(instance_categories.is_admin_cat_name(cat)) |
2056 | + |
2057 | + def test_instance_cat_check_default_values(self): |
2058 | + """Ensures correctness of default values""" |
2059 | + self.assertEqual(instance_categories.default_category_name(), |
2060 | + FLAGS.instance_cat_regular) |
2061 | + self.assertEqual(instance_categories.\ |
2062 | + get_default_instance_cat()['name'], |
2063 | + FLAGS.instance_cat_regular) |
2064 | + |
2065 | + def test_instance_cat_create_check_params_and_admin_func(self): |
2066 | + """Ensures that different get and is_admin funcs operate correctly""" |
2067 | + instance_categories.create(self.name, self.description) |
2068 | + |
2069 | + self.assertEqual(self.name, |
2070 | + instance_categories.\ |
2071 | + get_instance_cat_by_name(self.name)['name']) |
2072 | + |
2073 | + cat_id = instance_categories.get_instance_cat_id_by_name(self.name) |
2074 | + self.assertEqual(self.name, |
2075 | + instance_categories.get_instance_cat(cat_id)['name']) |
2076 | + |
2077 | + instance = {} |
2078 | + instance['name'] = 'test compute instance' |
2079 | + instance['instance_cat_id'] = None |
2080 | + # no 'instance_cat_id' field |
2081 | + self.assertFalse(instance_categories.is_admin_instance(instance)) |
2082 | + |
2083 | + instance['instance_cat_id'] = cat_id + 1 |
2084 | + # invalid category id |
2085 | + self.assertRaises(exception.NotFound, |
2086 | + instance_categories.is_admin_instance, instance) |
2087 | + |
2088 | + instance['instance_cat_id'] = cat_id |
2089 | + self.assertTrue(instance_categories.is_admin_instance(instance)) |
2090 | + self.assertEqual(self.name, |
2091 | + instance_categories.get_instance_cat_name(instance)) |
2092 | + |
2093 | + instance['instance_cat'] = {} |
2094 | + self.assertEqual(self.name, |
2095 | + instance_categories.get_instance_cat_name(instance)) |
2096 | + instance['instance_cat']['name'] = self.name |
2097 | + self.assertEqual(self.name, |
2098 | + instance_categories.get_instance_cat_name(instance)) |
2099 | + |
2100 | + self.assertTrue(instance_categories.is_admin_instance(instance)) |
2101 | + self.assertTrue(instance_categories.\ |
2102 | + is_admin_instance(instance, |
2103 | + self.name)) |
2104 | + self.assertFalse(instance_categories.\ |
2105 | + is_admin_instance(instance, |
2106 | + FLAGS.instance_cat_vpn)) |
2107 | + |
2108 | + instance['instance_cat']['name'] = FLAGS.instance_cat_regular |
2109 | + self.assertFalse(instance_categories.is_admin_instance(instance)) |
2110 | + self.assertFalse(instance_categories.\ |
2111 | + is_admin_instance(instance, |
2112 | + FLAGS.instance_cat_regular)) |
2113 | + self.assertFalse(instance_categories.\ |
2114 | + is_admin_instance(instance, |
2115 | + FLAGS.instance_cat_vpn)) |
2116 | + |
2117 | + instance_categories.destroy(self.name) |
2118 | + instance_categories.purge(self.name) |
2119 | + |
2120 | + self.assertTrue(instance_categories.\ |
2121 | + is_access_allowed(self.cntx, instance)) |
2122 | + |
2123 | + instance['instance_cat']['name'] = FLAGS.instance_cat_vpn |
2124 | + self.assertFalse(instance_categories.\ |
2125 | + is_access_allowed(self.cntx, instance)) |
2126 | + |
2127 | + instance_cat = instance_categories.\ |
2128 | + get_instance_cat_by_name(FLAGS.instance_cat_vpn) |
2129 | + self.assertTrue(instance_categories.\ |
2130 | + is_access_allowed(self.cntx, |
2131 | + instance, instance_cat)) |
2132 | + self.assertTrue(instance_categories.\ |
2133 | + is_access_allowed(self.cntx_admin, |
2134 | + instance)) |
2135 | + |
2136 | + instance_cat = instance_categories.\ |
2137 | + get_instance_cat_by_name(FLAGS.instance_cat_vc) |
2138 | + self.assertTrue(instance_categories.\ |
2139 | + is_access_allowed(self.cntx_admin, |
2140 | + instance, instance_cat)) |
2141 | + |
2142 | + instance['instance_cat']['name'] = FLAGS.instance_cat_regular |
2143 | + self.assertTrue(instance_categories.\ |
2144 | + is_access_allowed(self.cntx_admin, instance)) |
2145 | |
2146 | === modified file 'nova/virt/libvirt/firewall.py' |
2147 | --- nova/virt/libvirt/firewall.py 2011-06-24 12:01:51 +0000 |
2148 | +++ nova/virt/libvirt/firewall.py 2011-06-28 22:54:37 +0000 |
2149 | @@ -26,6 +26,7 @@ |
2150 | from nova import log as logging |
2151 | from nova import utils |
2152 | from nova.virt.libvirt import netutils |
2153 | +from nova.compute import instance_categories |
2154 | |
2155 | |
2156 | LOG = logging.getLogger("nova.virt.libvirt.firewall") |
2157 | @@ -204,7 +205,8 @@ |
2158 | logging.info('ensuring static filters') |
2159 | self._ensure_static_filters() |
2160 | |
2161 | - if instance['image_ref'] == str(FLAGS.vpn_image_id): |
2162 | + if instance_categories.is_admin_instance(instance, |
2163 | + FLAGS.instance_cat_vpn): |
2164 | base_filter = 'nova-vpn' |
2165 | else: |
2166 | base_filter = 'nova-base' |
2167 | @@ -375,7 +377,8 @@ |
2168 | |
2169 | def _create_network_filters(self, instance, network_info, |
2170 | instance_secgroup_filter_name): |
2171 | - if instance['image_ref'] == str(FLAGS.vpn_image_id): |
2172 | + if instance_categories.is_admin_instance(instance, |
2173 | + FLAGS.instance_cat_vpn): |
2174 | base_filter = 'nova-vpn' |
2175 | else: |
2176 | base_filter = 'nova-base' |
Hi Vladimir,
You have a number of merge conflicts that need to be resolved.