core3: some properties can be set only once
Some properties should not be changed by user at will (like UUID). The solution is to make them write-once, so they will be set when loading from XML and frozen for the lifespan of the object holding the property. When desperately needed, users may edit XML by hand. fixes QubesOS/qubes-issues#1235
This commit is contained in:
parent
a017d78174
commit
b4d51b016b
@ -585,7 +585,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
|||||||
_NO_DEFAULT = object()
|
_NO_DEFAULT = object()
|
||||||
|
|
||||||
def __init__(self, name, setter=None, saver=None, type=None,
|
def __init__(self, name, setter=None, saver=None, type=None,
|
||||||
default=_NO_DEFAULT, load_stage=2, order=0, save_via_ref=False,
|
default=_NO_DEFAULT, write_once=False, load_stage=2, order=0,
|
||||||
|
save_via_ref=False,
|
||||||
ls_head=None, ls_width=None, doc=None):
|
ls_head=None, ls_width=None, doc=None):
|
||||||
# pylint: disable=redefined-builtin
|
# pylint: disable=redefined-builtin
|
||||||
self.__name__ = name
|
self.__name__ = name
|
||||||
@ -594,6 +595,7 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
|||||||
lambda self, prop, value: str(value))
|
lambda self, prop, value: str(value))
|
||||||
self._type = type
|
self._type = type
|
||||||
self._default = default
|
self._default = default
|
||||||
|
self._write_once = write_once
|
||||||
self.order = order
|
self.order = order
|
||||||
self.load_stage = load_stage
|
self.load_stage = load_stage
|
||||||
self.save_via_ref = save_via_ref
|
self.save_via_ref = save_via_ref
|
||||||
@ -628,6 +630,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
|||||||
|
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
self._enforce_write_once(instance)
|
||||||
|
|
||||||
if value is self.__class__.DEFAULT:
|
if value is self.__class__.DEFAULT:
|
||||||
self.__delete__(instance)
|
self.__delete__(instance)
|
||||||
return
|
return
|
||||||
@ -661,6 +665,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
|||||||
|
|
||||||
|
|
||||||
def __delete__(self, instance):
|
def __delete__(self, instance):
|
||||||
|
self._enforce_write_once(instance)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
oldvalue = getattr(instance, self.__name__)
|
oldvalue = getattr(instance, self.__name__)
|
||||||
has_oldvalue = True
|
has_oldvalue = True
|
||||||
@ -700,6 +706,13 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
|||||||
return self.__name__ == other.__name__
|
return self.__name__ == other.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def _enforce_write_once(self, instance):
|
||||||
|
if self._write_once and not instance.property_is_default(self):
|
||||||
|
raise AttributeError(
|
||||||
|
'property {!r} is write-once and already set'.format(
|
||||||
|
self.__name__))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# exceptions
|
# exceptions
|
||||||
#
|
#
|
||||||
@ -806,17 +819,20 @@ class PropertyHolder(qubes.events.Emitter):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, xml, **kwargs):
|
def __init__(self, xml, **kwargs):
|
||||||
super(PropertyHolder, self).__init__()
|
|
||||||
self.xml = xml
|
self.xml = xml
|
||||||
|
|
||||||
for key, value in kwargs.items():
|
propvalues = {}
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
all_names = set(prop.__name__ for prop in self.property_list())
|
all_names = set(prop.__name__ for prop in self.property_list())
|
||||||
for key in list(kwargs.keys()):
|
for key in list(kwargs.keys()):
|
||||||
if not key in all_names:
|
if not key in all_names:
|
||||||
continue
|
continue
|
||||||
setattr(self, key, kwargs.pop(key))
|
propvalues[key] = kwargs.pop(key)
|
||||||
|
|
||||||
|
super(PropertyHolder, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
for key, value in propvalues.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -134,7 +134,7 @@ class TC_10_property(qubes.tests.QubesTestCase):
|
|||||||
self.assertEqual(holder.testprop1, 5)
|
self.assertEqual(holder.testprop1, 5)
|
||||||
self.assertNotEqual(holder.testprop1, '5')
|
self.assertNotEqual(holder.testprop1, '5')
|
||||||
|
|
||||||
def test_090_delete(self):
|
def test_080_delete(self):
|
||||||
self.holder.testprop1 = 'testvalue'
|
self.holder.testprop1 = 'testvalue'
|
||||||
try:
|
try:
|
||||||
if self.holder.testprop1 != 'testvalue':
|
if self.holder.testprop1 != 'testvalue':
|
||||||
@ -147,7 +147,7 @@ class TC_10_property(qubes.tests.QubesTestCase):
|
|||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
self.holder.testprop1
|
self.holder.testprop1
|
||||||
|
|
||||||
def test_090_delete_by_assign(self):
|
def test_081_delete_by_assign(self):
|
||||||
self.holder.testprop1 = 'testvalue'
|
self.holder.testprop1 = 'testvalue'
|
||||||
try:
|
try:
|
||||||
if self.holder.testprop1 != 'testvalue':
|
if self.holder.testprop1 != 'testvalue':
|
||||||
@ -160,7 +160,7 @@ class TC_10_property(qubes.tests.QubesTestCase):
|
|||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
self.holder.testprop1
|
self.holder.testprop1
|
||||||
|
|
||||||
def test_092_delete_default(self):
|
def test_082_delete_default(self):
|
||||||
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
|
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
|
||||||
testprop1 = qubes.property('testprop1', default='defaultvalue')
|
testprop1 = qubes.property('testprop1', default='defaultvalue')
|
||||||
holder = MyTestHolder(None)
|
holder = MyTestHolder(None)
|
||||||
@ -176,6 +176,26 @@ class TC_10_property(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
self.assertEqual(holder.testprop1, 'defaultvalue')
|
self.assertEqual(holder.testprop1, 'defaultvalue')
|
||||||
|
|
||||||
|
def test_090_write_once_set(self):
|
||||||
|
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
|
||||||
|
testprop1 = qubes.property('testprop1', write_once=True)
|
||||||
|
holder = MyTestHolder(None)
|
||||||
|
|
||||||
|
holder.testprop1 = 'testvalue'
|
||||||
|
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
holder.testprop1 = 'testvalue2'
|
||||||
|
|
||||||
|
def test_091_write_once_delete(self):
|
||||||
|
class MyTestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
|
||||||
|
testprop1 = qubes.property('testprop1', write_once=True)
|
||||||
|
holder = MyTestHolder(None)
|
||||||
|
|
||||||
|
holder.testprop1 = 'testvalue'
|
||||||
|
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
del holder.testprop1
|
||||||
|
|
||||||
|
|
||||||
class TestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
|
class TestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
|
||||||
testprop1 = qubes.property('testprop1', order=0)
|
testprop1 = qubes.property('testprop1', order=0)
|
||||||
|
@ -59,15 +59,16 @@ class TC_00_Column(qubes.tests.QubesTestCase):
|
|||||||
class TC_90_globals(qubes.tests.QubesTestCase):
|
class TC_90_globals(qubes.tests.QubesTestCase):
|
||||||
# @qubes.tests.skipUnlessDom0
|
# @qubes.tests.skipUnlessDom0
|
||||||
def test_100_simple_flag(self):
|
def test_100_simple_flag(self):
|
||||||
flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'qid')
|
flag = qubes.tools.qvm_ls.simple_flag(1, 'T', 'internal')
|
||||||
|
|
||||||
# TODO after serious testing of QubesVM and Qubes app, this should be
|
# TODO after serious testing of QubesVM and Qubes app, this should be
|
||||||
# using normal components
|
# using normal components
|
||||||
app = qubes.tests.vm.adminvm.TestApp()
|
app = qubes.tests.vm.adminvm.TestApp()
|
||||||
vm = qubes.vm.adminvm.AdminVM(app, None, qid=0, name='dom0')
|
vm = qubes.vm.adminvm.AdminVM(app, None,
|
||||||
|
qid=0, name='dom0', internal='False')
|
||||||
|
|
||||||
self.assertFalse(flag(None, vm))
|
self.assertFalse(flag(None, vm))
|
||||||
vm.qid = 1
|
vm.internal = 'True'
|
||||||
self.assertTrue(flag(None, vm))
|
self.assertTrue(flag(None, vm))
|
||||||
|
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
# type=bool, setter=qubes.property.bool,
|
# type=bool, setter=qubes.property.bool,
|
||||||
# doc='`True` if it is NetVM or ProxyVM, false otherwise.')
|
# doc='`True` if it is NetVM or ProxyVM, false otherwise.')
|
||||||
|
|
||||||
qid = qubes.property('qid', type=int,
|
qid = qubes.property('qid', type=int, write_once=True,
|
||||||
setter=_setter_qid,
|
setter=_setter_qid,
|
||||||
ls_width=3,
|
ls_width=3,
|
||||||
doc='''Internal, persistent identificator of particular domain. Note
|
doc='''Internal, persistent identificator of particular domain. Note
|
||||||
@ -164,7 +164,7 @@ class QubesVM(qubes.vm.BaseVM):
|
|||||||
ls_width=31,
|
ls_width=31,
|
||||||
doc='User-specified name of the domain.')
|
doc='User-specified name of the domain.')
|
||||||
|
|
||||||
uuid = qubes.property('uuid', type=uuid.UUID,
|
uuid = qubes.property('uuid', type=uuid.UUID, write_once=True,
|
||||||
ls_width=36,
|
ls_width=36,
|
||||||
doc='UUID from libvirt.')
|
doc='UUID from libvirt.')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user