Adding an invalid tag (one with a dot) to a machine in the Web UI silently fails. This is printed to the log when that happens:
Jan 07 10:08:41 maas-controller sh[1368]: 2020-01-07 10:08:41 maasserver.websockets.protocol: [critical] Error on request (63) machine.update: {"name": ["Enter a valid value."]}
Jan 07 10:08:41 maas-controller sh[1368]: Traceback (most recent call last):
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3.6/threading.py", line 864, in run
Jan 07 10:08:41 maas-controller sh[1368]: self._target(*self._args, **self._kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/provisioningserver/utils/twisted.py", line 850, in worker
Jan 07 10:08:41 maas-controller sh[1368]: return target()
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/twisted/_threads/_threadworker.py", line 46, in work
Jan 07 10:08:41 maas-controller sh[1368]: task()
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/twisted/_threads/_team.py", line 190, in doWork
Jan 07 10:08:41 maas-controller sh[1368]: task()
Jan 07 10:08:41 maas-controller sh[1368]: --- <exception caught here> ---
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/twisted/python/threadpool.py", line 250, in inContext
Jan 07 10:08:41 maas-controller sh[1368]: result = inContext.theWork()
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/twisted/python/threadpool.py", line 266, in <lambda>
Jan 07 10:08:41 maas-controller sh[1368]: inContext.theWork = lambda: context.call(ctx, func, *args, **kw)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/twisted/python/context.py", line 122, in callWithContext
Jan 07 10:08:41 maas-controller sh[1368]: return self.currentContext().callWithContext(ctx, func, *args, **kw)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/twisted/python/context.py", line 85, in callWithContext
Jan 07 10:08:41 maas-controller sh[1368]: return func(*args,**kw)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/provisioningserver/utils/twisted.py", line 883, in callInContext
Jan 07 10:08:41 maas-controller sh[1368]: return func(*args, **kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/provisioningserver/utils/twisted.py", line 232, in wrapper
Jan 07 10:08:41 maas-controller sh[1368]: result = func(*args, **kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/maasserver/utils/orm.py", line 756, in call_within_transaction
Jan 07 10:08:41 maas-controller sh[1368]: return func_outside_txn(*args, **kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/maasserver/utils/orm.py", line 563, in retrier
Jan 07 10:08:41 maas-controller sh[1368]: return func(*args, **kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3.6/contextlib.py", line 52, in inner
Jan 07 10:08:41 maas-controller sh[1368]: return func(*args, **kwds)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/maasserver/websockets/handlers/machine.py", line 369, in update
Jan 07 10:08:41 maas-controller sh[1368]: self.update_tags(node_obj, params['tags'])
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/maasserver/websockets/handlers/node.py", line 733, in update_tags
Jan 07 10:08:41 maas-controller sh[1368]: tag_obj, _ = Tag.objects.get_or_create(name=tag_name)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/django/db/models/manager.py", line 85, in manager_method
Jan 07 10:08:41 maas-controller sh[1368]: return getattr(self.get_queryset(), name)(*args, **kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/django/db/models/query.py", line 466, in get_or_create
Jan 07 10:08:41 maas-controller sh[1368]: return self._create_object_from_params(lookup, params)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/django/db/models/query.py", line 498, in _create_object_from_params
Jan 07 10:08:41 maas-controller sh[1368]: obj = self.create(**params)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/django/db/models/query.py", line 394, in create
Jan 07 10:08:41 maas-controller sh[1368]: obj.save(force_insert=True, using=self.db)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/maasserver/models/tag.py", line 151, in save
Jan 07 10:08:41 maas-controller sh[1368]: super(Tag, self).save(*args, **kwargs)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/maasserver/models/cleansave.py", line 212, in save
Jan 07 10:08:41 maas-controller sh[1368]: validate_unique=False)
Jan 07 10:08:41 maas-controller sh[1368]: File "/usr/lib/python3/dist-packages/django/db/models/base.py", line 1250, in full_clean
Jan 07 10:08:41 maas-controller sh[1368]: raise ValidationError(errors)
Jan 07 10:08:41 maas-controller sh[1368]: django.core.exceptions.ValidationError: {'name': ['Enter a valid value.']}
Form now shows an error returned from server if tag name is invalid