Add streaming service
Adds a service capable of streaming Any objects bi-directionally. This can be used by services to send data, received data, or to initiate requests from server to client. Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
257
plugins/streaming/manager.go
Normal file
257
plugins/streaming/manager.go
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package streaming
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/gc"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/metadata"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/pkg/streaming"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register(&plugin.Registration{
|
||||
Type: plugin.StreamingPlugin,
|
||||
ID: "manager",
|
||||
Requires: []plugin.Type{
|
||||
plugin.MetadataPlugin,
|
||||
},
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
md, err := ic.Get(plugin.MetadataPlugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sm := &streamManager{
|
||||
streams: map[string]map[string]*managedStream{},
|
||||
byLease: map[string]map[string]map[string]struct{}{},
|
||||
}
|
||||
md.(*metadata.DB).RegisterCollectibleResource(metadata.ResourceStream, sm)
|
||||
return sm, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type streamManager struct {
|
||||
// streams maps namespace -> name -> stream
|
||||
streams map[string]map[string]*managedStream
|
||||
|
||||
byLease map[string]map[string]map[string]struct{}
|
||||
|
||||
rwlock sync.RWMutex
|
||||
}
|
||||
|
||||
func (sm *streamManager) Register(ctx context.Context, name string, stream streaming.Stream) error {
|
||||
ns, _ := namespaces.Namespace(ctx)
|
||||
ls, _ := leases.FromContext(ctx)
|
||||
|
||||
ms := &managedStream{
|
||||
Stream: stream,
|
||||
ns: ns,
|
||||
name: name,
|
||||
lease: ls,
|
||||
manager: sm,
|
||||
}
|
||||
|
||||
sm.rwlock.Lock()
|
||||
defer sm.rwlock.Unlock()
|
||||
nsMap, ok := sm.streams[ns]
|
||||
if !ok {
|
||||
nsMap = make(map[string]*managedStream)
|
||||
sm.streams[ns] = nsMap
|
||||
}
|
||||
if _, ok := nsMap[name]; ok {
|
||||
return errdefs.ErrAlreadyExists
|
||||
}
|
||||
nsMap[name] = ms
|
||||
|
||||
if ls != "" {
|
||||
nsMap, ok := sm.byLease[ns]
|
||||
if !ok {
|
||||
nsMap = make(map[string]map[string]struct{})
|
||||
sm.byLease[ns] = nsMap
|
||||
}
|
||||
lsMap, ok := nsMap[ls]
|
||||
if !ok {
|
||||
lsMap = make(map[string]struct{})
|
||||
nsMap[ls] = lsMap
|
||||
}
|
||||
lsMap[name] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *streamManager) Get(ctx context.Context, name string) (streaming.Stream, error) {
|
||||
ns, _ := namespaces.Namespace(ctx)
|
||||
sm.rwlock.RLock()
|
||||
defer sm.rwlock.RUnlock()
|
||||
|
||||
nsMap, ok := sm.streams[ns]
|
||||
if !ok {
|
||||
return nil, errdefs.ErrNotFound
|
||||
}
|
||||
stream, ok := nsMap[name]
|
||||
if !ok {
|
||||
return nil, errdefs.ErrNotFound
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (sm *streamManager) StartCollection(context.Context) (metadata.CollectionContext, error) {
|
||||
// lock now and collection will unlock
|
||||
sm.rwlock.Lock()
|
||||
|
||||
return &collectionContext{
|
||||
manager: sm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sm *streamManager) ReferenceLabel() string {
|
||||
return "stream"
|
||||
}
|
||||
|
||||
type managedStream struct {
|
||||
streaming.Stream
|
||||
|
||||
ns string
|
||||
name string
|
||||
lease string
|
||||
manager *streamManager
|
||||
}
|
||||
|
||||
func (m *managedStream) Close() error {
|
||||
m.manager.rwlock.Lock()
|
||||
if nsMap, ok := m.manager.streams[m.ns]; ok {
|
||||
delete(nsMap, m.name)
|
||||
if len(nsMap) == 0 {
|
||||
delete(m.manager.streams, m.ns)
|
||||
}
|
||||
}
|
||||
if m.lease != "" {
|
||||
if nsMap, ok := m.manager.byLease[m.ns]; ok {
|
||||
if lsMap, ok := nsMap[m.lease]; ok {
|
||||
delete(lsMap, m.name)
|
||||
if len(lsMap) == 0 {
|
||||
delete(nsMap, m.lease)
|
||||
}
|
||||
}
|
||||
if len(nsMap) == 0 {
|
||||
delete(m.manager.byLease, m.ns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.manager.rwlock.Unlock()
|
||||
return m.Stream.Close()
|
||||
}
|
||||
|
||||
type collectionContext struct {
|
||||
manager *streamManager
|
||||
removed []gc.Node
|
||||
}
|
||||
|
||||
func (cc *collectionContext) All(fn func(gc.Node)) {
|
||||
for ns, nsMap := range cc.manager.streams {
|
||||
for name := range nsMap {
|
||||
fn(gc.Node{
|
||||
Type: metadata.ResourceStream,
|
||||
Namespace: ns,
|
||||
Key: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (cc *collectionContext) Active(ns string, fn func(gc.Node)) {
|
||||
if nsMap, ok := cc.manager.streams[ns]; ok {
|
||||
for name, stream := range nsMap {
|
||||
// Don't consider leased streams as active, the lease
|
||||
// will determine the status
|
||||
// TODO: expire non-active streams
|
||||
if stream.lease == "" {
|
||||
fn(gc.Node{
|
||||
Type: metadata.ResourceStream,
|
||||
Namespace: ns,
|
||||
Key: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *collectionContext) Leased(ns, lease string, fn func(gc.Node)) {
|
||||
if nsMap, ok := cc.manager.byLease[ns]; ok {
|
||||
if lsMap, ok := nsMap[lease]; ok {
|
||||
for name := range lsMap {
|
||||
fn(gc.Node{
|
||||
Type: metadata.ResourceStream,
|
||||
Namespace: ns,
|
||||
Key: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *collectionContext) Remove(n gc.Node) {
|
||||
cc.removed = append(cc.removed, n)
|
||||
}
|
||||
|
||||
func (cc *collectionContext) Cancel() error {
|
||||
cc.manager.rwlock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *collectionContext) Finish() error {
|
||||
defer cc.manager.rwlock.Unlock()
|
||||
for _, node := range cc.removed {
|
||||
var lease string
|
||||
if nsMap, ok := cc.manager.streams[node.Namespace]; ok {
|
||||
if ms, ok := nsMap[node.Key]; ok {
|
||||
delete(nsMap, node.Key)
|
||||
ms.Close()
|
||||
lease = ms.lease
|
||||
}
|
||||
if len(nsMap) == 0 {
|
||||
delete(cc.manager.streams, node.Namespace)
|
||||
}
|
||||
}
|
||||
if lease != "" {
|
||||
if nsMap, ok := cc.manager.byLease[node.Namespace]; ok {
|
||||
if lsMap, ok := nsMap[lease]; ok {
|
||||
delete(lsMap, node.Key)
|
||||
if len(lsMap) == 0 {
|
||||
delete(nsMap, lease)
|
||||
}
|
||||
}
|
||||
if len(nsMap) == 0 {
|
||||
delete(cc.manager.byLease, node.Namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user