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

Commit

Permalink
Multi-tenancy VMODL implementation (#859)
Browse files Browse the repository at this point in the history
* Docker Volume Service VMODL implementation for multi-tenancy.

Docker Volume Service VMODL is based on VSAN VMODL extension support and uses vsanmgmtd as the VMOMI server. So, vsanmgmtd needs to be started to access Docker Volume Service VMODL.

Note: current Makefile brute-force restarts vsanmgmtd on every start of our daemon.
It also assumes vsan service running.

This PR includes multiple changes:
1. VMODL API update: the API definition has been changed significantly since initial check-in, especially exception handling
2. VMODL API implementation - integrated with auth_api
3. Unit tests for all VMODL APIs. Totally 40 test cases covering all basic positive and negative scenarios.
4. Build scripts. Pylint is also enabled for VMODL python files.
5. Related back-end modification and bug fixes - done by Liping
  • Loading branch information
Sam Chen authored Jan 31, 2017
1 parent 39d1105 commit 5029c3f
Show file tree
Hide file tree
Showing 18 changed files with 2,126 additions and 461 deletions.
12 changes: 10 additions & 2 deletions esx_service/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ VMCI_SRV_LIBS := $(VMCI_SRV_LIB) $(VMCI_SRV_LIB64)

# Source code - all .py stuff which is not tests
PY_SRC := $(filter-out %_test.py, $(wildcard *.py))
PY_VMODL := $(filter-out %_test.py, $(wildcard vmodl/*.py))
# exclude __init__.py to avoid clash with __init__.py from parent
PY_CLI := $(filter-out %_test.py, $(wildcard cli/[a-z]*.py))
PY_UTILS := $(filter-out %_test.py, $(wildcard utils/*.py))
Expand Down Expand Up @@ -115,8 +116,10 @@ TO_ESX_INITD := $(INIT_SCRIPT)
PY_SQLITE := tools/sqlite/sqlite3
SO_SQLITE := tools/sqlite/*.so

VMODL_PY := $(shell find ../esx_service/vmodl -name '*.py')

# copy files to staging area and then build the VIB
$(VIB_BIN): $(TO_WDIR) $(TO_ESX_BIN) $(TO_ESX_PY) $(TO_ESX_LIB) $(TO_ESX_INITD) $(PY_SQLITE) $(SO_SQLITE)
$(VIB_BIN): $(TO_WDIR) $(TO_ESX_BIN) $(TO_ESX_PY) $(TO_ESX_LIB) $(TO_ESX_INITD) $(VMODL_PY) $(PY_SQLITE) $(SO_SQLITE)
@echo Staging in $(PAYLOAD)
ifeq ($(INCLUDE_UI), true)
@mkdir -p $(VMDKOPS_BIN) $(VMDKOPS_PY) $(VMDKOPS_PY2) $(VMDKOPS_LIB) $(VMDKOPS_LIB64) $(VMDKOPS_INITD) $(UI_TO)
Expand All @@ -134,6 +137,7 @@ endif
cp $(VMCI_SRV_LIB) $(VMDKOPS_LIB)/$(VMCI_SRV_LIB_NAME)
cp $(VMCI_SRV_LIB64) $(VMDKOPS_LIB64)/$(VMCI_SRV_LIB_NAME)
cp $(TO_ESX_INITD) $(VMDKOPS_INITD)
VMDKOPS_PAYLOAD=$(VMDKOPS_PAYLOAD) $(MAKE) --directory=vmodl copy_to_payload
@chmod -R u+x $(VMDKOPS_BIN)
@sed "s/ <version>1.0.0-0.0.1<\/version>/ <version>$(PKG_VERSION)-$(RELEASE_VERSION)<\/version>/g" $(TO_WDIR) > $(WDIR)/$(DESCRIPTOR)
$(VIBAUTHOR) $(VIBAUTHOR_PARAMS)
Expand All @@ -153,12 +157,16 @@ PYLINT_IGNORE := --errors-only -d import-error,no-name-in-module
PYLINT_COMMAND := pylint $(PYLINT_IGNORE) --rcfile pylint_cfg.ini

.PHONY: pylint
pylint: .lint_src .lint_cli .lint_util
pylint: .lint_src .lint_vmodl .lint_cli .lint_util

.lint_src: $(PY_SRC)
$(PYLINT_COMMAND) ../esx_service
@touch $@

.lint_vmodl: $(PY_VMODL)
$(PYLINT_COMMAND) ../esx_service/vmodl
@touch $@

.lint_cli: $(PY_CLI)
$(PYLINT_COMMAND) cli
@touch $@
Expand Down
101 changes: 71 additions & 30 deletions esx_service/cli/vmdkops_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import auth_api

NOT_AVAILABLE = 'N/A'
UNSET = "Unset"

def main():
log_config.configure()
Expand Down Expand Up @@ -280,6 +281,22 @@ def commands():
}
},

'replace': {
'help': 'Replace VM(s) for a tenant',
'func': tenant_vm_replace,
'args': {
'--name': {
'help': "Tenant to replace the VM for",
'required': True
},
'--vm-list': {
'help': "A list of VM names to replace for this Tenant",
'type': comma_seperated_string,
'required': True
}
}
},

'ls': {
'help': "list VMs in a tenant",
'func': tenant_vm_ls,
Expand Down Expand Up @@ -312,7 +329,7 @@ def commands():
'action': 'store_true'
},
'--allow-create': {
'help': 'Allow create and delete on datastore if set to True',
'help': 'Allow create and delete on datastore if set',
'action': 'store_true'
},
'--volume-maxsize': {
Expand All @@ -339,8 +356,8 @@ def commands():
'required': True
},
'--allow-create': {
'help': 'Allow create and delete on datastore if set to True',
'action': 'store_true'
'help':
'Allow create and delete on datastore if set to True; disallow create and delete on datastore if set to False',
},
'--volume-maxsize': {
'help': 'Maximum size of the volume that can be created',
Expand Down Expand Up @@ -767,15 +784,14 @@ def tenant_ls_headers():
headers = ['Uuid', 'Name', 'Description', 'Default_datastore', 'VM_list']
return headers

def generate_vm_list(vms):
def generate_vm_list(vms_uuid):
""" Generate vm names with given list of vm uuid"""
# vms is a list of (vm_uuid)
# example: vms=[("vm1_uuid"), ("vm2_uuid")]
# vms_uuid is a list of vm_uuid
# example: vms_uuid=["vm1_uuid", "vm2_uuid"]
# the return value is a string like this vm1,vm2
res = ""
for vm in vms:
# vm[0] is vm_uuid, vm has format (vm_uuid)
vm_name = vmdk_utils.get_vm_name_by_uuid(vm[0])
for vm_uuid in vms_uuid:
vm_name = vmdk_utils.get_vm_name_by_uuid(vm_uuid)
res = res + vm_name
res = res + ","

Expand Down Expand Up @@ -809,7 +825,7 @@ def tenant_create(args):
vm_list=args.vm_list,
privileges=[])
if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant create succeeded")

Expand All @@ -821,7 +837,7 @@ def tenant_update(args):
default_datastore=args.default_datastore)

if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant modify succeeded")

Expand All @@ -837,15 +853,15 @@ def tenant_rm(args):
error_info = auth_api._tenant_rm(args.name, remove_volumes)

if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant rm succeeded")

def tenant_ls(args):
""" Handle tenant ls command """
error_info, tenant_list = auth_api._tenant_ls()
if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)

header = tenant_ls_headers()
rows = generate_tenant_ls_rows(tenant_list)
Expand All @@ -856,7 +872,7 @@ def tenant_vm_add(args):
error_info = auth_api._tenant_vm_add(args.name, args.vm_list)

if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant vm add succeeded")

Expand All @@ -865,10 +881,19 @@ def tenant_vm_rm(args):
error_info = auth_api._tenant_vm_rm(args.name, args.vm_list)

if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant vm rm succeeded")

def tenant_vm_replace(args):
""" Handle tenant vm replace command """
error_info = auth_api._tenant_vm_replace(args.name, args.vm_list)

if error_info:
return operation_fail(error_info.msg)
else:
print("tenant vm replace succeeded")

def tenant_vm_ls_headers():
""" Return column names for tenant vm ls command """
headers = ['Uuid', 'Name']
Expand All @@ -879,7 +904,7 @@ def generate_tenant_vm_ls_rows(vms):
rows = []
for vm in vms:
# vm has the format like this (vm_uuid)
uuid = vm[0]
uuid = vm
name = vmdk_utils.get_vm_name_by_uuid(uuid)
rows.append([uuid, name])

Expand All @@ -889,7 +914,7 @@ def tenant_vm_ls(args):
""" Handle tenant vm ls command """
error_info, vms = auth_api._tenant_vm_ls(args.name)
if error_info:
return error_info
return operation_fail(error_info.msg)

header = tenant_vm_ls_headers()
rows = generate_tenant_vm_ls_rows(vms)
Expand All @@ -899,37 +924,51 @@ def tenant_vm_ls(args):

def tenant_access_add(args):
""" Handle tenant access command """
volume_maxsize_in_MB = None
volume_totalsize_in_MB = None
if args.volume_maxsize:
volume_maxsize_in_MB = convert.convert_to_MB(args.volume_maxsize)
if args.volume_totalsize:
volume_totalsize_in_MB = convert.convert_to_MB(args.volume_totalsize)

error_info = auth_api._tenant_access_add(name=args.name,
datastore=args.datastore,
default_datastore=args.default_datastore,
allow_create=args.allow_create,
volume_maxsize=args.volume_maxsize,
volume_totalsize=args.volume_totalsize
volume_maxsize_in_MB=volume_maxsize_in_MB,
volume_totalsize_in_MB=volume_totalsize_in_MB
)

if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant access add succeeded")

def tenant_access_set(args):
""" Handle tenant access set command """
volume_maxsize_in_MB = None
volume_totalsize_in_MB = None
if args.volume_maxsize:
volume_maxsize_in_MB = convert.convert_to_MB(args.volume_maxsize)
if args.volume_totalsize:
volume_totalsize_in_MB = convert.convert_to_MB(args.volume_totalsize)

error_info = auth_api._tenant_access_set(name=args.name,
datastore=args.datastore,
allow_create=args.allow_create,
volume_maxsize=args.volume_maxsize,
volume_totalsize=args.volume_totalsize)
volume_maxsize_in_MB=volume_maxsize_in_MB,
volume_totalsize_in_MB=volume_totalsize_in_MB)

if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant access set succeeded")

def tenant_access_rm(args):
""" Handle tenant access rm command """
error_info = auth_api._tenant_access_rm(args.name, args.datastore)
if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)
else:
print("tenant access rm succeeded")

Expand All @@ -942,13 +981,15 @@ def generate_tenant_access_ls_rows(privileges):
""" Generate output for tenant access ls command """
rows = []
for p in privileges:
datastore_url = p[auth_data_const.COL_DATASTORE_URL]
datastore = vmdk_utils.get_datastore_name(datastore_url)
allow_create = ("False", "True")[p[auth_data_const.COL_ALLOW_CREATE]]
if not p.datastore_url or p.datastore_url == auth.DEFAULT_DS_URL:
datastore = ""
else:
datastore = vmdk_utils.get_datastore_name(p.datastore_url)
allow_create = ("False", "True")[p.allow_create]
# p[auth_data_const.COL_MAX_VOLUME_SIZE] is max_volume_size in MB
max_vol_size = "Unset" if p[auth_data_const.COL_MAX_VOLUME_SIZE] == 0 else human_readable(p[auth_data_const.COL_MAX_VOLUME_SIZE]*MB)
max_vol_size = UNSET if p.max_volume_size == 0 else human_readable(p.max_volume_size * MB)
# p[auth_data_const.COL_USAGE_QUOTA] is total_size in MB
total_size = "Unset" if p[auth_data_const.COL_USAGE_QUOTA] == 0 else human_readable(p[auth_data_const.COL_USAGE_QUOTA]*MB)
total_size = UNSET if p.usage_quota == 0 else human_readable(p.usage_quota * MB)
rows.append([datastore, allow_create, max_vol_size, total_size])

return rows
Expand All @@ -958,7 +999,7 @@ def tenant_access_ls(args):
name = args.name
error_info, privileges = auth_api._tenant_access_ls(name)
if error_info:
return operation_fail(error_info)
return operation_fail(error_info.msg)

header = tenant_access_ls_headers()
rows = generate_tenant_access_ls_rows(privileges)
Expand Down
38 changes: 27 additions & 11 deletions esx_service/cli/vmdkops_admin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import auth_api
import log_config
import logging
import convert

# Number of expected columns in ADMIN_CLI ls
EXPECTED_COLUMN_COUNT = 12
Expand Down Expand Up @@ -192,12 +193,21 @@ def test_tenant_access_add_missing_option_fails(self):
def test_tenant_access_add_invalid_option_fails(self):
self.assert_parse_error('tenant access add --name=tenant1 --datastore=datastore1 --rights=create mount')

def test_tenant_accss_set(self):
args = self.parser.parse_args('tenant access set --name=tenant1 --datastore=datastore1 --allow-create --volume-maxsize=500MB --volume-totalsize=1GB'.split())
def test_tenant_access_set(self):
args = self.parser.parse_args('tenant access set --name=tenant1 --datastore=datastore1 --allow-create=True --volume-maxsize=500MB --volume-totalsize=1GB'.split())
self.assertEqual(args.func, vmdkops_admin.tenant_access_set)
self.assertEqual(args.name, 'tenant1')
self.assertEqual(args.datastore, 'datastore1')
self.assertEqual(args.allow_create, True)
self.assertEqual(args.allow_create, "True")
self.assertEqual(args.volume_maxsize, '500MB')
self.assertEqual(args.volume_totalsize, '1GB')

def test_tenant_accss_set_not_set_allow_create(self):
args = self.parser.parse_args('tenant access set --name=tenant1 --datastore=datastore1 --volume-maxsize=500MB --volume-totalsize=1GB'.split())
self.assertEqual(args.func, vmdkops_admin.tenant_access_set)
self.assertEqual(args.name, 'tenant1')
self.assertEqual(args.datastore, 'datastore1')
self.assertEqual(args.allow_create, None)
self.assertEqual(args.volume_maxsize, '500MB')
self.assertEqual(args.volume_totalsize, '1GB')

Expand Down Expand Up @@ -679,17 +689,19 @@ def test_tenant_access(self):
vm_list=[self.vm1_name],
privileges=[])
self.assertEqual(None, error_info)

# add first access privilege for tenant
# allow_create = False
# max_volume size = 600MB
# total_volume size = 1GB
volume_maxsize_in_MB = convert.convert_to_MB("600MB")
volume_totalsize_in_MB = convert.convert_to_MB("1GB")
error_info = auth_api._tenant_access_add(name=self.tenant1_name,
datastore=self.datastore_name,
default_datastore=False,
allow_create=False,
volume_maxsize="600MB",
volume_totalsize="1GB"
volume_maxsize_in_MB=volume_maxsize_in_MB,
volume_totalsize_in_MB=volume_totalsize_in_MB
)
self.assertEqual(None, error_info)

Expand Down Expand Up @@ -721,11 +733,13 @@ def test_tenant_access(self):
# change allow_create to True
# change max_volume size to 1000MB
# change total_volume size to 2GB
volume_maxsize_in_MB = convert.convert_to_MB("1000MB")
volume_totalsize_in_MB = convert.convert_to_MB("3GB")
error_info = auth_api._tenant_access_set(name=self.tenant1_name,
datastore=self.datastore_name,
allow_create=True,
volume_maxsize="1000MB",
volume_totalsize="3GB"
allow_create="True",
volume_maxsize_in_MB=volume_maxsize_in_MB,
volume_totalsize_in_MB=volume_totalsize_in_MB
)
self.assertEqual(None, error_info)

Expand Down Expand Up @@ -769,12 +783,14 @@ def test_tenant_access(self):
# allow_create = False
# max_volume size = 600MB
# total_volume size = 1GB
volume_maxsize_in_MB = convert.convert_to_MB("600MB")
volume_totalsize_in_MB = convert.convert_to_MB("1GB")
error_info = auth_api._tenant_access_add(name=self.tenant1_name,
datastore=self.datastore1_name,
default_datastore=True,
allow_create=False,
volume_maxsize="600MB",
volume_totalsize="1GB"
volume_maxsize_in_MB=volume_maxsize_in_MB,
volume_totalsize_in_MB=volume_totalsize_in_MB
)
self.assertEqual(None, error_info)

Expand Down
Loading

0 comments on commit 5029c3f

Please # to comment.