From 8fb2c9c33c6bd7b1a58685668eedcbb57607f8c5 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 17 Dec 2019 16:47:45 +0000 Subject: [PATCH 01/16] Replace deprecated prometheus.InstrumentHandler and unsafe time.Ticker --- common/httpapi.go | 12 +++++- common/transactions/transactions.go | 2 +- go.mod | 26 ++++++----- go.sum | 67 +++++++++++++++++++++++++++++ mediaapi/routing/routing.go | 51 +++++++++++++--------- 5 files changed, 124 insertions(+), 34 deletions(-) diff --git a/common/httpapi.go b/common/httpapi.go index bf634ff4a..59b303b6d 100644 --- a/common/httpapi.go +++ b/common/httpapi.go @@ -11,6 +11,7 @@ import ( opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -59,7 +60,16 @@ func MakeHTMLAPI(metricsName string, f func(http.ResponseWriter, *http.Request) } } - return prometheus.InstrumentHandler(metricsName, http.HandlerFunc(withSpan)) + return promhttp.InstrumentHandlerCounter( + promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: metricsName, + Help: "Total number of http requests for HTML resources", + }, + []string{"code"}, + ), + http.HandlerFunc(withSpan), + ) } // MakeInternalAPI turns a util.JSONRequestHandler function into an http.Handler. diff --git a/common/transactions/transactions.go b/common/transactions/transactions.go index 80b403a98..d2eb0f27f 100644 --- a/common/transactions/transactions.go +++ b/common/transactions/transactions.go @@ -85,7 +85,7 @@ func (t *Cache) AddTransaction(accessToken, txnID string, res *util.JSONResponse // It guarantees that an entry will be present in cache for at least cleanupPeriod & at most 2 * cleanupPeriod. // This cycles the txnMaps forward, i.e. back map is assigned the front and front is assigned an empty map. func cacheCleanService(t *Cache) { - ticker := time.Tick(t.cleanupPeriod) + ticker := time.NewTicker(t.cleanupPeriod).C for range ticker { t.Lock() t.txnsMaps[1] = t.txnsMaps[0] diff --git a/go.mod b/go.mod index d51f0a33e..d2cb80bb8 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/matrix-org/dendrite require ( github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3 github.com/alecthomas/gometalinter v2.0.2+incompatible - github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7 - github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a + github.com/beorn7/perks v1.0.1 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b github.com/davecgh/go-spew v1.1.1 github.com/eapache/go-resiliency v0.0.0-20160104191539-b86b1ec0dd42 github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934 github.com/eapache/queue v1.1.0 - github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b + github.com/golang/protobuf v1.3.2 github.com/golang/snappy v0.0.0-20170119014723-7db9049039a0 github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 github.com/gorilla/context v1.1.1 @@ -28,18 +28,20 @@ require ( github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0 github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 github.com/matttproud/golang_protobuf_extensions v1.0.1 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/nicksnyder/go-i18n v1.8.1 github.com/opentracing/opentracing-go v0.0.0-20170806192116-8ebe5d4e236e github.com/pelletier/go-toml v0.0.0-20170904195809-1d6b12b7cb29 github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897 - github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 + github.com/pkg/errors v0.8.1 github.com/pmezard/go-difflib v1.0.0 - github.com/prometheus/client_golang v0.0.0-20180519192340-c51dc758d4bb - github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335 - github.com/prometheus/common v0.0.0-20170108231212-dd2f054febf4 - github.com/prometheus/procfs v0.0.0-20170128160123-1878d9fbb537 + github.com/prometheus/client_golang v1.2.1 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 + github.com/prometheus/common v0.7.0 + github.com/prometheus/procfs v0.0.5 github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/objx v0.2.0 // indirect @@ -54,9 +56,9 @@ require ( go.uber.org/atomic v1.3.0 go.uber.org/multierr v0.0.0-20170829224307-fb7d312c2c04 go.uber.org/zap v1.7.1 - golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 - golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 - golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 + golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 + golang.org/x/sys v0.0.0-20191010194322-b09406accb47 gopkg.in/Shopify/sarama.v1 v1.11.0 gopkg.in/airbrake/gobrake.v2 v2.0.9 gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20170727041045-23bcc3c4eae3 @@ -65,3 +67,5 @@ require ( gopkg.in/macaroon.v2 v2.1.0 gopkg.in/yaml.v2 v2.2.2 ) + +go 1.13 diff --git a/go.sum b/go.sum index 56781c9a6..962d0f5bb 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,19 @@ github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3 h1:j6BAEHYn1kUyW2j7kY0mOJ/R8A0qWwXpvUAEHGemm/g= github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/alecthomas/gometalinter v2.0.2+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a h1:BtpsbiV638WQZwhA98cEZw2BsbnQJrbd0BI7tsy0W1c= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= @@ -19,24 +28,41 @@ github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934/go.mod h1 github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b h1:fE/yi9pibxGEc0gSJuEShcsBXE2d5FW3OudsjE9tKzQ= github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20170119014723-7db9049039a0 h1:FMElzTwkd/2jQ2QzLEzt97JRgvFhYhnYiaQSwZ7tuyU= github.com/golang/snappy v0.0.0-20170119014723-7db9049039a0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.3.0 h1:HwSEKGN6U5T2aAQTfu5pW8fiwjSp3IgwdRbkICydk/c= github.com/gorilla/mux v1.3.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/jaegertracing/jaeger-client-go v0.0.0-20170921145708-3ad49a1d839b/go.mod h1:HWG7INeOG1ZE17I/S8eeb+svquXmBS/hf1Obi6hJUyQ= github.com/jaegertracing/jaeger-lib v0.0.0-20170920222118-21a3da6d66fe/go.mod h1:VqeqQrZmZr9G4WdLw4ei9tAHU54iJRkfoFHvTTQn4jQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -68,7 +94,13 @@ github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 h1:W7l5CP4V7wPyPb4 github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5/go.mod h1:lePuOiXLNDott7NZfnQvJk0lAZ5HgvIuWGhel6J+RLA= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0= github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= @@ -82,19 +114,38 @@ github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897 h1:jp3jc/PyyTrTKjJJ github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 h1:chPfVn+gpAM5CTpTyVU9j8J+xgRGwmoDlNDLjKnJiYo= github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.0.0-20180519192340-c51dc758d4bb h1:ghXIh3jvLRo/h3y2O7wBgcmH1th5NXQ4XHwK5CgI//I= github.com/prometheus/client_golang v0.0.0-20180519192340-c51dc758d4bb/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335 h1:0E/5GnGmzoDCtmzTycjGDWW33H0UBmAhR0h+FC8hWLs= github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20170108231212-dd2f054febf4 h1:bZG2YNnM/Fjd3kiqaVt13Apkhzz24wBKlxQ+URiggXk= github.com/prometheus/common v0.0.0-20170108231212-dd2f054febf4/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20170128160123-1878d9fbb537 h1:Lq69k27tHOmljEqDOHDy3b6kQyEie2yWeAiF0OKFMJ8= github.com/prometheus/procfs v0.0.0-20170128160123-1878d9fbb537/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5 h1:gwcdIpH6NU2iF8CmcqD+CP6+1CkRBOhHaPR+iu6raBY= github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/sirupsen/logrus v0.0.0-20170822132746-89742aefa4b2 h1:+8J/sCAVv2Y9Ct1BKszDFJEVWv6Aynr2O4FYGUg6+Mc= github.com/sirupsen/logrus v0.0.0-20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -104,6 +155,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tidwall/gjson v1.0.2 h1:5BsM7kyEAHAUGEGDkEKO9Mdyiuw6QQ6TSDdarP0Nnmk= github.com/tidwall/gjson v1.0.2/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= @@ -132,28 +184,43 @@ golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd h1:VtIkGDhk0ph3t+THbvXHfM golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20170927055102-0a9397675ba3 h1:tTDpczhDVjW6WN3DinzKcw5juwkDTVn22I7MNlfxSXM= golang.org/x/net v0.0.0-20170927055102-0a9397675ba3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU= golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20171012164349-43eea11bc926 h1:PY6OU86NqbyZiOzaPnDw6oOjAGtYQqIua16z6y9QkwE= golang.org/x/sys v0.0.0-20171012164349-43eea11bc926/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/Shopify/sarama.v1 v1.11.0 h1:/3kaCyeYaPbr59IBjeqhIcUOB1vXlIVqXAYa5g5C5F0= gopkg.in/Shopify/sarama.v1 v1.11.0/go.mod h1:AxnvoaevB2nBjNK17cG61A3LleFcWFwVBHBt+cot4Oc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20170727041045-23bcc3c4eae3/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/h2non/bimg.v1 v1.0.18/go.mod h1:PgsZL7dLwUbsGm1NYps320GxGgvQNTnecMCZqxV11So= +gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM= gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I= gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o= gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab h1:yZ6iByf7GKeJ3gsd1Dr/xaj1DyJ//wxKX1Cdh8LhoAw= gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 5bcce1772..232a57fd1 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -29,6 +29,8 @@ import ( "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" ) const pathPrefixR0 = "/_matrix/media/r0" @@ -83,26 +85,33 @@ func makeDownloadAPI( activeRemoteRequests *types.ActiveRemoteRequests, activeThumbnailGeneration *types.ActiveThumbnailGeneration, ) http.HandlerFunc { - return prometheus.InstrumentHandler(name, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - req = util.RequestWithLogging(req) + return promhttp.InstrumentHandlerCounter( + promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: name, + Help: "Total number of media_api requests for either thumbnails or full downloads", + }, + []string{"code"}, + ), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + req = util.RequestWithLogging(req) - // Set common headers returned regardless of the outcome of the request - util.SetCORSHeaders(w) - // Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors - w.Header().Set("Content-Type", "application/json") - - vars, _ := common.URLDecodeMapValues(mux.Vars(req)) - Download( - w, - req, - gomatrixserverlib.ServerName(vars["serverName"]), - types.MediaID(vars["mediaId"]), - cfg, - db, - client, - activeRemoteRequests, - activeThumbnailGeneration, - name == "thumbnail", - ) - })) + // Set common headers returned regardless of the outcome of the request + util.SetCORSHeaders(w) + // Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors + w.Header().Set("Content-Type", "application/json") + vars := mux.Vars(req) + Download( + w, + req, + gomatrixserverlib.ServerName(vars["serverName"]), + types.MediaID(vars["mediaId"]), + cfg, + db, + client, + activeRemoteRequests, + activeThumbnailGeneration, + name == "thumbnail", + ) + }, + )) } From de5d463dd2b5f8955b714d440d02777269255ed0 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 17 Dec 2019 16:54:21 +0000 Subject: [PATCH 02/16] goimports --- go.mod | 2 -- mediaapi/routing/routing.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d2cb80bb8..723bf99d3 100644 --- a/go.mod +++ b/go.mod @@ -28,8 +28,6 @@ require ( github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0 github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 github.com/matttproud/golang_protobuf_extensions v1.0.1 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/nicksnyder/go-i18n v1.8.1 github.com/opentracing/opentracing-go v0.0.0-20170806192116-8ebe5d4e236e diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 232a57fd1..bc919bacd 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -113,5 +113,5 @@ func makeDownloadAPI( name == "thumbnail", ) }, - )) + )) } From 2baf5baa75e1a3e1834bd2dde2fa3601ab834f0d Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 17 Dec 2019 17:05:39 +0000 Subject: [PATCH 03/16] re-add temporarily missing deps? --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 723bf99d3..d2cb80bb8 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,8 @@ require ( github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0 github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 github.com/matttproud/golang_protobuf_extensions v1.0.1 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/nicksnyder/go-i18n v1.8.1 github.com/opentracing/opentracing-go v0.0.0-20170806192116-8ebe5d4e236e From 9b5d6c9745bf8cb1c5a2057ee6218640c82e5250 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 18 Dec 2019 15:10:53 +0000 Subject: [PATCH 04/16] Refactor InstrumentHandlerCounter definition --- mediaapi/routing/routing.go | 56 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index bc919bacd..3a8cb9ca6 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -85,33 +85,33 @@ func makeDownloadAPI( activeRemoteRequests *types.ActiveRemoteRequests, activeThumbnailGeneration *types.ActiveThumbnailGeneration, ) http.HandlerFunc { - return promhttp.InstrumentHandlerCounter( - promauto.NewCounterVec( - prometheus.CounterOpts{ - Name: name, - Help: "Total number of media_api requests for either thumbnails or full downloads", - }, - []string{"code"}, - ), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - req = util.RequestWithLogging(req) - - // Set common headers returned regardless of the outcome of the request - util.SetCORSHeaders(w) - // Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors - w.Header().Set("Content-Type", "application/json") - vars := mux.Vars(req) - Download( - w, - req, - gomatrixserverlib.ServerName(vars["serverName"]), - types.MediaID(vars["mediaId"]), - cfg, - db, - client, - activeRemoteRequests, - activeThumbnailGeneration, - name == "thumbnail", - ) + counterVec := promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: name, + Help: "Total number of media_api requests for either thumbnails or full downloads", }, - )) + []string{"code"}, + ) + httpHandler := func(w http.ResponseWriter, req *http.Request) { + req = util.RequestWithLogging(req) + + // Set common headers returned regardless of the outcome of the request + util.SetCORSHeaders(w) + // Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors + w.Header().Set("Content-Type", "application/json") + vars := mux.Vars(req) + Download( + w, + req, + gomatrixserverlib.ServerName(vars["serverName"]), + types.MediaID(vars["mediaId"]), + cfg, + db, + client, + activeRemoteRequests, + activeThumbnailGeneration, + name == "thumbnail", + ) + } + return promhttp.InstrumentHandlerCounter(counterVec, http.HandlerFunc(httpHandler)) } From f392ce8ed744654c25f9ea58ed609db8edb25b68 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 18 Dec 2019 15:29:27 +0000 Subject: [PATCH 05/16] URL decode args --- mediaapi/routing/routing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 3a8cb9ca6..0f2266640 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -99,7 +99,7 @@ func makeDownloadAPI( util.SetCORSHeaders(w) // Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors w.Header().Set("Content-Type", "application/json") - vars := mux.Vars(req) + vars, _ := common.URLDecodeMapValues(mux.Vars(req)) Download( w, req, From b4d638cd04d64bf574d8af75fc22efb72b4a0798 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 20 Dec 2019 13:24:57 +0000 Subject: [PATCH 06/16] Return server names (#833) * Remove unnecessary map->array processing * Return server names in room federation directory query * Knock off a TODO --- cmd/dendrite-federation-api-server/main.go | 3 ++- cmd/dendrite-monolith-server/main.go | 2 +- federationapi/federationapi.go | 5 ++++- federationapi/routing/query.go | 11 +++++++++-- federationapi/routing/routing.go | 4 +++- federationsender/query/query.go | 9 +++------ 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/cmd/dendrite-federation-api-server/main.go b/cmd/dendrite-federation-api-server/main.go index c83845d25..367f5dc0c 100644 --- a/cmd/dendrite-federation-api-server/main.go +++ b/cmd/dendrite-federation-api-server/main.go @@ -29,6 +29,7 @@ func main() { deviceDB := base.CreateDeviceDB() keyDB := base.CreateKeyDB() federation := base.CreateFederationClient() + federationSender := base.CreateHTTPFederationSenderAPIs() keyRing := keydb.CreateKeyRing(federation.Client, keyDB) alias, input, query := base.CreateHTTPRoomserverAPIs() @@ -36,7 +37,7 @@ func main() { federationapi.SetupFederationAPIComponent( base, accountDB, deviceDB, federation, &keyRing, - alias, input, query, asQuery, + alias, input, query, asQuery, federationSender, ) base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI)) diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 0a320616e..5ea6b154a 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -67,7 +67,7 @@ func main() { federation, &keyRing, alias, input, query, typingInputAPI, asQuery, transactions.New(), fedSenderAPI, ) - federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery) + federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery, fedSenderAPI) mediaapi.SetupMediaAPIComponent(base, deviceDB) publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB) syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query) diff --git a/federationapi/federationapi.go b/federationapi/federationapi.go index 87402d976..c2aef06c8 100644 --- a/federationapi/federationapi.go +++ b/federationapi/federationapi.go @@ -19,6 +19,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" "github.com/matrix-org/dendrite/clientapi/auth/storage/devices" "github.com/matrix-org/dendrite/common/basecomponent" + federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" // TODO: Are we really wanting to pull in the producer from clientapi @@ -39,11 +40,13 @@ func SetupFederationAPIComponent( inputAPI roomserverAPI.RoomserverInputAPI, queryAPI roomserverAPI.RoomserverQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI, + federationSenderAPI federationSenderAPI.FederationSenderQueryAPI, ) { roomserverProducer := producers.NewRoomserverProducer(inputAPI) routing.Setup( base.APIMux, *base.Cfg, queryAPI, aliasAPI, asAPI, - roomserverProducer, *keyRing, federation, accountsDB, deviceDB, + roomserverProducer, federationSenderAPI, *keyRing, federation, accountsDB, + deviceDB, ) } diff --git a/federationapi/routing/query.go b/federationapi/routing/query.go index 781b8ea46..ed2d8b741 100644 --- a/federationapi/routing/query.go +++ b/federationapi/routing/query.go @@ -21,6 +21,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/common/config" + federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" @@ -33,6 +34,7 @@ func RoomAliasToID( federation *gomatrixserverlib.FederationClient, cfg config.Dendrite, aliasAPI roomserverAPI.RoomserverAliasAPI, + senderAPI federationSenderAPI.FederationSenderQueryAPI, ) util.JSONResponse { roomAlias := httpReq.FormValue("room_alias") if roomAlias == "" { @@ -59,10 +61,15 @@ func RoomAliasToID( } if queryRes.RoomID != "" { - // TODO: List servers that are aware of this room alias + serverQueryReq := federationSenderAPI.QueryJoinedHostServerNamesInRoomRequest{RoomID: queryRes.RoomID} + var serverQueryRes federationSenderAPI.QueryJoinedHostServerNamesInRoomResponse + if err = senderAPI.QueryJoinedHostServerNamesInRoom(httpReq.Context(), &serverQueryReq, &serverQueryRes); err != nil { + return httputil.LogThenError(httpReq, err) + } + resp = gomatrixserverlib.RespDirectory{ RoomID: queryRes.RoomID, - Servers: []gomatrixserverlib.ServerName{}, + Servers: serverQueryRes.ServerNames, } } else { // If no alias was found, return an error diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index 9f576790b..eae1fabdb 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -24,6 +24,7 @@ import ( "github.com/matrix-org/dendrite/clientapi/producers" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/common/config" + federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -46,6 +47,7 @@ func Setup( aliasAPI roomserverAPI.RoomserverAliasAPI, asAPI appserviceAPI.AppServiceQueryAPI, producer *producers.RoomserverProducer, + federationSenderAPI federationSenderAPI.FederationSenderQueryAPI, keys gomatrixserverlib.KeyRing, federation *gomatrixserverlib.FederationClient, accountDB *accounts.Database, @@ -156,7 +158,7 @@ func Setup( "federation_query_room_alias", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { return RoomAliasToID( - httpReq, federation, cfg, aliasAPI, + httpReq, federation, cfg, aliasAPI, federationSenderAPI, ) }, )).Methods(http.MethodGet) diff --git a/federationsender/query/query.go b/federationsender/query/query.go index 088244826..8c35bb29e 100644 --- a/federationsender/query/query.go +++ b/federationsender/query/query.go @@ -45,15 +45,12 @@ func (f *FederationSenderQueryAPI) QueryJoinedHostServerNamesInRoom( return } - serverNamesSet := make(map[gomatrixserverlib.ServerName]bool, len(joinedHosts)) + response.ServerNames = make([]gomatrixserverlib.ServerName, 0, len(joinedHosts)) for _, host := range joinedHosts { - serverNamesSet[host.ServerName] = true + response.ServerNames = append(response.ServerNames, host.ServerName) } - response.ServerNames = make([]gomatrixserverlib.ServerName, 0, len(serverNamesSet)) - for name := range serverNamesSet { - response.ServerNames = append(response.ServerNames, name) - } + // TODO: remove duplicates? return } From af9568ba4468eb106ec305df496fbe1319fd74eb Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 20 Dec 2019 14:41:32 +0000 Subject: [PATCH 07/16] Fix /send_join and /send_leave (#821) Fix the /send_join and /send_leave endpoints, so that they use the v2 endpoints as mandated by MSC1802. Also comment out the SyTest tests that are failing because of lack of support for the v1 endpoints. --- federationapi/routing/routing.go | 6 ++++-- testfile | 25 ++++++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index eae1fabdb..302f7ed0b 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -33,6 +33,7 @@ import ( const ( pathPrefixV2Keys = "/_matrix/key/v2" pathPrefixV1Federation = "/_matrix/federation/v1" + pathPrefixV2Federation = "/_matrix/federation/v2" ) // Setup registers HTTP handlers with the given ServeMux. @@ -55,6 +56,7 @@ func Setup( ) { v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter() v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter() + v2fedmux := apiMux.PathPrefix(pathPrefixV2Federation).Subrouter() localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse { return LocalKeys(cfg) @@ -200,7 +202,7 @@ func Setup( }, )).Methods(http.MethodGet) - v1fedmux.Handle("/send_join/{roomID}/{userID}", common.MakeFedAPI( + v2fedmux.Handle("/send_join/{roomID}/{userID}", common.MakeFedAPI( "federation_send_join", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { vars, err := common.URLDecodeMapValues(mux.Vars(httpReq)) @@ -230,7 +232,7 @@ func Setup( }, )).Methods(http.MethodGet) - v1fedmux.Handle("/send_leave/{roomID}/{userID}", common.MakeFedAPI( + v2fedmux.Handle("/send_leave/{roomID}/{userID}", common.MakeFedAPI( "federation_send_leave", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { vars, err := common.URLDecodeMapValues(mux.Vars(httpReq)) diff --git a/testfile b/testfile index 5ad3426e9..8a4e9de12 100644 --- a/testfile +++ b/testfile @@ -126,8 +126,14 @@ Checking local federation server Inbound federation can query profile data Outbound federation can send room-join requests Outbound federation can send events -Inbound federation can backfill events -Backfill checks the events requested belong to the room +# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, +# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this +# test for now. +#Inbound federation can backfill events +# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, +# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this +# test for now. +#Backfill checks the events requested belong to the room Can upload without a file name Can download without a file name locally Can upload with ASCII file name @@ -143,7 +149,10 @@ Trying to get push rules with unknown rule_id fails with 404 Events come down the correct room local user can join room with version 5 User can invite local user to room with version 5 -Inbound federation can receive v1 room-join requests +# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, +# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this +# test for now. +#Inbound federation can receive v1 room-join requests Typing events appear in initial sync Typing events appear in incremental sync Typing events appear in gapped sync @@ -156,8 +165,14 @@ User can create and send/receive messages in a room with version 1 POST /createRoom ignores attempts to set the room version via creation_content Inbound federation rejects remote attempts to join local users to rooms Inbound federation rejects remote attempts to kick local users to rooms -An event which redacts itself should be ignored -A pair of events which redact each other should be ignored +# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, +# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this +# test for now. +#An event which redacts itself should be ignored +# SyTest currently only implements the v1 endpoints for /send_join and /send_leave, +# whereas Dendrite only supports the v2 endpoints for those, so let's ignore this +# test for now. +#A pair of events which redact each other should be ignored Full state sync includes joined rooms A message sent after an initial sync appears in the timeline of an incremental sync. Can add tag From e2d73855ebdb236aaa4388685872792ba3907925 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar <31231064+abhishekkumar2718@users.noreply.github.com> Date: Fri, 20 Dec 2019 20:12:57 +0530 Subject: [PATCH 08/16] Refuse /send_join without m.room.create (#824) Signed-off-by: Abhishek Kumar --- federationapi/routing/join.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/federationapi/routing/join.go b/federationapi/routing/join.go index 6f6574dd7..e2885dd99 100644 --- a/federationapi/routing/join.go +++ b/federationapi/routing/join.go @@ -154,16 +154,23 @@ func SendJoin( // Fetch the state and auth chain. We do this before we send the events // on, in case this fails. - var stateAndAuthChainRepsonse api.QueryStateAndAuthChainResponse + var stateAndAuthChainResponse api.QueryStateAndAuthChainResponse err = query.QueryStateAndAuthChain(httpReq.Context(), &api.QueryStateAndAuthChainRequest{ PrevEventIDs: event.PrevEventIDs(), AuthEventIDs: event.AuthEventIDs(), RoomID: roomID, - }, &stateAndAuthChainRepsonse) + }, &stateAndAuthChainResponse) if err != nil { return httputil.LogThenError(httpReq, err) } + if !stateAndAuthChainResponse.RoomExists { + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound("Room does not exist"), + } + } + // Send the events to the room server. // We are responsible for notifying other servers that the user has joined // the room, so set SendAsServer to cfg.Matrix.ServerName @@ -177,8 +184,8 @@ func SendJoin( return util.JSONResponse{ Code: http.StatusOK, JSON: map[string]interface{}{ - "state": stateAndAuthChainRepsonse.StateEvents, - "auth_chain": stateAndAuthChainRepsonse.AuthChainEvents, + "state": stateAndAuthChainResponse.StateEvents, + "auth_chain": stateAndAuthChainResponse.AuthChainEvents, }, } } From f1e229e9d4a6c7f6ad7eda146ec0e3a8a6603af1 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 20 Dec 2019 14:44:34 +0000 Subject: [PATCH 09/16] AS should use the v1 endpoint, rather than r0 (#827) --- appservice/routing/routing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appservice/routing/routing.go b/appservice/routing/routing.go index 3c19c8401..0e4bd6bab 100644 --- a/appservice/routing/routing.go +++ b/appservice/routing/routing.go @@ -27,7 +27,7 @@ import ( "github.com/matrix-org/util" ) -const pathPrefixApp = "/_matrix/app/r0" +const pathPrefixApp = "/_matrix/app/v1" // Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client // to clients which need to make outbound HTTP requests. From 52dfa40471e1ba220960ed8f14033d7e03a96385 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 20 Dec 2019 14:46:59 +0000 Subject: [PATCH 10/16] docker: Passthrough parameters to dendrite-monolith-server --- docker/services/monolith.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/services/monolith.sh b/docker/services/monolith.sh index a038728b4..2287555cd 100644 --- a/docker/services/monolith.sh +++ b/docker/services/monolith.sh @@ -2,4 +2,4 @@ bash ./docker/build.sh -./bin/dendrite-monolith-server --tls-cert=server.crt --tls-key=server.key +./bin/dendrite-monolith-server --tls-cert=server.crt --tls-key=server.key $@ From 4f75e4febe5ec3ed8666fc723e0f2350f372bf2d Mon Sep 17 00:00:00 2001 From: S7evinK Date: Fri, 20 Dec 2019 16:00:25 +0100 Subject: [PATCH 11/16] Fix copy & paste error (#812) --- roomserver/api/query.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roomserver/api/query.go b/roomserver/api/query.go index a544f8aa2..e52c74ac3 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -332,7 +332,7 @@ const RoomserverQueryMissingEventsPath = "/api/roomserver/queryMissingEvents" // RoomserverQueryStateAndAuthChainPath is the HTTP path for the QueryStateAndAuthChain API const RoomserverQueryStateAndAuthChainPath = "/api/roomserver/queryStateAndAuthChain" -// RoomserverQueryBackfillPath is the HTTP path for the QueryMissingEvents API +// RoomserverQueryBackfillPath is the HTTP path for the QueryBackfill API const RoomserverQueryBackfillPath = "/api/roomserver/QueryBackfill" // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API. @@ -475,6 +475,6 @@ func (h *httpRoomserverQueryAPI) QueryBackfill( span, ctx := opentracing.StartSpanFromContext(ctx, "QueryBackfill") defer span.Finish() - apiURL := h.roomserverURL + RoomserverQueryMissingEventsPath + apiURL := h.roomserverURL + RoomserverQueryBackfillPath return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } From b34fce0d85be4626fcc9d7ffaa110aca148cdb4b Mon Sep 17 00:00:00 2001 From: S7evinK Date: Fri, 20 Dec 2019 16:02:09 +0100 Subject: [PATCH 12/16] Use gomatrixserverlib.Transaction instead of local type (#590) (#811) --- federationapi/routing/backfill.go | 10 ++++--- federationapi/types/types.go | 43 ------------------------------- 2 files changed, 6 insertions(+), 47 deletions(-) delete mode 100644 federationapi/types/types.go diff --git a/federationapi/routing/backfill.go b/federationapi/routing/backfill.go index d996db6a3..5c6b0087f 100644 --- a/federationapi/routing/backfill.go +++ b/federationapi/routing/backfill.go @@ -17,11 +17,11 @@ package routing import ( "net/http" "strconv" + "time" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/common/config" - "github.com/matrix-org/dendrite/federationapi/types" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -90,9 +90,11 @@ func Backfill( } } - txn := types.NewTransaction() - txn.Origin = cfg.Matrix.ServerName - txn.PDUs = evs + txn := gomatrixserverlib.Transaction{ + Origin: cfg.Matrix.ServerName, + PDUs: evs, + OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()), + } // Send the events to the client. return util.JSONResponse{ diff --git a/federationapi/types/types.go b/federationapi/types/types.go deleted file mode 100644 index 24838d547..000000000 --- a/federationapi/types/types.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "time" - - "github.com/matrix-org/gomatrixserverlib" -) - -// Transaction is the representation of a transaction from the federation API -// See https://matrix.org/docs/spec/server_server/unstable.html for more info. -type Transaction struct { - // The server_name of the homeserver sending this transaction. - Origin gomatrixserverlib.ServerName `json:"origin"` - // POSIX timestamp in milliseconds on originating homeserver when this - // transaction started. - OriginServerTS int64 `json:"origin_server_ts"` - // List of persistent updates to rooms. - PDUs []gomatrixserverlib.Event `json:"pdus"` -} - -// NewTransaction sets the timestamp of a new transaction instance and then -// returns the said instance. -func NewTransaction() Transaction { - // Retrieve the current timestamp in nanoseconds and make it a milliseconds - // one. - ts := time.Now().UnixNano() / int64(time.Millisecond) - - return Transaction{OriginServerTS: ts} -} From dc25fb6837f4e301d572b71d2f480f0c052a399c Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Wed, 25 Dec 2019 18:14:58 +0000 Subject: [PATCH 13/16] Move files back if linting fails (#810) --- scripts/find-lint.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/find-lint.sh b/scripts/find-lint.sh index ca991b62c..c9663e4e8 100755 --- a/scripts/find-lint.sh +++ b/scripts/find-lint.sh @@ -29,8 +29,15 @@ echo "Installing golangci-lint..." cp go.mod go.mod.bak && cp go.sum go.sum.bak go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1 +# Run linting echo "Looking for lint..." -golangci-lint run $args + +# Capture exit code to ensure go.{mod,sum} is restored before exiting +exit_code=0 + +golangci-lint run $args || exit_code=1 # Restore go.{mod,sum} mv go.mod.bak go.mod && mv go.sum.bak go.sum + +exit $exit_code From 5741235833489d2769c72c24f1b5d93abe9d21ed Mon Sep 17 00:00:00 2001 From: Arshpreet <30545756+arsh-7@users.noreply.github.com> Date: Wed, 25 Dec 2019 23:58:10 +0530 Subject: [PATCH 14/16] replaced gometalinter description with golangci-lint (#837) --- CODE_STYLE.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/CODE_STYLE.md b/CODE_STYLE.md index e8208172c..1f40c76fc 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -1,21 +1,23 @@ # Code Style -We follow the standard Go style using gofmt, but with a few extra +We follow the standard Go style using goimports, but with a few extra considerations. ## Linters -We use `gometalinter` to run a number of linters, the exact list can be found -in [linter.json](linter.json). Some of these are slow and expensive to run, but -a subset can be found in [linter-fast.json](linter-fast.json) that run quickly -enough to be run as part of an IDE. +We use `golangci-lint` to run a number of linters, the exact list can be found +under linters in [.golangci.yml](.golangci.yml). +[Installation](https://github.com/golangci/golangci-lint#install) and [Editor +Integration](https://github.com/golangci/golangci-lint#editor-integration) for +it can be found in the readme of golangci-lint. For rare cases where a linter is giving a spurious warning, it can be disabled -for that line or statement using a [comment directive](https://github.com/alecthomas/gometalinter#comment-directives), e.g. -`// nolint: gocyclo`. This should be used sparingly and only when its clear -that the lint warning is spurious. +for that line or statement using a [comment +directive](https://github.com/golangci/golangci-lint#nolint), e.g. `var +bad_name int //nolint:golint,unused`. This should be used sparingly and only +when its clear that the lint warning is spurious. -The linters are vendored, and can be run using [scripts/find-lint.sh](scripts/find-lint.sh) +The linters can be run using [scripts/find-lint.sh](scripts/find-lint.sh) (see file for docs) or as part of a build/test/lint cycle using [scripts/build-test-lint.sh](scripts/build-test-lint.sh). @@ -82,9 +84,9 @@ sets up linting correctly: ```json { - "go.gopath": "${workspaceRoot}:${workspaceRoot}/vendor", - "go.lintOnSave": "workspace", - "go.lintTool": "gometalinter", - "go.lintFlags": ["--config=linter-fast.json", "--concurrency=5"] + "go.lintTool":"golangci-lint", + "go.lintFlags": [ + "--fast" + ] } ``` From 6cab62246816baf1fdd026744727cba9a7b21c28 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 2 Jan 2020 14:51:21 +0000 Subject: [PATCH 15/16] Amend syncapi SQL queries to return missing columns (#840) * This commit updates a couple of the syncapi SQL queries to return additional columns that are required/expected by rowsToStreamEvents in output_room_events_table.go. It's not exactly clear to me yet what transaction_id and session_id do, but these being added n #367 results in state events breaking the /sync endpoint. This is a temporary fix. We need to come up with a better solution. * gomatrix to gomatrixserverlib on some weird line change * Tweaks from @babolivier review comments --- syncapi/storage/current_room_state_table.go | 7 ++++++- syncapi/storage/output_room_events_table.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/syncapi/storage/current_room_state_table.go b/syncapi/storage/current_room_state_table.go index 88e7a76c3..1ab70879a 100644 --- a/syncapi/storage/current_room_state_table.go +++ b/syncapi/storage/current_room_state_table.go @@ -84,7 +84,12 @@ const selectStateEventSQL = "" + "SELECT event_json FROM syncapi_current_room_state WHERE room_id = $1 AND type = $2 AND state_key = $3" const selectEventsWithEventIDsSQL = "" + - "SELECT added_at, event_json FROM syncapi_current_room_state WHERE event_id = ANY($1)" + // TODO: The session_id and transaction_id blanks are here because otherwise + // the rowsToStreamEvents expects there to be exactly four columns. We need to + // figure out if these really need to be in the DB, and if so, we need a + // better permanent fix for this. - neilalexander, 2 Jan 2020 + "SELECT added_at, event_json, 0 AS session_id, '' AS transaction_id" + + " FROM syncapi_current_room_state WHERE event_id = ANY($1)" type currentRoomStateStatements struct { upsertRoomStateStmt *sql.Stmt diff --git a/syncapi/storage/output_room_events_table.go b/syncapi/storage/output_room_events_table.go index 2df2a96a1..e1803a17d 100644 --- a/syncapi/storage/output_room_events_table.go +++ b/syncapi/storage/output_room_events_table.go @@ -67,7 +67,7 @@ const insertEventSQL = "" + ") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id" const selectEventsSQL = "" + - "SELECT id, event_json FROM syncapi_output_room_events WHERE event_id = ANY($1)" + "SELECT id, event_json, session_id, transaction_id FROM syncapi_output_room_events WHERE event_id = ANY($1)" const selectRecentEventsSQL = "" + "SELECT id, event_json, session_id, transaction_id FROM syncapi_output_room_events" + From c28577ea25d4f9c82110450f7f371905c2750f71 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 3 Jan 2020 14:07:05 +0000 Subject: [PATCH 16/16] Implement storage interfaces (#841) * Implement interfaces for federationsender storage * Implement interfaces for mediaapi storage * Implement interfaces for publicroomsapi storage * Implement interfaces for roomserver storage * Implement interfaces for syncapi storage * Implement interfaces for keydb storage * common.PartitionStorer in publicroomsapi interface * Update copyright notices --- common/basecomponent/base.go | 2 +- common/keydb/keydb.go | 70 +- common/keydb/postgres/keydb.go | 83 ++ .../keydb/{ => postgres}/server_key_table.go | 5 +- federationsender/consumers/roomserver.go | 4 +- federationsender/consumers/typingserver.go | 4 +- .../{ => postgres}/joined_hosts_table.go | 5 +- .../storage/{ => postgres}/room_table.go | 5 +- federationsender/storage/postgres/storage.go | 122 +++ federationsender/storage/storage.go | 108 +-- mediaapi/routing/download.go | 14 +- mediaapi/routing/routing.go | 4 +- mediaapi/routing/upload.go | 6 +- .../{ => postgres}/media_repository_table.go | 5 +- mediaapi/storage/{ => postgres}/prepare.go | 5 +- mediaapi/storage/{ => postgres}/sql.go | 5 +- mediaapi/storage/postgres/storage.go | 106 +++ .../storage/{ => postgres}/thumbnail_table.go | 5 +- mediaapi/storage/storage.go | 94 +-- mediaapi/thumbnailer/thumbnailer.go | 2 +- mediaapi/thumbnailer/thumbnailer_nfnt.go | 6 +- publicroomsapi/consumers/roomserver.go | 4 +- publicroomsapi/directory/directory.go | 4 +- publicroomsapi/directory/public_rooms.go | 2 +- publicroomsapi/routing/routing.go | 2 +- .../storage/{ => postgres}/prepare.go | 5 +- .../{ => postgres}/public_rooms_table.go | 5 +- publicroomsapi/storage/postgres/storage.go | 253 ++++++ publicroomsapi/storage/storage.go | 244 +----- .../{ => postgres}/event_json_table.go | 5 +- .../{ => postgres}/event_state_keys_table.go | 5 +- .../{ => postgres}/event_types_table.go | 5 +- .../storage/{ => postgres}/events_table.go | 5 +- .../storage/{ => postgres}/invite_table.go | 5 +- .../{ => postgres}/membership_table.go | 5 +- roomserver/storage/{ => postgres}/prepare.go | 5 +- .../{ => postgres}/previous_events_table.go | 5 +- .../{ => postgres}/room_aliases_table.go | 5 +- .../storage/{ => postgres}/rooms_table.go | 5 +- roomserver/storage/{ => postgres}/sql.go | 5 +- .../{ => postgres}/state_block_table.go | 5 +- .../{ => postgres}/state_block_table_test.go | 5 +- .../{ => postgres}/state_snapshot_table.go | 5 +- roomserver/storage/postgres/storage.go | 713 +++++++++++++++++ .../{ => postgres}/transactions_table.go | 5 +- roomserver/storage/storage.go | 724 +----------------- syncapi/consumers/clientapi.go | 4 +- syncapi/consumers/roomserver.go | 4 +- syncapi/consumers/typingserver.go | 4 +- syncapi/routing/routing.go | 2 +- syncapi/routing/state.go | 4 +- .../{ => postgres}/account_data_table.go | 5 +- .../current_room_state_table.go | 5 +- syncapi/storage/{ => postgres}/filtering.go | 2 +- .../storage/{ => postgres}/invites_table.go | 17 +- .../output_room_events_table.go | 5 +- syncapi/storage/{ => postgres}/syncserver.go | 5 +- syncapi/storage/storage.go | 63 ++ syncapi/sync/notifier.go | 2 +- syncapi/sync/requestpool.go | 4 +- 60 files changed, 1594 insertions(+), 1223 deletions(-) create mode 100644 common/keydb/postgres/keydb.go rename common/keydb/{ => postgres}/server_key_table.go (97%) rename federationsender/storage/{ => postgres}/joined_hosts_table.go (97%) rename federationsender/storage/{ => postgres}/room_table.go (96%) create mode 100644 federationsender/storage/postgres/storage.go rename mediaapi/storage/{ => postgres}/media_repository_table.go (97%) rename mediaapi/storage/{ => postgres}/prepare.go (90%) rename mediaapi/storage/{ => postgres}/sql.go (88%) create mode 100644 mediaapi/storage/postgres/storage.go rename mediaapi/storage/{ => postgres}/thumbnail_table.go (98%) rename publicroomsapi/storage/{ => postgres}/prepare.go (90%) rename publicroomsapi/storage/{ => postgres}/public_rooms_table.go (98%) create mode 100644 publicroomsapi/storage/postgres/storage.go rename roomserver/storage/{ => postgres}/event_json_table.go (96%) rename roomserver/storage/{ => postgres}/event_state_keys_table.go (98%) rename roomserver/storage/{ => postgres}/event_types_table.go (98%) rename roomserver/storage/{ => postgres}/events_table.go (99%) rename roomserver/storage/{ => postgres}/invite_table.go (97%) rename roomserver/storage/{ => postgres}/membership_table.go (98%) rename roomserver/storage/{ => postgres}/prepare.go (90%) rename roomserver/storage/{ => postgres}/previous_events_table.go (97%) rename roomserver/storage/{ => postgres}/room_aliases_table.go (96%) rename roomserver/storage/{ => postgres}/rooms_table.go (97%) rename roomserver/storage/{ => postgres}/sql.go (92%) rename roomserver/storage/{ => postgres}/state_block_table.go (98%) rename roomserver/storage/{ => postgres}/state_block_table_test.go (95%) rename roomserver/storage/{ => postgres}/state_snapshot_table.go (97%) create mode 100644 roomserver/storage/postgres/storage.go rename roomserver/storage/{ => postgres}/transactions_table.go (95%) rename syncapi/storage/{ => postgres}/account_data_table.go (97%) rename syncapi/storage/{ => postgres}/current_room_state_table.go (98%) rename syncapi/storage/{ => postgres}/filtering.go (98%) rename syncapi/storage/{ => postgres}/invites_table.go (84%) rename syncapi/storage/{ => postgres}/output_room_events_table.go (99%) rename syncapi/storage/{ => postgres}/syncserver.go (99%) create mode 100644 syncapi/storage/storage.go diff --git a/common/basecomponent/base.go b/common/basecomponent/base.go index b05ec42db..50fc2d5c6 100644 --- a/common/basecomponent/base.go +++ b/common/basecomponent/base.go @@ -138,7 +138,7 @@ func (b *BaseDendrite) CreateAccountsDB() *accounts.Database { // CreateKeyDB creates a new instance of the key database. Should only be called // once per component. -func (b *BaseDendrite) CreateKeyDB() *keydb.Database { +func (b *BaseDendrite) CreateKeyDB() keydb.Database { db, err := keydb.NewDatabase(string(b.Cfg.Database.ServerKey)) if err != nil { logrus.WithError(err).Panicf("failed to connect to keys db") diff --git a/common/keydb/keydb.go b/common/keydb/keydb.go index 9e8b6a6fa..b9fa884e2 100644 --- a/common/keydb/keydb.go +++ b/common/keydb/keydb.go @@ -1,4 +1,4 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,67 +16,29 @@ package keydb import ( "context" - "database/sql" + "errors" + "net/url" + "github.com/matrix-org/dendrite/common/keydb/postgres" "github.com/matrix-org/gomatrixserverlib" ) -// A Database implements gomatrixserverlib.KeyDatabase and is used to store -// the public keys for other matrix servers. -type Database struct { - statements serverKeyStatements +type Database interface { + FetcherName() string + FetchKeys(ctx context.Context, requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) + StoreKeys(ctx context.Context, keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error } -// NewDatabase prepares a new key database. -// It creates the necessary tables if they don't already exist. -// It prepares all the SQL statements that it will use. -// Returns an error if there was a problem talking to the database. -func NewDatabase(dataSourceName string) (*Database, error) { - db, err := sql.Open("postgres", dataSourceName) +// NewDatabase opens a database connection. +func NewDatabase(dataSourceName string) (Database, error) { + uri, err := url.Parse(dataSourceName) if err != nil { return nil, err } - d := &Database{} - err = d.statements.prepare(db) - if err != nil { - return nil, err + switch uri.Scheme { + case "postgres": + return postgres.NewDatabase(dataSourceName) + default: + return nil, errors.New("unknown schema") } - return d, nil -} - -// FetcherName implements KeyFetcher -func (d Database) FetcherName() string { - return "KeyDatabase" -} - -// FetchKeys implements gomatrixserverlib.KeyDatabase -func (d *Database) FetchKeys( - ctx context.Context, - requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, -) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { - return d.statements.bulkSelectServerKeys(ctx, requests) -} - -// StoreKeys implements gomatrixserverlib.KeyDatabase -func (d *Database) StoreKeys( - ctx context.Context, - keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, -) error { - // TODO: Inserting all the keys within a single transaction may - // be more efficient since the transaction overhead can be quite - // high for a single insert statement. - var lastErr error - for request, keys := range keyMap { - if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil { - // Rather than returning immediately on error we try to insert the - // remaining keys. - // Since we are inserting the keys outside of a transaction it is - // possible for some of the inserts to succeed even though some - // of the inserts have failed. - // Ensuring that we always insert all the keys we can means that - // this behaviour won't depend on the iteration order of the map. - lastErr = err - } - } - return lastErr } diff --git a/common/keydb/postgres/keydb.go b/common/keydb/postgres/keydb.go new file mode 100644 index 000000000..bf0ff69c9 --- /dev/null +++ b/common/keydb/postgres/keydb.go @@ -0,0 +1,83 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + + "github.com/matrix-org/gomatrixserverlib" +) + +// A Database implements gomatrixserverlib.KeyDatabase and is used to store +// the public keys for other matrix servers. +type Database struct { + statements serverKeyStatements +} + +// NewDatabase prepares a new key database. +// It creates the necessary tables if they don't already exist. +// It prepares all the SQL statements that it will use. +// Returns an error if there was a problem talking to the database. +func NewDatabase(dataSourceName string) (*Database, error) { + db, err := sql.Open("postgres", dataSourceName) + if err != nil { + return nil, err + } + d := &Database{} + err = d.statements.prepare(db) + if err != nil { + return nil, err + } + return d, nil +} + +// FetcherName implements KeyFetcher +func (d Database) FetcherName() string { + return "KeyDatabase" +} + +// FetchKeys implements gomatrixserverlib.KeyDatabase +func (d *Database) FetchKeys( + ctx context.Context, + requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { + return d.statements.bulkSelectServerKeys(ctx, requests) +} + +// StoreKeys implements gomatrixserverlib.KeyDatabase +func (d *Database) StoreKeys( + ctx context.Context, + keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, +) error { + // TODO: Inserting all the keys within a single transaction may + // be more efficient since the transaction overhead can be quite + // high for a single insert statement. + var lastErr error + for request, keys := range keyMap { + if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil { + // Rather than returning immediately on error we try to insert the + // remaining keys. + // Since we are inserting the keys outside of a transaction it is + // possible for some of the inserts to succeed even though some + // of the inserts have failed. + // Ensuring that we always insert all the keys we can means that + // this behaviour won't depend on the iteration order of the map. + lastErr = err + } + } + return lastErr +} diff --git a/common/keydb/server_key_table.go b/common/keydb/postgres/server_key_table.go similarity index 97% rename from common/keydb/server_key_table.go rename to common/keydb/postgres/server_key_table.go index c922fa98d..8fb9a0ee9 100644 --- a/common/keydb/server_key_table.go +++ b/common/keydb/postgres/server_key_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package keydb +package postgres import ( "context" diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 3ba978b1d..4568f44dc 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -33,7 +33,7 @@ import ( // OutputRoomEventConsumer consumes events that originated in the room server. type OutputRoomEventConsumer struct { roomServerConsumer *common.ContinualConsumer - db *storage.Database + db storage.Database queues *queue.OutgoingQueues query api.RoomserverQueryAPI } @@ -43,7 +43,7 @@ func NewOutputRoomEventConsumer( cfg *config.Dendrite, kafkaConsumer sarama.Consumer, queues *queue.OutgoingQueues, - store *storage.Database, + store storage.Database, queryAPI api.RoomserverQueryAPI, ) *OutputRoomEventConsumer { consumer := common.ContinualConsumer{ diff --git a/federationsender/consumers/typingserver.go b/federationsender/consumers/typingserver.go index c4cd0e599..590fcb257 100644 --- a/federationsender/consumers/typingserver.go +++ b/federationsender/consumers/typingserver.go @@ -29,7 +29,7 @@ import ( // OutputTypingEventConsumer consumes events that originate in typing server. type OutputTypingEventConsumer struct { consumer *common.ContinualConsumer - db *storage.Database + db storage.Database queues *queue.OutgoingQueues ServerName gomatrixserverlib.ServerName } @@ -39,7 +39,7 @@ func NewOutputTypingEventConsumer( cfg *config.Dendrite, kafkaConsumer sarama.Consumer, queues *queue.OutgoingQueues, - store *storage.Database, + store storage.Database, ) *OutputTypingEventConsumer { consumer := common.ContinualConsumer{ Topic: string(cfg.Kafka.Topics.OutputTypingEvent), diff --git a/federationsender/storage/joined_hosts_table.go b/federationsender/storage/postgres/joined_hosts_table.go similarity index 97% rename from federationsender/storage/joined_hosts_table.go rename to federationsender/storage/postgres/joined_hosts_table.go index 5d652a1a1..bd580e3b5 100644 --- a/federationsender/storage/joined_hosts_table.go +++ b/federationsender/storage/postgres/joined_hosts_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/federationsender/storage/room_table.go b/federationsender/storage/postgres/room_table.go similarity index 96% rename from federationsender/storage/room_table.go rename to federationsender/storage/postgres/room_table.go index bb52b7076..a64424b44 100644 --- a/federationsender/storage/room_table.go +++ b/federationsender/storage/postgres/room_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/federationsender/storage/postgres/storage.go b/federationsender/storage/postgres/storage.go new file mode 100644 index 000000000..c60f6dc5c --- /dev/null +++ b/federationsender/storage/postgres/storage.go @@ -0,0 +1,122 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/federationsender/types" +) + +// Database stores information needed by the federation sender +type Database struct { + joinedHostsStatements + roomStatements + common.PartitionOffsetStatements + db *sql.DB +} + +// NewDatabase opens a new database +func NewDatabase(dataSourceName string) (*Database, error) { + var result Database + var err error + if result.db, err = sql.Open("postgres", dataSourceName); err != nil { + return nil, err + } + if err = result.prepare(); err != nil { + return nil, err + } + return &result, nil +} + +func (d *Database) prepare() error { + var err error + + if err = d.joinedHostsStatements.prepare(d.db); err != nil { + return err + } + + if err = d.roomStatements.prepare(d.db); err != nil { + return err + } + + return d.PartitionOffsetStatements.Prepare(d.db, "federationsender") +} + +// UpdateRoom updates the joined hosts for a room and returns what the joined +// hosts were before the update, or nil if this was a duplicate message. +// This is called when we receive a message from kafka, so we pass in +// oldEventID and newEventID to check that we haven't missed any messages or +// this isn't a duplicate message. +func (d *Database) UpdateRoom( + ctx context.Context, + roomID, oldEventID, newEventID string, + addHosts []types.JoinedHost, + removeHosts []string, +) (joinedHosts []types.JoinedHost, err error) { + err = common.WithTransaction(d.db, func(txn *sql.Tx) error { + err = d.insertRoom(ctx, txn, roomID) + if err != nil { + return err + } + + lastSentEventID, err := d.selectRoomForUpdate(ctx, txn, roomID) + if err != nil { + return err + } + + if lastSentEventID == newEventID { + // We've handled this message before, so let's just ignore it. + // We can only get a duplicate for the last message we processed, + // so its enough just to compare the newEventID with lastSentEventID + return nil + } + + if lastSentEventID != oldEventID { + return types.EventIDMismatchError{ + DatabaseID: lastSentEventID, RoomServerID: oldEventID, + } + } + + joinedHosts, err = d.selectJoinedHostsWithTx(ctx, txn, roomID) + if err != nil { + return err + } + + for _, add := range addHosts { + err = d.insertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName) + if err != nil { + return err + } + } + if err = d.deleteJoinedHosts(ctx, txn, removeHosts); err != nil { + return err + } + return d.updateRoom(ctx, txn, roomID, newEventID) + }) + return +} + +// GetJoinedHosts returns the currently joined hosts for room, +// as known to federationserver. +// Returns an error if something goes wrong. +func (d *Database) GetJoinedHosts( + ctx context.Context, roomID string, +) ([]types.JoinedHost, error) { + return d.selectJoinedHosts(ctx, roomID) +} diff --git a/federationsender/storage/storage.go b/federationsender/storage/storage.go index 3a0f87752..8cffdbf1a 100644 --- a/federationsender/storage/storage.go +++ b/federationsender/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,106 +16,30 @@ package storage import ( "context" - "database/sql" + "errors" + "net/url" "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/federationsender/storage/postgres" "github.com/matrix-org/dendrite/federationsender/types" ) -// Database stores information needed by the federation sender -type Database struct { - joinedHostsStatements - roomStatements - common.PartitionOffsetStatements - db *sql.DB +type Database interface { + common.PartitionStorer + UpdateRoom(ctx context.Context, roomID, oldEventID, newEventID string, addHosts []types.JoinedHost, removeHosts []string) (joinedHosts []types.JoinedHost, err error) + GetJoinedHosts(ctx context.Context, roomID string) ([]types.JoinedHost, error) } // NewDatabase opens a new database -func NewDatabase(dataSourceName string) (*Database, error) { - var result Database - var err error - if result.db, err = sql.Open("postgres", dataSourceName); err != nil { +func NewDatabase(dataSourceName string) (Database, error) { + uri, err := url.Parse(dataSourceName) + if err != nil { return nil, err } - if err = result.prepare(); err != nil { - return nil, err + switch uri.Scheme { + case "postgres": + return postgres.NewDatabase(dataSourceName) + default: + return nil, errors.New("unknown schema") } - return &result, nil -} - -func (d *Database) prepare() error { - var err error - - if err = d.joinedHostsStatements.prepare(d.db); err != nil { - return err - } - - if err = d.roomStatements.prepare(d.db); err != nil { - return err - } - - return d.PartitionOffsetStatements.Prepare(d.db, "federationsender") -} - -// UpdateRoom updates the joined hosts for a room and returns what the joined -// hosts were before the update, or nil if this was a duplicate message. -// This is called when we receive a message from kafka, so we pass in -// oldEventID and newEventID to check that we haven't missed any messages or -// this isn't a duplicate message. -func (d *Database) UpdateRoom( - ctx context.Context, - roomID, oldEventID, newEventID string, - addHosts []types.JoinedHost, - removeHosts []string, -) (joinedHosts []types.JoinedHost, err error) { - err = common.WithTransaction(d.db, func(txn *sql.Tx) error { - err = d.insertRoom(ctx, txn, roomID) - if err != nil { - return err - } - - lastSentEventID, err := d.selectRoomForUpdate(ctx, txn, roomID) - if err != nil { - return err - } - - if lastSentEventID == newEventID { - // We've handled this message before, so let's just ignore it. - // We can only get a duplicate for the last message we processed, - // so its enough just to compare the newEventID with lastSentEventID - return nil - } - - if lastSentEventID != oldEventID { - return types.EventIDMismatchError{ - DatabaseID: lastSentEventID, RoomServerID: oldEventID, - } - } - - joinedHosts, err = d.selectJoinedHostsWithTx(ctx, txn, roomID) - if err != nil { - return err - } - - for _, add := range addHosts { - err = d.insertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName) - if err != nil { - return err - } - } - if err = d.deleteJoinedHosts(ctx, txn, removeHosts); err != nil { - return err - } - return d.updateRoom(ctx, txn, roomID, newEventID) - }) - return -} - -// GetJoinedHosts returns the currently joined hosts for room, -// as known to federationserver. -// Returns an error if something goes wrong. -func (d *Database) GetJoinedHosts( - ctx context.Context, roomID string, -) ([]types.JoinedHost, error) { - return d.selectJoinedHosts(ctx, roomID) } diff --git a/mediaapi/routing/download.go b/mediaapi/routing/download.go index 80ad8418d..8544bd64f 100644 --- a/mediaapi/routing/download.go +++ b/mediaapi/routing/download.go @@ -67,7 +67,7 @@ func Download( origin gomatrixserverlib.ServerName, mediaID types.MediaID, cfg *config.Dendrite, - db *storage.Database, + db storage.Database, client *gomatrixserverlib.Client, activeRemoteRequests *types.ActiveRemoteRequests, activeThumbnailGeneration *types.ActiveThumbnailGeneration, @@ -192,7 +192,7 @@ func (r *downloadRequest) doDownload( ctx context.Context, w http.ResponseWriter, cfg *config.Dendrite, - db *storage.Database, + db storage.Database, client *gomatrixserverlib.Client, activeRemoteRequests *types.ActiveRemoteRequests, activeThumbnailGeneration *types.ActiveThumbnailGeneration, @@ -235,7 +235,7 @@ func (r *downloadRequest) respondFromLocalFile( absBasePath config.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, - db *storage.Database, + db storage.Database, dynamicThumbnails bool, thumbnailSizes []config.ThumbnailSize, ) (*types.MediaMetadata, error) { @@ -325,7 +325,7 @@ func (r *downloadRequest) getThumbnailFile( filePath types.Path, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, - db *storage.Database, + db storage.Database, dynamicThumbnails bool, thumbnailSizes []config.ThumbnailSize, ) (*os.File, *types.ThumbnailMetadata, error) { @@ -407,7 +407,7 @@ func (r *downloadRequest) generateThumbnail( thumbnailSize types.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, - db *storage.Database, + db storage.Database, ) (*types.ThumbnailMetadata, error) { r.Logger.WithFields(log.Fields{ "Width": thumbnailSize.Width, @@ -443,7 +443,7 @@ func (r *downloadRequest) getRemoteFile( ctx context.Context, client *gomatrixserverlib.Client, cfg *config.Dendrite, - db *storage.Database, + db storage.Database, activeRemoteRequests *types.ActiveRemoteRequests, activeThumbnailGeneration *types.ActiveThumbnailGeneration, ) (errorResponse error) { @@ -545,7 +545,7 @@ func (r *downloadRequest) fetchRemoteFileAndStoreMetadata( client *gomatrixserverlib.Client, absBasePath config.Path, maxFileSizeBytes config.FileSizeBytes, - db *storage.Database, + db storage.Database, thumbnailSizes []config.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 0f2266640..dcc6ac06c 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -43,7 +43,7 @@ const pathPrefixR0 = "/_matrix/media/r0" func Setup( apiMux *mux.Router, cfg *config.Dendrite, - db *storage.Database, + db storage.Database, deviceDB *devices.Database, client *gomatrixserverlib.Client, ) { @@ -80,7 +80,7 @@ func Setup( func makeDownloadAPI( name string, cfg *config.Dendrite, - db *storage.Database, + db storage.Database, client *gomatrixserverlib.Client, activeRemoteRequests *types.ActiveRemoteRequests, activeThumbnailGeneration *types.ActiveThumbnailGeneration, diff --git a/mediaapi/routing/upload.go b/mediaapi/routing/upload.go index 2cb0d8757..91a453190 100644 --- a/mediaapi/routing/upload.go +++ b/mediaapi/routing/upload.go @@ -53,7 +53,7 @@ type uploadResponse struct { // This implementation supports a configurable maximum file size limit in bytes. If a user tries to upload more than this, they will receive an error that their upload is too large. // Uploaded files are processed piece-wise to avoid DoS attacks which would starve the server of memory. // TODO: We should time out requests if they have not received any data within a configured timeout period. -func Upload(req *http.Request, cfg *config.Dendrite, db *storage.Database, activeThumbnailGeneration *types.ActiveThumbnailGeneration) util.JSONResponse { +func Upload(req *http.Request, cfg *config.Dendrite, db storage.Database, activeThumbnailGeneration *types.ActiveThumbnailGeneration) util.JSONResponse { r, resErr := parseAndValidateRequest(req, cfg) if resErr != nil { return *resErr @@ -96,7 +96,7 @@ func (r *uploadRequest) doUpload( ctx context.Context, reqReader io.Reader, cfg *config.Dendrite, - db *storage.Database, + db storage.Database, activeThumbnailGeneration *types.ActiveThumbnailGeneration, ) *util.JSONResponse { r.Logger.WithFields(log.Fields{ @@ -214,7 +214,7 @@ func (r *uploadRequest) storeFileAndMetadata( ctx context.Context, tmpDir types.Path, absBasePath config.Path, - db *storage.Database, + db storage.Database, thumbnailSizes []config.ThumbnailSize, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, diff --git a/mediaapi/storage/media_repository_table.go b/mediaapi/storage/postgres/media_repository_table.go similarity index 97% rename from mediaapi/storage/media_repository_table.go rename to mediaapi/storage/postgres/media_repository_table.go index addd47b49..e975530ad 100644 --- a/mediaapi/storage/media_repository_table.go +++ b/mediaapi/storage/postgres/media_repository_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/mediaapi/storage/prepare.go b/mediaapi/storage/postgres/prepare.go similarity index 90% rename from mediaapi/storage/prepare.go rename to mediaapi/storage/postgres/prepare.go index a30586de4..090c3d17d 100644 --- a/mediaapi/storage/prepare.go +++ b/mediaapi/storage/postgres/prepare.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,7 +15,7 @@ // FIXME: This should be made common! -package storage +package postgres import ( "database/sql" diff --git a/mediaapi/storage/sql.go b/mediaapi/storage/postgres/sql.go similarity index 88% rename from mediaapi/storage/sql.go rename to mediaapi/storage/postgres/sql.go index 1f8c7be30..181cd15ff 100644 --- a/mediaapi/storage/sql.go +++ b/mediaapi/storage/postgres/sql.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "database/sql" diff --git a/mediaapi/storage/postgres/storage.go b/mediaapi/storage/postgres/storage.go new file mode 100644 index 000000000..6259f4a13 --- /dev/null +++ b/mediaapi/storage/postgres/storage.go @@ -0,0 +1,106 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + + // Import the postgres database driver. + _ "github.com/lib/pq" + "github.com/matrix-org/dendrite/mediaapi/types" + "github.com/matrix-org/gomatrixserverlib" +) + +// Database is used to store metadata about a repository of media files. +type Database struct { + statements statements + db *sql.DB +} + +// Open opens a postgres database. +func Open(dataSourceName string) (*Database, error) { + var d Database + var err error + if d.db, err = sql.Open("postgres", dataSourceName); err != nil { + return nil, err + } + if err = d.statements.prepare(d.db); err != nil { + return nil, err + } + return &d, nil +} + +// StoreMediaMetadata inserts the metadata about the uploaded media into the database. +// Returns an error if the combination of MediaID and Origin are not unique in the table. +func (d *Database) StoreMediaMetadata( + ctx context.Context, mediaMetadata *types.MediaMetadata, +) error { + return d.statements.media.insertMedia(ctx, mediaMetadata) +} + +// GetMediaMetadata returns metadata about media stored on this server. +// The media could have been uploaded to this server or fetched from another server and cached here. +// Returns nil metadata if there is no metadata associated with this media. +func (d *Database) GetMediaMetadata( + ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, +) (*types.MediaMetadata, error) { + mediaMetadata, err := d.statements.media.selectMedia(ctx, mediaID, mediaOrigin) + if err != nil && err == sql.ErrNoRows { + return nil, nil + } + return mediaMetadata, err +} + +// StoreThumbnail inserts the metadata about the thumbnail into the database. +// Returns an error if the combination of MediaID and Origin are not unique in the table. +func (d *Database) StoreThumbnail( + ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata, +) error { + return d.statements.thumbnail.insertThumbnail(ctx, thumbnailMetadata) +} + +// GetThumbnail returns metadata about a specific thumbnail. +// The media could have been uploaded to this server or fetched from another server and cached here. +// Returns nil metadata if there is no metadata associated with this thumbnail. +func (d *Database) GetThumbnail( + ctx context.Context, + mediaID types.MediaID, + mediaOrigin gomatrixserverlib.ServerName, + width, height int, + resizeMethod string, +) (*types.ThumbnailMetadata, error) { + thumbnailMetadata, err := d.statements.thumbnail.selectThumbnail( + ctx, mediaID, mediaOrigin, width, height, resizeMethod, + ) + if err != nil && err == sql.ErrNoRows { + return nil, nil + } + return thumbnailMetadata, err +} + +// GetThumbnails returns metadata about all thumbnails for a specific media stored on this server. +// The media could have been uploaded to this server or fetched from another server and cached here. +// Returns nil metadata if there are no thumbnails associated with this media. +func (d *Database) GetThumbnails( + ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, +) ([]*types.ThumbnailMetadata, error) { + thumbnails, err := d.statements.thumbnail.selectThumbnails(ctx, mediaID, mediaOrigin) + if err != nil && err == sql.ErrNoRows { + return nil, nil + } + return thumbnails, err +} diff --git a/mediaapi/storage/thumbnail_table.go b/mediaapi/storage/postgres/thumbnail_table.go similarity index 98% rename from mediaapi/storage/thumbnail_table.go rename to mediaapi/storage/postgres/thumbnail_table.go index f100485f8..167e37957 100644 --- a/mediaapi/storage/thumbnail_table.go +++ b/mediaapi/storage/postgres/thumbnail_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/mediaapi/storage/storage.go b/mediaapi/storage/storage.go index bef134a95..0f39c1d0c 100644 --- a/mediaapi/storage/storage.go +++ b/mediaapi/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,90 +16,32 @@ package storage import ( "context" - "database/sql" + "errors" + "net/url" - // Import the postgres database driver. - _ "github.com/lib/pq" + "github.com/matrix-org/dendrite/mediaapi/storage/postgres" "github.com/matrix-org/dendrite/mediaapi/types" "github.com/matrix-org/gomatrixserverlib" ) -// Database is used to store metadata about a repository of media files. -type Database struct { - statements statements - db *sql.DB +type Database interface { + StoreMediaMetadata(ctx context.Context, mediaMetadata *types.MediaMetadata) error + GetMediaMetadata(ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName) (*types.MediaMetadata, error) + StoreThumbnail(ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata) error + GetThumbnail(ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, width, height int, resizeMethod string) (*types.ThumbnailMetadata, error) + GetThumbnails(ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName) ([]*types.ThumbnailMetadata, error) } // Open opens a postgres database. -func Open(dataSourceName string) (*Database, error) { - var d Database - var err error - if d.db, err = sql.Open("postgres", dataSourceName); err != nil { +func Open(dataSourceName string) (Database, error) { + uri, err := url.Parse(dataSourceName) + if err != nil { return nil, err } - if err = d.statements.prepare(d.db); err != nil { - return nil, err + switch uri.Scheme { + case "postgres": + return postgres.Open(dataSourceName) + default: + return nil, errors.New("unknown schema") } - return &d, nil -} - -// StoreMediaMetadata inserts the metadata about the uploaded media into the database. -// Returns an error if the combination of MediaID and Origin are not unique in the table. -func (d *Database) StoreMediaMetadata( - ctx context.Context, mediaMetadata *types.MediaMetadata, -) error { - return d.statements.media.insertMedia(ctx, mediaMetadata) -} - -// GetMediaMetadata returns metadata about media stored on this server. -// The media could have been uploaded to this server or fetched from another server and cached here. -// Returns nil metadata if there is no metadata associated with this media. -func (d *Database) GetMediaMetadata( - ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, -) (*types.MediaMetadata, error) { - mediaMetadata, err := d.statements.media.selectMedia(ctx, mediaID, mediaOrigin) - if err != nil && err == sql.ErrNoRows { - return nil, nil - } - return mediaMetadata, err -} - -// StoreThumbnail inserts the metadata about the thumbnail into the database. -// Returns an error if the combination of MediaID and Origin are not unique in the table. -func (d *Database) StoreThumbnail( - ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata, -) error { - return d.statements.thumbnail.insertThumbnail(ctx, thumbnailMetadata) -} - -// GetThumbnail returns metadata about a specific thumbnail. -// The media could have been uploaded to this server or fetched from another server and cached here. -// Returns nil metadata if there is no metadata associated with this thumbnail. -func (d *Database) GetThumbnail( - ctx context.Context, - mediaID types.MediaID, - mediaOrigin gomatrixserverlib.ServerName, - width, height int, - resizeMethod string, -) (*types.ThumbnailMetadata, error) { - thumbnailMetadata, err := d.statements.thumbnail.selectThumbnail( - ctx, mediaID, mediaOrigin, width, height, resizeMethod, - ) - if err != nil && err == sql.ErrNoRows { - return nil, nil - } - return thumbnailMetadata, err -} - -// GetThumbnails returns metadata about all thumbnails for a specific media stored on this server. -// The media could have been uploaded to this server or fetched from another server and cached here. -// Returns nil metadata if there are no thumbnails associated with this media. -func (d *Database) GetThumbnails( - ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, -) ([]*types.ThumbnailMetadata, error) { - thumbnails, err := d.statements.thumbnail.selectThumbnails(ctx, mediaID, mediaOrigin) - if err != nil && err == sql.ErrNoRows { - return nil, nil - } - return thumbnails, err } diff --git a/mediaapi/thumbnailer/thumbnailer.go b/mediaapi/thumbnailer/thumbnailer.go index 61b66ebc6..ebf5138c5 100644 --- a/mediaapi/thumbnailer/thumbnailer.go +++ b/mediaapi/thumbnailer/thumbnailer.go @@ -136,7 +136,7 @@ func isThumbnailExists( dst types.Path, config types.ThumbnailSize, mediaMetadata *types.MediaMetadata, - db *storage.Database, + db storage.Database, logger *log.Entry, ) (bool, error) { thumbnailMetadata, err := db.GetThumbnail( diff --git a/mediaapi/thumbnailer/thumbnailer_nfnt.go b/mediaapi/thumbnailer/thumbnailer_nfnt.go index 5df6ce4be..4f1e98aa0 100644 --- a/mediaapi/thumbnailer/thumbnailer_nfnt.go +++ b/mediaapi/thumbnailer/thumbnailer_nfnt.go @@ -45,7 +45,7 @@ func GenerateThumbnails( mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, - db *storage.Database, + db storage.Database, logger *log.Entry, ) (busy bool, errorReturn error) { img, err := readFile(string(src)) @@ -78,7 +78,7 @@ func GenerateThumbnail( mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, - db *storage.Database, + db storage.Database, logger *log.Entry, ) (busy bool, errorReturn error) { img, err := readFile(string(src)) @@ -142,7 +142,7 @@ func createThumbnail( mediaMetadata *types.MediaMetadata, activeThumbnailGeneration *types.ActiveThumbnailGeneration, maxThumbnailGenerators int, - db *storage.Database, + db storage.Database, logger *log.Entry, ) (busy bool, errorReturn error) { logger = logger.WithFields(log.Fields{ diff --git a/publicroomsapi/consumers/roomserver.go b/publicroomsapi/consumers/roomserver.go index b7d42b111..9a817735a 100644 --- a/publicroomsapi/consumers/roomserver.go +++ b/publicroomsapi/consumers/roomserver.go @@ -29,7 +29,7 @@ import ( // OutputRoomEventConsumer consumes events that originated in the room server. type OutputRoomEventConsumer struct { roomServerConsumer *common.ContinualConsumer - db *storage.PublicRoomsServerDatabase + db storage.Database query api.RoomserverQueryAPI } @@ -37,7 +37,7 @@ type OutputRoomEventConsumer struct { func NewOutputRoomEventConsumer( cfg *config.Dendrite, kafkaConsumer sarama.Consumer, - store *storage.PublicRoomsServerDatabase, + store storage.Database, queryAPI api.RoomserverQueryAPI, ) *OutputRoomEventConsumer { consumer := common.ContinualConsumer{ diff --git a/publicroomsapi/directory/directory.go b/publicroomsapi/directory/directory.go index 626a1c153..889815498 100644 --- a/publicroomsapi/directory/directory.go +++ b/publicroomsapi/directory/directory.go @@ -30,7 +30,7 @@ type roomVisibility struct { // GetVisibility implements GET /directory/list/room/{roomID} func GetVisibility( - req *http.Request, publicRoomsDatabase *storage.PublicRoomsServerDatabase, + req *http.Request, publicRoomsDatabase storage.Database, roomID string, ) util.JSONResponse { isPublic, err := publicRoomsDatabase.GetRoomVisibility(req.Context(), roomID) @@ -54,7 +54,7 @@ func GetVisibility( // SetVisibility implements PUT /directory/list/room/{roomID} // TODO: Check if user has the power level to edit the room visibility func SetVisibility( - req *http.Request, publicRoomsDatabase *storage.PublicRoomsServerDatabase, + req *http.Request, publicRoomsDatabase storage.Database, roomID string, ) util.JSONResponse { var v roomVisibility diff --git a/publicroomsapi/directory/public_rooms.go b/publicroomsapi/directory/public_rooms.go index ef7b2662e..10aaa0700 100644 --- a/publicroomsapi/directory/public_rooms.go +++ b/publicroomsapi/directory/public_rooms.go @@ -44,7 +44,7 @@ type publicRoomRes struct { // GetPostPublicRooms implements GET and POST /publicRooms func GetPostPublicRooms( - req *http.Request, publicRoomDatabase *storage.PublicRoomsServerDatabase, + req *http.Request, publicRoomDatabase storage.Database, ) util.JSONResponse { var limit int16 var offset int64 diff --git a/publicroomsapi/routing/routing.go b/publicroomsapi/routing/routing.go index 422414bc2..3d2d2ac04 100644 --- a/publicroomsapi/routing/routing.go +++ b/publicroomsapi/routing/routing.go @@ -34,7 +34,7 @@ const pathPrefixR0 = "/_matrix/client/r0" // Due to Setup being used to call many other functions, a gocyclo nolint is // applied: // nolint: gocyclo -func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB *storage.PublicRoomsServerDatabase) { +func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB storage.Database) { r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() authData := auth.Data{ diff --git a/publicroomsapi/storage/prepare.go b/publicroomsapi/storage/postgres/prepare.go similarity index 90% rename from publicroomsapi/storage/prepare.go rename to publicroomsapi/storage/postgres/prepare.go index b19765992..70b6e5161 100644 --- a/publicroomsapi/storage/prepare.go +++ b/publicroomsapi/storage/postgres/prepare.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "database/sql" diff --git a/publicroomsapi/storage/public_rooms_table.go b/publicroomsapi/storage/postgres/public_rooms_table.go similarity index 98% rename from publicroomsapi/storage/public_rooms_table.go rename to publicroomsapi/storage/postgres/public_rooms_table.go index 5e1eb3e12..852afe770 100644 --- a/publicroomsapi/storage/public_rooms_table.go +++ b/publicroomsapi/storage/postgres/public_rooms_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/publicroomsapi/storage/postgres/storage.go b/publicroomsapi/storage/postgres/storage.go new file mode 100644 index 000000000..67b5efc37 --- /dev/null +++ b/publicroomsapi/storage/postgres/storage.go @@ -0,0 +1,253 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + "encoding/json" + + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/publicroomsapi/types" + + "github.com/matrix-org/gomatrixserverlib" +) + +// PublicRoomsServerDatabase represents a public rooms server database. +type PublicRoomsServerDatabase struct { + db *sql.DB + common.PartitionOffsetStatements + statements publicRoomsStatements +} + +type attributeValue interface{} + +// NewPublicRoomsServerDatabase creates a new public rooms server database. +func NewPublicRoomsServerDatabase(dataSourceName string) (*PublicRoomsServerDatabase, error) { + var db *sql.DB + var err error + if db, err = sql.Open("postgres", dataSourceName); err != nil { + return nil, err + } + partitions := common.PartitionOffsetStatements{} + if err = partitions.Prepare(db, "publicroomsapi"); err != nil { + return nil, err + } + statements := publicRoomsStatements{} + if err = statements.prepare(db); err != nil { + return nil, err + } + return &PublicRoomsServerDatabase{db, partitions, statements}, nil +} + +// GetRoomVisibility returns the room visibility as a boolean: true if the room +// is publicly visible, false if not. +// Returns an error if the retrieval failed. +func (d *PublicRoomsServerDatabase) GetRoomVisibility( + ctx context.Context, roomID string, +) (bool, error) { + return d.statements.selectRoomVisibility(ctx, roomID) +} + +// SetRoomVisibility updates the visibility attribute of a room. This attribute +// must be set to true if the room is publicly visible, false if not. +// Returns an error if the update failed. +func (d *PublicRoomsServerDatabase) SetRoomVisibility( + ctx context.Context, visible bool, roomID string, +) error { + return d.statements.updateRoomAttribute(ctx, "visibility", visible, roomID) +} + +// CountPublicRooms returns the number of room set as publicly visible on the server. +// Returns an error if the retrieval failed. +func (d *PublicRoomsServerDatabase) CountPublicRooms(ctx context.Context) (int64, error) { + return d.statements.countPublicRooms(ctx) +} + +// GetPublicRooms returns an array containing the local rooms set as publicly visible, ordered by their number +// of joined members. This array can be limited by a given number of elements, and offset by a given value. +// If the limit is 0, doesn't limit the number of results. If the offset is 0 too, the array contains all +// the rooms set as publicly visible on the server. +// Returns an error if the retrieval failed. +func (d *PublicRoomsServerDatabase) GetPublicRooms( + ctx context.Context, offset int64, limit int16, filter string, +) ([]types.PublicRoom, error) { + return d.statements.selectPublicRooms(ctx, offset, limit, filter) +} + +// UpdateRoomFromEvents iterate over a slice of state events and call +// UpdateRoomFromEvent on each of them to update the database representation of +// the rooms updated by each event. +// The slice of events to remove is used to update the number of joined members +// for the room in the database. +// If the update triggered by one of the events failed, aborts the process and +// returns an error. +func (d *PublicRoomsServerDatabase) UpdateRoomFromEvents( + ctx context.Context, + eventsToAdd []gomatrixserverlib.Event, + eventsToRemove []gomatrixserverlib.Event, +) error { + for _, event := range eventsToAdd { + if err := d.UpdateRoomFromEvent(ctx, event); err != nil { + return err + } + } + + for _, event := range eventsToRemove { + if event.Type() == "m.room.member" { + if err := d.updateNumJoinedUsers(ctx, event, true); err != nil { + return err + } + } + } + + return nil +} + +// UpdateRoomFromEvent updates the database representation of a room from a Matrix event, by +// checking the event's type to know which attribute to change and using the event's content +// to define the new value of the attribute. +// If the event doesn't match with any property used to compute the public room directory, +// does nothing. +// If something went wrong during the process, returns an error. +func (d *PublicRoomsServerDatabase) UpdateRoomFromEvent( + ctx context.Context, event gomatrixserverlib.Event, +) error { + // Process the event according to its type + switch event.Type() { + case "m.room.create": + return d.statements.insertNewRoom(ctx, event.RoomID()) + case "m.room.member": + return d.updateNumJoinedUsers(ctx, event, false) + case "m.room.aliases": + return d.updateRoomAliases(ctx, event) + case "m.room.canonical_alias": + var content common.CanonicalAliasContent + field := &(content.Alias) + attrName := "canonical_alias" + return d.updateStringAttribute(ctx, attrName, event, &content, field) + case "m.room.name": + var content common.NameContent + field := &(content.Name) + attrName := "name" + return d.updateStringAttribute(ctx, attrName, event, &content, field) + case "m.room.topic": + var content common.TopicContent + field := &(content.Topic) + attrName := "topic" + return d.updateStringAttribute(ctx, attrName, event, &content, field) + case "m.room.avatar": + var content common.AvatarContent + field := &(content.URL) + attrName := "avatar_url" + return d.updateStringAttribute(ctx, attrName, event, &content, field) + case "m.room.history_visibility": + var content common.HistoryVisibilityContent + field := &(content.HistoryVisibility) + attrName := "world_readable" + strForTrue := "world_readable" + return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue) + case "m.room.guest_access": + var content common.GuestAccessContent + field := &(content.GuestAccess) + attrName := "guest_can_join" + strForTrue := "can_join" + return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue) + } + + // If the event type didn't match, return with no error + return nil +} + +// updateNumJoinedUsers updates the number of joined user in the database representation +// of a room using a given "m.room.member" Matrix event. +// If the membership property of the event isn't "join", ignores it and returs nil. +// If the remove parameter is set to false, increments the joined members counter in the +// database, if set to truem decrements it. +// Returns an error if the update failed. +func (d *PublicRoomsServerDatabase) updateNumJoinedUsers( + ctx context.Context, membershipEvent gomatrixserverlib.Event, remove bool, +) error { + membership, err := membershipEvent.Membership() + if err != nil { + return err + } + + if membership != gomatrixserverlib.Join { + return nil + } + + if remove { + return d.statements.decrementJoinedMembersInRoom(ctx, membershipEvent.RoomID()) + } + return d.statements.incrementJoinedMembersInRoom(ctx, membershipEvent.RoomID()) +} + +// updateStringAttribute updates a given string attribute in the database +// representation of a room using a given string data field from content of the +// Matrix event triggering the update. +// Returns an error if decoding the Matrix event's content or updating the attribute +// failed. +func (d *PublicRoomsServerDatabase) updateStringAttribute( + ctx context.Context, attrName string, event gomatrixserverlib.Event, + content interface{}, field *string, +) error { + if err := json.Unmarshal(event.Content(), content); err != nil { + return err + } + + return d.statements.updateRoomAttribute(ctx, attrName, *field, event.RoomID()) +} + +// updateBooleanAttribute updates a given boolean attribute in the database +// representation of a room using a given string data field from content of the +// Matrix event triggering the update. +// The attribute is set to true if the field matches a given string, false if not. +// Returns an error if decoding the Matrix event's content or updating the attribute +// failed. +func (d *PublicRoomsServerDatabase) updateBooleanAttribute( + ctx context.Context, attrName string, event gomatrixserverlib.Event, + content interface{}, field *string, strForTrue string, +) error { + if err := json.Unmarshal(event.Content(), content); err != nil { + return err + } + + var attrValue bool + if *field == strForTrue { + attrValue = true + } else { + attrValue = false + } + + return d.statements.updateRoomAttribute(ctx, attrName, attrValue, event.RoomID()) +} + +// updateRoomAliases decodes the content of a "m.room.aliases" Matrix event and update the list of aliases of +// a given room with it. +// Returns an error if decoding the Matrix event or updating the list failed. +func (d *PublicRoomsServerDatabase) updateRoomAliases( + ctx context.Context, aliasesEvent gomatrixserverlib.Event, +) error { + var content common.AliasesContent + if err := json.Unmarshal(aliasesEvent.Content(), &content); err != nil { + return err + } + + return d.statements.updateRoomAttribute( + ctx, "aliases", content.Aliases, aliasesEvent.RoomID(), + ) +} diff --git a/publicroomsapi/storage/storage.go b/publicroomsapi/storage/storage.go index aa9806945..a6a39d523 100644 --- a/publicroomsapi/storage/storage.go +++ b/publicroomsapi/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,237 +16,35 @@ package storage import ( "context" - "database/sql" - "encoding/json" + "errors" + "net/url" "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/publicroomsapi/storage/postgres" "github.com/matrix-org/dendrite/publicroomsapi/types" - "github.com/matrix-org/gomatrixserverlib" ) -// PublicRoomsServerDatabase represents a public rooms server database. -type PublicRoomsServerDatabase struct { - db *sql.DB - common.PartitionOffsetStatements - statements publicRoomsStatements +type Database interface { + common.PartitionStorer + GetRoomVisibility(ctx context.Context, roomID string) (bool, error) + SetRoomVisibility(ctx context.Context, visible bool, roomID string) error + CountPublicRooms(ctx context.Context) (int64, error) + GetPublicRooms(ctx context.Context, offset int64, limit int16, filter string) ([]types.PublicRoom, error) + UpdateRoomFromEvents(ctx context.Context, eventsToAdd []gomatrixserverlib.Event, eventsToRemove []gomatrixserverlib.Event) error + UpdateRoomFromEvent(ctx context.Context, event gomatrixserverlib.Event) error } -type attributeValue interface{} - -// NewPublicRoomsServerDatabase creates a new public rooms server database. -func NewPublicRoomsServerDatabase(dataSourceName string) (*PublicRoomsServerDatabase, error) { - var db *sql.DB - var err error - if db, err = sql.Open("postgres", dataSourceName); err != nil { - return nil, err - } - partitions := common.PartitionOffsetStatements{} - if err = partitions.Prepare(db, "publicroomsapi"); err != nil { - return nil, err - } - statements := publicRoomsStatements{} - if err = statements.prepare(db); err != nil { - return nil, err - } - return &PublicRoomsServerDatabase{db, partitions, statements}, nil -} - -// GetRoomVisibility returns the room visibility as a boolean: true if the room -// is publicly visible, false if not. -// Returns an error if the retrieval failed. -func (d *PublicRoomsServerDatabase) GetRoomVisibility( - ctx context.Context, roomID string, -) (bool, error) { - return d.statements.selectRoomVisibility(ctx, roomID) -} - -// SetRoomVisibility updates the visibility attribute of a room. This attribute -// must be set to true if the room is publicly visible, false if not. -// Returns an error if the update failed. -func (d *PublicRoomsServerDatabase) SetRoomVisibility( - ctx context.Context, visible bool, roomID string, -) error { - return d.statements.updateRoomAttribute(ctx, "visibility", visible, roomID) -} - -// CountPublicRooms returns the number of room set as publicly visible on the server. -// Returns an error if the retrieval failed. -func (d *PublicRoomsServerDatabase) CountPublicRooms(ctx context.Context) (int64, error) { - return d.statements.countPublicRooms(ctx) -} - -// GetPublicRooms returns an array containing the local rooms set as publicly visible, ordered by their number -// of joined members. This array can be limited by a given number of elements, and offset by a given value. -// If the limit is 0, doesn't limit the number of results. If the offset is 0 too, the array contains all -// the rooms set as publicly visible on the server. -// Returns an error if the retrieval failed. -func (d *PublicRoomsServerDatabase) GetPublicRooms( - ctx context.Context, offset int64, limit int16, filter string, -) ([]types.PublicRoom, error) { - return d.statements.selectPublicRooms(ctx, offset, limit, filter) -} - -// UpdateRoomFromEvents iterate over a slice of state events and call -// UpdateRoomFromEvent on each of them to update the database representation of -// the rooms updated by each event. -// The slice of events to remove is used to update the number of joined members -// for the room in the database. -// If the update triggered by one of the events failed, aborts the process and -// returns an error. -func (d *PublicRoomsServerDatabase) UpdateRoomFromEvents( - ctx context.Context, - eventsToAdd []gomatrixserverlib.Event, - eventsToRemove []gomatrixserverlib.Event, -) error { - for _, event := range eventsToAdd { - if err := d.UpdateRoomFromEvent(ctx, event); err != nil { - return err - } - } - - for _, event := range eventsToRemove { - if event.Type() == "m.room.member" { - if err := d.updateNumJoinedUsers(ctx, event, true); err != nil { - return err - } - } - } - - return nil -} - -// UpdateRoomFromEvent updates the database representation of a room from a Matrix event, by -// checking the event's type to know which attribute to change and using the event's content -// to define the new value of the attribute. -// If the event doesn't match with any property used to compute the public room directory, -// does nothing. -// If something went wrong during the process, returns an error. -func (d *PublicRoomsServerDatabase) UpdateRoomFromEvent( - ctx context.Context, event gomatrixserverlib.Event, -) error { - // Process the event according to its type - switch event.Type() { - case "m.room.create": - return d.statements.insertNewRoom(ctx, event.RoomID()) - case "m.room.member": - return d.updateNumJoinedUsers(ctx, event, false) - case "m.room.aliases": - return d.updateRoomAliases(ctx, event) - case "m.room.canonical_alias": - var content common.CanonicalAliasContent - field := &(content.Alias) - attrName := "canonical_alias" - return d.updateStringAttribute(ctx, attrName, event, &content, field) - case "m.room.name": - var content common.NameContent - field := &(content.Name) - attrName := "name" - return d.updateStringAttribute(ctx, attrName, event, &content, field) - case "m.room.topic": - var content common.TopicContent - field := &(content.Topic) - attrName := "topic" - return d.updateStringAttribute(ctx, attrName, event, &content, field) - case "m.room.avatar": - var content common.AvatarContent - field := &(content.URL) - attrName := "avatar_url" - return d.updateStringAttribute(ctx, attrName, event, &content, field) - case "m.room.history_visibility": - var content common.HistoryVisibilityContent - field := &(content.HistoryVisibility) - attrName := "world_readable" - strForTrue := "world_readable" - return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue) - case "m.room.guest_access": - var content common.GuestAccessContent - field := &(content.GuestAccess) - attrName := "guest_can_join" - strForTrue := "can_join" - return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue) - } - - // If the event type didn't match, return with no error - return nil -} - -// updateNumJoinedUsers updates the number of joined user in the database representation -// of a room using a given "m.room.member" Matrix event. -// If the membership property of the event isn't "join", ignores it and returs nil. -// If the remove parameter is set to false, increments the joined members counter in the -// database, if set to truem decrements it. -// Returns an error if the update failed. -func (d *PublicRoomsServerDatabase) updateNumJoinedUsers( - ctx context.Context, membershipEvent gomatrixserverlib.Event, remove bool, -) error { - membership, err := membershipEvent.Membership() +// NewPublicRoomsServerDatabase opens a database connection. +func NewPublicRoomsServerDatabase(dataSourceName string) (Database, error) { + uri, err := url.Parse(dataSourceName) if err != nil { - return err + return nil, err } - - if membership != gomatrixserverlib.Join { - return nil + switch uri.Scheme { + case "postgres": + return postgres.NewPublicRoomsServerDatabase(dataSourceName) + default: + return nil, errors.New("unknown schema") } - - if remove { - return d.statements.decrementJoinedMembersInRoom(ctx, membershipEvent.RoomID()) - } - return d.statements.incrementJoinedMembersInRoom(ctx, membershipEvent.RoomID()) -} - -// updateStringAttribute updates a given string attribute in the database -// representation of a room using a given string data field from content of the -// Matrix event triggering the update. -// Returns an error if decoding the Matrix event's content or updating the attribute -// failed. -func (d *PublicRoomsServerDatabase) updateStringAttribute( - ctx context.Context, attrName string, event gomatrixserverlib.Event, - content interface{}, field *string, -) error { - if err := json.Unmarshal(event.Content(), content); err != nil { - return err - } - - return d.statements.updateRoomAttribute(ctx, attrName, *field, event.RoomID()) -} - -// updateBooleanAttribute updates a given boolean attribute in the database -// representation of a room using a given string data field from content of the -// Matrix event triggering the update. -// The attribute is set to true if the field matches a given string, false if not. -// Returns an error if decoding the Matrix event's content or updating the attribute -// failed. -func (d *PublicRoomsServerDatabase) updateBooleanAttribute( - ctx context.Context, attrName string, event gomatrixserverlib.Event, - content interface{}, field *string, strForTrue string, -) error { - if err := json.Unmarshal(event.Content(), content); err != nil { - return err - } - - var attrValue bool - if *field == strForTrue { - attrValue = true - } else { - attrValue = false - } - - return d.statements.updateRoomAttribute(ctx, attrName, attrValue, event.RoomID()) -} - -// updateRoomAliases decodes the content of a "m.room.aliases" Matrix event and update the list of aliases of -// a given room with it. -// Returns an error if decoding the Matrix event or updating the list failed. -func (d *PublicRoomsServerDatabase) updateRoomAliases( - ctx context.Context, aliasesEvent gomatrixserverlib.Event, -) error { - var content common.AliasesContent - if err := json.Unmarshal(aliasesEvent.Content(), &content); err != nil { - return err - } - - return d.statements.updateRoomAttribute( - ctx, "aliases", content.Aliases, aliasesEvent.RoomID(), - ) } diff --git a/roomserver/storage/event_json_table.go b/roomserver/storage/postgres/event_json_table.go similarity index 96% rename from roomserver/storage/event_json_table.go rename to roomserver/storage/postgres/event_json_table.go index b81667d9d..415fb84eb 100644 --- a/roomserver/storage/event_json_table.go +++ b/roomserver/storage/postgres/event_json_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/event_state_keys_table.go b/roomserver/storage/postgres/event_state_keys_table.go similarity index 98% rename from roomserver/storage/event_state_keys_table.go rename to roomserver/storage/postgres/event_state_keys_table.go index 1ef933704..c3aaa498e 100644 --- a/roomserver/storage/event_state_keys_table.go +++ b/roomserver/storage/postgres/event_state_keys_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/event_types_table.go b/roomserver/storage/postgres/event_types_table.go similarity index 98% rename from roomserver/storage/event_types_table.go rename to roomserver/storage/postgres/event_types_table.go index 7b8d53a5d..1ec2e7cde 100644 --- a/roomserver/storage/event_types_table.go +++ b/roomserver/storage/postgres/event_types_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/events_table.go b/roomserver/storage/postgres/events_table.go similarity index 99% rename from roomserver/storage/events_table.go rename to roomserver/storage/postgres/events_table.go index 5bad939fa..1e8a5665b 100644 --- a/roomserver/storage/events_table.go +++ b/roomserver/storage/postgres/events_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/invite_table.go b/roomserver/storage/postgres/invite_table.go similarity index 97% rename from roomserver/storage/invite_table.go rename to roomserver/storage/postgres/invite_table.go index 4f9cdfb41..43cd5ba09 100644 --- a/roomserver/storage/invite_table.go +++ b/roomserver/storage/postgres/invite_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/membership_table.go b/roomserver/storage/postgres/membership_table.go similarity index 98% rename from roomserver/storage/membership_table.go rename to roomserver/storage/postgres/membership_table.go index 88a9ed725..9f41fd67b 100644 --- a/roomserver/storage/membership_table.go +++ b/roomserver/storage/postgres/membership_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/prepare.go b/roomserver/storage/postgres/prepare.go similarity index 90% rename from roomserver/storage/prepare.go rename to roomserver/storage/postgres/prepare.go index b19765992..70b6e5161 100644 --- a/roomserver/storage/prepare.go +++ b/roomserver/storage/postgres/prepare.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "database/sql" diff --git a/roomserver/storage/previous_events_table.go b/roomserver/storage/postgres/previous_events_table.go similarity index 97% rename from roomserver/storage/previous_events_table.go rename to roomserver/storage/postgres/previous_events_table.go index 81d581a96..4c21b3081 100644 --- a/roomserver/storage/previous_events_table.go +++ b/roomserver/storage/postgres/previous_events_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/room_aliases_table.go b/roomserver/storage/postgres/room_aliases_table.go similarity index 96% rename from roomserver/storage/room_aliases_table.go rename to roomserver/storage/postgres/room_aliases_table.go index 3ed20e8e3..ad1b560c2 100644 --- a/roomserver/storage/room_aliases_table.go +++ b/roomserver/storage/postgres/room_aliases_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/rooms_table.go b/roomserver/storage/postgres/rooms_table.go similarity index 97% rename from roomserver/storage/rooms_table.go rename to roomserver/storage/postgres/rooms_table.go index 64193ffee..ccc201b18 100644 --- a/roomserver/storage/rooms_table.go +++ b/roomserver/storage/postgres/rooms_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/sql.go b/roomserver/storage/postgres/sql.go similarity index 92% rename from roomserver/storage/sql.go rename to roomserver/storage/postgres/sql.go index 05efa8dd4..5956886ce 100644 --- a/roomserver/storage/sql.go +++ b/roomserver/storage/postgres/sql.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "database/sql" diff --git a/roomserver/storage/state_block_table.go b/roomserver/storage/postgres/state_block_table.go similarity index 98% rename from roomserver/storage/state_block_table.go rename to roomserver/storage/postgres/state_block_table.go index b2e8ef8a6..15e69cc98 100644 --- a/roomserver/storage/state_block_table.go +++ b/roomserver/storage/postgres/state_block_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/state_block_table_test.go b/roomserver/storage/postgres/state_block_table_test.go similarity index 95% rename from roomserver/storage/state_block_table_test.go rename to roomserver/storage/postgres/state_block_table_test.go index f891b5bc8..a0e2ec952 100644 --- a/roomserver/storage/state_block_table_test.go +++ b/roomserver/storage/postgres/state_block_table_test.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "sort" diff --git a/roomserver/storage/state_snapshot_table.go b/roomserver/storage/postgres/state_snapshot_table.go similarity index 97% rename from roomserver/storage/state_snapshot_table.go rename to roomserver/storage/postgres/state_snapshot_table.go index aa14daad7..76f1d2b66 100644 --- a/roomserver/storage/state_snapshot_table.go +++ b/roomserver/storage/postgres/state_snapshot_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/postgres/storage.go b/roomserver/storage/postgres/storage.go new file mode 100644 index 000000000..93450e5a5 --- /dev/null +++ b/roomserver/storage/postgres/storage.go @@ -0,0 +1,713 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + + // Import the postgres database driver. + _ "github.com/lib/pq" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/types" + "github.com/matrix-org/gomatrixserverlib" +) + +// A Database is used to store room events and stream offsets. +type Database struct { + statements statements + db *sql.DB +} + +// Open a postgres database. +func Open(dataSourceName string) (*Database, error) { + var d Database + var err error + if d.db, err = sql.Open("postgres", dataSourceName); err != nil { + return nil, err + } + if err = d.statements.prepare(d.db); err != nil { + return nil, err + } + return &d, nil +} + +// StoreEvent implements input.EventDatabase +func (d *Database) StoreEvent( + ctx context.Context, event gomatrixserverlib.Event, + txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID, +) (types.RoomNID, types.StateAtEvent, error) { + var ( + roomNID types.RoomNID + eventTypeNID types.EventTypeNID + eventStateKeyNID types.EventStateKeyNID + eventNID types.EventNID + stateNID types.StateSnapshotNID + err error + ) + + if txnAndSessionID != nil { + if err = d.statements.insertTransaction( + ctx, txnAndSessionID.TransactionID, + txnAndSessionID.SessionID, event.Sender(), event.EventID(), + ); err != nil { + return 0, types.StateAtEvent{}, err + } + } + + if roomNID, err = d.assignRoomNID(ctx, nil, event.RoomID()); err != nil { + return 0, types.StateAtEvent{}, err + } + + if eventTypeNID, err = d.assignEventTypeNID(ctx, event.Type()); err != nil { + return 0, types.StateAtEvent{}, err + } + + eventStateKey := event.StateKey() + // Assigned a numeric ID for the state_key if there is one present. + // Otherwise set the numeric ID for the state_key to 0. + if eventStateKey != nil { + if eventStateKeyNID, err = d.assignStateKeyNID(ctx, nil, *eventStateKey); err != nil { + return 0, types.StateAtEvent{}, err + } + } + + if eventNID, stateNID, err = d.statements.insertEvent( + ctx, + roomNID, + eventTypeNID, + eventStateKeyNID, + event.EventID(), + event.EventReference().EventSHA256, + authEventNIDs, + event.Depth(), + ); err != nil { + if err == sql.ErrNoRows { + // We've already inserted the event so select the numeric event ID + eventNID, stateNID, err = d.statements.selectEvent(ctx, event.EventID()) + } + if err != nil { + return 0, types.StateAtEvent{}, err + } + } + + if err = d.statements.insertEventJSON(ctx, eventNID, event.JSON()); err != nil { + return 0, types.StateAtEvent{}, err + } + + return roomNID, types.StateAtEvent{ + BeforeStateSnapshotNID: stateNID, + StateEntry: types.StateEntry{ + StateKeyTuple: types.StateKeyTuple{ + EventTypeNID: eventTypeNID, + EventStateKeyNID: eventStateKeyNID, + }, + EventNID: eventNID, + }, + }, nil +} + +func (d *Database) assignRoomNID( + ctx context.Context, txn *sql.Tx, roomID string, +) (types.RoomNID, error) { + // Check if we already have a numeric ID in the database. + roomNID, err := d.statements.selectRoomNID(ctx, txn, roomID) + if err == sql.ErrNoRows { + // We don't have a numeric ID so insert one into the database. + roomNID, err = d.statements.insertRoomNID(ctx, txn, roomID) + if err == sql.ErrNoRows { + // We raced with another insert so run the select again. + roomNID, err = d.statements.selectRoomNID(ctx, txn, roomID) + } + } + return roomNID, err +} + +func (d *Database) assignEventTypeNID( + ctx context.Context, eventType string, +) (types.EventTypeNID, error) { + // Check if we already have a numeric ID in the database. + eventTypeNID, err := d.statements.selectEventTypeNID(ctx, eventType) + if err == sql.ErrNoRows { + // We don't have a numeric ID so insert one into the database. + eventTypeNID, err = d.statements.insertEventTypeNID(ctx, eventType) + if err == sql.ErrNoRows { + // We raced with another insert so run the select again. + eventTypeNID, err = d.statements.selectEventTypeNID(ctx, eventType) + } + } + return eventTypeNID, err +} + +func (d *Database) assignStateKeyNID( + ctx context.Context, txn *sql.Tx, eventStateKey string, +) (types.EventStateKeyNID, error) { + // Check if we already have a numeric ID in the database. + eventStateKeyNID, err := d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey) + if err == sql.ErrNoRows { + // We don't have a numeric ID so insert one into the database. + eventStateKeyNID, err = d.statements.insertEventStateKeyNID(ctx, txn, eventStateKey) + if err == sql.ErrNoRows { + // We raced with another insert so run the select again. + eventStateKeyNID, err = d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey) + } + } + return eventStateKeyNID, err +} + +// StateEntriesForEventIDs implements input.EventDatabase +func (d *Database) StateEntriesForEventIDs( + ctx context.Context, eventIDs []string, +) ([]types.StateEntry, error) { + return d.statements.bulkSelectStateEventByID(ctx, eventIDs) +} + +// EventTypeNIDs implements state.RoomStateDatabase +func (d *Database) EventTypeNIDs( + ctx context.Context, eventTypes []string, +) (map[string]types.EventTypeNID, error) { + return d.statements.bulkSelectEventTypeNID(ctx, eventTypes) +} + +// EventStateKeyNIDs implements state.RoomStateDatabase +func (d *Database) EventStateKeyNIDs( + ctx context.Context, eventStateKeys []string, +) (map[string]types.EventStateKeyNID, error) { + return d.statements.bulkSelectEventStateKeyNID(ctx, eventStateKeys) +} + +// EventStateKeys implements query.RoomserverQueryAPIDatabase +func (d *Database) EventStateKeys( + ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID, +) (map[types.EventStateKeyNID]string, error) { + return d.statements.bulkSelectEventStateKey(ctx, eventStateKeyNIDs) +} + +// EventNIDs implements query.RoomserverQueryAPIDatabase +func (d *Database) EventNIDs( + ctx context.Context, eventIDs []string, +) (map[string]types.EventNID, error) { + return d.statements.bulkSelectEventNID(ctx, eventIDs) +} + +// Events implements input.EventDatabase +func (d *Database) Events( + ctx context.Context, eventNIDs []types.EventNID, +) ([]types.Event, error) { + eventJSONs, err := d.statements.bulkSelectEventJSON(ctx, eventNIDs) + if err != nil { + return nil, err + } + results := make([]types.Event, len(eventJSONs)) + for i, eventJSON := range eventJSONs { + result := &results[i] + result.EventNID = eventJSON.EventNID + // TODO: Use NewEventFromTrustedJSON for efficiency + result.Event, err = gomatrixserverlib.NewEventFromUntrustedJSON(eventJSON.EventJSON) + if err != nil { + return nil, err + } + } + return results, nil +} + +// AddState implements input.EventDatabase +func (d *Database) AddState( + ctx context.Context, + roomNID types.RoomNID, + stateBlockNIDs []types.StateBlockNID, + state []types.StateEntry, +) (types.StateSnapshotNID, error) { + if len(state) > 0 { + stateBlockNID, err := d.statements.selectNextStateBlockNID(ctx) + if err != nil { + return 0, err + } + if err = d.statements.bulkInsertStateData(ctx, stateBlockNID, state); err != nil { + return 0, err + } + stateBlockNIDs = append(stateBlockNIDs[:len(stateBlockNIDs):len(stateBlockNIDs)], stateBlockNID) + } + + return d.statements.insertState(ctx, roomNID, stateBlockNIDs) +} + +// SetState implements input.EventDatabase +func (d *Database) SetState( + ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID, +) error { + return d.statements.updateEventState(ctx, eventNID, stateNID) +} + +// StateAtEventIDs implements input.EventDatabase +func (d *Database) StateAtEventIDs( + ctx context.Context, eventIDs []string, +) ([]types.StateAtEvent, error) { + return d.statements.bulkSelectStateAtEventByID(ctx, eventIDs) +} + +// StateBlockNIDs implements state.RoomStateDatabase +func (d *Database) StateBlockNIDs( + ctx context.Context, stateNIDs []types.StateSnapshotNID, +) ([]types.StateBlockNIDList, error) { + return d.statements.bulkSelectStateBlockNIDs(ctx, stateNIDs) +} + +// StateEntries implements state.RoomStateDatabase +func (d *Database) StateEntries( + ctx context.Context, stateBlockNIDs []types.StateBlockNID, +) ([]types.StateEntryList, error) { + return d.statements.bulkSelectStateBlockEntries(ctx, stateBlockNIDs) +} + +// SnapshotNIDFromEventID implements state.RoomStateDatabase +func (d *Database) SnapshotNIDFromEventID( + ctx context.Context, eventID string, +) (types.StateSnapshotNID, error) { + _, stateNID, err := d.statements.selectEvent(ctx, eventID) + return stateNID, err +} + +// EventIDs implements input.RoomEventDatabase +func (d *Database) EventIDs( + ctx context.Context, eventNIDs []types.EventNID, +) (map[types.EventNID]string, error) { + return d.statements.bulkSelectEventID(ctx, eventNIDs) +} + +// GetLatestEventsForUpdate implements input.EventDatabase +func (d *Database) GetLatestEventsForUpdate( + ctx context.Context, roomNID types.RoomNID, +) (types.RoomRecentEventsUpdater, error) { + txn, err := d.db.Begin() + if err != nil { + return nil, err + } + eventNIDs, lastEventNIDSent, currentStateSnapshotNID, err := + d.statements.selectLatestEventsNIDsForUpdate(ctx, txn, roomNID) + if err != nil { + txn.Rollback() // nolint: errcheck + return nil, err + } + stateAndRefs, err := d.statements.bulkSelectStateAtEventAndReference(ctx, txn, eventNIDs) + if err != nil { + txn.Rollback() // nolint: errcheck + return nil, err + } + var lastEventIDSent string + if lastEventNIDSent != 0 { + lastEventIDSent, err = d.statements.selectEventID(ctx, txn, lastEventNIDSent) + if err != nil { + txn.Rollback() // nolint: errcheck + return nil, err + } + } + return &roomRecentEventsUpdater{ + transaction{ctx, txn}, d, roomNID, stateAndRefs, lastEventIDSent, currentStateSnapshotNID, + }, nil +} + +// GetTransactionEventID implements input.EventDatabase +func (d *Database) GetTransactionEventID( + ctx context.Context, transactionID string, + sessionID int64, userID string, +) (string, error) { + eventID, err := d.statements.selectTransactionEventID(ctx, transactionID, sessionID, userID) + if err == sql.ErrNoRows { + return "", nil + } + return eventID, err +} + +type roomRecentEventsUpdater struct { + transaction + d *Database + roomNID types.RoomNID + latestEvents []types.StateAtEventAndReference + lastEventIDSent string + currentStateSnapshotNID types.StateSnapshotNID +} + +// LatestEvents implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) LatestEvents() []types.StateAtEventAndReference { + return u.latestEvents +} + +// LastEventIDSent implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) LastEventIDSent() string { + return u.lastEventIDSent +} + +// CurrentStateSnapshotNID implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) CurrentStateSnapshotNID() types.StateSnapshotNID { + return u.currentStateSnapshotNID +} + +// StorePreviousEvents implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) StorePreviousEvents(eventNID types.EventNID, previousEventReferences []gomatrixserverlib.EventReference) error { + for _, ref := range previousEventReferences { + if err := u.d.statements.insertPreviousEvent(u.ctx, u.txn, ref.EventID, ref.EventSHA256, eventNID); err != nil { + return err + } + } + return nil +} + +// IsReferenced implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) IsReferenced(eventReference gomatrixserverlib.EventReference) (bool, error) { + err := u.d.statements.selectPreviousEventExists(u.ctx, u.txn, eventReference.EventID, eventReference.EventSHA256) + if err == nil { + return true, nil + } + if err == sql.ErrNoRows { + return false, nil + } + return false, err +} + +// SetLatestEvents implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) SetLatestEvents( + roomNID types.RoomNID, latest []types.StateAtEventAndReference, lastEventNIDSent types.EventNID, + currentStateSnapshotNID types.StateSnapshotNID, +) error { + eventNIDs := make([]types.EventNID, len(latest)) + for i := range latest { + eventNIDs[i] = latest[i].EventNID + } + return u.d.statements.updateLatestEventNIDs(u.ctx, u.txn, roomNID, eventNIDs, lastEventNIDSent, currentStateSnapshotNID) +} + +// HasEventBeenSent implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) HasEventBeenSent(eventNID types.EventNID) (bool, error) { + return u.d.statements.selectEventSentToOutput(u.ctx, u.txn, eventNID) +} + +// MarkEventAsSent implements types.RoomRecentEventsUpdater +func (u *roomRecentEventsUpdater) MarkEventAsSent(eventNID types.EventNID) error { + return u.d.statements.updateEventSentToOutput(u.ctx, u.txn, eventNID) +} + +func (u *roomRecentEventsUpdater) MembershipUpdater(targetUserNID types.EventStateKeyNID) (types.MembershipUpdater, error) { + return u.d.membershipUpdaterTxn(u.ctx, u.txn, u.roomNID, targetUserNID) +} + +// RoomNID implements query.RoomserverQueryAPIDB +func (d *Database) RoomNID(ctx context.Context, roomID string) (types.RoomNID, error) { + roomNID, err := d.statements.selectRoomNID(ctx, nil, roomID) + if err == sql.ErrNoRows { + return 0, nil + } + return roomNID, err +} + +// LatestEventIDs implements query.RoomserverQueryAPIDatabase +func (d *Database) LatestEventIDs( + ctx context.Context, roomNID types.RoomNID, +) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, int64, error) { + eventNIDs, currentStateSnapshotNID, err := d.statements.selectLatestEventNIDs(ctx, roomNID) + if err != nil { + return nil, 0, 0, err + } + references, err := d.statements.bulkSelectEventReference(ctx, eventNIDs) + if err != nil { + return nil, 0, 0, err + } + depth, err := d.statements.selectMaxEventDepth(ctx, eventNIDs) + if err != nil { + return nil, 0, 0, err + } + return references, currentStateSnapshotNID, depth, nil +} + +// GetInvitesForUser implements query.RoomserverQueryAPIDatabase +func (d *Database) GetInvitesForUser( + ctx context.Context, + roomNID types.RoomNID, + targetUserNID types.EventStateKeyNID, +) (senderUserIDs []types.EventStateKeyNID, err error) { + return d.statements.selectInviteActiveForUserInRoom(ctx, targetUserNID, roomNID) +} + +// SetRoomAlias implements alias.RoomserverAliasAPIDB +func (d *Database) SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error { + return d.statements.insertRoomAlias(ctx, alias, roomID, creatorUserID) +} + +// GetRoomIDForAlias implements alias.RoomserverAliasAPIDB +func (d *Database) GetRoomIDForAlias(ctx context.Context, alias string) (string, error) { + return d.statements.selectRoomIDFromAlias(ctx, alias) +} + +// GetAliasesForRoomID implements alias.RoomserverAliasAPIDB +func (d *Database) GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error) { + return d.statements.selectAliasesFromRoomID(ctx, roomID) +} + +// GetCreatorIDForAlias implements alias.RoomserverAliasAPIDB +func (d *Database) GetCreatorIDForAlias( + ctx context.Context, alias string, +) (string, error) { + return d.statements.selectCreatorIDFromAlias(ctx, alias) +} + +// RemoveRoomAlias implements alias.RoomserverAliasAPIDB +func (d *Database) RemoveRoomAlias(ctx context.Context, alias string) error { + return d.statements.deleteRoomAlias(ctx, alias) +} + +// StateEntriesForTuples implements state.RoomStateDatabase +func (d *Database) StateEntriesForTuples( + ctx context.Context, + stateBlockNIDs []types.StateBlockNID, + stateKeyTuples []types.StateKeyTuple, +) ([]types.StateEntryList, error) { + return d.statements.bulkSelectFilteredStateBlockEntries( + ctx, stateBlockNIDs, stateKeyTuples, + ) +} + +// MembershipUpdater implements input.RoomEventDatabase +func (d *Database) MembershipUpdater( + ctx context.Context, roomID, targetUserID string, +) (types.MembershipUpdater, error) { + txn, err := d.db.Begin() + if err != nil { + return nil, err + } + succeeded := false + defer func() { + if !succeeded { + txn.Rollback() // nolint: errcheck + } + }() + + roomNID, err := d.assignRoomNID(ctx, txn, roomID) + if err != nil { + return nil, err + } + + targetUserNID, err := d.assignStateKeyNID(ctx, txn, targetUserID) + if err != nil { + return nil, err + } + + updater, err := d.membershipUpdaterTxn(ctx, txn, roomNID, targetUserNID) + if err != nil { + return nil, err + } + + succeeded = true + return updater, nil +} + +type membershipUpdater struct { + transaction + d *Database + roomNID types.RoomNID + targetUserNID types.EventStateKeyNID + membership membershipState +} + +func (d *Database) membershipUpdaterTxn( + ctx context.Context, + txn *sql.Tx, + roomNID types.RoomNID, + targetUserNID types.EventStateKeyNID, +) (types.MembershipUpdater, error) { + + if err := d.statements.insertMembership(ctx, txn, roomNID, targetUserNID); err != nil { + return nil, err + } + + membership, err := d.statements.selectMembershipForUpdate(ctx, txn, roomNID, targetUserNID) + if err != nil { + return nil, err + } + + return &membershipUpdater{ + transaction{ctx, txn}, d, roomNID, targetUserNID, membership, + }, nil +} + +// IsInvite implements types.MembershipUpdater +func (u *membershipUpdater) IsInvite() bool { + return u.membership == membershipStateInvite +} + +// IsJoin implements types.MembershipUpdater +func (u *membershipUpdater) IsJoin() bool { + return u.membership == membershipStateJoin +} + +// IsLeave implements types.MembershipUpdater +func (u *membershipUpdater) IsLeave() bool { + return u.membership == membershipStateLeaveOrBan +} + +// SetToInvite implements types.MembershipUpdater +func (u *membershipUpdater) SetToInvite(event gomatrixserverlib.Event) (bool, error) { + senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, event.Sender()) + if err != nil { + return false, err + } + inserted, err := u.d.statements.insertInviteEvent( + u.ctx, u.txn, event.EventID(), u.roomNID, u.targetUserNID, senderUserNID, event.JSON(), + ) + if err != nil { + return false, err + } + if u.membership != membershipStateInvite { + if err = u.d.statements.updateMembership( + u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateInvite, 0, + ); err != nil { + return false, err + } + } + return inserted, nil +} + +// SetToJoin implements types.MembershipUpdater +func (u *membershipUpdater) SetToJoin(senderUserID string, eventID string, isUpdate bool) ([]string, error) { + var inviteEventIDs []string + + senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID) + if err != nil { + return nil, err + } + + // If this is a join event update, there is no invite to update + if !isUpdate { + inviteEventIDs, err = u.d.statements.updateInviteRetired( + u.ctx, u.txn, u.roomNID, u.targetUserNID, + ) + if err != nil { + return nil, err + } + } + + // Look up the NID of the new join event + nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID}) + if err != nil { + return nil, err + } + + if u.membership != membershipStateJoin || isUpdate { + if err = u.d.statements.updateMembership( + u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, + membershipStateJoin, nIDs[eventID], + ); err != nil { + return nil, err + } + } + + return inviteEventIDs, nil +} + +// SetToLeave implements types.MembershipUpdater +func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]string, error) { + senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID) + if err != nil { + return nil, err + } + inviteEventIDs, err := u.d.statements.updateInviteRetired( + u.ctx, u.txn, u.roomNID, u.targetUserNID, + ) + if err != nil { + return nil, err + } + + // Look up the NID of the new leave event + nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID}) + if err != nil { + return nil, err + } + + if u.membership != membershipStateLeaveOrBan { + if err = u.d.statements.updateMembership( + u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, + membershipStateLeaveOrBan, nIDs[eventID], + ); err != nil { + return nil, err + } + } + return inviteEventIDs, nil +} + +// GetMembership implements query.RoomserverQueryAPIDB +func (d *Database) GetMembership( + ctx context.Context, roomNID types.RoomNID, requestSenderUserID string, +) (membershipEventNID types.EventNID, stillInRoom bool, err error) { + requestSenderUserNID, err := d.assignStateKeyNID(ctx, nil, requestSenderUserID) + if err != nil { + return + } + + senderMembershipEventNID, senderMembership, err := + d.statements.selectMembershipFromRoomAndTarget( + ctx, roomNID, requestSenderUserNID, + ) + if err == sql.ErrNoRows { + // The user has never been a member of that room + return 0, false, nil + } else if err != nil { + return + } + + return senderMembershipEventNID, senderMembership == membershipStateJoin, nil +} + +// GetMembershipEventNIDsForRoom implements query.RoomserverQueryAPIDB +func (d *Database) GetMembershipEventNIDsForRoom( + ctx context.Context, roomNID types.RoomNID, joinOnly bool, +) ([]types.EventNID, error) { + if joinOnly { + return d.statements.selectMembershipsFromRoomAndMembership( + ctx, roomNID, membershipStateJoin, + ) + } + + return d.statements.selectMembershipsFromRoom(ctx, roomNID) +} + +// EventsFromIDs implements query.RoomserverQueryAPIEventDB +func (d *Database) EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) { + nidMap, err := d.EventNIDs(ctx, eventIDs) + if err != nil { + return nil, err + } + + var nids []types.EventNID + for _, nid := range nidMap { + nids = append(nids, nid) + } + + return d.Events(ctx, nids) +} + +type transaction struct { + ctx context.Context + txn *sql.Tx +} + +// Commit implements types.Transaction +func (t *transaction) Commit() error { + return t.txn.Commit() +} + +// Rollback implements types.Transaction +func (t *transaction) Rollback() error { + return t.txn.Rollback() +} diff --git a/roomserver/storage/transactions_table.go b/roomserver/storage/postgres/transactions_table.go similarity index 95% rename from roomserver/storage/transactions_table.go rename to roomserver/storage/postgres/transactions_table.go index b98ea3f33..87c1cacae 100644 --- a/roomserver/storage/transactions_table.go +++ b/roomserver/storage/postgres/transactions_table.go @@ -1,3 +1,6 @@ +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -10,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/roomserver/storage/storage.go b/roomserver/storage/storage.go index 7e8eb98c9..325d96e99 100644 --- a/roomserver/storage/storage.go +++ b/roomserver/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,697 +16,57 @@ package storage import ( "context" - "database/sql" + "errors" + "net/url" - // Import the postgres database driver. - _ "github.com/lib/pq" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/storage/postgres" "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" ) -// A Database is used to store room events and stream offsets. -type Database struct { - statements statements - db *sql.DB +type Database interface { + StoreEvent(ctx context.Context, event gomatrixserverlib.Event, txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID) (types.RoomNID, types.StateAtEvent, error) + StateEntriesForEventIDs(ctx context.Context, eventIDs []string) ([]types.StateEntry, error) + EventTypeNIDs(ctx context.Context, eventTypes []string) (map[string]types.EventTypeNID, error) + EventStateKeyNIDs(ctx context.Context, eventStateKeys []string) (map[string]types.EventStateKeyNID, error) + EventStateKeys(ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID) (map[types.EventStateKeyNID]string, error) + EventNIDs(ctx context.Context, eventIDs []string) (map[string]types.EventNID, error) + Events(ctx context.Context, eventNIDs []types.EventNID) ([]types.Event, error) + AddState(ctx context.Context, roomNID types.RoomNID, stateBlockNIDs []types.StateBlockNID, state []types.StateEntry) (types.StateSnapshotNID, error) + SetState(ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID) error + StateAtEventIDs(ctx context.Context, eventIDs []string) ([]types.StateAtEvent, error) + StateBlockNIDs(ctx context.Context, stateNIDs []types.StateSnapshotNID) ([]types.StateBlockNIDList, error) + StateEntries(ctx context.Context, stateBlockNIDs []types.StateBlockNID) ([]types.StateEntryList, error) + SnapshotNIDFromEventID(ctx context.Context, eventID string) (types.StateSnapshotNID, error) + EventIDs(ctx context.Context, eventNIDs []types.EventNID) (map[types.EventNID]string, error) + GetLatestEventsForUpdate(ctx context.Context, roomNID types.RoomNID) (types.RoomRecentEventsUpdater, error) + GetTransactionEventID(ctx context.Context, transactionID string, sessionID int64, userID string) (string, error) + RoomNID(ctx context.Context, roomID string) (types.RoomNID, error) + LatestEventIDs(ctx context.Context, roomNID types.RoomNID) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, int64, error) + GetInvitesForUser(ctx context.Context, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID) (senderUserIDs []types.EventStateKeyNID, err error) + SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error + GetRoomIDForAlias(ctx context.Context, alias string) (string, error) + GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error) + GetCreatorIDForAlias(ctx context.Context, alias string) (string, error) + RemoveRoomAlias(ctx context.Context, alias string) error + StateEntriesForTuples(ctx context.Context, stateBlockNIDs []types.StateBlockNID, stateKeyTuples []types.StateKeyTuple) ([]types.StateEntryList, error) + MembershipUpdater(ctx context.Context, roomID, targetUserID string) (types.MembershipUpdater, error) + GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderUserID string) (membershipEventNID types.EventNID, stillInRoom bool, err error) + GetMembershipEventNIDsForRoom(ctx context.Context, roomNID types.RoomNID, joinOnly bool) ([]types.EventNID, error) + EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) } -// Open a postgres database. -func Open(dataSourceName string) (*Database, error) { - var d Database - var err error - if d.db, err = sql.Open("postgres", dataSourceName); err != nil { - return nil, err - } - if err = d.statements.prepare(d.db); err != nil { - return nil, err - } - return &d, nil -} - -// StoreEvent implements input.EventDatabase -func (d *Database) StoreEvent( - ctx context.Context, event gomatrixserverlib.Event, - txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID, -) (types.RoomNID, types.StateAtEvent, error) { - var ( - roomNID types.RoomNID - eventTypeNID types.EventTypeNID - eventStateKeyNID types.EventStateKeyNID - eventNID types.EventNID - stateNID types.StateSnapshotNID - err error - ) - - if txnAndSessionID != nil { - if err = d.statements.insertTransaction( - ctx, txnAndSessionID.TransactionID, - txnAndSessionID.SessionID, event.Sender(), event.EventID(), - ); err != nil { - return 0, types.StateAtEvent{}, err - } - } - - if roomNID, err = d.assignRoomNID(ctx, nil, event.RoomID()); err != nil { - return 0, types.StateAtEvent{}, err - } - - if eventTypeNID, err = d.assignEventTypeNID(ctx, event.Type()); err != nil { - return 0, types.StateAtEvent{}, err - } - - eventStateKey := event.StateKey() - // Assigned a numeric ID for the state_key if there is one present. - // Otherwise set the numeric ID for the state_key to 0. - if eventStateKey != nil { - if eventStateKeyNID, err = d.assignStateKeyNID(ctx, nil, *eventStateKey); err != nil { - return 0, types.StateAtEvent{}, err - } - } - - if eventNID, stateNID, err = d.statements.insertEvent( - ctx, - roomNID, - eventTypeNID, - eventStateKeyNID, - event.EventID(), - event.EventReference().EventSHA256, - authEventNIDs, - event.Depth(), - ); err != nil { - if err == sql.ErrNoRows { - // We've already inserted the event so select the numeric event ID - eventNID, stateNID, err = d.statements.selectEvent(ctx, event.EventID()) - } - if err != nil { - return 0, types.StateAtEvent{}, err - } - } - - if err = d.statements.insertEventJSON(ctx, eventNID, event.JSON()); err != nil { - return 0, types.StateAtEvent{}, err - } - - return roomNID, types.StateAtEvent{ - BeforeStateSnapshotNID: stateNID, - StateEntry: types.StateEntry{ - StateKeyTuple: types.StateKeyTuple{ - EventTypeNID: eventTypeNID, - EventStateKeyNID: eventStateKeyNID, - }, - EventNID: eventNID, - }, - }, nil -} - -func (d *Database) assignRoomNID( - ctx context.Context, txn *sql.Tx, roomID string, -) (types.RoomNID, error) { - // Check if we already have a numeric ID in the database. - roomNID, err := d.statements.selectRoomNID(ctx, txn, roomID) - if err == sql.ErrNoRows { - // We don't have a numeric ID so insert one into the database. - roomNID, err = d.statements.insertRoomNID(ctx, txn, roomID) - if err == sql.ErrNoRows { - // We raced with another insert so run the select again. - roomNID, err = d.statements.selectRoomNID(ctx, txn, roomID) - } - } - return roomNID, err -} - -func (d *Database) assignEventTypeNID( - ctx context.Context, eventType string, -) (types.EventTypeNID, error) { - // Check if we already have a numeric ID in the database. - eventTypeNID, err := d.statements.selectEventTypeNID(ctx, eventType) - if err == sql.ErrNoRows { - // We don't have a numeric ID so insert one into the database. - eventTypeNID, err = d.statements.insertEventTypeNID(ctx, eventType) - if err == sql.ErrNoRows { - // We raced with another insert so run the select again. - eventTypeNID, err = d.statements.selectEventTypeNID(ctx, eventType) - } - } - return eventTypeNID, err -} - -func (d *Database) assignStateKeyNID( - ctx context.Context, txn *sql.Tx, eventStateKey string, -) (types.EventStateKeyNID, error) { - // Check if we already have a numeric ID in the database. - eventStateKeyNID, err := d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey) - if err == sql.ErrNoRows { - // We don't have a numeric ID so insert one into the database. - eventStateKeyNID, err = d.statements.insertEventStateKeyNID(ctx, txn, eventStateKey) - if err == sql.ErrNoRows { - // We raced with another insert so run the select again. - eventStateKeyNID, err = d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey) - } - } - return eventStateKeyNID, err -} - -// StateEntriesForEventIDs implements input.EventDatabase -func (d *Database) StateEntriesForEventIDs( - ctx context.Context, eventIDs []string, -) ([]types.StateEntry, error) { - return d.statements.bulkSelectStateEventByID(ctx, eventIDs) -} - -// EventTypeNIDs implements state.RoomStateDatabase -func (d *Database) EventTypeNIDs( - ctx context.Context, eventTypes []string, -) (map[string]types.EventTypeNID, error) { - return d.statements.bulkSelectEventTypeNID(ctx, eventTypes) -} - -// EventStateKeyNIDs implements state.RoomStateDatabase -func (d *Database) EventStateKeyNIDs( - ctx context.Context, eventStateKeys []string, -) (map[string]types.EventStateKeyNID, error) { - return d.statements.bulkSelectEventStateKeyNID(ctx, eventStateKeys) -} - -// EventStateKeys implements query.RoomserverQueryAPIDatabase -func (d *Database) EventStateKeys( - ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID, -) (map[types.EventStateKeyNID]string, error) { - return d.statements.bulkSelectEventStateKey(ctx, eventStateKeyNIDs) -} - -// EventNIDs implements query.RoomserverQueryAPIDatabase -func (d *Database) EventNIDs( - ctx context.Context, eventIDs []string, -) (map[string]types.EventNID, error) { - return d.statements.bulkSelectEventNID(ctx, eventIDs) -} - -// Events implements input.EventDatabase -func (d *Database) Events( - ctx context.Context, eventNIDs []types.EventNID, -) ([]types.Event, error) { - eventJSONs, err := d.statements.bulkSelectEventJSON(ctx, eventNIDs) +// NewPublicRoomsServerDatabase opens a database connection. +func Open(dataSourceName string) (Database, error) { + uri, err := url.Parse(dataSourceName) if err != nil { return nil, err } - results := make([]types.Event, len(eventJSONs)) - for i, eventJSON := range eventJSONs { - result := &results[i] - result.EventNID = eventJSON.EventNID - // TODO: Use NewEventFromTrustedJSON for efficiency - result.Event, err = gomatrixserverlib.NewEventFromUntrustedJSON(eventJSON.EventJSON) - if err != nil { - return nil, err - } + switch uri.Scheme { + case "postgres": + return postgres.Open(dataSourceName) + default: + return nil, errors.New("unknown schema") } - return results, nil -} - -// AddState implements input.EventDatabase -func (d *Database) AddState( - ctx context.Context, - roomNID types.RoomNID, - stateBlockNIDs []types.StateBlockNID, - state []types.StateEntry, -) (types.StateSnapshotNID, error) { - if len(state) > 0 { - stateBlockNID, err := d.statements.selectNextStateBlockNID(ctx) - if err != nil { - return 0, err - } - if err = d.statements.bulkInsertStateData(ctx, stateBlockNID, state); err != nil { - return 0, err - } - stateBlockNIDs = append(stateBlockNIDs[:len(stateBlockNIDs):len(stateBlockNIDs)], stateBlockNID) - } - - return d.statements.insertState(ctx, roomNID, stateBlockNIDs) -} - -// SetState implements input.EventDatabase -func (d *Database) SetState( - ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID, -) error { - return d.statements.updateEventState(ctx, eventNID, stateNID) -} - -// StateAtEventIDs implements input.EventDatabase -func (d *Database) StateAtEventIDs( - ctx context.Context, eventIDs []string, -) ([]types.StateAtEvent, error) { - return d.statements.bulkSelectStateAtEventByID(ctx, eventIDs) -} - -// StateBlockNIDs implements state.RoomStateDatabase -func (d *Database) StateBlockNIDs( - ctx context.Context, stateNIDs []types.StateSnapshotNID, -) ([]types.StateBlockNIDList, error) { - return d.statements.bulkSelectStateBlockNIDs(ctx, stateNIDs) -} - -// StateEntries implements state.RoomStateDatabase -func (d *Database) StateEntries( - ctx context.Context, stateBlockNIDs []types.StateBlockNID, -) ([]types.StateEntryList, error) { - return d.statements.bulkSelectStateBlockEntries(ctx, stateBlockNIDs) -} - -// SnapshotNIDFromEventID implements state.RoomStateDatabase -func (d *Database) SnapshotNIDFromEventID( - ctx context.Context, eventID string, -) (types.StateSnapshotNID, error) { - _, stateNID, err := d.statements.selectEvent(ctx, eventID) - return stateNID, err -} - -// EventIDs implements input.RoomEventDatabase -func (d *Database) EventIDs( - ctx context.Context, eventNIDs []types.EventNID, -) (map[types.EventNID]string, error) { - return d.statements.bulkSelectEventID(ctx, eventNIDs) -} - -// GetLatestEventsForUpdate implements input.EventDatabase -func (d *Database) GetLatestEventsForUpdate( - ctx context.Context, roomNID types.RoomNID, -) (types.RoomRecentEventsUpdater, error) { - txn, err := d.db.Begin() - if err != nil { - return nil, err - } - eventNIDs, lastEventNIDSent, currentStateSnapshotNID, err := - d.statements.selectLatestEventsNIDsForUpdate(ctx, txn, roomNID) - if err != nil { - txn.Rollback() // nolint: errcheck - return nil, err - } - stateAndRefs, err := d.statements.bulkSelectStateAtEventAndReference(ctx, txn, eventNIDs) - if err != nil { - txn.Rollback() // nolint: errcheck - return nil, err - } - var lastEventIDSent string - if lastEventNIDSent != 0 { - lastEventIDSent, err = d.statements.selectEventID(ctx, txn, lastEventNIDSent) - if err != nil { - txn.Rollback() // nolint: errcheck - return nil, err - } - } - return &roomRecentEventsUpdater{ - transaction{ctx, txn}, d, roomNID, stateAndRefs, lastEventIDSent, currentStateSnapshotNID, - }, nil -} - -// GetTransactionEventID implements input.EventDatabase -func (d *Database) GetTransactionEventID( - ctx context.Context, transactionID string, - sessionID int64, userID string, -) (string, error) { - eventID, err := d.statements.selectTransactionEventID(ctx, transactionID, sessionID, userID) - if err == sql.ErrNoRows { - return "", nil - } - return eventID, err -} - -type roomRecentEventsUpdater struct { - transaction - d *Database - roomNID types.RoomNID - latestEvents []types.StateAtEventAndReference - lastEventIDSent string - currentStateSnapshotNID types.StateSnapshotNID -} - -// LatestEvents implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) LatestEvents() []types.StateAtEventAndReference { - return u.latestEvents -} - -// LastEventIDSent implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) LastEventIDSent() string { - return u.lastEventIDSent -} - -// CurrentStateSnapshotNID implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) CurrentStateSnapshotNID() types.StateSnapshotNID { - return u.currentStateSnapshotNID -} - -// StorePreviousEvents implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) StorePreviousEvents(eventNID types.EventNID, previousEventReferences []gomatrixserverlib.EventReference) error { - for _, ref := range previousEventReferences { - if err := u.d.statements.insertPreviousEvent(u.ctx, u.txn, ref.EventID, ref.EventSHA256, eventNID); err != nil { - return err - } - } - return nil -} - -// IsReferenced implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) IsReferenced(eventReference gomatrixserverlib.EventReference) (bool, error) { - err := u.d.statements.selectPreviousEventExists(u.ctx, u.txn, eventReference.EventID, eventReference.EventSHA256) - if err == nil { - return true, nil - } - if err == sql.ErrNoRows { - return false, nil - } - return false, err -} - -// SetLatestEvents implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) SetLatestEvents( - roomNID types.RoomNID, latest []types.StateAtEventAndReference, lastEventNIDSent types.EventNID, - currentStateSnapshotNID types.StateSnapshotNID, -) error { - eventNIDs := make([]types.EventNID, len(latest)) - for i := range latest { - eventNIDs[i] = latest[i].EventNID - } - return u.d.statements.updateLatestEventNIDs(u.ctx, u.txn, roomNID, eventNIDs, lastEventNIDSent, currentStateSnapshotNID) -} - -// HasEventBeenSent implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) HasEventBeenSent(eventNID types.EventNID) (bool, error) { - return u.d.statements.selectEventSentToOutput(u.ctx, u.txn, eventNID) -} - -// MarkEventAsSent implements types.RoomRecentEventsUpdater -func (u *roomRecentEventsUpdater) MarkEventAsSent(eventNID types.EventNID) error { - return u.d.statements.updateEventSentToOutput(u.ctx, u.txn, eventNID) -} - -func (u *roomRecentEventsUpdater) MembershipUpdater(targetUserNID types.EventStateKeyNID) (types.MembershipUpdater, error) { - return u.d.membershipUpdaterTxn(u.ctx, u.txn, u.roomNID, targetUserNID) -} - -// RoomNID implements query.RoomserverQueryAPIDB -func (d *Database) RoomNID(ctx context.Context, roomID string) (types.RoomNID, error) { - roomNID, err := d.statements.selectRoomNID(ctx, nil, roomID) - if err == sql.ErrNoRows { - return 0, nil - } - return roomNID, err -} - -// LatestEventIDs implements query.RoomserverQueryAPIDatabase -func (d *Database) LatestEventIDs( - ctx context.Context, roomNID types.RoomNID, -) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, int64, error) { - eventNIDs, currentStateSnapshotNID, err := d.statements.selectLatestEventNIDs(ctx, roomNID) - if err != nil { - return nil, 0, 0, err - } - references, err := d.statements.bulkSelectEventReference(ctx, eventNIDs) - if err != nil { - return nil, 0, 0, err - } - depth, err := d.statements.selectMaxEventDepth(ctx, eventNIDs) - if err != nil { - return nil, 0, 0, err - } - return references, currentStateSnapshotNID, depth, nil -} - -// GetInvitesForUser implements query.RoomserverQueryAPIDatabase -func (d *Database) GetInvitesForUser( - ctx context.Context, - roomNID types.RoomNID, - targetUserNID types.EventStateKeyNID, -) (senderUserIDs []types.EventStateKeyNID, err error) { - return d.statements.selectInviteActiveForUserInRoom(ctx, targetUserNID, roomNID) -} - -// SetRoomAlias implements alias.RoomserverAliasAPIDB -func (d *Database) SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error { - return d.statements.insertRoomAlias(ctx, alias, roomID, creatorUserID) -} - -// GetRoomIDForAlias implements alias.RoomserverAliasAPIDB -func (d *Database) GetRoomIDForAlias(ctx context.Context, alias string) (string, error) { - return d.statements.selectRoomIDFromAlias(ctx, alias) -} - -// GetAliasesForRoomID implements alias.RoomserverAliasAPIDB -func (d *Database) GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error) { - return d.statements.selectAliasesFromRoomID(ctx, roomID) -} - -// GetCreatorIDForAlias implements alias.RoomserverAliasAPIDB -func (d *Database) GetCreatorIDForAlias( - ctx context.Context, alias string, -) (string, error) { - return d.statements.selectCreatorIDFromAlias(ctx, alias) -} - -// RemoveRoomAlias implements alias.RoomserverAliasAPIDB -func (d *Database) RemoveRoomAlias(ctx context.Context, alias string) error { - return d.statements.deleteRoomAlias(ctx, alias) -} - -// StateEntriesForTuples implements state.RoomStateDatabase -func (d *Database) StateEntriesForTuples( - ctx context.Context, - stateBlockNIDs []types.StateBlockNID, - stateKeyTuples []types.StateKeyTuple, -) ([]types.StateEntryList, error) { - return d.statements.bulkSelectFilteredStateBlockEntries( - ctx, stateBlockNIDs, stateKeyTuples, - ) -} - -// MembershipUpdater implements input.RoomEventDatabase -func (d *Database) MembershipUpdater( - ctx context.Context, roomID, targetUserID string, -) (types.MembershipUpdater, error) { - txn, err := d.db.Begin() - if err != nil { - return nil, err - } - succeeded := false - defer func() { - if !succeeded { - txn.Rollback() // nolint: errcheck - } - }() - - roomNID, err := d.assignRoomNID(ctx, txn, roomID) - if err != nil { - return nil, err - } - - targetUserNID, err := d.assignStateKeyNID(ctx, txn, targetUserID) - if err != nil { - return nil, err - } - - updater, err := d.membershipUpdaterTxn(ctx, txn, roomNID, targetUserNID) - if err != nil { - return nil, err - } - - succeeded = true - return updater, nil -} - -type membershipUpdater struct { - transaction - d *Database - roomNID types.RoomNID - targetUserNID types.EventStateKeyNID - membership membershipState -} - -func (d *Database) membershipUpdaterTxn( - ctx context.Context, - txn *sql.Tx, - roomNID types.RoomNID, - targetUserNID types.EventStateKeyNID, -) (types.MembershipUpdater, error) { - - if err := d.statements.insertMembership(ctx, txn, roomNID, targetUserNID); err != nil { - return nil, err - } - - membership, err := d.statements.selectMembershipForUpdate(ctx, txn, roomNID, targetUserNID) - if err != nil { - return nil, err - } - - return &membershipUpdater{ - transaction{ctx, txn}, d, roomNID, targetUserNID, membership, - }, nil -} - -// IsInvite implements types.MembershipUpdater -func (u *membershipUpdater) IsInvite() bool { - return u.membership == membershipStateInvite -} - -// IsJoin implements types.MembershipUpdater -func (u *membershipUpdater) IsJoin() bool { - return u.membership == membershipStateJoin -} - -// IsLeave implements types.MembershipUpdater -func (u *membershipUpdater) IsLeave() bool { - return u.membership == membershipStateLeaveOrBan -} - -// SetToInvite implements types.MembershipUpdater -func (u *membershipUpdater) SetToInvite(event gomatrixserverlib.Event) (bool, error) { - senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, event.Sender()) - if err != nil { - return false, err - } - inserted, err := u.d.statements.insertInviteEvent( - u.ctx, u.txn, event.EventID(), u.roomNID, u.targetUserNID, senderUserNID, event.JSON(), - ) - if err != nil { - return false, err - } - if u.membership != membershipStateInvite { - if err = u.d.statements.updateMembership( - u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateInvite, 0, - ); err != nil { - return false, err - } - } - return inserted, nil -} - -// SetToJoin implements types.MembershipUpdater -func (u *membershipUpdater) SetToJoin(senderUserID string, eventID string, isUpdate bool) ([]string, error) { - var inviteEventIDs []string - - senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID) - if err != nil { - return nil, err - } - - // If this is a join event update, there is no invite to update - if !isUpdate { - inviteEventIDs, err = u.d.statements.updateInviteRetired( - u.ctx, u.txn, u.roomNID, u.targetUserNID, - ) - if err != nil { - return nil, err - } - } - - // Look up the NID of the new join event - nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID}) - if err != nil { - return nil, err - } - - if u.membership != membershipStateJoin || isUpdate { - if err = u.d.statements.updateMembership( - u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, - membershipStateJoin, nIDs[eventID], - ); err != nil { - return nil, err - } - } - - return inviteEventIDs, nil -} - -// SetToLeave implements types.MembershipUpdater -func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]string, error) { - senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID) - if err != nil { - return nil, err - } - inviteEventIDs, err := u.d.statements.updateInviteRetired( - u.ctx, u.txn, u.roomNID, u.targetUserNID, - ) - if err != nil { - return nil, err - } - - // Look up the NID of the new leave event - nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID}) - if err != nil { - return nil, err - } - - if u.membership != membershipStateLeaveOrBan { - if err = u.d.statements.updateMembership( - u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, - membershipStateLeaveOrBan, nIDs[eventID], - ); err != nil { - return nil, err - } - } - return inviteEventIDs, nil -} - -// GetMembership implements query.RoomserverQueryAPIDB -func (d *Database) GetMembership( - ctx context.Context, roomNID types.RoomNID, requestSenderUserID string, -) (membershipEventNID types.EventNID, stillInRoom bool, err error) { - requestSenderUserNID, err := d.assignStateKeyNID(ctx, nil, requestSenderUserID) - if err != nil { - return - } - - senderMembershipEventNID, senderMembership, err := - d.statements.selectMembershipFromRoomAndTarget( - ctx, roomNID, requestSenderUserNID, - ) - if err == sql.ErrNoRows { - // The user has never been a member of that room - return 0, false, nil - } else if err != nil { - return - } - - return senderMembershipEventNID, senderMembership == membershipStateJoin, nil -} - -// GetMembershipEventNIDsForRoom implements query.RoomserverQueryAPIDB -func (d *Database) GetMembershipEventNIDsForRoom( - ctx context.Context, roomNID types.RoomNID, joinOnly bool, -) ([]types.EventNID, error) { - if joinOnly { - return d.statements.selectMembershipsFromRoomAndMembership( - ctx, roomNID, membershipStateJoin, - ) - } - - return d.statements.selectMembershipsFromRoom(ctx, roomNID) -} - -// EventsFromIDs implements query.RoomserverQueryAPIEventDB -func (d *Database) EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) { - nidMap, err := d.EventNIDs(ctx, eventIDs) - if err != nil { - return nil, err - } - - var nids []types.EventNID - for _, nid := range nidMap { - nids = append(nids, nid) - } - - return d.Events(ctx, nids) -} - -type transaction struct { - ctx context.Context - txn *sql.Tx -} - -// Commit implements types.Transaction -func (t *transaction) Commit() error { - return t.txn.Commit() -} - -// Rollback implements types.Transaction -func (t *transaction) Rollback() error { - return t.txn.Rollback() } diff --git a/syncapi/consumers/clientapi.go b/syncapi/consumers/clientapi.go index f0db56427..ed39cd2d0 100644 --- a/syncapi/consumers/clientapi.go +++ b/syncapi/consumers/clientapi.go @@ -30,7 +30,7 @@ import ( // OutputClientDataConsumer consumes events that originated in the client API server. type OutputClientDataConsumer struct { clientAPIConsumer *common.ContinualConsumer - db *storage.SyncServerDatasource + db storage.Database notifier *sync.Notifier } @@ -39,7 +39,7 @@ func NewOutputClientDataConsumer( cfg *config.Dendrite, kafkaConsumer sarama.Consumer, n *sync.Notifier, - store *storage.SyncServerDatasource, + store storage.Database, ) *OutputClientDataConsumer { consumer := common.ContinualConsumer{ diff --git a/syncapi/consumers/roomserver.go b/syncapi/consumers/roomserver.go index e4f1ab460..cde2f5080 100644 --- a/syncapi/consumers/roomserver.go +++ b/syncapi/consumers/roomserver.go @@ -33,7 +33,7 @@ import ( // OutputRoomEventConsumer consumes events that originated in the room server. type OutputRoomEventConsumer struct { roomServerConsumer *common.ContinualConsumer - db *storage.SyncServerDatasource + db storage.Database notifier *sync.Notifier query api.RoomserverQueryAPI } @@ -43,7 +43,7 @@ func NewOutputRoomEventConsumer( cfg *config.Dendrite, kafkaConsumer sarama.Consumer, n *sync.Notifier, - store *storage.SyncServerDatasource, + store storage.Database, queryAPI api.RoomserverQueryAPI, ) *OutputRoomEventConsumer { diff --git a/syncapi/consumers/typingserver.go b/syncapi/consumers/typingserver.go index 5d998a18a..392f79872 100644 --- a/syncapi/consumers/typingserver.go +++ b/syncapi/consumers/typingserver.go @@ -30,7 +30,7 @@ import ( // OutputTypingEventConsumer consumes events that originated in the typing server. type OutputTypingEventConsumer struct { typingConsumer *common.ContinualConsumer - db *storage.SyncServerDatasource + db storage.Database notifier *sync.Notifier } @@ -40,7 +40,7 @@ func NewOutputTypingEventConsumer( cfg *config.Dendrite, kafkaConsumer sarama.Consumer, n *sync.Notifier, - store *storage.SyncServerDatasource, + store storage.Database, ) *OutputTypingEventConsumer { consumer := common.ContinualConsumer{ diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index 0f5019fc3..bd9389bdd 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -34,7 +34,7 @@ const pathPrefixR0 = "/_matrix/client/r0" // Due to Setup being used to call many other functions, a gocyclo nolint is // applied: // nolint: gocyclo -func Setup(apiMux *mux.Router, srp *sync.RequestPool, syncDB *storage.SyncServerDatasource, deviceDB *devices.Database) { +func Setup(apiMux *mux.Router, srp *sync.RequestPool, syncDB storage.Database, deviceDB *devices.Database) { r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter() authData := auth.Data{ diff --git a/syncapi/routing/state.go b/syncapi/routing/state.go index 87a93d194..61eaf218a 100644 --- a/syncapi/routing/state.go +++ b/syncapi/routing/state.go @@ -40,7 +40,7 @@ type stateEventInStateResp struct { // TODO: Check if the user is in the room. If not, check if the room's history // is publicly visible. Current behaviour is returning an empty array if the // user cannot see the room's history. -func OnIncomingStateRequest(req *http.Request, db *storage.SyncServerDatasource, roomID string) util.JSONResponse { +func OnIncomingStateRequest(req *http.Request, db storage.Database, roomID string) util.JSONResponse { // TODO(#287): Auth request and handle the case where the user has left (where // we should return the state at the poin they left) @@ -87,7 +87,7 @@ func OnIncomingStateRequest(req *http.Request, db *storage.SyncServerDatasource, // /rooms/{roomID}/state/{type}/{statekey} request. It will look in current // state to see if there is an event with that type and state key, if there // is then (by default) we return the content, otherwise a 404. -func OnIncomingStateTypeRequest(req *http.Request, db *storage.SyncServerDatasource, roomID string, evType, stateKey string) util.JSONResponse { +func OnIncomingStateTypeRequest(req *http.Request, db storage.Database, roomID string, evType, stateKey string) util.JSONResponse { // TODO(#287): Auth request and handle the case where the user has left (where // we should return the state at the poin they left) diff --git a/syncapi/storage/account_data_table.go b/syncapi/storage/postgres/account_data_table.go similarity index 97% rename from syncapi/storage/account_data_table.go rename to syncapi/storage/postgres/account_data_table.go index 7b4803e3d..33cfffad8 100644 --- a/syncapi/storage/account_data_table.go +++ b/syncapi/storage/postgres/account_data_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/syncapi/storage/current_room_state_table.go b/syncapi/storage/postgres/current_room_state_table.go similarity index 98% rename from syncapi/storage/current_room_state_table.go rename to syncapi/storage/postgres/current_room_state_table.go index 1ab70879a..dbfa111ba 100644 --- a/syncapi/storage/current_room_state_table.go +++ b/syncapi/storage/postgres/current_room_state_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/syncapi/storage/filtering.go b/syncapi/storage/postgres/filtering.go similarity index 98% rename from syncapi/storage/filtering.go rename to syncapi/storage/postgres/filtering.go index 27b0b888a..dcc421362 100644 --- a/syncapi/storage/filtering.go +++ b/syncapi/storage/postgres/filtering.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "strings" diff --git a/syncapi/storage/invites_table.go b/syncapi/storage/postgres/invites_table.go similarity index 84% rename from syncapi/storage/invites_table.go rename to syncapi/storage/postgres/invites_table.go index 9f52087f6..ced4bfc46 100644 --- a/syncapi/storage/invites_table.go +++ b/syncapi/storage/postgres/invites_table.go @@ -1,4 +1,19 @@ -package storage +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres import ( "context" diff --git a/syncapi/storage/output_room_events_table.go b/syncapi/storage/postgres/output_room_events_table.go similarity index 99% rename from syncapi/storage/output_room_events_table.go rename to syncapi/storage/postgres/output_room_events_table.go index e1803a17d..3927f0c38 100644 --- a/syncapi/storage/output_room_events_table.go +++ b/syncapi/storage/postgres/output_room_events_table.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/syncapi/storage/syncserver.go b/syncapi/storage/postgres/syncserver.go similarity index 99% rename from syncapi/storage/syncserver.go rename to syncapi/storage/postgres/syncserver.go index cda44d2e3..fc7b4e401 100644 --- a/syncapi/storage/syncserver.go +++ b/syncapi/storage/postgres/syncserver.go @@ -1,4 +1,5 @@ -// Copyright 2017 Vector Creations Ltd +// Copyright 2017-2018 New Vector Ltd +// Copyright 2019-2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package postgres import ( "context" diff --git a/syncapi/storage/storage.go b/syncapi/storage/storage.go new file mode 100644 index 000000000..eedb42f0e --- /dev/null +++ b/syncapi/storage/storage.go @@ -0,0 +1,63 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storage + +import ( + "context" + "errors" + "net/url" + "time" + + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/syncapi/storage/postgres" + "github.com/matrix-org/dendrite/syncapi/types" + "github.com/matrix-org/dendrite/typingserver/cache" + "github.com/matrix-org/gomatrixserverlib" +) + +type Database interface { + common.PartitionStorer + AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) + Events(ctx context.Context, eventIDs []string) ([]gomatrixserverlib.Event, error) + WriteEvent(ctx context.Context, ev *gomatrixserverlib.Event, addStateEvents []gomatrixserverlib.Event, addStateEventIDs, removeStateEventIDs []string, transactionID *api.TransactionID) (pduPosition int64, returnErr error) + GetStateEvent(ctx context.Context, roomID, evType, stateKey string) (*gomatrixserverlib.Event, error) + GetStateEventsForRoom(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.FilterPart) (stateEvents []gomatrixserverlib.Event, err error) + SyncPosition(ctx context.Context) (types.SyncPosition, error) + IncrementalSync(ctx context.Context, device authtypes.Device, fromPos, toPos types.SyncPosition, numRecentEventsPerRoom int, wantFullState bool) (*types.Response, error) + CompleteSync(ctx context.Context, userID string, numRecentEventsPerRoom int) (*types.Response, error) + GetAccountDataInRange(ctx context.Context, userID string, oldPos, newPos int64, accountDataFilterPart *gomatrixserverlib.FilterPart) (map[string][]string, error) + UpsertAccountData(ctx context.Context, userID, roomID, dataType string) (int64, error) + AddInviteEvent(ctx context.Context, inviteEvent gomatrixserverlib.Event) (int64, error) + RetireInviteEvent(ctx context.Context, inviteEventID string) error + SetTypingTimeoutCallback(fn cache.TimeoutCallbackFn) + AddTypingUser(userID, roomID string, expireTime *time.Time) int64 + RemoveTypingUser(userID, roomID string) int64 +} + +// NewPublicRoomsServerDatabase opens a database connection. +func NewSyncServerDatasource(dataSourceName string) (Database, error) { + uri, err := url.Parse(dataSourceName) + if err != nil { + return nil, err + } + switch uri.Scheme { + case "postgres": + return postgres.NewSyncServerDatasource(dataSourceName) + default: + return nil, errors.New("unknown schema") + } +} diff --git a/syncapi/sync/notifier.go b/syncapi/sync/notifier.go index 15d6b070c..548a17acd 100644 --- a/syncapi/sync/notifier.go +++ b/syncapi/sync/notifier.go @@ -141,7 +141,7 @@ func (n *Notifier) GetListener(req syncRequest) UserStreamListener { } // Load the membership states required to notify users correctly. -func (n *Notifier) Load(ctx context.Context, db *storage.SyncServerDatasource) error { +func (n *Notifier) Load(ctx context.Context, db storage.Database) error { roomToUsers, err := db.AllJoinedUsersInRooms(ctx) if err != nil { return err diff --git a/syncapi/sync/requestpool.go b/syncapi/sync/requestpool.go index 94a369001..82505e681 100644 --- a/syncapi/sync/requestpool.go +++ b/syncapi/sync/requestpool.go @@ -31,13 +31,13 @@ import ( // RequestPool manages HTTP long-poll connections for /sync type RequestPool struct { - db *storage.SyncServerDatasource + db storage.Database accountDB *accounts.Database notifier *Notifier } // NewRequestPool makes a new RequestPool -func NewRequestPool(db *storage.SyncServerDatasource, n *Notifier, adb *accounts.Database) *RequestPool { +func NewRequestPool(db storage.Database, n *Notifier, adb *accounts.Database) *RequestPool { return &RequestPool{db, adb, n} }