Skip to content
This repository has been archived by the owner on Nov 9, 2020. It is now read-only.

Handling tenancy related edge cases #1060

Merged
merged 3 commits into from
Mar 31, 2017
Merged

Conversation

pshahzeb
Copy link
Contributor

@pshahzeb pshahzeb commented Mar 21, 2017

  1. Disallow tenant rm, tenant vm rm, tenant vm replace when VMs which are part of the given tenant have any volumes currently mounted and being used. Safe approach to alert the user so that he can complete the ongoing jobs and then manipulate the tenancy membership changes.

  2. Send empty list as output to docker volume ls command to forcefully make docker
    print emtpy list and not the cached volume names

  3. [Minor] print to indicate negative test case and avoid CI output confusion

Fixes #1045
Create tenant1 and add a vm to it.

[root@sc-rdops-vm05-dhcp-133-3:~] /usr/lib/vmware/vmdkops/bin/vmdkops_admin.py vm-group ls
Uuid                                  Name      Description               Default_datastore  VM_list
------------------------------------  --------  ------------------------  -----------------  ------------
11111111-1111-1111-1111-111111111111  _DEFAULT  This is a default tenant
b06c6101-6b01-44c5-a318-09bb85392fd9  tenant1                                                photon-VM0.1

Create a volume and attach it to a container inside this VM. Keep the container running.

root@photon-xVmYMbyTn [ ~ ]# docker volume create --driver=vsphere --name  myVolume
myVolume
root@photon-xVmYMbyTn [ ~ ]# docker run -it --volume-driver=vsphere -v myVolume@sharedVmfs-0:/vol1 --name ub ubuntu
root@ada941b998cf:/#

Try to remove the VM from tenant, replace the VM with another VM and try to delete the tenant.
Error with appropriate message

[root@sc-rdops-vm05-dhcp-133-3:~] /usr/lib/vmware/vmdkops/bin/vmdkops_admin.py vm-group vm rm --name tenant1 --vm-list photon-VM0.1
Cannot complete vm-group vm rm. VM 'photon-VM0.1' has volumes mounted.
[root@sc-rdops-vm05-dhcp-133-3:~] /usr/lib/vmware/vmdkops/bin/vmdkops_admin.py vm-group vm replace --name tenant1 --vm-list photon-VM1.1
Cannot complete vm-group vm replace. VM 'photon-VM0.1' has volumes mounted.
[root@sc-rdops-vm05-dhcp-133-3:~] /usr/lib/vmware/vmdkops/bin/vmdkops_admin.py vm-group rm --name tenant1
Cannot complete tenant rm. VM 'photon-VM0.1' has volumes mounted.

VM is orphan now. docker volume ls shows empty list of volumes

root@photon-xVmYMbyTn [ ~ ]# docker volume ls
DRIVER              VOLUME NAME

Fixes #990
Create a volume from a VM and then delete the VM's tenant

root@photon-xVmYMbyTn [ ~ ]# docker volume create --driver=vsphere --name i_will_be_orphan
i_will_be_orphan
root@photon-xVmYMbyTn [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             i_will_be_orphan@sharedVmfs-0
[root@sc-rdops-vm05-dhcp-133-3:~] /usr/lib/vmware/vmdkops/bin/vmdkops_admin.py vm-group ls
Uuid                                  Name     Description  Default_datastore  VM_list
------------------------------------  -------  -----------  -----------------  ------------
b06c6101-6b01-44c5-a318-09bb85392fd9  tenant1                                  photon-VM0.1

[root@sc-rdops-vm05-dhcp-133-3:~] /usr/lib/vmware/vmdkops/bin/vmdkops_admin.py vm-group rm --name tenant1
vm-group rm succeeded

docker volume ls from the VM gives empty list

root@photon-xVmYMbyTn [ ~ ]# docker volume ls
DRIVER              VOLUME NAME

Copy link
Contributor

@shaominchen shaominchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Just one comment which is just refactoring the code - you can decide if you want to address it in a separate change or not.

@@ -786,9 +786,23 @@ def executeRequest(vm_uuid, vm_name, config_path, cmd, full_vol_name, opts):
vm_datastore_url = vmdk_utils.get_datastore_url_from_config_path(config_path)
vm_datastore = get_datastore_name(vm_datastore_url)

# Detach is implicity allowed if a disk has been attached
# Do not find tenant or privileges; just detach the disk
if cmd == "detach":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we introduce an enum to define all the commands?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, but outside of scope for this PR. @shaominchen - Please file an issue (or code it up and submit a PR)

Generally, executeRequest() became too messy. It was a small function with a switch for 5 simple commands, now it's quite complex. Would be nice to clean up. Certainly not in this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed issue #1070.

@@ -656,11 +656,11 @@ def attachVMDK(vmdk_path, vm_uuid):


# Return error, or None for OK.
def detachVMDK(vmdk_path, vm_uuid):
def detachVMDK(full_vol_name, vm_uuid):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer full_vmdk_path rather than full_vol_name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so is it a name (vol@DS) or a path?
A comment (in a dockstring) would be good

return err(error_info)
# For docker volume ls, docker prints a list of cached volume names in case
# of error(in this case, orphan VM).
# Explicity providing empty list of volumes to avoid misleading output.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you differentiate between genuine error versus VM not member of any tenant?

Please add link to issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest using if err_info and tenant_uuid: <read_error> and if erron_info and !tenant_uuid - no tenant

But yes, this one hides all real errors from get_tenant, if any. (mainly DB errors/file IO I guess)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -1297,16 +1311,20 @@ def err(string):
return {u'Error': string}


def disk_detach(vmdk_path, vm):
"""detach disk (by full path) from a vm amd return None or err(msg)"""
def disk_detach(full_vol_name, vm):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Since we are manipulating VMDK, I would refer this as full_vmdk_path

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the content of param 'full_vol_name' is qualified volume name in format volume@datastore
Calling it full_vmdk_path would be confusing.
I will update the param comment though

Copy link
Contributor

@msterin msterin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments inline. The main one - I do not understand how it will behave if there are two volumes with identical (short) names on different datastores, both attached. Which one will detach ?

@@ -832,6 +832,7 @@ def test_tenant_access(self):
rows[0][3]]
self.assertEqual(expected_output, actual_output)

print("[Negative test case]: Expected invalid values for allow-create option")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks !

@@ -656,11 +656,11 @@ def attachVMDK(vmdk_path, vm_uuid):


# Return error, or None for OK.
def detachVMDK(vmdk_path, vm_uuid):
def detachVMDK(full_vol_name, vm_uuid):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so is it a name (vol@DS) or a path?
A comment (in a dockstring) would be good

@@ -786,9 +786,23 @@ def executeRequest(vm_uuid, vm_name, config_path, cmd, full_vol_name, opts):
vm_datastore_url = vmdk_utils.get_datastore_url_from_config_path(config_path)
vm_datastore = get_datastore_name(vm_datastore_url)

# Detach is implicity allowed if a disk has been attached
# Do not find tenant or privileges; just detach the disk
if cmd == "detach":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, but outside of scope for this PR. @shaominchen - Please file an issue (or code it up and submit a PR)

Generally, executeRequest() became too messy. It was a small function with a switch for 5 simple commands, now it's quite complex. Would be nice to clean up. Certainly not in this PR

if cmd == "detach":
with lockManager.get_lock(vm_uuid):
response = detachVMDK(full_vol_name, vm_uuid)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (can be ignored): generally I'd prefer to have a simple dict-driven code, with something like {"attach": ["auth_needed": True, "func": "code", ,,} ...} to drive the execution for commands. (this is the same note as the above reply to @shaominchen ).

return err(error_info)
# For docker volume ls, docker prints a list of cached volume names in case
# of error(in this case, orphan VM).
# Explicity providing empty list of volumes to avoid misleading output.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest using if err_info and tenant_uuid: <read_error> and if erron_info and !tenant_uuid - no tenant

But yes, this one hides all real errors from get_tenant, if any. (mainly DB errors/file IO I guess)

logging.debug("findDeviceByName: datastore = %s, backing_disk_name = %s",
datastore, backing_disk_name)

if backing_disk_name == vol_name:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what if there are 2 Docker volumes with the same names on different datastores. both attached ? Which one will be detached? I do not see DS names used in matching.... not 'dockvols' part of the path

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@msterin
Fixing it to compare both volume name and datastore name with backing disk name and backing disk datastore.
Confirmed that we always get a qualified volume name i.e. volumename@datastore; from docker -> vmdk_plugin -> esx host.

@@ -1071,7 +1084,8 @@ def handle_stale_attach(vmdk_path, kv_uuid):
if cur_vm.runtime.powerState == VM_POWERED_OFF:
logging.info("Detaching disk %s from VM(powered off) - %s\n",
vmdk_path, cur_vm.config.name)
device = findDeviceByPath(vmdk_path, cur_vm)
vol_name = vmdk_utils.strip_vmdk_extension(os.path.basename(vmdk_path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strip extension should be responsibility of the caller, but instead done in findDeviceByName (if needed)

@pdhamdhere
Copy link
Contributor

@pshahzeb Is it ready for review? Looks like you are still addressing review comments?

@pshahzeb
Copy link
Contributor Author

Yes. This fix fails to detach volumes with two same short names but on different DS. we get short name for volume for detach during volume create process.
Working on it.

@pshahzeb pshahzeb force-pushed the tenancy_edge_cases.pshahzeb branch 2 times, most recently from d14432c to 2e48603 Compare March 29, 2017 22:51
@pshahzeb pshahzeb changed the title Handling tenancy related edge cases [No review] Handling tenancy related edge cases Mar 29, 2017
@pshahzeb pshahzeb changed the title [No review] Handling tenancy related edge cases Handling tenancy related edge cases Mar 29, 2017
# Filename format is as follows:
# "[<datastore name>] <parent-directory>/tenant/<vmdk-descriptor-name>"
# Trim the datastore name and keep disk path.
_, disk_path = d.backing.fileName.split(None, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot trim the datastore correctly if whitespaces are included in the datastore name.

fileName = "[datastor e1] /dockvols/tenant1/vol1.vmdk"

_, disk_path = fileName.split(None, 1)
print disk_path
e1] /dockvols/tenant1/vol1.vmdk

Use "]" as a separator.

def disk_detach(full_vol_name, vm):
"""detach disk (by full_vol_name) from a vm and return None or err(msg)"""
def disk_detach(vmdk_path, vm):
"""detach disk (by full path) from a vm amd return None or err(msg)"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: amd -> and

pshahzeb added 2 commits March 30, 2017 14:35
1. Disallowing tenant-vm membership change when volumes are mounted and in use by VMs
2. Send empty list as output to docker volume ls command to forcefully make docker
   print emtpy list and not the cached volume names

3. Minor print to indicate negative test case and avoid CI output confusion

Fixes #990 #1045
@pshahzeb pshahzeb force-pushed the tenancy_edge_cases.pshahzeb branch from fe64c8d to d1eb652 Compare March 30, 2017 22:59
Copy link
Contributor

@shaominchen shaominchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. A few nits.

@@ -409,6 +409,17 @@ def _tenant_rm(name, remove_volumes=False):
error_info = error_code.generate_error_info(ErrorCode.TENANT_NOT_EXIST, name)
return error_info

# check if vms that are a part if this tenant have any volumes mounted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "if this tenant" => "of this tenant"

# check if vms that are a part if this tenant have any volumes mounted.
# If they have, can't delete the tenant.
if tenant.vms:
logging.info("tenant rm. VMs in tenant are %s", tenant.vms)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "tenant rm" => "_tenant_rm", just for consistency.

@@ -75,6 +76,7 @@ class ErrorCode:
ErrorCode.VM_IN_ANOTHER_TENANT : "VM '{0}' has already been associated with tenant '{1}', can't add it",
ErrorCode.VM_LIST_EMPTY : "VM list cannot be empty",
ErrorCode.VM_DUPLICATE : "VMs {0} contain duplicates, they should be unique",
ErrorCode.VM_WITH_MOUNTED_VOLUMES :"VM '{0}' has volumes mounted.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add a space after ":"

@@ -307,6 +311,32 @@ def get_vm_config_path(vm_name):
vm_config_path = os.path.join(datastore_path, path)
return vm_config_path

def check_volumes_mounted(vm_list):
"""
Return error_info any vm in @param vm_list have docker volume mounted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add an "if" before "any vm"

# Trim the datastore name and keep disk path.
_, disk_path = d.backing.fileName.rsplit("]", 1)
logging.info("backing disk name is %s", disk_path)
# If disk path start with "dockvols", the disk is a docker volume
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract the logic of checking whether a disk is a docker volume (322-332) to a separate method?

Copy link
Contributor

@lipingxue lipingxue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@shaominchen shaominchen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants