-
Notifications
You must be signed in to change notification settings - Fork 95
code refactoring for tenant management API #729
Conversation
Please review it @msterin @shaominchen |
@lipingxue CI failing. Please fix. |
return operation_fail(error_info) | ||
|
||
name = args.name | ||
name = args.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.
Currently we do not validate any parameters. We should. Separate PR is fine with me.
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.
Issue #734 is filed to track this.
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 a quick first pass. See comments below.
@@ -324,6 +326,25 @@ def add_volume_to_volumes_table(tenant_uuid, datastore, vol_name, vol_size_in_MB | |||
|
|||
return None | |||
|
|||
def remove_volume_from_volumes_table(tenant_uuid, datastore, vol_name): | |||
""" Remove volume from volumes table. """ |
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 we add a line about return code e.g. "Returns None on success or error string" to all functions?
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.
vm_uuid = vmdk_utils.get_vm_uuid_by_name(vm_name) | ||
if not vm_uuid: | ||
error_info = "Cannot find vm_uuid for vm {0}, tenant_create failed".format(vm_name) | ||
return error_info, None |
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 I had commented on this last PR. We should not terminate loop on first bad VM name. Rather iterate through entire list and return VMs that are successfully assigned and VMs that couldn't be found.
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.
'global_visibility': 0, | ||
'create_volume': 0, | ||
'delete_volume': 0, | ||
'mount_volume': 0, |
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: Looks off by a space.
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.
def default_privileges(): | ||
privileges = [{'datastore': 'datastore1', | ||
'global_visibility': 0, | ||
'create_volume': 0, |
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.
Are we going to group privileges as discussed in #709
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 this is a refactoring of existing code, changing/ grouping provileges should not be in this PR IMO.
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.
Sure. Fine with separate PR.
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.
Existing issue #709 tracked this.
@@ -28,6 +28,7 @@ | |||
CMD_REMOVE = 'remove' | |||
CMD_ATTACH = 'attach' | |||
CMD_DETACH = 'detach' | |||
CMD_LIST = '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 had removed this in my last PR. Any reason we are re-introducing?
See executeRequest. Auth checks are bypassed for 'list' command.
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 my unit test code, I need to test 'list' command. It seems that your change assume we only put commands which need authorize in auth.py. I will remove 'list' command from this file and update the comment.
# mount_volume, max_volume_size, usage_quota) | ||
privileges_dict = {} | ||
privileges_dict[auth_data_const.COL_DATASTORE] = privileges[1] | ||
privileges_dict[auth_data_const.COL_GLOBAL_VISIBILITY] = privileges[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.
Didn't we get rid of GLOBAL_VISIBILITY?
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 this one - there are too many open issues on this one , we need to drop 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.
Done. Remove "global_visibility" from the code.
@@ -359,15 +359,16 @@ def create_tables(self): | |||
''' | |||
CREATE TABLE tenants( | |||
-- uuid for the tenant, which is generated by create_tenant() API | |||
id TEXT PRIMARY KEY NOT NULL, | |||
id TEXT NOT NULL, |
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.
If name is unique then why do we need ID?
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 we can rename without moving volumes, and have something invariant no matter what the ever-changing name is :-)
Still ma consider using it as a key, just need to be careful on rename
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.
PR ##711 tracks "tenant rename".
vol_size_in_MB = convert.convert_to_MB(auth.get_vol_size(opts)) | ||
auth.add_volume_to_volumes_table(tenant_uuid, datastore, vol_name, vol_size_in_MB) | ||
else: | ||
logging.warning(" VM %s does not belong to any tenant", vm_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.
warnings can flood logs. How about changing it to 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.
I think it's not a big deal now - we should not have this warning once "_fallback" is implemented - in which case "not belonging to any tenant" is a warning and the access for VM is denied
However given the costs, it's easier to change to debug than discuss :-)
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.
Change the log to "DEBUG".
return error_info | ||
else: | ||
if not vm_name: | ||
logging.warning("VM %s does not belong to any tenant", vm_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.
Same here. These warnings can flood logs. Change it to 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.
Please try to avoid copy-n-paste of messages. It is usually an indication that you need to either introduce a global for format string, or a simple function to return formatted string
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 this PR, I just define a global for format string. But in the long run, I think we need to have a file which defines all the error code and corresponding error string for all the error message we used in our code.
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.
Comments inline. All in all good change , the majority of comments are format / readability issues (e.g. docstrings or copy-n-paste of messages).
privileges = privileges) | ||
|
||
error_info, tenant = auth_api._tenant_create( | ||
name = 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.
Applies to multiple places:
PEP8 https://www.python.org/dev/peps/pep-0008/#whitespace-in-expressions-and-statements:
Don't use spaces around the = sign when used to indicate a keyword argument or a default parameter value.
Yes:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
No:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
(note that spaces around assignment in other cases are still needed)
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.
datastore = args.datastore | ||
rights = args.rights | ||
volume_maxsize = args.volume_maxsize | ||
volume_totalsize = args.volume_totalsize |
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.
are those assignment really needed ? What prevents us from simply callng
error_info = auth_api._tenant_access_add(
name=args.name,
datastore=args.datastore,
privileges=generate_privileges(args),
...
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.
privileges_dict = modify_privileges(privileges_dict, args) | ||
|
||
error_info = tenant.set_datastore_access_privileges(_auth_mgr.conn, [privileges_dict]) | ||
error_info = auth_api._tenant_access_set(name, datastore, add_rights, rm_rights, volume_maxsize, volume_totalsize) |
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 question - assignment above do not not seem to be needed, you can use param=args.field
in the call to _tenant_access_set
the comment applies to the rest of the wrappers too
import auth_data | ||
import vmdk_utils | ||
|
||
def get_auth_mgr(): |
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.
dosctrings are missing, please add - that applies to all new methods/functions
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
def get_auth_mgr(): | ||
try: | ||
auth_mgr = auth.get_auth_mgr() | ||
except auth_data.DbConnectionError, 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.
If it's new code, please use 'except as . If this is just code move, its OK to leave 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.
Done
if (not self.datastore_name): | ||
datastores = vmdk_utils.get_datastores() | ||
datastore = datastores[0] | ||
if (not datastore): |
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 looks suspicious. Is there a case where non-empty array with None as the first element is returned by get_datastores() ?
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.
|
||
def tearDown(self): | ||
""" Cleanup after each test """ | ||
logging.debug("VMDKTenantTest tearDown 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.
logging.info ? we are in tests, it's important to have traces
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.
vmdk_ops.removeVMDK(vmdk_path) | ||
|
||
# cleanup existing tenant | ||
error_info = auth_api._tenant_rm( |
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: PEP8 (just a reminder). no spaces around =
in param assignment
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.
self.assertEqual("tenant2_vol2", result[1]['Name']) | ||
self.assertEqual("tenant2_vol3", result[2]['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.
A general comment on tests: it is better to have them data drivel . I.e. instead of using tenantx_volx ad opts and other stuff in the code, ALL that should be in data structures and the test should just taverse the structures. See example (testInfo traversing) in vmdk_ops_test.py.
It's OK to leave it as is for this PR but please do open an issue for test rearrangement and making them data driven - that allows to understand tests better and to add to the test set easily
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.
Issue #740 is filed to track this
@@ -62,6 +62,7 @@ def test_create_delete(self): | |||
|
|||
def test_delete_nonexistent_policy_fails(self): | |||
self.assertNotEqual(None, vsan_policy.delete(self.name)) | |||
print ("The test itself is expected to fail, and please ignore the errors printed above.") |
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.
does not it have to be stderr ? Or log ? (just asking since I do not see this message in the log
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.
put it in logging.info.
68c5e04
to
fd1fa8b
Compare
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.
Looks good, almost there. A couple of files are missing standard headers ,and I am not sure why ' name' is a part of keys in Tenant. When this is resolved, ready to merge
@@ -0,0 +1,336 @@ | |||
import auth |
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 add standard file header
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 thought it was auto-generated? CC/ @kerneltime
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 was auto-generated when we did a pass of adding headers :-). Now it is copy-n-paste before push.
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.
-- brief description of the tenant, which is specified by user when creating the tenant | ||
-- this field can be changed laster by using set_description API | ||
description TEXT, | ||
-- not use currently | ||
default_datastore TEXT | ||
default_datastore TEXT, | ||
PRIMARY KEY(id, 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.
why do we need a key with both id, name ? does it mean the key changes when name changes ?
I thought ID would be the key and name would be just a text field (unique is good, but just text, not key) ?
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. Make "name" to be UNIQUE, but not the key.
@@ -446,6 +442,12 @@ def create_tenant(self, name, description, default_datastore, default_privileges | |||
|
|||
""" | |||
logging.debug ("create_tenant name=%s", name) | |||
error_info, exist_tenant = self.get_tenant(name) | |||
if exist_tenant: | |||
error_info = error_code.TENANT_ALREADY_EXIST.format(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.
nice !
@@ -0,0 +1,7 @@ | |||
# Tenant related error message |
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 add standard file header
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.
@@ -214,4 +214,38 @@ def get_vm_uuid_by_name(vm_name): | |||
return vm[0].config.uuid | |||
except: | |||
return None | |||
def get_datastore_path(ds_name): | |||
for (datastore, url_name, path) in get_datastores(): |
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.
dosctring (or comment) is missing. Also, an empty line before def
is needed
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.
Took quick look. It's huge change so couldn't go through logic again. Overall LGTM. Please address Mark's review comments and I have some minor comments below. Once those are addressed, it's good to go from my side.
privileges=privileges) | ||
if error_info: | ||
return error_info, None | ||
|
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.
Print/ Log not_found_vms
informing user?
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.
vol_name = vmdk_utils.strip_vmdk_extension(vmdk['filename']), | ||
vm_name = None, | ||
tenant_uuid = tenant_id, | ||
datastore = vmdk['datastore']) |
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.
Spaces still exist. Forgot to push?
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" | ||
|
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!
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.
return err(msg) | ||
|
||
error_info = err(msg) | ||
remove_err = removeVMDK(vmdk_path = 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.
remove space?
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.
logging.warning(" VM %s does not belong to any tenant", vm_name) | ||
|
||
elif cmd == "create": | ||
response = createVMDK(vmdk_path = 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.
remove spaces?
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.
elif cmd == "remove": | ||
response = removeVMDK(vmdk_path) | ||
response = removeVMDK(vmdk_path = 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.
remove spaces?
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.
@lipingxue - it looks like there are some conflicts to be resolved. and the latest changes (marked as "done" are still not in. Looking forward... |
Yes. After rebase with the master, there are some conflict with latest change in master(Bruno's change). I am addressing those conflict and rerun the test. Will do the push when it is done. |
fd1fa8b
to
f77514a
Compare
@msterin I have resolved conflict and push the change. Please take another look. |
@@ -438,52 +438,6 @@ def remove_vm(si, vm_name): | |||
task = vm.Destroy_Task() | |||
vmdk_ops.wait_for_tasks(si, [task]) | |||
|
|||
class ValidationTestCase(unittest.TestCase): |
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.
why this test is removed?
910a9db
to
3f8b606
Compare
@msterin I checked the log and also looked at our code, and I figured out this is reason why I see the above error. We call function "get_auth_mgr()" to get a connection to the DB. This function will call "thread_local._auth_mgr.connect()" ONLY if it does not have a cached value. In function"AuthorizationDataManager.connect()", we have the logic to decide whether we need to create table or not (check whether DB file exists or not). Assume we start from the state that we have pre-exist DB in the testbed.
I think we only need to remove the old DB one time. This logic also apply to the possible DB migration.
Please review this newly added code. |
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.
file removal should be a part of the esx_service/Makefile not .py code
logging.debug("auth DB %s exist, try to remove the file", AUTH_DB_PATH) | ||
os.remove(AUTH_DB_PATH) | ||
# insert some delay | ||
time.sleep(60) |
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.
why the delay ?
|
||
if os.path.isfile(self.db_path): | ||
# already exist | ||
if not has_migrate_auth_db and self.need_migrate_auth_db(): |
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.
self.need_migrate_auth_db() need to check for 'has already migrated' (what you cann has_migrate_auth_db ) for better encapsulation
# already exist | ||
if not has_migrate_auth_db and self.need_migrate_auth_db(): | ||
self.migrate_auth_db() | ||
has_migrate_auth_db = True |
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.
well, since it's global it's "test and set" should be atomic , so I suggest a lock here
@@ -117,9 +117,11 @@ function deployesx { | |||
do | |||
TARGET=root@$ip | |||
log "Deploying to ESX $TARGET" | |||
$SSH $TARGET rm -f /etc/vmware/vmdkops/log_config.json |
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 one should be a different commit - it's a useful fix
3f8b606
to
563c044
Compare
…get a connection to auth DB.
…ent. Those APIs can be called by both AdminCLI and VMODL code.
…sfully. 2. make change to make "tenant name" unique.
1) attachVMDK, detachVMDK, listVMDK 2) test to make sure isolation between tenants
… in vmdkops test for multi-tenancy. Fix small bugs in auth_api.py.
…e testbed." This reverts commit 3f8b606.
Fix a bug in function "get_vm_config_path".
563c044
to
84f013f
Compare
@msterin I have addressed your comments, please 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.
Contingent on passing CI
* Refactor code to add an abstract layer to provide APIs for tenant management. Those APIs can be called by both AdminCLI and VMODL code. * Add unit test for multi-tenancy in "vmdk_ops_test.py" * remove volume from "volumes" table when removing the volume successfully. * make change to make "tenant name" unique in DB schema. * Force remove file "/etc/vmware/vmdkops/log_config.json" in function "deployesx". * Add code to force removing auth DB file before running unit test.
Fixed #710 |
This PR includes: