RFC 619: (M1) Generate uploads service skeleton (#33613)

This commit is contained in:
Eric Fritz 2022-04-21 19:03:35 -05:00 committed by GitHub
parent 1b80f459a6
commit 254aea69ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 959 additions and 0 deletions

View File

@ -0,0 +1,28 @@
package codeintel
import (
"context"
"github.com/sourcegraph/sourcegraph/cmd/worker/job"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/background/commitgraph"
"github.com/sourcegraph/sourcegraph/internal/env"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
type commitGraphUpdaterJob struct{}
func NewCommitGraphUpdaterJob() job.Job {
return &commitGraphUpdaterJob{}
}
func (j *commitGraphUpdaterJob) Config() []env.Config {
return []env.Config{
commitgraph.ConfigInst,
}
}
func (j *commitGraphUpdaterJob) Routines(ctx context.Context) ([]goroutine.BackgroundRoutine, error) {
return []goroutine.BackgroundRoutine{
commitgraph.NewUpdater(),
}, nil
}

View File

@ -0,0 +1,28 @@
package codeintel
import (
"context"
"github.com/sourcegraph/sourcegraph/cmd/worker/job"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/background/expiration"
"github.com/sourcegraph/sourcegraph/internal/env"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
type uploadExpirerJob struct{}
func NewUploadExpirerJob() job.Job {
return &uploadExpirerJob{}
}
func (j *uploadExpirerJob) Config() []env.Config {
return []env.Config{
expiration.ConfigInst,
}
}
func (j *uploadExpirerJob) Routines(ctx context.Context) ([]goroutine.BackgroundRoutine, error) {
return []goroutine.BackgroundRoutine{
expiration.NewExpirer(),
}, nil
}

View File

@ -0,0 +1,28 @@
package codeintel
import (
"context"
"github.com/sourcegraph/sourcegraph/cmd/worker/job"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/background/cleanup"
"github.com/sourcegraph/sourcegraph/internal/env"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
type uploadJanitorJob struct{}
func NewUploadJanitorJob() job.Job {
return &uploadJanitorJob{}
}
func (j *uploadJanitorJob) Config() []env.Config {
return []env.Config{
cleanup.ConfigInst,
}
}
func (j *uploadJanitorJob) Routines(ctx context.Context) ([]goroutine.BackgroundRoutine, error) {
return []goroutine.BackgroundRoutine{
cleanup.NewJanitor(),
}, nil
}

View File

@ -42,6 +42,9 @@ func Start(additionalJobs map[string]job.Job, registerEnterpriseMigrations func(
builtins := map[string]job.Job{
"webhook-log-janitor": webhooks.NewJanitor(),
"out-of-band-migrations": migrations.NewMigrator(registerMigrations),
"codeintel-upload-janitor": codeintel.NewUploadJanitorJob(),
"codeintel-upload-expirer": codeintel.NewUploadExpirerJob(),
"codeintel-commitgraph-updater": codeintel.NewCommitGraphUpdaterJob(),
"codeintel-documents-indexer": codeintel.NewDocumentsIndexerJob(),
"codeintel-autoindexing-scheduler": codeintel.NewAutoindexingSchedulerJob(),
"codeintel-dependencies-indexer": codeintel.NewDependenciesIndexerJob(),

View File

@ -10,6 +10,18 @@ The following jobs are defined by the `worker` service.
This job runs [out of band migrations](migration.md#mout-of-band-migrations), which perform large data migrations in the background over time instead of synchronously during Sourcegraph instance updates.
#### `codeintel-upload-janitor`
This job will eventually (and partially) replace `codeintel-janitor`.
#### `codeintel-upload-expirer`
This job will eventually (and partially) replace `codeintel-janitor`
#### `codeintel-commitgraph-updater`
This job will eventually replace `codeintel-commitgraph`.
#### `codeintel-documents-indexer`
This job periodically indexes file contents at a syntactic level to build an index of search-based code intelligence.

View File

@ -17,6 +17,8 @@ import (
store "github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/stores/dbstore"
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/stores/lsifstore"
"github.com/sourcegraph/sourcegraph/enterprise/internal/codeintel/stores/lsifuploadstore"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads"
uploadshttp "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/transport/http"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
"github.com/sourcegraph/sourcegraph/internal/database"
@ -70,6 +72,15 @@ func NewServices(ctx context.Context, config *Config, siteConfig conftypes.Watch
// Initialize http endpoints
operations := httpapi.NewOperations(observationContext)
newUploadHandler := func(internal bool) http.Handler {
if false {
// Until this handler has been implemented, we retain the origial
// LSIF update handler.
//
// See https://github.com/sourcegraph/sourcegraph/issues/33375
return uploadshttp.GetHandler(uploads.GetService(db))
}
return httpapi.NewUploadHandler(
db,
&httpapi.DBStoreShim{Store: dbStore},

View File

@ -0,0 +1,19 @@
package cleanup
import (
"time"
"github.com/sourcegraph/sourcegraph/internal/env"
)
type config struct {
env.BaseConfig
Interval time.Duration
}
var ConfigInst = &config{}
func (c *config) Load() {
c.Interval = c.GetInterval("CODEINTEL_UPLOLAD_JANITOR_INTERVAL", "1s", "How frequently to run the updater janitor routine.")
}

View File

@ -0,0 +1,11 @@
package cleanup
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
func NewJanitor() goroutine.BackgroundRoutine {
return goroutine.NewPeriodicGoroutine(context.Background(), ConfigInst.Interval, &janitor{})
}

View File

@ -0,0 +1,20 @@
package cleanup
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
type janitor struct{}
var _ goroutine.Handler = &janitor{}
var _ goroutine.ErrorHandler = &janitor{}
func (r *janitor) Handle(ctx context.Context) error {
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
return nil
}
func (r *janitor) HandleError(err error) {
}

View File

@ -0,0 +1,19 @@
package commitgraph
import (
"time"
"github.com/sourcegraph/sourcegraph/internal/env"
)
type config struct {
env.BaseConfig
Interval time.Duration
}
var ConfigInst = &config{}
func (c *config) Load() {
c.Interval = c.GetInterval("CODEINTEL_UPLOAD_COMMITGRAPH_UPDATER_INTERVAL", "1s", "How frequently to run the upload commitgraph updater routine.")
}

View File

@ -0,0 +1,11 @@
package commitgraph
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
func NewUpdater() goroutine.BackgroundRoutine {
return goroutine.NewPeriodicGoroutine(context.Background(), ConfigInst.Interval, &updater{})
}

View File

@ -0,0 +1,20 @@
package commitgraph
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
type updater struct{}
var _ goroutine.Handler = &updater{}
var _ goroutine.ErrorHandler = &updater{}
func (r *updater) Handle(ctx context.Context) error {
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
return nil
}
func (r *updater) HandleError(err error) {
}

View File

@ -0,0 +1,19 @@
package expiration
import (
"time"
"github.com/sourcegraph/sourcegraph/internal/env"
)
type config struct {
env.BaseConfig
Interval time.Duration
}
var ConfigInst = &config{}
func (c *config) Load() {
c.Interval = c.GetInterval("CODEINTEL_UPLOAD_EXPIRER_INTERVAL", "1s", "How frequently to run the upload expirer routine.")
}

View File

@ -0,0 +1,20 @@
package expiration
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
type expirer struct{}
var _ goroutine.Handler = &expirer{}
var _ goroutine.ErrorHandler = &expirer{}
func (r *expirer) Handle(ctx context.Context) error {
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
return nil
}
func (r *expirer) HandleError(err error) {
}

View File

@ -0,0 +1,11 @@
package expiration
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/goroutine"
)
func NewExpirer() goroutine.BackgroundRoutine {
return goroutine.NewPeriodicGoroutine(context.Background(), ConfigInst.Interval, &expirer{})
}

View File

@ -0,0 +1,3 @@
package uploads
//go:generate ../../../dev/mockgen.sh github.com/sourcegraph/sourcegraph/internal/codeintel/uploads -i Store -o mock_iface_test.go

View File

@ -0,0 +1,11 @@
package uploads
import (
"context"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/internal/store"
)
type Store interface {
List(ctx context.Context, opts store.ListOpts) ([]Upload, error)
}

View File

@ -0,0 +1,46 @@
package uploads
import (
"sync"
"github.com/inconshreveable/log15"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/internal/store"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/observation"
"github.com/sourcegraph/sourcegraph/internal/trace"
)
var (
svc *Service
svcOnce sync.Once
)
// GetService creates or returns an already-initialized uplopads service. If the service is
// new, it will use the given database handle.
func GetService(db database.DB) *Service {
svcOnce.Do(func() {
observationContext := &observation.Context{
Logger: log15.Root(),
Tracer: &trace.Tracer{Tracer: opentracing.GlobalTracer()},
Registerer: prometheus.DefaultRegisterer,
}
svc = newService(
store.GetStore(db),
observationContext,
)
})
return svc
}
// TestService creates a fresh uplopads service with the given database handle.
func TestService(db database.DB) *Service {
return newService(
store.GetStore(db),
&observation.TestContext,
)
}

View File

@ -0,0 +1,36 @@
package store
import (
"sync"
"github.com/inconshreveable/log15"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/observation"
"github.com/sourcegraph/sourcegraph/internal/trace"
)
var (
store *Store
storeOnce sync.Once
)
func GetStore(db database.DB) *Store {
storeOnce.Do(func() {
observationContext := &observation.Context{
Logger: log15.Root(),
Tracer: &trace.Tracer{Tracer: opentracing.GlobalTracer()},
Registerer: prometheus.DefaultRegisterer,
}
store = newStore(db, observationContext)
})
return store
}
func TestStore(db database.DB) *Store {
return newStore(db, &observation.TestContext)
}

View File

@ -0,0 +1,33 @@
package store
import (
"fmt"
"github.com/sourcegraph/sourcegraph/internal/metrics"
"github.com/sourcegraph/sourcegraph/internal/observation"
)
type operations struct {
list *observation.Operation
}
func newOperations(observationContext *observation.Context) *operations {
metrics := metrics.NewREDMetrics(
observationContext.Registerer,
"codeintel_uploads_store",
metrics.WithLabels("op"),
metrics.WithCountHelp("Total number of method invocations."),
)
op := func(name string) *observation.Operation {
return observationContext.Operation(observation.Op{
Name: fmt.Sprintf("codeintel.uploads.store.%s", name),
MetricLabelValues: []string{name},
Metrics: metrics,
})
}
return &operations{
list: op("List"),
}
}

View File

@ -0,0 +1,29 @@
package store
import (
"database/sql"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/shared"
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
)
func scanUploads(rows *sql.Rows, queryErr error) (uploads []shared.Upload, err error) {
if queryErr != nil {
return nil, queryErr
}
defer func() { err = basestore.CloseRows(rows, err) }()
for rows.Next() {
var upload shared.Upload
if err = rows.Scan(
&upload.ID,
); err != nil {
return nil, err
}
uploads = append(uploads, upload)
}
return uploads, nil
}

View File

@ -0,0 +1,70 @@
package store
import (
"context"
"database/sql"
"github.com/keegancsmith/sqlf"
"github.com/opentracing/opentracing-go/log"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/shared"
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
"github.com/sourcegraph/sourcegraph/internal/database/dbutil"
"github.com/sourcegraph/sourcegraph/internal/observation"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
type Store struct {
*basestore.Store
operations *operations
}
func newStore(db dbutil.DB, observationContext *observation.Context) *Store {
return &Store{
Store: basestore.NewWithDB(db, sql.TxOptions{}),
operations: newOperations(observationContext),
}
}
func (s *Store) With(other basestore.ShareableStore) *Store {
return &Store{
Store: s.Store.With(other),
operations: s.operations,
}
}
func (s *Store) Transact(ctx context.Context) (*Store, error) {
txBase, err := s.Store.Transact(ctx)
if err != nil {
return nil, err
}
return &Store{
Store: txBase,
operations: s.operations,
}, nil
}
type ListOpts struct {
Limit int
}
func (s *Store) List(ctx context.Context, opts ListOpts) (uploads []shared.Upload, err error) {
ctx, endObservation := s.operations.list.With(ctx, &err, observation.Args{})
defer func() {
endObservation(1, observation.Args{LogFields: []log.Field{
log.Int("numUploads", len(uploads)),
}})
}()
// This is only a stub and will be replaced or significantly modified
// in https://github.com/sourcegraph/sourcegraph/issues/33375
_, _ = scanUploads(s.Query(ctx, sqlf.Sprintf(listQuery, opts.Limit)))
return nil, errors.Newf("unimplemented: uploads.store.List")
}
const listQuery = `
-- source: internal/codeintel/uploads/store/store.go:List
SELECT id FROM TODO
LIMIT %s
`

View File

@ -0,0 +1,161 @@
// Code generated by go-mockgen 1.1.5; DO NOT EDIT.
package uploads
import (
"context"
"sync"
store "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/internal/store"
shared "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/shared"
)
// MockStore is a mock implementation of the Store interface (from the
// package github.com/sourcegraph/sourcegraph/internal/codeintel/uploads)
// used for unit testing.
type MockStore struct {
// ListFunc is an instance of a mock function object controlling the
// behavior of the method List.
ListFunc *StoreListFunc
}
// NewMockStore creates a new mock of the Store interface. All methods
// return zero values for all results, unless overwritten.
func NewMockStore() *MockStore {
return &MockStore{
ListFunc: &StoreListFunc{
defaultHook: func(context.Context, store.ListOpts) ([]shared.Upload, error) {
return nil, nil
},
},
}
}
// NewStrictMockStore creates a new mock of the Store interface. All methods
// panic on invocation, unless overwritten.
func NewStrictMockStore() *MockStore {
return &MockStore{
ListFunc: &StoreListFunc{
defaultHook: func(context.Context, store.ListOpts) ([]shared.Upload, error) {
panic("unexpected invocation of MockStore.List")
},
},
}
}
// NewMockStoreFrom creates a new mock of the MockStore interface. All
// methods delegate to the given implementation, unless overwritten.
func NewMockStoreFrom(i Store) *MockStore {
return &MockStore{
ListFunc: &StoreListFunc{
defaultHook: i.List,
},
}
}
// StoreListFunc describes the behavior when the List method of the parent
// MockStore instance is invoked.
type StoreListFunc struct {
defaultHook func(context.Context, store.ListOpts) ([]shared.Upload, error)
hooks []func(context.Context, store.ListOpts) ([]shared.Upload, error)
history []StoreListFuncCall
mutex sync.Mutex
}
// List delegates to the next hook function in the queue and stores the
// parameter and result values of this invocation.
func (m *MockStore) List(v0 context.Context, v1 store.ListOpts) ([]shared.Upload, error) {
r0, r1 := m.ListFunc.nextHook()(v0, v1)
m.ListFunc.appendCall(StoreListFuncCall{v0, v1, r0, r1})
return r0, r1
}
// SetDefaultHook sets function that is called when the List method of the
// parent MockStore instance is invoked and the hook queue is empty.
func (f *StoreListFunc) SetDefaultHook(hook func(context.Context, store.ListOpts) ([]shared.Upload, error)) {
f.defaultHook = hook
}
// PushHook adds a function to the end of hook queue. Each invocation of the
// List method of the parent MockStore instance invokes the hook at the
// front of the queue and discards it. After the queue is empty, the default
// hook function is invoked for any future action.
func (f *StoreListFunc) PushHook(hook func(context.Context, store.ListOpts) ([]shared.Upload, error)) {
f.mutex.Lock()
f.hooks = append(f.hooks, hook)
f.mutex.Unlock()
}
// SetDefaultReturn calls SetDefaultHook with a function that returns the
// given values.
func (f *StoreListFunc) SetDefaultReturn(r0 []shared.Upload, r1 error) {
f.SetDefaultHook(func(context.Context, store.ListOpts) ([]shared.Upload, error) {
return r0, r1
})
}
// PushReturn calls PushHook with a function that returns the given values.
func (f *StoreListFunc) PushReturn(r0 []shared.Upload, r1 error) {
f.PushHook(func(context.Context, store.ListOpts) ([]shared.Upload, error) {
return r0, r1
})
}
func (f *StoreListFunc) nextHook() func(context.Context, store.ListOpts) ([]shared.Upload, error) {
f.mutex.Lock()
defer f.mutex.Unlock()
if len(f.hooks) == 0 {
return f.defaultHook
}
hook := f.hooks[0]
f.hooks = f.hooks[1:]
return hook
}
func (f *StoreListFunc) appendCall(r0 StoreListFuncCall) {
f.mutex.Lock()
f.history = append(f.history, r0)
f.mutex.Unlock()
}
// History returns a sequence of StoreListFuncCall objects describing the
// invocations of this function.
func (f *StoreListFunc) History() []StoreListFuncCall {
f.mutex.Lock()
history := make([]StoreListFuncCall, len(f.history))
copy(history, f.history)
f.mutex.Unlock()
return history
}
// StoreListFuncCall is an object that describes an invocation of method
// List on an instance of MockStore.
type StoreListFuncCall struct {
// Arg0 is the value of the 1st argument passed to this method
// invocation.
Arg0 context.Context
// Arg1 is the value of the 2nd argument passed to this method
// invocation.
Arg1 store.ListOpts
// Result0 is the value of the 1st result returned from this method
// invocation.
Result0 []shared.Upload
// Result1 is the value of the 2nd result returned from this method
// invocation.
Result1 error
}
// Args returns an interface slice containing the arguments of this
// invocation.
func (c StoreListFuncCall) Args() []interface{} {
return []interface{}{c.Arg0, c.Arg1}
}
// Results returns an interface slice containing the results of this
// invocation.
func (c StoreListFuncCall) Results() []interface{} {
return []interface{}{c.Result0, c.Result1}
}

View File

@ -0,0 +1,45 @@
package uploads
import (
"fmt"
"github.com/sourcegraph/sourcegraph/internal/metrics"
"github.com/sourcegraph/sourcegraph/internal/observation"
)
type operations struct {
list *observation.Operation
get *observation.Operation
getBatch *observation.Operation
enqueue *observation.Operation
delete *observation.Operation
commitsVisibleTo *observation.Operation
uploadsVisibleTo *observation.Operation
}
func newOperations(observationContext *observation.Context) *operations {
metrics := metrics.NewREDMetrics(
observationContext.Registerer,
"codeintel_uploads",
metrics.WithLabels("op"),
metrics.WithCountHelp("Total number of method invocations."),
)
op := func(name string) *observation.Operation {
return observationContext.Operation(observation.Op{
Name: fmt.Sprintf("codeintel.uploads.%s", name),
MetricLabelValues: []string{name},
Metrics: metrics,
})
}
return &operations{
list: op("List"),
get: op("Get"),
getBatch: op("GetBatch"),
enqueue: op("Enqueue"),
delete: op("Delete"),
commitsVisibleTo: op("CommitsVisibleTo"),
uploadsVisibleTo: op("UploadsVisibleTo"),
}
}

View File

@ -0,0 +1,93 @@
package uploads
import (
"context"
"io"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/internal/store"
"github.com/sourcegraph/sourcegraph/internal/codeintel/uploads/shared"
"github.com/sourcegraph/sourcegraph/internal/observation"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
type Service struct {
uploadsStore Store
operations *operations
}
func newService(uploadsStore Store, observationContext *observation.Context) *Service {
return &Service{
uploadsStore: uploadsStore,
operations: newOperations(observationContext),
}
}
type Upload = shared.Upload
type ListOpts struct {
Limit int
}
func (s *Service) List(ctx context.Context, opts ListOpts) (uploads []Upload, err error) {
ctx, endObservation := s.operations.list.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
return s.uploadsStore.List(ctx, store.ListOpts(opts))
}
func (s *Service) Get(ctx context.Context, id int) (upload Upload, ok bool, err error) {
ctx, endObservation := s.operations.get.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_ = ctx
return Upload{}, false, errors.Newf("unimplemented: uploads.Get")
}
func (s *Service) GetBatch(ctx context.Context, ids ...int) (uploads []Upload, err error) {
ctx, endObservation := s.operations.getBatch.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_ = ctx
return nil, errors.Newf("unimplemented: uploads.GetBatch")
}
type UploadState struct {
}
func (s *Service) Enqueue(ctx context.Context, state UploadState, reader io.Reader) (err error) {
ctx, endObservation := s.operations.enqueue.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_ = ctx
return errors.Newf("unimplemented: uploads.Enqueue")
}
func (s *Service) Delete(ctx context.Context, id int) (err error) {
ctx, endObservation := s.operations.delete.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_ = ctx
return errors.Newf("unimplemented: uploads.Delete")
}
func (s *Service) CommitsVisibleToUpload(ctx context.Context, id int) (commits []string, err error) {
ctx, endObservation := s.operations.commitsVisibleTo.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_ = ctx
return nil, errors.Newf("unimplemented: uploads.CommitsVisibleToUpload")
}
func (s *Service) UploadsVisibleToCommit(ctx context.Context, commit string) (uploads []Upload, err error) {
ctx, endObservation := s.operations.uploadsVisibleTo.With(ctx, &err, observation.Args{})
defer endObservation(1, observation.Args{})
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_ = ctx
return nil, errors.Newf("unimplemented: uploads.UploadsVisibleToCommit")
}

View File

@ -0,0 +1,5 @@
package shared
type Upload struct {
ID int
}

View File

@ -0,0 +1,32 @@
package http
import (
"sync"
"github.com/inconshreveable/log15"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus"
uploads "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads"
"github.com/sourcegraph/sourcegraph/internal/observation"
"github.com/sourcegraph/sourcegraph/internal/trace"
)
var (
handler *Handler
handlerOnce sync.Once
)
func GetHandler(svc *uploads.Service) *Handler {
handlerOnce.Do(func() {
observationContext := &observation.Context{
Logger: log15.Root(),
Tracer: &trace.Tracer{Tracer: opentracing.GlobalTracer()},
Registerer: prometheus.DefaultRegisterer,
}
handler = newHandler(svc, observationContext)
})
return handler
}

View File

@ -0,0 +1,33 @@
package http
import (
"fmt"
"github.com/sourcegraph/sourcegraph/internal/metrics"
"github.com/sourcegraph/sourcegraph/internal/observation"
)
type operations struct {
todo *observation.Operation
}
func newOperations(observationContext *observation.Context) *operations {
metrics := metrics.NewREDMetrics(
observationContext.Registerer,
"codeintel_uploads_transport_http",
metrics.WithLabels("op"),
metrics.WithCountHelp("Total number of method invocations."),
)
op := func(name string) *observation.Operation {
return observationContext.Operation(observation.Op{
Name: fmt.Sprintf("codeintel.uploads.transport.http.%s", name),
MetricLabelValues: []string{name},
Metrics: metrics,
})
}
return &operations{
todo: op("Todo"),
}
}

View File

@ -0,0 +1,69 @@
package http
import (
"bytes"
"encoding/json"
"io"
"net/http"
"github.com/opentracing/opentracing-go/log"
uploads "github.com/sourcegraph/sourcegraph/internal/codeintel/uploads"
"github.com/sourcegraph/sourcegraph/internal/observation"
)
type Handler struct {
svc *uploads.Service
operations *operations
}
var _ http.Handler = &Handler{}
func newHandler(svc *uploads.Service, observationContext *observation.Context) *Handler {
return &Handler{
svc: svc,
operations: newOperations(observationContext),
}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.serveJSON(w, r)
}
func (h *Handler) serveJSON(w http.ResponseWriter, r *http.Request) {
payload, statusCode, err := h.handleRequest(r)
if err != nil {
handleErr(w, err, "request failed", statusCode)
return
}
if payload == nil {
w.WriteHeader(http.StatusNoContent)
return
}
data, err := json.Marshal(payload)
if err != nil {
handleErr(w, err, "failed to serialize result", http.StatusInternalServerError)
return
}
w.WriteHeader(statusCode)
if _, err := io.Copy(w, bytes.NewReader(data)); err != nil {
handleErr(nil, err, "failed to write payload to client", http.StatusInternalServerError)
return
}
}
func (h *Handler) handleRequest(r *http.Request) (payload interface{}, statusCode int, err error) {
ctx, trace, endObservation := h.operations.todo.WithAndLogger(r.Context(), &err, observation.Args{})
defer func() {
endObservation(1, observation.Args{LogFields: []log.Field{
log.Int("statusCode", statusCode),
}})
}()
// To be implemented in https://github.com/sourcegraph/sourcegraph/issues/33375
_, _ = ctx, trace
return nil, 0, nil
}

View File

@ -0,0 +1,33 @@
package http
import (
"fmt"
"net/http"
"github.com/inconshreveable/log15"
)
// func hasQuery(r *http.Request, name string) bool {
// return r.URL.Query().Get(name) != ""
// }
// func getQuery(r *http.Request, name string) string {
// return r.URL.Query().Get(name)
// }
// func getQueryInt(r *http.Request, name string) int {
// value, _ := strconv.Atoi(r.URL.Query().Get(name))
// return value
// }
const logPrefix = "codeintel.uploads.transport.http"
func handleErr(w http.ResponseWriter, err error, logMessage string, statusCode int) {
if statusCode >= 500 {
log15.Error(fmt.Sprintf("%s: %s", logPrefix, logMessage), "error", err)
}
if w != nil {
http.Error(w, fmt.Sprintf("%s: %s: %s", logPrefix, logMessage, err), statusCode)
}
}