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:
Andrey Pavlov 2014-10-15 12:26:23 +04:00
parent c4d6b776a5
commit 5d3295fa89
17 changed files with 603 additions and 976 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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'])

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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():

View File

@ -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')}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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",
},
{

View File

@ -41,6 +41,7 @@ EXPECTED_INSTANCES = [{
}]
}],
"disks": [{
"autoDelete": False,
"kind": "compute#attachedDisk",
"index": 0,
"type": "PERSISTENT",

View File

@ -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/'