692 lines
18 KiB
Go
692 lines
18 KiB
Go
package runtime_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
ptypes "github.com/traefik/paerser/types"
|
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
|
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
|
)
|
|
|
|
// all the Routers/Middlewares/Services are considered fully qualified.
|
|
func TestPopulateUsedBy(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
conf *runtime.Configuration
|
|
expected runtime.Configuration
|
|
}{
|
|
{
|
|
desc: "nil config",
|
|
conf: nil,
|
|
expected: runtime.Configuration{},
|
|
},
|
|
{
|
|
desc: "One service used by two routers",
|
|
conf: &runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"foo@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`bar.foo`)",
|
|
},
|
|
},
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{URL: "http://127.0.0.1:8085"},
|
|
{URL: "http://127.0.0.1:8086"},
|
|
},
|
|
HealthCheck: &dynamic.ServerHealthCheck{
|
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
|
Path: "/health",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"foo@myprovider": {},
|
|
"bar@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "foo@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "One service used by two routers, but one router with wrong rule",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{URL: "http://127.0.0.1"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"foo@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "WrongRule(`bar.foo`)",
|
|
},
|
|
},
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"foo@myprovider": {},
|
|
"bar@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "foo@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "Broken Service used by one Router",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: nil,
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "2 different Services each used by a distinct router.",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{
|
|
URL: "http://127.0.0.1:8085",
|
|
},
|
|
{
|
|
URL: "http://127.0.0.1:8086",
|
|
},
|
|
},
|
|
HealthCheck: &dynamic.ServerHealthCheck{
|
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
|
Path: "/health",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"bar-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{
|
|
URL: "http://127.0.0.1:8087",
|
|
},
|
|
{
|
|
URL: "http://127.0.0.1:8088",
|
|
},
|
|
},
|
|
HealthCheck: &dynamic.ServerHealthCheck{
|
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
|
Path: "/health",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"foo@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`bar.foo`)",
|
|
},
|
|
},
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "bar-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {},
|
|
"foo@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"foo@myprovider"},
|
|
},
|
|
"bar-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "2 middlewares both used by 2 Routers",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{
|
|
URL: "http://127.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
BasicAuth: &dynamic.BasicAuth{
|
|
Users: []string{"admin:admin"},
|
|
},
|
|
},
|
|
},
|
|
"addPrefixTest@myprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
AddPrefix: &dynamic.AddPrefix{
|
|
Prefix: "/toto",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
Middlewares: []string{"auth", "addPrefixTest"},
|
|
},
|
|
},
|
|
"test@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar.other`)",
|
|
Middlewares: []string{"addPrefixTest", "auth"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {},
|
|
"test@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
},
|
|
"addPrefixTest@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "Unknown middleware is not used by the Router",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{
|
|
URL: "http://127.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
BasicAuth: &dynamic.BasicAuth{
|
|
Users: []string{"admin:admin"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
Middlewares: []string{"unknown"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "Broken middleware is used by Router",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{
|
|
URL: "http://127.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
BasicAuth: &dynamic.BasicAuth{
|
|
Users: []string{"badConf"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
Middlewares: []string{"auth@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "2 middlewares from 2 distinct providers both used by 2 Routers",
|
|
conf: &runtime.Configuration{
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
Service: &dynamic.Service{
|
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
|
Servers: []dynamic.Server{
|
|
{
|
|
URL: "http://127.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
BasicAuth: &dynamic.BasicAuth{
|
|
Users: []string{"admin:admin"},
|
|
},
|
|
},
|
|
},
|
|
"addPrefixTest@myprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
AddPrefix: &dynamic.AddPrefix{
|
|
Prefix: "/titi",
|
|
},
|
|
},
|
|
},
|
|
"addPrefixTest@anotherprovider": {
|
|
Middleware: &dynamic.Middleware{
|
|
AddPrefix: &dynamic.AddPrefix{
|
|
Prefix: "/toto",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
|
},
|
|
},
|
|
"test@myprovider": {
|
|
Router: &dynamic.Router{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar.other`)",
|
|
Middlewares: []string{"addPrefixTest", "auth"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
Routers: map[string]*runtime.RouterInfo{
|
|
"bar@myprovider": {},
|
|
"test@myprovider": {},
|
|
},
|
|
Services: map[string]*runtime.ServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
},
|
|
},
|
|
Middlewares: map[string]*runtime.MiddlewareInfo{
|
|
"auth@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "test@myprovider"},
|
|
},
|
|
"addPrefixTest@myprovider": {
|
|
UsedBy: []string{"test@myprovider"},
|
|
},
|
|
"addPrefixTest@anotherprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// TCP tests from hereon
|
|
{
|
|
desc: "TCP, One service used by two routers",
|
|
conf: &runtime.Configuration{
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"foo@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`bar.foo`)",
|
|
},
|
|
},
|
|
"bar@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
TCPService: &dynamic.TCPService{
|
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
|
Servers: []dynamic.TCPServer{
|
|
{
|
|
Address: "127.0.0.1",
|
|
Port: "8085",
|
|
},
|
|
{
|
|
Address: "127.0.0.1",
|
|
Port: "8086",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"foo@myprovider": {},
|
|
"bar@myprovider": {},
|
|
},
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "foo@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "TCP, One service used by two routers, but one router with wrong rule",
|
|
conf: &runtime.Configuration{
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
TCPService: &dynamic.TCPService{
|
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
|
Servers: []dynamic.TCPServer{
|
|
{
|
|
Address: "127.0.0.1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"foo@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "WrongRule(`bar.foo`)",
|
|
},
|
|
},
|
|
"bar@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"foo@myprovider": {},
|
|
"bar@myprovider": {},
|
|
},
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider", "foo@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "TCP, Broken Service used by one Router",
|
|
conf: &runtime.Configuration{
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
TCPService: &dynamic.TCPService{
|
|
LoadBalancer: nil,
|
|
},
|
|
},
|
|
},
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"bar@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"bar@myprovider": {},
|
|
},
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "TCP, 2 different Services each used by a distinct router.",
|
|
conf: &runtime.Configuration{
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
TCPService: &dynamic.TCPService{
|
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
|
Servers: []dynamic.TCPServer{
|
|
{
|
|
Address: "127.0.0.1",
|
|
Port: "8085",
|
|
},
|
|
{
|
|
Address: "127.0.0.1",
|
|
Port: "8086",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"bar-service@myprovider": {
|
|
TCPService: &dynamic.TCPService{
|
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
|
Servers: []dynamic.TCPServer{
|
|
{
|
|
Address: "127.0.0.1",
|
|
Port: "8087",
|
|
},
|
|
{
|
|
Address: "127.0.0.1",
|
|
Port: "8088",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"foo@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "foo-service@myprovider",
|
|
Rule: "Host(`bar.foo`)",
|
|
},
|
|
},
|
|
"bar@myprovider": {
|
|
TCPRouter: &dynamic.TCPRouter{
|
|
EntryPoints: []string{"web"},
|
|
Service: "bar-service@myprovider",
|
|
Rule: "Host(`foo.bar`)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: runtime.Configuration{
|
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
|
"bar@myprovider": {},
|
|
"foo@myprovider": {},
|
|
},
|
|
TCPServices: map[string]*runtime.TCPServiceInfo{
|
|
"foo-service@myprovider": {
|
|
UsedBy: []string{"foo@myprovider"},
|
|
},
|
|
"bar-service@myprovider": {
|
|
UsedBy: []string{"bar@myprovider"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, test := range testCases {
|
|
test := test
|
|
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runtimeConf := test.conf
|
|
runtimeConf.PopulateUsedBy()
|
|
|
|
for key, expectedService := range test.expected.Services {
|
|
require.NotNil(t, runtimeConf.Services[key])
|
|
assert.Equal(t, expectedService.UsedBy, runtimeConf.Services[key].UsedBy)
|
|
}
|
|
|
|
for key, expectedMiddleware := range test.expected.Middlewares {
|
|
require.NotNil(t, runtimeConf.Middlewares[key])
|
|
assert.Equal(t, expectedMiddleware.UsedBy, runtimeConf.Middlewares[key].UsedBy)
|
|
}
|
|
|
|
for key, expectedTCPService := range test.expected.TCPServices {
|
|
require.NotNil(t, runtimeConf.TCPServices[key])
|
|
assert.Equal(t, expectedTCPService.UsedBy, runtimeConf.TCPServices[key].UsedBy)
|
|
}
|
|
})
|
|
}
|
|
}
|