Removed enum duplication from nova.compute
There are many state constants held within nova.compute. Because these now need to be used in versioned notifications, they were converted into enums within nova.objects.fields. In order to prevent duplication, the constants in nova.compute were converted over to use the enums created for the notifications. Also, because the comments in nova.compute.vm_states were not the best, they were rearranged to more appropriately fit the lines they were attempting to comment. The object hash changes are ignored as it is only caused by the new sentinel enum value _UNUSED which is not intended to be used. Change-Id: I4c147a1a1145295b6ccfe9680706abe34cb35170
This commit is contained in:
parent
8f35bb321d
commit
7e435fa8f3
@ -24,24 +24,23 @@ updated, and should also be updated at the end of a task if the task is
|
|||||||
supposed to affect power_state.
|
supposed to affect power_state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from nova.objects import fields
|
||||||
|
|
||||||
# NOTE(maoy): These are *not* virDomainState values from libvirt.
|
# NOTE(maoy): These are *not* virDomainState values from libvirt.
|
||||||
# The hex value happens to match virDomainState for backward-compatibility
|
# The hex value happens to match virDomainState for backward-compatibility
|
||||||
# reasons.
|
# reasons.
|
||||||
NOSTATE = 0x00
|
NOSTATE = fields.InstancePowerState.index(fields.InstancePowerState.NOSTATE)
|
||||||
RUNNING = 0x01
|
RUNNING = fields.InstancePowerState.index(fields.InstancePowerState.RUNNING)
|
||||||
PAUSED = 0x03
|
PAUSED = fields.InstancePowerState.index(fields.InstancePowerState.PAUSED)
|
||||||
SHUTDOWN = 0x04 # the VM is powered off
|
# the VM is powered off
|
||||||
CRASHED = 0x06
|
SHUTDOWN = fields.InstancePowerState.index(fields.InstancePowerState.SHUTDOWN)
|
||||||
SUSPENDED = 0x07
|
CRASHED = fields.InstancePowerState.index(fields.InstancePowerState.CRASHED)
|
||||||
|
SUSPENDED = fields.InstancePowerState.index(
|
||||||
|
fields.InstancePowerState.SUSPENDED)
|
||||||
|
|
||||||
# TODO(justinsb): Power state really needs to be a proper class,
|
# TODO(justinsb): Power state really needs to be a proper class,
|
||||||
# so that we're not locked into the libvirt status codes and can put mapping
|
# so that we're not locked into the libvirt status codes and can put mapping
|
||||||
# logic here rather than spread throughout the code
|
# logic here rather than spread throughout the code
|
||||||
STATE_MAP = {
|
STATE_MAP = {fields.InstancePowerState.index(state): state
|
||||||
NOSTATE: 'pending',
|
for state in fields.InstancePowerState.ALL
|
||||||
RUNNING: 'running',
|
if state != fields.InstancePowerState._UNUSED}
|
||||||
PAUSED: 'paused',
|
|
||||||
SHUTDOWN: 'shutdown',
|
|
||||||
CRASHED: 'crashed',
|
|
||||||
SUSPENDED: 'suspended',
|
|
||||||
}
|
|
||||||
|
@ -23,95 +23,99 @@ necessary.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from nova.objects import fields
|
||||||
|
|
||||||
# possible task states during create()
|
# possible task states during create()
|
||||||
SCHEDULING = 'scheduling'
|
SCHEDULING = fields.InstanceTaskState.SCHEDULING
|
||||||
BLOCK_DEVICE_MAPPING = 'block_device_mapping'
|
BLOCK_DEVICE_MAPPING = fields.InstanceTaskState.BLOCK_DEVICE_MAPPING
|
||||||
NETWORKING = 'networking'
|
NETWORKING = fields.InstanceTaskState.NETWORKING
|
||||||
SPAWNING = 'spawning'
|
SPAWNING = fields.InstanceTaskState.SPAWNING
|
||||||
|
|
||||||
# possible task states during snapshot()
|
# possible task states during snapshot()
|
||||||
IMAGE_SNAPSHOT = 'image_snapshot'
|
IMAGE_SNAPSHOT = fields.InstanceTaskState.IMAGE_SNAPSHOT
|
||||||
IMAGE_SNAPSHOT_PENDING = 'image_snapshot_pending'
|
IMAGE_SNAPSHOT_PENDING = fields.InstanceTaskState.IMAGE_SNAPSHOT_PENDING
|
||||||
IMAGE_PENDING_UPLOAD = 'image_pending_upload'
|
IMAGE_PENDING_UPLOAD = fields.InstanceTaskState.IMAGE_PENDING_UPLOAD
|
||||||
IMAGE_UPLOADING = 'image_uploading'
|
IMAGE_UPLOADING = fields.InstanceTaskState.IMAGE_UPLOADING
|
||||||
|
|
||||||
# possible task states during backup()
|
# possible task states during backup()
|
||||||
IMAGE_BACKUP = 'image_backup'
|
IMAGE_BACKUP = fields.InstanceTaskState.IMAGE_BACKUP
|
||||||
|
|
||||||
# possible task states during set_admin_password()
|
# possible task states during set_admin_password()
|
||||||
UPDATING_PASSWORD = 'updating_password'
|
UPDATING_PASSWORD = fields.InstanceTaskState.UPDATING_PASSWORD
|
||||||
|
|
||||||
# possible task states during resize()
|
# possible task states during resize()
|
||||||
RESIZE_PREP = 'resize_prep'
|
RESIZE_PREP = fields.InstanceTaskState.RESIZE_PREP
|
||||||
RESIZE_MIGRATING = 'resize_migrating'
|
RESIZE_MIGRATING = fields.InstanceTaskState.RESIZE_MIGRATING
|
||||||
RESIZE_MIGRATED = 'resize_migrated'
|
RESIZE_MIGRATED = fields.InstanceTaskState.RESIZE_MIGRATED
|
||||||
RESIZE_FINISH = 'resize_finish'
|
RESIZE_FINISH = fields.InstanceTaskState.RESIZE_FINISH
|
||||||
|
|
||||||
# possible task states during revert_resize()
|
# possible task states during revert_resize()
|
||||||
RESIZE_REVERTING = 'resize_reverting'
|
RESIZE_REVERTING = fields.InstanceTaskState.RESIZE_REVERTING
|
||||||
|
|
||||||
# possible task states during confirm_resize()
|
# possible task states during confirm_resize()
|
||||||
RESIZE_CONFIRMING = 'resize_confirming'
|
RESIZE_CONFIRMING = fields.InstanceTaskState.RESIZE_CONFIRMING
|
||||||
|
|
||||||
# possible task states during reboot()
|
# possible task states during reboot()
|
||||||
REBOOTING = 'rebooting'
|
REBOOTING = fields.InstanceTaskState.REBOOTING
|
||||||
REBOOT_PENDING = 'reboot_pending'
|
REBOOT_PENDING = fields.InstanceTaskState.REBOOT_PENDING
|
||||||
REBOOT_STARTED = 'reboot_started'
|
REBOOT_STARTED = fields.InstanceTaskState.REBOOT_STARTED
|
||||||
REBOOTING_HARD = 'rebooting_hard'
|
REBOOTING_HARD = fields.InstanceTaskState.REBOOTING_HARD
|
||||||
REBOOT_PENDING_HARD = 'reboot_pending_hard'
|
REBOOT_PENDING_HARD = fields.InstanceTaskState.REBOOT_PENDING_HARD
|
||||||
REBOOT_STARTED_HARD = 'reboot_started_hard'
|
REBOOT_STARTED_HARD = fields.InstanceTaskState.REBOOT_STARTED_HARD
|
||||||
|
|
||||||
# possible task states during pause()
|
# possible task states during pause()
|
||||||
PAUSING = 'pausing'
|
PAUSING = fields.InstanceTaskState.PAUSING
|
||||||
|
|
||||||
# possible task states during unpause()
|
# possible task states during unpause()
|
||||||
UNPAUSING = 'unpausing'
|
UNPAUSING = fields.InstanceTaskState.UNPAUSING
|
||||||
|
|
||||||
# possible task states during suspend()
|
# possible task states during suspend()
|
||||||
SUSPENDING = 'suspending'
|
SUSPENDING = fields.InstanceTaskState.SUSPENDING
|
||||||
|
|
||||||
# possible task states during resume()
|
# possible task states during resume()
|
||||||
RESUMING = 'resuming'
|
RESUMING = fields.InstanceTaskState.RESUMING
|
||||||
|
|
||||||
# possible task states during power_off()
|
# possible task states during power_off()
|
||||||
POWERING_OFF = 'powering-off'
|
POWERING_OFF = fields.InstanceTaskState.POWERING_OFF
|
||||||
|
|
||||||
# possible task states during power_on()
|
# possible task states during power_on()
|
||||||
POWERING_ON = 'powering-on'
|
POWERING_ON = fields.InstanceTaskState.POWERING_ON
|
||||||
|
|
||||||
# possible task states during rescue()
|
# possible task states during rescue()
|
||||||
RESCUING = 'rescuing'
|
RESCUING = fields.InstanceTaskState.RESCUING
|
||||||
|
|
||||||
# possible task states during unrescue()
|
# possible task states during unrescue()
|
||||||
UNRESCUING = 'unrescuing'
|
UNRESCUING = fields.InstanceTaskState.UNRESCUING
|
||||||
|
|
||||||
# possible task states during rebuild()
|
# possible task states during rebuild()
|
||||||
REBUILDING = 'rebuilding'
|
REBUILDING = fields.InstanceTaskState.REBUILDING
|
||||||
REBUILD_BLOCK_DEVICE_MAPPING = "rebuild_block_device_mapping"
|
REBUILD_BLOCK_DEVICE_MAPPING = \
|
||||||
REBUILD_SPAWNING = 'rebuild_spawning'
|
fields.InstanceTaskState.REBUILD_BLOCK_DEVICE_MAPPING
|
||||||
|
REBUILD_SPAWNING = fields.InstanceTaskState.REBUILD_SPAWNING
|
||||||
|
|
||||||
# possible task states during live_migrate()
|
# possible task states during live_migrate()
|
||||||
MIGRATING = "migrating"
|
MIGRATING = fields.InstanceTaskState.MIGRATING
|
||||||
|
|
||||||
# possible task states during delete()
|
# possible task states during delete()
|
||||||
DELETING = 'deleting'
|
DELETING = fields.InstanceTaskState.DELETING
|
||||||
|
|
||||||
# possible task states during soft_delete()
|
# possible task states during soft_delete()
|
||||||
SOFT_DELETING = 'soft-deleting'
|
SOFT_DELETING = fields.InstanceTaskState.SOFT_DELETING
|
||||||
|
|
||||||
# possible task states during restore()
|
# possible task states during restore()
|
||||||
RESTORING = 'restoring'
|
RESTORING = fields.InstanceTaskState.RESTORING
|
||||||
|
|
||||||
# possible task states during shelve()
|
# possible task states during shelve()
|
||||||
SHELVING = 'shelving'
|
SHELVING = fields.InstanceTaskState.SHELVING
|
||||||
SHELVING_IMAGE_PENDING_UPLOAD = 'shelving_image_pending_upload'
|
SHELVING_IMAGE_PENDING_UPLOAD = \
|
||||||
SHELVING_IMAGE_UPLOADING = 'shelving_image_uploading'
|
fields.InstanceTaskState.SHELVING_IMAGE_PENDING_UPLOAD
|
||||||
|
SHELVING_IMAGE_UPLOADING = fields.InstanceTaskState.SHELVING_IMAGE_UPLOADING
|
||||||
|
|
||||||
# possible task states during shelve_offload()
|
# possible task states during shelve_offload()
|
||||||
SHELVING_OFFLOADING = 'shelving_offloading'
|
SHELVING_OFFLOADING = fields.InstanceTaskState.SHELVING_OFFLOADING
|
||||||
|
|
||||||
# possible task states during unshelve()
|
# possible task states during unshelve()
|
||||||
UNSHELVING = 'unshelving'
|
UNSHELVING = fields.InstanceTaskState.UNSHELVING
|
||||||
|
|
||||||
ALLOW_REBOOT = [None, REBOOTING, REBOOT_PENDING, REBOOT_STARTED, RESUMING,
|
ALLOW_REBOOT = [None, REBOOTING, REBOOT_PENDING, REBOOT_STARTED, RESUMING,
|
||||||
REBOOTING_HARD, UNPAUSING, PAUSING, SUSPENDING]
|
REBOOTING_HARD, UNPAUSING, PAUSING, SUSPENDING]
|
||||||
|
@ -27,32 +27,52 @@ health and progress.
|
|||||||
See http://wiki.openstack.org/VMState
|
See http://wiki.openstack.org/VMState
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ACTIVE = 'active' # VM is running
|
from nova.objects import fields
|
||||||
BUILDING = 'building' # VM only exists in DB
|
|
||||||
PAUSED = 'paused'
|
|
||||||
SUSPENDED = 'suspended' # VM is suspended to disk.
|
|
||||||
STOPPED = 'stopped' # VM is powered off, the disk image is still there.
|
|
||||||
RESCUED = 'rescued' # A rescue image is running with the original VM image
|
|
||||||
# attached.
|
|
||||||
RESIZED = 'resized' # a VM with the new size is active. The user is expected
|
|
||||||
# to manually confirm or revert.
|
|
||||||
|
|
||||||
SOFT_DELETED = 'soft-delete' # VM is marked as deleted but the disk images are
|
|
||||||
# still available to restore.
|
|
||||||
DELETED = 'deleted' # VM is permanently deleted.
|
|
||||||
|
|
||||||
ERROR = 'error'
|
# VM is running
|
||||||
|
ACTIVE = fields.InstanceState.ACTIVE
|
||||||
|
|
||||||
SHELVED = 'shelved' # VM is powered off, resources still on hypervisor
|
# VM only exists in DB
|
||||||
SHELVED_OFFLOADED = 'shelved_offloaded' # VM and associated resources are
|
BUILDING = fields.InstanceState.BUILDING
|
||||||
# not on hypervisor
|
|
||||||
|
PAUSED = fields.InstanceState.PAUSED
|
||||||
|
|
||||||
|
# VM is suspended to disk.
|
||||||
|
SUSPENDED = fields.InstanceState.SUSPENDED
|
||||||
|
|
||||||
|
# VM is powered off, the disk image is still there.
|
||||||
|
STOPPED = fields.InstanceState.STOPPED
|
||||||
|
|
||||||
|
# A rescue image is running with the original VM image attached
|
||||||
|
RESCUED = fields.InstanceState.RESCUED
|
||||||
|
|
||||||
|
# a VM with the new size is active. The user is expected to manually confirm
|
||||||
|
# or revert.
|
||||||
|
RESIZED = fields.InstanceState.RESIZED
|
||||||
|
|
||||||
|
# VM is marked as deleted but the disk images are still available to restore.
|
||||||
|
SOFT_DELETED = fields.InstanceState.SOFT_DELETED
|
||||||
|
|
||||||
|
# VM is permanently deleted.
|
||||||
|
DELETED = fields.InstanceState.DELETED
|
||||||
|
|
||||||
|
ERROR = fields.InstanceState.ERROR
|
||||||
|
|
||||||
|
# VM is powered off, resources still on hypervisor
|
||||||
|
SHELVED = fields.InstanceState.SHELVED
|
||||||
|
|
||||||
|
# VM and associated resources are not on hypervisor
|
||||||
|
SHELVED_OFFLOADED = fields.InstanceState.SHELVED_OFFLOADED
|
||||||
|
|
||||||
|
# states we can soft reboot from
|
||||||
|
ALLOW_SOFT_REBOOT = [ACTIVE]
|
||||||
|
|
||||||
ALLOW_SOFT_REBOOT = [ACTIVE] # states we can soft reboot from
|
|
||||||
ALLOW_HARD_REBOOT = ALLOW_SOFT_REBOOT + [STOPPED, PAUSED, SUSPENDED, ERROR]
|
|
||||||
# states we allow hard reboot from
|
# states we allow hard reboot from
|
||||||
|
ALLOW_HARD_REBOOT = ALLOW_SOFT_REBOOT + [STOPPED, PAUSED, SUSPENDED, ERROR]
|
||||||
|
|
||||||
ALLOW_TRIGGER_CRASH_DUMP = [ACTIVE, PAUSED, RESCUED, RESIZED, ERROR]
|
|
||||||
# states we allow to trigger crash dump
|
# states we allow to trigger crash dump
|
||||||
|
ALLOW_TRIGGER_CRASH_DUMP = [ACTIVE, PAUSED, RESCUED, RESIZED, ERROR]
|
||||||
|
|
||||||
ALLOW_RESOURCE_REMOVAL = [DELETED, SHELVED_OFFLOADED]
|
|
||||||
# states we allow resources to be freed in
|
# states we allow resources to be freed in
|
||||||
|
ALLOW_RESOURCE_REMOVAL = [DELETED, SHELVED_OFFLOADED]
|
||||||
|
@ -522,9 +522,10 @@ class NotificationAction(BaseNovaEnum):
|
|||||||
POWER_OFF, SHELVE, RESUME, RESTORE)
|
POWER_OFF, SHELVE, RESUME, RESTORE)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(rlrossit): These should be changed over to be a StateMachine enum from
|
||||||
|
# oslo.versionedobjects using the valid state transitions described in
|
||||||
|
# nova.compute.vm_states
|
||||||
class InstanceState(BaseNovaEnum):
|
class InstanceState(BaseNovaEnum):
|
||||||
# TODO(gibi): this is currently a copy of nova.compute.vm_states, remove
|
|
||||||
# the duplication
|
|
||||||
ACTIVE = 'active'
|
ACTIVE = 'active'
|
||||||
BUILDING = 'building'
|
BUILDING = 'building'
|
||||||
PAUSED = 'paused'
|
PAUSED = 'paused'
|
||||||
@ -542,9 +543,10 @@ class InstanceState(BaseNovaEnum):
|
|||||||
SOFT_DELETED, DELETED, ERROR, SHELVED, SHELVED_OFFLOADED)
|
SOFT_DELETED, DELETED, ERROR, SHELVED, SHELVED_OFFLOADED)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(rlrossit): These should be changed over to be a StateMachine enum from
|
||||||
|
# oslo.versionedobjects using the valid state transitions described in
|
||||||
|
# nova.compute.task_states
|
||||||
class InstanceTaskState(BaseNovaEnum):
|
class InstanceTaskState(BaseNovaEnum):
|
||||||
# TODO(gibi): this is currently a copy of nova.compute.task_states, remove
|
|
||||||
# the duplication
|
|
||||||
SCHEDULING = 'scheduling'
|
SCHEDULING = 'scheduling'
|
||||||
BLOCK_DEVICE_MAPPING = 'block_device_mapping'
|
BLOCK_DEVICE_MAPPING = 'block_device_mapping'
|
||||||
NETWORKING = 'networking'
|
NETWORKING = 'networking'
|
||||||
@ -601,35 +603,49 @@ class InstanceTaskState(BaseNovaEnum):
|
|||||||
SHELVING_OFFLOADING, UNSHELVING)
|
SHELVING_OFFLOADING, UNSHELVING)
|
||||||
|
|
||||||
|
|
||||||
class InstancePowerState(BaseNovaEnum):
|
class InstancePowerState(Enum):
|
||||||
# TODO(gibi): this is currently a copy of nova.compute.power_state, remove
|
_UNUSED = '_unused'
|
||||||
# the duplication
|
|
||||||
NOSTATE = 'pending'
|
NOSTATE = 'pending'
|
||||||
RUNNING = 'running'
|
RUNNING = 'running'
|
||||||
PAUSED = 'paused'
|
PAUSED = 'paused'
|
||||||
SHUTDOWN = 'shutdown'
|
SHUTDOWN = 'shutdown'
|
||||||
CRASHED = 'crashed'
|
CRASHED = 'crashed'
|
||||||
SUSPENDED = 'suspended'
|
SUSPENDED = 'suspended'
|
||||||
|
# The order is important here. If you make changes, only *append*
|
||||||
|
# values to the end of the list.
|
||||||
|
ALL = (
|
||||||
|
NOSTATE,
|
||||||
|
RUNNING,
|
||||||
|
_UNUSED,
|
||||||
|
PAUSED,
|
||||||
|
SHUTDOWN,
|
||||||
|
_UNUSED,
|
||||||
|
CRASHED,
|
||||||
|
SUSPENDED,
|
||||||
|
)
|
||||||
|
|
||||||
VALUE_MAP = {
|
def __init__(self):
|
||||||
0x00: NOSTATE,
|
super(InstancePowerState, self).__init__(
|
||||||
0x01: RUNNING,
|
valid_values=InstancePowerState.ALL)
|
||||||
0x03: PAUSED,
|
|
||||||
0x04: SHUTDOWN,
|
|
||||||
0x06: CRASHED,
|
|
||||||
0x07: SUSPENDED
|
|
||||||
}
|
|
||||||
|
|
||||||
ALL = (NOSTATE, RUNNING, PAUSED, SHUTDOWN, CRASHED, SUSPENDED)
|
|
||||||
|
|
||||||
def coerce(self, obj, attr, value):
|
def coerce(self, obj, attr, value):
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
value = InstancePowerState.VALUE_MAP[value]
|
value = self.from_index(value)
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
pass
|
pass
|
||||||
return super(InstancePowerState, self).coerce(obj, attr, value)
|
return super(InstancePowerState, self).coerce(obj, attr, value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def index(cls, value):
|
||||||
|
"""Return an index into the Enum given a value."""
|
||||||
|
return cls.ALL.index(value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_index(cls, index):
|
||||||
|
"""Return the Enum value at a given index."""
|
||||||
|
return cls.ALL[index]
|
||||||
|
|
||||||
|
|
||||||
class IPV4AndV6Address(IPAddress):
|
class IPV4AndV6Address(IPAddress):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -264,11 +264,11 @@ notification_object_data = {
|
|||||||
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
|
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
|
||||||
'FlavorPayload': '1.0-8ad962ab0bafc7270f474c7dda0b7c20',
|
'FlavorPayload': '1.0-8ad962ab0bafc7270f474c7dda0b7c20',
|
||||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||||
'InstanceActionPayload': '1.0-aa6a322cf1a3a19d090259fee65d1094',
|
'InstanceActionPayload': '1.0-d94994d6043bb87fde603976ce811cba',
|
||||||
'InstancePayload': '1.0-878bbc5a7a20bdeac7c6570f438a53aa',
|
'InstancePayload': '1.0-4473793aa2a0a4083d328847f3ab638a',
|
||||||
'InstanceStateUpdatePayload': '1.0-a934d04e1b314318e42e8062647edd11',
|
'InstanceStateUpdatePayload': '1.0-a934d04e1b314318e42e8062647edd11',
|
||||||
'InstanceUpdateNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
'InstanceUpdateNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||||
'InstanceUpdatePayload': '1.0-c69e17e00400455bfe602e5573a61f0b',
|
'InstanceUpdatePayload': '1.0-2e21e6950fbb04e701e54e8563a21dbc',
|
||||||
'IpPayload': '1.0-26b40117c41ed95a61ae104f0fcb5fdc',
|
'IpPayload': '1.0-26b40117c41ed95a61ae104f0fcb5fdc',
|
||||||
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
|
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
|
||||||
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||||
|
Loading…
Reference in New Issue
Block a user