[Kimchi-devel] [PATCH 3/4] Return users and groups when fetching VM info

Crístian Viana vianac at linux.vnet.ibm.com
Wed Feb 26 18:09:52 UTC 2014


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 at linux.vnet.ibm.com>
---
 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   | 32 +++++++++++++++++++++++++++++++-
 tests/test_mockmodel.py   |  2 +-
 tests/test_model.py       |  4 +++-
 tests/test_rest.py        |  2 ++
 9 files changed, 73 insertions(+), 5 deletions(-)

diff --git a/docs/API.md b/docs/API.md
index e97eace..e79a38b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -96,6 +96,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 f595bbf..9d19351 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -183,6 +183,28 @@
                     "type": "string",
                     "minLength": 1,
                     "error": "KCHVM0011E"
+                },
+                "users": {
+                    "description": "Array of users who have permission to the VM",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "error": "KCHVM0020E",
+                    "items": {
+                        "description": "User name",
+                        "type": "string",
+                        "error": "KCHVM0021E"
+                    }
+                },
+                "groups": {
+                    "description": "Array of groups who have permission to the VM",
+                    "type": "array",
+                    "uniqueItems": true,
+                    "error": "KCHVM0022E",
+                    "items": {
+                        "description": "Group name",
+                        "type": "string",
+                        "error": "KCHVM0023E"
+                    }
                 }
             }
         },
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a74ce27..89e9f45 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -58,7 +58,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 ca72a80..179b38b 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -77,6 +77,10 @@ messages = {
     "KCHVM0017E": _("Volume list (LUNs names) not given."),
     "KCHVM0018E": _("Virtual machine volumes must be a list of strings with distinct LUNs names."),
     "KCHVM0019E": _("Unable to start virtual machine %(name)s. Details: %(err)s"),
+    "KCHVM0020E": _("User names list must be an array"),
+    "KCHVM0021E": _("User name must be a string"),
+    "KCHVM0022E": _("Group names list must be an array"),
+    "KCHVM0023E": _("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 b23a024..5cdbc01 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -937,7 +937,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 b6a42e6..dcb352c 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -20,6 +20,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 ast
 import os
 import time
 import uuid
@@ -27,6 +28,7 @@ from xml.etree import ElementTree
 
 import libvirt
 from cherrypy.process.plugins import BackgroundTask
+from lxml import etree
 
 from kimchi import vnc
 from kimchi import xmlutils
@@ -53,6 +55,17 @@ VM_LIVE_UPDATE_PARAMS = {}
 
 stats = {}
 
+KIMCHI_NS_TAG = 'kimchi'
+KIMCHI_NS_URI = 'http://github.com/kimchi-project/kimchi'
+
+
+def _get_vm_metadata(xml, tag):
+    et = etree.fromstring(xml)
+    val = et.xpath("/domain/metadata/%s:access/%s:%s"
+                   % (KIMCHI_NS_TAG, KIMCHI_NS_TAG, tag),
+                   namespaces={KIMCHI_NS_TAG: KIMCHI_NS_URI})
+    return ast.literal_eval(val[0].text) if val else []
+
 
 class VMsModel(object):
     def __init__(self, **kargs):
@@ -305,6 +318,21 @@ 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)
+        try:
+            users = _get_vm_metadata(xml, "users")
+        except:
+            # Wrong XML tree structure, wrong array syntax, or empty value:
+            #     ignore error and build an empty list
+            users = []
+
+        try:
+            groups = _get_vm_metadata(xml, "groups")
+        except:
+            # Wrong XML tree structure, wrong array syntax, or empty value:
+            #     ignore error and build an empty list
+            groups = []
+
         return {'state': state,
                 'stats': res,
                 'uuid': dom.UUIDString(),
@@ -314,7 +342,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 ab5eb59..863c740 100644
--- a/tests/test_mockmodel.py
+++ b/tests/test_mockmodel.py
@@ -138,7 +138,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 74e2424..474da57 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -58,7 +58,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'))
@@ -70,6 +70,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 ca96dc0..f8a87e6 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -182,6 +182,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.8.5.3




More information about the Kimchi-devel mailing list