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

Make change to authorize() function to take "datastore_url" and volume create fail after datastore rename #979

Merged
merged 5 commits into from
Mar 3, 2017

Conversation

lipingxue
Copy link
Contributor

This PR is the second part of fix for #818. This PR includes:

  1. make change to authorize() function to take "datastore_url" to avoid unnecessary conversion between datastore_url and datastore_name during the authorize process.
  2. fix the issue that volume create failure after datastore rename by forcing datastore cache refresh in executeRequest() function. We use "os.path.exists("/vmfs/volumes/datastore_name")" to determine whether the datastore with given "datastore_name" exist or not. If the datastore does not exist, force refresh the datastore cache.
  3. corresponding changes to test code.

@lipingxue
Copy link
Contributor Author

Testing Done:

Configuration 1:
One VM "photon6" is created on "datastore1", and this VM is associated with _DEFAULT tenant

Case 1.1: after VM datastore "datastore1" rename, create volume with short name succeeded.

root@photon-KwqUODFXp [ ~ ]# docker volume create --driver=vsphere --name=vol1
vol1

---- rename "datastore1" to "new-datastore1"

root@photon-KwqUODFXp [ ~ ]# docker volume create --driver=vsphere --name=vol2
vol2
root@photon-KwqUODFXp [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             vol1@new-datastore1
vsphere             vol2@new-datastore1
root@photon-KwqUODFXp [ ~ ]#

Case1.2: after VM datastore "datastore1" rename, create volume with volname@old-datastore failed, and create volume with volname@new-datastore succeeded.

root@photon-KwqUODFXp [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             vol1@datastore1
vsphere             vol2@datastore1

---- rename "datastore1" to "new-datastore1"

root@photon-KwqUODFXp [ ~ ]# docker volume create --driver=vsphere --name="vol3@datastore1"
Error response from daemon: create vol3@datastore1: VolumeDriver.Create: Invalid datastore 'datastore1'.
Known datastores: new-datastore1, datastore2.
Default datastore: new-datastore1
root@photon-KwqUODFXp [ ~ ]# docker volume create --driver=vsphere --name="vol3@new-datastore1"
vol3@new-datastore1
root@photon-KwqUODFXp [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             vol1@new-datastore1
vsphere             vol2@new-datastore1
vsphere             vol3@new-datastore1

Configuration2:
One VM "photon7" is created on "datastore1", this VM is associated with tenant "tenant1". The default_datastore for "tenant1" is "datastore1"

Case2.1: after default datastore "datastore1", rename, create volume with short name succeeded.

Case3:
root@photon-P0itmvfOB [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             tenant1-vol1@datastore1

----rename "datastore1" to "new-datastore1"

root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol2
tenant1-vol2
root@photon-P0itmvfOB [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             tenant1-vol1@new-datastore1
vsphere             tenant1-vol2@new-datastore1

Case2.2: after non default datastore "datastore2" rename, create volume with full name volname@old-datastore failed, and create volume with volname@new-datastore succeeded.

root@photon-P0itmvfOB [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             tenant1-vol1@datastore1
vsphere             tenant1-vol2@datastore1

--- rename "datastore2" to "new-datastore2"

root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol3@datastore2
Error response from daemon: create tenant1-vol3@datastore2: VolumeDriver.Create: Invalid datastore 'datastore2'.
Known datastores: datastore1, new-datastore2.
Default datastore: datastore1
root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol3@new-datastore2
tenant1-vol3@new-datastore2
root@photon-P0itmvfOB [ ~ ]# docker volume ls
DRIVER              VOLUME NAME
vsphere             tenant1-vol1@datastore1
vsphere             tenant1-vol2@datastore1
vsphere             tenant1-vol3@new-datastore2

@lipingxue
Copy link
Contributor Author

@msterin @shaominchen Please review it.

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.

Good change. Comments inside - mainly about some refactoring and a few nits.
Also, did you test (manually or automated) setting limits on a tenant (default tenant is the best to test here) ?

@@ -288,7 +288,7 @@ def setUp(self):
""" Setup run before each test """
self.vol_count = 0
self.cleanup()
for (datastore, url_name, path) in vmdk_utils.get_datastores():
for (datastore, url_name, path, url) in vmdk_utils.get_datastores():
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the difference between url_name and url ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same confusion. We should rename these 2 variables to clarify the difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

url_name is part of url.
As we discussed offline, will remove "url_name" from vmdk_utils.get_datastores()

@@ -250,7 +244,7 @@ def generate_privileges_dict(privileges):

def get_default_datastore(name):
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: better to rename to get_default_datastore_url.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with Mark.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -227,7 +227,7 @@ def set_name(self, conn, name, new_name):
# rename the old symbol link /vmfs/volumes/datastore_name/tenant_name
# to a new name /vmfs/volumes/datastore_name/new_tenant_name
# which still point to path /vmfs/volumes/datastore_name/tenant_uuid
for (datastore, url_name, path) in vmdk_utils.get_datastores():
for (datastore, url_name, path, url) in vmdk_utils.get_datastores():
Copy link
Contributor

Choose a reason for hiding this comment

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

same question - what's the difference between url_name and url ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See above.

if err:
logging.error("remove vmdk %s failed with error %s", vmdk_path, err)
error_msg += str(err)

VOL_RM_LOG_PREFIX = "Tenant <name> %s removal: "
# delete the symlink /vmfs/volume/datastore_name/tenant_name
# which point to /vmfs/volumes/datastore_name/tenant_uuid
for (datastore, url_name, path) in vmdk_utils.get_datastores():
for (datastore, url_name, path, url) in vmdk_utils.get_datastores():
Copy link
Contributor

Choose a reason for hiding this comment

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

nit - this section seems to be cut-n-pasted afew times, better to have a function

% (config_path, config_ds_url))
return config_ds_url

def datastore_path_exist(datastore):
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: either datastore_name or just name (instead of 'datastore) in params. Datastore is confusing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -764,24 +773,45 @@ def executeRequest(vm_uuid, vm_name, config_path, cmd, full_vol_name, opts):
Returns None (if all OK) or error string
"""
logging.debug("config_path=%s", config_path)
vm_datastore = get_datastore_name(config_path)
vm_datastore_url = get_datastore_url_from_config_path(config_path)
vm_datastore = vmdk_utils.get_datastore_name(vm_datastore_url)
Copy link
Contributor

Choose a reason for hiding this comment

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

this block spreads the knowledge about cache manipulation onto too many places.
I think it would be way better to hide it , say in get_datastore_name() ... it can check get_datastores() and reset the cache internally if needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

default_datastore = vm_datastore
else:
default_datastore = vmdk_utils.get_datastore_name(default_datastore_url)
if not datastore_path_exist(default_datastore):
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment as above , plus copy-n-paste is evil :-) (makes it much harder to change later) .
Please consider hiding the path check and cache update inside of get_datastore_name()

Copy link
Contributor

Choose a reason for hiding this comment

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

Totally agree - cache manipulation (refresh on demand) is the API's responsibility, not the API user's.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


if not vmdk_utils.validate_datastore(datastore):
if datastore and not vmdk_utils.validate_datastore(datastore):
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's better to hide a check for if datastore inside of validate datastore, which should always return False if passed None ? (depends on other places in the code, so it's a nit)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here "datastore==None" means the volume which user passed is a short name(no datastore is specified by user), so no need to validate "datastore" in this case.

if error_info:
return err(error_info)

# get /vmfs/volumes/<volid>/dockvols path on ESX:
datastore = vmdk_utils.get_datastore_name(datastore_url)
Copy link
Contributor

Choose a reason for hiding this comment

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

and another confirmation that we need to hide this in get_datastore_name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

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.

Overall looks great.

@@ -288,7 +288,7 @@ def setUp(self):
""" Setup run before each test """
self.vol_count = 0
self.cleanup()
for (datastore, url_name, path) in vmdk_utils.get_datastores():
for (datastore, url_name, path, url) in vmdk_utils.get_datastores():
Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same confusion. We should rename these 2 variables to clarify the difference.

@@ -250,7 +244,7 @@ def generate_privileges_dict(privileges):

def get_default_datastore(name):
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with Mark.


Return value:
error_msg: return None on success or error info on failure
datastore: return default_datastore name on success or None on failure
datastore: return default_datastore url on success or None on failure
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: datastore => datastore_url

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -74,7 +74,8 @@ def init_datastoreCache():
continue
datastores.append((datastore.info.name,
os.path.split(datastore.info.url)[1],
Copy link
Contributor

Choose a reason for hiding this comment

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

I doubt this statement may not really work. I tried to run this statement with a real datastore url, it always return empty:

import os
url =os.path.split("ds:///vmfs/volumes/vsan:525ab51e793147fd-c0f242a06e800fc2/")[1]
print(url)

If this field is incorrect and it's not being used anywhere, we should just get rid of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed

@@ -331,7 +336,7 @@ def get_datastore_name(datastore_url):
return auth.DEFAULT_DS

# Query datastore name from VIM API
res = [d.info.name for d in get_datastore_objects() if d.info.url == datastore_url]
res = [d[0] for d in get_datastores() if d[3] == datastore_url]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to improve the readability to avoid the magic d[0] and d[3]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Add some comments here.

else:
logging.debug(error_code.error_code_to_message[ErrorCode.VM_NOT_BELONG_TO_TENANT].format(vm_name))


def cloneVMDK(vm_name, vmdk_path, opts={}, vm_uuid=None, vm_datastore=None):
logging.info("*** cloneVMDK: %s opts = %s", vmdk_path, opts)
def cloneVMDK(vm_name, vmdk_path, opts={}, vm_uuid=None, vm_datastore_url=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

Just for consistency: rename vm_datastore_url to datastore_url.

default_datastore = vm_datastore
else:
default_datastore = vmdk_utils.get_datastore_name(default_datastore_url)
if not datastore_path_exist(default_datastore):
Copy link
Contributor

Choose a reason for hiding this comment

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

Totally agree - cache manipulation (refresh on demand) is the API's responsibility, not the API user's.

@lipingxue lipingxue force-pushed the use_ds_url_in_authorize.liping branch from 454140c to 7a59ceb Compare March 2, 2017 00:35
@lipingxue
Copy link
Contributor Author

I also did the following test to make sure max_vol_size and total_size(quota) limit for tenant is not broken by this change.
Configuration:
Create a tenant "tenant1", add VM "photon7" to this tenant

/usr/lib/vmware/vmdkops/bin/vmdkops_admin.py tenant ls
Uuid                                  Name      Description               Default_datastore  VM_list 
------------------------------------  --------  ------------------------  -----------------  ------- 
11111111-1111-1111-1111-111111111111  _DEFAULT  This is a default tenant                             
e6a0cef4-22ce-48f6-a9ed-a28950c7277e  tenant1                             datastore1         photon7 


/usr/lib/vmware/vmdkops/bin/vmdkops_admin.py tenant access ls --name=tenant1
Datastore   Allow_create  Max_volume_size  Total_size 
----------  ------------  ---------------  ---------- 
datastore1  True          500.00MB         1.00GB     
root@photon-P0itmvfOB [ ~ ]# docker volume ls
DRIVER              VOLUME NAME

root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol1 -o size=600MB
Error response from daemon: create tenant1-vol1: VolumeDriver.Create: volume size exceeds the max volume size limit
root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol1 -o size=500MB
tenant1-vol1
root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol2 -o size=500MB
tenant1-vol2
root@photon-P0itmvfOB [ ~ ]# docker volume create --driver=vsphere --name=tenant1-vol3 -o size=100MB
Error response from daemon: create tenant1-vol3: VolumeDriver.Create: The total volume size exceeds the usage quota
root@photon-P0itmvfOB [ ~ ]#        


# dockvol_path has the format like "/vmfs/volumes/<datastore_name>"
# tenant_path has the format like "/vmfs/volumes/<datastore_name>/tenant_id"
dockvol_path = os.path.join("/vmfs/volumes", datastore_name, vmdk_ops.DOCK_VOLS_DIR)
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: can we define /vmfs/volumes as constant?

Copy link
Contributor

Choose a reason for hiding this comment

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

agree with this but it should delay the merge

# path can be /vmfs/volumes/<datastore_url_name>/...
# or /vmfs/volumes/datastore_name/...
# so extract datastore_url_name:
config_ds_url = os.path.join("/vmfs/volumes/", os.path.realpath(config_path).split("/")[3])
Copy link
Contributor

Choose a reason for hiding this comment

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

seen multiple occurrence of /vmfs/volumes/; it is better to declare as constant. Please refactor for each occurrences.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I personally think "/vmfs/volumes" is more readable in the context of the code, so will drop this comment.

logging.error("get_datastore_name: found more than one match: %s" % ds_name)
logging.debug("get_datastore_name: path=%s name=%s" % (config_ds_name, ds_name))
return ds_name[0]
def get_datastore_url_from_config_path(config_path):
Copy link
Contributor

Choose a reason for hiding this comment

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

can this be moved to vmdk_utils as part of this PR (seems the whole method is refactored)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

% (config_path, config_ds_url))
return config_ds_url

def datastore_path_exist(datastore_name):
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above .. it is better to move to vmdk_utils.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will keep it here for now since now no other callers need this function.

Copy link
Contributor

@shuklanirdesh82 shuklanirdesh82 left a comment

Choose a reason for hiding this comment

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

overall look good to me .. supplying some minor comments.

@@ -324,6 +324,8 @@ def get_datastore_url(datastore_name):
return None

# Query datastore URL from VIM API
# get_datastores() return a list of tuple
# each tuple has for format like (datastore_name, datastore_url, dockvol_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: remove the redundant "for"

@@ -335,6 +337,8 @@ def get_datastore_name(datastore_url):
return auth.DEFAULT_DS

# Query datastore name from VIM API
# get_datastores() return a list of tuple
# each tuple has for format like (datastore_name, datastore_url, dockvol_path)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: same as above.

""" Get datastore_name with given datastore_url """
datastore_name = vmdk_utils.get_datastore_name(datastore_url)
datastore_path = os.path.join("/vmfs/volumes/", datastore_name)
if not datastore_path_exist(datastore_path):
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we pass the datastore_name instead of datastore_path here? See the API signature at line 757.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, good catch!

@lipingxue lipingxue force-pushed the use_ds_url_in_authorize.liping branch from fdfedd2 to 4b23184 Compare March 2, 2017 20:07
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.

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.

LGTM. Please do not forget to squash the commits

@lipingxue lipingxue force-pushed the use_ds_url_in_authorize.liping branch from 4b23184 to 12b03d0 Compare March 3, 2017 18:16
@lipingxue lipingxue merged commit 8ab5681 into master Mar 3, 2017
@lipingxue lipingxue deleted the use_ds_url_in_authorize.liping branch March 3, 2017 22:17
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

5 participants