-
Notifications
You must be signed in to change notification settings - Fork 95
Tenancy: Implement __FALLBACK tenant and __FALLBACK privilege #788
Conversation
if result: | ||
# found __FALLBACK tenant | ||
tenant_uuid = result[0] | ||
logging.debug("Found __FALLBACK tenant, tenant_uuid %s, tenant_name %s", tenant_uuid, FALLBACK_TENANT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Since the last parameter tenant_name is "__FALLBACK", I think we can log the message "Found fallback tenant..." instead of "Found __FALLBACK tenant...".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it's still the same?
return None, None, None | ||
|
||
|
||
def get_fallback_privileges(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There will be one and only one fallback privilege, correct? Shouldn't we use singular form "privilege" instead of "privileges" everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This should just be one entry for fallback privilege. But in this entry, they have different privileges (create, remove, and mount). That is why I use "privileges".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should differentiate the term "privilege" and "right". IIUC, in our service the term "privilege" indicates some rights (create, remove, mount, etc.) on one datastore. One tenant can have multiple "privileges" on different datastores, but there will be one and only one privilege (which includes multiple "rights") on one single datastore.
) | ||
privileges = cur.fetchone() | ||
except sqlite3.Error as e: | ||
error_info = "Error {0} when querying privileges table for __FALLBACK privilege".format(e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Same as above - I suggest to use "fallback" instead of "__FALLBACK" in the log messages. We should always use above constants (FALLBACK_TENANT or FALLBACK_DS) to reference "__FALLBACK" strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it's still the same?
@@ -308,6 +309,35 @@ def get_auth_db_path(self): | |||
|
|||
""" | |||
return AUTH_DB_PATH | |||
|
|||
def create_fallback_tenant_and_privileges(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to split this method into two: create_fallback_tenant() and create_fallback_privilege(). One method does one thing only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on splitting into 2 separate methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
error_info, tenant_uuid, tenant_name = get_fallback_tenant() | ||
if error_info: | ||
return error_info, None, None | ||
if not tenant_uuid: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I'd prefer "if" than "if not" as the former is slightly easier to understand:
if tenant_uuid:
....
else:
....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will keep it as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please explain a little bit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is different coding style. No major advantage to make the change.
@@ -18,6 +18,8 @@ | |||
VM_NOT_BELONG_TO_TENANT = "VM {0} does not belong to any tenant" | |||
TENANT_NOT_EXIST = "Tenant {0} does not exist" | |||
TENANT_ALREADY_EXIST = "Tenant {0} already exists" | |||
TENANT_CREATE_FAILED = "Tenant {0} create failed with err:{1}" |
There was a problem hiding this comment.
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 "err:"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -18,6 +18,8 @@ | |||
VM_NOT_BELONG_TO_TENANT = "VM {0} does not belong to any tenant" | |||
TENANT_NOT_EXIST = "Tenant {0} does not exist" | |||
TENANT_ALREADY_EXIST = "Tenant {0} already exists" | |||
TENANT_CREATE_FAILED = "Tenant {0} create failed with err:{1}" | |||
TENANT_SET_ACCESS_PRIVILEGES_FAILED = "Tenant {0} set access privileges on datastore {1} failed with {2}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Let's keep the error messages consistent: either "failed with {}" or "failed with error: {}"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -252,6 +252,7 @@ def cloneVMDK(vm_name, vmdk_path, opts={}, vm_uuid=None, vm_datastore=None): | |||
return err("Failed to initialize source volume path {0}: {1}".format(src_path, errMsg)) | |||
|
|||
src_vmdk_path = vmdk_utils.get_vmdk_path(src_path, src_volume) | |||
logging.debug("cloneVMDK: src_path=%s, src_volume=%s, src_vmdk_path=%s", src_path, src_volume, src_vmdk_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change in this file doesn't seem to be related to __FALLBACK feature. If you want to include these changes, please add some description in the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This is not related to FALLBACK feature. The reason I add this debug log is I found it is very difficult when I debug FALLBACK feature, the unit test failed in cloneVMDK related test. I think the log is helpful for others to debug if they hit errors in this part.
@@ -336,6 +366,7 @@ def connect(self): | |||
|
|||
if need_create_table: | |||
self.create_tables() | |||
self.create_fallback_tenant_and_privileges() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to be the right place to create fallbacks. I think we should add a flag to check if need_crreate_fallback. This way, we can handle the "upgrade" more smoothly. Also in the unit test we shouldn't have the logic to create fallbacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fallback has nothing to do with upgrade.
fallback tenant and fallback privilege will be created automatically when table was first created.
upgrade happens when we detect that there exist a DB file, but we found the version is not the latest, then we should have some code to upgrade the DB table to the latest. Those part of code is not here yet.
Since user can remove the fallback tenant and fallback privilege, in unit test, we have some test to test the behavior when fallback tenant and fallback privileges are removed. And after our test, we want to return back to the status that fallback tenant and fallback privileges are still in the DB table.
@@ -685,7 +689,38 @@ class VmdkTenantTestCase(unittest.TestCase): | |||
datastore_name = None | |||
datastore_path = None | |||
|
|||
|
|||
def create_fallback_tenant_and_privileges(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in above comment, we should check whether fallbacks need to be created even if table already exists. This way we don't need to do this in unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
@msterin Please review this change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM.
_FALLBACK tenant will show up in admin tenant ls
command rt? Also I assume Admin can override default privileges using Admin CLI. Please confirm
@@ -30,6 +32,8 @@ | |||
CMD_DETACH = 'detach' | |||
|
|||
SIZE = 'size' | |||
FALLBACK_TENANT = '__FALLBACK' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FALLBACK => _DEFAULT or _SYSTEM?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I do not see much difference. 'fallback' is semantically more accurate. But "__DEFAULT" sounds good too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will keep the name as __FALLBACK.
# create a __FALLBACK tenant | ||
error_info, tenant = self.create_tenant( | ||
name=auth.FALLBACK_TENANT, | ||
description="This is a fallback tenant", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"This is a system _default tenant"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will keep it as FALLBACK.
@@ -308,6 +309,35 @@ def get_auth_db_path(self): | |||
|
|||
""" | |||
return AUTH_DB_PATH | |||
|
|||
def create_fallback_tenant_and_privileges(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on splitting into 2 separate methods.
@pdhamdhere |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good change ! Some comments inline. Please add the test description to the PR description. I will be looking forward for you addressing Sam's and Prashant;s comments (mine are mainly nits)
@@ -30,6 +32,8 @@ | |||
CMD_DETACH = 'detach' | |||
|
|||
SIZE = 'size' | |||
FALLBACK_TENANT = '__FALLBACK' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I do not see much difference. 'fallback' is semantically more accurate. But "__DEFAULT" sounds good too.
def get_fallback_tenant(): | ||
""" | ||
Get __FALLBACK tenant by querying the auth DB. | ||
VMs which does not belong to any tenant explicitly will |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "VMs which do not belong" or "VM which does not belong"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
be assigned to __FALLBACK tenant if __FALLBACK tenant exists | ||
Return value: | ||
-- error_info: return None on success or error info on failure | ||
-- tenant_uuid: return __FALLBACK tenant uuid on success, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please use FALLBACK_TENANT in comments, instead of the variable value :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
tenant_uuid = None | ||
_auth_mgr = get_auth_mgr() | ||
try: | ||
cur = _auth_mgr.conn.execute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this code is going to be executed on each VMDK request ? Any way to cache it for say 1 sec ? (not a issue, just musing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not going to address it in this PR, but will file an issue to track this enhancement.
return None, tenant_uuid, tenant_name | ||
return None, tenant_uuid, tenant_name | ||
else: | ||
error_info, tenant_uuid, tenant_name = get_fallback_tenant() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_fallback_tenant() seems to be doing pretty much the dame as the code above (line 136-151). I think it would be better wrap it in a function get_tenant_from_db(where_clause, key) , and use it with something like.
get_tenant_from_db("id = ?", (tenant_uuid, ))
Basically I think copy-n-paste is evil :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two places are different. For normal tenant, from vm_uuid, we query vms table to find the tenant_uuid of the tenant which own this VM. Then we query the tenant table by id to get tenant_name.
For FALLBACK tenant, we know the tenant name is "__FALLBACK". We then query the tenant table by name to get tenant_uuid.
(FALLBACK_DS,) | ||
) | ||
privileges = cur.fetchone() | ||
except sqlite3.Error as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels the exceptions are handled too often. I think yu should handle them much higher up in the stack. Let's discuss offline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have discussed offline.
tenants_list[tenant2_idx].default_privileges[0][auth_data_const.COL_DELETE_VOLUME], | ||
tenants_list[tenant2_idx].default_privileges[0][auth_data_const.COL_MOUNT_VOLUME], | ||
tenants_list[tenant2_idx].default_privileges[0][auth_data_const.COL_MAX_VOLUME_SIZE], | ||
tenants_list[tenant2_idx].default_privileges[0][auth_data_const.COL_USAGE_QUOTA] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice seeing unit tests in the PR !
A suggestion: I think you need to check for fallback tenant and priviledge being created in the DB as a separate test. And I think there should be a test with deleting the fallback and making sure ops from nontenant fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"test_vmdkops_on_fallback_tenant_vm" did this.
- It test the create, attach, detach, remove command when FALLBACK tenant and FALLBACK privileges are present
- It then test the vmdk ops after removing FALLBACK tenant and FALLBACK privilege
What do you mean "you need to check for fallback tenant and privileges being created in the DB as a separate test"?
FALLBACK tenant and privileges are created when table is created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean "you need to check for fallback tenant and privileges being created in the DB as a separate test"?
FALLBACK tenant and privileges are created when table is created.
is there a sanity check that validates it ? (low priority)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the setUp() function of class "VmdkTenantTestCase", I have the code to check whether FALLBACK tenant and FALLBACK privilege are missing. It they are missing, the code will try to create them. See function "create_fallback_tenant_and_privileges".
si = vmdk_ops.get_si() | ||
try: | ||
vm = [d for d in si.content.rootFolder.childEntity[0].vmFolder.childEntity if d.config.uuid == vm_uuid] | ||
return vm[0].config.name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can it be more that 1 element in the list ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. I think the vm name should be unique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the vm name should be unique?
no they are not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then how do we handle this? Assume we already has an VM named "vm1", then I try to create another vm also named "vm1". Then how can I tell which VM is the one I just created?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in one esx, the vm name should be unique, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made change to "create_vm" and "remove_vm" to avoid using functino "find_vm_by_name".
@@ -251,6 +251,7 @@ def testPolicy(self): | |||
err = vmdk_ops.removeVMDK(vmdk_path) | |||
self.assertEqual(err == None, unit[2], err) | |||
|
|||
@unittest.skip("Liping remove") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you want to remove it :-) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes :)
18599d4
to
2f3838c
Compare
I have adressed all comments. Please take a look. |
1d9a423
to
d43b42f
Compare
CI test failed in "VmdkClone" test. I have added more debug info in the change to debug the CI failure. Please ignore the change related to that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please
(1) revert DEBUG log level back to INFO in log_config.py
(2) add a few words for to description about WHAT cases are covered by tests and
(3) CI should pass (need int()
in kvESX.py:convert()
per our discussion.
After that it's LGTM.
@@ -30,7 +30,7 @@ | |||
# since we rely on it to locate log file name after config is loaded. | |||
LOG_CONFIG_FILE = "/etc/vmware/vmdkops/log_config.json" | |||
|
|||
LOG_LEVEL_DEFAULT = 'INFO' | |||
LOG_LEVEL_DEFAULT = 'DEBUG' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be changed back before merge
Add unit test to test vmdkops on VM which belongs to __FALLBACK tenant.
…moving old volumes which are left from previous test run.
… "find_vm_by_name". Remove function "find_vm_by_name" since vm name is not unique and we cannot find vm by name.
There is a python2 and python 3 compitible issue with division. In python 2, 1 / 2 will return 0 (an integer value), but in python 3, 1/2 will return 0.5(a floating value).
d43b42f
to
58da306
Compare
"_DEFAULT" tenant and "_DEFAULT" privilege.
This change solved two problems in this issue #621.
The remain problem "support default_datastore" will be addressed in my next PR. In my next PR, I plan to address "support default_datastore" and privilege changes (Tenancy: Creating volume fails in absence of "mount" privilage #709)
This change also include necessary unit test to support __FALLBACK tenant and __FALLBACK privilege.