27
27
from nova.api.openstack.compute.views import servers as views_servers
28
28
from nova.api.openstack import wsgi
29
29
from nova.api.openstack import xmlutil
30
from nova import block_device
30
31
from nova import compute
31
32
from nova.compute import flavors
32
33
from nova import exception
33
from nova.openstack.common import importutils
34
from nova.openstack.common.gettextutils import _
34
35
from nova.openstack.common import log as logging
35
36
from nova.openstack.common.rpc import common as rpc_common
36
37
from nova.openstack.common import strutils
37
38
from nova.openstack.common import timeutils
38
39
from nova.openstack.common import uuidutils
40
from nova import policy
39
41
from nova import utils
219
221
if block_device_mapping is not None:
220
222
server["block_device_mapping"] = block_device_mapping
224
block_device_mapping_v2 = self._extract_block_device_mapping_v2(
226
if block_device_mapping_v2 is not None:
227
server["block_device_mapping_v2"] = block_device_mapping_v2
222
229
# NOTE(vish): Support this incorrect version because it was in the code
223
230
# base for a while and we don't want to accidentally break
224
231
# anyone that might be using it.
271
def _extract_block_device_mapping_v2(self, server_node):
272
"""Marshal the new block_device_mappings."""
273
node = self.find_first_child_named(server_node,
274
"block_device_mapping_v2")
276
block_device_mapping = []
277
for child in self.extract_elements(node):
278
if child.nodeName != "mapping":
280
block_device_mapping.append(
281
dict((attr, child.getAttribute(attr))
282
for attr in block_device.bdm_new_api_fields
283
if child.getAttribute(attr)))
284
return block_device_mapping
264
286
def _extract_scheduler_hints(self, server_node):
265
287
"""Marshal the scheduler hints attribute of a parsed request."""
266
288
node = self.find_first_child_named_in_namespace(server_node,
463
485
super(Controller, self).__init__(**kwargs)
464
486
self.compute_api = compute.API()
465
487
self.ext_mgr = ext_mgr
466
self.neutron_attempted = False
468
489
@wsgi.serializers(xml=MinimalServersTemplate)
469
490
def index(self, req):
494
515
self._get_server_search_options())
496
517
# Verify search by 'status' contains a valid status.
497
# Convert it to filter by vm_state for compute_api.
518
# Convert it to filter by vm_state or task_state for compute_api.
498
519
status = search_opts.pop('status', None)
499
520
if status is not None:
500
state = common.vm_state_from_status(status)
521
vm_state, task_state = common.task_and_vm_state_from_status(status)
522
if not vm_state and not task_state:
502
523
return {'servers': []}
503
search_opts['vm_state'] = state
524
search_opts['vm_state'] = vm_state
525
# When we search by vm state, task state will return 'default'.
526
# So we don't need task_state search_opt.
527
if 'default' not in task_state:
528
search_opts['task_state'] = task_state
505
530
if 'changes-since' in search_opts:
580
605
def _validate_server_name(self, value):
581
606
self._check_string_length(value, 'Server name', max_length=255)
583
def _validate_int_value(self, str_value, str_name,
584
min_value=None, max_value=None):
586
value = int(str(str_value))
588
msg = _('%(value_name)s must be an integer')
589
raise exc.HTTPBadRequest(explanation=msg % (
590
{'value_name': str_name}))
592
if min_value is not None:
593
if value < min_value:
594
msg = _('%(value_name)s must be >= %(min_value)d')
595
raise exc.HTTPBadRequest(explanation=msg % (
596
{'value_name': str_name,
597
'min_value': min_value}))
598
if max_value is not None:
599
if value > max_value:
600
msg = _('%{value_name}s must be <= %(max_value)d')
601
raise exc.HTTPBadRequest(explanation=msg % (
602
{'value_name': str_name,
603
'max_value': max_value}))
606
def _validate_block_device(self, bd):
607
self._check_string_length(bd['device_name'],
608
'Device name', max_length=255)
610
if ' ' in bd['device_name']:
611
msg = _("Device name cannot include spaces.")
612
raise exc.HTTPBadRequest(explanation=msg)
614
if 'volume_size' in bd:
615
self._validate_int_value(bd['volume_size'], 'volume_size',
618
608
def _get_injected_files(self, personality):
619
609
"""Create a list of injected files from the personality attribute.
640
630
injected_files.append((path, contents))
641
631
return injected_files
643
def _is_neutron_v2(self):
644
# NOTE(dprince): neutronclient is not a requirement
645
if self.neutron_attempted:
646
return self.have_neutron
649
# compatibility with Folsom/Grizzly configs
650
cls_name = CONF.network_api_class
651
if cls_name == 'nova.network.quantumv2.api.API':
652
cls_name = 'nova.network.neutronv2.api.API'
653
self.neutron_attempted = True
655
from nova.network.neutronv2 import api as neutron_api
656
self.have_neutron = issubclass(
657
importutils.import_class(cls_name),
660
self.have_neutron = False
662
return self.have_neutron
664
633
def _get_requested_networks(self, requested_networks):
665
634
"""Create a list of requested networks from the networks attribute."""
669
638
port_id = network.get('port', None)
671
640
network_uuid = None
672
if not self._is_neutron_v2():
641
if not utils.is_neutron():
673
642
# port parameter is only for neutron v2.0
674
643
msg = _("Unknown argment : port")
675
644
raise exc.HTTPBadRequest(explanation=msg)
700
669
# For neutronv2, requestd_networks
701
670
# should be tuple of (network_uuid, fixed_ip, port_id)
702
if self._is_neutron_v2():
671
if utils.is_neutron():
703
672
networks.append((network_uuid, address, port_id))
705
674
# check if the network id is already present in the list,
813
782
requested_networks = None
814
783
if (self.ext_mgr.is_loaded('os-networks')
815
or self._is_neutron_v2()):
784
or utils.is_neutron()):
816
785
requested_networks = server_dict.get('networks')
818
787
if requested_networks is not None:
851
820
availability_zone = server_dict.get('availability_zone')
853
822
block_device_mapping = None
823
block_device_mapping_v2 = None
854
825
if self.ext_mgr.is_loaded('os-volumes'):
855
826
block_device_mapping = server_dict.get('block_device_mapping', [])
856
827
for bdm in block_device_mapping:
857
# Ignore empty volume size
858
if 'volume_size' in bdm and not bdm['volume_size']:
859
del bdm['volume_size']
860
self._validate_block_device(bdm)
829
block_device.validate_device_name(bdm.get("device_name"))
830
block_device.validate_and_default_volume_size(bdm)
831
except exception.InvalidBDMFormat as e:
832
raise exc.HTTPBadRequest(explanation=e.format_message())
861
834
if 'delete_on_termination' in bdm:
862
835
bdm['delete_on_termination'] = strutils.bool_from_string(
863
836
bdm['delete_on_termination'])
838
if self.ext_mgr.is_loaded('os-block-device-mapping-v2-boot'):
839
# Consider the new data format for block device mapping
840
block_device_mapping_v2 = server_dict.get(
841
'block_device_mapping_v2', [])
842
# NOTE (ndipanov): Disable usage of both legacy and new
843
# block device format in the same request
844
if block_device_mapping and block_device_mapping_v2:
845
expl = _('Using different block_device_mapping syntaxes '
846
'is not allowed in the same request.')
847
raise exc.HTTPBadRequest(explanation=expl)
849
# Assume legacy format
850
legacy_bdm = not bool(block_device_mapping_v2)
853
block_device_mapping_v2 = [
854
block_device.BlockDeviceDict.from_api(bdm_dict)
855
for bdm_dict in block_device_mapping_v2]
856
except exception.InvalidBDMFormat as e:
857
raise exc.HTTPBadRequest(explanation=e.format_message())
859
block_device_mapping = (block_device_mapping or
860
block_device_mapping_v2)
865
862
ret_resv_id = False
866
863
# min_count and max_count are optional. If they exist, they may come
867
864
# in as strings. Verify that they are valid integers and > 0.
874
871
min_count = server_dict.get('min_count', 1)
875
872
max_count = server_dict.get('max_count', min_count)
877
min_count = self._validate_int_value(min_count, "min_count",
879
max_count = self._validate_int_value(max_count, "max_count",
875
min_count = utils.validate_integer(
876
min_count, "min_count", min_value=1)
877
max_count = utils.validate_integer(
878
max_count, "max_count", min_value=1)
879
except exception.InvalidInput as e:
880
raise exc.HTTPBadRequest(explanation=e.format_message())
882
882
if min_count > max_count:
883
883
msg = _('min_count must be <= max_count')
895
895
_get_inst_type = flavors.get_flavor_by_flavor_id
896
inst_type = _get_inst_type(flavor_id, read_deleted="no")
896
inst_type = _get_inst_type(flavor_id, ctxt=context,
898
899
(instances, resv_id) = self.compute_api.create(context,
915
916
config_drive=config_drive,
916
917
block_device_mapping=block_device_mapping,
917
918
auto_disk_config=auto_disk_config,
918
scheduler_hints=scheduler_hints)
919
scheduler_hints=scheduler_hints,
920
legacy_bdm=legacy_bdm)
919
921
except exception.QuotaError as error:
920
922
raise exc.HTTPRequestEntityTooLarge(
921
923
explanation=error.format_message(),
948
950
exception.InstanceTypeNotFound,
949
951
exception.InvalidMetadata,
950
952
exception.InvalidRequest,
951
exception.SecurityGroupNotFound) as error:
953
exception.PortNotFound,
954
exception.SecurityGroupNotFound,
955
exception.InvalidBDM) as error:
952
956
raise exc.HTTPBadRequest(explanation=error.format_message())
957
except exception.PortInUse as error:
958
raise exc.HTTPConflict(explanation=error.format_message())
954
960
# If the caller wanted a reservation_id, return it
969
975
def _delete(self, context, req, instance_uuid):
970
976
instance = self._get_server(context, req, instance_uuid)
971
977
if CONF.reclaim_instance_interval:
972
self.compute_api.soft_delete(context, instance)
979
self.compute_api.soft_delete(context, instance)
980
except exception.InstanceInvalidState:
981
# Note(yufang521247): instance which has never been active
982
# is not allowed to be soft_deleted. Thus we have to call
983
# delete() to clean up the instance.
984
self.compute_api.delete(context, instance)
974
986
self.compute_api.delete(context, instance)
1018
1030
instance = self.compute_api.get(ctxt, id,
1019
1031
want_objects=True)
1020
1032
req.cache_db_instance(instance)
1021
self.compute_api.update(ctxt, instance, **update_dict)
1033
policy.enforce(ctxt, 'compute:update', instance)
1034
instance.update(update_dict)
1022
1036
except exception.NotFound:
1023
1037
msg = _("Instance could not be found")
1024
1038
raise exc.HTTPNotFound(explanation=msg)
1026
# FIXME(danms): Until compute_api.update() is object-aware,
1027
# we need to apply the updates to the instance object so
1028
# that views will return the new data
1029
instance.update(update_dict)
1031
1040
return self._view_builder.show(req, instance)
1033
1042
@wsgi.response(202)
1165
1174
image_ref = data['server'].get('imageRef')
1166
1175
bdm = data['server'].get('block_device_mapping')
1176
bdm_v2 = data['server'].get('block_device_mapping_v2')
1168
if not image_ref and bdm and self.ext_mgr.is_loaded('os-volumes'):
1178
if (not image_ref and (
1179
(bdm and self.ext_mgr.is_loaded('os-volumes')) or
1181
self.ext_mgr.is_loaded('os-block-device-mapping-v2-boot')))):
1171
1184
image_href = self._image_ref_from_req_data(data)
1362
1375
if self.compute_api.is_volume_backed_instance(context, instance,
1364
1377
img = instance['image_ref']
1365
src_image = self.compute_api.image_service.show(context, img)
1366
image_meta = dict(src_image)
1379
# NOTE(Vincent Hou) The private method
1380
# _get_bdm_image_metadata only works, when boot
1381
# device is set to 'vda'. It needs to be fixed later,
1382
# but tentatively we use it here.
1383
image_meta = {'properties': self.compute_api.
1384
_get_bdm_image_metadata(context, bdms)}
1386
src_image = self.compute_api.image_service.\
1388
image_meta = dict(src_image)
1368
1390
image = self.compute_api.snapshot_volume_backed(