github.com/wasbazi/deis@v1.7.1-0.20150609203025-5765871615de/logspout/attacher.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"log"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/fsouza/go-dockerclient"
    11  )
    12  
    13  type AttachManager struct {
    14  	sync.Mutex
    15  	attached map[string]*LogPump
    16  	channels map[chan *AttachEvent]struct{}
    17  	client   *docker.Client
    18  }
    19  
    20  func NewAttachManager(client *docker.Client) *AttachManager {
    21  	m := &AttachManager{
    22  		attached: make(map[string]*LogPump),
    23  		channels: make(map[chan *AttachEvent]struct{}),
    24  		client:   client,
    25  	}
    26  	containers, err := client.ListContainers(docker.ListContainersOptions{})
    27  	assert(err, "attacher")
    28  	for _, listing := range containers {
    29  		m.attach(listing.ID[:12])
    30  	}
    31  	go func() {
    32  		events := make(chan *docker.APIEvents)
    33  		assert(client.AddEventListener(events), "attacher")
    34  		for msg := range events {
    35  			debug("event:", msg.ID[:12], msg.Status)
    36  			if msg.Status == "start" {
    37  				go m.attach(msg.ID[:12])
    38  			}
    39  		}
    40  		log.Fatal("ruh roh") // todo: loop?
    41  	}()
    42  	return m
    43  }
    44  
    45  func (m *AttachManager) attach(id string) {
    46  	container, err := m.client.InspectContainer(id)
    47  	assert(err, "attacher")
    48  	name := container.Name[1:]
    49  	success := make(chan struct{})
    50  	failure := make(chan error)
    51  	outrd, outwr := io.Pipe()
    52  	errrd, errwr := io.Pipe()
    53  	go func() {
    54  		err := m.client.AttachToContainer(docker.AttachToContainerOptions{
    55  			Container:    id,
    56  			OutputStream: outwr,
    57  			ErrorStream:  errwr,
    58  			Stdin:        false,
    59  			Stdout:       true,
    60  			Stderr:       true,
    61  			Stream:       true,
    62  			Success:      success,
    63  		})
    64  		outwr.Close()
    65  		errwr.Close()
    66  		debug("attach:", id, "finished")
    67  		if err != nil {
    68  			close(success)
    69  			failure <- err
    70  		}
    71  		m.send(&AttachEvent{Type: "detach", ID: id, Name: name})
    72  		m.Lock()
    73  		delete(m.attached, id)
    74  		m.Unlock()
    75  	}()
    76  	_, ok := <-success
    77  	if ok {
    78  		m.Lock()
    79  		m.attached[id] = NewLogPump(outrd, errrd, id, name)
    80  		m.Unlock()
    81  		success <- struct{}{}
    82  		m.send(&AttachEvent{ID: id, Name: name, Type: "attach"})
    83  		debug("attach:", id, name, "success")
    84  		return
    85  	}
    86  	debug("attach:", id, "failure:", <-failure)
    87  }
    88  
    89  func (m *AttachManager) send(event *AttachEvent) {
    90  	m.Lock()
    91  	defer m.Unlock()
    92  	for ch, _ := range m.channels {
    93  		// TODO: log err after timeout and continue
    94  		ch <- event
    95  	}
    96  }
    97  
    98  func (m *AttachManager) addListener(ch chan *AttachEvent) {
    99  	m.Lock()
   100  	defer m.Unlock()
   101  	m.channels[ch] = struct{}{}
   102  	go func() {
   103  		for id, pump := range m.attached {
   104  			ch <- &AttachEvent{ID: id, Name: pump.Name, Type: "attach"}
   105  		}
   106  	}()
   107  }
   108  
   109  func (m *AttachManager) removeListener(ch chan *AttachEvent) {
   110  	m.Lock()
   111  	defer m.Unlock()
   112  	delete(m.channels, ch)
   113  }
   114  
   115  func (m *AttachManager) Get(id string) *LogPump {
   116  	m.Lock()
   117  	defer m.Unlock()
   118  	return m.attached[id]
   119  }
   120  
   121  func (m *AttachManager) Listen(source *Source, logstream chan *Log, closer <-chan bool) {
   122  	if source == nil {
   123  		source = new(Source)
   124  	}
   125  	events := make(chan *AttachEvent)
   126  	m.addListener(events)
   127  	defer m.removeListener(events)
   128  	for {
   129  		select {
   130  		case event := <-events:
   131  			if event.Type == "attach" && (source.All() ||
   132  				(source.ID != "" && strings.HasPrefix(event.ID, source.ID)) ||
   133  				(source.Name != "" && event.Name == source.Name) ||
   134  				(source.Filter != "" && strings.Contains(event.Name, source.Filter))) {
   135  				pump := m.Get(event.ID)
   136  				pump.AddListener(logstream)
   137  				defer func() {
   138  					if pump != nil {
   139  						pump.RemoveListener(logstream)
   140  					}
   141  				}()
   142  			} else if source.ID != "" && event.Type == "detach" &&
   143  				strings.HasPrefix(event.ID, source.ID) {
   144  				return
   145  			}
   146  		case <-closer:
   147  			return
   148  		}
   149  	}
   150  }
   151  
   152  type LogPump struct {
   153  	sync.Mutex
   154  	ID       string
   155  	Name     string
   156  	channels map[chan *Log]struct{}
   157  }
   158  
   159  func NewLogPump(stdout, stderr io.Reader, id, name string) *LogPump {
   160  	obj := &LogPump{
   161  		ID:       id,
   162  		Name:     name,
   163  		channels: make(map[chan *Log]struct{}),
   164  	}
   165  	pump := func(typ string, source io.Reader) {
   166  		buf := bufio.NewReader(source)
   167  		for {
   168  			data, err := buf.ReadBytes('\n')
   169  			if err != nil {
   170  				if err != io.EOF {
   171  					debug("pump:", id, typ+":", err)
   172  				}
   173  				return
   174  			}
   175  			obj.send(&Log{
   176  				Data: strings.TrimSuffix(string(data), "\n"),
   177  				ID:   id,
   178  				Name: name,
   179  				Type: typ,
   180  			})
   181  		}
   182  	}
   183  	go pump("stdout", stdout)
   184  	go pump("stderr", stderr)
   185  	return obj
   186  }
   187  
   188  func (o *LogPump) send(log *Log) {
   189  	o.Lock()
   190  	defer o.Unlock()
   191  	for ch, _ := range o.channels {
   192  		// TODO: log err after timeout and continue
   193  		ch <- log
   194  	}
   195  }
   196  
   197  func (o *LogPump) AddListener(ch chan *Log) {
   198  	o.Lock()
   199  	defer o.Unlock()
   200  	o.channels[ch] = struct{}{}
   201  }
   202  
   203  func (o *LogPump) RemoveListener(ch chan *Log) {
   204  	o.Lock()
   205  	defer o.Unlock()
   206  	delete(o.channels, ch)
   207  }