Documentation ¶
Overview ¶
Package session provides support for robot session management.
When working with robots, we want a protocol and system-wide means to be able to understand the presence of a client connected to a robot. This provides for safer operation scenarios when dealing with actuating controls. Specifically, without this, controls that would be "sticky" (e.g. SetPower of a base) based on the last input of a client, can have a robot try to continue what it was told to do forever. These clients range from SDK scripts, input controllers, and robots talking amongst themselves.
Summary ¶
Part of the solution to this is session management. A session, as defined here, is a presence mechanism at the application layer (i.e. RDK, not TCP) maintained by a client (e.g. SDK) with a server (e.g. RDK). Since the client maintains the session, it is responsible for telling the server it is still present every so often; this will be called staying within the heartbeat window. The client must send at least one session heartbeat within this window. As soon as the window lapses/expires, the server will safely stop all resources that are marked for safety monitoring that have been last used by that session, and no others; a lapsed client will attempt to establish a new session immediately prior to the next operation it performs.
Goals of session management ¶
Session management is opt-in from a protocol perspective
If an SDK doesn't start/maintain a session and it disconnects, the server will not Stop anything in response to that.
Once implemented in an SDK, session management is opt-out from the client usage perspective.
Users will need to provide options to disable it in order to acknowledge the safety risk
Sessions are only maintained between a client and server.
Sessions are bound to metadata, not any kind of TCP connection, in order to support many ways to maintain a session.
The heartbeat window has a sensible default of 2s but is user configurable between 30ms and 1min.
Authenticated users can only use the sessions they create and no others.
Remotes will respond back to parents with resources that were safety monitored in the request such that they can be stopped if the parent session expires.
The remotes will not know about the parent session and its details.
Adding a new component/service makes it easier to describe which of its gRPC methods are safety monitored.
API ¶
The API for session management is supported by four parts in Protobuf/gRPC
- The "RobotService" exposes a StartSession and SendSessionHeartbeat in order to facilitate the construction and maintenance of a session. (see robot.proto)
- All methods in gRPC will accept a "viam-sid" session ID metadata header to indicate that the method to be invoked is bound to a session and may be safety monitored depending on the resource associated with the request.
- All methods in gRPC will be able to opt into safety monitoring via the common.v1.safety_heartbeat_monitored extension boolean. (see common.proto and base.proto)
- All responses to methods in gRPC may return a metadata header of "viam-smrn" to indicate a list of resources that should be safety monitored in the event the caller has its own delegated session management.
SDK Client Implementation Notes ¶
This assumes that the SDK has a concept of a gRPC Transport (either Direct or WebRTC based) with client interceptor support.
The interceptor should be split up into two parts, the session heatbeater/manager and session interceptor.
Client Session Heartbeater/Manager ¶
The manager should implement a session metadata method that returns the current session ID for all methods except:
/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo /proto.rpc.webrtc.v1.SignalingService/Call /proto.rpc.webrtc.v1.SignalingService/CallUpdate /proto.rpc.webrtc.v1.SignalingService/OptionalWebRTCConfig /proto.rpc.v1.AuthService/Authenticate /viam.robot.v1.RobotService/ResourceNames /viam.robot.v1.RobotService/ResourceRPCSubtypes /viam.robot.v1.RobotService/StartSession /viam.robot.v1.RobotService/SendSessionHeartbeat
If the current session ID does not exist, the method should StartSession and start a background heartbeater that runs at an interval of StartSession's response heartbeat_window at a factor of 5. The heartbeater itself should send SendSessionHeartbeat requests at that interval. If the heartbeat request ever fails due to an Unavailable gRPC code, the session ID should be unset and the background routine stopped in order to signal to the main thread that a new session is required. Once a new session is required, this process will restart.
Client Session Interceptor ¶
The interceptor should call the manager's session metadata method and use the return value to fill the "viam-sid" metadata header. If the invoked gRPC call fails with code "InvalidArgument" and message "SESSION_EXPIRED", the session should be reset and the request should be retried. If it is too difficult to support retry functionality (particularly for streams that would need to buffer messages), then it is okay to leave out and propagate the "SESSION_EXPIRED" error.
Resetting a session means to simply start a new session on the next valid intercepted call. In the event of a disconnection, the heartbeater stops as mentioned above but it is possible that the session is still alive on the server. In that case, it would be nice to preserve that session so the client should attempt to start a session again with the resume field set to the last session. This will end up heartbeating it if it exists; if it does not exist, a new session must be returned.
Client Session Options ¶
By default, this feature is opt-in, but you must provide a client option to disable session heartbeating.
Client Session Implementations ¶
Starting points:
SDK Server Implementation Notes ¶
This assumes that the server has a concept of gRPC server interceptors.
The interceptor should be split up into two parts, the session heatbeater/manager and session interceptor.
Server Session Manager ¶
The session manager is responsible for keeping track of all sessions in addition to which session was the last to be associated with a safety monitored resource. It should check session expiration on an interval less than or equal to the minimum heartbeat window (e.g. 1ms). When a session expires, it should Stop all resources it was associated with having their safety monitored methods called.
Server Session Interceptor ¶
The interceptor should exempt the following methods:
/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo /proto.rpc.webrtc.v1.SignalingService/Call /proto.rpc.webrtc.v1.SignalingService/CallUpdate /proto.rpc.webrtc.v1.SignalingService/OptionalWebRTCConfig /proto.rpc.v1.AuthService/Authenticate /viam.robot.v1.RobotService/ResourceNames /viam.robot.v1.RobotService/ResourceRPCSubtypes /viam.robot.v1.RobotService/StartSession /viam.robot.v1.RobotService/SendSessionHeartbeat
Before handling the method request, the interceptor must do two things:
- Check if the session passed in is still alive. If it is not, a gRPC error with code "InvalidArgument" and message "SESSION_EXPIRED" should be returned.
- Detect the resource being referenced and check if its proto method description has the extension boolean mentioned above set to true. If it does not, the method should be invoked. If it does, the resource should be associated with the current session. If there is no current session, the resource should have its last associated session cleared in the manager.
Additionally, where possible, the interceptor should pass to the invoked handler, access to the session manager to associate resources indirectly accessed that need to be safety monitored. For example, if invoking an input controller method via gRPC, the implementation of the controller needs to tell the manager what resources it's about to control. In the go implementation, we provide a session.SafetyMonitor method that accepts a request context and a resource name to monitor.
Note: When gRPC server streaming is used, the safety monitored resources must be returned before the first response is sent out. This is due to limitations in not being able to send response headers during a gRPC response.
Server Session Implementations ¶
Starting points:
Remote Robot Considerations ¶
When connecting to a remote robot, the underlying client will maintain its own, single, session that is separated from the session of some end user calling a method into the main robot. Because of how the interceptor works, we will still mark a remote resource name as being safety monitored. Furthermore, the remotely invoked method can return metadata for the caller to associate up into the session that ultimately invoked it. This provides for two facets of safety. If the end user disconnects, its session will expire and the safety monitored resources it accessed last (if no one else accessed them) will be stopped. If the robot disconnects or crashes from the remote, then the remote robot will have the remote session be expired and also terminate all resources that the connecting robot had accessed last in the same vein.
Security Considerations ¶
- Since the loss of a session can result in stopping moves to components, which we would consider an authorized action, then sessions must be associated with the authorization subject, if present. That means if there two subjects, Alice and Bob, and Alice starts Session 1 (S1) and Bob Session 2 (S2), then it must be forbidden that Bob can send heartbeats and attach session metadata for Alice's session (and vice versa).
Index ¶
- Constants
- Variables
- func SafetyMonitor(ctx context.Context, target resource.Resource)
- func SafetyMonitorResourceName(ctx context.Context, targetName resource.Name)
- func ToContext(ctx context.Context, sess *Session) context.Context
- type Manager
- type ServerInterceptors
- type Session
- func (s *Session) Active(at time.Time) bool
- func (s *Session) CheckOwnerID(against string) bool
- func (s *Session) Deadline() time.Time
- func (s *Session) Heartbeat()
- func (s *Session) HeartbeatWindow() time.Duration
- func (s *Session) ID() uuid.UUID
- func (s *Session) PeerConnectionInfo() *pb.PeerConnectionInfo
Constants ¶
const ( // IDMetadataKey is the gRPC metadata key to use when transmitting session information. IDMetadataKey = "viam-sid" // SafetyMonitoredResourceMetadataKey is the gRPC metadata key to use when transmitting // safety monitored resource names in a response. SafetyMonitoredResourceMetadataKey = "viam-smrn" )
Variables ¶
var ( // StatusNoSession is returned when a session has expired or does not exist. StatusNoSession = status.New(codes.InvalidArgument, "SESSION_EXPIRED") // ErrNoSession is returned when a session has expired or does not exist. ErrNoSession = StatusNoSession.Err() )
Functions ¶
func SafetyMonitor ¶
SafetyMonitor signals to the session, if present, that the given target should be safety monitored so that if the session ends and this session was the last to monitor the target, it will attempt to be stopped. Note: This not be called by a resource being monitored itself but instead by another resource or call site that is controlling a resource on behalf of some request/routine (e.g. a remote controller moving a base). In the context of a gRPC handled request, this can only be called before the first response is sent back (in the case of unary, before the handler returns).
func SafetyMonitorResourceName ¶
SafetyMonitorResourceName works just like SafetyMonitor but uses resource names directly.
Types ¶
type Manager ¶
type Manager interface { Start(ownerID string, peerConnInfo *pb.PeerConnectionInfo) (*Session, error) All() []*Session FindByID(id uuid.UUID, ownerID string) (*Session, error) AssociateResource(id uuid.UUID, resourceName resource.Name) Close() // ServerInterceptors returns gRPC interceptors to work with sessions. ServerInterceptors() ServerInterceptors }
A Manager holds sessions for a particular robot and manages their lifetime.
type ServerInterceptors ¶
type ServerInterceptors struct { UnaryServerInterceptor func( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (interface{}, error) StreamServerInterceptor func( srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, ) error }
ServerInterceptors provide gRPC interceptors to work with sessions.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
A Session allows a client to express that it is actively connected and supports stopping actuating components when it's not.
func FromContext ¶
FromContext returns the current session.
func New ¶
func New( ownerID string, peerConnInfo *pb.PeerConnectionInfo, heartbeatWindow time.Duration, associateResource func(id uuid.UUID, resourceName resource.Name), ) *Session
New makes a new session.
func NewWithID ¶
func NewWithID( id uuid.UUID, ownerID string, peerConnInfo *pb.PeerConnectionInfo, heartbeatWindow time.Duration, associateResource func(id uuid.UUID, resourceName resource.Name), ) *Session
NewWithID makes a new session with an ID.
func (*Session) CheckOwnerID ¶
CheckOwnerID checks if the given owner is the same as the one on this session.
func (*Session) Heartbeat ¶
func (s *Session) Heartbeat()
Heartbeat signals a single heartbeat to the session.
func (*Session) HeartbeatWindow ¶
HeartbeatWindow returns the time window that a single heartbeat must sent within.
func (*Session) PeerConnectionInfo ¶
func (s *Session) PeerConnectionInfo() *pb.PeerConnectionInfo
PeerConnectionInfo returns connection info related to the session.