~rackspace-titan/nova/api-profiling

« back to all changes in this revision

Viewing changes to nova/api/openstack/servers.py

  • Committer: Tarmac
  • Author(s): Sandy Walsh
  • Date: 2011-06-14 21:11:25 UTC
  • mfrom: (1063.8.27 dist-sched-4)
  • Revision ID: tarmac-20110614211125-sbdntqdts9nn0ha9
Phew ... ok, this is the last dist-scheduler merge before we get into serious testing and minor tweaks. The heavy lifting is largely done.

This branch adds an OS API POST /zone/boot command which returns a reservation ID (unlike POST /servers which returns a single instance_id). 

This branch requires v2.5 of python-novaclient

Additionally GET /servers can now take an optional reservation_id parameter, which will return all the instances with that reservation ID across all zones.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
import traceback
18
18
 
19
19
from webob import exc
20
 
from xml.dom import minidom
21
20
 
22
21
from nova import compute
23
22
from nova import exception
24
23
from nova import flags
25
 
import nova.image
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
40
36
 
48
44
 
49
45
    def __init__(self):
50
46
        self.compute_api = compute.API()
51
 
        self._image_service = utils.import_object(FLAGS.image_service)
 
47
        self.helper = helper.CreateInstanceHelper(self)
52
48
 
53
49
    def index(self, req):
54
50
        """ Returns a list of server names and ids for a given user """
66
62
            return exc.HTTPBadRequest(str(err))
67
63
        return servers
68
64
 
69
 
    def _image_ref_from_req_data(self, data):
70
 
        raise NotImplementedError()
71
 
 
72
 
    def _flavor_id_from_req_data(self, data):
73
 
        raise NotImplementedError()
74
 
 
75
65
    def _get_view_builder(self, req):
76
66
        raise NotImplementedError()
77
67
 
86
76
 
87
77
        builder - the response model builder
88
78
        """
89
 
        instance_list = self.compute_api.get_all(req.environ['nova.context'])
 
79
        reservation_id = req.str_GET.get('reservation_id')
 
80
        instance_list = self.compute_api.get_all(
 
81
                                            req.environ['nova.context'],
 
82
                                            reservation_id=reservation_id)
90
83
        limited_list = self._limit_items(instance_list, req)
91
84
        builder = self._get_view_builder(req)
92
85
        servers = [builder.build(inst, is_detail)['server']
115
108
 
116
109
    def create(self, req, body):
117
110
        """ Creates a new server for a given user """
118
 
        if not body:
119
 
            return faults.Fault(exc.HTTPUnprocessableEntity())
120
 
 
121
 
        context = req.environ['nova.context']
122
 
 
123
 
        password = self._get_server_admin_password(body['server'])
124
 
 
125
 
        key_name = None
126
 
        key_data = None
127
 
        key_pairs = auth_manager.AuthManager.get_key_pairs(context)
128
 
        if key_pairs:
129
 
            key_pair = key_pairs[0]
130
 
            key_name = key_pair['name']
131
 
            key_data = key_pair['public_key']
132
 
 
133
 
        image_href = self._image_ref_from_req_data(body)
134
 
        try:
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
140
 
        except:
141
 
            msg = _("Cannot find requested image %s") % image_href
142
 
            return faults.Fault(exc.HTTPBadRequest(msg))
143
 
 
144
 
        personality = body['server'].get('personality')
145
 
 
146
 
        injected_files = []
147
 
        if personality:
148
 
            injected_files = self._get_injected_files(personality)
149
 
 
150
 
        flavor_id = self._flavor_id_from_req_data(body)
151
 
 
152
 
        if not 'name' in body['server']:
153
 
            msg = _("Server name is not defined")
154
 
            return exc.HTTPBadRequest(msg)
155
 
 
156
 
        zone_blob = body['server'].get('blob')
157
 
        name = body['server']['name']
158
 
        self._validate_server_name(name)
159
 
        name = name.strip()
160
 
 
161
 
        try:
162
 
            inst_type = \
163
 
                instance_types.get_instance_type_by_flavor_id(flavor_id)
164
 
            (inst,) = self.compute_api.create(
165
 
                context,
166
 
                inst_type,
167
 
                image_href,
168
 
                kernel_id=kernel_id,
169
 
                ramdisk_id=ramdisk_id,
170
 
                display_name=name,
171
 
                display_description=name,
172
 
                key_name=key_name,
173
 
                key_data=key_data,
174
 
                metadata=body['server'].get('metadata', {}),
175
 
                injected_files=injected_files,
176
 
                admin_password=password,
177
 
                zone_blob=zone_blob)
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))
183
 
 
184
 
        inst['instance_type'] = inst_type
185
 
        inst['image_ref'] = image_href
 
111
        extra_values = None
 
112
        result = None
 
113
        try:
 
114
            extra_values, result = self.helper.create_instance(
 
115
                                    req, body, self.compute_api.create)
 
116
        except faults.Fault, f:
 
117
            return f
 
118
 
 
119
        instances = result
 
120
 
 
121
        (inst, ) = instances
 
122
        for key in ['instance_type', 'image_ref']:
 
123
            inst[key] = extra_values[key]
186
124
 
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']
190
128
        return server
191
129
 
192
 
    def _get_injected_files(self, personality):
193
 
        """
194
 
        Create a list of injected files from the personality attribute
195
 
 
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.
199
 
        """
200
 
        injected_files = []
201
 
 
202
 
        for item in personality:
203
 
            try:
204
 
                path = item['path']
205
 
                contents = item['contents']
206
 
            except KeyError as key:
207
 
                expl = _('Bad personality format: missing %s') % key
208
 
                raise exc.HTTPBadRequest(explanation=expl)
209
 
            except TypeError:
210
 
                expl = _('Bad personality format')
211
 
                raise exc.HTTPBadRequest(explanation=expl)
212
 
            try:
213
 
                contents = base64.b64decode(contents)
214
 
            except TypeError:
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
219
 
 
220
 
    def _handle_quota_error(self, error):
221
 
        """
222
 
        Reraise quota errors as api-specific http exceptions
223
 
        """
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
234
 
        raise error
235
 
 
236
 
    def _get_server_admin_password(self, server):
237
 
        """ Determine the admin password for a server on creation """
238
 
        return utils.generate_password(16)
239
 
 
240
130
    @scheduler_api.redirect_handler
241
131
    def update(self, req, id, body):
242
132
        """ Updates the server name or password """
251
141
 
252
142
        if 'name' in body['server']:
253
143
            name = body['server']['name']
254
 
            self._validate_server_name(name)
 
144
            self.helper._validate_server_name(name)
255
145
            update_dict['display_name'] = name.strip()
256
146
 
257
147
        self._parse_update(ctxt, id, body, update_dict)
263
153
 
264
154
        return exc.HTTPNoContent()
265
155
 
266
 
    def _validate_server_name(self, value):
267
 
        if not isinstance(value, basestring):
268
 
            msg = _("Server name is not a string or unicode")
269
 
            raise exc.HTTPBadRequest(msg)
270
 
 
271
 
        if value.strip() == '':
272
 
            msg = _("Server name is an empty string")
273
 
            raise exc.HTTPBadRequest(msg)
274
 
 
275
156
    def _parse_update(self, context, id, inst_dict, update_dict):
276
157
        pass
277
158
 
520
401
                error=item.error))
521
402
        return dict(actions=actions)
522
403
 
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.
526
 
        """
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)
532
 
 
533
 
    @staticmethod
534
 
    def  _do_get_kernel_ramdisk_from_image(image_meta):
535
 
        """Given an ImageService image_meta, return kernel and ramdisk image
536
 
        ids if present.
537
 
 
538
 
        This is only valid for `ami` style images.
539
 
        """
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"))
544
 
 
545
 
        if image_meta.get('container_format') != 'ami':
546
 
            return None, None
547
 
 
548
 
        try:
549
 
            kernel_id = image_meta['properties']['kernel_id']
550
 
        except KeyError:
551
 
            raise exception.KernelNotFoundForImage(image_id=image_id)
552
 
 
553
 
        try:
554
 
            ramdisk_id = image_meta['properties']['ramdisk_id']
555
 
        except KeyError:
556
 
            raise exception.RamdiskNotFoundForImage(image_id=image_id)
557
 
 
558
 
        return kernel_id, ramdisk_id
559
 
 
560
404
 
561
405
class ControllerV10(Controller):
 
406
 
562
407
    def _image_ref_from_req_data(self, data):
563
408
        return data['server']['imageId']
564
409
 
615
460
        response.empty_body = True
616
461
        return response
617
462
 
 
463
    def _get_server_admin_password(self, server):
 
464
        """ Determine the admin password for a server on creation """
 
465
        return self.helper._get_server_admin_password_old_style(server)
 
466
 
618
467
 
619
468
class ControllerV11(Controller):
620
469
    def _image_ref_from_req_data(self, data):
724
573
        response.empty_body = True
725
574
        return response
726
575
 
 
576
    def get_default_xmlns(self, req):
 
577
        return common.XML_NS_V11
 
578
 
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')
730
 
        if password is None:
731
 
            return utils.generate_password(16)
732
 
        if not isinstance(password, basestring) or password == '':
733
 
            msg = _("Invalid adminPass")
734
 
            raise exc.HTTPBadRequest(msg)
735
 
        return password
736
 
 
737
 
 
738
 
class ServerXMLDeserializer(wsgi.XMLDeserializer):
739
 
    """
740
 
    Deserializer to handle xml-formatted server create requests.
741
 
 
742
 
    Handles standard server attributes as well as optional metadata
743
 
    and personality attributes
744
 
    """
745
 
 
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}
751
 
 
752
 
    def _extract_server(self, node):
753
 
        """Marshal the server attribute of a parsed request"""
754
 
        server = {}
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
765
 
        return server
766
 
 
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:
771
 
            return None
772
 
        metadata = {}
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)
776
 
        return metadata
777
 
 
778
 
    def _extract_personality(self, server_node):
779
 
        """Marshal the personality attribute of a parsed request"""
780
 
        personality_node = \
781
 
                self._find_first_child_named(server_node, "personality")
782
 
        if personality_node is None:
783
 
            return None
784
 
        personality = []
785
 
        for file_node in self._find_children_named(personality_node, "file"):
786
 
            item = {}
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)
791
 
        return personality
792
 
 
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:
797
 
                return node
798
 
        return None
799
 
 
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:
804
 
                yield node
805
 
 
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
812
 
        return ""
 
581
        return self.helper._get_server_admin_password_new_style(server)
813
582
 
814
583
 
815
584
def create_resource(version='1.0'):
845
614
    }
846
615
 
847
616
    deserializers = {
848
 
        'application/xml': ServerXMLDeserializer(),
 
617
        'application/xml': helper.ServerXMLDeserializer(),
849
618
    }
850
619
 
851
620
    return wsgi.Resource(controller, serializers=serializers,