launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #12637
[Merge] lp:~jtv/maas/db-persistent-errors into lp:maas
Jeroen T. Vermeulen has proposed merging lp:~jtv/maas/db-persistent-errors into lp:maas.
Commit message:
Make the persistent-errors system database-backed.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jtv/maas/db-persistent-errors/+merge/127001
Discussed with Julian. Raphael suggested using the Django cache mechanism with a database backend, but this ran into two snags:
1. Timeouts. There is no way to specify that a value should last until we say otherwise. And the timeout field has a weird irregular interpretation: below some threshold it's a number of seconds (not timedelta, oh no) and above the threshold it's some representation of a date!
2. Cache keys can't be queried; the sensible way to do it would be to store the whole persistent-errors dict in the cache as one value. But then we've got a data structure in there, and the structure may change while old data survives. We already have an effective way to deal with that problem: schema migration.
The new model is basically just a relational representation of a dict. All access goes through components.py, which is covered by thorough tests that need no substantial changes.
If we're worried about heavy write traffic to the new table, we can avoid the delete/insert dance when storing errors that are already stored. For now, however, this is not an issue. Nor is there any point in trying to use updates instead of delete/inserts: in an MVCC database such as postgres, an update is still a delete/insert pair internally and it works very nicely, thank you.
A follow-up branch will tweak the initialization of the two persistent errors for the import script.
Jeroen
--
https://code.launchpad.net/~jtv/maas/db-persistent-errors/+merge/127001
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/maas/db-persistent-errors into lp:maas.
=== modified file 'src/maasserver/components.py'
--- src/maasserver/components.py 2012-08-16 11:11:16 +0000
+++ src/maasserver/components.py 2012-09-28 15:47:25 +0000
@@ -16,7 +16,7 @@
"register_persistent_error",
]
-import threading
+from maasserver.models import ComponentError
class COMPONENT:
@@ -24,25 +24,14 @@
IMPORT_PXE_FILES = 'maas-import-pxe-files script'
-# Persistent errors are global to a MAAS instance.
-# This is a mapping: component -> error message.
-_PERSISTENT_ERRORS = {}
-
-
-_PERSISTENT_ERRORS_LOCK = threading.Lock()
+def discard_persistent_error(component):
+ ComponentError.objects.filter(component=component).delete()
def register_persistent_error(component, error_message):
- with _PERSISTENT_ERRORS_LOCK:
- global _PERSISTENT_ERRORS
- _PERSISTENT_ERRORS[component] = error_message
-
-
-def discard_persistent_error(component):
- with _PERSISTENT_ERRORS_LOCK:
- global _PERSISTENT_ERRORS
- _PERSISTENT_ERRORS.pop(component, None)
+ discard_persistent_error(component)
+ ComponentError.objects.create(component=component, error=error_message)
def get_persistent_errors():
- return _PERSISTENT_ERRORS.values()
+ return sorted(err.error for err in ComponentError.objects.all())
=== added file 'src/maasserver/migrations/0031_componenterror.py'
--- src/maasserver/migrations/0031_componenterror.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0031_componenterror.py 2012-09-28 15:47:25 +0000
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+import datetime
+
+from django.db import models
+from south.db import db
+from south.v2 import SchemaMigration
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'ComponentError'
+ db.create_table(u'maasserver_componenterror', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('component', self.gf('django.db.models.fields.CharField')(unique=True, max_length=40)),
+ ('error', self.gf('django.db.models.fields.CharField')(max_length=1000)),
+ ))
+ db.send_create_signal(u'maasserver', ['ComponentError'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'ComponentError'
+ db.delete_table(u'maasserver_componenterror')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'maasserver.bootimage': {
+ 'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ u'maasserver.componenterror': {
+ 'Meta': {'object_name': 'ComponentError'},
+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ u'maasserver.config': {
+ 'Meta': {'object_name': 'Config'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
+ },
+ u'maasserver.dhcplease': {
+ 'Meta': {'object_name': 'DHCPLease'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
+ },
+ u'maasserver.filestorage': {
+ 'Meta': {'object_name': 'FileStorage'},
+ 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
+ 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ u'maasserver.macaddress': {
+ 'Meta': {'object_name': 'MACAddress'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'maasserver.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386'", 'max_length': '10'}),
+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-0de6dbce-0979-11e2-ac17-002608dc6120'", 'unique': 'True', 'max_length': '41'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'maasserver.nodegroup': {
+ 'Meta': {'object_name': 'NodeGroup'},
+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
+ },
+ u'maasserver.nodegroupinterface': {
+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
+ 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+ 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
+ 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
+ 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'maasserver.sshkey': {
+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.TextField', [], {}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ u'maasserver.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'definition': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'maasserver.userprofile': {
+ 'Meta': {'object_name': 'UserProfile'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
+ },
+ 'piston.consumer': {
+ 'Meta': {'object_name': 'Consumer'},
+ 'description': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"})
+ },
+ 'piston.token': {
+ 'Meta': {'object_name': 'Token'},
+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1348842630L'}),
+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
+ }
+ }
+
+ complete_apps = ['maasserver']
\ No newline at end of file
=== modified file 'src/maasserver/models/__init__.py'
--- src/maasserver/models/__init__.py 2012-09-21 13:18:13 +0000
+++ src/maasserver/models/__init__.py 2012-09-28 15:47:25 +0000
@@ -12,6 +12,7 @@
__metaclass__ = type
__all__ = [
'BootImage',
+ 'ComponentError',
'Config',
'DHCPLease',
'FileStorage',
@@ -38,6 +39,7 @@
from django.db.models.signals import post_save
from maasserver.enum import NODE_PERMISSION
from maasserver.models.bootimage import BootImage
+from maasserver.models.component_error import ComponentError
from maasserver.models.config import Config
from maasserver.models.dhcplease import DHCPLease
from maasserver.models.filestorage import FileStorage
@@ -60,8 +62,8 @@
# Suppress warning about symbols being imported, but only used for
# export in __all__.
ignore_unused(
- Config, DHCPLease, FileStorage, MACAddress, NodeGroup, SSHKey,
- Tag, UserProfile, NodeGroupInterface)
+ ComponentError, Config, DHCPLease, FileStorage, MACAddress, NodeGroup,
+ SSHKey, Tag, UserProfile, NodeGroupInterface)
# Connect the 'create_user' method to the post save signal of User.
=== added file 'src/maasserver/models/component_error.py'
--- src/maasserver/models/component_error.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/component_error.py 2012-09-28 15:47:25 +0000
@@ -0,0 +1,38 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Persistent component errors."""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = [
+ 'ComponentError',
+ ]
+
+
+from django.db.models import (
+ CharField,
+ Model,
+ )
+from maasserver import DefaultMeta
+
+
+class ComponentError(Model):
+ """Error state of a major component of the system."""
+
+ class Meta(DefaultMeta):
+ """Needed for South to recognize this model."""
+
+ # A descriptor for the failing component, as in the COMPONENT enum.
+ # This is a failure state for an out-of-process component. We won't
+ # know much about what's wrong, and we don't support multiple errors
+ # for a single component.
+ component = CharField(max_length=40, unique=True, blank=False)
+
+ # Human-readable description of what's wrong.
+ error = CharField(max_length=1000, blank=False)
=== modified file 'src/maasserver/tests/test_components.py'
--- src/maasserver/tests/test_components.py 2012-04-30 16:26:38 +0000
+++ src/maasserver/tests/test_components.py 2012-09-28 15:47:25 +0000
@@ -15,7 +15,6 @@
import random
-from maasserver import components
from maasserver.components import (
COMPONENT,
discard_persistent_error,
@@ -32,22 +31,19 @@
def get_random_component():
- random.choice(map_enum(COMPONENT).values())
+ return random.choice(map_enum(COMPONENT).values())
class PersistentErrorsUtilitiesTest(TestCase):
def setUp(self):
super(PersistentErrorsUtilitiesTest, self).setUp()
- self._PERSISTENT_ERRORS = {}
- self.patch(components, '_PERSISTENT_ERRORS', self._PERSISTENT_ERRORS)
def test_register_persistent_error_registers_error(self):
error_message = factory.getRandomString()
component = get_random_component()
register_persistent_error(component, error_message)
- self.assertItemsEqual(
- {component: error_message}, self._PERSISTENT_ERRORS)
+ self.assertItemsEqual([error_message], get_persistent_errors())
def test_register_persistent_error_stores_last_error(self):
error_message = factory.getRandomString()
@@ -56,14 +52,14 @@
register_persistent_error(component, error_message)
register_persistent_error(component, error_message2)
self.assertItemsEqual(
- {component: error_message2}, self._PERSISTENT_ERRORS)
+ [error_message2], get_persistent_errors())
def test_discard_persistent_error_discards_error(self):
error_message = factory.getRandomString()
component = get_random_component()
register_persistent_error(component, error_message)
discard_persistent_error(component)
- self.assertItemsEqual({}, self._PERSISTENT_ERRORS)
+ self.assertItemsEqual([], get_persistent_errors())
def test_discard_persistent_error_can_be_called_many_times(self):
error_message = factory.getRandomString()
@@ -71,7 +67,7 @@
register_persistent_error(component, error_message)
discard_persistent_error(component)
discard_persistent_error(component)
- self.assertItemsEqual({}, self._PERSISTENT_ERRORS)
+ self.assertItemsEqual([], get_persistent_errors())
def get_persistent_errors_returns_text_for_error_codes(self):
errors, components = [], []
=== modified file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py 2012-09-11 08:28:51 +0000
+++ src/maasserver/tests/test_views.py 2012-09-28 15:47:25 +0000
@@ -23,7 +23,6 @@
from django.test.client import RequestFactory
from django.utils.html import escape
from lxml.html import fromstring
-from maasserver import components
from maasserver.components import register_persistent_error
from maasserver.exceptions import ExternalComponentException
from maasserver.testing import extract_redirect
@@ -280,7 +279,6 @@
class PermanentErrorDisplayTest(LoggedInTestCase):
def test_permanent_error_displayed(self):
- self.patch(components, '_PERSISTENT_ERRORS', {})
fault_codes = [
randint(1, 100),
randint(101, 200),
@@ -289,8 +287,8 @@
for fault in fault_codes:
# Create component with getRandomString to be sure
# to display all the errors.
- component = factory.getRandomString()
- error_message = factory.getRandomString()
+ component = factory.make_name('component')
+ error_message = factory.make_name('error')
error = Fault(fault, error_message)
errors.append(error)
register_persistent_error(component, error_message)