Example #1
0
func (suite *sessionRecoverySuite) TestExpiredCacheRecovery() {
	scope := New().(*realScope)
	scope.userCache = newTestCache()

	mock := multiclient.NewMock()
	stub := &multiclient.Stub{
		Service:  loginService,
		Endpoint: readSessionEndpoint,
		Response: &sessreadproto.Response{
			SessId: proto.String(testSessId),
			Token:  proto.String(testToken),
		},
	}
	mock.Stub(stub)
	multiclient.SetCaller(mock.Caller())

	u, err := FromSessionToken(testSessId, testToken)
	suite.Assertions.Equal(testSessId, u.SessId)
	suite.Assertions.NoError(err)
	suite.Assertions.NotNil(u)
	u.RenewTs = time.Now().Add(-2 * time.Minute)
	u.ExpiryTs = u.RenewTs.Add(1 * time.Minute)
	suite.Assertions.NoError(scope.userCache.Store(u))
	suite.Assertions.Equal(0, stub.CountCalls())

	// As this session has expired, the login service should be called to recover (and renew) it
	suite.Assertions.False(scope.IsAuth())
	suite.Assertions.NoError(scope.RecoverSession(testSessId))
	suite.Assertions.True(scope.IsAuth())
	u = scope.AuthUser()
	suite.Assertions.NotNil(u)
	suite.Assertions.Equal(1, stub.CountCalls())
}
Example #2
0
// ToProtobuf takes a Error and returns a protobuf error
func ToProtobuf(err Error) *pe.PlatformError {
	return &pe.PlatformError{
		Type:        pe.PlatformError_ErrorType(pe.PlatformError_ErrorType_value[err.Type()]).Enum(),
		Code:        proto.String(err.Code()),
		Description: proto.String(err.Description()),
		Context:     err.Context(),
		HttpCode:    proto.Uint32(err.HttpCode()),
	}
}
Example #3
0
func healthCheckSampleToProto(hc *HealthCheck, sample *Sample) *hcproto.HealthCheck {
	return &hcproto.HealthCheck{
		Timestamp:        proto.Int64(sample.At.Unix()),
		HealthCheckId:    proto.String(hc.Id),
		ServiceName:      proto.String(hc.ServiceName),
		ServiceVersion:   proto.Uint64(hc.ServiceVersion),
		Hostname:         proto.String(hc.Hostname),
		InstanceId:       proto.String(hc.InstanceId),
		IsHealthy:        proto.Bool(sample.IsHealthy),
		ErrorDescription: proto.String(sample.ErrorDescription),
		Measurements:     mapToProto(sample.Measurements),
		Priority:         hcproto.HealthCheck_Priority(hc.Priority).Enum(),
	}
}
Example #4
0
func mapToProto(m map[string]string) []*hcproto.HealthCheck_KeyValue {
	if m == nil {
		return []*hcproto.HealthCheck_KeyValue{}
	}
	ret := make([]*hcproto.HealthCheck_KeyValue, len(m))
	i := 0
	for k, v := range m {
		ret[i] = &hcproto.HealthCheck_KeyValue{
			Key:   proto.String(k),
			Value: proto.String(v),
		}
		i++
	}
	return ret
}
Example #5
0
// SignOut destroys the current session so that it cannot be used again
func (s *realScope) SignOut(user *User) error {
	cl := multiclient.New().DefaultScopeFrom(s.getRpcScope())

	cl.AddScopedReq(&multiclient.ScopedReq{
		Uid:      "deletesess",
		Service:  loginService,
		Endpoint: deleteSessionEndpoint,
		Req: &sessdelproto.Request{
			SessId: proto.String(user.SessId),
		},
		Rsp: &sessdelproto.Response{},
	})

	if cl.Execute().AnyErrors() {
		return cl.Succeeded("deletesess")
	}

	if err := s.userCache.Purge(user.SessId); err != nil {
		log.Errorf("[Auth] Error purging session cache: %v", err)
	}

	s.Lock()
	defer s.Unlock()

	s.authUser = nil
	s.triedAuth = false

	return nil
}
Example #6
0
// jsonschemaHandler returns all registered endpoints in json schema format as per ITF draft4
// http://json-schema.org/latest/json-schema-core.html
func jsonschemaHandler(req *Request) (proto.Message, errors.Error) {
	// Get all endpoints
	request := req.Data().(*jsonschemaproto.Request)
	endpoint := request.GetEndpoint()

	endpoints := reg.iterate()
	schemas := make([]*jsonschema.JsonSchema, 0)
	for _, ep := range endpoints {
		if endpoint != "" && endpoint != ep.GetName() {
			continue
		}
		schema, err := marshalEndpoint(ep)
		if err == nil && schema != nil {
			schemas = append(schemas, schema)
		}
	}

	rsp, err := json.Marshal(schemas)
	if err != nil {
		return nil, errors.InternalServerError("com.hailocab.kernel.marshal.error", fmt.Sprintf("Unable to unmarshal response data: %v", err.Error()))
	}

	return &jsonschemaproto.Response{
		Jsonschema: proto.String(string(rsp)),
	}, nil
}
Example #7
0
func (s *realScope) doAuth(mech, device string, creds map[string]string) (*User, error) {
	reqProto := &authproto.Request{
		Mech:       proto.String(mech),
		DeviceType: proto.String(device),
		Meta:       make([]*loginproto.KeyValue, 0),
	}
	for k, v := range creds {
		switch k {
		case "username":
			reqProto.Username = proto.String(v)
		case "password":
			reqProto.Password = proto.String(v)
		case "newPassword":
			reqProto.NewPassword = proto.String(v)
		case "application":
			reqProto.Application = proto.String(v)
		default:
			// Add additional fields to Meta, such as DeviceId, osVersion, appVersion
			reqProto.Meta = append(reqProto.Meta, &loginproto.KeyValue{
				Key:   proto.String(k),
				Value: proto.String(v),
			})
		}
	}

	cl := multiclient.New().DefaultScopeFrom(s.getRpcScope())
	rsp := &authproto.Response{}
	cl.AddScopedReq(&multiclient.ScopedReq{
		Uid:      "auth",
		Service:  loginService,
		Endpoint: authEndpoint,
		Req:      reqProto,
		Rsp:      rsp,
		Options:  client.Options{"retries": 0},
	})

	if cl.Execute().AnyErrors() {
		// specfically map out bad credentials error
		err := cl.Succeeded("auth")
		if err.Code() == badCredentialsErrCode {
			return nil, BadCredentialsError
		}
		return nil, err
	}

	// recover this user
	u, err := FromSessionToken(rsp.GetSessId(), rsp.GetToken())
	if err != nil {
		return nil, err
	}

	if err := s.userCache.Store(u); err != nil {
		log.Errorf("[Auth] Error caching session: %v", err)
	}

	return u, nil
}
Example #8
0
func (s *stats) endpoint(ep Endpoint, t endpointTimers) *pstats.EndpointStats {
	return &pstats.EndpointStats{
		EndpointName: proto.String(ep.GetName()),
		Sla:          endpointSLA(ep),
		Success:      endpointStat(t.success),
		Error:        endpointStat(t.errors),
	}
}
Example #9
0
// publishFailure publishes a failure/panic event to be monitored.
func publishFailure(r interface{}) {
	var p string

	switch r.(type) {
	case string:
		p = r.(string)
	case error:
		p = fmt.Sprintf("%v", r.(error))
	default:
		p = "Unknown panic"
	}

	b := make([]byte, 1024)
	runtime.Stack(b, true)

	if err := client.Pub("com.hailocab.monitor.failure", &fproto.Failure{
		ServiceName:    proto.String(Name),
		ServiceVersion: proto.Uint64(Version),
		AzName:         proto.String(az),
		Hostname:       proto.String(hostname),
		InstanceId:     proto.String(InstanceID),
		Timestamp:      proto.Int64(time.Now().Unix()),
		Uptime:         proto.Int64(int64(time.Since(serviceStarted).Seconds())),
		Type:           proto.String("PANIC"),
		Reason:         proto.String(p),
		Stack:          proto.String(string(b)),
	}); err != nil {
		log.Errorf("[Server] Failed to publish failure event: %v", err)
	}
}
Example #10
0
func TestResponder(t *testing.T) {
	stub := &Stub{
		Service:  mockFooService,
		Endpoint: mockHealthEndpoint,
		Responder: func(invocation int, req *client.Request) (proto.Message, errors.Error) {
			if invocation == 1 {
				return &hcproto.Response{
					Healthchecks: []*hcproto.HealthCheck{
						&hcproto.HealthCheck{
							Timestamp:      proto.Int64(1403629015),
							ServiceName:    proto.String("foo"),
							ServiceVersion: proto.Uint64(1403629015),
							Hostname:       proto.String("localhost"),
							InstanceId:     proto.String("foobar"),
							HealthCheckId:  proto.String("boom"),
							IsHealthy:      proto.Bool(true),
						},
					},
				}, nil
			}
			return nil, errors.InternalServerError("only.one.allowed", "First call only works")
		},
	}
	mock := NewMock().Stub(stub)

	caller := mock.Caller()
	req, _ := client.NewRequest(mockFooService, mockHealthEndpoint, &hcproto.Request{})
	rsp := &hcproto.Response{}
	e := caller(req, rsp)

	assert.Nil(t, e,
		"Expecting our mocked call to be intercepted and stubbed response returned, got err: %v", e)

	assert.Len(t, rsp.GetHealthchecks(), 1,
		"Response does not contain our mocked content: no healthchecks")

	// now repeat, and we SHOULD get an error
	e = caller(req, rsp)
	assert.NotNil(t, e,
		"Expecting our mocked call to be intercepted and error response returned on 2nd call")

	assert.Equal(t, e.Code(), "only.one.allowed",
		"Expecting code 'only.one.allowed', got '%s'", e.Code())
}
Example #11
0
// doRecoverSession is the meat and veg for RecoverSession
func (s *realScope) doRecoverSession(sessId string) (*User, error) {
	// Check cache; ignore errors (will have impact on service performance, but not functionality)
	queryLogin := false
	u, hit, err := s.userCache.Fetch(sessId)
	if err != nil {
		log.Warnf("[Auth] Error fetching session from cache (will call login service): %v", err)
		queryLogin = true
	} else if u != nil && u.ExpiryTs.Before(time.Now()) && u.CanAutoRenew() { // Cached token has expired
		log.Infof("[Auth] Cache-recovered token has expired (%s); will call login service", u.ExpiryTs.String())
		queryLogin = true
	} else {
		queryLogin = u == nil && !hit
	}

	if queryLogin {
		cl := multiclient.New().DefaultScopeFrom(s.getRpcScope())
		rsp := &sessreadproto.Response{}
		cl.AddScopedReq(&multiclient.ScopedReq{
			Uid:      "readsess",
			Service:  loginService,
			Endpoint: readSessionEndpoint,
			Req: &sessreadproto.Request{
				SessId: proto.String(sessId),
			},
			Rsp: rsp,
		})

		if cl.Execute().AnyErrorsIgnoring([]string{errors.ErrorNotFound}, nil) {
			err := cl.Succeeded("readsess")
			log.Errorf("[Auth] Auth scope recovery error [%s: %s] %v", err.Type(), err.Code(), err.Description())
			return nil, err
		}

		// found a session?
		if rsp.GetSessId() == "" && rsp.GetToken() == "" {
			log.Debugf("[Auth] Session '%s' not found (not valid) when trying to recover from login service", sessId)
			// @todo we could cache this (at least for a short time) to prevent repeated hammering of login service
		} else {
			u, err = FromSessionToken(rsp.GetSessId(), rsp.GetToken())
			if err != nil {
				log.Errorf("[Auth] Error getting user from session: %v", err)
			} else {
				log.Tracef("[Auth] Auth scope - recovered user '%s' from session '%s'", u.Id, rsp.GetSessId())
			}
		}

		// ignore errors; just means we have no user
		if u != nil {
			s.userCache.Store(u)
		}
	}

	return u, nil
}
Example #12
0
// TestRecoverSessionHappyFound tests happy case (no service call failures) when we do find a user
func (suite *sessionRecoverySuite) TestRecoverSessionHappyFound() {
	scope := New().(*realScope)
	scope.userCache = newTestCache()

	mock := multiclient.NewMock()
	stub := &multiclient.Stub{
		Service:  loginService,
		Endpoint: readSessionEndpoint,
		Response: &sessreadproto.Response{
			SessId: proto.String(testSessId),
			Token:  proto.String(testToken),
		},
	}
	mock.Stub(stub)
	multiclient.SetCaller(mock.Caller())

	err := scope.RecoverSession(testSessId)
	suite.Assertions.NoError(err, "Unexpected recovery error")
	suite.Assertions.True(scope.IsAuth())
	suite.Assertions.True(scope.HasTriedAuth())
	suite.Assertions.Equal(1, stub.CountCalls(), "Expecting 1 call to readsession")
	req := &sessreadproto.Request{}
	err = stub.Request(0).Unmarshal(req)
	suite.Assertions.NoError(err)
	suite.Assertions.Equal(testSessId, req.GetSessId())
	suite.Assertions.False(req.GetNoRenew())
	u := scope.authUser
	suite.Assertions.NotNil(u)
	suite.Assertions.Equal("dave", u.Id)

	// Clean out scope
	scope.Clean()
	suite.Assertions.False(scope.IsAuth(), "Expecting scope to be IsAuth==false after Clean()")
	suite.Assertions.False(scope.HasTriedAuth(), "Expecting scope to have HasTriedAuth()==false after Clean()")
	suite.Assertions.Nil(scope.AuthUser(), "Expecting AuthUser()==nil after Clean()")

	// recover AGAIN -- this time it shoud be cached
	suite.Assertions.NoError(scope.RecoverSession(testSessId), "Unexpected recovery error")
	suite.Assertions.True(scope.IsAuth())
	suite.Assertions.Equal(1, stub.CountCalls(), "Expecting 1 call to readsession (should be cached now)")
}
Example #13
0
// reloadSlas loads timeouts from discovery service for all services we know about (have tried to call)
func (t *Timeout) reloadSlas() {
	replacement := make(map[string]map[string]time.Duration)

	for service := range t.endpoints {
		// load from discovery service
		log.Debugf("[Client] Loading SLAs from discovery service for %v...", service)
		req, err := NewRequest("com.hailocab.kernel.discovery", "endpoints", &eps.Request{
			Service: proto.String(service),
		})
		if err != nil {
			log.Warnf("[Client] Failed to create proto request to get endpoints for service: %s", service)
			continue
		}
		rsp := &eps.Response{}
		// explicitly define timeout since we're in no rush
		if err := t.client.Req(req, rsp, Options{"retries": 0, "timeout": time.Second * 5}); err != nil {
			log.Warnf("[Client] Trouble getting endpoint response back from discovery-service for service: %s", service)
			continue
		}

		for _, ep := range rsp.GetEndpoints() {
			endpoint := strings.TrimLeft(strings.TrimPrefix(ep.GetFqName(), service), ".")
			if _, ok := replacement[service]; !ok {
				replacement[service] = make(map[string]time.Duration)
			}
			replacement[service][endpoint] = msToDuration(ep.GetUpper95())
		}
	}

	// double check we have all the things we started with -- if not, but back the "last known" (probably defaults)
	for service, serviceEndpoints := range t.endpoints {
		for endpoint, timeout := range serviceEndpoints {
			if _, ok := replacement[service]; !ok {
				replacement[service] = make(map[string]time.Duration)
			}
			if _, ok := replacement[service][endpoint]; !ok {
				log.Debugf("[Client] Failed to find SLA for %s.%s, falling back to %v", service, endpoint, timeout)
				replacement[service][endpoint] = timeout
			}
		}
	}

	// SLAs changed? if not, don't bother switching+logging
	if hashSlas(replacement) == t.hashEndpoints() {
		return
	}

	t.Lock()
	defer t.Unlock()
	t.endpoints = replacement

	log.Infof("[Client] Loaded new SLAs from discovery service: %v", t.endpoints)
}
// traceIn traces a request inbound to a service to handle
func traceIn(req *Request) {
	if req.shouldTrace() {
		go trace.Send(&traceproto.Event{
			Timestamp:         proto.Int64(time.Now().UnixNano()),
			TraceId:           proto.String(req.TraceID()),
			Type:              traceproto.Event_IN.Enum(),
			MessageId:         proto.String(req.MessageID()),
			ParentMessageId:   proto.String(req.ParentMessageID()),
			From:              proto.String(req.From()),
			To:                proto.String(fmt.Sprintf("%v.%v", req.Service(), req.Endpoint())),
			Hostname:          proto.String(hostname),
			Az:                proto.String(az),
			Payload:           proto.String(""), // @todo
			HandlerInstanceId: proto.String(InstanceID),
			PersistentTrace:   proto.Bool(req.TraceShouldPersist()),
		})
	}
}
Example #15
0
// traceReq decides if we want to trigger a trace event (when sending a request) and if so deals with it
func (c *client) traceReq(req *Request) {
	if req.shouldTrace() {
		trace.Send(&traceproto.Event{
			Timestamp:       proto.Int64(time.Now().UnixNano()),
			TraceId:         proto.String(req.TraceID()),
			Type:            traceproto.Event_REQ.Enum(),
			MessageId:       proto.String(req.MessageID()),
			ParentMessageId: proto.String(req.ParentMessageID()),
			From:            proto.String(req.From()),
			FromEndpoint:    proto.String(req.FromEndpoint()),
			To:              proto.String(fmt.Sprintf("%v.%v", req.Service(), req.Endpoint())),
			Hostname:        proto.String(c.hostname),
			Az:              proto.String(c.az),
			Payload:         proto.String(""), // @todo
			PersistentTrace: proto.Bool(req.TraceShouldPersist()),
		})
	}
}
Example #16
0
File: foo.go Project: armada-io/h2
func Foo(req h2.Request) (proto.Message, h2.Error) {
	request := req.Data().(*foo.Request)
	log.Debugf("Received bar=%v", request.GetBar())

	s := services.Response{}
	err := h2.Call("com.hailocab.kernel.discovery", "services", &services.Request{}, &s)
	if err != nil {
		log.Warnf("Ouch... the discovery services seems to be down ): %v", err)
	}
	rsp := &foo.Response{
		Baz: proto.String(fmt.Sprintf("There are %v services running on your h2 cluster", len(s.GetServices()))),
	}
	return rsp, nil
}
Example #17
0
// load config via login service
func (s *serviceToService) load() error {
	svc := s.getService()
	if svc == "" {
		log.Debug("[Auth] Skipping loading service-to-service auth rules (no service defined)")
		return nil
	}

	log.Tracef("[Auth] Loading service-to-service auth rules for %s", svc)
	reqProto := &endpointauth.Request{
		Service: proto.String(svc),
	}
	req, err := client.NewRequest("com.hailocab.service.login", "endpointauth", reqProto)
	if err != nil {
		return err
	}
	// scope it
	req.SetFrom(svc)
	rsp := &endpointauth.Response{}
	if err := client.Req(req, rsp); err != nil {
		return err
	}

	newEndpoints := make(map[string]grantedServices)

	for _, ep := range rsp.GetEndpoints() {
		name := ep.GetEndpoint()
		if _, ok := newEndpoints[name]; !ok {
			newEndpoints[name] = make(grantedServices)
		}
		// add in the granted services to this endpoint
		for _, gs := range ep.GetGranted() {
			newEndpoints[name][gs.GetName()] = role(gs.GetRole())
		}
	}

	// check if changed - to avoid locking/changing/logging if not
	if hashEndpoints(newEndpoints) == s.hash() {
		return nil
	}

	// switch in config
	s.Lock()
	defer s.Unlock()
	s.endpoints = newEndpoints

	log.Debugf("[Auth] Loaded service-to-service auth rules: %#v", s.endpoints)

	return nil
}
Example #18
0
func Endpoint(name string, configStruct interface{}) *service.Endpoint {
	handler := func(req *server.Request) (proto.Message, errors.Error) {
		return &schemaProto.Response{
			Schema: proto.String(schema.Of(configStruct).String()),
		}, nil
	}

	return &server.Endpoint{
		Name:       name,
		Mean:       200,
		Upper95:    400,
		Handler:    handler,
		Authoriser: service.OpenToTheWorldAuthoriser(),
	}
}
Example #19
0
File: hello.go Project: jqk6/H2
// Hello provides a func to process an incoming 'hello' request.
// A handler requires a request parameter and will return either a response or an error
func Hello(req h2.Request) (proto.Message, h2.Error) {
	request := req.Data().(*protoHello.Request)

	// Error example
	if len(request.GetName()) == 0 {
		// We specify multi error types in the platform layer repository in the 'errors'
		// package. See: https://godoc.org/github.com/hailocab/platform-layer/errors
		return nil, errors.BadRequest(HelloEndpoint, "You didn't specify a name in the request")
	}

	// Return response with the message set to `Hello <name>`
	return &protoHello.Response{
		Message: proto.String(fmt.Sprintf("Hello %q", request.GetName())),
	}, nil
}
Example #20
0
func TestMockCallerPopulatesResponse(t *testing.T) {
	req, _ := client.NewRequest(mockFooService, mockHealthEndpoint, &hcproto.Request{})
	stub := &Stub{
		Service:  mockFooService,
		Endpoint: mockHealthEndpoint,
		Response: &hcproto.Response{
			Healthchecks: []*hcproto.HealthCheck{
				&hcproto.HealthCheck{
					Timestamp:      proto.Int64(1403629015),
					ServiceName:    proto.String("foo"),
					ServiceVersion: proto.Uint64(1403629015),
					Hostname:       proto.String("localhost"),
					InstanceId:     proto.String("foobar"),
					HealthCheckId:  proto.String("boom"),
					IsHealthy:      proto.Bool(true),
				},
			},
		},
	}
	mock := NewMock().Stub(stub)

	caller := mock.Caller()
	rsp := &hcproto.Response{}
	e := caller(req, rsp)
	assert.Nil(t, e,
		"Expecting our mocked call to be intercepted and stubbed response returned, got err: %v", e)

	// ensure stub has what we expect
	assert.Len(t, stub.matched, 1,
		"Expecting 1 match payload to be stored after execution")

	assert.Equal(t, stub.CountCalls(), 1, "CountCalls should return 1 too")

	assert.Len(t, rsp.GetHealthchecks(), 1,
		"Response does not contain our mocked content: no healthchecks")
}
Example #21
0
// get returns a snapshot of platform stats
func (s *stats) get(status string) *pstats.PlatformStats {
	rusageStats := s.rusage()
	runtimeStats := s.runtime()
	endpointStats := s.endpoints()
	return &pstats.PlatformStats{
		ServiceName:    proto.String(ServiceName),
		ServiceVersion: proto.Uint64(ServiceVersion),
		ServiceType:    proto.String(ServiceType),
		AzName:         proto.String(AzName),
		Hostname:       proto.String(hostname),
		InstanceId:     proto.String(InstanceID),
		Status:         proto.String(status),
		Timestamp:      proto.Int64(time.Now().Unix()),
		Uptime:         proto.Int64(int64(time.Since(s.startTime).Seconds())),
		Rusage:         rusageStats,
		Runtime:        runtimeStats,
		Endpoints:      endpointStats,
	}
}
// ConfiguredHttpCaller with more explicit configuration options than simple HttpCaller
func ConfiguredHttpCaller(opts Options) Caller {
	tp := &httpclient.Transport{
		ConnectTimeout:        durationOrDefault(opts.ConnectTimeout, 5*time.Second),
		RequestTimeout:        durationOrDefault(opts.RequestTimeout, 5*time.Second),
		ResponseHeaderTimeout: durationOrDefault(opts.ResponseHeaderTimeout, 5*time.Second),
	}
	if opts.TlsSkipVerify {
		tp.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}

	httpClient := &http.Client{Transport: tp}

	return func(req *client.Request, rsp proto.Message) errors.Error {
		u, err := url.Parse(opts.BaseUrl)

		q := u.Query()
		q.Set("session_id", req.SessionID())
		q.Set("service", req.Service())
		q.Set("endpoint", req.Endpoint())
		u.Path = "/rpc"
		u.RawQuery = q.Encode()

		var httpReq *http.Request

		// send JSON req content-type to thin API as form-encoded data
		// send proto req content-type directly as bytes, with proto content type
		if req.ContentType() == jsonContentType {
			values := make(url.Values)
			values.Set("service", req.Service())
			values.Set("endpoint", req.Endpoint())
			values.Set("request", string(req.Payload()))
			httpReq, _ = http.NewRequest("POST", u.String(), bytes.NewReader([]byte(values.Encode())))
			httpReq.Header.Set("Content-Type", formEncodedContentType)
		} else {
			httpReq, _ = http.NewRequest("POST", u.String(), bytes.NewReader(req.Payload()))
			httpReq.Header.Set("Content-Type", protoContentType)
		}

		log.Tracef("[Multiclient] HTTP caller - calling '%s' : content-type '%s'", u.String(), req.ContentType())

		httpRsp, err := httpClient.Do(httpReq)
		if err != nil {
			log.Warnf("[Multiclient] HTTP caller error calling %s.%s via %s : %s", req.Service(), req.Endpoint(), u.String(), err)
			return errors.InternalServerError("multiclienthttp.postform", fmt.Sprintf("Error calling %s.%s via %s : %s", req.Service(), req.Endpoint(), u.String(), err))
		}

		defer httpRsp.Body.Close()
		rspBody, err := ioutil.ReadAll(httpRsp.Body)
		if err != nil {
			return errors.BadResponse("multiclienthttp.readresponse", fmt.Sprintf("Error reading response bytes: %v", err))
		}

		// what status code?
		if httpRsp.StatusCode != 200 {
			// deal with error
			e := &protoerror.PlatformError{}
			var err error
			if req.ContentType() == jsonContentType {
				jsonErr := &errorBody{}
				err = json.Unmarshal(rspBody, jsonErr)
				e.Code = proto.String(jsonErr.DottedCode)
				e.Context = jsonErr.Context
				e.Description = proto.String(jsonErr.Payload)
				e.HttpCode = proto.Uint32(uint32(httpRsp.StatusCode))
				// this conversion is lossy, since the JSON response for errors, as crafted
				// by the "thin API", does not currently include the error type, so we have
				// to guess from HTTP status code, but there is no distinct code for "BAD_RESPONSE"
				switch httpRsp.StatusCode {
				case 400:
					e.Type = protoerror.PlatformError_BAD_REQUEST.Enum()
				case 403:
					e.Type = protoerror.PlatformError_FORBIDDEN.Enum()
				case 404:
					e.Type = protoerror.PlatformError_NOT_FOUND.Enum()
				case 500:
					e.Type = protoerror.PlatformError_INTERNAL_SERVER_ERROR.Enum()
				case 504:
					e.Type = protoerror.PlatformError_TIMEOUT.Enum()
				}
			} else {
				err = proto.Unmarshal(rspBody, e)
			}
			// some issue understanding error rsp
			if err != nil {
				return errors.BadResponse("multiclienthttp.unmarshalerr", fmt.Sprintf("Error unmarshaling error response '%s': %v", string(rspBody), err))
			}
			return errors.FromProtobuf(e)
		}

		// unmarshal response
		if req.ContentType() == jsonContentType {
			err = json.Unmarshal(rspBody, rsp)
		} else {
			err = proto.Unmarshal(rspBody, rsp)
		}
		if err != nil {
			return errors.BadResponse("multiclienthttp.unmarshal", fmt.Sprintf("Error unmarshaling response: %v", err))
		}

		return nil
	}
}
Example #23
0
// traceAttemptTimeout decides if we want to trigger a trace event for an attempt timeout, and processes it
func (c *client) traceAttemptTimeout(req *Request, attemptNum int, timeout time.Duration) {
	if req.shouldTrace() {
		desc := fmt.Sprintf("Attempt %v timeout talking to '%s.%s' after '%v' for '%s'", attemptNum, req.Service(), req.Endpoint(), timeout, req.MessageID())
		trace.Send(&traceproto.Event{
			Timestamp:        proto.Int64(time.Now().UnixNano()),
			TraceId:          proto.String(req.TraceID()),
			Type:             traceproto.Event_ATTEMPT_TIMEOUT.Enum(),
			MessageId:        proto.String(req.MessageID()),
			From:             proto.String(req.From()),
			FromEndpoint:     proto.String(req.FromEndpoint()),
			To:               proto.String(fmt.Sprintf("%v.%v", req.Service(), req.Endpoint())),
			ParentMessageId:  proto.String(req.ParentMessageID()),
			Hostname:         proto.String(c.hostname),
			Az:               proto.String(c.az),
			Payload:          proto.String(""), // @todo
			ErrorCode:        proto.String("com.hailocab.kernel.platform.attemptTimeout"),
			ErrorDescription: proto.String(desc),
			Duration:         proto.Int64(int64(timeout)),
			PersistentTrace:  proto.Bool(req.TraceShouldPersist()),
		})
	}
}
Example #24
0
// traceRsp decides if we want to trigger a trace event (when processing response) and if so deals with it
func (c *client) traceRsp(req *Request, rsp *Response, err errors.Error, d time.Duration) {
	if req.shouldTrace() {
		e := &traceproto.Event{
			Timestamp:       proto.Int64(time.Now().UnixNano()),
			TraceId:         proto.String(req.TraceID()),
			Type:            traceproto.Event_REP.Enum(),
			MessageId:       proto.String(req.MessageID()),
			From:            proto.String(req.From()),
			FromEndpoint:    proto.String(req.FromEndpoint()),
			To:              proto.String(fmt.Sprintf("%v.%v", req.Service(), req.Endpoint())),
			ParentMessageId: proto.String(req.ParentMessageID()),
			Hostname:        proto.String(c.hostname),
			Az:              proto.String(c.az),
			Payload:         proto.String(""), // @todo
			Duration:        proto.Int64(int64(d)),
			PersistentTrace: proto.Bool(req.TraceShouldPersist()),
		}
		if err != nil {
			e.ErrorCode = proto.String(err.Code())
			e.ErrorDescription = proto.String(err.Description())
		}
		trace.Send(e)
	}
}
Example #25
0
// callDiscoveryService sends off a request to register or unregister to the discovery service
func (self *discovery) callDiscoveryService(action string, successState bool) error {
	log.Infof("[Server] Attempting to %s with the discovery service...", action)

	azName, _ := util.GetAwsAZName()
	regSize := reg.size()
	machineClass := os.Getenv("H2O_MACHINE_CLASS")

	endpoints := make([]*register.MultiRequest_Endpoint, regSize)
	i := 0
	for _, endpoint := range reg.iterate() {
		endpoints[i] = &register.MultiRequest_Endpoint{
			Name:      proto.String(endpoint.Name),
			Mean:      proto.Int32(endpoint.Mean),
			Upper95:   proto.Int32(endpoint.Upper95),
			Subscribe: proto.String(endpoint.Subscribe),
		}

		i++
	}

	service := &dscShared.Service{
		Name:        proto.String(Name),
		Description: proto.String(Description),
		Version:     proto.Uint64(Version),
		Source:      proto.String(Source),
		OwnerEmail:  proto.String(OwnerEmail),
		OwnerMobile: proto.String(OwnerMobile),
		OwnerTeam:   proto.String(OwnerTeam),
	}

	request, err := ScopedRequest(
		"com.hailocab.kernel.discovery",
		action,
		&register.MultiRequest{
			InstanceId:   proto.String(InstanceID),
			Hostname:     proto.String(self.hostname),
			MachineClass: proto.String(machineClass),
			AzName:       proto.String(azName),
			Service:      service,
			Endpoints:    endpoints,
		},
	)

	if err != nil {
		log.Warnf("[Server] Failed to build request when %sing services", action)
		return err
	}

	// explicitly define timeout, since we're happy to wait
	clientOptions := client.Options{"retries": 0, "timeout": 5 * time.Second}

	rsp := &register.Response{}
	if err := client.Req(request, rsp, clientOptions); err != nil {
		log.Warnf("[Server] Failed to %s services: %v", action, err)
		return err
	}

	// ok -- all done!
	self.connected = successState
	log.Infof("[Server] Successfully %sed with the hive mind!", action)

	return nil
}
Example #26
0
// loadedConfigHandler handles inbound requests to `loadedconfig` endpoint
func loadedConfigHandler(req *Request) (proto.Message, errors.Error) {
	configJson := string(config.Raw())
	return &loadedconfigproto.Response{
		Config: proto.String(configJson),
	}, nil
}
Example #27
0
// TestAuthHappyCaseValid tests when things work, and when the credentials are valid
func (suite *sessionRecoverySuite) TestAuthHappyCaseValid() {
	t := suite.T()

	scope := New().(*realScope)
	scope.userCache = newTestCache()

	mock := multiclient.NewMock()
	stub := &multiclient.Stub{
		Service:  loginService,
		Endpoint: authEndpoint,
		Response: &authproto.Response{
			SessId: proto.String(testSessId),
			Token:  proto.String(testToken),
		},
	}
	sessLookupStub := &multiclient.Stub{
		Service:  loginService,
		Endpoint: readSessionEndpoint,
		Response: &sessreadproto.Response{
			SessId: proto.String(testSessId),
			Token:  proto.String(testToken),
		},
	}
	mock.Stub(stub).Stub(sessLookupStub)
	multiclient.SetCaller(mock.Caller())

	testMech, testDeviceType := "h2", "cli"
	testUsername, testPassword := "******", "Securez1"
	testCreds := map[string]string{
		"username": testUsername,
		"password": testPassword,
	}

	err := scope.Auth(testMech, testDeviceType, testCreds)
	if err != nil {
		t.Errorf("Unexpected auth error: %v", err)
	}
	if !scope.IsAuth() {
		t.Error("Expecting scope to be IsAuth==true after auth")
	}
	if !scope.HasTriedAuth() {
		t.Error("Expecting scope to have HasTriedAuth()==true after auth")
	}

	// verify we made correct request(s)
	if stub.CountCalls() != 1 {
		t.Fatalf("Expecting 1 call to auth; got %v", stub.CountCalls())
	}
	req := &authproto.Request{}
	err = stub.Request(0).Unmarshal(req)
	if err != nil {
		t.Fatalf("Unexpected error unmarshaling our request: %v", err)
	}
	if req.GetMech() != testMech {
		t.Errorf("Request did not contain our expected mech '%s', got '%s'", testMech, req.GetMech())
	}
	if req.GetDeviceType() != testDeviceType {
		t.Errorf("Request did not contain our expected device type '%s', got '%s'", testDeviceType, req.GetDeviceType())
	}
	if req.GetUsername() != testUsername {
		t.Errorf("Request did not contain our expected username '%s', got '%s'", testUsername, req.GetUsername())
	}
	if req.GetPassword() != testPassword {
		t.Errorf("Request did not contain our expected password '%s', got '%s'", testPassword, req.GetPassword())
	}

	// verify user returned
	u := scope.AuthUser()
	if u == nil {
		t.Fatal("IsAuthed scope returned nil user")
	}
	if u.Id != "dave" {
		t.Errorf("Expecting user ID 'dave'; got '%s'", u.Id)
	}

	// clean out scope
	scope.Clean()
	if scope.IsAuth() {
		t.Error("Expecting scope to be IsAuth==false after Clean()")
	}
	if scope.HasTriedAuth() {
		t.Error("Expecting scope to have HasTriedAuth()==false after Clean()")
	}
	if u := scope.AuthUser(); u != nil {
		t.Error("Expecting AuthUser()==nil after Clean()")
	}

	// auth AGAIN -- we should be calling login service _again_
	err = scope.Auth(testMech, testDeviceType, testCreds)
	if err != nil {
		t.Errorf("Unexpected recover error: %v", err)
	}
	if !scope.IsAuth() {
		t.Error("Expecting scope to be IsAuth=true after recovery")
	}

	// verify we haven't made _any more_ requests
	if stub.CountCalls() != 2 {
		t.Fatalf("Expecting 2 call to auth (because it should be called each time); got %v", stub.CountCalls())
	}

	// session should be pushed into cache
	err = scope.RecoverSession(testSessId)
	if err != nil {
		t.Errorf("Unexpected recover error: %v", err)
	}
	if sessLookupStub.CountCalls() != 0 {
		t.Fatalf("Expecting 0 call to readsession (because it should be cached); got %v", stub.CountCalls())
	}
}
Example #28
0
// TestSignOutHappy tests signout when it works
func (suite *sessionRecoverySuite) TestSignOutHappy() {
	t := suite.T()

	scope := New().(*realScope)
	scope.userCache = newTestCache()

	mock := multiclient.NewMock()
	stub := &multiclient.Stub{
		Service:  loginService,
		Endpoint: deleteSessionEndpoint,
		Response: &sessdelproto.Response{},
	}
	readSessStub := &multiclient.Stub{
		Service:  loginService,
		Endpoint: readSessionEndpoint,
		Response: &sessreadproto.Response{
			SessId: proto.String(testSessId),
			Token:  proto.String(testToken),
		},
	}
	mock.Stub(stub).Stub(readSessStub)
	multiclient.SetCaller(mock.Caller())

	// need to recover first
	err := scope.RecoverSession(testSessId)
	if err != nil || !scope.IsAuth() {
		t.Errorf("Unexpected recover error, or not Authed as expected: %v", err)
	}

	// now signout
	err = scope.SignOut(scope.AuthUser())
	if err != nil {
		t.Errorf("Unexpected signout error: %v", err)
	}

	// verify we made correct request(s)
	if stub.CountCalls() != 1 {
		t.Fatalf("Expecting 1 call to deletesession; got %v", stub.CountCalls())
	}
	req := &sessdelproto.Request{}
	err = stub.Request(0).Unmarshal(req)
	if err != nil {
		t.Fatalf("Unexpected error unmarshaling our request: %v", err)
	}
	if req.GetSessId() != testSessId {
		t.Errorf("Request did not contain our expected sessId '%s', got '%s'", testSessId, req.GetSessId())
	}
	if scope.IsAuth() {
		t.Error("Expecting scope to be IsAuth==false after SignOut()")
	}
	if scope.HasTriedAuth() {
		t.Error("Expecting scope to have HasTriedAuth()==false after SignOut()")
	}
	if u := scope.AuthUser(); u != nil {
		t.Error("Expecting AuthUser()==nil after SignOut()")
	}

	// make sure we have purged cache
	err = scope.RecoverSession(testSessId)
	if err != nil {
		t.Errorf("Expecting error when recovering session again")
	}
	if readSessStub.CountCalls() != 2 {
		t.Error("Expecting 2 calls to readsession - sincee we should have purged the cache after SignOut()")
	}
}
Example #29
0
// traceOut traces a request outbound from a service handler
func traceOut(req *Request, msg proto.Message, err perrors.Error, d time.Duration) {
	if req.shouldTrace() {
		e := &traceproto.Event{
			Timestamp:         proto.Int64(time.Now().UnixNano()),
			TraceId:           proto.String(req.TraceID()),
			Type:              traceproto.Event_OUT.Enum(),
			MessageId:         proto.String(req.MessageID()),
			ParentMessageId:   proto.String(req.ParentMessageID()),
			From:              proto.String(req.From()),
			To:                proto.String(fmt.Sprintf("%v.%v", req.Service(), req.Endpoint())),
			Hostname:          proto.String(hostname),
			Az:                proto.String(az),
			Payload:           proto.String(""), // @todo
			HandlerInstanceId: proto.String(InstanceID),
			Duration:          proto.Int64(int64(d)),
			PersistentTrace:   proto.Bool(req.TraceShouldPersist()),
		}
		if err != nil {
			e.ErrorCode = proto.String(err.Code())
			e.ErrorDescription = proto.String(err.Description())
		}
		trace.Send(e)
	}
}