|
1 This fix will be included in future 2015.1.3 (Kilo) and 5.0.1 (Liberty) |
|
2 releases. |
|
3 |
|
4 From fa19a617a79fd1cb0d892bb8ea87c4b9f6398c34 Mon Sep 17 00:00:00 2001 |
|
5 From: Zane Bitter <[email protected]> |
|
6 Date: Tue, 24 Nov 2015 12:29:38 -0500 |
|
7 Subject: Load template files only from their known source |
|
8 |
|
9 Modify get_class to ensure that user-defined resources cannot result in |
|
10 reads from the local filesystem. Only resources defined by the operator |
|
11 in the global environment should read local files. |
|
12 |
|
13 To make this work, this patch also adds a separate |
|
14 get_class_to_instantiate() method to the Environment. |
|
15 |
|
16 We were previously using get_class for two different purposes - to get a |
|
17 resource plugin on which we could perform introspection to obtain the |
|
18 properties and attributes schema, and to get a resource plugin we could |
|
19 instantiate to create a Resource object. These are both the same except in |
|
20 the case of a TemplateResource, where having two different use cases for |
|
21 the same piece of code was adding considerable extra complexity. Combining |
|
22 the use cases in this way also made the error handling confusing (leading |
|
23 to bug 1518458). |
|
24 |
|
25 This change separates out the two cases. |
|
26 |
|
27 Change-Id: I845e7d23c73242a4a4c9c40599690ab705c75caa |
|
28 Closes-Bug: #1496277 |
|
29 Related-Bug: #1447194 |
|
30 Related-Bug: #1518458 |
|
31 Related-Bug: #1508115 |
|
32 (cherry picked from commit 06a713c4456203cd561f16721dc8ac3bcbb37a3 |
|
33 and 26e6d5f6d776c1027c4f27058767952a58d15e25) |
|
34 --- |
|
35 |
|
36 --- heat-2015.1.2/heat/engine/environment.py.~1~ 2015-10-13 09:51:53.000000000 -0700 |
|
37 +++ heat-2015.1.2/heat/engine/environment.py 2016-01-25 20:50:15.096875593 -0800 |
|
38 @@ -112,6 +112,12 @@ class ResourceInfo(object): |
|
39 def matches(self, resource_type): |
|
40 return False |
|
41 |
|
42 + def get_class(self): |
|
43 + raise NotImplemented |
|
44 + |
|
45 + def get_class_to_instantiate(self): |
|
46 + return self.get_class() |
|
47 + |
|
48 def __str__(self): |
|
49 return '[%s](User:%s) %s -> %s' % (self.description, |
|
50 self.user_resource, |
|
51 @@ -140,10 +146,20 @@ class TemplateResourceInfo(ResourceInfo) |
|
52 |
|
53 def get_class(self): |
|
54 from heat.engine.resources import template_resource |
|
55 + if self.user_resource: |
|
56 + allowed_schemes = template_resource.REMOTE_SCHEMES |
|
57 + else: |
|
58 + allowed_schemes = template_resource.LOCAL_SCHEMES |
|
59 + data = template_resource.TemplateResource.get_template_file( |
|
60 + self.template_name, |
|
61 + allowed_schemes) |
|
62 env = self.registry.environment |
|
63 - return template_resource.generate_class(str(self.name), |
|
64 - self.template_name, |
|
65 - env) |
|
66 + return template_resource.generate_class_from_template(str(self.name), |
|
67 + data, env) |
|
68 + |
|
69 + def get_class_to_instantiate(self): |
|
70 + from heat.engine.resources import template_resource |
|
71 + return template_resource.TemplateResource |
|
72 |
|
73 |
|
74 class MapResourceInfo(ResourceInfo): |
|
75 @@ -398,6 +414,13 @@ class ResourceRegistry(object): |
|
76 return match |
|
77 |
|
78 def get_class(self, resource_type, resource_name=None): |
|
79 + info = self.get_resource_info(resource_type, |
|
80 + resource_name=resource_name) |
|
81 + if info is None: |
|
82 + raise exception.ResourceTypeNotFound(type_name=resource_type) |
|
83 + return info.get_class() |
|
84 + |
|
85 + def get_class_to_instantiate(self, resource_type, resource_name=None): |
|
86 if resource_type == "": |
|
87 msg = _('Resource "%s" has no type') % resource_name |
|
88 raise exception.StackValidationFailed(message=msg) |
|
89 @@ -414,7 +437,7 @@ class ResourceRegistry(object): |
|
90 if info is None: |
|
91 msg = _("Unknown resource Type : %s") % resource_type |
|
92 raise exception.StackValidationFailed(message=msg) |
|
93 - return info.get_class() |
|
94 + return info.get_class_to_instantiate() |
|
95 |
|
96 def as_dict(self): |
|
97 """Return user resources in a dict format.""" |
|
98 @@ -521,6 +544,10 @@ class Environment(object): |
|
99 def get_class(self, resource_type, resource_name=None): |
|
100 return self.registry.get_class(resource_type, resource_name) |
|
101 |
|
102 + def get_class_to_instantiate(self, resource_type, resource_name=None): |
|
103 + return self.registry.get_class_to_instantiate(resource_type, |
|
104 + resource_name) |
|
105 + |
|
106 def get_types(self, support_status=None): |
|
107 return self.registry.get_types(support_status) |
|
108 |
|
109 --- heat-2015.1.2/heat/engine/resource.py.~1~ 2015-10-13 09:51:53.000000000 -0700 |
|
110 +++ heat-2015.1.2/heat/engine/resource.py 2016-01-25 20:50:15.097540727 -0800 |
|
111 @@ -137,14 +137,11 @@ class Resource(object): |
|
112 # Call is already for a subclass, so pass it through |
|
113 ResourceClass = cls |
|
114 else: |
|
115 - from heat.engine.resources import template_resource |
|
116 - |
|
117 registry = stack.env.registry |
|
118 - try: |
|
119 - ResourceClass = registry.get_class(definition.resource_type, |
|
120 - resource_name=name) |
|
121 - except exception.NotFound: |
|
122 - ResourceClass = template_resource.TemplateResource |
|
123 + ResourceClass = registry.get_class_to_instantiate( |
|
124 + definition.resource_type, |
|
125 + resource_name=name) |
|
126 + |
|
127 assert issubclass(ResourceClass, Resource) |
|
128 |
|
129 return super(Resource, cls).__new__(ResourceClass) |
|
130 --- heat-2015.1.2/heat/engine/resources/openstack/heat/resource_group.py.~1~ 2015-10-13 09:51:53.000000000 -0700 |
|
131 +++ heat-2015.1.2/heat/engine/resources/openstack/heat/resource_group.py 2016-01-25 20:50:15.098020940 -0800 |
|
132 @@ -193,11 +193,7 @@ class ResourceGroup(stack_resource.Stack |
|
133 val_templ = template.Template(test_tmpl) |
|
134 res_def = val_templ.resource_definitions(self.stack)["0"] |
|
135 # make sure we can resolve the nested resource type |
|
136 - try: |
|
137 - self.stack.env.get_class(res_def.resource_type) |
|
138 - except exception.NotFound: |
|
139 - # its a template resource |
|
140 - pass |
|
141 + self.stack.env.get_class_to_instantiate(res_def.resource_type) |
|
142 |
|
143 try: |
|
144 name = "%s-%s" % (self.stack.name, self.name) |
|
145 --- heat-2015.1.2/heat/engine/resources/template_resource.py.~1~ 2015-10-13 09:51:53.000000000 -0700 |
|
146 +++ heat-2015.1.2/heat/engine/resources/template_resource.py 2016-01-25 20:50:15.098440251 -0800 |
|
147 @@ -26,8 +26,11 @@ from heat.engine.resources import stack_ |
|
148 from heat.engine import template |
|
149 |
|
150 |
|
151 -def generate_class(name, template_name, env): |
|
152 - data = TemplateResource.get_template_file(template_name, ('file',)) |
|
153 +REMOTE_SCHEMES = ('http', 'https') |
|
154 +LOCAL_SCHEMES = ('file',) |
|
155 + |
|
156 + |
|
157 +def generate_class_from_template(name, data, env): |
|
158 tmpl = template.Template(template_format.parse(data)) |
|
159 props, attrs = TemplateResource.get_schemas(tmpl, env.param_defaults) |
|
160 cls = type(name, (TemplateResource,), |
|
161 @@ -74,9 +77,9 @@ class TemplateResource(stack_resource.St |
|
162 self.template_name = tri.template_name |
|
163 self.resource_type = tri.name |
|
164 if tri.user_resource: |
|
165 - self.allowed_schemes = ('http', 'https') |
|
166 + self.allowed_schemes = REMOTE_SCHEMES |
|
167 else: |
|
168 - self.allowed_schemes = ('http', 'https', 'file') |
|
169 + self.allowed_schemes = REMOTE_SCHEMES + LOCAL_SCHEMES |
|
170 |
|
171 return tri |
|
172 |
|
173 --- heat-2015.1.2/heat/engine/service.py.~1~ 2015-10-13 09:51:53.000000000 -0700 |
|
174 +++ heat-2015.1.2/heat/engine/service.py 2016-01-25 20:50:15.099200696 -0800 |
|
175 @@ -978,8 +978,6 @@ class EngineService(service.Service): |
|
176 """ |
|
177 try: |
|
178 resource_class = resources.global_env().get_class(type_name) |
|
179 - except exception.StackValidationFailed: |
|
180 - raise exception.ResourceTypeNotFound(type_name=type_name) |
|
181 except exception.NotFound as ex: |
|
182 raise exception.StackValidationFailed(message=ex.message) |
|
183 |
|
184 @@ -1010,8 +1008,6 @@ class EngineService(service.Service): |
|
185 try: |
|
186 return resources.global_env().get_class( |
|
187 type_name).resource_to_template(type_name) |
|
188 - except exception.StackValidationFailed: |
|
189 - raise exception.ResourceTypeNotFound(type_name=type_name) |
|
190 except exception.NotFound as ex: |
|
191 raise exception.StackValidationFailed(message=ex.message) |
|
192 |
|
193 --- heat-2015.1.2/heat/tests/test_provider_template.py.~1~ 2015-10-13 09:51:54.000000000 -0700 |
|
194 +++ heat-2015.1.2/heat/tests/test_provider_template.py 2016-01-25 20:50:15.099763200 -0800 |
|
195 @@ -613,7 +613,11 @@ class ProviderTemplateTest(common.HeatTe |
|
196 |
|
197 env_str = {'resource_registry': {'resources': {'fred': { |
|
198 "OS::ResourceType": test_templ_name}}}} |
|
199 - env = environment.Environment(env_str) |
|
200 + global_env = environment.Environment({}, user_env=False) |
|
201 + global_env.load(env_str) |
|
202 + with mock.patch('heat.engine.resources._environment', |
|
203 + global_env): |
|
204 + env = environment.Environment({}) |
|
205 cls = env.get_class('OS::ResourceType', 'fred') |
|
206 self.assertNotEqual(template_resource.TemplateResource, cls) |
|
207 self.assertTrue(issubclass(cls, template_resource.TemplateResource)) |
|
208 @@ -640,10 +644,6 @@ class ProviderTemplateTest(common.HeatTe |
|
209 self.assertTrue(test_templ, "Empty test template") |
|
210 self.m.StubOutWithMock(urlfetch, "get") |
|
211 urlfetch.get(test_templ_name, |
|
212 - allowed_schemes=('file',) |
|
213 - ).AndRaise(urlfetch.URLFetchError( |
|
214 - _('Failed to retrieve template'))) |
|
215 - urlfetch.get(test_templ_name, |
|
216 allowed_schemes=('http', 'https')).AndReturn(test_templ) |
|
217 parsed_test_templ = template_format.parse(test_templ) |
|
218 self.m.ReplayAll() |
|
219 --- heat-2015.1.2/heat/tests/test_resource.py.~1~ 2015-10-13 09:51:54.000000000 -0700 |
|
220 +++ heat-2015.1.2/heat/tests/test_resource.py 2016-01-25 20:50:15.100592773 -0800 |
|
221 @@ -67,12 +67,13 @@ class ResourceTest(common.HeatTestCase): |
|
222 self.patch('heat.engine.resource.warnings') |
|
223 |
|
224 def test_get_class_ok(self): |
|
225 - cls = resources.global_env().get_class('GenericResourceType') |
|
226 + cls = resources.global_env().get_class_to_instantiate( |
|
227 + 'GenericResourceType') |
|
228 self.assertEqual(generic_rsrc.GenericResource, cls) |
|
229 |
|
230 def test_get_class_noexist(self): |
|
231 self.assertRaises(exception.StackValidationFailed, |
|
232 - resources.global_env().get_class, |
|
233 + resources.global_env().get_class_to_instantiate, |
|
234 'NoExistResourceType') |
|
235 |
|
236 def test_resource_new_ok(self): |