v3 refactor (#1868)
* Move to v3 Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
@@ -1,621 +0,0 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hpcloud/tail"
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/runtime/local/git"
|
||||
)
|
||||
|
||||
// defaultNamespace to use if not provided as an option
|
||||
const defaultNamespace = "default"
|
||||
|
||||
type runtime struct {
|
||||
sync.RWMutex
|
||||
// options configure runtime
|
||||
options Options
|
||||
// used to stop the runtime
|
||||
closed chan bool
|
||||
// used to start new services
|
||||
start chan *service
|
||||
// indicates if we're running
|
||||
running bool
|
||||
// namespaces stores services grouped by namespace, e.g. namespaces["foo"]["go.micro.auth:latest"]
|
||||
// would return the latest version of go.micro.auth from the foo namespace
|
||||
namespaces map[string]map[string]*service
|
||||
}
|
||||
|
||||
// NewRuntime creates new local runtime and returns it
|
||||
func NewRuntime(opts ...Option) Runtime {
|
||||
// get default options
|
||||
options := Options{}
|
||||
|
||||
// apply requested options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// make the logs directory
|
||||
path := filepath.Join(os.TempDir(), "micro", "logs")
|
||||
_ = os.MkdirAll(path, 0755)
|
||||
|
||||
return &runtime{
|
||||
options: options,
|
||||
closed: make(chan bool),
|
||||
start: make(chan *service, 128),
|
||||
namespaces: make(map[string]map[string]*service),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runtime) checkoutSourceIfNeeded(s *Service) error {
|
||||
// Runtime service like config have no source.
|
||||
// Skip checkout in that case
|
||||
if len(s.Source) == 0 {
|
||||
return nil
|
||||
}
|
||||
// @todo make this come from config
|
||||
cpath := filepath.Join(os.TempDir(), "micro", "uploads", s.Source)
|
||||
path := strings.ReplaceAll(cpath, ".tar.gz", "")
|
||||
if ex, _ := exists(cpath); ex {
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(path, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = git.Uncompress(cpath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Source = path
|
||||
return nil
|
||||
}
|
||||
source, err := git.ParseSourceLocal("", s.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source.Ref = s.Version
|
||||
|
||||
err = git.CheckoutSource(os.TempDir(), source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Source = source.FullPath
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes runtime options
|
||||
func (r *runtime) Init(opts ...Option) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
for _, o := range opts {
|
||||
o(&r.options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// run runs the runtime management loop
|
||||
func (r *runtime) run(events <-chan Event) {
|
||||
t := time.NewTicker(time.Second * 5)
|
||||
defer t.Stop()
|
||||
|
||||
// process event processes an incoming event
|
||||
processEvent := func(event Event, service *service, ns string) error {
|
||||
// get current vals
|
||||
r.RLock()
|
||||
name := service.Name
|
||||
updated := service.updated
|
||||
r.RUnlock()
|
||||
|
||||
// only process if the timestamp is newer
|
||||
if !event.Timestamp.After(updated) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime updating service %s in %v namespace", name, ns)
|
||||
}
|
||||
|
||||
// this will cause a delete followed by created
|
||||
if err := r.Update(service.Service, UpdateNamespace(ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update the local timestamp
|
||||
r.Lock()
|
||||
service.updated = updated
|
||||
r.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
// check running services
|
||||
r.RLock()
|
||||
for _, sevices := range r.namespaces {
|
||||
for _, service := range sevices {
|
||||
if !service.ShouldStart() {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: check service error
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime starting %s", service.Name)
|
||||
}
|
||||
if err := service.Start(); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error starting %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
r.RUnlock()
|
||||
case service := <-r.start:
|
||||
if !service.ShouldStart() {
|
||||
continue
|
||||
}
|
||||
// TODO: check service error
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime starting service %s", service.Name)
|
||||
}
|
||||
if err := service.Start(); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error starting service %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
case event := <-events:
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime received notification event: %v", event)
|
||||
}
|
||||
// NOTE: we only handle Update events for now
|
||||
switch event.Type {
|
||||
case Update:
|
||||
if event.Service != nil {
|
||||
ns := defaultNamespace
|
||||
if event.Options != nil && len(event.Options.Namespace) > 0 {
|
||||
ns = event.Options.Namespace
|
||||
}
|
||||
|
||||
r.RLock()
|
||||
if _, ok := r.namespaces[ns]; !ok {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime unknown namespace: %s", ns)
|
||||
}
|
||||
r.RUnlock()
|
||||
continue
|
||||
}
|
||||
service, ok := r.namespaces[ns][fmt.Sprintf("%v:%v", event.Service.Name, event.Service.Version)]
|
||||
r.RUnlock()
|
||||
if !ok {
|
||||
logger.Debugf("Runtime unknown service: %s", event.Service)
|
||||
}
|
||||
|
||||
if err := processEvent(event, service, ns); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error updating service %s: %v", event.Service, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
r.RLock()
|
||||
namespaces := r.namespaces
|
||||
r.RUnlock()
|
||||
|
||||
// if blank service was received we update all services
|
||||
for ns, services := range namespaces {
|
||||
for _, service := range services {
|
||||
if err := processEvent(event, service, ns); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error updating service %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-r.closed:
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime stopped")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logFile(serviceName string) string {
|
||||
// make the directory
|
||||
name := strings.Replace(serviceName, "/", "-", -1)
|
||||
path := filepath.Join(os.TempDir(), "micro", "logs")
|
||||
return filepath.Join(path, fmt.Sprintf("%v.log", name))
|
||||
}
|
||||
|
||||
func serviceKey(s *Service) string {
|
||||
return fmt.Sprintf("%v:%v", s.Name, s.Version)
|
||||
}
|
||||
|
||||
// Create creates a new service which is then started by runtime
|
||||
func (r *runtime) Create(s *Service, opts ...CreateOption) error {
|
||||
err := r.checkoutSourceIfNeeded(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
var options CreateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = defaultNamespace
|
||||
}
|
||||
if len(options.Command) == 0 {
|
||||
options.Command = []string{"go"}
|
||||
options.Args = []string{"run", "."}
|
||||
}
|
||||
|
||||
// pass credentials as env vars
|
||||
if len(options.Credentials) > 0 {
|
||||
// validate the creds
|
||||
comps := strings.Split(options.Credentials, ":")
|
||||
if len(comps) != 2 {
|
||||
return errors.New("Invalid credentials, expected format 'user:pass'")
|
||||
}
|
||||
|
||||
options.Env = append(options.Env, "MICRO_AUTH_ID", comps[0])
|
||||
options.Env = append(options.Env, "MICRO_AUTH_SECRET", comps[1])
|
||||
}
|
||||
|
||||
if _, ok := r.namespaces[options.Namespace]; !ok {
|
||||
r.namespaces[options.Namespace] = make(map[string]*service)
|
||||
}
|
||||
if _, ok := r.namespaces[options.Namespace][serviceKey(s)]; ok {
|
||||
return errors.New("service already running")
|
||||
}
|
||||
|
||||
// create new service
|
||||
service := newService(s, options)
|
||||
|
||||
f, err := os.OpenFile(logFile(service.Name), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if service.output != nil {
|
||||
service.output = io.MultiWriter(service.output, f)
|
||||
} else {
|
||||
service.output = f
|
||||
}
|
||||
// start the service
|
||||
if err := service.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
// save service
|
||||
r.namespaces[options.Namespace][serviceKey(s)] = service
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// exists returns whether the given file or directory exists
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// @todo: Getting existing lines is not supported yet.
|
||||
// The reason for this is because it's hard to calculate line offset
|
||||
// as opposed to character offset.
|
||||
// This logger streams by default and only supports the `StreamCount` option.
|
||||
func (r *runtime) Logs(s *Service, options ...LogsOption) (LogStream, error) {
|
||||
lopts := LogsOptions{}
|
||||
for _, o := range options {
|
||||
o(&lopts)
|
||||
}
|
||||
ret := &logStream{
|
||||
service: s.Name,
|
||||
stream: make(chan LogRecord),
|
||||
stop: make(chan bool),
|
||||
}
|
||||
|
||||
fpath := logFile(s.Name)
|
||||
if ex, err := exists(fpath); err != nil {
|
||||
return nil, err
|
||||
} else if !ex {
|
||||
return nil, fmt.Errorf("Logs not found for service %s", s.Name)
|
||||
}
|
||||
|
||||
// have to check file size to avoid too big of a seek
|
||||
fi, err := os.Stat(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := fi.Size()
|
||||
|
||||
whence := 2
|
||||
// Multiply by length of an average line of log in bytes
|
||||
offset := lopts.Count * 200
|
||||
|
||||
if offset > size {
|
||||
offset = size
|
||||
}
|
||||
offset *= -1
|
||||
|
||||
t, err := tail.TailFile(fpath, tail.Config{Follow: lopts.Stream, Location: &tail.SeekInfo{
|
||||
Whence: whence,
|
||||
Offset: int64(offset),
|
||||
}, Logger: tail.DiscardingLogger})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret.tail = t
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case line, ok := <-t.Lines:
|
||||
if !ok {
|
||||
ret.Stop()
|
||||
return
|
||||
}
|
||||
ret.stream <- LogRecord{Message: line.Text}
|
||||
case <-ret.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type logStream struct {
|
||||
tail *tail.Tail
|
||||
service string
|
||||
stream chan LogRecord
|
||||
sync.Mutex
|
||||
stop chan bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (l *logStream) Chan() chan LogRecord {
|
||||
return l.stream
|
||||
}
|
||||
|
||||
func (l *logStream) Error() error {
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *logStream) Stop() error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
select {
|
||||
case <-l.stop:
|
||||
return nil
|
||||
default:
|
||||
close(l.stop)
|
||||
close(l.stream)
|
||||
err := l.tail.Stop()
|
||||
if err != nil {
|
||||
logger.Errorf("Error stopping tail: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns all instances of requested service
|
||||
// If no service name is provided we return all the track services.
|
||||
func (r *runtime) Read(opts ...ReadOption) ([]*Service, error) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
gopts := ReadOptions{}
|
||||
for _, o := range opts {
|
||||
o(&gopts)
|
||||
}
|
||||
if len(gopts.Namespace) == 0 {
|
||||
gopts.Namespace = defaultNamespace
|
||||
}
|
||||
|
||||
save := func(k, v string) bool {
|
||||
if len(k) == 0 {
|
||||
return true
|
||||
}
|
||||
return k == v
|
||||
}
|
||||
|
||||
//nolint:prealloc
|
||||
var services []*Service
|
||||
|
||||
if _, ok := r.namespaces[gopts.Namespace]; !ok {
|
||||
return make([]*Service, 0), nil
|
||||
}
|
||||
|
||||
for _, service := range r.namespaces[gopts.Namespace] {
|
||||
if !save(gopts.Service, service.Name) {
|
||||
continue
|
||||
}
|
||||
if !save(gopts.Version, service.Version) {
|
||||
continue
|
||||
}
|
||||
// TODO deal with service type
|
||||
// no version has sbeen requested, just append the service
|
||||
services = append(services, service.Service)
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Update attempts to update the service
|
||||
func (r *runtime) Update(s *Service, opts ...UpdateOption) error {
|
||||
var options UpdateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = defaultNamespace
|
||||
}
|
||||
|
||||
err := r.checkoutSourceIfNeeded(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
srvs, ok := r.namespaces[options.Namespace]
|
||||
r.Unlock()
|
||||
if !ok {
|
||||
return errors.New("Service not found")
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
service, ok := srvs[serviceKey(s)]
|
||||
r.Unlock()
|
||||
if !ok {
|
||||
return errors.New("Service not found")
|
||||
}
|
||||
|
||||
if err := service.Stop(); err != nil && err.Error() != "no such process" {
|
||||
logger.Errorf("Error stopping service %s: %s", service.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return service.Start()
|
||||
}
|
||||
|
||||
// Delete removes the service from the runtime and stops it
|
||||
func (r *runtime) Delete(s *Service, opts ...DeleteOption) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
var options DeleteOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = defaultNamespace
|
||||
}
|
||||
|
||||
srvs, ok := r.namespaces[options.Namespace]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime deleting service %s", s.Name)
|
||||
}
|
||||
|
||||
service, ok := srvs[serviceKey(s)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if running
|
||||
if !service.Running() {
|
||||
delete(srvs, service.key())
|
||||
r.namespaces[options.Namespace] = srvs
|
||||
return nil
|
||||
}
|
||||
// otherwise stop it
|
||||
if err := service.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
// delete it
|
||||
delete(srvs, service.key())
|
||||
r.namespaces[options.Namespace] = srvs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the runtime
|
||||
func (r *runtime) Start() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
// already running
|
||||
if r.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// set running
|
||||
r.running = true
|
||||
r.closed = make(chan bool)
|
||||
|
||||
var events <-chan Event
|
||||
if r.options.Scheduler != nil {
|
||||
var err error
|
||||
events, err = r.options.Scheduler.Notify()
|
||||
if err != nil {
|
||||
// TODO: should we bail here?
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime failed to start update notifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go r.run(events)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the runtime
|
||||
func (r *runtime) Stop() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if !r.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-r.closed:
|
||||
return nil
|
||||
default:
|
||||
close(r.closed)
|
||||
|
||||
// set not running
|
||||
r.running = false
|
||||
|
||||
// stop all the services
|
||||
for _, services := range r.namespaces {
|
||||
for _, service := range services {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime stopping %s", service.Name)
|
||||
}
|
||||
service.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// stop the scheduler
|
||||
if r.options.Scheduler != nil {
|
||||
return r.options.Scheduler.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements stringer interface
|
||||
func (r *runtime) String() string {
|
||||
return "local"
|
||||
}
|
@@ -9,10 +9,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
log "github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/runtime"
|
||||
"github.com/micro/go-micro/v2/util/kubernetes/client"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
log "github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/runtime"
|
||||
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
||||
)
|
||||
|
||||
// action to take on runtime service
|
||||
|
@@ -9,9 +9,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/runtime"
|
||||
"github.com/micro/go-micro/v2/util/kubernetes/client"
|
||||
"github.com/micro/go-micro/v2/util/log"
|
||||
"github.com/micro/go-micro/v3/runtime"
|
||||
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
||||
"github.com/micro/go-micro/v3/util/log"
|
||||
)
|
||||
|
||||
type klog struct {
|
||||
|
@@ -5,10 +5,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/runtime"
|
||||
"github.com/micro/go-micro/v2/util/kubernetes/api"
|
||||
"github.com/micro/go-micro/v2/util/kubernetes/client"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/runtime"
|
||||
"github.com/micro/go-micro/v3/util/kubernetes/api"
|
||||
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/runtime/local/source"
|
||||
"github.com/micro/go-micro/v3/runtime/local/source"
|
||||
)
|
||||
|
||||
// Builder builds binaries
|
||||
|
@@ -9,8 +9,8 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/runtime/local/build"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/runtime/local/build"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/micro/go-micro/v2/runtime/local/build"
|
||||
"github.com/micro/go-micro/v3/runtime/local/build"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
|
@@ -1,11 +1,622 @@
|
||||
// Package local provides a local runtime
|
||||
package local
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/runtime"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hpcloud/tail"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/runtime"
|
||||
"github.com/micro/go-micro/v3/runtime/local/git"
|
||||
)
|
||||
|
||||
// NewRuntime returns a new local runtime
|
||||
func NewRuntime(opts ...runtime.Option) runtime.Runtime {
|
||||
return runtime.NewRuntime(opts...)
|
||||
// defaultNamespace to use if not provided as an option
|
||||
const defaultNamespace = "default"
|
||||
|
||||
type localRuntime struct {
|
||||
sync.RWMutex
|
||||
// options configure runtime
|
||||
options runtime.Options
|
||||
// used to stop the runtime
|
||||
closed chan bool
|
||||
// used to start new services
|
||||
start chan *service
|
||||
// indicates if we're running
|
||||
running bool
|
||||
// namespaces stores services grouped by namespace, e.g. namespaces["foo"]["go.micro.auth:latest"]
|
||||
// would return the latest version of go.micro.auth from the foo namespace
|
||||
namespaces map[string]map[string]*service
|
||||
}
|
||||
|
||||
// NewRuntime creates new local runtime and returns it
|
||||
func NewRuntime(opts ...runtime.Option) runtime.Runtime {
|
||||
// get default options
|
||||
options := runtime.Options{}
|
||||
|
||||
// apply requested options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// make the logs directory
|
||||
path := filepath.Join(os.TempDir(), "micro", "logs")
|
||||
_ = os.MkdirAll(path, 0755)
|
||||
|
||||
return &localRuntime{
|
||||
options: options,
|
||||
closed: make(chan bool),
|
||||
start: make(chan *service, 128),
|
||||
namespaces: make(map[string]map[string]*service),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *localRuntime) checkoutSourceIfNeeded(s *runtime.Service) error {
|
||||
// Runtime service like config have no source.
|
||||
// Skip checkout in that case
|
||||
if len(s.Source) == 0 {
|
||||
return nil
|
||||
}
|
||||
// @todo make this come from config
|
||||
cpath := filepath.Join(os.TempDir(), "micro", "uploads", s.Source)
|
||||
path := strings.ReplaceAll(cpath, ".tar.gz", "")
|
||||
if ex, _ := exists(cpath); ex {
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(path, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = git.Uncompress(cpath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Source = path
|
||||
return nil
|
||||
}
|
||||
source, err := git.ParseSourceLocal("", s.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source.Ref = s.Version
|
||||
|
||||
err = git.CheckoutSource(os.TempDir(), source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Source = source.FullPath
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes runtime options
|
||||
func (r *localRuntime) Init(opts ...runtime.Option) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
for _, o := range opts {
|
||||
o(&r.options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// run runs the runtime management loop
|
||||
func (r *localRuntime) run(events <-chan runtime.Event) {
|
||||
t := time.NewTicker(time.Second * 5)
|
||||
defer t.Stop()
|
||||
|
||||
// process event processes an incoming event
|
||||
processEvent := func(event runtime.Event, service *service, ns string) error {
|
||||
// get current vals
|
||||
r.RLock()
|
||||
name := service.Name
|
||||
updated := service.updated
|
||||
r.RUnlock()
|
||||
|
||||
// only process if the timestamp is newer
|
||||
if !event.Timestamp.After(updated) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime updating service %s in %v namespace", name, ns)
|
||||
}
|
||||
|
||||
// this will cause a delete followed by created
|
||||
if err := r.Update(service.Service, runtime.UpdateNamespace(ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update the local timestamp
|
||||
r.Lock()
|
||||
service.updated = updated
|
||||
r.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
// check running services
|
||||
r.RLock()
|
||||
for _, sevices := range r.namespaces {
|
||||
for _, service := range sevices {
|
||||
if !service.ShouldStart() {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: check service error
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime starting %s", service.Name)
|
||||
}
|
||||
if err := service.Start(); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error starting %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
r.RUnlock()
|
||||
case service := <-r.start:
|
||||
if !service.ShouldStart() {
|
||||
continue
|
||||
}
|
||||
// TODO: check service error
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime starting service %s", service.Name)
|
||||
}
|
||||
if err := service.Start(); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error starting service %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
case event := <-events:
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime received notification event: %v", event)
|
||||
}
|
||||
// NOTE: we only handle Update events for now
|
||||
switch event.Type {
|
||||
case runtime.Update:
|
||||
if event.Service != nil {
|
||||
ns := defaultNamespace
|
||||
if event.Options != nil && len(event.Options.Namespace) > 0 {
|
||||
ns = event.Options.Namespace
|
||||
}
|
||||
|
||||
r.RLock()
|
||||
if _, ok := r.namespaces[ns]; !ok {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime unknown namespace: %s", ns)
|
||||
}
|
||||
r.RUnlock()
|
||||
continue
|
||||
}
|
||||
service, ok := r.namespaces[ns][fmt.Sprintf("%v:%v", event.Service.Name, event.Service.Version)]
|
||||
r.RUnlock()
|
||||
if !ok {
|
||||
logger.Debugf("Runtime unknown service: %s", event.Service)
|
||||
}
|
||||
|
||||
if err := processEvent(event, service, ns); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error updating service %s: %v", event.Service, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
r.RLock()
|
||||
namespaces := r.namespaces
|
||||
r.RUnlock()
|
||||
|
||||
// if blank service was received we update all services
|
||||
for ns, services := range namespaces {
|
||||
for _, service := range services {
|
||||
if err := processEvent(event, service, ns); err != nil {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime error updating service %s: %v", service.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-r.closed:
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime stopped")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logFile(serviceName string) string {
|
||||
// make the directory
|
||||
name := strings.Replace(serviceName, "/", "-", -1)
|
||||
path := filepath.Join(os.TempDir(), "micro", "logs")
|
||||
return filepath.Join(path, fmt.Sprintf("%v.log", name))
|
||||
}
|
||||
|
||||
func serviceKey(s *runtime.Service) string {
|
||||
return fmt.Sprintf("%v:%v", s.Name, s.Version)
|
||||
}
|
||||
|
||||
// Create creates a new service which is then started by runtime
|
||||
func (r *localRuntime) Create(s *runtime.Service, opts ...runtime.CreateOption) error {
|
||||
err := r.checkoutSourceIfNeeded(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
var options runtime.CreateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = defaultNamespace
|
||||
}
|
||||
if len(options.Command) == 0 {
|
||||
options.Command = []string{"go"}
|
||||
options.Args = []string{"run", "."}
|
||||
}
|
||||
|
||||
// pass credentials as env vars
|
||||
if len(options.Credentials) > 0 {
|
||||
// validate the creds
|
||||
comps := strings.Split(options.Credentials, ":")
|
||||
if len(comps) != 2 {
|
||||
return errors.New("Invalid credentials, expected format 'user:pass'")
|
||||
}
|
||||
|
||||
options.Env = append(options.Env, "MICRO_AUTH_ID", comps[0])
|
||||
options.Env = append(options.Env, "MICRO_AUTH_SECRET", comps[1])
|
||||
}
|
||||
|
||||
if _, ok := r.namespaces[options.Namespace]; !ok {
|
||||
r.namespaces[options.Namespace] = make(map[string]*service)
|
||||
}
|
||||
if _, ok := r.namespaces[options.Namespace][serviceKey(s)]; ok {
|
||||
return errors.New("service already running")
|
||||
}
|
||||
|
||||
// create new service
|
||||
service := newService(s, options)
|
||||
|
||||
f, err := os.OpenFile(logFile(service.Name), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if service.output != nil {
|
||||
service.output = io.MultiWriter(service.output, f)
|
||||
} else {
|
||||
service.output = f
|
||||
}
|
||||
// start the service
|
||||
if err := service.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
// save service
|
||||
r.namespaces[options.Namespace][serviceKey(s)] = service
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// exists returns whether the given file or directory exists
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// @todo: Getting existing lines is not supported yet.
|
||||
// The reason for this is because it's hard to calculate line offset
|
||||
// as opposed to character offset.
|
||||
// This logger streams by default and only supports the `StreamCount` option.
|
||||
func (r *localRuntime) Logs(s *runtime.Service, options ...runtime.LogsOption) (runtime.LogStream, error) {
|
||||
lopts := runtime.LogsOptions{}
|
||||
for _, o := range options {
|
||||
o(&lopts)
|
||||
}
|
||||
ret := &logStream{
|
||||
service: s.Name,
|
||||
stream: make(chan runtime.LogRecord),
|
||||
stop: make(chan bool),
|
||||
}
|
||||
|
||||
fpath := logFile(s.Name)
|
||||
if ex, err := exists(fpath); err != nil {
|
||||
return nil, err
|
||||
} else if !ex {
|
||||
return nil, fmt.Errorf("Logs not found for service %s", s.Name)
|
||||
}
|
||||
|
||||
// have to check file size to avoid too big of a seek
|
||||
fi, err := os.Stat(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := fi.Size()
|
||||
|
||||
whence := 2
|
||||
// Multiply by length of an average line of log in bytes
|
||||
offset := lopts.Count * 200
|
||||
|
||||
if offset > size {
|
||||
offset = size
|
||||
}
|
||||
offset *= -1
|
||||
|
||||
t, err := tail.TailFile(fpath, tail.Config{Follow: lopts.Stream, Location: &tail.SeekInfo{
|
||||
Whence: whence,
|
||||
Offset: int64(offset),
|
||||
}, Logger: tail.DiscardingLogger})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret.tail = t
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case line, ok := <-t.Lines:
|
||||
if !ok {
|
||||
ret.Stop()
|
||||
return
|
||||
}
|
||||
ret.stream <- runtime.LogRecord{Message: line.Text}
|
||||
case <-ret.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type logStream struct {
|
||||
tail *tail.Tail
|
||||
service string
|
||||
stream chan runtime.LogRecord
|
||||
sync.Mutex
|
||||
stop chan bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (l *logStream) Chan() chan runtime.LogRecord {
|
||||
return l.stream
|
||||
}
|
||||
|
||||
func (l *logStream) Error() error {
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *logStream) Stop() error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
select {
|
||||
case <-l.stop:
|
||||
return nil
|
||||
default:
|
||||
close(l.stop)
|
||||
close(l.stream)
|
||||
err := l.tail.Stop()
|
||||
if err != nil {
|
||||
logger.Errorf("Error stopping tail: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns all instances of requested service
|
||||
// If no service name is provided we return all the track services.
|
||||
func (r *localRuntime) Read(opts ...runtime.ReadOption) ([]*runtime.Service, error) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
gopts := runtime.ReadOptions{}
|
||||
for _, o := range opts {
|
||||
o(&gopts)
|
||||
}
|
||||
if len(gopts.Namespace) == 0 {
|
||||
gopts.Namespace = defaultNamespace
|
||||
}
|
||||
|
||||
save := func(k, v string) bool {
|
||||
if len(k) == 0 {
|
||||
return true
|
||||
}
|
||||
return k == v
|
||||
}
|
||||
|
||||
//nolint:prealloc
|
||||
var services []*runtime.Service
|
||||
|
||||
if _, ok := r.namespaces[gopts.Namespace]; !ok {
|
||||
return make([]*runtime.Service, 0), nil
|
||||
}
|
||||
|
||||
for _, service := range r.namespaces[gopts.Namespace] {
|
||||
if !save(gopts.Service, service.Name) {
|
||||
continue
|
||||
}
|
||||
if !save(gopts.Version, service.Version) {
|
||||
continue
|
||||
}
|
||||
// TODO deal with service type
|
||||
// no version has sbeen requested, just append the service
|
||||
services = append(services, service.Service)
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Update attempts to update the service
|
||||
func (r *localRuntime) Update(s *runtime.Service, opts ...runtime.UpdateOption) error {
|
||||
var options runtime.UpdateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = defaultNamespace
|
||||
}
|
||||
|
||||
err := r.checkoutSourceIfNeeded(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
srvs, ok := r.namespaces[options.Namespace]
|
||||
r.Unlock()
|
||||
if !ok {
|
||||
return errors.New("Service not found")
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
service, ok := srvs[serviceKey(s)]
|
||||
r.Unlock()
|
||||
if !ok {
|
||||
return errors.New("Service not found")
|
||||
}
|
||||
|
||||
if err := service.Stop(); err != nil && err.Error() != "no such process" {
|
||||
logger.Errorf("Error stopping service %s: %s", service.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return service.Start()
|
||||
}
|
||||
|
||||
// Delete removes the service from the runtime and stops it
|
||||
func (r *localRuntime) Delete(s *runtime.Service, opts ...runtime.DeleteOption) error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
var options runtime.DeleteOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Namespace) == 0 {
|
||||
options.Namespace = defaultNamespace
|
||||
}
|
||||
|
||||
srvs, ok := r.namespaces[options.Namespace]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime deleting service %s", s.Name)
|
||||
}
|
||||
|
||||
service, ok := srvs[serviceKey(s)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if running
|
||||
if !service.Running() {
|
||||
delete(srvs, service.key())
|
||||
r.namespaces[options.Namespace] = srvs
|
||||
return nil
|
||||
}
|
||||
// otherwise stop it
|
||||
if err := service.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
// delete it
|
||||
delete(srvs, service.key())
|
||||
r.namespaces[options.Namespace] = srvs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the runtime
|
||||
func (r *localRuntime) Start() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
// already running
|
||||
if r.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// set running
|
||||
r.running = true
|
||||
r.closed = make(chan bool)
|
||||
|
||||
var events <-chan runtime.Event
|
||||
if r.options.Scheduler != nil {
|
||||
var err error
|
||||
events, err = r.options.Scheduler.Notify()
|
||||
if err != nil {
|
||||
// TODO: should we bail here?
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime failed to start update notifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go r.run(events)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the runtime
|
||||
func (r *localRuntime) Stop() error {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if !r.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-r.closed:
|
||||
return nil
|
||||
default:
|
||||
close(r.closed)
|
||||
|
||||
// set not running
|
||||
r.running = false
|
||||
|
||||
// stop all the services
|
||||
for _, services := range r.namespaces {
|
||||
for _, service := range services {
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("Runtime stopping %s", service.Name)
|
||||
}
|
||||
service.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// stop the scheduler
|
||||
if r.options.Scheduler != nil {
|
||||
return r.options.Scheduler.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements stringer interface
|
||||
func (r *localRuntime) String() string {
|
||||
return "local"
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/micro/go-micro/v2/runtime/local/process"
|
||||
"github.com/micro/go-micro/v3/runtime/local/process"
|
||||
)
|
||||
|
||||
func (p *Process) Exec(exe *process.Executable) error {
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/micro/go-micro/v2/runtime/local/process"
|
||||
"github.com/micro/go-micro/v3/runtime/local/process"
|
||||
)
|
||||
|
||||
func (p *Process) Exec(exe *process.Executable) error {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/runtime/local/process"
|
||||
"github.com/micro/go-micro/v3/runtime/local/process"
|
||||
)
|
||||
|
||||
type Process struct{}
|
||||
|
@@ -4,7 +4,7 @@ package process
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/micro/go-micro/v2/runtime/local/build"
|
||||
"github.com/micro/go-micro/v3/runtime/local/build"
|
||||
)
|
||||
|
||||
// Process manages a running process
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package runtime
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -9,10 +9,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/runtime/local/build"
|
||||
"github.com/micro/go-micro/v2/runtime/local/process"
|
||||
proc "github.com/micro/go-micro/v2/runtime/local/process/os"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/runtime"
|
||||
"github.com/micro/go-micro/v3/runtime/local/build"
|
||||
"github.com/micro/go-micro/v3/runtime/local/process"
|
||||
proc "github.com/micro/go-micro/v3/runtime/local/process/os"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
@@ -30,7 +31,7 @@ type service struct {
|
||||
output io.Writer
|
||||
|
||||
// service to manage
|
||||
*Service
|
||||
*runtime.Service
|
||||
// process creator
|
||||
Process *proc.Process
|
||||
// Exec
|
||||
@@ -39,7 +40,7 @@ type service struct {
|
||||
PID *process.PID
|
||||
}
|
||||
|
||||
func newService(s *Service, c CreateOptions) *service {
|
||||
func newService(s *runtime.Service, c runtime.CreateOptions) *service {
|
||||
var exec string
|
||||
var args []string
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/micro/go-micro/v2/runtime/local/source"
|
||||
"github.com/micro/go-micro/v3/runtime/local/source"
|
||||
)
|
||||
|
||||
// Source retrieves source code
|
||||
|
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/micro/go-micro/v2/runtime/local/source"
|
||||
"github.com/micro/go-micro/v3/runtime/local/source"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v3/client"
|
||||
)
|
||||
|
||||
type Option func(o *Options)
|
||||
|
@@ -7,11 +7,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRuntime is default micro runtime
|
||||
DefaultRuntime Runtime = NewRuntime()
|
||||
// DefaultName is default runtime service name
|
||||
DefaultName = "go.micro.runtime"
|
||||
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,236 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: runtime/service/proto/runtime.proto
|
||||
|
||||
package go_micro_runtime
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/micro/go-micro/v2/api"
|
||||
client "github.com/micro/go-micro/v2/client"
|
||||
server "github.com/micro/go-micro/v2/server"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ api.Endpoint
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Api Endpoints for Runtime service
|
||||
|
||||
func NewRuntimeEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Runtime service
|
||||
|
||||
type RuntimeService interface {
|
||||
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
|
||||
Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error)
|
||||
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
|
||||
Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error)
|
||||
Logs(ctx context.Context, in *LogsRequest, opts ...client.CallOption) (Runtime_LogsService, error)
|
||||
}
|
||||
|
||||
type runtimeService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewRuntimeService(name string, c client.Client) RuntimeService {
|
||||
return &runtimeService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *runtimeService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Runtime.Create", in)
|
||||
out := new(CreateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *runtimeService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Runtime.Read", in)
|
||||
out := new(ReadResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *runtimeService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Runtime.Delete", in)
|
||||
out := new(DeleteResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *runtimeService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Runtime.Update", in)
|
||||
out := new(UpdateResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *runtimeService) Logs(ctx context.Context, in *LogsRequest, opts ...client.CallOption) (Runtime_LogsService, error) {
|
||||
req := c.c.NewRequest(c.name, "Runtime.Logs", &LogsRequest{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := stream.Send(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeServiceLogs{stream}, nil
|
||||
}
|
||||
|
||||
type Runtime_LogsService interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Recv() (*LogRecord, error)
|
||||
}
|
||||
|
||||
type runtimeServiceLogs struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *runtimeServiceLogs) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *runtimeServiceLogs) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *runtimeServiceLogs) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *runtimeServiceLogs) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *runtimeServiceLogs) Recv() (*LogRecord, error) {
|
||||
m := new(LogRecord)
|
||||
err := x.stream.Recv(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Server API for Runtime service
|
||||
|
||||
type RuntimeHandler interface {
|
||||
Create(context.Context, *CreateRequest, *CreateResponse) error
|
||||
Read(context.Context, *ReadRequest, *ReadResponse) error
|
||||
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
|
||||
Update(context.Context, *UpdateRequest, *UpdateResponse) error
|
||||
Logs(context.Context, *LogsRequest, Runtime_LogsStream) error
|
||||
}
|
||||
|
||||
func RegisterRuntimeHandler(s server.Server, hdlr RuntimeHandler, opts ...server.HandlerOption) error {
|
||||
type runtime interface {
|
||||
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
|
||||
Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error
|
||||
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
|
||||
Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error
|
||||
Logs(ctx context.Context, stream server.Stream) error
|
||||
}
|
||||
type Runtime struct {
|
||||
runtime
|
||||
}
|
||||
h := &runtimeHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Runtime{h}, opts...))
|
||||
}
|
||||
|
||||
type runtimeHandler struct {
|
||||
RuntimeHandler
|
||||
}
|
||||
|
||||
func (h *runtimeHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
|
||||
return h.RuntimeHandler.Create(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *runtimeHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error {
|
||||
return h.RuntimeHandler.Read(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *runtimeHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
|
||||
return h.RuntimeHandler.Delete(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *runtimeHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error {
|
||||
return h.RuntimeHandler.Update(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *runtimeHandler) Logs(ctx context.Context, stream server.Stream) error {
|
||||
m := new(LogsRequest)
|
||||
if err := stream.Recv(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return h.RuntimeHandler.Logs(ctx, m, &runtimeLogsStream{stream})
|
||||
}
|
||||
|
||||
type Runtime_LogsStream interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*LogRecord) error
|
||||
}
|
||||
|
||||
type runtimeLogsStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *runtimeLogsStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *runtimeLogsStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *runtimeLogsStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *runtimeLogsStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *runtimeLogsStream) Send(m *LogRecord) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
@@ -1,139 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package go.micro.runtime;
|
||||
|
||||
service Runtime {
|
||||
rpc Create(CreateRequest) returns (CreateResponse) {};
|
||||
rpc Read(ReadRequest) returns (ReadResponse) {};
|
||||
rpc Delete(DeleteRequest) returns (DeleteResponse) {};
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse) {};
|
||||
rpc Logs(LogsRequest) returns (stream LogRecord) {};
|
||||
}
|
||||
|
||||
message Service {
|
||||
// name of the service
|
||||
string name = 1;
|
||||
// version of the service
|
||||
string version = 2;
|
||||
// git url of the source
|
||||
string source = 3;
|
||||
// service metadata
|
||||
map<string,string> metadata = 4;
|
||||
}
|
||||
|
||||
message Event {
|
||||
string type = 1;
|
||||
int64 timestamp = 2;
|
||||
string service = 3;
|
||||
string version = 4;
|
||||
}
|
||||
|
||||
message CreateOptions {
|
||||
// command to pass in
|
||||
repeated string command = 1;
|
||||
// args to pass into command
|
||||
repeated string args = 2;
|
||||
// environment to pass in
|
||||
repeated string env = 3;
|
||||
// output to send to
|
||||
string output = 4;
|
||||
// create type of service
|
||||
string type = 5;
|
||||
// image to use
|
||||
string image = 6;
|
||||
// namespace to create the service in
|
||||
string namespace = 7;
|
||||
}
|
||||
|
||||
message CreateRequest {
|
||||
Service service = 1;
|
||||
CreateOptions options = 2;
|
||||
}
|
||||
|
||||
message CreateResponse {}
|
||||
|
||||
message ReadOptions {
|
||||
// service name
|
||||
string service = 1;
|
||||
// version of the service
|
||||
string version = 2;
|
||||
// type of service
|
||||
string type = 3;
|
||||
// namespace of the service
|
||||
string namespace = 4;
|
||||
}
|
||||
|
||||
message ReadRequest {
|
||||
ReadOptions options = 1;
|
||||
}
|
||||
|
||||
message ReadResponse {
|
||||
repeated Service services = 1;
|
||||
}
|
||||
|
||||
message DeleteOptions {
|
||||
// namespace of the service
|
||||
string namespace = 1;
|
||||
}
|
||||
|
||||
message DeleteRequest {
|
||||
Service service = 1;
|
||||
DeleteOptions options = 2;
|
||||
}
|
||||
|
||||
message DeleteResponse {}
|
||||
|
||||
message UpdateOptions {
|
||||
// namespace of the service
|
||||
string namespace = 1;
|
||||
}
|
||||
|
||||
message UpdateRequest {
|
||||
Service service = 1;
|
||||
UpdateOptions options = 2;
|
||||
}
|
||||
|
||||
message UpdateResponse {}
|
||||
|
||||
message ListOptions {
|
||||
// namespace of the service
|
||||
string namespace = 1;
|
||||
}
|
||||
|
||||
message ListRequest {
|
||||
ListOptions options = 1;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Service services = 1;
|
||||
}
|
||||
|
||||
message LogsOptions {
|
||||
// namespace of the service
|
||||
string namespace = 1;
|
||||
}
|
||||
|
||||
message LogsRequest{
|
||||
// service to request logs for
|
||||
string service = 1;
|
||||
// stream records continuously
|
||||
bool stream = 2;
|
||||
// count of records to request
|
||||
int64 count = 3;
|
||||
// relative time in seconds
|
||||
// before the current time
|
||||
// from which to show logs
|
||||
int64 since = 4;
|
||||
// options to use
|
||||
LogsOptions options = 5;
|
||||
}
|
||||
|
||||
message LogRecord {
|
||||
// timestamp of log record
|
||||
int64 timestamp = 1;
|
||||
// record metadata
|
||||
map<string,string> metadata = 2;
|
||||
// message
|
||||
string message = 3;
|
||||
}
|
||||
|
@@ -1,301 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v2/runtime"
|
||||
pb "github.com/micro/go-micro/v2/runtime/service/proto"
|
||||
)
|
||||
|
||||
type svc struct {
|
||||
sync.RWMutex
|
||||
options runtime.Options
|
||||
runtime pb.RuntimeService
|
||||
}
|
||||
|
||||
// Init initializes runtime with given options
|
||||
func (s *svc) Init(opts ...runtime.Option) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
for _, o := range opts {
|
||||
o(&s.options)
|
||||
}
|
||||
|
||||
// reset the runtime as the client could have changed
|
||||
s.runtime = pb.NewRuntimeService(runtime.DefaultName, s.options.Client)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create registers a service in the runtime
|
||||
func (s *svc) Create(svc *runtime.Service, opts ...runtime.CreateOption) error {
|
||||
var options runtime.CreateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
}
|
||||
|
||||
// set the default source from MICRO_RUNTIME_SOURCE
|
||||
if len(svc.Source) == 0 {
|
||||
svc.Source = s.options.Source
|
||||
}
|
||||
|
||||
// runtime service create request
|
||||
req := &pb.CreateRequest{
|
||||
Service: &pb.Service{
|
||||
Name: svc.Name,
|
||||
Version: svc.Version,
|
||||
Source: svc.Source,
|
||||
Metadata: svc.Metadata,
|
||||
},
|
||||
Options: &pb.CreateOptions{
|
||||
Command: options.Command,
|
||||
Args: options.Args,
|
||||
Env: options.Env,
|
||||
Type: options.Type,
|
||||
Image: options.Image,
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := s.runtime.Create(options.Context, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *svc) Logs(service *runtime.Service, opts ...runtime.LogsOption) (runtime.LogStream, error) {
|
||||
var options runtime.LogsOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
}
|
||||
|
||||
ls, err := s.runtime.Logs(options.Context, &pb.LogsRequest{
|
||||
Service: service.Name,
|
||||
Stream: options.Stream,
|
||||
Count: options.Count,
|
||||
Options: &pb.LogsOptions{
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logStream := &serviceLogStream{
|
||||
service: service.Name,
|
||||
stream: make(chan runtime.LogRecord),
|
||||
stop: make(chan bool),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
// @todo this never seems to return, investigate
|
||||
case <-ls.Context().Done():
|
||||
logStream.Stop()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
// @todo this never seems to return, investigate
|
||||
case <-ls.Context().Done():
|
||||
return
|
||||
case _, ok := <-logStream.stream:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
default:
|
||||
record := pb.LogRecord{}
|
||||
err := ls.RecvMsg(&record)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
logStream.err = err
|
||||
}
|
||||
logStream.Stop()
|
||||
return
|
||||
}
|
||||
logStream.stream <- runtime.LogRecord{
|
||||
Message: record.GetMessage(),
|
||||
Metadata: record.GetMetadata(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return logStream, nil
|
||||
}
|
||||
|
||||
type serviceLogStream struct {
|
||||
service string
|
||||
stream chan runtime.LogRecord
|
||||
sync.Mutex
|
||||
stop chan bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (l *serviceLogStream) Error() error {
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *serviceLogStream) Chan() chan runtime.LogRecord {
|
||||
return l.stream
|
||||
}
|
||||
|
||||
func (l *serviceLogStream) Stop() error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
select {
|
||||
case <-l.stop:
|
||||
return nil
|
||||
default:
|
||||
close(l.stream)
|
||||
close(l.stop)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns the service with the given name from the runtime
|
||||
func (s *svc) Read(opts ...runtime.ReadOption) ([]*runtime.Service, error) {
|
||||
var options runtime.ReadOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
}
|
||||
|
||||
// runtime service create request
|
||||
req := &pb.ReadRequest{
|
||||
Options: &pb.ReadOptions{
|
||||
Service: options.Service,
|
||||
Version: options.Version,
|
||||
Type: options.Type,
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.runtime.Read(options.Context, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
services := make([]*runtime.Service, 0, len(resp.Services))
|
||||
for _, service := range resp.Services {
|
||||
svc := &runtime.Service{
|
||||
Name: service.Name,
|
||||
Version: service.Version,
|
||||
Source: service.Source,
|
||||
Metadata: service.Metadata,
|
||||
}
|
||||
services = append(services, svc)
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Update updates the running service
|
||||
func (s *svc) Update(svc *runtime.Service, opts ...runtime.UpdateOption) error {
|
||||
var options runtime.UpdateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
}
|
||||
|
||||
// runtime service create request
|
||||
req := &pb.UpdateRequest{
|
||||
Service: &pb.Service{
|
||||
Name: svc.Name,
|
||||
Version: svc.Version,
|
||||
Source: svc.Source,
|
||||
Metadata: svc.Metadata,
|
||||
},
|
||||
Options: &pb.UpdateOptions{
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := s.runtime.Update(options.Context, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete stops and removes the service from the runtime
|
||||
func (s *svc) Delete(svc *runtime.Service, opts ...runtime.DeleteOption) error {
|
||||
var options runtime.DeleteOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.Background()
|
||||
}
|
||||
|
||||
// runtime service create request
|
||||
req := &pb.DeleteRequest{
|
||||
Service: &pb.Service{
|
||||
Name: svc.Name,
|
||||
Version: svc.Version,
|
||||
Source: svc.Source,
|
||||
Metadata: svc.Metadata,
|
||||
},
|
||||
Options: &pb.DeleteOptions{
|
||||
Namespace: options.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := s.runtime.Delete(options.Context, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the runtime
|
||||
func (s *svc) Start() error {
|
||||
// NOTE: nothing to be done here
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the runtime
|
||||
func (s *svc) Stop() error {
|
||||
// NOTE: nothing to be done here
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the runtime service implementation
|
||||
func (s *svc) String() string {
|
||||
return "service"
|
||||
}
|
||||
|
||||
// NewRuntime creates new service runtime and returns it
|
||||
func NewRuntime(opts ...runtime.Option) runtime.Runtime {
|
||||
var options runtime.Options
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Client == nil {
|
||||
options.Client = client.DefaultClient
|
||||
}
|
||||
|
||||
return &svc{
|
||||
options: options,
|
||||
runtime: pb.NewRuntimeService(runtime.DefaultName, options.Client),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user