Пример #1
0
	"github.com/leveros/leveros/dockerutil"
	"github.com/leveros/leveros/leverutil"
	"github.com/leveros/leveros/scale"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// ManagerService is the name of the host manager internal service.
const ManagerService = "leverhostmanager"

var (
	// InstanceIDFlag is the instance ID of the manager. Note: This is
	// a different instance ID than leverutil.InstanceIDFlag because they
	// serve different things.
	InstanceIDFlag = config.DeclareString(
		PackageName, "managerInstanceID", leverutil.RandomID())

	// RegionalNetworkFlag is the name of the Docker network that the proxy
	// uses internally.
	RegionalNetworkFlag = config.DeclareString(
		PackageName, "regionalNetwork", "leveros_default")
)

// KeepAliveFun is a function that should be called on every event on a stream
// to signal the manager that the unerlying infrastructure is still being used.
type KeepAliveFun func(resourceName, levResResourceID, levResSessionID string)

type envEntry struct {
	// Guard the following, as well as network creation / destruction.
	envLock        sync.Mutex
	networkID      string
Пример #2
0
func (proxy *LeverProxy) handleInStream(stream *http2stream.HTTP2Stream) {
	headers := stream.GetHeaders()
	err := expectHeaders(
		headers,
		"lever-url",
		"x-lever-src-env",
		"x-lever-dest-instance-id",
		"x-lever-dest-container-id",
		"x-lever-serving-id",
		"x-lever-code-version",
		"x-lever-inst-resource-id",
		"x-lever-inst-session-id",
		"x-lever-res-resource-id",
		"x-lever-res-session-id",
	)
	if err != nil {
		proxy.inLogger.WithFields("err", err).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	leverURL, err := core.ParseLeverURL(headers["lever-url"][0])
	if err != nil {
		proxy.inLogger.WithFields(
			"err", err, "leverURL", headers["lever-url"][0]).Error(
			"Unable to parse Lever URL")
	}

	if !core.IsInternalEnvironment(leverURL.Environment) {
		err = fmt.Errorf("Cannot route to dest env")
		proxy.inLogger.WithFields(
			"err", err,
			"leverEnv", leverURL.Environment,
		).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	srcEnv := headers["x-lever-src-env"][0]
	instanceID := headers["x-lever-dest-instance-id"][0]
	containerID := headers["x-lever-dest-container-id"][0]
	servingID := headers["x-lever-serving-id"][0]
	codeVersionInt, err := strconv.Atoi(headers["x-lever-code-version"][0])
	if err != nil {
		proxy.inLogger.WithFields("err", err).Error("Cannot parse code version")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}
	codeVersion := int64(codeVersionInt)
	levInstResourceID := headers["x-lever-inst-resource-id"][0]
	levInstSessionID := headers["x-lever-inst-session-id"][0]
	levResResourceID := headers["x-lever-res-resource-id"][0]
	levResSessionID := headers["x-lever-res-session-id"][0]

	if instanceID == "" {
		instanceID, err = proxy.manager.RandomInstaceID(servingID)
		if err != nil {
			proxy.inLogger.WithFields(
				"err", err,
				"leverEnv", leverURL.Environment,
			).Error("Could not find an instanceID for provided servingID")
			stream.Write(&http2stream.MsgError{Err: err})
			return
		}
	}

	streamID := leverutil.RandomID()
	proxy.inLogger.WithFields(
		"leverURL", leverURL.String(),
		"srcEnv", srcEnv,
		"leverInstanceID", instanceID,
		"containerID", containerID,
		"servingID", servingID,
		"levInstSessionID", levInstSessionID,
		"levInstResourceID", levInstResourceID,
		"streamID", streamID,
	).Debug("Receiving stream")
	streamLogger := proxy.inLogger.WithFields("streamID", streamID)

	if !core.IsInternalEnvironment(leverURL.Environment) {
		err = fmt.Errorf("Environment not routable internally")
		streamLogger.WithFields(
			"err", err,
			"leverEnv", leverURL.Environment,
		).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	_, ownIP, instanceAddr, keepAliveFun, err :=
		proxy.manager.EnsureInfrastructureInitialized(&hostman.InstanceInfo{
			Environment:       leverURL.Environment,
			Service:           leverURL.Service,
			InstanceID:        instanceID,
			ContainerID:       containerID,
			ServingID:         servingID,
			LevInstResourceID: levInstResourceID,
			LevInstSessionID:  levInstSessionID,
		})
	if err != nil {
		streamLogger.WithFields("err", err).Error(
			"Error initializing instance")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	err = proxy.serveOut(leverURL.Environment, ownIP)
	if err != nil {
		streamLogger.WithFields("err", err).Error(
			"Error listening on env network")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	destStreamI, err := leverutil.ExpBackoff(
		func() (clientStream interface{}, err error, finalErr error) {
			clientStream, err = proxy.client.NewStream(instanceAddr)
			if err != nil {
				if strings.Contains(err.Error(), "connection refused") &&
					proxy.manager.IsInstanceAlive(servingID, instanceID) {
					// Retry.
					return nil, err, nil
				}
				if err == leverutil.ErrNotYetConstructed ||
					err == leverutil.ErrWasDestructed {
					// Retry.
					return nil, err, nil
				}
				return nil, nil, err
			}
			return clientStream, nil, nil
		}, 10*time.Millisecond, 15*time.Second)
	if err != nil {
		streamLogger.WithFields(
			"err", err,
			"instanceAddr", instanceAddr,
		).Error("Error trying to create client stream to dest")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}
	destStream := destStreamI.(*http2stream.HTTP2Stream)

	addHeaders := make(map[string][]string)
	if srcEnv != "" {
		addHeaders["x-lever-src-env"] = []string{srcEnv}
	}
	addHeaders["x-lever-internal-rpc-gateway"] = []string{
		ownIP + ":" + EnvOutListenPortFlag.Get()}

	startTime := time.Now()
	firstHeaders := true
	stream.ProxyTo(
		destStream,
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(instanceAddr)
			keepAliveFun(
				leverURL.Resource, levResResourceID, levResSessionID)
			return proxy.filterTo(&firstHeaders, addHeaders, msg)
		},
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(instanceAddr)
			keepAliveFun(
				leverURL.Resource, levResResourceID, levResSessionID)
			return noFilter(msg)
		})
	// Wait for RPC to finish.
	<-destStream.Closed()
	rpcNanos := uint64(time.Now().Sub(startTime).Nanoseconds())
	streamLogger.WithFields("rpcNanos", rpcNanos).Debug("RPC nanos")

	// Send RPC stats to fleettracker.
	// TODO: For services with high load, we should not send info on every
	//       RPC. They should be batched and sent say... every ~50ms
	//       (well below tracker tick interval).
	err = fleettracker.OnRPC(proxy.grpcPool, &fleettracker.RPCEvent{
		Environment: leverURL.Environment,
		Service:     leverURL.Service,
		ServingID:   servingID,
		CodeVersion: codeVersion,
		IsAdmin:     core.IsAdmin(leverURL),
		RpcNanos:    rpcNanos,
	})
	if err != nil {
		streamLogger.WithFields("err", err).Error(
			"Failed to send RPC stats to fleettracker")
	}
}
Пример #3
0
	"github.com/leveros/leveros/leverutil"
	"github.com/leveros/leveros/scale"
)

// PackageName is the name of this package.
const PackageName = "devlogger"

var (
	// DevLoggerListenPortFlag is the port to listen to for syslog messages.
	DevLoggerListenPortFlag = config.DeclareString(
		PackageName, "devLoggerListenPort", "6514")
	// InstanceIDFlag is the instance ID of the devlogger. Note: This is a
	// different instance ID than leverutil.InstanceIDFlag because they serve
	// different things.
	InstanceIDFlag = config.DeclareString(
		PackageName, "instanceID", leverutil.RandomID())
	// DisableFlag disables the devlogger server.
	DisableFlag = config.DeclareBool(PackageName, "disable")
)

var logger = leverutil.GetLogger(PackageName, "DevLogger")

// DevLoggerService is the name of the devlogger internal service.
const DevLoggerService = "devlogger"

// DevLogger is a syslog server meant to be used only for dev instances of
// Lever. It doesn't have the necessary redundancy and safeguards in place for
// production use.
type DevLogger struct {
	server     *syslog.Server
	serviceSKA *scale.SelfKeepAlive
Пример #4
0
func (tracker *FleetTracker) scaleUp(delta int, rpcEvent *RPCEvent) error {
	logger.WithFields(
		"leverEnv", rpcEvent.Environment,
		"leverService", rpcEvent.Service,
		"codeVersion", rpcEvent.CodeVersion,
		"servingID", rpcEvent.ServingID,
		"deltaInstances", delta,
	).Info("Scaling up")

	// Read the entry point from the config.
	codeDir := dockerutil.CodeDirPath(
		rpcEvent.Environment, rpcEvent.Service, rpcEvent.CodeVersion)
	leverConfig, err := core.ReadLeverConfig(codeDir)
	if err != nil {
		return err
	}

	// Spin up.
	hadErrors := false
	for i := 0; i < delta; i++ {
		instanceID := leverutil.RandomID()
		containerID, node, err := dockerutil.StartDockerContainer(
			tracker.docker, rpcEvent.Environment, rpcEvent.Service,
			instanceID, rpcEvent.CodeVersion, rpcEvent.IsAdmin, leverConfig)
		if err != nil {
			logger.WithFields(
				"err", err,
				"leverEnv", rpcEvent.Environment,
				"leverService", rpcEvent.Service,
				"codeVersion", rpcEvent.CodeVersion,
				"servingID", rpcEvent.ServingID,
			).Error("Error starting docker container")
			hadErrors = true
			continue
		}

		err = hostman.InitializeInstance(
			tracker.grpcPool,
			&hostman.InstanceInfo{
				Environment:       rpcEvent.Environment,
				Service:           rpcEvent.Service,
				InstanceID:        instanceID,
				ContainerID:       containerID,
				ServingID:         rpcEvent.ServingID,
				LevInstResourceID: "",
				LevInstSessionID:  "",
			}, node)
		if err != nil {
			logger.WithFields(
				"err", err,
				"leverEnv", rpcEvent.Environment,
				"leverService", rpcEvent.Service,
				"codeVersion", rpcEvent.CodeVersion,
				"servingID", rpcEvent.ServingID,
				"node", node,
				"leverInstanceID", instanceID,
			).Error("Failed to initialize instance remotely")
			hadErrors = true
			continue
		}
	}
	if hadErrors {
		return fmt.Errorf("There were errors during scale down")
	}
	return nil
}
Пример #5
0
func (proxy *LeverProxy) handleExtOutStream(
	srcEnv string, stream *http2stream.HTTP2Stream, isExtStream bool) {
	streamLogger := proxy.outLogger
	if isExtStream {
		streamLogger = proxy.extLogger
	}

	headers := stream.GetHeaders()
	err := expectHeaders(headers, "lever-url")
	if err != nil {
		streamLogger.WithFields("err", err).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	leverURL, err := core.ParseLeverURL(headers["lever-url"][0])
	if err != nil {
		proxy.inLogger.WithFields(
			"err", err, "leverURL", headers["lever-url"][0]).Error(
			"Unable to parse Lever URL")
	}

	if !core.IsInternalEnvironment(leverURL.Environment) {
		err = fmt.Errorf("Cannot route to dest env")
		streamLogger.WithFields(
			"err", err,
			"leverEnv", leverURL.Environment,
		).Error("")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	streamID := leverutil.RandomID()
	streamLogger.WithFields(
		"leverURL", leverURL.String(),
		"srcEnv", srcEnv,
		"streamID", streamID,
	).Debug("Receiving stream")
	streamLogger = streamLogger.WithFields("streamID", streamID)

	isFromExt := !core.IsInternalEnvironment(srcEnv)
	hostInfo, err := proxy.finder.GetHost(
		leverURL.Environment, leverURL.Service, leverURL.Resource, isFromExt)
	if err != nil {
		streamLogger.WithFields(
			"err", err,
			"leverURL", leverURL.String(),
		).Error("Error trying to find / allocate host for request")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	streamLogger.WithFields(
		"leverInstanceID", hostInfo.InstanceID,
		"containerID", hostInfo.ContainerID,
		"servingID", hostInfo.ServingID,
		"hostProxyAddr", hostInfo.HostAddr,
		"isNewInstance", hostInfo.IsNewInstance,
	).Debug("Routing to host proxy")
	destStream, err := proxy.client.NewStream(hostInfo.HostAddr)
	if err != nil {
		streamLogger.WithFields(
			"err", err,
			"hostProxyAddr", hostInfo.HostAddr,
		).Error("Error trying to create client stream to dest")
		stream.Write(&http2stream.MsgError{Err: err})
		return
	}

	addHeaders := make(map[string][]string)
	addHeaders["x-lever-src-env"] = []string{srcEnv}
	addHeaders["x-lever-dest-instance-id"] = []string{hostInfo.InstanceID}
	addHeaders["x-lever-dest-container-id"] = []string{hostInfo.ContainerID}
	addHeaders["x-lever-serving-id"] = []string{hostInfo.ServingID}
	addHeaders["x-lever-code-version"] = []string{
		strconv.Itoa(int(hostInfo.CodeVersion))}
	addHeaders["x-lever-inst-resource-id"] = []string{
		hostInfo.LevInstResourceID}
	addHeaders["x-lever-inst-session-id"] = []string{hostInfo.LevInstSessionID}
	addHeaders["x-lever-res-resource-id"] = []string{hostInfo.LevResResourceID}
	addHeaders["x-lever-res-session-id"] = []string{hostInfo.LevResSessionID}

	firstHeaders := true
	stream.ProxyTo(
		destStream,
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(hostInfo.HostAddr)
			return proxy.filterTo(&firstHeaders, addHeaders, msg)
		},
		func(msg http2stream.MsgItem) []http2stream.MsgItem {
			proxy.client.KeepAlive(hostInfo.HostAddr)
			return noFilter(msg)
		})
}
Пример #6
0
func (finder *Finder) newInstance(
	env, service string, version int64) (
	levInstTarget *LevInstTarget, targetNode string,
	levInstResource *scale.Resource, success bool, err error) {
	// Note: This process only takes place for the very first instance within
	//       a service. The non-first instances do not have a resource entry
	//       and they are found via service lookup through DNS.
	levInstResourceID := makeLevInstResourceID(env, service, version)
	levInstResource, success, err = scale.ConstructResource(
		leverutil.ServiceFlag.Get(), levInstResourceID,
		InstanceConstructTimeoutFlag.Get())
	if err != nil {
		finder.logger.WithFields("err", err).Error(
			"Failed to construct new instance")
		return nil, "", nil, false, err
	}
	if !success {
		// Failed to get lock. Someone else constructed instance.
		levInstTarget = new(LevInstTarget)
		err = json.Unmarshal([]byte(levInstResource.GetTarget()), levInstTarget)
		if err != nil {
			finder.logger.WithFields("err", err).Panic("Failed to decode json")
		}
		finder.logger.WithFields(
			"leverEnv", env,
			"leverService", service,
			"leverInstanceID", levInstTarget.InstanceID,
		).Debug("Reusing instance")
		return levInstTarget, levInstResource.GetTargetNode(), levInstResource,
			false, nil
	}

	// Read the entry point from the config.
	codeDir := dockerutil.CodeDirPath(env, service, version)
	leverConfig, err := core.ReadLeverConfig(codeDir)
	if err != nil {
		finder.logger.WithFields("err", err).Error(
			"Failed to read lever.json")
		return nil, "", nil, false, err
	}

	// TODO: If somehow first RPC fails and service no longer
	//       contacted afterwards, then the container remains hanging
	//       forever. Need a way to kill it / make it expire after a
	//       while for this situation.
	//       Idea: Poll docker every ~30s for instances that we are not
	//       handling and register those.
	instanceID := leverutil.RandomID() // TODO: Collisions possible.
	leverURL := &core.LeverURL{
		Environment: env,
		Service:     service,
	}
	isAdmin := core.IsAdmin(leverURL)
	containerID, node, err := dockerutil.StartDockerContainer(
		finder.docker, env, service, instanceID, version, isAdmin, leverConfig)
	if err != nil {
		finder.logger.WithFields(
			"err", err,
			"leverEnv", env,
			"leverService", service,
			"leverInstanceID", instanceID,
		).Error("Unable to start container")
		return nil, "", nil, false, err
	}

	hostAddr, err := GetHostAddrOnNode(node)
	if err != nil {
		finder.logger.WithFields("err", err).Error(
			"Failed to get Host addr on node")
		return nil, "", nil, false, err
	}

	finder.logger.WithFields(
		"leverEnv", env,
		"leverService", service,
		"leverInstanceID", instanceID,
		"containerID", containerID,
		"node", node,
	).Debug("Creating new instance")

	levInstTarget = &LevInstTarget{
		HostAddr:    hostAddr,
		InstanceID:  instanceID,
		ContainerID: containerID,
	}
	target, err := json.Marshal(levInstTarget)
	if err != nil {
		finder.logger.WithFields("err", err).Panic("Failed to encode json")
	}

	err = levInstResource.DoneConstructing(
		string(target), node, 2*hostman.InstanceExpiryTimeFlag.Get())
	if err != nil {
		finder.logger.WithFields("err", err).Error(
			"Failed to register new instance")
		return nil, "", nil, false, err
	}

	return levInstTarget, node, levInstResource, true, nil
}
Пример #7
0
func tmpDir(env string) string {
	return filepath.Join(
		codeBaseDir, env, ".tmp-"+leverutil.RandomID())
}