update instance creation and disk attaching to new protocol
It must be updated because of new way of instance creation with disk. Previously it takes disk but now it takes parameters for disk creation. Partial-Bug: #1381173 Change-Id: Ief3a8c204bf471b9253241816fb2dd6a5b705e84
This commit is contained in:
parent
c4d6b776a5
commit
5d3295fa89
File diff suppressed because it is too large
Load Diff
@ -144,6 +144,11 @@ class APIRouter(wsgi.Router):
|
||||
controller=self.resources['instances'],
|
||||
action="detach_disk",
|
||||
conditions={"method": ["POST"]})
|
||||
mapper.connect("/{project_id}/zones/{scope_id}/instances/{id}/"
|
||||
"setDiskAutoDelete",
|
||||
controller=self.resources['instances'],
|
||||
action="set_disk_auto_delete",
|
||||
conditions={"method": ["POST"]})
|
||||
|
||||
mapper.resource("images", "global/images",
|
||||
controller=self.resources['images'])
|
||||
|
@ -149,6 +149,10 @@ class API(object):
|
||||
|
||||
self._callbacks.append((reason, func))
|
||||
|
||||
@classmethod
|
||||
def _get_complex_operation_progress(cls, context, item_id):
|
||||
return None
|
||||
|
||||
def _prepare_item(self, item, db_item):
|
||||
if db_item is not None:
|
||||
item.update(db_item)
|
||||
@ -160,7 +164,7 @@ class API(object):
|
||||
if key in item)
|
||||
if ("creationTimestamp" in self._get_persistent_attributes() and
|
||||
"creationTimestamp" not in db_item):
|
||||
# TODO(ft): Google not returns microseconds but returns
|
||||
# TODO(ft): Google doesn't return microseconds but returns
|
||||
# server time zone: 2013-12-06T03:34:31.340-08:00
|
||||
utcnow = timeutils.isotime(None, True)
|
||||
db_item["creationTimestamp"] = utcnow
|
||||
|
@ -64,7 +64,7 @@ class Controller(object):
|
||||
|
||||
def process_result(self, request, action, action_result):
|
||||
context = self._get_context(request)
|
||||
operation = operation_util.save_operaton(context, action_result)
|
||||
operation = operation_util.save_operation(context, action_result)
|
||||
if operation is not None:
|
||||
scope = self._operation_api.get_scopes(context, operation)[0]
|
||||
action_result = self._format_operation(request, operation, scope)
|
||||
|
@ -107,7 +107,8 @@ class API(base_api.API):
|
||||
client.delete(volumes[0])
|
||||
|
||||
def add_item(self, context, name, body, scope=None):
|
||||
sizeGb = int(body['sizeGb']) if 'sizeGb' in body else None
|
||||
sizeGb = body.get("sizeGb")
|
||||
sizeGb = int(sizeGb) if sizeGb else None
|
||||
|
||||
snapshot_uri = body.get("sourceSnapshot")
|
||||
image_uri = body.get("sourceImage")
|
||||
@ -134,11 +135,11 @@ class API(base_api.API):
|
||||
operation_util.start_operation(context, self._get_add_item_progress)
|
||||
volume = client.volumes.create(
|
||||
sizeGb, snapshot_id=snapshot_id,
|
||||
display_name=body.get('name'),
|
||||
display_name=body.get('name', name),
|
||||
display_description=body.get('description'),
|
||||
imageRef=image_id,
|
||||
availability_zone=scope.get_name())
|
||||
operation_util.set_item_id(context, volume.id)
|
||||
operation_util.set_item_id(context, volume.id, self.KIND)
|
||||
|
||||
return self._prepare_item(client, utils.to_dict(volume))
|
||||
|
||||
@ -147,15 +148,17 @@ class API(base_api.API):
|
||||
try:
|
||||
volume = client.volumes.get(volume_id)
|
||||
except clients.cinderclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if (volume.status not in ["creating", "downloading"]):
|
||||
return operation_api.gef_final_progress(volume.status == "error")
|
||||
return operation_util.get_final_progress(volume.status == "error")
|
||||
return None
|
||||
|
||||
def _get_delete_item_progress(self, context, volume_id):
|
||||
client = clients.cinder(context)
|
||||
try:
|
||||
volume = client.volumes.get(volume_id)
|
||||
except clients.cinderclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if volume.status not in ["deleting", "deleted"]:
|
||||
return operation_api.gef_final_progress(True)
|
||||
return operation_util.get_final_progress(True)
|
||||
return None
|
||||
|
@ -120,7 +120,7 @@ class API(base_api.API):
|
||||
image_service = clients.glance(context).images
|
||||
operation_util.start_operation(context, self._get_add_item_progress)
|
||||
image = image_service.create(**meta)
|
||||
operation_util.set_item_id(context, image.id)
|
||||
operation_util.set_item_id(context, image.id, self.KIND)
|
||||
|
||||
new_image = self._prepare_image(utils.to_dict(image))
|
||||
new_image["description"] = body.get("description", "")
|
||||
@ -133,15 +133,17 @@ class API(base_api.API):
|
||||
try:
|
||||
image = image_service.get(image_id)
|
||||
except glanceclient_exc.HTTPNotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if image.status not in ["queued", "saving"]:
|
||||
return operation_api.gef_final_progress(image.status == "killed")
|
||||
return operation_util.get_final_progress(image.status == "killed")
|
||||
return None
|
||||
|
||||
def _get_delete_item_progress(self, context, image_id):
|
||||
image_service = clients.glance(context).images
|
||||
try:
|
||||
image = image_service.get(image_id)
|
||||
if image.status in self._deleted_statuses:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
except glanceclient_exc.HTTPNotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
return None
|
||||
|
@ -34,7 +34,9 @@ class Controller(gce_common.Controller):
|
||||
},
|
||||
"status": image["status"],
|
||||
"archiveSizeBytes": image["size"],
|
||||
"description": image.get("description", "")
|
||||
"description": image.get("description", ""),
|
||||
# NOTE(apavlov): Size of the image when restored onto a disk.
|
||||
#"diskSizeGb": 0
|
||||
}
|
||||
|
||||
return self._format_item(request, result_dict, scope)
|
||||
|
@ -75,12 +75,6 @@ class API(base_api.API):
|
||||
firewall_api.API()._register_callback(
|
||||
base_api._callback_reasons.pre_delete,
|
||||
self._remove_secgroup_from_instances)
|
||||
operation_api.API().register_get_progress_method(
|
||||
"instance-add",
|
||||
self._get_add_item_progress)
|
||||
operation_api.API().register_get_progress_method(
|
||||
"instance-delete",
|
||||
self._get_delete_item_progress)
|
||||
operation_api.API().register_get_progress_method(
|
||||
"instance-reset",
|
||||
self._get_reset_instance_progress)
|
||||
@ -140,8 +134,9 @@ class API(base_api.API):
|
||||
if not ad:
|
||||
name = volume["display_name"]
|
||||
ad = instance_disk_api.API().register_item(context,
|
||||
instance["name"], volume["id"], name)
|
||||
instance["name"], volume["id"], name, False)
|
||||
volume["device_name"] = ad["name"]
|
||||
volume["auto_delete"] = ad["auto_delete"]
|
||||
# NOTE(apavlov): cleanup unused from db for this instance
|
||||
for ad in ads:
|
||||
ad = instance_disk_api.API().unregister_item(context,
|
||||
@ -231,9 +226,19 @@ class API(base_api.API):
|
||||
if not instances or len(instances) != 1:
|
||||
raise exception.NotFound
|
||||
instance = instances[0]
|
||||
operation_util.start_operation(context,
|
||||
self._get_delete_item_progress,
|
||||
instance.id)
|
||||
operation_util.start_operation(
|
||||
context, base_api.API._get_complex_operation_progress)
|
||||
|
||||
ads = instance_disk_api.API().get_items(context, instance.name)
|
||||
disks_to_delete = []
|
||||
for ad in ads:
|
||||
if ad["auto_delete"]:
|
||||
disks_to_delete.append(ad)
|
||||
|
||||
if not disks_to_delete:
|
||||
operation_util.set_item_id(context, instance.id, self.KIND)
|
||||
|
||||
client = clients.nova(context)
|
||||
instance.delete()
|
||||
instance = utils.to_dict(instance)
|
||||
instance = self._prepare_instance(client, context, instance)
|
||||
@ -249,6 +254,56 @@ class API(base_api.API):
|
||||
ac = instance_address_api.API().unregister_item(context,
|
||||
instance["name"], ac["name"])
|
||||
|
||||
if not disks_to_delete:
|
||||
return
|
||||
|
||||
context.operation_data["scope"] = scope
|
||||
context.operation_data["count"] = 1 + len(disks_to_delete)
|
||||
context.operation_data["instance"] = instance
|
||||
context.operation_data["disks"] = disks_to_delete
|
||||
operation_util.continue_operation(
|
||||
context, lambda: self._delete_instance(context))
|
||||
|
||||
def _delete_instance(self, context):
|
||||
progress = {"progress": 0}
|
||||
full_count = context.operation_data.get("count")
|
||||
disks = context.operation_data.get("disks")
|
||||
instance = context.operation_data.get("instance")
|
||||
if instance:
|
||||
item_progress = self._get_delete_item_progress(context,
|
||||
instance["id"])
|
||||
if not operation_util.is_final_progress(item_progress):
|
||||
return progress
|
||||
context.operation_data.pop("instance")
|
||||
|
||||
progress = {"progress": int(100.0 * (full_count - len(disks))
|
||||
/ full_count)}
|
||||
|
||||
disk = context.operation_data.get("disk")
|
||||
if disk:
|
||||
volume_id = disk["volume_id"]
|
||||
item_progress = disk_api.API()._get_delete_item_progress(context,
|
||||
volume_id)
|
||||
if not operation_util.is_final_progress(item_progress):
|
||||
return progress
|
||||
context.operation_data.pop("disk")
|
||||
progress = {"progress": int(100.0 * (full_count - len(disks) + 1)
|
||||
/ full_count)}
|
||||
|
||||
if disks:
|
||||
disk = disks.pop()
|
||||
try:
|
||||
cinder_client = clients.cinder(context)
|
||||
volume = cinder_client.volumes.get(disk["volume_id"])
|
||||
cinder_client.volumes.delete(volume)
|
||||
context.operation_data["disk"] = disk
|
||||
except Exception:
|
||||
LOG.exception("Failed to remove disk %s of instance" %
|
||||
disk["volume_id"])
|
||||
return progress
|
||||
|
||||
return operation_util.get_final_progress()
|
||||
|
||||
def add_item(self, context, name, body, scope=None):
|
||||
name = body['name']
|
||||
client = clients.nova(context)
|
||||
@ -257,18 +312,6 @@ class API(base_api.API):
|
||||
flavor_id = machine_type_api.API().get_item(
|
||||
context, flavor_name, scope)["id"]
|
||||
|
||||
disks = body.get('disks', [])
|
||||
disks.sort(None, lambda x: x.get("boot", False), True)
|
||||
bdm = dict()
|
||||
diskDevice = 0
|
||||
for disk in disks:
|
||||
device_name = "vd" + string.ascii_lowercase[diskDevice]
|
||||
volume_name = utils._extract_name_from_url(disk["source"])
|
||||
volume = disk_api.API().get_item(context, volume_name, scope)
|
||||
disk["id"] = volume["id"]
|
||||
bdm[device_name] = volume["id"]
|
||||
diskDevice += 1
|
||||
|
||||
nics = []
|
||||
#NOTE(ft) 'default' security group contains output rules
|
||||
#but output rules doesn't configurable by GCE API
|
||||
@ -299,6 +342,19 @@ class API(base_api.API):
|
||||
metadatas = []
|
||||
instance_metadata = dict([(x['key'], x['value']) for x in metadatas])
|
||||
|
||||
disks = body.get('disks', [])
|
||||
for disk in disks:
|
||||
disk["boot"] = True if "initializeParams" in disk else False
|
||||
if "source" in disk:
|
||||
volume_name = utils._extract_name_from_url(disk["source"])
|
||||
volume = disk_api.API().get_item(context, volume_name, scope)
|
||||
disk["id"] = volume["id"]
|
||||
elif "initializeParams" not in disk:
|
||||
msg = _('Disk config must contain either "source" or '
|
||||
'"initializeParams".')
|
||||
raise exception.InvalidRequest(msg)
|
||||
disks.sort(None, lambda x: x.get("boot", False), True)
|
||||
|
||||
ssh_keys = instance_metadata.pop('sshKeys', None)
|
||||
if ssh_keys is not None:
|
||||
key = ssh_keys.split('\n')[0].split(":")
|
||||
@ -308,79 +364,139 @@ class API(base_api.API):
|
||||
else:
|
||||
key_name = project_api.API().get_gce_user_keypair_name(context)
|
||||
|
||||
try:
|
||||
operation_util.start_operation(
|
||||
context, self._get_add_item_progress)
|
||||
instance = client.servers.create(name, None, flavor_id,
|
||||
meta=instance_metadata, min_count=1, max_count=1,
|
||||
security_groups=groups_names, key_name=key_name,
|
||||
availability_zone=scope.get_name(), block_device_mapping=bdm,
|
||||
nics=nics)
|
||||
if not acs:
|
||||
operation_util.set_item_id(context, instance.id)
|
||||
finally:
|
||||
if ssh_keys is not None:
|
||||
client.keypairs.delete(key_name)
|
||||
operation_util.start_operation(
|
||||
context, base_api.API._get_complex_operation_progress)
|
||||
|
||||
for disk in disks:
|
||||
instance_disk_api.API().register_item(context, name,
|
||||
disk["id"], disk["deviceName"])
|
||||
context.operation_data["acs"] = acs
|
||||
context.operation_data["ssh_keys"] = ssh_keys
|
||||
|
||||
instance = utils.to_dict(client.servers.get(instance.id))
|
||||
instance = self._prepare_instance(client, context, instance)
|
||||
if "description" in body:
|
||||
instance["description"] = body["description"]
|
||||
instance = self._add_db_item(context, instance)
|
||||
context.operation_data["bdm"] = dict()
|
||||
context.operation_data["disk_device"] = 0
|
||||
context.operation_data["disks"] = disks
|
||||
|
||||
if acs:
|
||||
operation_util.continue_operation(
|
||||
context,
|
||||
lambda: self._add_access_config(context, instance,
|
||||
scope, acs))
|
||||
context.operation_data["scope"] = scope
|
||||
context.operation_data["args"] = [name, None, flavor_id]
|
||||
context.operation_data["kwargs"] = {"meta": instance_metadata,
|
||||
"min_count": 1, "max_count": 1, "nics": nics,
|
||||
"security_groups": groups_names, "key_name": key_name}
|
||||
context.operation_data["description"] = body.get("description")
|
||||
|
||||
return instance
|
||||
operation_util.continue_operation(
|
||||
context, lambda: self._create_instance(context))
|
||||
|
||||
def _create_instance(self, context):
|
||||
disks = context.operation_data["disks"]
|
||||
acs = context.operation_data["acs"]
|
||||
full_count = 1 + len(disks) + (1 if acs else 0)
|
||||
disk_device = context.operation_data["disk_device"]
|
||||
instance = context.operation_data.get("instance")
|
||||
progress = {"progress": int(100.0 * disk_device / full_count)}
|
||||
|
||||
disk_device = context.operation_data["disk_device"]
|
||||
disk = context.operation_data.get("disk")
|
||||
if disk:
|
||||
volume_id = disk["id"]
|
||||
item_progress = disk_api.API()._get_add_item_progress(context,
|
||||
volume_id)
|
||||
if not operation_util.is_final_progress(item_progress):
|
||||
return progress
|
||||
context.operation_data.pop("disk")
|
||||
disk_device += 1
|
||||
context.operation_data["disk_device"] = disk_device
|
||||
progress["progress"] = int(100.0 * disk_device / full_count)
|
||||
|
||||
scope = context.operation_data["scope"]
|
||||
args = context.operation_data["args"]
|
||||
|
||||
bdm = context.operation_data["bdm"]
|
||||
while disk_device < len(disks):
|
||||
disk = disks[disk_device]
|
||||
if "initializeParams" in disk:
|
||||
da = disk_api.API()
|
||||
params = disk["initializeParams"]
|
||||
body = {"sizeGb": params.get("diskSizeGb"),
|
||||
"sourceImage": params["sourceImage"]}
|
||||
volume = da.add_item(context, params.get("diskName", args[0]),
|
||||
body, scope=scope)
|
||||
disk["id"] = volume["id"]
|
||||
context.operation_data["disk"] = disk
|
||||
device_name = "vd" + string.ascii_lowercase[disk_device]
|
||||
bdm[device_name] = disk["id"]
|
||||
if "initializeParams" in disk:
|
||||
return progress
|
||||
disk_device += 1
|
||||
context.operation_data["disk_device"] = disk_device
|
||||
|
||||
if not instance:
|
||||
kwargs = context.operation_data["kwargs"]
|
||||
kwargs["block_device_mapping"] = bdm
|
||||
kwargs["availability_zone"] = scope.get_name()
|
||||
client = clients.nova(context)
|
||||
try:
|
||||
instance = client.servers.create(*args, **kwargs)
|
||||
|
||||
for disk in disks:
|
||||
instance_disk_api.API().register_item(context, args[0],
|
||||
disk["id"], disk["deviceName"], disk["autoDelete"])
|
||||
|
||||
instance = utils.to_dict(client.servers.get(instance.id))
|
||||
instance = self._prepare_instance(client, context, instance)
|
||||
instance["description"] = context.operation_data["description"]
|
||||
instance = self._add_db_item(context, instance)
|
||||
finally:
|
||||
try:
|
||||
ssh_keys = context.operation_data["ssh_keys"]
|
||||
if ssh_keys is not None:
|
||||
client.keypairs.delete(kwargs["key_name"])
|
||||
except Exception:
|
||||
pass
|
||||
context.operation_data["instance"] = instance
|
||||
return progress
|
||||
|
||||
def _add_access_config(self, context, instance, scope, acs):
|
||||
progress = self._get_add_item_progress(context, instance["id"])
|
||||
if progress is None or not operation_api.is_final_progress(progress):
|
||||
if not operation_util.is_final_progress(progress):
|
||||
return progress
|
||||
|
||||
client = clients.nova(context)
|
||||
try:
|
||||
instance = client.servers.get(instance["id"])
|
||||
except clients.novaclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
|
||||
for net in acs:
|
||||
ac = acs[net]
|
||||
instance_address_api.API().add_item(context, instance.name,
|
||||
net, ac.get("natIP"), ac.get("type"), ac.get("name"))
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
|
||||
def _get_add_item_progress(self, context, instance_id):
|
||||
client = clients.nova(context)
|
||||
try:
|
||||
instance = client.servers.get(instance_id)
|
||||
except clients.novaclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if instance.status != "BUILD":
|
||||
return operation_api.gef_final_progress(instance.status == "ERROR")
|
||||
return operation_util.get_final_progress(instance.status
|
||||
== "ERROR")
|
||||
return None
|
||||
|
||||
def _get_delete_item_progress(self, context, instance_id):
|
||||
client = clients.nova(context)
|
||||
try:
|
||||
instance = client.servers.get(instance_id)
|
||||
except clients.novaclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if getattr(instance, "OS-EXT-STS:task_state") != "deleting":
|
||||
return operation_api.gef_final_progress(
|
||||
return operation_util.get_final_progress(
|
||||
instance.status != "DELETED")
|
||||
return None
|
||||
|
||||
def _get_reset_instance_progress(self, context, instance_id):
|
||||
client = clients.nova(context)
|
||||
try:
|
||||
instance = client.servers.get(instance_id)
|
||||
except clients.novaclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if instance.status != "HARD_REBOOT":
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
return None
|
||||
|
@ -26,18 +26,18 @@ from gceapi.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
GB = 1024 ** 3
|
||||
|
||||
|
||||
class API(base_api.API):
|
||||
"""GCE Attached disk API."""
|
||||
|
||||
KIND = "attached_disk"
|
||||
PERSISTENT_ATTRIBUTES = ["id", "instance_name", "volume_id", "name"]
|
||||
PERSISTENT_ATTRIBUTES = ["id", "instance_name", "volume_id", "name",
|
||||
"auto_delete"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(API, self).__init__(*args, **kwargs)
|
||||
operation_api.API().register_get_progress_method(
|
||||
"attached_disk-add",
|
||||
self._get_add_item_progress)
|
||||
operation_api.API().register_get_progress_method(
|
||||
"attached_disk-delete",
|
||||
self._get_delete_item_progress)
|
||||
@ -58,19 +58,17 @@ class API(base_api.API):
|
||||
|
||||
def get_items(self, context, instance_name):
|
||||
items = self._get_db_items(context)
|
||||
for item in items:
|
||||
item.setdefault("auto_delete", False)
|
||||
return [i for i in items if i["instance_name"] == instance_name]
|
||||
|
||||
def add_item(self, context, instance_name, source, name):
|
||||
def add_item(self, context, instance_name, params, source, name,
|
||||
auto_delete, scope):
|
||||
# NOTE(apavlov): name is a 'device_name' here
|
||||
if not name:
|
||||
msg = _("There is no name to assign.")
|
||||
raise exception.InvalidRequest(msg)
|
||||
|
||||
volume_name = utils._extract_name_from_url(source)
|
||||
if not volume_name:
|
||||
msg = _("There is no volume to assign.")
|
||||
raise exception.NotFound(msg)
|
||||
volume = disk_api.API().get_item(context, volume_name, None)
|
||||
|
||||
nova_client = clients.nova(context)
|
||||
instances = nova_client.servers.list(
|
||||
search_opts={"name": instance_name})
|
||||
@ -92,15 +90,68 @@ class API(base_api.API):
|
||||
break
|
||||
else:
|
||||
raise exception.OverQuota
|
||||
context.operation_data["device_name"] = device_name
|
||||
|
||||
operation_util.start_operation(context, self._get_add_item_progress)
|
||||
if source:
|
||||
volume_name = utils._extract_name_from_url(source)
|
||||
if not volume_name:
|
||||
msg = _("There is no volume to assign.")
|
||||
raise exception.NotFound(msg)
|
||||
volume = disk_api.API().get_item(context, volume_name, scope)
|
||||
context.operation_data["volume_id"] = volume["id"]
|
||||
elif params:
|
||||
params.setdefault("diskName", instance_name)
|
||||
context.operation_data["params"] = params
|
||||
context.operation_data["scope"] = scope
|
||||
else:
|
||||
msg = _('Disk config must contain either "source" or '
|
||||
'"initializeParams".')
|
||||
raise exception.InvalidRequest(msg)
|
||||
|
||||
context.operation_data["instance_id"] = instance.id
|
||||
context.operation_data["register_args"] = [instance_name, name,
|
||||
auto_delete]
|
||||
operation_util.start_operation(
|
||||
context, base_api.API._get_complex_operation_progress)
|
||||
operation_util.continue_operation(
|
||||
context, lambda: self._attach_volume(context), timeout=0)
|
||||
|
||||
def _attach_volume(self, context):
|
||||
params = context.operation_data.get("params")
|
||||
if params:
|
||||
scope = context.operation_data["scope"]
|
||||
context.operation_data.pop("params")
|
||||
body = {"sizeGb": params.get("diskSizeGb"),
|
||||
"sourceImage": params["sourceImage"]}
|
||||
volume = disk_api.API().add_item(context, params.get("diskName"),
|
||||
body, scope=scope)
|
||||
context.operation_data["disk"] = volume
|
||||
return None
|
||||
|
||||
disk = context.operation_data.get("disk")
|
||||
if disk:
|
||||
volume_id = disk["id"]
|
||||
item_progress = disk_api.API()._get_add_item_progress(context,
|
||||
volume_id)
|
||||
if not operation_util.is_final_progress(item_progress):
|
||||
return None
|
||||
context.operation_data.pop("disk")
|
||||
context.operation_data["volume_id"] = volume_id
|
||||
|
||||
instance_id = context.operation_data["instance_id"]
|
||||
device_name = context.operation_data["device_name"]
|
||||
volume_id = context.operation_data["volume_id"]
|
||||
volumes_client = clients.nova(context).volumes
|
||||
volumes_client.create_server_volume(
|
||||
instance.id, volume["id"], "/dev/" + device_name)
|
||||
instance_id, volume_id, "/dev/" + device_name)
|
||||
|
||||
item = self.register_item(context, instance_name, volume["id"], name)
|
||||
operation_util.set_item_id(context, item["id"])
|
||||
args = context.operation_data["register_args"]
|
||||
self.register_item(context, args[0], volume_id, args[1], args[2])
|
||||
|
||||
def register_item(self, context, instance_name, volume_id, name):
|
||||
return operation_util.get_final_progress()
|
||||
|
||||
def register_item(self, context, instance_name, volume_id, name,
|
||||
auto_delete):
|
||||
if not name:
|
||||
msg = _("There is no name to assign.")
|
||||
raise exception.InvalidRequest(msg)
|
||||
@ -113,6 +164,7 @@ class API(base_api.API):
|
||||
"instance_name": instance_name,
|
||||
"volume_id": volume_id,
|
||||
"name": name,
|
||||
"auto_delete": auto_delete
|
||||
}
|
||||
new_item = self._add_db_item(context, new_item)
|
||||
return new_item
|
||||
@ -135,12 +187,17 @@ class API(base_api.API):
|
||||
|
||||
self._delete_db_item(context, item)
|
||||
|
||||
def set_disk_auto_delete(self, context, instance_name, name, auto_delete):
|
||||
item = self.get_item(context, instance_name, name)
|
||||
item["auto_delete"] = auto_delete
|
||||
self._update_db_item(context, item)
|
||||
|
||||
def unregister_item(self, context, instance_name, name):
|
||||
item = self.get_item(context, instance_name, name)
|
||||
self._delete_db_item(context, item)
|
||||
|
||||
def _get_add_item_progress(self, context, dummy_id):
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
|
||||
def _get_delete_item_progress(self, context, dummy_id):
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
|
@ -90,6 +90,9 @@ class Controller(gce_common.Controller):
|
||||
"network '%(n)") % {"i": instance["name"], "n": network})
|
||||
result_dict["networkInterfaces"].append(ni)
|
||||
|
||||
# TODO(apavlov): add code to support returning ephemeral disks as
|
||||
# SCRATCH disks. Such disk couldn't be created via GCE but it can
|
||||
# present in OpenStack cloud.
|
||||
disk_index = 0
|
||||
for volume in instance["volumes"]:
|
||||
readonly = volume.get("metadata", {}).get("readonly", "False")
|
||||
@ -101,7 +104,8 @@ class Controller(gce_common.Controller):
|
||||
"source": self._qualify(request,
|
||||
"disks", volume["display_name"], scope),
|
||||
"deviceName": volume["device_name"],
|
||||
"boot": True if volume["bootable"] == "true" else False
|
||||
"boot": True if volume["bootable"] == "true" else False,
|
||||
"autoDelete": volume["auto_delete"]
|
||||
}
|
||||
result_dict["disks"].append(google_disk)
|
||||
disk_index += 1
|
||||
@ -142,7 +146,9 @@ class Controller(gce_common.Controller):
|
||||
operation_util.init_operation(context, "attachDisk",
|
||||
self._type_name, id, scope)
|
||||
self._instance_disk_api.add_item(context, id,
|
||||
body["source"], body.get("deviceName"))
|
||||
body.get("initializeParams"), body.get("source"),
|
||||
body.get("deviceName"), body.get("autoDelete", False),
|
||||
scope)
|
||||
|
||||
def detach_disk(self, req, scope_id, id):
|
||||
context = self._get_context(req)
|
||||
@ -150,7 +156,16 @@ class Controller(gce_common.Controller):
|
||||
operation_util.init_operation(context, "detachDisk",
|
||||
self._type_name, id, scope)
|
||||
self._instance_disk_api.delete_item(context, id,
|
||||
req.params.get('deviceName'))
|
||||
req.params.get("deviceName"))
|
||||
|
||||
def set_disk_auto_delete(self, req, scope_id, id):
|
||||
context = self._get_context(req)
|
||||
scope = self._get_scope(req, scope_id)
|
||||
operation_util.init_operation(context, "setDiskAutoDelete",
|
||||
self._type_name, id, scope)
|
||||
auto_delete = req.params.get("autoDelete").lower() == "true"
|
||||
self._instance_disk_api.set_disk_auto_delete(context, id,
|
||||
req.params.get("deviceName"), auto_delete)
|
||||
|
||||
|
||||
def create_resource():
|
||||
|
@ -34,8 +34,9 @@ class API(base_api.API):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(API, self).__init__(*args, **kwargs)
|
||||
self._method_keys = {}
|
||||
self._get_progress_methods = {}
|
||||
method = base_api.API._get_complex_operation_progress
|
||||
self._method_keys = {method: "complex_operation"}
|
||||
self._get_progress_methods = {"complex_operation": method}
|
||||
|
||||
def _get_type(self):
|
||||
return self.KIND
|
||||
@ -79,6 +80,9 @@ class API(base_api.API):
|
||||
raise exception.NotFound
|
||||
self._delete_db_item(context, item)
|
||||
|
||||
# TODO(apavlov): rework updating end_time field
|
||||
# now it updates only by user request that may occurs
|
||||
# after a long period of time
|
||||
def _update_operation_progress(self, context, operation):
|
||||
if operation["status"] == "DONE" or not operation.get("item_id"):
|
||||
return operation
|
||||
@ -113,7 +117,7 @@ class API(base_api.API):
|
||||
def save_operation(self, context, operation, start_time,
|
||||
get_progress_method, item_id, operation_result):
|
||||
if isinstance(operation_result, Exception):
|
||||
operation.update(_error_from_exception(operation_result))
|
||||
operation.update(self._error_from_exception(operation_result))
|
||||
operation["start_time"] = start_time
|
||||
method_key = self._method_keys.get(get_progress_method)
|
||||
if method_key is None or "error_code" in operation:
|
||||
@ -134,35 +138,16 @@ class API(base_api.API):
|
||||
# NOTE(ft): it may lead to hungup not finished operation in DB
|
||||
return
|
||||
if isinstance(operation_result, Exception):
|
||||
operation.update(_error_from_exception(operation_result))
|
||||
else:
|
||||
operation.update(self._error_from_exception(operation_result))
|
||||
elif operation_result:
|
||||
operation.update(operation_result)
|
||||
if operation["progress"] == 100 or "error_code" in operation:
|
||||
operation["status"] = "DONE"
|
||||
operation["end_time"] = timeutils.isotime(None, True)
|
||||
operation.update(operation)
|
||||
self._update_db_item(context, operation)
|
||||
|
||||
|
||||
def gef_final_progress(with_error=False):
|
||||
progress = {"progress": 100}
|
||||
if with_error:
|
||||
progress["error_code"] = 500
|
||||
progress["error_message"] = _('Internal server error')
|
||||
progress["errors"] = [{
|
||||
"code": "UNKNOWN_OS_ERROR",
|
||||
"message": _("Operation finished with unknown error. "
|
||||
"See OpenStack logs.")
|
||||
}]
|
||||
return progress
|
||||
|
||||
|
||||
def is_final_progress(progress):
|
||||
return progress is not None and (progress.get("progress") == 100 or
|
||||
progress.get("error_code") is not None)
|
||||
|
||||
|
||||
def _error_from_exception(ex):
|
||||
return {"errors": [{"code": ex.__class__.__name__, "message": str(ex)}],
|
||||
def _error_from_exception(self, ex):
|
||||
return {
|
||||
"errors": [{"code": ex.__class__.__name__, "message": str(ex)}],
|
||||
"error_code": 500,
|
||||
"error_message": _('Internal server error')}
|
||||
|
@ -15,6 +15,7 @@
|
||||
import threading
|
||||
|
||||
from gceapi.api import operation_api
|
||||
from gceapi.openstack.common.gettextutils import _
|
||||
from gceapi.openstack.common import timeutils
|
||||
|
||||
|
||||
@ -27,7 +28,7 @@ def init_operation(context, op_type, target_type, target_name, scope):
|
||||
return operation
|
||||
|
||||
|
||||
def save_operaton(context, action_result):
|
||||
def save_operation(context, action_result):
|
||||
if context.operation is None or context.operation_start_time is None:
|
||||
return None
|
||||
return operation_api.API().save_operation(
|
||||
@ -45,11 +46,12 @@ def start_operation(context, get_progress_method=None, item_id=None):
|
||||
context.operation_start_time = timeutils.isotime(None, True)
|
||||
context.operation_get_progress_method = get_progress_method
|
||||
context.operation_item_id = item_id
|
||||
set_item_id(context, item_id)
|
||||
|
||||
|
||||
def set_item_id(context, item_id):
|
||||
if context.operation is None or context.operation_start_time is None:
|
||||
def set_item_id(context, item_id, item_type):
|
||||
if (context.operation is None
|
||||
or context.operation_start_time is None
|
||||
or context.operation["target_type"] != item_type):
|
||||
return
|
||||
context.operation_item_id = item_id
|
||||
|
||||
@ -62,12 +64,28 @@ def _continue_operation(context, func):
|
||||
operation = context.operation
|
||||
try:
|
||||
operation_result = func()
|
||||
if not is_final_progress(operation_result):
|
||||
continue_operation(context, func, timeout=2)
|
||||
except Exception as ex:
|
||||
operation_result = ex
|
||||
if operation is None:
|
||||
return
|
||||
if operation_result is None:
|
||||
continue_operation(context, func, timeout=2)
|
||||
else:
|
||||
operation_api.API().update_operation(context, operation["id"],
|
||||
operation_result)
|
||||
|
||||
operation_api.API().update_operation(context, operation["id"],
|
||||
operation_result)
|
||||
|
||||
|
||||
def get_final_progress(with_error=False):
|
||||
progress = {"progress": 100}
|
||||
if with_error:
|
||||
progress["error_code"] = 500
|
||||
progress["error_message"] = _('Internal server error')
|
||||
progress["errors"] = [{
|
||||
"code": "UNKNOWN_OS_ERROR",
|
||||
"message": _("Operation finished with unknown error. "
|
||||
"See OpenStack logs.")
|
||||
}]
|
||||
return progress
|
||||
|
||||
|
||||
def is_final_progress(progress):
|
||||
return progress is not None and (progress.get("progress") == 100 or
|
||||
progress.get("error_code") is not None)
|
||||
|
@ -81,7 +81,7 @@ class API(base_api.API):
|
||||
operation_util.start_operation(context, self._get_add_item_progress)
|
||||
snapshot = client.volume_snapshots.create(
|
||||
volumes[0].id, True, name, body["description"])
|
||||
operation_util.set_item_id(context, snapshot.id)
|
||||
operation_util.set_item_id(context, snapshot.id, self.KIND)
|
||||
|
||||
return self._prepare_item(client, utils.to_dict(snapshot))
|
||||
|
||||
@ -99,15 +99,18 @@ class API(base_api.API):
|
||||
try:
|
||||
snapshot = client.volume_snapshots.get(snapshot_id)
|
||||
except clients.cinderclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if (snapshot.status != "creating"):
|
||||
return operation_api.gef_final_progress(snapshot.status == "error")
|
||||
return operation_util.get_final_progress(snapshot.status
|
||||
== "error")
|
||||
return None
|
||||
|
||||
def _get_delete_item_progress(self, context, snapshot_id):
|
||||
client = clients.cinder(context)
|
||||
try:
|
||||
snapshot = client.volume_snapshots.get(snapshot_id)
|
||||
except clients.cinderclient.exceptions.NotFound:
|
||||
return operation_api.gef_final_progress()
|
||||
return operation_util.get_final_progress()
|
||||
if snapshot.status not in ["deleting", "deleted"]:
|
||||
return operation_api.gef_final_progress(True)
|
||||
return operation_util.get_final_progress(True)
|
||||
return None
|
||||
|
@ -90,6 +90,7 @@ class RequestContext(object):
|
||||
self.operation_start_time = None
|
||||
self.operation_get_progress_method = None
|
||||
self.operation_item_id = None
|
||||
self.operation_data = {}
|
||||
|
||||
def _get_read_deleted(self):
|
||||
return self._read_deleted
|
||||
|
@ -109,7 +109,7 @@ ITEMS = [
|
||||
"scope_name": "nova",
|
||||
"target_type": "instance",
|
||||
"target_name": "i1",
|
||||
"method_key": "instance-add",
|
||||
"method_key": "complex_operation",
|
||||
"item_id": "d6957005-3ce7-4727-91d2-ae37fe5a199a",
|
||||
},
|
||||
{
|
||||
@ -126,7 +126,7 @@ ITEMS = [
|
||||
"scope_name": "nova",
|
||||
"target_type": "instance",
|
||||
"target_name": "i-deleted",
|
||||
"method_key": "instance-delete",
|
||||
"method_key": "complex_operation",
|
||||
"item_id": "a6d176c9-389b-4a68-94f2-92a4cc276124",
|
||||
},
|
||||
{
|
||||
|
@ -41,6 +41,7 @@ EXPECTED_INSTANCES = [{
|
||||
}]
|
||||
}],
|
||||
"disks": [{
|
||||
"autoDelete": False,
|
||||
"kind": "compute#attachedDisk",
|
||||
"index": 0,
|
||||
"type": "PERSISTENT",
|
||||
|
@ -16,7 +16,7 @@ from gceapi.api import operations
|
||||
from gceapi.tests.api import common
|
||||
|
||||
FAKE_ADD_INSTANCE = {
|
||||
u'status': u'DONE',
|
||||
u'status': u'RUNNING',
|
||||
u'kind': u'compute#operation',
|
||||
u'operationType': u'add',
|
||||
u'zone': (u'http://localhost/compute/v1beta15/projects/'
|
||||
@ -27,8 +27,7 @@ FAKE_ADD_INSTANCE = {
|
||||
u'targetLink': (u'http://localhost/compute/v1beta15/projects/'
|
||||
'fake_project/zones/nova/instances/i1'),
|
||||
u'insertTime': u'2014-01-20T11:17:39.735738Z',
|
||||
u'progress': 100,
|
||||
u'endTime': u'2013-12-27T08:46:34.684354Z',
|
||||
u'progress': 0,
|
||||
u'id': u'2720525776854968247',
|
||||
u'selfLink': (u'http://localhost/compute/v1beta15/projects/'
|
||||
'fake_project/zones/nova/operations/'
|
||||
@ -36,7 +35,7 @@ FAKE_ADD_INSTANCE = {
|
||||
u'user': u'admin'
|
||||
}
|
||||
FAKE_DELETE_INSTANCE = {
|
||||
u'status': u'DONE',
|
||||
u'status': u'RUNNING',
|
||||
u'kind': u'compute#operation',
|
||||
u'operationType': u'delete',
|
||||
u'zone': (u'http://localhost/compute/v1beta15/projects/'
|
||||
@ -47,8 +46,7 @@ FAKE_DELETE_INSTANCE = {
|
||||
u'targetLink': (u'http://localhost/compute/v1beta15/projects/'
|
||||
'fake_project/zones/nova/instances/i-deleted'),
|
||||
u'insertTime': u'2014-01-20T11:17:39.735738Z',
|
||||
u'progress': 100,
|
||||
u'endTime': u'2013-12-27T08:46:34.684354Z',
|
||||
u'progress': 0,
|
||||
u'id': u'5384375190177147022',
|
||||
u'selfLink': (u'http://localhost/compute/v1beta15/projects/'
|
||||
'fake_project/zones/nova/operations/'
|
||||
|
Loading…
x
Reference in New Issue
Block a user