19
19
from webob import exc
20
from xml.dom import minidom
22
21
from nova import compute
23
22
from nova import exception
24
23
from nova import flags
26
24
from nova import log as logging
27
from nova import quota
28
25
from nova import utils
29
26
from nova.api.openstack import common
27
from nova.api.openstack import create_instance_helper as helper
30
28
from nova.api.openstack import faults
31
29
import nova.api.openstack.views.addresses
32
30
import nova.api.openstack.views.flavors
33
31
import nova.api.openstack.views.images
34
32
import nova.api.openstack.views.servers
35
33
from nova.api.openstack import wsgi
36
from nova.auth import manager as auth_manager
37
from nova.compute import instance_types
38
34
import nova.api.openstack
39
35
from nova.scheduler import api as scheduler_api
116
109
def create(self, req, body):
117
110
""" Creates a new server for a given user """
119
return faults.Fault(exc.HTTPUnprocessableEntity())
121
context = req.environ['nova.context']
123
password = self._get_server_admin_password(body['server'])
127
key_pairs = auth_manager.AuthManager.get_key_pairs(context)
129
key_pair = key_pairs[0]
130
key_name = key_pair['name']
131
key_data = key_pair['public_key']
133
image_href = self._image_ref_from_req_data(body)
135
image_service, image_id = nova.image.get_image_service(image_href)
136
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
137
req, image_service, image_id)
138
images = set([str(x['id']) for x in image_service.index(context)])
139
assert str(image_id) in images
141
msg = _("Cannot find requested image %s") % image_href
142
return faults.Fault(exc.HTTPBadRequest(msg))
144
personality = body['server'].get('personality')
148
injected_files = self._get_injected_files(personality)
150
flavor_id = self._flavor_id_from_req_data(body)
152
if not 'name' in body['server']:
153
msg = _("Server name is not defined")
154
return exc.HTTPBadRequest(msg)
156
zone_blob = body['server'].get('blob')
157
name = body['server']['name']
158
self._validate_server_name(name)
163
instance_types.get_instance_type_by_flavor_id(flavor_id)
164
(inst,) = self.compute_api.create(
169
ramdisk_id=ramdisk_id,
171
display_description=name,
174
metadata=body['server'].get('metadata', {}),
175
injected_files=injected_files,
176
admin_password=password,
178
except quota.QuotaError as error:
179
self._handle_quota_error(error)
180
except exception.ImageNotFound as error:
181
msg = _("Can not find requested image")
182
return faults.Fault(exc.HTTPBadRequest(msg))
184
inst['instance_type'] = inst_type
185
inst['image_ref'] = image_href
114
extra_values, result = self.helper.create_instance(
115
req, body, self.compute_api.create)
116
except faults.Fault, f:
122
for key in ['instance_type', 'image_ref']:
123
inst[key] = extra_values[key]
187
125
builder = self._get_view_builder(req)
188
126
server = builder.build(inst, is_detail=True)
189
server['server']['adminPass'] = password
127
server['server']['adminPass'] = extra_values['password']
192
def _get_injected_files(self, personality):
194
Create a list of injected files from the personality attribute
196
At this time, injected_files must be formatted as a list of
197
(file_path, file_content) pairs for compatibility with the
198
underlying compute service.
202
for item in personality:
205
contents = item['contents']
206
except KeyError as key:
207
expl = _('Bad personality format: missing %s') % key
208
raise exc.HTTPBadRequest(explanation=expl)
210
expl = _('Bad personality format')
211
raise exc.HTTPBadRequest(explanation=expl)
213
contents = base64.b64decode(contents)
215
expl = _('Personality content for %s cannot be decoded') % path
216
raise exc.HTTPBadRequest(explanation=expl)
217
injected_files.append((path, contents))
218
return injected_files
220
def _handle_quota_error(self, error):
222
Reraise quota errors as api-specific http exceptions
224
if error.code == "OnsetFileLimitExceeded":
225
expl = _("Personality file limit exceeded")
226
raise exc.HTTPBadRequest(explanation=expl)
227
if error.code == "OnsetFilePathLimitExceeded":
228
expl = _("Personality file path too long")
229
raise exc.HTTPBadRequest(explanation=expl)
230
if error.code == "OnsetFileContentLimitExceeded":
231
expl = _("Personality file content too long")
232
raise exc.HTTPBadRequest(explanation=expl)
233
# if the original error is okay, just reraise it
236
def _get_server_admin_password(self, server):
237
""" Determine the admin password for a server on creation """
238
return utils.generate_password(16)
240
130
@scheduler_api.redirect_handler
241
131
def update(self, req, id, body):
242
132
""" Updates the server name or password """
520
401
error=item.error))
521
402
return dict(actions=actions)
523
def _get_kernel_ramdisk_from_image(self, req, image_service, image_id):
524
"""Fetch an image from the ImageService, then if present, return the
525
associated kernel and ramdisk image IDs.
527
context = req.environ['nova.context']
528
image_meta = image_service.show(context, image_id)
529
# NOTE(sirp): extracted to a separate method to aid unit-testing, the
530
# new method doesn't need a request obj or an ImageService stub
531
return self._do_get_kernel_ramdisk_from_image(image_meta)
534
def _do_get_kernel_ramdisk_from_image(image_meta):
535
"""Given an ImageService image_meta, return kernel and ramdisk image
538
This is only valid for `ami` style images.
540
image_id = image_meta['id']
541
if image_meta['status'] != 'active':
542
raise exception.ImageUnacceptable(image_id=image_id,
543
reason=_("status is not active"))
545
if image_meta.get('container_format') != 'ami':
549
kernel_id = image_meta['properties']['kernel_id']
551
raise exception.KernelNotFoundForImage(image_id=image_id)
554
ramdisk_id = image_meta['properties']['ramdisk_id']
556
raise exception.RamdiskNotFoundForImage(image_id=image_id)
558
return kernel_id, ramdisk_id
561
405
class ControllerV10(Controller):
562
407
def _image_ref_from_req_data(self, data):
563
408
return data['server']['imageId']
724
573
response.empty_body = True
576
def get_default_xmlns(self, req):
577
return common.XML_NS_V11
727
579
def _get_server_admin_password(self, server):
728
580
""" Determine the admin password for a server on creation """
729
password = server.get('adminPass')
731
return utils.generate_password(16)
732
if not isinstance(password, basestring) or password == '':
733
msg = _("Invalid adminPass")
734
raise exc.HTTPBadRequest(msg)
738
class ServerXMLDeserializer(wsgi.XMLDeserializer):
740
Deserializer to handle xml-formatted server create requests.
742
Handles standard server attributes as well as optional metadata
743
and personality attributes
746
def create(self, string):
747
"""Deserialize an xml-formatted server create request"""
748
dom = minidom.parseString(string)
749
server = self._extract_server(dom)
750
return {'server': server}
752
def _extract_server(self, node):
753
"""Marshal the server attribute of a parsed request"""
755
server_node = self._find_first_child_named(node, 'server')
756
for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
757
if server_node.getAttribute(attr):
758
server[attr] = server_node.getAttribute(attr)
759
metadata = self._extract_metadata(server_node)
760
if metadata is not None:
761
server["metadata"] = metadata
762
personality = self._extract_personality(server_node)
763
if personality is not None:
764
server["personality"] = personality
767
def _extract_metadata(self, server_node):
768
"""Marshal the metadata attribute of a parsed request"""
769
metadata_node = self._find_first_child_named(server_node, "metadata")
770
if metadata_node is None:
773
for meta_node in self._find_children_named(metadata_node, "meta"):
774
key = meta_node.getAttribute("key")
775
metadata[key] = self._extract_text(meta_node)
778
def _extract_personality(self, server_node):
779
"""Marshal the personality attribute of a parsed request"""
781
self._find_first_child_named(server_node, "personality")
782
if personality_node is None:
785
for file_node in self._find_children_named(personality_node, "file"):
787
if file_node.hasAttribute("path"):
788
item["path"] = file_node.getAttribute("path")
789
item["contents"] = self._extract_text(file_node)
790
personality.append(item)
793
def _find_first_child_named(self, parent, name):
794
"""Search a nodes children for the first child with a given name"""
795
for node in parent.childNodes:
796
if node.nodeName == name:
800
def _find_children_named(self, parent, name):
801
"""Return all of a nodes children who have the given name"""
802
for node in parent.childNodes:
803
if node.nodeName == name:
806
def _extract_text(self, node):
807
"""Get the text field contained by the given node"""
808
if len(node.childNodes) == 1:
809
child = node.childNodes[0]
810
if child.nodeType == child.TEXT_NODE:
811
return child.nodeValue
581
return self.helper._get_server_admin_password_new_style(server)
815
584
def create_resource(version='1.0'):