[PATCH 0/5] Add users and groups to VMs

From: Aline Manera <alinefm@br.ibm.com> This patch set is based on Cristian's patches. It uses the <metadata> tag to store users and groups to the VM XML. It does not uses virDomain.setMetadata() as it does not well in some libvirt versions. Aline Manera (4): Use proper term "user name" instead of "user id" Add functions to check if a user/group exists Return users and groups when fetching VM info Add/remove users and groups to VMs Crístian Viana (1): Override only the updated "User" methods in "patch_auth" docs/API.md | 6 ++++ po/en_US.po | 55 ++++++++++++++++++++++++++++-- po/kimchi.pot | 55 ++++++++++++++++++++++++++++-- po/pt_BR.po | 63 ++++++++++++++++++++++++++++++++--- po/zh_CN.po | 62 +++++++++++++++++++++++++++++++--- src/kimchi/API.json | 22 ++++++++++++ src/kimchi/auth.py | 60 ++++++++++++++++++++++----------- src/kimchi/control/vms.py | 6 ++-- src/kimchi/i18n.py | 10 ++++-- src/kimchi/mockmodel.py | 7 ++-- src/kimchi/model/vms.py | 68 ++++++++++++++++++++++++++++++++------ src/kimchi/root.py | 4 +-- tests/test_authorization.py | 19 +++++++++++ tests/test_mockmodel.py | 2 +- tests/test_model.py | 44 +++++++++++++++++++++++- tests/test_rest.py | 10 +++--- tests/utils.py | 26 ++++----------- ui/js/src/kimchi.login_window.js | 16 ++++----- ui/js/src/kimchi.user.js | 14 ++++---- ui/pages/login-window.html.tmpl | 4 +-- 20 files changed, 457 insertions(+), 96 deletions(-) -- 1.7.10.4

From: Crístian Viana <vianac@linux.vnet.ibm.com> The function "patch_auth" creates a fake class to override the existing kimchi.auth.User with a few updated methods. That attribution overrides the entire User class along with all its methods. In order to avoid side effects, override only the methods we need to change in the "User" class. Other methods not related to "patch_auth" will not be affected now. --- tests/utils.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 0a1a967..b373f34 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -149,25 +149,12 @@ def patch_auth(sudo=True): Override the authenticate function with a simple test against an internal dict of users and passwords. """ - USER_ID = 'userid' - USER_GROUPS = 'groups' - USER_SUDO = 'sudo' - class _User(object): - def __init__(self, userid): - self.user = {} - self.user[USER_ID] = userid - self.user[USER_GROUPS] = None - self.user[USER_SUDO] = sudo + def _get_groups(self): + return None - def get_groups(self): - return self.user[USER_GROUPS] - - def has_sudo(self): - return self.user[USER_SUDO] - - def get_user(self): - return self.user + def _has_sudo(self): + return sudo def _authenticate(username, password, service="passwd"): try: @@ -178,7 +165,8 @@ def patch_auth(sudo=True): import kimchi.auth kimchi.auth.authenticate = _authenticate - kimchi.auth.User = _User + kimchi.auth.User.get_groups = _get_groups + kimchi.auth.User.has_sudo = _has_sudo def normalize_xml(xml_str): -- 1.7.10.4

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 04/11/2014 05:57 PM, Aline Manera wrote:
From: Crístian Viana <vianac@linux.vnet.ibm.com>
The function "patch_auth" creates a fake class to override the existing kimchi.auth.User with a few updated methods. That attribution overrides the entire User class along with all its methods.
In order to avoid side effects, override only the methods we need to change in the "User" class. Other methods not related to "patch_auth" will not be affected now. --- tests/utils.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-)
diff --git a/tests/utils.py b/tests/utils.py index 0a1a967..b373f34 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -149,25 +149,12 @@ def patch_auth(sudo=True): Override the authenticate function with a simple test against an internal dict of users and passwords. """ - USER_ID = 'userid' - USER_GROUPS = 'groups' - USER_SUDO = 'sudo'
- class _User(object): - def __init__(self, userid): - self.user = {} - self.user[USER_ID] = userid - self.user[USER_GROUPS] = None - self.user[USER_SUDO] = sudo + def _get_groups(self): + return None
- def get_groups(self): - return self.user[USER_GROUPS] - - def has_sudo(self): - return self.user[USER_SUDO] - - def get_user(self): - return self.user + def _has_sudo(self): + return sudo
def _authenticate(username, password, service="passwd"): try: @@ -178,7 +165,8 @@ def patch_auth(sudo=True):
import kimchi.auth kimchi.auth.authenticate = _authenticate - kimchi.auth.User = _User + kimchi.auth.User.get_groups = _get_groups + kimchi.auth.User.has_sudo = _has_sudo
def normalize_xml(xml_str):

From: Aline Manera <alinefm@br.ibm.com> In the Linux environment, a user ID is an integer code which identifies a user; a user name is the human friendly text identifier of that user. Kimchi uses both terms interchangeably. Rename all occurrences of "userid" (and its variants) to "username" (and its variants) in external UI messages and internal code. Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- po/en_US.po | 33 +++++++++++++++++++++++++++++++-- po/kimchi.pot | 33 +++++++++++++++++++++++++++++++-- po/pt_BR.po | 38 +++++++++++++++++++++++++++++++++++--- po/zh_CN.po | 36 +++++++++++++++++++++++++++++++++--- src/kimchi/auth.py | 38 +++++++++++++++++++------------------- src/kimchi/i18n.py | 2 +- src/kimchi/root.py | 4 ++-- tests/test_rest.py | 8 ++++---- tests/utils.py | 2 +- ui/js/src/kimchi.login_window.js | 16 ++++++++-------- ui/js/src/kimchi.user.js | 14 +++++++------- ui/pages/login-window.html.tmpl | 4 ++-- 12 files changed, 174 insertions(+), 54 deletions(-) diff --git a/po/en_US.po b/po/en_US.po index f2abcf0..0b05f1e 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-26 16:19-0300\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: 2013-07-11 17:32-0400\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: English\n" @@ -379,6 +379,11 @@ msgid "Create a network" msgstr "Create a network" msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "" + +msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr "" @@ -662,6 +667,9 @@ msgstr "VLAN ID" msgid "No templates found." msgstr "No templates found." +msgid "Clone" +msgstr "" + msgid "Location" msgstr "Location" @@ -797,7 +805,11 @@ msgid "Datastore is not initiated in the model object." msgstr "" #, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +msgid "Unable to start task due error: %(err)s" +msgstr "" + +#, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr "" msgid "You are not authorized to access Kimchi" @@ -1038,6 +1050,14 @@ msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "" #, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "" + +#, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage pool %(name)s already exists" msgstr "" @@ -1179,6 +1199,10 @@ msgid "" msgstr "" #, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage volume %(name)s already exists" msgstr "" @@ -1247,6 +1271,11 @@ msgid "Storage volume requires a volume name" msgstr "" #, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "" + +#, python-format msgid "Interface %(name)s does not exist" msgstr "" diff --git a/po/kimchi.pot b/po/kimchi.pot index 7b33eb3..92d5401 100755 --- a/po/kimchi.pot +++ b/po/kimchi.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-26 16:19-0300\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -373,6 +373,11 @@ msgid "Create a network" msgstr "" msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "" + +msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr "" @@ -650,6 +655,9 @@ msgstr "" msgid "No templates found." msgstr "" +msgid "Clone" +msgstr "" + msgid "Location" msgstr "" @@ -785,7 +793,11 @@ msgid "Datastore is not initiated in the model object." msgstr "" #, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +msgid "Unable to start task due error: %(err)s" +msgstr "" + +#, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr "" msgid "You are not authorized to access Kimchi" @@ -1026,6 +1038,14 @@ msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "" #, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "" + +#, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage pool %(name)s already exists" msgstr "" @@ -1167,6 +1187,10 @@ msgid "" msgstr "" #, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage volume %(name)s already exists" msgstr "" @@ -1235,6 +1259,11 @@ msgid "Storage volume requires a volume name" msgstr "" #, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "" + +#, python-format msgid "Interface %(name)s does not exist" msgstr "" diff --git a/po/pt_BR.po b/po/pt_BR.po index 4967e1d..cbb2b31 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-26 16:19-0300\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: Aline Manera <alinefm@br.ibm.com>\n" @@ -407,6 +407,14 @@ msgstr "" msgid "Create a network" msgstr "Criar uma rede" +#, fuzzy +msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "" +"O storage pool não é persistente. Ao invés de desativar, essa ação vai " +"removê-lo permanentemente. Deseja continuar?" + msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr "O storage pool vai ser permanentemente removido. Deseja continuar?" @@ -695,6 +703,9 @@ msgstr "VLAN ID" msgid "No templates found." msgstr "Nenhum modelo encontrado." +msgid "Clone" +msgstr "" + msgid "Location" msgstr "Localização" @@ -829,8 +840,12 @@ msgstr "Essa API suporta apenas JSON" msgid "Datastore is not initiated in the model object." msgstr "Datastore não é inicializado no objeto modelo." -#, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +#, fuzzy, python-format +msgid "Unable to start task due error: %(err)s" +msgstr "Não foi possível iniciar a máquina virtual %(name)s. Detalhes: %(err)s" + +#, fuzzy, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr "O usuário '%(userid)s' falhou na autenticação. [Error code: %(code)s]" msgid "You are not authorized to access Kimchi" @@ -1092,6 +1107,14 @@ msgstr "" msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "O volume de storage: %(volume)s não existe no storage pool %(pool)s" +#, fuzzy, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "Não é possível criar o storage pool %(name)s.Detalhes: %(err)s" + +#, fuzzy, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "Incapaz de deletar a rede ativa %(name)s" + #, python-format msgid "Storage pool %(name)s already exists" msgstr "Storage pool %(name)s já existe" @@ -1254,6 +1277,10 @@ msgstr "" "Um grupo de volume chamado '%(name)s' já existe. Por favor, escolher outro " "nome para criar o pool lógico." +#, fuzzy, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "Incapaz de carregar as informações do repositório. Detalhes: %(err)s" + #, python-format msgid "Storage volume %(name)s already exists" msgstr "Volume de storage %(name)s já existe" @@ -1329,6 +1356,11 @@ msgstr "Formato do volume não suportado" msgid "Storage volume requires a volume name" msgstr "Volume requer um nome" +#, fuzzy, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "Incapaz de deletar o volume %(name)s. Detalhes: %(err)s" + #, python-format msgid "Interface %(name)s does not exist" msgstr "Interface %(name)s não existe" diff --git a/po/zh_CN.po b/po/zh_CN.po index 6393b83..3b3754c 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-28 15:35+0800\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" "Language-Team: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" @@ -389,6 +389,12 @@ msgstr "此操作将中断依赖此网络的虚拟机的网络连接。" msgid "Create a network" msgstr "创建一个网络" +#, fuzzy +msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "对于非持久存储池,这个操作将会永久删除存储池而不是停用。是否继续?" + msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr "这将永久删除存储池。是否继续?" @@ -669,6 +675,9 @@ msgstr "VLAN号" msgid "No templates found." msgstr "没有发现模板" +msgid "Clone" +msgstr "" + msgid "Location" msgstr "路径" @@ -803,8 +812,12 @@ msgstr "这个API仅支持JSON" msgid "Datastore is not initiated in the model object." msgstr "尚未为model对象初始化数据存储。" -#, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +#, fuzzy, python-format +msgid "Unable to start task due error: %(err)s" +msgstr "不能启动虚拟机 %(name)s. 详情:%(err)s" + +#, fuzzy, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr "用户 '%(userid)s' 验证失败。[错误代码: %(code)s]" msgid "You are not authorized to access Kimchi" @@ -1048,6 +1061,14 @@ msgstr "当模板的存储池是iscsi或scsi时,必须为模板指定一个卷 msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "存储池%(pool)s中没有存储卷%(volume)s" +#, fuzzy, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "不能创建存储池 %(name)s。详情: %(err)s" + +#, fuzzy, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "不能删除激活的网络%(name)s" + #, python-format msgid "Storage pool %(name)s already exists" msgstr "存储池%(name)s已经存在" @@ -1189,6 +1210,10 @@ msgid "" "to create the logical pool." msgstr "卷组'%(name)s'已经存在,请选择其它的名字来创建逻辑存储池。" +#, fuzzy, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "不能获取软件仓库的信息。详情:'%(err)s'" + #, python-format msgid "Storage volume %(name)s already exists" msgstr "存储卷%(name)s已经存在" @@ -1257,6 +1282,11 @@ msgstr "存储卷格式不支持" msgid "Storage volume requires a volume name" msgstr "存储卷需要名字" +#, fuzzy, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "不能删除存储卷%(name)s。详情:%(err)s" + #, python-format msgid "Interface %(name)s does not exist" msgstr "接口%(name)s不存在" diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 63e11c4..dc78ded 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -35,7 +35,7 @@ from kimchi.exception import InvalidOperation, OperationFailed from kimchi.utils import run_command -USER_ID = 'userid' +USER_NAME = 'username' USER_GROUPS = 'groups' USER_SUDO = 'sudo' REFRESH = 'robot-refresh' @@ -48,15 +48,15 @@ def debug(msg): class User(object): - def __init__(self, userid): + def __init__(self, username): self.user = {} - self.user[USER_ID] = userid + self.user[USER_NAME] = username self.user[USER_GROUPS] = None self.user[USER_SUDO] = False def get_groups(self): self.user[USER_GROUPS] = [g.gr_name for g in grp.getgrall() - if self.user[USER_ID] in g.gr_mem] + if self.user[USER_NAME] in g.gr_mem] return self.user[USER_GROUPS] def has_sudo(self): @@ -72,16 +72,16 @@ class User(object): os.setsid() fcntl.ioctl(slave, termios.TIOCSCTTY, 0) - out, err, exit = run_command(['sudo', '-l', '-U', self.user[USER_ID], + out, err, exit = run_command(['sudo', '-l', '-U', self.user[USER_NAME], 'sudo']) if exit == 0: - debug("User %s is allowed to run sudo" % self.user[USER_ID]) + debug("User %s is allowed to run sudo" % self.user[USER_NAME]) # sudo allows a wide range of configurations, such as controlling # which binaries the user can execute with sudo. # For now, we will just check whether the user is allowed to run # any command with sudo. out, err, exit = run_command(['sudo', '-l', '-U', - self.user[USER_ID]]) + self.user[USER_NAME]]) for line in out.split('\n'): if line and re.search("(ALL)", line): result.value = 1 @@ -89,9 +89,9 @@ class User(object): result.value) return debug("User %s can only run some commands with sudo" % - self.user[USER_ID]) + self.user[USER_NAME]) else: - debug("User %s is not allowed to run sudo" % self.user[USER_ID]) + debug("User %s is not allowed to run sudo" % self.user[USER_NAME]) def get_user(self): return self.user @@ -125,7 +125,7 @@ def authenticate(username, password, service="passwd"): try: auth.authenticate() except PAM.error, (resp, code): - msg_args = {'userid': username, 'code': code} + msg_args = {'username': username, 'code': code} raise OperationFailed("KCHAUTH0001E", msg_args) return True @@ -145,7 +145,7 @@ def check_auth_session(): for the user. """ cherrypy.session.acquire_lock() - session = cherrypy.session.get(USER_ID, None) + session = cherrypy.session.get(USER_NAME, None) cherrypy.session.release_lock() if session is not None: debug("Session authenticated for user %s" % session) @@ -153,7 +153,7 @@ def check_auth_session(): if kimchiRobot == "kimchi-robot": if (time.time() - cherrypy.session[REFRESH] > cherrypy.session.timeout * 60): - cherrypy.session[USER_ID] = None + cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() raise cherrypy.HTTPError(401) else: @@ -183,20 +183,20 @@ def check_auth_httpba(): b64data = re.sub("Basic ", "", authheader) decodeddata = base64.b64decode(b64data.encode("ASCII")) # TODO: test how this handles ':' characters in username/passphrase. - userid, password = decodeddata.decode().split(":", 1) + username, password = decodeddata.decode().split(":", 1) - return login(userid, password) + return login(username, password) -def login(userid, password): - if not authenticate(userid, password): +def login(username, password): + if not authenticate(username, password): debug("User cannot be verified with the supplied password") return None - user = User(userid) + user = User(username) debug("User verified, establishing session") cherrypy.session.acquire_lock() cherrypy.session.regenerate() - cherrypy.session[USER_ID] = userid + cherrypy.session[USER_NAME] = username cherrypy.session[USER_GROUPS] = user.get_groups() cherrypy.session[USER_SUDO] = user.has_sudo() cherrypy.session[REFRESH] = time.time() @@ -206,7 +206,7 @@ def login(userid, password): def logout(): cherrypy.session.acquire_lock() - cherrypy.session[USER_ID] = None + cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() cherrypy.lib.sessions.expire() diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index ae8d24e..0bde5b9 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -34,7 +34,7 @@ messages = { "KCHASYNC0001E": _("Datastore is not initiated in the model object."), "KCHASYNC0002E": _("Unable to start task due error: %(err)s"), - "KCHAUTH0001E": _("Authentication failed for user '%(userid)s'. [Error code: %(code)s]"), + "KCHAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"), "KCHAUTH0002E": _("You are not authorized to access Kimchi"), "KCHAUTH0003E": _("Specify %(item)s to login into Kimchi"), "KCHAUTH0004E": _("This operation is not allowed as you have restricted access to Kimchi."), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 9bae34a..514d75d 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -102,14 +102,14 @@ class KimchiRoot(Root): def login(self, *args): params = parse_request() try: - userid = params['userid'] + username = params['username'] password = params['password'] except KeyError, item: e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) raise cherrypy.HTTPError(400, e.message) try: - user_info = auth.login(userid, password) + user_info = auth.login(username, password) except OperationFailed: raise cherrypy.HTTPError(401) diff --git a/tests/test_rest.py b/tests/test_rest.py index cd40d55..6bce606 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -106,7 +106,7 @@ class RestTests(unittest.TestCase): # HTTP:401. Since HTTP Simple Auth is not allowed for text/html, we # need to use the login API and establish a session. user, pw = fake_user.items()[0] - req = json.dumps({'userid': user, 'password': pw}) + req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST') self.assertEquals(200, resp.status) cookie = resp.getheader('set-cookie') @@ -1367,7 +1367,7 @@ class RestTests(unittest.TestCase): self.assertEquals(200, resp.status) user, pw = fake_user.items()[0] - req = json.dumps({'userid': user, 'password': pw}) + req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(200, resp.status) @@ -1388,7 +1388,7 @@ class RestTests(unittest.TestCase): # Test REST API hdrs = {'AUTHORIZATION': ''} - req = json.dumps({'userid': 'nouser', 'password': 'badpass'}) + req = json.dumps({'username': 'nouser', 'password': 'badpass'}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(401, resp.status) @@ -1413,7 +1413,7 @@ class RestTests(unittest.TestCase): # Execute a login call user, pw = fake_user.items()[0] - req = json.dumps({'userid': user, 'password': pw}) + req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(200, resp.status) cookie = resp.getheader('set-cookie') diff --git a/tests/utils.py b/tests/utils.py index b373f34..fe03a1a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -160,7 +160,7 @@ def patch_auth(sudo=True): try: return fake_user[username] == password except KeyError, e: - raise OperationFailed("KCHAUTH0001E", {'userid': 'username', + raise OperationFailed("KCHAUTH0001E", {'username': 'username', 'code': e.message}) import kimchi.auth diff --git a/ui/js/src/kimchi.login_window.js b/ui/js/src/kimchi.login_window.js index f18981e..44e5617 100644 --- a/ui/js/src/kimchi.login_window.js +++ b/ui/js/src/kimchi.login_window.js @@ -56,10 +56,10 @@ kimchi.login_main = function() { return; } - var userName = kimchi.user.getUserID(); - userName && $('#user-id').val(userName); + var userName = kimchi.user.getUserName(); + userName && $('#username').val(userName); - var nodeToFocus = ! $('#user-id').val() ? $('#user-id') : + var nodeToFocus = ! $('#username').val() ? $('#username') : (! $('#password').val() ? $('#password') : $('#btn-login')); $(nodeToFocus).focus(); @@ -67,16 +67,16 @@ kimchi.login_main = function() { var login = function(event) { - if (!validateNonEmpty(['user-id', 'password'])) { + if (!validateNonEmpty(['username', 'password'])) { return false; } $('#btn-login').text(i18n['KCHAUTH6002M']).prop('disabled', true); - var userID = $('#user-id').val(); - userID && kimchi.user.setUserID(userID); + var userName = $('#username').val(); + userName && kimchi.user.setUserName(userName); var settings = { - userid: userID, + username: userName, password: $("#password").val() }; @@ -96,7 +96,7 @@ kimchi.login_main = function() { }, function() { kimchi.message.error.code('KCHAUTH6001E'); $('#btn-login').prop('disabled', false).text(i18n['KCHAUTH6001M']); - placeCursor('user-id'); + placeCursor('username'); }); return false; diff --git a/ui/js/src/kimchi.user.js b/ui/js/src/kimchi.user.js index bd7d20b..9134849 100644 --- a/ui/js/src/kimchi.user.js +++ b/ui/js/src/kimchi.user.js @@ -16,17 +16,17 @@ * limitations under the License. */ kimchi.user = (function() { - var getUserID = function() { - return kimchi.cookie.get('userid'); + var getUserName = function() { + return kimchi.cookie.get('username'); }; - var setUserID = function(userID) { - kimchi.cookie.set('userid', userID, 365); + var setUserName = function(userName) { + kimchi.cookie.set('username', userName, 365); }; var showUser = function(toShow) { if (toShow) { - var userName = getUserID(); + var userName = getUserName(); userName && $('#user-name').text(userName); $('#user').removeClass('not-logged-in'); return; @@ -36,8 +36,8 @@ kimchi.user = (function() { }; return { - getUserID: getUserID, - setUserID: setUserID, + getUserName: getUserName, + setUserName: setUserName, showUser: showUser }; })(); diff --git a/ui/pages/login-window.html.tmpl b/ui/pages/login-window.html.tmpl index b7850e5..3e451c4 100644 --- a/ui/pages/login-window.html.tmpl +++ b/ui/pages/login-window.html.tmpl @@ -34,8 +34,8 @@ <div class="content login-panel"> <form id="form-login" action="/login" method="POST"> <div class="row"> - <input type="text" id="user-id" name="userid" required="required" placeholder="$_("User Name")" /> - <div id="user-id-msg" class="msg-required"></div> + <input type="text" id="username" name="username" required="required" placeholder="$_("User Name")" /> + <div id="username-msg" class="msg-required"></div> </div> <div class="row"> <input type="password" id="password" name="password" required="required" placeholder="$_("Password")" /> -- 1.7.10.4

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 04/11/2014 05:57 PM, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
In the Linux environment, a user ID is an integer code which identifies a user; a user name is the human friendly text identifier of that user. Kimchi uses both terms interchangeably.
Rename all occurrences of "userid" (and its variants) to "username" (and its variants) in external UI messages and internal code.
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- po/en_US.po | 33 +++++++++++++++++++++++++++++++-- po/kimchi.pot | 33 +++++++++++++++++++++++++++++++-- po/pt_BR.po | 38 +++++++++++++++++++++++++++++++++++--- po/zh_CN.po | 36 +++++++++++++++++++++++++++++++++--- src/kimchi/auth.py | 38 +++++++++++++++++++------------------- src/kimchi/i18n.py | 2 +- src/kimchi/root.py | 4 ++-- tests/test_rest.py | 8 ++++---- tests/utils.py | 2 +- ui/js/src/kimchi.login_window.js | 16 ++++++++-------- ui/js/src/kimchi.user.js | 14 +++++++------- ui/pages/login-window.html.tmpl | 4 ++-- 12 files changed, 174 insertions(+), 54 deletions(-)
diff --git a/po/en_US.po b/po/en_US.po index f2abcf0..0b05f1e 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-26 16:19-0300\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: 2013-07-11 17:32-0400\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: English\n" @@ -379,6 +379,11 @@ msgid "Create a network" msgstr "Create a network"
msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "" + +msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr ""
@@ -662,6 +667,9 @@ msgstr "VLAN ID" msgid "No templates found." msgstr "No templates found."
+msgid "Clone" +msgstr "" + msgid "Location" msgstr "Location"
@@ -797,7 +805,11 @@ msgid "Datastore is not initiated in the model object." msgstr ""
#, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +msgid "Unable to start task due error: %(err)s" +msgstr "" + +#, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr ""
msgid "You are not authorized to access Kimchi" @@ -1038,6 +1050,14 @@ msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr ""
#, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "" + +#, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage pool %(name)s already exists" msgstr ""
@@ -1179,6 +1199,10 @@ msgid "" msgstr ""
#, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage volume %(name)s already exists" msgstr ""
@@ -1247,6 +1271,11 @@ msgid "Storage volume requires a volume name" msgstr ""
#, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "" + +#, python-format msgid "Interface %(name)s does not exist" msgstr ""
diff --git a/po/kimchi.pot b/po/kimchi.pot index 7b33eb3..92d5401 100755 --- a/po/kimchi.pot +++ b/po/kimchi.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-26 16:19-0300\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -373,6 +373,11 @@ msgid "Create a network" msgstr ""
msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "" + +msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr ""
@@ -650,6 +655,9 @@ msgstr "" msgid "No templates found." msgstr ""
+msgid "Clone" +msgstr "" + msgid "Location" msgstr ""
@@ -785,7 +793,11 @@ msgid "Datastore is not initiated in the model object." msgstr ""
#, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +msgid "Unable to start task due error: %(err)s" +msgstr "" + +#, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr ""
msgid "You are not authorized to access Kimchi" @@ -1026,6 +1038,14 @@ msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr ""
#, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "" + +#, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage pool %(name)s already exists" msgstr ""
@@ -1167,6 +1187,10 @@ msgid "" msgstr ""
#, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "" + +#, python-format msgid "Storage volume %(name)s already exists" msgstr ""
@@ -1235,6 +1259,11 @@ msgid "Storage volume requires a volume name" msgstr ""
#, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "" + +#, python-format msgid "Interface %(name)s does not exist" msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po index 4967e1d..cbb2b31 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-26 16:19-0300\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: Aline Manera <alinefm@br.ibm.com>\n" @@ -407,6 +407,14 @@ msgstr "" msgid "Create a network" msgstr "Criar uma rede"
+#, fuzzy +msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "" +"O storage pool não é persistente. Ao invés de desativar, essa ação vai " +"removê-lo permanentemente. Deseja continuar?" + msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr "O storage pool vai ser permanentemente removido. Deseja continuar?" @@ -695,6 +703,9 @@ msgstr "VLAN ID" msgid "No templates found." msgstr "Nenhum modelo encontrado."
+msgid "Clone" +msgstr "" + msgid "Location" msgstr "Localização"
@@ -829,8 +840,12 @@ msgstr "Essa API suporta apenas JSON" msgid "Datastore is not initiated in the model object." msgstr "Datastore não é inicializado no objeto modelo."
-#, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +#, fuzzy, python-format +msgid "Unable to start task due error: %(err)s" +msgstr "Não foi possível iniciar a máquina virtual %(name)s. Detalhes: %(err)s" + +#, fuzzy, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr "O usuário '%(userid)s' falhou na autenticação. [Error code: %(code)s]"
msgid "You are not authorized to access Kimchi" @@ -1092,6 +1107,14 @@ msgstr "" msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "O volume de storage: %(volume)s não existe no storage pool %(pool)s"
+#, fuzzy, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "Não é possível criar o storage pool %(name)s.Detalhes: %(err)s" + +#, fuzzy, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "Incapaz de deletar a rede ativa %(name)s" + #, python-format msgid "Storage pool %(name)s already exists" msgstr "Storage pool %(name)s já existe" @@ -1254,6 +1277,10 @@ msgstr "" "Um grupo de volume chamado '%(name)s' já existe. Por favor, escolher outro " "nome para criar o pool lógico."
+#, fuzzy, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "Incapaz de carregar as informações do repositório. Detalhes: %(err)s" + #, python-format msgid "Storage volume %(name)s already exists" msgstr "Volume de storage %(name)s já existe" @@ -1329,6 +1356,11 @@ msgstr "Formato do volume não suportado" msgid "Storage volume requires a volume name" msgstr "Volume requer um nome"
+#, fuzzy, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "Incapaz de deletar o volume %(name)s. Detalhes: %(err)s" + #, python-format msgid "Interface %(name)s does not exist" msgstr "Interface %(name)s não existe" diff --git a/po/zh_CN.po b/po/zh_CN.po index 6393b83..3b3754c 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-03-28 15:35+0800\n" +"POT-Creation-Date: 2014-04-09 17:11-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" "Language-Team: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" @@ -389,6 +389,12 @@ msgstr "此操作将中断依赖此网络的虚拟机的网络连接。" msgid "Create a network" msgstr "创建一个网络"
+#, fuzzy +msgid "" +"This network is not persistent. Instead of stop, this action will " +"permanently delete it. Would you like to continue?" +msgstr "对于非持久存储池,这个操作将会永久删除存储池而不是停用。是否继续?" + msgid "" "This will permanently delete the storage pool. Would you like to continue?" msgstr "这将永久删除存储池。是否继续?" @@ -669,6 +675,9 @@ msgstr "VLAN号" msgid "No templates found." msgstr "没有发现模板"
+msgid "Clone" +msgstr "" + msgid "Location" msgstr "路径"
@@ -803,8 +812,12 @@ msgstr "这个API仅支持JSON" msgid "Datastore is not initiated in the model object." msgstr "尚未为model对象初始化数据存储。"
-#, python-format -msgid "Authentication failed for user '%(userid)s'. [Error code: %(code)s]" +#, fuzzy, python-format +msgid "Unable to start task due error: %(err)s" +msgstr "不能启动虚拟机 %(name)s. 详情:%(err)s" + +#, fuzzy, python-format +msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" msgstr "用户 '%(userid)s' 验证失败。[错误代码: %(code)s]"
msgid "You are not authorized to access Kimchi" @@ -1048,6 +1061,14 @@ msgstr "当模板的存储池是iscsi或scsi时,必须为模板指定一个卷 msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "存储池%(pool)s中没有存储卷%(volume)s"
+#, fuzzy, python-format +msgid "Unable to create template due error: %(err)s" +msgstr "不能创建存储池 %(name)s。详情: %(err)s" + +#, fuzzy, python-format +msgid "Unable to delete template due error: %(err)s" +msgstr "不能删除激活的网络%(name)s" + #, python-format msgid "Storage pool %(name)s already exists" msgstr "存储池%(name)s已经存在" @@ -1189,6 +1210,10 @@ msgid "" "to create the logical pool." msgstr "卷组'%(name)s'已经存在,请选择其它的名字来创建逻辑存储池。"
+#, fuzzy, python-format +msgid "Unable to update database with deep scan information due error: %(err)s" +msgstr "不能获取软件仓库的信息。详情:'%(err)s'" + #, python-format msgid "Storage volume %(name)s already exists" msgstr "存储卷%(name)s已经存在" @@ -1257,6 +1282,11 @@ msgstr "存储卷格式不支持" msgid "Storage volume requires a volume name" msgstr "存储卷需要名字"
+#, fuzzy, python-format +msgid "" +"Unable to update database with storage volume information due error: %(err)s" +msgstr "不能删除存储卷%(name)s。详情:%(err)s" + #, python-format msgid "Interface %(name)s does not exist" msgstr "接口%(name)s不存在" diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index 63e11c4..dc78ded 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -35,7 +35,7 @@ from kimchi.exception import InvalidOperation, OperationFailed from kimchi.utils import run_command
-USER_ID = 'userid' +USER_NAME = 'username' USER_GROUPS = 'groups' USER_SUDO = 'sudo' REFRESH = 'robot-refresh' @@ -48,15 +48,15 @@ def debug(msg):
class User(object):
- def __init__(self, userid): + def __init__(self, username): self.user = {} - self.user[USER_ID] = userid + self.user[USER_NAME] = username self.user[USER_GROUPS] = None self.user[USER_SUDO] = False
def get_groups(self): self.user[USER_GROUPS] = [g.gr_name for g in grp.getgrall() - if self.user[USER_ID] in g.gr_mem] + if self.user[USER_NAME] in g.gr_mem] return self.user[USER_GROUPS]
def has_sudo(self): @@ -72,16 +72,16 @@ class User(object): os.setsid() fcntl.ioctl(slave, termios.TIOCSCTTY, 0)
- out, err, exit = run_command(['sudo', '-l', '-U', self.user[USER_ID], + out, err, exit = run_command(['sudo', '-l', '-U', self.user[USER_NAME], 'sudo']) if exit == 0: - debug("User %s is allowed to run sudo" % self.user[USER_ID]) + debug("User %s is allowed to run sudo" % self.user[USER_NAME]) # sudo allows a wide range of configurations, such as controlling # which binaries the user can execute with sudo. # For now, we will just check whether the user is allowed to run # any command with sudo. out, err, exit = run_command(['sudo', '-l', '-U', - self.user[USER_ID]]) + self.user[USER_NAME]]) for line in out.split('\n'): if line and re.search("(ALL)", line): result.value = 1 @@ -89,9 +89,9 @@ class User(object): result.value) return debug("User %s can only run some commands with sudo" % - self.user[USER_ID]) + self.user[USER_NAME]) else: - debug("User %s is not allowed to run sudo" % self.user[USER_ID]) + debug("User %s is not allowed to run sudo" % self.user[USER_NAME])
def get_user(self): return self.user @@ -125,7 +125,7 @@ def authenticate(username, password, service="passwd"): try: auth.authenticate() except PAM.error, (resp, code): - msg_args = {'userid': username, 'code': code} + msg_args = {'username': username, 'code': code} raise OperationFailed("KCHAUTH0001E", msg_args)
return True @@ -145,7 +145,7 @@ def check_auth_session(): for the user. """ cherrypy.session.acquire_lock() - session = cherrypy.session.get(USER_ID, None) + session = cherrypy.session.get(USER_NAME, None) cherrypy.session.release_lock() if session is not None: debug("Session authenticated for user %s" % session) @@ -153,7 +153,7 @@ def check_auth_session(): if kimchiRobot == "kimchi-robot": if (time.time() - cherrypy.session[REFRESH] > cherrypy.session.timeout * 60): - cherrypy.session[USER_ID] = None + cherrypy.session[USER_NAME] = None cherrypy.lib.sessions.expire() raise cherrypy.HTTPError(401) else: @@ -183,20 +183,20 @@ def check_auth_httpba(): b64data = re.sub("Basic ", "", authheader) decodeddata = base64.b64decode(b64data.encode("ASCII")) # TODO: test how this handles ':' characters in username/passphrase. - userid, password = decodeddata.decode().split(":", 1) + username, password = decodeddata.decode().split(":", 1)
- return login(userid, password) + return login(username, password)
-def login(userid, password): - if not authenticate(userid, password): +def login(username, password): + if not authenticate(username, password): debug("User cannot be verified with the supplied password") return None - user = User(userid) + user = User(username) debug("User verified, establishing session") cherrypy.session.acquire_lock() cherrypy.session.regenerate() - cherrypy.session[USER_ID] = userid + cherrypy.session[USER_NAME] = username cherrypy.session[USER_GROUPS] = user.get_groups() cherrypy.session[USER_SUDO] = user.has_sudo() cherrypy.session[REFRESH] = time.time() @@ -206,7 +206,7 @@ def login(userid, password):
def logout(): cherrypy.session.acquire_lock() - cherrypy.session[USER_ID] = None + cherrypy.session[USER_NAME] = None cherrypy.session[REFRESH] = 0 cherrypy.session.release_lock() cherrypy.lib.sessions.expire() diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index ae8d24e..0bde5b9 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -34,7 +34,7 @@ messages = { "KCHASYNC0001E": _("Datastore is not initiated in the model object."), "KCHASYNC0002E": _("Unable to start task due error: %(err)s"),
- "KCHAUTH0001E": _("Authentication failed for user '%(userid)s'. [Error code: %(code)s]"), + "KCHAUTH0001E": _("Authentication failed for user '%(username)s'. [Error code: %(code)s]"), "KCHAUTH0002E": _("You are not authorized to access Kimchi"), "KCHAUTH0003E": _("Specify %(item)s to login into Kimchi"), "KCHAUTH0004E": _("This operation is not allowed as you have restricted access to Kimchi."), diff --git a/src/kimchi/root.py b/src/kimchi/root.py index 9bae34a..514d75d 100644 --- a/src/kimchi/root.py +++ b/src/kimchi/root.py @@ -102,14 +102,14 @@ class KimchiRoot(Root): def login(self, *args): params = parse_request() try: - userid = params['userid'] + username = params['username'] password = params['password'] except KeyError, item: e = MissingParameter('KCHAUTH0003E', {'item': str(item)}) raise cherrypy.HTTPError(400, e.message)
try: - user_info = auth.login(userid, password) + user_info = auth.login(username, password) except OperationFailed: raise cherrypy.HTTPError(401)
diff --git a/tests/test_rest.py b/tests/test_rest.py index cd40d55..6bce606 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -106,7 +106,7 @@ class RestTests(unittest.TestCase): # HTTP:401. Since HTTP Simple Auth is not allowed for text/html, we # need to use the login API and establish a session. user, pw = fake_user.items()[0] - req = json.dumps({'userid': user, 'password': pw}) + req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST') self.assertEquals(200, resp.status) cookie = resp.getheader('set-cookie') @@ -1367,7 +1367,7 @@ class RestTests(unittest.TestCase): self.assertEquals(200, resp.status)
user, pw = fake_user.items()[0] - req = json.dumps({'userid': user, 'password': pw}) + req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(200, resp.status)
@@ -1388,7 +1388,7 @@ class RestTests(unittest.TestCase):
# Test REST API hdrs = {'AUTHORIZATION': ''} - req = json.dumps({'userid': 'nouser', 'password': 'badpass'}) + req = json.dumps({'username': 'nouser', 'password': 'badpass'}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(401, resp.status)
@@ -1413,7 +1413,7 @@ class RestTests(unittest.TestCase):
# Execute a login call user, pw = fake_user.items()[0] - req = json.dumps({'userid': user, 'password': pw}) + req = json.dumps({'username': user, 'password': pw}) resp = self.request('/login', req, 'POST', hdrs) self.assertEquals(200, resp.status) cookie = resp.getheader('set-cookie') diff --git a/tests/utils.py b/tests/utils.py index b373f34..fe03a1a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -160,7 +160,7 @@ def patch_auth(sudo=True): try: return fake_user[username] == password except KeyError, e: - raise OperationFailed("KCHAUTH0001E", {'userid': 'username', + raise OperationFailed("KCHAUTH0001E", {'username': 'username', 'code': e.message})
import kimchi.auth diff --git a/ui/js/src/kimchi.login_window.js b/ui/js/src/kimchi.login_window.js index f18981e..44e5617 100644 --- a/ui/js/src/kimchi.login_window.js +++ b/ui/js/src/kimchi.login_window.js @@ -56,10 +56,10 @@ kimchi.login_main = function() { return; }
- var userName = kimchi.user.getUserID(); - userName && $('#user-id').val(userName); + var userName = kimchi.user.getUserName(); + userName && $('#username').val(userName);
- var nodeToFocus = ! $('#user-id').val() ? $('#user-id') : + var nodeToFocus = ! $('#username').val() ? $('#username') : (! $('#password').val() ? $('#password') : $('#btn-login'));
$(nodeToFocus).focus(); @@ -67,16 +67,16 @@ kimchi.login_main = function() {
var login = function(event) {
- if (!validateNonEmpty(['user-id', 'password'])) { + if (!validateNonEmpty(['username', 'password'])) { return false; }
$('#btn-login').text(i18n['KCHAUTH6002M']).prop('disabled', true);
- var userID = $('#user-id').val(); - userID && kimchi.user.setUserID(userID); + var userName = $('#username').val(); + userName && kimchi.user.setUserName(userName); var settings = { - userid: userID, + username: userName, password: $("#password").val() };
@@ -96,7 +96,7 @@ kimchi.login_main = function() { }, function() { kimchi.message.error.code('KCHAUTH6001E'); $('#btn-login').prop('disabled', false).text(i18n['KCHAUTH6001M']); - placeCursor('user-id'); + placeCursor('username'); });
return false; diff --git a/ui/js/src/kimchi.user.js b/ui/js/src/kimchi.user.js index bd7d20b..9134849 100644 --- a/ui/js/src/kimchi.user.js +++ b/ui/js/src/kimchi.user.js @@ -16,17 +16,17 @@ * limitations under the License. */ kimchi.user = (function() { - var getUserID = function() { - return kimchi.cookie.get('userid'); + var getUserName = function() { + return kimchi.cookie.get('username'); };
- var setUserID = function(userID) { - kimchi.cookie.set('userid', userID, 365); + var setUserName = function(userName) { + kimchi.cookie.set('username', userName, 365); };
var showUser = function(toShow) { if (toShow) { - var userName = getUserID(); + var userName = getUserName(); userName && $('#user-name').text(userName); $('#user').removeClass('not-logged-in'); return; @@ -36,8 +36,8 @@ kimchi.user = (function() { };
return { - getUserID: getUserID, - setUserID: setUserID, + getUserName: getUserName, + setUserName: setUserName, showUser: showUser }; })(); diff --git a/ui/pages/login-window.html.tmpl b/ui/pages/login-window.html.tmpl index b7850e5..3e451c4 100644 --- a/ui/pages/login-window.html.tmpl +++ b/ui/pages/login-window.html.tmpl @@ -34,8 +34,8 @@ <div class="content login-panel"> <form id="form-login" action="/login" method="POST"> <div class="row"> - <input type="text" id="user-id" name="userid" required="required" placeholder="$_("User Name")" /> - <div id="user-id-msg" class="msg-required"></div> + <input type="text" id="username" name="username" required="required" placeholder="$_("User Name")" /> + <div id="username-msg" class="msg-required"></div> </div> <div class="row"> <input type="password" id="password" name="password" required="required" placeholder="$_("Password")" />

From: Aline Manera <alinefm@br.ibm.com> The user/group validation is done on the current system. Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- src/kimchi/auth.py | 22 ++++++++++++++++++++++ tests/test_authorization.py | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..2186987 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -25,6 +25,7 @@ import multiprocessing import os import PAM import pty +import pwd import re import termios import time @@ -96,6 +97,27 @@ class User(object): def get_user(self): return self.user + def exists(self): + try: + pwd.getpwnam(self.user[USER_NAME]) + except KeyError: + return False + else: + return True + + +class Group(object): + def __init__(self, groupname): + self.groupname = groupname + + def exists(self): + try: + grp.getgrnam(self.groupname) + except KeyError: + return False + else: + return True + def authenticate(username, password, service="passwd"): '''Returns True if authenticate is OK via PAM.''' diff --git a/tests/test_authorization.py b/tests/test_authorization.py index b211e06..ab98987 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -17,14 +17,17 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +import grp import json import os +import pwd import unittest from functools import partial +import kimchi.auth import kimchi.mockmodel from utils import get_free_port, patch_auth, request from utils import run_server @@ -119,3 +122,19 @@ class AuthorizationTests(unittest.TestCase): self.assertEquals(403, resp.status) resp = self.request('/vms', '{}', 'DELETE') self.assertEquals(403, resp.status) + + +class CurrentUserGroupTests(unittest.TestCase): + def test_current_user(self): + current_user = pwd.getpwuid(os.getuid()).pw_name + self.assertTrue(kimchi.auth.User(current_user).exists()) + + invalid_user = "userdoesnotexist" + self.assertFalse(kimchi.auth.User(invalid_user).exists()) + + def test_current_group(self): + current_group = grp.getgrgid(os.getgid()).gr_name + self.assertTrue(kimchi.auth.Group(current_group).exists()) + + invalid_group = "groupdoesnotexist" + self.assertFalse(kimchi.auth.Group(invalid_group).exists()) -- 1.7.10.4

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 04/11/2014 05:57 PM, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
The user/group validation is done on the current system.
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- src/kimchi/auth.py | 22 ++++++++++++++++++++++ tests/test_authorization.py | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py index dc78ded..2186987 100644 --- a/src/kimchi/auth.py +++ b/src/kimchi/auth.py @@ -25,6 +25,7 @@ import multiprocessing import os import PAM import pty +import pwd import re import termios import time @@ -96,6 +97,27 @@ class User(object): def get_user(self): return self.user
+ def exists(self): + try: + pwd.getpwnam(self.user[USER_NAME]) + except KeyError: + return False + else: + return True + + +class Group(object): + def __init__(self, groupname): + self.groupname = groupname + + def exists(self): + try: + grp.getgrnam(self.groupname) + except KeyError: + return False + else: + return True +
def authenticate(username, password, service="passwd"): '''Returns True if authenticate is OK via PAM.''' diff --git a/tests/test_authorization.py b/tests/test_authorization.py index b211e06..ab98987 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -17,14 +17,17 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import grp import json import os +import pwd import unittest
from functools import partial
+import kimchi.auth import kimchi.mockmodel from utils import get_free_port, patch_auth, request from utils import run_server @@ -119,3 +122,19 @@ class AuthorizationTests(unittest.TestCase): self.assertEquals(403, resp.status) resp = self.request('/vms', '{}', 'DELETE') self.assertEquals(403, resp.status) + + +class CurrentUserGroupTests(unittest.TestCase): + def test_current_user(self): + current_user = pwd.getpwuid(os.getuid()).pw_name + self.assertTrue(kimchi.auth.User(current_user).exists()) + + invalid_user = "userdoesnotexist" + self.assertFalse(kimchi.auth.User(invalid_user).exists()) + + def test_current_group(self): + current_group = grp.getgrgid(os.getgid()).gr_name + self.assertTrue(kimchi.auth.Group(current_group).exists()) + + invalid_group = "groupdoesnotexist" + self.assertFalse(kimchi.auth.Group(invalid_group).exists())

From: Aline Manera <alinefm@br.ibm.com> In order to have more control over what users can use a virtual machine under Kimchi, a VM should have users and groups associated with it. Kimchi uses metadata information (http://libvirt.org/formatdomain.html#elementsMetadata) to store users and groups to the virtual machines. The data is stored as string representations of Python lists. Keep in mind that these security restrictions will not apply when using other virtual machine managers (e.g. virsh, virt-manager, etc). Users and groups stored in the VM are now returned when the user queries the VM data. Currently, there is no way to associate users and groups to a VM. The REST command below now returns the users and groups of every virtual machine: /vms/:name GET: It now lists the users and groups associated with that VM. { "users":[ "user1", "user2" ], "groups":[ "group1", "group2" ], ... } If there is no metadata information about users and groups, empty lists will be displayed Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> show show --- docs/API.md | 4 ++++ src/kimchi/API.json | 22 ++++++++++++++++++++++ src/kimchi/control/vms.py | 4 +++- src/kimchi/i18n.py | 4 ++++ src/kimchi/mockmodel.py | 4 +++- src/kimchi/model/vms.py | 13 ++++++++++--- tests/test_mockmodel.py | 2 +- tests/test_model.py | 4 +++- tests/test_rest.py | 2 ++ 9 files changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/API.md b/docs/API.md index 143c70c..b726b9c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -94,6 +94,10 @@ the following general conventions: * port: The real port number of the graphics, vnc or spice. Users can use this port to connect to the vm with general vnc/spice clients. + * users: A list of system users who have permission to access the VM. + Default is: empty. + * groups: A list of system groups whose users have permission to access + the VM. Default is: empty. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 5ca94e3..6294dc8 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -201,6 +201,28 @@ "type": "string", "minLength": 1, "error": "KCHVM0011E" + }, + "users": { + "description": "Array of users who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0022E", + "items": { + "description": "User name", + "type": "string", + "error": "KCHVM0023E" + } + }, + "groups": { + "description": "Array of groups who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0024E", + "items": { + "description": "Group name", + "type": "string", + "error": "KCHVM0025E" + } } } }, diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index e75b27f..63c1e24 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], 'listen': self.info['graphics']['listen'], - 'port': self.info['graphics']['port']} + 'port': self.info['graphics']['port']}, + 'users': self.info['users'], + 'groups': self.info['groups'] } diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 0bde5b9..20f9716 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -80,6 +80,10 @@ messages = { "KCHVM0019E": _("Unable to start virtual machine %(name)s. Details: %(err)s"), "KCHVM0020E": _("Unable to power off virtual machine %(name)s. Details: %(err)s"), "KCHVM0021E": _("Unable to delete virtual machine %(name)s. Details: %(err)s"), + "KCHVM0022E": _("User names list must be an array"), + "KCHVM0023E": _("User name must be a string"), + "KCHVM0024E": _("Group names list must be an array"), + "KCHVM0025E": _("Group name must be a string"), "KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index bf17975..87ca21c 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -969,7 +969,9 @@ class MockVM(object): 'cpus': template_info['cpus'], 'icon': None, 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', - 'port': None} + 'port': None}, + 'users': ['user1', 'user2', 'root'], + 'groups': ['group1', 'group2', 'admin'] } self.info['graphics'].update(template_info['graphics']) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 2425a43..ba091e5 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -34,8 +34,9 @@ from kimchi.model.config import CapabilitiesModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.screenshot import VMScreenshot -from kimchi.utils import kimchi_log -from kimchi.utils import run_setfacl_set_attr, template_name_from_uri +from kimchi.utils import kimchi_log, run_setfacl_set_attr +from kimchi.utils import template_name_from_uri +from kimchi.xmlutils import xpath_get_text DOM_STATE_MAP = {0: 'nostate', @@ -304,6 +305,10 @@ class VMModel(object): res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100) + xml = dom.XMLDesc(0) + users = xpath_get_text(xml, "/domain/metadata/kimchi/access/user") + groups = xpath_get_text(xml, "/domain/metadata/kimchi/access/group") + return {'state': state, 'stats': res, 'uuid': dom.UUIDString(), @@ -313,7 +318,9 @@ class VMModel(object): 'icon': icon, 'graphics': {"type": graphics_type, "listen": graphics_listen, - "port": graphics_port} + "port": graphics_port}, + 'users': users, + 'groups': groups } def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 0798701..9e258ce 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -135,7 +135,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(u'test', vms[0]) keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) diff --git a/tests/test_model.py b/tests/test_model.py index 3041196..00c02d3 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -61,7 +61,7 @@ class ModelTests(unittest.TestCase): self.assertEquals('test', vms[0]) keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) @@ -73,6 +73,8 @@ class ModelTests(unittest.TestCase): self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') + self.assertEquals([], info['users']) + self.assertEquals([], info['groups']) @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_lifecycle(self): diff --git a/tests/test_rest.py b/tests/test_rest.py index 6bce606..9c2f997 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -181,6 +181,8 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) self.assertEquals('shutoff', vm['state']) + self.assertEquals(['user1', 'user2', 'root'], vm['users']) + self.assertEquals(['group1', 'group2', 'admin'], vm['groups']) def test_edit_vm(self): req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) -- 1.7.10.4

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 04/11/2014 05:58 PM, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
In order to have more control over what users can use a virtual machine under Kimchi, a VM should have users and groups associated with it.
Kimchi uses metadata information (http://libvirt.org/formatdomain.html#elementsMetadata) to store users and groups to the virtual machines. The data is stored as string representations of Python lists. Keep in mind that these security restrictions will not apply when using other virtual machine managers (e.g. virsh, virt-manager, etc).
Users and groups stored in the VM are now returned when the user queries the VM data. Currently, there is no way to associate users and groups to a VM.
The REST command below now returns the users and groups of every virtual machine:
/vms/:name GET: It now lists the users and groups associated with that VM.
{ "users":[ "user1", "user2" ], "groups":[ "group1", "group2" ], ... }
If there is no metadata information about users and groups, empty lists will be displayed
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com>
show
show --- docs/API.md | 4 ++++ src/kimchi/API.json | 22 ++++++++++++++++++++++ src/kimchi/control/vms.py | 4 +++- src/kimchi/i18n.py | 4 ++++ src/kimchi/mockmodel.py | 4 +++- src/kimchi/model/vms.py | 13 ++++++++++--- tests/test_mockmodel.py | 2 +- tests/test_model.py | 4 +++- tests/test_rest.py | 2 ++ 9 files changed, 52 insertions(+), 7 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 143c70c..b726b9c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -94,6 +94,10 @@ the following general conventions: * port: The real port number of the graphics, vnc or spice. Users can use this port to connect to the vm with general vnc/spice clients. + * users: A list of system users who have permission to access the VM. + Default is: empty. + * groups: A list of system groups whose users have permission to access + the VM. Default is: empty. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 5ca94e3..6294dc8 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -201,6 +201,28 @@ "type": "string", "minLength": 1, "error": "KCHVM0011E" + }, + "users": { + "description": "Array of users who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0022E", + "items": { + "description": "User name", + "type": "string", + "error": "KCHVM0023E" + } + }, + "groups": { + "description": "Array of groups who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0024E", + "items": { + "description": "Group name", + "type": "string", + "error": "KCHVM0025E" + } } } }, diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index e75b27f..63c1e24 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], 'listen': self.info['graphics']['listen'], - 'port': self.info['graphics']['port']} + 'port': self.info['graphics']['port']}, + 'users': self.info['users'], + 'groups': self.info['groups'] }
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 0bde5b9..20f9716 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -80,6 +80,10 @@ messages = { "KCHVM0019E": _("Unable to start virtual machine %(name)s. Details: %(err)s"), "KCHVM0020E": _("Unable to power off virtual machine %(name)s. Details: %(err)s"), "KCHVM0021E": _("Unable to delete virtual machine %(name)s. Details: %(err)s"), + "KCHVM0022E": _("User names list must be an array"), + "KCHVM0023E": _("User name must be a string"), + "KCHVM0024E": _("Group names list must be an array"), + "KCHVM0025E": _("Group name must be a string"),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index bf17975..87ca21c 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -969,7 +969,9 @@ class MockVM(object): 'cpus': template_info['cpus'], 'icon': None, 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', - 'port': None} + 'port': None}, + 'users': ['user1', 'user2', 'root'], + 'groups': ['group1', 'group2', 'admin'] } self.info['graphics'].update(template_info['graphics'])
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 2425a43..ba091e5 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -34,8 +34,9 @@ from kimchi.model.config import CapabilitiesModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.screenshot import VMScreenshot -from kimchi.utils import kimchi_log -from kimchi.utils import run_setfacl_set_attr, template_name_from_uri +from kimchi.utils import kimchi_log, run_setfacl_set_attr +from kimchi.utils import template_name_from_uri +from kimchi.xmlutils import xpath_get_text
DOM_STATE_MAP = {0: 'nostate', @@ -304,6 +305,10 @@ class VMModel(object): res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
+ xml = dom.XMLDesc(0) + users = xpath_get_text(xml, "/domain/metadata/kimchi/access/user") + groups = xpath_get_text(xml, "/domain/metadata/kimchi/access/group") + return {'state': state, 'stats': res, 'uuid': dom.UUIDString(), @@ -313,7 +318,9 @@ class VMModel(object): 'icon': icon, 'graphics': {"type": graphics_type, "listen": graphics_listen, - "port": graphics_port} + "port": graphics_port}, + 'users': users, + 'groups': groups }
def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 0798701..9e258ce 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -135,7 +135,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(u'test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) diff --git a/tests/test_model.py b/tests/test_model.py index 3041196..00c02d3 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -61,7 +61,7 @@ class ModelTests(unittest.TestCase): self.assertEquals('test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) @@ -73,6 +73,8 @@ class ModelTests(unittest.TestCase): self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') + self.assertEquals([], info['users']) + self.assertEquals([], info['groups'])
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_lifecycle(self): diff --git a/tests/test_rest.py b/tests/test_rest.py index 6bce606..9c2f997 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -181,6 +181,8 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) self.assertEquals('shutoff', vm['state']) + self.assertEquals(['user1', 'user2', 'root'], vm['users']) + self.assertEquals(['group1', 'group2', 'admin'], vm['groups'])
def test_edit_vm(self): req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'})

One comment below... On Fri, 2014-04-11 at 17:58 -0300, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
In order to have more control over what users can use a virtual machine under Kimchi, a VM should have users and groups associated with it.
Kimchi uses metadata information (http://libvirt.org/formatdomain.html#elementsMetadata) to store users and groups to the virtual machines. The data is stored as string representations of Python lists. Keep in mind that these security restrictions will not apply when using other virtual machine managers (e.g. virsh, virt-manager, etc).
Users and groups stored in the VM are now returned when the user queries the VM data. Currently, there is no way to associate users and groups to a VM.
The REST command below now returns the users and groups of every virtual machine:
/vms/:name GET: It now lists the users and groups associated with that VM.
{ "users":[ "user1", "user2" ], "groups":[ "group1", "group2" ], ... }
If there is no metadata information about users and groups, empty lists will be displayed
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com>
show
show --- docs/API.md | 4 ++++ src/kimchi/API.json | 22 ++++++++++++++++++++++ src/kimchi/control/vms.py | 4 +++- src/kimchi/i18n.py | 4 ++++ src/kimchi/mockmodel.py | 4 +++- src/kimchi/model/vms.py | 13 ++++++++++--- tests/test_mockmodel.py | 2 +- tests/test_model.py | 4 +++- tests/test_rest.py | 2 ++ 9 files changed, 52 insertions(+), 7 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 143c70c..b726b9c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -94,6 +94,10 @@ the following general conventions: * port: The real port number of the graphics, vnc or spice. Users can use this port to connect to the vm with general vnc/spice clients. + * users: A list of system users who have permission to access the VM. + Default is: empty. + * groups: A list of system groups whose users have permission to access + the VM. Default is: empty. Sorry this is a little late, but, from this, it's not obvious what the default empty list means. Does it mean that no users can access the VM, or that there are no restrictions on access? I think a simple patch to clarify will prevent confusion. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 5ca94e3..6294dc8 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -201,6 +201,28 @@ "type": "string", "minLength": 1, "error": "KCHVM0011E" + }, + "users": { + "description": "Array of users who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0022E", + "items": { + "description": "User name", + "type": "string", + "error": "KCHVM0023E" + } + }, + "groups": { + "description": "Array of groups who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0024E", + "items": { + "description": "Group name", + "type": "string", + "error": "KCHVM0025E" + } } } }, diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index e75b27f..63c1e24 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], 'listen': self.info['graphics']['listen'], - 'port': self.info['graphics']['port']} + 'port': self.info['graphics']['port']}, + 'users': self.info['users'], + 'groups': self.info['groups'] }
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 0bde5b9..20f9716 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -80,6 +80,10 @@ messages = { "KCHVM0019E": _("Unable to start virtual machine %(name)s. Details: %(err)s"), "KCHVM0020E": _("Unable to power off virtual machine %(name)s. Details: %(err)s"), "KCHVM0021E": _("Unable to delete virtual machine %(name)s. Details: %(err)s"), + "KCHVM0022E": _("User names list must be an array"), + "KCHVM0023E": _("User name must be a string"), + "KCHVM0024E": _("Group names list must be an array"), + "KCHVM0025E": _("Group name must be a string"),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index bf17975..87ca21c 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -969,7 +969,9 @@ class MockVM(object): 'cpus': template_info['cpus'], 'icon': None, 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', - 'port': None} + 'port': None}, + 'users': ['user1', 'user2', 'root'], + 'groups': ['group1', 'group2', 'admin'] } self.info['graphics'].update(template_info['graphics'])
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 2425a43..ba091e5 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -34,8 +34,9 @@ from kimchi.model.config import CapabilitiesModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.screenshot import VMScreenshot -from kimchi.utils import kimchi_log -from kimchi.utils import run_setfacl_set_attr, template_name_from_uri +from kimchi.utils import kimchi_log, run_setfacl_set_attr +from kimchi.utils import template_name_from_uri +from kimchi.xmlutils import xpath_get_text
DOM_STATE_MAP = {0: 'nostate', @@ -304,6 +305,10 @@ class VMModel(object): res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
+ xml = dom.XMLDesc(0) + users = xpath_get_text(xml, "/domain/metadata/kimchi/access/user") + groups = xpath_get_text(xml, "/domain/metadata/kimchi/access/group") + return {'state': state, 'stats': res, 'uuid': dom.UUIDString(), @@ -313,7 +318,9 @@ class VMModel(object): 'icon': icon, 'graphics': {"type": graphics_type, "listen": graphics_listen, - "port": graphics_port} + "port": graphics_port}, + 'users': users, + 'groups': groups }
def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 0798701..9e258ce 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -135,7 +135,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(u'test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) diff --git a/tests/test_model.py b/tests/test_model.py index 3041196..00c02d3 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -61,7 +61,7 @@ class ModelTests(unittest.TestCase): self.assertEquals('test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) @@ -73,6 +73,8 @@ class ModelTests(unittest.TestCase): self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') + self.assertEquals([], info['users']) + self.assertEquals([], info['groups'])
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_lifecycle(self): diff --git a/tests/test_rest.py b/tests/test_rest.py index 6bce606..9c2f997 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -181,6 +181,8 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) self.assertEquals('shutoff', vm['state']) + self.assertEquals(['user1', 'user2', 'root'], vm['users']) + self.assertEquals(['group1', 'group2', 'admin'], vm['groups'])
def test_edit_vm(self): req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'})

On 04/21/2014 12:41 PM, Christy Perez wrote:
One comment below...
On Fri, 2014-04-11 at 17:58 -0300, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
In order to have more control over what users can use a virtual machine under Kimchi, a VM should have users and groups associated with it.
Kimchi uses metadata information (http://libvirt.org/formatdomain.html#elementsMetadata) to store users and groups to the virtual machines. The data is stored as string representations of Python lists. Keep in mind that these security restrictions will not apply when using other virtual machine managers (e.g. virsh, virt-manager, etc).
Users and groups stored in the VM are now returned when the user queries the VM data. Currently, there is no way to associate users and groups to a VM.
The REST command below now returns the users and groups of every virtual machine:
/vms/:name GET: It now lists the users and groups associated with that VM.
{ "users":[ "user1", "user2" ], "groups":[ "group1", "group2" ], ... }
If there is no metadata information about users and groups, empty lists will be displayed
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com>
show
show --- docs/API.md | 4 ++++ src/kimchi/API.json | 22 ++++++++++++++++++++++ src/kimchi/control/vms.py | 4 +++- src/kimchi/i18n.py | 4 ++++ src/kimchi/mockmodel.py | 4 +++- src/kimchi/model/vms.py | 13 ++++++++++--- tests/test_mockmodel.py | 2 +- tests/test_model.py | 4 +++- tests/test_rest.py | 2 ++ 9 files changed, 52 insertions(+), 7 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 143c70c..b726b9c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -94,6 +94,10 @@ the following general conventions: * port: The real port number of the graphics, vnc or spice. Users can use this port to connect to the vm with general vnc/spice clients. + * users: A list of system users who have permission to access the VM. + Default is: empty. + * groups: A list of system groups whose users have permission to access + the VM. Default is: empty. Sorry this is a little late, but, from this, it's not obvious what the default empty list means. Does it mean that no users can access the VM, or that there are no restrictions on access? I think a simple patch to clarify will prevent confusion.
Sure. Feel free to send a patch for it, Christy! In fact, this information is not being used yet. But when the list is empty, only root (with sudo) users can manage the VM.
* **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 5ca94e3..6294dc8 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -201,6 +201,28 @@ "type": "string", "minLength": 1, "error": "KCHVM0011E" + }, + "users": { + "description": "Array of users who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0022E", + "items": { + "description": "User name", + "type": "string", + "error": "KCHVM0023E" + } + }, + "groups": { + "description": "Array of groups who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0024E", + "items": { + "description": "Group name", + "type": "string", + "error": "KCHVM0025E" + } } } }, diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index e75b27f..63c1e24 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], 'listen': self.info['graphics']['listen'], - 'port': self.info['graphics']['port']} + 'port': self.info['graphics']['port']}, + 'users': self.info['users'], + 'groups': self.info['groups'] }
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 0bde5b9..20f9716 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -80,6 +80,10 @@ messages = { "KCHVM0019E": _("Unable to start virtual machine %(name)s. Details: %(err)s"), "KCHVM0020E": _("Unable to power off virtual machine %(name)s. Details: %(err)s"), "KCHVM0021E": _("Unable to delete virtual machine %(name)s. Details: %(err)s"), + "KCHVM0022E": _("User names list must be an array"), + "KCHVM0023E": _("User name must be a string"), + "KCHVM0024E": _("Group names list must be an array"), + "KCHVM0025E": _("Group name must be a string"),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index bf17975..87ca21c 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -969,7 +969,9 @@ class MockVM(object): 'cpus': template_info['cpus'], 'icon': None, 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', - 'port': None} + 'port': None}, + 'users': ['user1', 'user2', 'root'], + 'groups': ['group1', 'group2', 'admin'] } self.info['graphics'].update(template_info['graphics'])
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 2425a43..ba091e5 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -34,8 +34,9 @@ from kimchi.model.config import CapabilitiesModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.screenshot import VMScreenshot -from kimchi.utils import kimchi_log -from kimchi.utils import run_setfacl_set_attr, template_name_from_uri +from kimchi.utils import kimchi_log, run_setfacl_set_attr +from kimchi.utils import template_name_from_uri +from kimchi.xmlutils import xpath_get_text
DOM_STATE_MAP = {0: 'nostate', @@ -304,6 +305,10 @@ class VMModel(object): res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
+ xml = dom.XMLDesc(0) + users = xpath_get_text(xml, "/domain/metadata/kimchi/access/user") + groups = xpath_get_text(xml, "/domain/metadata/kimchi/access/group") + return {'state': state, 'stats': res, 'uuid': dom.UUIDString(), @@ -313,7 +318,9 @@ class VMModel(object): 'icon': icon, 'graphics': {"type": graphics_type, "listen": graphics_listen, - "port": graphics_port} + "port": graphics_port}, + 'users': users, + 'groups': groups }
def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 0798701..9e258ce 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -135,7 +135,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(u'test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) diff --git a/tests/test_model.py b/tests/test_model.py index 3041196..00c02d3 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -61,7 +61,7 @@ class ModelTests(unittest.TestCase): self.assertEquals('test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) @@ -73,6 +73,8 @@ class ModelTests(unittest.TestCase): self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') + self.assertEquals([], info['users']) + self.assertEquals([], info['groups'])
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_lifecycle(self): diff --git a/tests/test_rest.py b/tests/test_rest.py index 6bce606..9c2f997 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -181,6 +181,8 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) self.assertEquals('shutoff', vm['state']) + self.assertEquals(['user1', 'user2', 'root'], vm['users']) + self.assertEquals(['group1', 'group2', 'admin'], vm['groups'])
def test_edit_vm(self): req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'})
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On Tue, 2014-04-22 at 13:54 -0300, Aline Manera wrote:
On 04/21/2014 12:41 PM, Christy Perez wrote:
One comment below...
On Fri, 2014-04-11 at 17:58 -0300, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
In order to have more control over what users can use a virtual machine under Kimchi, a VM should have users and groups associated with it.
Kimchi uses metadata information (http://libvirt.org/formatdomain.html#elementsMetadata) to store users and groups to the virtual machines. The data is stored as string representations of Python lists. Keep in mind that these security restrictions will not apply when using other virtual machine managers (e.g. virsh, virt-manager, etc).
Users and groups stored in the VM are now returned when the user queries the VM data. Currently, there is no way to associate users and groups to a VM.
The REST command below now returns the users and groups of every virtual machine:
/vms/:name GET: It now lists the users and groups associated with that VM.
{ "users":[ "user1", "user2" ], "groups":[ "group1", "group2" ], ... }
If there is no metadata information about users and groups, empty lists will be displayed
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com>
show
show --- docs/API.md | 4 ++++ src/kimchi/API.json | 22 ++++++++++++++++++++++ src/kimchi/control/vms.py | 4 +++- src/kimchi/i18n.py | 4 ++++ src/kimchi/mockmodel.py | 4 +++- src/kimchi/model/vms.py | 13 ++++++++++--- tests/test_mockmodel.py | 2 +- tests/test_model.py | 4 +++- tests/test_rest.py | 2 ++ 9 files changed, 52 insertions(+), 7 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 143c70c..b726b9c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -94,6 +94,10 @@ the following general conventions: * port: The real port number of the graphics, vnc or spice. Users can use this port to connect to the vm with general vnc/spice clients. + * users: A list of system users who have permission to access the VM. + Default is: empty. + * groups: A list of system groups whose users have permission to access + the VM. Default is: empty. Sorry this is a little late, but, from this, it's not obvious what the default empty list means. Does it mean that no users can access the VM, or that there are no restrictions on access? I think a simple patch to clarify will prevent confusion.
Sure. Feel free to send a patch for it, Christy! In fact, this information is not being used yet. But when the list is empty, only root (with sudo) users can manage the VM.
Thanks Aline. Patch sent!
* **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 5ca94e3..6294dc8 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -201,6 +201,28 @@ "type": "string", "minLength": 1, "error": "KCHVM0011E" + }, + "users": { + "description": "Array of users who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0022E", + "items": { + "description": "User name", + "type": "string", + "error": "KCHVM0023E" + } + }, + "groups": { + "description": "Array of groups who have permission to the VM", + "type": "array", + "uniqueItems": true, + "error": "KCHVM0024E", + "items": { + "description": "Group name", + "type": "string", + "error": "KCHVM0025E" + } } } }, diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index e75b27f..63c1e24 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], 'listen': self.info['graphics']['listen'], - 'port': self.info['graphics']['port']} + 'port': self.info['graphics']['port']}, + 'users': self.info['users'], + 'groups': self.info['groups'] }
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 0bde5b9..20f9716 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -80,6 +80,10 @@ messages = { "KCHVM0019E": _("Unable to start virtual machine %(name)s. Details: %(err)s"), "KCHVM0020E": _("Unable to power off virtual machine %(name)s. Details: %(err)s"), "KCHVM0021E": _("Unable to delete virtual machine %(name)s. Details: %(err)s"), + "KCHVM0022E": _("User names list must be an array"), + "KCHVM0023E": _("User name must be a string"), + "KCHVM0024E": _("Group names list must be an array"), + "KCHVM0025E": _("Group name must be a string"),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index bf17975..87ca21c 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -969,7 +969,9 @@ class MockVM(object): 'cpus': template_info['cpus'], 'icon': None, 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', - 'port': None} + 'port': None}, + 'users': ['user1', 'user2', 'root'], + 'groups': ['group1', 'group2', 'admin'] } self.info['graphics'].update(template_info['graphics'])
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 2425a43..ba091e5 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -34,8 +34,9 @@ from kimchi.model.config import CapabilitiesModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.screenshot import VMScreenshot -from kimchi.utils import kimchi_log -from kimchi.utils import run_setfacl_set_attr, template_name_from_uri +from kimchi.utils import kimchi_log, run_setfacl_set_attr +from kimchi.utils import template_name_from_uri +from kimchi.xmlutils import xpath_get_text
DOM_STATE_MAP = {0: 'nostate', @@ -304,6 +305,10 @@ class VMModel(object): res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
+ xml = dom.XMLDesc(0) + users = xpath_get_text(xml, "/domain/metadata/kimchi/access/user") + groups = xpath_get_text(xml, "/domain/metadata/kimchi/access/group") + return {'state': state, 'stats': res, 'uuid': dom.UUIDString(), @@ -313,7 +318,9 @@ class VMModel(object): 'icon': icon, 'graphics': {"type": graphics_type, "listen": graphics_listen, - "port": graphics_port} + "port": graphics_port}, + 'users': users, + 'groups': groups }
def _vm_get_disk_paths(self, dom): diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 0798701..9e258ce 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -135,7 +135,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(u'test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) diff --git a/tests/test_model.py b/tests/test_model.py index 3041196..00c02d3 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -61,7 +61,7 @@ class ModelTests(unittest.TestCase): self.assertEquals('test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory', 'cpus', 'screenshot', - 'icon', 'graphics')) + 'icon', 'graphics', 'users', 'groups')) stats_keys = set(('cpu_utilization', 'net_throughput', 'net_throughput_peak', 'io_throughput', 'io_throughput_peak')) @@ -73,6 +73,8 @@ class ModelTests(unittest.TestCase): self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') + self.assertEquals([], info['users']) + self.assertEquals([], info['groups'])
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_lifecycle(self): diff --git a/tests/test_rest.py b/tests/test_rest.py index 6bce606..9c2f997 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -181,6 +181,8 @@ class RestTests(unittest.TestCase): vm = json.loads(self.request('/vms/vm-1').read()) self.assertEquals('vm-1', vm['name']) self.assertEquals('shutoff', vm['state']) + self.assertEquals(['user1', 'user2', 'root'], vm['users']) + self.assertEquals(['group1', 'group2', 'admin'], vm['groups'])
def test_edit_vm(self): req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'})
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

From: Aline Manera <alinefm@br.ibm.com> To update (add/remove) the users and groups of a virtual machine, you should use the REST command: /vms/<vm_name> PUT "{ 'users': [ 'user1', 'user2' ], 'groups': [ 'group1', 'group2' ] }" Users and groups associated with a virtual machine must be valid when they are added to the VM. Such verification is not done when they are removed from the system. This means that virtual machines may contain outdated information about users and groups. Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- docs/API.md | 2 ++ po/en_US.po | 24 +++++++++++++++++-- po/kimchi.pot | 24 +++++++++++++++++-- po/pt_BR.po | 57 +++++++++++++++++++++++++++++++-------------- po/zh_CN.po | 56 ++++++++++++++++++++++++++++++-------------- src/kimchi/control/vms.py | 2 +- src/kimchi/i18n.py | 4 +++- src/kimchi/mockmodel.py | 3 +-- src/kimchi/model/vms.py | 55 +++++++++++++++++++++++++++++++++++++------ tests/test_model.py | 40 +++++++++++++++++++++++++++++++ 10 files changed, 217 insertions(+), 50 deletions(-) diff --git a/docs/API.md b/docs/API.md index b726b9c..2c57665 100644 --- a/docs/API.md +++ b/docs/API.md @@ -101,6 +101,8 @@ the following general conventions: * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) + * users: New list of system users. + * groups: New list of system groups. * **POST**: *See Virtual Machine Actions* **Actions (POST):** diff --git a/po/en_US.po b/po/en_US.po index 0b05f1e..f7b6e38 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: 2013-07-11 17:32-0400\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: English\n" @@ -914,7 +914,7 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr "" #, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" msgstr "" #, python-format @@ -957,6 +957,26 @@ msgstr "" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr "" +msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "" diff --git a/po/kimchi.pot b/po/kimchi.pot index 92d5401..1da141b 100755 --- a/po/kimchi.pot +++ b/po/kimchi.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -902,7 +902,7 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr "" #, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" msgstr "" #, python-format @@ -945,6 +945,26 @@ msgstr "" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr "" +msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "" diff --git a/po/pt_BR.po b/po/pt_BR.po index cbb2b31..098ca8e 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: Aline Manera <alinefm@br.ibm.com>\n" @@ -407,13 +407,10 @@ msgstr "" msgid "Create a network" msgstr "Criar uma rede" -#, fuzzy msgid "" "This network is not persistent. Instead of stop, this action will " "permanently delete it. Would you like to continue?" msgstr "" -"O storage pool não é persistente. Ao invés de desativar, essa ação vai " -"removê-lo permanentemente. Deseja continuar?" msgid "" "This will permanently delete the storage pool. Would you like to continue?" @@ -840,13 +837,13 @@ msgstr "Essa API suporta apenas JSON" msgid "Datastore is not initiated in the model object." msgstr "Datastore não é inicializado no objeto modelo." -#, fuzzy, python-format +#, python-format msgid "Unable to start task due error: %(err)s" -msgstr "Não foi possível iniciar a máquina virtual %(name)s. Detalhes: %(err)s" +msgstr "" -#, fuzzy, python-format +#, python-format msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" -msgstr "O usuário '%(userid)s' falhou na autenticação. [Error code: %(code)s]" +msgstr "" msgid "You are not authorized to access Kimchi" msgstr "Você não está autorizado para acessar o Kimchi" @@ -965,8 +962,8 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr "Não é possível criar a máquina virtual %(name)s. Detalhes: %(err)s" #, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" -msgstr "Não é possível renomear a máquina virtual %(name)s. Detalhes: %(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" +msgstr "" #, python-format msgid "Unable to retrieve virtual machine %(name)s. Details: %(err)s" @@ -1008,6 +1005,26 @@ msgstr "Não é possível parar a máquina virtual %(name)s. Detalhes: %(err)s" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr "Não é possível apagar a máquina virtual %(name)s. Detalhes: %(err)s" +msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "Interface %(iface)s não existe na máquina virtual %(name)s" @@ -1107,13 +1124,13 @@ msgstr "" msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "O volume de storage: %(volume)s não existe no storage pool %(pool)s" -#, fuzzy, python-format +#, python-format msgid "Unable to create template due error: %(err)s" -msgstr "Não é possível criar o storage pool %(name)s.Detalhes: %(err)s" +msgstr "" -#, fuzzy, python-format +#, python-format msgid "Unable to delete template due error: %(err)s" -msgstr "Incapaz de deletar a rede ativa %(name)s" +msgstr "" #, python-format msgid "Storage pool %(name)s already exists" @@ -1277,9 +1294,9 @@ msgstr "" "Um grupo de volume chamado '%(name)s' já existe. Por favor, escolher outro " "nome para criar o pool lógico." -#, fuzzy, python-format +#, python-format msgid "Unable to update database with deep scan information due error: %(err)s" -msgstr "Incapaz de carregar as informações do repositório. Detalhes: %(err)s" +msgstr "" #, python-format msgid "Storage volume %(name)s already exists" @@ -1356,10 +1373,10 @@ msgstr "Formato do volume não suportado" msgid "Storage volume requires a volume name" msgstr "Volume requer um nome" -#, fuzzy, python-format +#, python-format msgid "" "Unable to update database with storage volume information due error: %(err)s" -msgstr "Incapaz de deletar o volume %(name)s. Detalhes: %(err)s" +msgstr "" #, python-format msgid "Interface %(name)s does not exist" @@ -1686,3 +1703,7 @@ msgstr "Não é possível adicionar o repositório. Detalhes: %(err)s" #, python-format msgid "Unable to remove repository. Details: '%(err)s'" msgstr "Incapaz de remover o repositório. Detalhes: %(err)s" + +#~ msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +#~ msgstr "" +#~ "Não é possível renomear a máquina virtual %(name)s. Detalhes: %(err)s" diff --git a/po/zh_CN.po b/po/zh_CN.po index 3b3754c..a1a72b9 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" "Language-Team: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" @@ -389,11 +389,10 @@ msgstr "此操作将中断依赖此网络的虚拟机的网络连接。" msgid "Create a network" msgstr "创建一个网络" -#, fuzzy msgid "" "This network is not persistent. Instead of stop, this action will " "permanently delete it. Would you like to continue?" -msgstr "对于非持久存储池,这个操作将会永久删除存储池而不是停用。是否继续?" +msgstr "" msgid "" "This will permanently delete the storage pool. Would you like to continue?" @@ -812,13 +811,13 @@ msgstr "这个API仅支持JSON" msgid "Datastore is not initiated in the model object." msgstr "尚未为model对象初始化数据存储。" -#, fuzzy, python-format +#, python-format msgid "Unable to start task due error: %(err)s" -msgstr "不能启动虚拟机 %(name)s. 详情:%(err)s" +msgstr "" -#, fuzzy, python-format +#, python-format msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" -msgstr "用户 '%(userid)s' 验证失败。[错误代码: %(code)s]" +msgstr "" msgid "You are not authorized to access Kimchi" msgstr "您没有被授权访问Kimchi" @@ -926,8 +925,8 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr "不能创建虚拟机%(name)s。详情:%(err)s" #, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" -msgstr "不能重命名虚拟机%(name)s。详情:%(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" +msgstr "" #, python-format msgid "Unable to retrieve virtual machine %(name)s. Details: %(err)s" @@ -969,6 +968,26 @@ msgstr "不能停止虚拟机 %(name)s. 详情:%(err)s" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr "不能删除虚拟机 %(name)s. 详情:%(err)s" +msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "虚拟机 %(name)s 中没有接口 %(iface)s" @@ -1061,13 +1080,13 @@ msgstr "当模板的存储池是iscsi或scsi时,必须为模板指定一个卷 msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "存储池%(pool)s中没有存储卷%(volume)s" -#, fuzzy, python-format +#, python-format msgid "Unable to create template due error: %(err)s" -msgstr "不能创建存储池 %(name)s。详情: %(err)s" +msgstr "" -#, fuzzy, python-format +#, python-format msgid "Unable to delete template due error: %(err)s" -msgstr "不能删除激活的网络%(name)s" +msgstr "" #, python-format msgid "Storage pool %(name)s already exists" @@ -1210,9 +1229,9 @@ msgid "" "to create the logical pool." msgstr "卷组'%(name)s'已经存在,请选择其它的名字来创建逻辑存储池。" -#, fuzzy, python-format +#, python-format msgid "Unable to update database with deep scan information due error: %(err)s" -msgstr "不能获取软件仓库的信息。详情:'%(err)s'" +msgstr "" #, python-format msgid "Storage volume %(name)s already exists" @@ -1282,10 +1301,10 @@ msgstr "存储卷格式不支持" msgid "Storage volume requires a volume name" msgstr "存储卷需要名字" -#, fuzzy, python-format +#, python-format msgid "" "Unable to update database with storage volume information due error: %(err)s" -msgstr "不能删除存储卷%(name)s。详情:%(err)s" +msgstr "" #, python-format msgid "Interface %(name)s does not exist" @@ -1581,3 +1600,6 @@ msgstr "不能增加软件仓库。详情:'%(err)s'" #, python-format msgid "Unable to remove repository. Details: '%(err)s'" msgstr "不能移除软件仓库。详情:'%(err)s'" + +#~ msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +#~ msgstr "不能重命名虚拟机%(name)s。详情:%(err)s" diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 63c1e24..a44764b 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -32,7 +32,7 @@ class VMs(Collection): class VM(Resource): def __init__(self, model, ident): super(VM, self).__init__(model, ident) - self.update_params = ["name"] + self.update_params = ["name", "users", "groups"] self.screenshot = VMScreenShot(model, ident) self.uri_fmt = '/vms/%s' for ident, node in sub_nodes.items(): diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 20f9716..ef46505 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -68,7 +68,7 @@ messages = { "KCHVM0005E": _("Remote ISO image is not supported by this server."), "KCHVM0006E": _("Screenshot not supported for virtual machine %(name)s"), "KCHVM0007E": _("Unable to create virtual machine %(name)s. Details: %(err)s"), - "KCHVM0008E": _("Unable to rename virtual machine %(name)s. Details: %(err)s"), + "KCHVM0008E": _("Unable to update virtual machine %(name)s. Details: %(err)s"), "KCHVM0009E": _("Unable to retrieve virtual machine %(name)s. Details: %(err)s"), "KCHVM0010E": _("Unable to connect to powered off machine %(name)s."), "KCHVM0011E": _("Virtual machine name must be a string"), @@ -84,6 +84,8 @@ messages = { "KCHVM0023E": _("User name must be a string"), "KCHVM0024E": _("Group names list must be an array"), "KCHVM0025E": _("Group name must be a string"), + "KCHVM0026E": _("User %(user)s does not exist"), + "KCHVM0027E": _("Group %(group)s does not exist"), "KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 87ca21c..4fb28c6 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -49,7 +49,6 @@ from kimchi.exception import MissingParameter, NotFoundError, OperationFailed from kimchi.model.storagepools import ISO_POOL_NAME from kimchi.model.storageservers import STORAGE_SERVERS from kimchi.model.utils import get_vm_name -from kimchi.model.vms import VM_STATIC_UPDATE_PARAMS from kimchi.objectstore import ObjectStore from kimchi.screenshot import VMScreenshot from kimchi.utils import pool_name_from_uri @@ -98,7 +97,7 @@ class MockModel(object): self._mock_vms[dom.name] = dom for key, val in params.items(): - if key in VM_STATIC_UPDATE_PARAMS and key in dom.info: + if key in dom.info: dom.info[key] = val def _live_vm_update(self, dom, params): diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index ba091e5..f567390 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -17,6 +17,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +import lxml.etree as ET import os import time import uuid @@ -24,9 +25,11 @@ from xml.etree import ElementTree import libvirt from cherrypy.process.plugins import BackgroundTask +from lxml.builder import E from kimchi import vnc from kimchi import xmlutils +from kimchi.auth import Group, User from kimchi.config import READONLY_POOL_TYPE from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import NotFoundError, OperationFailed @@ -240,25 +243,63 @@ class VMModel(object): self._live_vm_update(dom, params) return dom.name().decode('utf-8') + def _get_metadata_node(self, users, groups): + access = E.access() + for user in users: + access.append(E.user(user)) + + for group in groups: + access.append(E.group(group)) + + return E.metadata(E.kimchi(access)) + def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0) + metadata_xpath = "/domain/metadata/kimchi/access/%s" + users = xpath_get_text(old_xml, metadata_xpath % "user") + groups = xpath_get_text(old_xml, metadata_xpath % "group") + for key, val in params.items(): - if key in VM_STATIC_UPDATE_PARAMS: - xpath = VM_STATIC_UPDATE_PARAMS[key] - new_xml = xmlutils.xml_item_update(new_xml, xpath, val) + if key == 'users': + for user in val: + if not User(user).exists(): + raise OperationFailed("KCHVM0026E", {'user': user}) + users = val + elif key == 'groups': + for group in val: + if not Group(group).exists(): + raise OperationFailed("KCHVM0027E", {'group': group}) + groups = val + else: + if key in VM_STATIC_UPDATE_PARAMS: + xpath = VM_STATIC_UPDATE_PARAMS[key] + new_xml = xmlutils.xml_item_update(new_xml, xpath, val) + conn = self.conn.get() try: if 'name' in params: if state == 'running': msg_args = {'name': dom.name(), 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) - else: - dom.undefine() - conn = self.conn.get() - dom = conn.defineXML(new_xml) + + # Undefine old vm and create a new one with updated values + dom.undefine() + conn = self.conn.get() + dom = conn.defineXML(new_xml) + + # Update metadata element + root = ET.fromstring(new_xml) + current_metadata = root.find('metadata') + new_metadata = self._get_metadata_node(users, groups) + if current_metadata is not None: + root.replace(current_metadata, new_metadata) + else: + root.append(new_metadata) + dom = conn.defineXML(ET.tostring(root)) + except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) raise OperationFailed("KCHVM0008E", {'name': dom.name(), diff --git a/tests/test_model.py b/tests/test_model.py index 00c02d3..357d969 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -18,9 +18,11 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +import grp import os import platform import psutil +import pwd import shutil import tempfile import threading @@ -608,6 +610,44 @@ class ModelTests(unittest.TestCase): rollback.prependDefer(self._rollback_wrapper, inst.vm_delete, u'пeω-∨м') + # change only VM users - groups are not changed (default is empty) + users = ['root'] + inst.vm_update(u'пeω-∨м', {'users': users}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['groups']) + + # change only VM groups - users are not changed (default is empty) + groups = ['root'] + inst.vm_update(u'пeω-∨м', {'groups': groups}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users and groups by adding a new element to each one + users.append(pwd.getpwuid(os.getuid()).pw_name) + groups.append(grp.getgrgid(os.getgid()).gr_name) + inst.vm_update(u'пeω-∨м', {'users': users, 'groups': groups}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users (wrong value) and groups + # when an error occurs, everything fails and nothing is changed + self.assertRaises(OperationFailed, inst.vm_update, u'пeω-∨м', + {'users': ['userdoesnotexist'], 'groups': []}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users and groups (wrong value) + # when an error occurs, everything fails and nothing is changed + self.assertRaises(OperationFailed, inst.vm_update, u'пeω-∨м', + {'users': [], 'groups': ['groupdoesnotexist']}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users and groups by removing all elements + inst.vm_update(u'пeω-∨м', {'users': [], 'groups': []}) + self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['groups']) + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_network(self): inst = model.Model('qemu:///system', self.tmp_store) -- 1.7.10.4

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 04/11/2014 05:58 PM, Aline Manera wrote:
From: Aline Manera <alinefm@br.ibm.com>
To update (add/remove) the users and groups of a virtual machine, you should use the REST command:
/vms/<vm_name> PUT "{ 'users': [ 'user1', 'user2' ], 'groups': [ 'group1', 'group2' ] }"
Users and groups associated with a virtual machine must be valid when they are added to the VM. Such verification is not done when they are removed from the system. This means that virtual machines may contain outdated information about users and groups.
Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@br.ibm.com> --- docs/API.md | 2 ++ po/en_US.po | 24 +++++++++++++++++-- po/kimchi.pot | 24 +++++++++++++++++-- po/pt_BR.po | 57 +++++++++++++++++++++++++++++++-------------- po/zh_CN.po | 56 ++++++++++++++++++++++++++++++-------------- src/kimchi/control/vms.py | 2 +- src/kimchi/i18n.py | 4 +++- src/kimchi/mockmodel.py | 3 +-- src/kimchi/model/vms.py | 55 +++++++++++++++++++++++++++++++++++++------ tests/test_model.py | 40 +++++++++++++++++++++++++++++++ 10 files changed, 217 insertions(+), 50 deletions(-)
diff --git a/docs/API.md b/docs/API.md index b726b9c..2c57665 100644 --- a/docs/API.md +++ b/docs/API.md @@ -101,6 +101,8 @@ the following general conventions: * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) + * users: New list of system users. + * groups: New list of system groups. * **POST**: *See Virtual Machine Actions*
**Actions (POST):** diff --git a/po/en_US.po b/po/en_US.po index 0b05f1e..f7b6e38 100644 --- a/po/en_US.po +++ b/po/en_US.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: 2013-07-11 17:32-0400\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: English\n" @@ -914,7 +914,7 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr ""
#, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" msgstr ""
#, python-format @@ -957,6 +957,26 @@ msgstr "" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr ""
+msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "" diff --git a/po/kimchi.pot b/po/kimchi.pot index 92d5401..1da141b 100755 --- a/po/kimchi.pot +++ b/po/kimchi.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -902,7 +902,7 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr ""
#, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" msgstr ""
#, python-format @@ -945,6 +945,26 @@ msgstr "" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr ""
+msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "" diff --git a/po/pt_BR.po b/po/pt_BR.po index cbb2b31..098ca8e 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: Crístian Viana <vianac@linux.vnet.ibm.com>\n" "Language-Team: Aline Manera <alinefm@br.ibm.com>\n" @@ -407,13 +407,10 @@ msgstr "" msgid "Create a network" msgstr "Criar uma rede"
-#, fuzzy msgid "" "This network is not persistent. Instead of stop, this action will " "permanently delete it. Would you like to continue?" msgstr "" -"O storage pool não é persistente. Ao invés de desativar, essa ação vai " -"removê-lo permanentemente. Deseja continuar?"
msgid "" "This will permanently delete the storage pool. Would you like to continue?" @@ -840,13 +837,13 @@ msgstr "Essa API suporta apenas JSON" msgid "Datastore is not initiated in the model object." msgstr "Datastore não é inicializado no objeto modelo."
-#, fuzzy, python-format +#, python-format msgid "Unable to start task due error: %(err)s" -msgstr "Não foi possível iniciar a máquina virtual %(name)s. Detalhes: %(err)s" +msgstr ""
-#, fuzzy, python-format +#, python-format msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" -msgstr "O usuário '%(userid)s' falhou na autenticação. [Error code: %(code)s]" +msgstr ""
msgid "You are not authorized to access Kimchi" msgstr "Você não está autorizado para acessar o Kimchi" @@ -965,8 +962,8 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr "Não é possível criar a máquina virtual %(name)s. Detalhes: %(err)s"
#, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" -msgstr "Não é possível renomear a máquina virtual %(name)s. Detalhes: %(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" +msgstr ""
#, python-format msgid "Unable to retrieve virtual machine %(name)s. Details: %(err)s" @@ -1008,6 +1005,26 @@ msgstr "Não é possível parar a máquina virtual %(name)s. Detalhes: %(err)s" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr "Não é possível apagar a máquina virtual %(name)s. Detalhes: %(err)s"
+msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "Interface %(iface)s não existe na máquina virtual %(name)s" @@ -1107,13 +1124,13 @@ msgstr "" msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "O volume de storage: %(volume)s não existe no storage pool %(pool)s"
-#, fuzzy, python-format +#, python-format msgid "Unable to create template due error: %(err)s" -msgstr "Não é possível criar o storage pool %(name)s.Detalhes: %(err)s" +msgstr ""
-#, fuzzy, python-format +#, python-format msgid "Unable to delete template due error: %(err)s" -msgstr "Incapaz de deletar a rede ativa %(name)s" +msgstr ""
#, python-format msgid "Storage pool %(name)s already exists" @@ -1277,9 +1294,9 @@ msgstr "" "Um grupo de volume chamado '%(name)s' já existe. Por favor, escolher outro " "nome para criar o pool lógico."
-#, fuzzy, python-format +#, python-format msgid "Unable to update database with deep scan information due error: %(err)s" -msgstr "Incapaz de carregar as informações do repositório. Detalhes: %(err)s" +msgstr ""
#, python-format msgid "Storage volume %(name)s already exists" @@ -1356,10 +1373,10 @@ msgstr "Formato do volume não suportado" msgid "Storage volume requires a volume name" msgstr "Volume requer um nome"
-#, fuzzy, python-format +#, python-format msgid "" "Unable to update database with storage volume information due error: %(err)s" -msgstr "Incapaz de deletar o volume %(name)s. Detalhes: %(err)s" +msgstr ""
#, python-format msgid "Interface %(name)s does not exist" @@ -1686,3 +1703,7 @@ msgstr "Não é possível adicionar o repositório. Detalhes: %(err)s" #, python-format msgid "Unable to remove repository. Details: '%(err)s'" msgstr "Incapaz de remover o repositório. Detalhes: %(err)s" + +#~ msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +#~ msgstr "" +#~ "Não é possível renomear a máquina virtual %(name)s. Detalhes: %(err)s" diff --git a/po/zh_CN.po b/po/zh_CN.po index 3b3754c..a1a72b9 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -20,7 +20,7 @@ msgid "" msgstr "" "Project-Id-Version: kimchi 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-09 17:11-0300\n" +"POT-Creation-Date: 2014-04-10 14:32-0300\n" "PO-Revision-Date: 2013-06-27 10:48+0000\n" "Last-Translator: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" "Language-Team: ShaoHe Feng <shaohef@linux.vnet.ibm.com>\n" @@ -389,11 +389,10 @@ msgstr "此操作将中断依赖此网络的虚拟机的网络连接。" msgid "Create a network" msgstr "创建一个网络"
-#, fuzzy msgid "" "This network is not persistent. Instead of stop, this action will " "permanently delete it. Would you like to continue?" -msgstr "对于非持久存储池,这个操作将会永久删除存储池而不是停用。是否继续?" +msgstr ""
msgid "" "This will permanently delete the storage pool. Would you like to continue?" @@ -812,13 +811,13 @@ msgstr "这个API仅支持JSON" msgid "Datastore is not initiated in the model object." msgstr "尚未为model对象初始化数据存储。"
-#, fuzzy, python-format +#, python-format msgid "Unable to start task due error: %(err)s" -msgstr "不能启动虚拟机 %(name)s. 详情:%(err)s" +msgstr ""
-#, fuzzy, python-format +#, python-format msgid "Authentication failed for user '%(username)s'. [Error code: %(code)s]" -msgstr "用户 '%(userid)s' 验证失败。[错误代码: %(code)s]" +msgstr ""
msgid "You are not authorized to access Kimchi" msgstr "您没有被授权访问Kimchi" @@ -926,8 +925,8 @@ msgid "Unable to create virtual machine %(name)s. Details: %(err)s" msgstr "不能创建虚拟机%(name)s。详情:%(err)s"
#, python-format -msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" -msgstr "不能重命名虚拟机%(name)s。详情:%(err)s" +msgid "Unable to update virtual machine %(name)s. Details: %(err)s" +msgstr ""
#, python-format msgid "Unable to retrieve virtual machine %(name)s. Details: %(err)s" @@ -969,6 +968,26 @@ msgstr "不能停止虚拟机 %(name)s. 详情:%(err)s" msgid "Unable to delete virtual machine %(name)s. Details: %(err)s" msgstr "不能删除虚拟机 %(name)s. 详情:%(err)s"
+msgid "User names list must be an array" +msgstr "" + +msgid "User name must be a string" +msgstr "" + +msgid "Group names list must be an array" +msgstr "" + +msgid "Group name must be a string" +msgstr "" + +#, python-format +msgid "User %(user)s does not exist" +msgstr "" + +#, python-format +msgid "Group %(group)s does not exist" +msgstr "" + #, python-format msgid "Interface %(iface)s does not exist in virtual machine %(name)s" msgstr "虚拟机 %(name)s 中没有接口 %(iface)s" @@ -1061,13 +1080,13 @@ msgstr "当模板的存储池是iscsi或scsi时,必须为模板指定一个卷 msgid "The volume: %(volume)s in not in storage pool %(pool)s" msgstr "存储池%(pool)s中没有存储卷%(volume)s"
-#, fuzzy, python-format +#, python-format msgid "Unable to create template due error: %(err)s" -msgstr "不能创建存储池 %(name)s。详情: %(err)s" +msgstr ""
-#, fuzzy, python-format +#, python-format msgid "Unable to delete template due error: %(err)s" -msgstr "不能删除激活的网络%(name)s" +msgstr ""
#, python-format msgid "Storage pool %(name)s already exists" @@ -1210,9 +1229,9 @@ msgid "" "to create the logical pool." msgstr "卷组'%(name)s'已经存在,请选择其它的名字来创建逻辑存储池。"
-#, fuzzy, python-format +#, python-format msgid "Unable to update database with deep scan information due error: %(err)s" -msgstr "不能获取软件仓库的信息。详情:'%(err)s'" +msgstr ""
#, python-format msgid "Storage volume %(name)s already exists" @@ -1282,10 +1301,10 @@ msgstr "存储卷格式不支持" msgid "Storage volume requires a volume name" msgstr "存储卷需要名字"
-#, fuzzy, python-format +#, python-format msgid "" "Unable to update database with storage volume information due error: %(err)s" -msgstr "不能删除存储卷%(name)s。详情:%(err)s" +msgstr ""
#, python-format msgid "Interface %(name)s does not exist" @@ -1581,3 +1600,6 @@ msgstr "不能增加软件仓库。详情:'%(err)s'" #, python-format msgid "Unable to remove repository. Details: '%(err)s'" msgstr "不能移除软件仓库。详情:'%(err)s'" + +#~ msgid "Unable to rename virtual machine %(name)s. Details: %(err)s" +#~ msgstr "不能重命名虚拟机%(name)s。详情:%(err)s" diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 63c1e24..a44764b 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -32,7 +32,7 @@ class VMs(Collection): class VM(Resource): def __init__(self, model, ident): super(VM, self).__init__(model, ident) - self.update_params = ["name"] + self.update_params = ["name", "users", "groups"] self.screenshot = VMScreenShot(model, ident) self.uri_fmt = '/vms/%s' for ident, node in sub_nodes.items(): diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 20f9716..ef46505 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -68,7 +68,7 @@ messages = { "KCHVM0005E": _("Remote ISO image is not supported by this server."), "KCHVM0006E": _("Screenshot not supported for virtual machine %(name)s"), "KCHVM0007E": _("Unable to create virtual machine %(name)s. Details: %(err)s"), - "KCHVM0008E": _("Unable to rename virtual machine %(name)s. Details: %(err)s"), + "KCHVM0008E": _("Unable to update virtual machine %(name)s. Details: %(err)s"), "KCHVM0009E": _("Unable to retrieve virtual machine %(name)s. Details: %(err)s"), "KCHVM0010E": _("Unable to connect to powered off machine %(name)s."), "KCHVM0011E": _("Virtual machine name must be a string"), @@ -84,6 +84,8 @@ messages = { "KCHVM0023E": _("User name must be a string"), "KCHVM0024E": _("Group names list must be an array"), "KCHVM0025E": _("Group name must be a string"), + "KCHVM0026E": _("User %(user)s does not exist"), + "KCHVM0027E": _("Group %(group)s does not exist"),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 87ca21c..4fb28c6 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -49,7 +49,6 @@ from kimchi.exception import MissingParameter, NotFoundError, OperationFailed from kimchi.model.storagepools import ISO_POOL_NAME from kimchi.model.storageservers import STORAGE_SERVERS from kimchi.model.utils import get_vm_name -from kimchi.model.vms import VM_STATIC_UPDATE_PARAMS from kimchi.objectstore import ObjectStore from kimchi.screenshot import VMScreenshot from kimchi.utils import pool_name_from_uri @@ -98,7 +97,7 @@ class MockModel(object): self._mock_vms[dom.name] = dom
for key, val in params.items(): - if key in VM_STATIC_UPDATE_PARAMS and key in dom.info: + if key in dom.info: dom.info[key] = val
def _live_vm_update(self, dom, params): diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index ba091e5..f567390 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -17,6 +17,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import lxml.etree as ET import os import time import uuid @@ -24,9 +25,11 @@ from xml.etree import ElementTree
import libvirt from cherrypy.process.plugins import BackgroundTask +from lxml.builder import E
from kimchi import vnc from kimchi import xmlutils +from kimchi.auth import Group, User from kimchi.config import READONLY_POOL_TYPE from kimchi.exception import InvalidOperation, InvalidParameter from kimchi.exception import NotFoundError, OperationFailed @@ -240,25 +243,63 @@ class VMModel(object): self._live_vm_update(dom, params) return dom.name().decode('utf-8')
+ def _get_metadata_node(self, users, groups): + access = E.access() + for user in users: + access.append(E.user(user)) + + for group in groups: + access.append(E.group(group)) + + return E.metadata(E.kimchi(access)) + def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]]
old_xml = new_xml = dom.XMLDesc(0)
+ metadata_xpath = "/domain/metadata/kimchi/access/%s" + users = xpath_get_text(old_xml, metadata_xpath % "user") + groups = xpath_get_text(old_xml, metadata_xpath % "group") + for key, val in params.items(): - if key in VM_STATIC_UPDATE_PARAMS: - xpath = VM_STATIC_UPDATE_PARAMS[key] - new_xml = xmlutils.xml_item_update(new_xml, xpath, val) + if key == 'users': + for user in val: + if not User(user).exists(): + raise OperationFailed("KCHVM0026E", {'user': user}) + users = val + elif key == 'groups': + for group in val: + if not Group(group).exists(): + raise OperationFailed("KCHVM0027E", {'group': group}) + groups = val + else: + if key in VM_STATIC_UPDATE_PARAMS: + xpath = VM_STATIC_UPDATE_PARAMS[key] + new_xml = xmlutils.xml_item_update(new_xml, xpath, val)
+ conn = self.conn.get() try: if 'name' in params: if state == 'running': msg_args = {'name': dom.name(), 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) - else: - dom.undefine() - conn = self.conn.get() - dom = conn.defineXML(new_xml) + + # Undefine old vm and create a new one with updated values + dom.undefine() + conn = self.conn.get() + dom = conn.defineXML(new_xml) + + # Update metadata element + root = ET.fromstring(new_xml) + current_metadata = root.find('metadata') + new_metadata = self._get_metadata_node(users, groups) + if current_metadata is not None: + root.replace(current_metadata, new_metadata) + else: + root.append(new_metadata) + dom = conn.defineXML(ET.tostring(root)) + except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) raise OperationFailed("KCHVM0008E", {'name': dom.name(), diff --git a/tests/test_model.py b/tests/test_model.py index 00c02d3..357d969 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -18,9 +18,11 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import grp import os import platform import psutil +import pwd import shutil import tempfile import threading @@ -608,6 +610,44 @@ class ModelTests(unittest.TestCase): rollback.prependDefer(self._rollback_wrapper, inst.vm_delete, u'пeω-∨м')
+ # change only VM users - groups are not changed (default is empty) + users = ['root'] + inst.vm_update(u'пeω-∨м', {'users': users}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['groups']) + + # change only VM groups - users are not changed (default is empty) + groups = ['root'] + inst.vm_update(u'пeω-∨м', {'groups': groups}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users and groups by adding a new element to each one + users.append(pwd.getpwuid(os.getuid()).pw_name) + groups.append(grp.getgrgid(os.getgid()).gr_name) + inst.vm_update(u'пeω-∨м', {'users': users, 'groups': groups}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users (wrong value) and groups + # when an error occurs, everything fails and nothing is changed + self.assertRaises(OperationFailed, inst.vm_update, u'пeω-∨м', + {'users': ['userdoesnotexist'], 'groups': []}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users and groups (wrong value) + # when an error occurs, everything fails and nothing is changed + self.assertRaises(OperationFailed, inst.vm_update, u'пeω-∨м', + {'users': [], 'groups': ['groupdoesnotexist']}) + self.assertEquals(users, inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals(groups, inst.vm_lookup(u'пeω-∨м')['groups']) + + # change VM users and groups by removing all elements + inst.vm_update(u'пeω-∨м', {'users': [], 'groups': []}) + self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['users']) + self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['groups']) + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_network(self): inst = model.Model('qemu:///system', self.tmp_store)
participants (3)
-
Aline Manera
-
Christy Perez
-
Daniel H Barboza