@routr/location 2.0.5-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.js +43 -0
- package/dist/config/get_config.d.ts +4 -0
- package/dist/config/get_config.js +90 -0
- package/dist/config/schema.d.ts +70 -0
- package/dist/config/schema.js +91 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.js +61 -0
- package/dist/helper.d.ts +3 -0
- package/dist/helper.js +26 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +47 -0
- package/dist/location.d.ts +14 -0
- package/dist/location.js +101 -0
- package/dist/memory_store.d.ts +13 -0
- package/dist/memory_store.js +53 -0
- package/dist/redis_store.d.ts +9 -0
- package/dist/redis_store.js +87 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +34 -0
- package/dist/service.d.ts +2 -0
- package/dist/service.js +46 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +13 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +99 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Fonoster Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AddRouteRequest, FindRoutesRequest, ILocationService, RemoveRoutesRequest } from "./types";
|
|
2
|
+
import { Route } from "@routr/common";
|
|
3
|
+
export default class Location implements ILocationService {
|
|
4
|
+
location: any;
|
|
5
|
+
config: {
|
|
6
|
+
addr: string;
|
|
7
|
+
};
|
|
8
|
+
constructor(config: {
|
|
9
|
+
addr: string;
|
|
10
|
+
});
|
|
11
|
+
addRoute(request: AddRouteRequest): Promise<void>;
|
|
12
|
+
findRoutes(request: FindRoutesRequest): Promise<Route[]>;
|
|
13
|
+
removeRoutes(request: RemoveRoutesRequest): Promise<void>;
|
|
14
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const common_1 = require("@routr/common");
|
|
13
|
+
const grpc = require("@grpc/grpc-js");
|
|
14
|
+
function container(self, request, name) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
self.location[name](request, (err, response) => {
|
|
17
|
+
if ((err === null || err === void 0 ? void 0 : err.code) === grpc.status.UNAVAILABLE) {
|
|
18
|
+
return reject(new common_1.ServiceUnavailableError(this.config.addr));
|
|
19
|
+
}
|
|
20
|
+
resolve(response);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
class Location {
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.location = new common_1.LOCATION_OBJECT_PROTO
|
|
28
|
+
.Location(config.addr, grpc.credentials.createInsecure());
|
|
29
|
+
}
|
|
30
|
+
addRoute(request) {
|
|
31
|
+
return container(this, request, this.addRoute.name);
|
|
32
|
+
}
|
|
33
|
+
findRoutes(request) {
|
|
34
|
+
var _a;
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
return ((_a = (yield container(this, request, this.findRoutes.name))) === null || _a === void 0 ? void 0 : _a.routes) || [];
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
removeRoutes(request) {
|
|
40
|
+
return container(this, request, this.removeRoutes.name);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.default = Location;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.getConfig = void 0;
|
|
26
|
+
/*
|
|
27
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
28
|
+
* http://github.com/fonoster/routr
|
|
29
|
+
*
|
|
30
|
+
* This file is part of Routr
|
|
31
|
+
*
|
|
32
|
+
* Licensed under the MIT License (the "License");
|
|
33
|
+
* you may not use this file except in compliance with
|
|
34
|
+
* the License. You may obtain a copy of the License at
|
|
35
|
+
*
|
|
36
|
+
* https://opensource.org/licenses/MIT
|
|
37
|
+
*
|
|
38
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
39
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
40
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
41
|
+
* See the License for the specific language governing permissions and
|
|
42
|
+
* limitations under the License.
|
|
43
|
+
*/
|
|
44
|
+
const types_1 = require("../types");
|
|
45
|
+
const fs_1 = __importDefault(require("fs"));
|
|
46
|
+
const schema_1 = require("./schema");
|
|
47
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
48
|
+
const E = __importStar(require("fp-ts/Either"));
|
|
49
|
+
const errors_1 = require("../errors");
|
|
50
|
+
const ajv = new ajv_1.default();
|
|
51
|
+
const validate = ajv.compile(schema_1.schema);
|
|
52
|
+
const hasBadCombiniation = (backends) => backends.some((b) => b.balancingAlgorithm === types_1.LB_ALGORITHM.ROUND_ROBIN && b.sessionAffinity);
|
|
53
|
+
const hasBadAlgorithm = (backends) => backends.some((b) => b.balancingAlgorithm !== types_1.LB_ALGORITHM.ROUND_ROBIN
|
|
54
|
+
&& b.balancingAlgorithm !== types_1.LB_ALGORITHM.LEAST_SESSIONS);
|
|
55
|
+
const getConfig = (path) => {
|
|
56
|
+
var _a, _b, _c;
|
|
57
|
+
const c = JSON.parse(fs_1.default.readFileSync(path, "utf8"));
|
|
58
|
+
if (!validate(Object.assign({}, c))) {
|
|
59
|
+
return E.left(new errors_1.InvalidSchemaConfiguration(JSON.stringify(validate.errors[0].message)));
|
|
60
|
+
}
|
|
61
|
+
const config = c.spec;
|
|
62
|
+
if (config.backends) {
|
|
63
|
+
config.backends.map((b) => {
|
|
64
|
+
// Setting round-robin by default
|
|
65
|
+
b.balancingAlgorithm = b.balancingAlgorithm
|
|
66
|
+
? b.balancingAlgorithm : types_1.LB_ALGORITHM.ROUND_ROBIN;
|
|
67
|
+
return b;
|
|
68
|
+
});
|
|
69
|
+
if (hasBadCombiniation(config.backends)) {
|
|
70
|
+
return E.left(new errors_1.BadAlgorithmAndAffinityCombination());
|
|
71
|
+
}
|
|
72
|
+
if (hasBadAlgorithm(config.backends)) {
|
|
73
|
+
return E.left(new errors_1.InvalidLoadBalancerAlgorithm());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!((_a = config === null || config === void 0 ? void 0 : config.cache) === null || _a === void 0 ? void 0 : _a.provider)) {
|
|
77
|
+
config.cache = {
|
|
78
|
+
provider: types_1.CACHE_PROVIDER.MEMORY
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (((_b = config === null || config === void 0 ? void 0 : config.cache) === null || _b === void 0 ? void 0 : _b.provider) === types_1.CACHE_PROVIDER.REDIS &&
|
|
82
|
+
!((_c = config === null || config === void 0 ? void 0 : config.cache) === null || _c === void 0 ? void 0 : _c.parameters)) {
|
|
83
|
+
config.cache = {
|
|
84
|
+
provider: types_1.CACHE_PROVIDER.REDIS,
|
|
85
|
+
parameters: "host=localhost,port=6379"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return E.right(config);
|
|
89
|
+
};
|
|
90
|
+
exports.getConfig = getConfig;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export declare const schema: {
|
|
2
|
+
$id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
type: string;
|
|
6
|
+
properties: {
|
|
7
|
+
kind: {
|
|
8
|
+
enum: string[];
|
|
9
|
+
};
|
|
10
|
+
apiVersion: {
|
|
11
|
+
enum: string[];
|
|
12
|
+
};
|
|
13
|
+
metadata: {
|
|
14
|
+
description: string;
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
17
|
+
spec: {
|
|
18
|
+
description: string;
|
|
19
|
+
type: string;
|
|
20
|
+
properties: {
|
|
21
|
+
bindAddr: {
|
|
22
|
+
description: string;
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
cache: {
|
|
26
|
+
type: string;
|
|
27
|
+
properties: {
|
|
28
|
+
provider: {
|
|
29
|
+
enum: string[];
|
|
30
|
+
};
|
|
31
|
+
parameters: {
|
|
32
|
+
type: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
required: string[];
|
|
36
|
+
};
|
|
37
|
+
backends: {
|
|
38
|
+
description: string;
|
|
39
|
+
type: string;
|
|
40
|
+
items: {
|
|
41
|
+
type: string;
|
|
42
|
+
};
|
|
43
|
+
properties: {
|
|
44
|
+
ref: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
balancingAlgorithm: {
|
|
48
|
+
enum: string[];
|
|
49
|
+
};
|
|
50
|
+
sessionAffinity: {
|
|
51
|
+
description: string;
|
|
52
|
+
type: string;
|
|
53
|
+
properties: {
|
|
54
|
+
enabled: {
|
|
55
|
+
type: string;
|
|
56
|
+
};
|
|
57
|
+
ref: {
|
|
58
|
+
type: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
required: string[];
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
required: string[];
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
required: string[];
|
|
70
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.schema = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
6
|
+
* http://github.com/fonoster/routr
|
|
7
|
+
*
|
|
8
|
+
* This file is part of Routr
|
|
9
|
+
*
|
|
10
|
+
* Licensed under the MIT License (the "License");
|
|
11
|
+
* you may not use this file except in compliance with
|
|
12
|
+
* the License. You may obtain a copy of the License at
|
|
13
|
+
*
|
|
14
|
+
* https://opensource.org/licenses/MIT
|
|
15
|
+
*
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
* See the License for the specific language governing permissions and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
exports.schema = {
|
|
23
|
+
"$id": "https://json-schema.org/draft/2020-12/schema",
|
|
24
|
+
"title": "Location Service configuration",
|
|
25
|
+
"description": "Configuration for an instance of the Location Service",
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"kind": {
|
|
29
|
+
"enum": ["Location", "location"]
|
|
30
|
+
},
|
|
31
|
+
"apiVersion": {
|
|
32
|
+
"enum": ["v2draft1"]
|
|
33
|
+
},
|
|
34
|
+
"metadata": {
|
|
35
|
+
"description": "Resource metadata",
|
|
36
|
+
"type": "object"
|
|
37
|
+
},
|
|
38
|
+
"spec": {
|
|
39
|
+
"description": "Operations spec for Location",
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"bindAddr": {
|
|
43
|
+
"description": "Ipv4 interface to accept request on",
|
|
44
|
+
"type": "string"
|
|
45
|
+
},
|
|
46
|
+
"cache": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"provider": {
|
|
50
|
+
"enum": ["memory", "redis"]
|
|
51
|
+
},
|
|
52
|
+
"parameters": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"required": ["provider"]
|
|
57
|
+
},
|
|
58
|
+
"backends": {
|
|
59
|
+
"description": "Optional SIP backends",
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object"
|
|
63
|
+
},
|
|
64
|
+
"properties": {
|
|
65
|
+
"ref": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
},
|
|
68
|
+
"balancingAlgorithm": {
|
|
69
|
+
"enum": ["round-robin", "least-sessions"]
|
|
70
|
+
},
|
|
71
|
+
"sessionAffinity": {
|
|
72
|
+
"description": "Optional session affinity",
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"enabled": {
|
|
76
|
+
"type": "boolean"
|
|
77
|
+
},
|
|
78
|
+
"ref": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": ["ref"]
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
"required": ["ref"]
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"required": ["kind", "metadata", "spec", "apiVersion"]
|
|
91
|
+
};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import grpc = require("@grpc/grpc-js");
|
|
2
|
+
export declare type LocationError = UnsupportedSchema | NotRoutesFoundForAOR;
|
|
3
|
+
export declare type InvalidConfiguration = BadAlgorithmAndAffinityCombination | InvalidSchemaConfiguration;
|
|
4
|
+
export declare class UnsupportedSchema extends Error {
|
|
5
|
+
code: grpc.status;
|
|
6
|
+
constructor(aor: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class NotRoutesFoundForAOR extends Error {
|
|
9
|
+
code: number;
|
|
10
|
+
constructor(aor: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class InvalidSchemaConfiguration extends Error {
|
|
13
|
+
code: grpc.status;
|
|
14
|
+
constructor(msg: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class BadAlgorithmAndAffinityCombination extends Error {
|
|
17
|
+
code: grpc.status;
|
|
18
|
+
constructor();
|
|
19
|
+
}
|
|
20
|
+
export declare class InvalidLoadBalancerAlgorithm extends Error {
|
|
21
|
+
code: grpc.status;
|
|
22
|
+
constructor();
|
|
23
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidLoadBalancerAlgorithm = exports.BadAlgorithmAndAffinityCombination = exports.InvalidSchemaConfiguration = exports.NotRoutesFoundForAOR = exports.UnsupportedSchema = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
6
|
+
* http://github.com/fonoster/routr
|
|
7
|
+
*
|
|
8
|
+
* This file is part of Routr
|
|
9
|
+
*
|
|
10
|
+
* Licensed under the MIT License (the "License")
|
|
11
|
+
* you may not use this file except in compliance with
|
|
12
|
+
* the License. You may obtain a copy of the License at
|
|
13
|
+
*
|
|
14
|
+
* https://opensource.org/licenses/MIT
|
|
15
|
+
*
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
* See the License for the specific language governing permissions and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
const grpc = require("@grpc/grpc-js");
|
|
23
|
+
class UnsupportedSchema extends Error {
|
|
24
|
+
constructor(aor) {
|
|
25
|
+
super(`aor ${aor} has an invalid schema, only "sip:" or "backend:" are allowed`);
|
|
26
|
+
this.code = grpc.status.INVALID_ARGUMENT;
|
|
27
|
+
// Set the prototype explicitly.
|
|
28
|
+
Object.setPrototypeOf(this, UnsupportedSchema.prototype);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.UnsupportedSchema = UnsupportedSchema;
|
|
32
|
+
class NotRoutesFoundForAOR extends Error {
|
|
33
|
+
constructor(aor) {
|
|
34
|
+
super(`no routes found for aor ${aor}`);
|
|
35
|
+
this.code = grpc.status.NOT_FOUND;
|
|
36
|
+
Object.setPrototypeOf(this, NotRoutesFoundForAOR.prototype);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.NotRoutesFoundForAOR = NotRoutesFoundForAOR;
|
|
40
|
+
class InvalidSchemaConfiguration extends Error {
|
|
41
|
+
constructor(msg) {
|
|
42
|
+
super(msg);
|
|
43
|
+
this.code = grpc.status.INVALID_ARGUMENT;
|
|
44
|
+
Object.setPrototypeOf(this, InvalidSchemaConfiguration.prototype);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.InvalidSchemaConfiguration = InvalidSchemaConfiguration;
|
|
48
|
+
class BadAlgorithmAndAffinityCombination extends Error {
|
|
49
|
+
constructor() {
|
|
50
|
+
super("session affinity can not be combined with round-robin load balancing");
|
|
51
|
+
Object.setPrototypeOf(this, BadAlgorithmAndAffinityCombination.prototype);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.BadAlgorithmAndAffinityCombination = BadAlgorithmAndAffinityCombination;
|
|
55
|
+
class InvalidLoadBalancerAlgorithm extends Error {
|
|
56
|
+
constructor() {
|
|
57
|
+
super("found invalid load balancing algorithm. must be round-robin or least-sessions");
|
|
58
|
+
Object.setPrototypeOf(this, BadAlgorithmAndAffinityCombination.prototype);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.InvalidLoadBalancerAlgorithm = InvalidLoadBalancerAlgorithm;
|
package/dist/helper.d.ts
ADDED
package/dist/helper.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRoute = void 0;
|
|
4
|
+
const processor_1 = require("@routr/processor");
|
|
5
|
+
const processor_2 = require("@routr/processor");
|
|
6
|
+
// TODO: Before finalizing this, consider using the old approach of saving the rport
|
|
7
|
+
// and received values (like here:
|
|
8
|
+
// https://github.com/fonoster/routr/blob/59bc98af86078088aede7e658c0d82a19fa18fa4/mod/registrar/utils.js#L87)
|
|
9
|
+
//
|
|
10
|
+
// Also consider: https://github.com/fonoster/routr/blob/ee5d339888344013939d06c734385f17f0cd75c2/mod/registrar/utils.js#L116
|
|
11
|
+
// and https://github.com/fonoster/routr/blob/ee5d339888344013939d06c734385f17f0cd75c2/mod/registrar/utils.js#L131
|
|
12
|
+
const createRoute = (request) => {
|
|
13
|
+
const uri = request.message.contact.address.uri;
|
|
14
|
+
return {
|
|
15
|
+
edgePortRef: request.edgePortRef,
|
|
16
|
+
user: uri.user,
|
|
17
|
+
host: uri.host,
|
|
18
|
+
port: uri.port,
|
|
19
|
+
transport: uri.transport,
|
|
20
|
+
registeredOn: Date.now(),
|
|
21
|
+
sessionCount: processor_1.Extensions.getHeaderValue(request, "x-session-count") || -1,
|
|
22
|
+
expires: processor_2.Target.getTargetExpires(request),
|
|
23
|
+
listeningPoint: request.listeningPoint
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
exports.createRoute = createRoute;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.LocationClient = exports.Utils = exports.Helper = void 0;
|
|
26
|
+
/*
|
|
27
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
28
|
+
* http://github.com/fonoster/routr
|
|
29
|
+
*
|
|
30
|
+
* This file is part of Routr
|
|
31
|
+
*
|
|
32
|
+
* Licensed under the MIT License (the "License")
|
|
33
|
+
* you may not use this file except in compliance with
|
|
34
|
+
* the License. You may obtain a copy of the License at
|
|
35
|
+
*
|
|
36
|
+
* https://opensource.org/licenses/MIT
|
|
37
|
+
*
|
|
38
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
39
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
40
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
41
|
+
* See the License for the specific language governing permissions and
|
|
42
|
+
* limitations under the License.
|
|
43
|
+
*/
|
|
44
|
+
const client_1 = __importDefault(require("./client"));
|
|
45
|
+
exports.LocationClient = client_1.default;
|
|
46
|
+
exports.Helper = __importStar(require("./helper"));
|
|
47
|
+
exports.Utils = __importStar(require("./utils"));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AddRouteRequest, Backend, FindRoutesRequest, ILocationService, ILocatorStore, RemoveRoutesRequest } from "./types";
|
|
2
|
+
import { Route } from "@routr/common";
|
|
3
|
+
export default class Location implements ILocationService {
|
|
4
|
+
private store;
|
|
5
|
+
private backends;
|
|
6
|
+
private rrCount;
|
|
7
|
+
private affinityStore;
|
|
8
|
+
constructor(store: ILocatorStore, backends?: Map<string, Backend>);
|
|
9
|
+
addRoute(request: AddRouteRequest): Promise<void>;
|
|
10
|
+
findRoutes(request: FindRoutesRequest): Promise<Route[]>;
|
|
11
|
+
removeRoutes(request: RemoveRoutesRequest): Promise<void>;
|
|
12
|
+
private next;
|
|
13
|
+
private nextWithAffinity;
|
|
14
|
+
}
|
package/dist/location.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
/*
|
|
13
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
14
|
+
* http://github.com/fonoster/routr
|
|
15
|
+
*
|
|
16
|
+
* This file is part of Routr
|
|
17
|
+
*
|
|
18
|
+
* Licensed under the MIT License (the "License")
|
|
19
|
+
* you may not use this file except in compliance with
|
|
20
|
+
* the License. You may obtain a copy of the License at
|
|
21
|
+
*
|
|
22
|
+
* https://opensource.org/licenses/MIT
|
|
23
|
+
*
|
|
24
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
25
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
26
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
27
|
+
* See the License for the specific language governing permissions and
|
|
28
|
+
* limitations under the License.
|
|
29
|
+
*/
|
|
30
|
+
const errors_1 = require("./errors");
|
|
31
|
+
const types_1 = require("./types");
|
|
32
|
+
const utils_1 = require("./utils");
|
|
33
|
+
class Location {
|
|
34
|
+
// Should fail if any backend has sessionAffinity and round-robin
|
|
35
|
+
constructor(store, backends = new Map()) {
|
|
36
|
+
this.store = store;
|
|
37
|
+
this.backends = backends;
|
|
38
|
+
this.rrCount = new Map();
|
|
39
|
+
this.affinityStore = new Map();
|
|
40
|
+
this.backends.forEach((value, key) => this.rrCount.set(key, 0));
|
|
41
|
+
}
|
|
42
|
+
addRoute(request) {
|
|
43
|
+
if (!request.aor.startsWith('sip:') &&
|
|
44
|
+
!request.aor.startsWith('backend:')) {
|
|
45
|
+
throw new errors_1.UnsupportedSchema(request.aor);
|
|
46
|
+
}
|
|
47
|
+
return this.store.put(request.aor, request.route);
|
|
48
|
+
}
|
|
49
|
+
findRoutes(request) {
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
const routes = request.labels
|
|
52
|
+
? (yield this.store.get(request.aor))
|
|
53
|
+
.filter(utils_1.filterOnlyMatchingLabels(request.labels))
|
|
54
|
+
: (yield this.store.get(request.aor)) || [];
|
|
55
|
+
if (request.aor.startsWith('sip:')) {
|
|
56
|
+
return routes;
|
|
57
|
+
}
|
|
58
|
+
else if (request.aor.startsWith('backend:')) {
|
|
59
|
+
const backend = this.backends.get(request.aor);
|
|
60
|
+
if (!backend) {
|
|
61
|
+
throw new errors_1.NotRoutesFoundForAOR(request.aor);
|
|
62
|
+
}
|
|
63
|
+
// If it has not affinity sesssion then get next
|
|
64
|
+
return !utils_1.hasAffinitySession(backend)
|
|
65
|
+
? [this.next(routes, backend)]
|
|
66
|
+
: [this.nextWithAffinity(yield routes, backend.sessionAffinity.ref)];
|
|
67
|
+
}
|
|
68
|
+
throw new errors_1.UnsupportedSchema(request.aor);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
removeRoutes(request) {
|
|
72
|
+
return this.store.delete(request.aor);
|
|
73
|
+
}
|
|
74
|
+
next(routes, backend) {
|
|
75
|
+
if (backend.balancingAlgorithm === types_1.LB_ALGORITHM.LEAST_SESSIONS) {
|
|
76
|
+
return routes.sort((r1, r2) => r1.sessionCount - r2.sessionCount)[0];
|
|
77
|
+
}
|
|
78
|
+
// Continues using round-robin
|
|
79
|
+
const nextPosition = this.rrCount.get(`backend:${backend.ref}`);
|
|
80
|
+
const result = routes[nextPosition];
|
|
81
|
+
if (nextPosition >= (routes.length - 1)) {
|
|
82
|
+
// Restarting round-robin counter
|
|
83
|
+
this.rrCount.set(`backend:${backend.ref}`, 0);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.rrCount.set(`backend:${backend.ref}`, nextPosition + 1);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
// Backend with session affinity does not support round-robin
|
|
91
|
+
nextWithAffinity(routes, sessionAffinityRef) {
|
|
92
|
+
let route = this.affinityStore.get(sessionAffinityRef);
|
|
93
|
+
if (route) {
|
|
94
|
+
return route;
|
|
95
|
+
}
|
|
96
|
+
route = routes.sort((r1, r2) => r1.sessionCount - r2.sessionCount)[0];
|
|
97
|
+
this.affinityStore.set(sessionAffinityRef, route);
|
|
98
|
+
return route;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.default = Location;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Route } from "@routr/common";
|
|
2
|
+
import { ILocatorStore } from "./types";
|
|
3
|
+
export default class MemoryStore implements ILocatorStore {
|
|
4
|
+
private collections;
|
|
5
|
+
private cleanupCount;
|
|
6
|
+
private maxCyclesBeforeCleanup;
|
|
7
|
+
constructor(maxCyclesBeforeCleanup?: number);
|
|
8
|
+
put(key: string, route: Route): Promise<void>;
|
|
9
|
+
get(key: string): Promise<Route[]>;
|
|
10
|
+
delete(key: string): Promise<void>;
|
|
11
|
+
size(): number;
|
|
12
|
+
cleanup(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("./utils");
|
|
4
|
+
const MAX_CYCLES_BEFORE_CLEANUP = 10000;
|
|
5
|
+
class MemoryStore {
|
|
6
|
+
constructor(maxCyclesBeforeCleanup = MAX_CYCLES_BEFORE_CLEANUP) {
|
|
7
|
+
this.collections = new Map();
|
|
8
|
+
this.maxCyclesBeforeCleanup = maxCyclesBeforeCleanup;
|
|
9
|
+
}
|
|
10
|
+
put(key, route) {
|
|
11
|
+
const routes = [...this.collections.get(key) || []];
|
|
12
|
+
// Avoids duplicate or expired routes
|
|
13
|
+
const filteredRoutes = routes
|
|
14
|
+
.filter(utils_1.expiredFilter)
|
|
15
|
+
.filter(r => utils_1.duplicateFilter(r, route));
|
|
16
|
+
if (filteredRoutes.length > 0) {
|
|
17
|
+
this.collections.set(key, [route, ...filteredRoutes]);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.collections.set(key, [route]);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
get(key) {
|
|
25
|
+
// Cleanup every so often to avoid memory build up
|
|
26
|
+
this.cleanupCount++;
|
|
27
|
+
if (this.cleanupCount >= this.maxCyclesBeforeCleanup) {
|
|
28
|
+
this.cleanupCount = 0;
|
|
29
|
+
this.cleanup();
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve((this.collections.get(key) || []).filter(utils_1.expiredFilter));
|
|
32
|
+
}
|
|
33
|
+
delete(key) {
|
|
34
|
+
this.collections.delete(key);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
size() {
|
|
38
|
+
return this.collections.size;
|
|
39
|
+
}
|
|
40
|
+
cleanup() {
|
|
41
|
+
// Remove any expired route from collection
|
|
42
|
+
this.collections.forEach((routes, key) => {
|
|
43
|
+
const filteredRoutes = routes.filter(utils_1.expiredFilter);
|
|
44
|
+
if (filteredRoutes.length > 0) {
|
|
45
|
+
this.collections.set(key, filteredRoutes);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.collections.delete(key);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.default = MemoryStore;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Route } from "@routr/common";
|
|
2
|
+
import { ILocatorStore, RedisStoreConfig } from "./types";
|
|
3
|
+
export default class RedisStore implements ILocatorStore {
|
|
4
|
+
client: any;
|
|
5
|
+
constructor(config?: RedisStoreConfig);
|
|
6
|
+
put(key: string, route: Route): Promise<void>;
|
|
7
|
+
get(key: string): Promise<Route[]>;
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
18
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
const redis_1 = require("redis");
|
|
23
|
+
const utils_1 = require("./utils");
|
|
24
|
+
const logger_1 = __importDefault(require("@fonoster/logger"));
|
|
25
|
+
class RedisStore {
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.client = config ? redis_1.createClient({ url: utils_1.getUrlString(config) }) : redis_1.createClient();
|
|
28
|
+
this.client.connect();
|
|
29
|
+
this.client.on('error', (err) => logger_1.default.error('redis client error: ', err));
|
|
30
|
+
}
|
|
31
|
+
put(key, route) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
// Formatted the key to ensure it is unique
|
|
34
|
+
logger_1.default.verbose(`${key}:${route.user}${route.host}${route.port}`);
|
|
35
|
+
yield this.client.set(`${key}:${route.user}${route.host}${route.port}`, JSON.stringify(route), {
|
|
36
|
+
EX: route.expires
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
get(key) {
|
|
42
|
+
var e_1, _a;
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
const routes = [];
|
|
45
|
+
try {
|
|
46
|
+
for (var _b = __asyncValues(yield this.client.scanIterator({
|
|
47
|
+
CURSOR: 0,
|
|
48
|
+
MATCH: `${key}:*`
|
|
49
|
+
})), _c; _c = yield _b.next(), !_c.done;) {
|
|
50
|
+
const k = _c.value;
|
|
51
|
+
routes.push(JSON.parse(yield this.client.get(k)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
55
|
+
finally {
|
|
56
|
+
try {
|
|
57
|
+
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
|
58
|
+
}
|
|
59
|
+
finally { if (e_1) throw e_1.error; }
|
|
60
|
+
}
|
|
61
|
+
return routes;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
delete(key) {
|
|
65
|
+
var e_2, _a;
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
try {
|
|
68
|
+
for (var _b = __asyncValues(yield this.client.scanIterator({
|
|
69
|
+
CURSOR: 0,
|
|
70
|
+
MATCH: `${key}:*`
|
|
71
|
+
})), _c; _c = yield _b.next(), !_c.done;) {
|
|
72
|
+
const k = _c.value;
|
|
73
|
+
yield this.client.del(k);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
77
|
+
finally {
|
|
78
|
+
try {
|
|
79
|
+
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
|
80
|
+
}
|
|
81
|
+
finally { if (e_2) throw e_2.error; }
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.default = RedisStore;
|
package/dist/runner.d.ts
ADDED
package/dist/runner.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
/*
|
|
8
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
9
|
+
* http://github.com/fonoster/routr
|
|
10
|
+
*
|
|
11
|
+
* This file is part of Routr
|
|
12
|
+
*
|
|
13
|
+
* Licensed under the MIT License (the "License");
|
|
14
|
+
* you may not use this file except in compliance with
|
|
15
|
+
* the License. You may obtain a copy of the License at
|
|
16
|
+
*
|
|
17
|
+
* https://opensource.org/licenses/MIT
|
|
18
|
+
*
|
|
19
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
20
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
21
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
22
|
+
* See the License for the specific language governing permissions and
|
|
23
|
+
* limitations under the License.
|
|
24
|
+
*/
|
|
25
|
+
const logger_1 = __importDefault(require("@fonoster/logger"));
|
|
26
|
+
const service_1 = __importDefault(require("./service"));
|
|
27
|
+
const get_config_1 = require("./config/get_config");
|
|
28
|
+
const result = get_config_1.getConfig(process.env.CONFIG_PATH);
|
|
29
|
+
if (result._tag === 'Right') {
|
|
30
|
+
service_1.default(result.right);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
logger_1.default.error(result.left);
|
|
34
|
+
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/*
|
|
7
|
+
* Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
|
|
8
|
+
* http://github.com/fonoster/routr
|
|
9
|
+
*
|
|
10
|
+
* This file is part of Routr
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the MIT License (the "License")
|
|
13
|
+
* you may not use this file except in compliance with
|
|
14
|
+
* the License. You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* https://opensource.org/licenses/MIT
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
const types_1 = require("./types");
|
|
25
|
+
const common_1 = require("@routr/common");
|
|
26
|
+
const utils_1 = require("./utils");
|
|
27
|
+
const location_1 = __importDefault(require("./location"));
|
|
28
|
+
const memory_store_1 = __importDefault(require("./memory_store"));
|
|
29
|
+
const redis_store_1 = __importDefault(require("./redis_store"));
|
|
30
|
+
const logger_1 = __importDefault(require("@fonoster/logger"));
|
|
31
|
+
const allowedParameters = ["host", "port", "username", "password", "secure"];
|
|
32
|
+
function LocationService(config) {
|
|
33
|
+
const { bindAddr, cache } = config;
|
|
34
|
+
let store;
|
|
35
|
+
if (cache.provider === types_1.CACHE_PROVIDER.REDIS) {
|
|
36
|
+
store = new redis_store_1.default(utils_1.configFromString(cache.parameters, allowedParameters));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
store = new memory_store_1.default();
|
|
40
|
+
}
|
|
41
|
+
const backends = new Map();
|
|
42
|
+
config.backends.forEach(b => backends.set(`backend:${b.ref}`, b));
|
|
43
|
+
common_1.createService(utils_1.getServiceInfo(bindAddr, new location_1.default(store, backends)));
|
|
44
|
+
logger_1.default.info(`using ${cache.provider} as cache provider`);
|
|
45
|
+
}
|
|
46
|
+
exports.default = LocationService;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Route } from "@routr/common";
|
|
2
|
+
export declare enum LB_ALGORITHM {
|
|
3
|
+
ROUND_ROBIN = "round-robin",
|
|
4
|
+
LEAST_SESSIONS = "least-sessions"
|
|
5
|
+
}
|
|
6
|
+
export declare enum CACHE_PROVIDER {
|
|
7
|
+
MEMORY = "memory",
|
|
8
|
+
REDIS = "redis"
|
|
9
|
+
}
|
|
10
|
+
export interface ILocationService {
|
|
11
|
+
addRoute(request: AddRouteRequest): Promise<void>;
|
|
12
|
+
findRoutes(request: FindRoutesRequest): Promise<Route[]>;
|
|
13
|
+
removeRoutes(request: RemoveRoutesRequest): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface ILocatorStore {
|
|
16
|
+
put(key: string, route: Route): Promise<void>;
|
|
17
|
+
get(key: string): Promise<Route[]>;
|
|
18
|
+
delete(key: string): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export interface AddRouteRequest {
|
|
21
|
+
aor: string;
|
|
22
|
+
route: Route;
|
|
23
|
+
}
|
|
24
|
+
export interface FindRoutesRequest {
|
|
25
|
+
aor: string;
|
|
26
|
+
labels?: Map<string, string>;
|
|
27
|
+
sessionAffinityRef?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface FindRoutesResponse {
|
|
30
|
+
routes: Array<Route>;
|
|
31
|
+
}
|
|
32
|
+
export interface RemoveRoutesRequest {
|
|
33
|
+
aor: string;
|
|
34
|
+
}
|
|
35
|
+
export interface Backend {
|
|
36
|
+
ref: string;
|
|
37
|
+
balancingAlgorithm?: LB_ALGORITHM;
|
|
38
|
+
sessionAffinity?: {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
ref: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface LocationConfig {
|
|
44
|
+
bindAddr: string;
|
|
45
|
+
backends?: Backend[];
|
|
46
|
+
cache?: {
|
|
47
|
+
provider: CACHE_PROVIDER;
|
|
48
|
+
parameters: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export interface RedisStoreConfig {
|
|
52
|
+
username?: string;
|
|
53
|
+
password?: string;
|
|
54
|
+
host: string;
|
|
55
|
+
port: number;
|
|
56
|
+
secure?: boolean;
|
|
57
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CACHE_PROVIDER = exports.LB_ALGORITHM = void 0;
|
|
4
|
+
var LB_ALGORITHM;
|
|
5
|
+
(function (LB_ALGORITHM) {
|
|
6
|
+
LB_ALGORITHM["ROUND_ROBIN"] = "round-robin";
|
|
7
|
+
LB_ALGORITHM["LEAST_SESSIONS"] = "least-sessions";
|
|
8
|
+
})(LB_ALGORITHM = exports.LB_ALGORITHM || (exports.LB_ALGORITHM = {}));
|
|
9
|
+
var CACHE_PROVIDER;
|
|
10
|
+
(function (CACHE_PROVIDER) {
|
|
11
|
+
CACHE_PROVIDER["MEMORY"] = "memory";
|
|
12
|
+
CACHE_PROVIDER["REDIS"] = "redis";
|
|
13
|
+
})(CACHE_PROVIDER = exports.CACHE_PROVIDER || (exports.CACHE_PROVIDER = {}));
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Route } from "@routr/common";
|
|
2
|
+
import { ServiceInfo } from "@routr/common";
|
|
3
|
+
import { Backend, ILocationService, RedisStoreConfig } from "./types";
|
|
4
|
+
export declare const expiredFilter: (r: Route) => boolean;
|
|
5
|
+
export declare const duplicateFilter: (r1: Route, r2: Route) => boolean;
|
|
6
|
+
export declare const mergeKeyValue: (map: Map<string, string>) => string[];
|
|
7
|
+
export declare const compareArrays: (arr: string[], target: string[]) => boolean;
|
|
8
|
+
export declare const filterOnlyMatchingLabels: (requestLabels: Map<string, string>) => (route: Route) => boolean;
|
|
9
|
+
export declare const hasAffinitySession: (backend: Backend) => boolean;
|
|
10
|
+
export declare function getServiceInfo(bindAddr: string, locator: ILocationService): ServiceInfo;
|
|
11
|
+
export declare const configFromString: (params: string, allowedKeys: string[]) => Record<string, string | boolean>;
|
|
12
|
+
export declare const getUrlString: (config: RedisStoreConfig) => string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getUrlString = exports.configFromString = exports.getServiceInfo = exports.hasAffinitySession = exports.filterOnlyMatchingLabels = exports.compareArrays = exports.mergeKeyValue = exports.duplicateFilter = exports.expiredFilter = void 0;
|
|
13
|
+
const common_1 = require("@routr/common");
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
const expiredFilter = (r) => (r.expires - (Date.now() - r.registeredOn) / 1000) > 0;
|
|
16
|
+
exports.expiredFilter = expiredFilter;
|
|
17
|
+
const duplicateFilter = (r1, r2) => !(r1.host === r2.host && r1.port === r2.port);
|
|
18
|
+
exports.duplicateFilter = duplicateFilter;
|
|
19
|
+
const mergeKeyValue = (map) => Array.from(map).map(l => l[0] + l[1]);
|
|
20
|
+
exports.mergeKeyValue = mergeKeyValue;
|
|
21
|
+
const compareArrays = (arr, target) => target.every(v => arr.includes(v)) && arr.length === target.length;
|
|
22
|
+
exports.compareArrays = compareArrays;
|
|
23
|
+
const filterOnlyMatchingLabels = (requestLabels) => (route) => route.labels
|
|
24
|
+
? exports.compareArrays(exports.mergeKeyValue(requestLabels), exports.mergeKeyValue(route.labels))
|
|
25
|
+
: false;
|
|
26
|
+
exports.filterOnlyMatchingLabels = filterOnlyMatchingLabels;
|
|
27
|
+
const hasAffinitySession = (backend) => { var _a; return (_a = backend === null || backend === void 0 ? void 0 : backend.sessionAffinity) === null || _a === void 0 ? void 0 : _a.enabled; };
|
|
28
|
+
exports.hasAffinitySession = hasAffinitySession;
|
|
29
|
+
function getServiceInfo(bindAddr, locator) {
|
|
30
|
+
return {
|
|
31
|
+
name: "location",
|
|
32
|
+
bindAddr,
|
|
33
|
+
service: common_1.LOCATION_OBJECT_PROTO.Location.service,
|
|
34
|
+
handlers: {
|
|
35
|
+
addRoute: (call, callback) => {
|
|
36
|
+
try {
|
|
37
|
+
locator.addRoute(call.request);
|
|
38
|
+
callback(null, {});
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
callback(e, null);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
findRoutes: (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
try {
|
|
46
|
+
const routes = yield locator.findRoutes(call.request);
|
|
47
|
+
if (routes.length === 0)
|
|
48
|
+
throw new errors_1.NotRoutesFoundForAOR(call.request.aor);
|
|
49
|
+
callback(null, {
|
|
50
|
+
routes: routes
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
callback(e, null);
|
|
55
|
+
}
|
|
56
|
+
}),
|
|
57
|
+
removeRoutes: (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
try {
|
|
59
|
+
callback(null, {
|
|
60
|
+
routes: yield locator.findRoutes(call.request)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
callback(e, null);
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
exports.getServiceInfo = getServiceInfo;
|
|
71
|
+
const configFromString = (params, allowedKeys) => {
|
|
72
|
+
if (params.length === 0)
|
|
73
|
+
return {};
|
|
74
|
+
const parameters = {};
|
|
75
|
+
params.split(',').forEach(par => {
|
|
76
|
+
try {
|
|
77
|
+
const key = par.split('=')[0];
|
|
78
|
+
const value = par.split('=')[1];
|
|
79
|
+
if (allowedKeys.indexOf(key) === -1) {
|
|
80
|
+
throw `invalid parameter: ${key}`;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
parameters[key] = value === "true" ? true : value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
throw new Error(`invalid parameters string: ${params}; should be something like 'host=localhost,port=6379'`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return parameters;
|
|
91
|
+
};
|
|
92
|
+
exports.configFromString = configFromString;
|
|
93
|
+
const getUrlString = (config) => {
|
|
94
|
+
return `redis${config.secure ? 's' : ''}`
|
|
95
|
+
+ `://${config.username ? config.username : ''}`
|
|
96
|
+
+ `${config.password ? ':' + config.password + '@' : ''}`
|
|
97
|
+
+ `${config.host}:${config.port}`;
|
|
98
|
+
};
|
|
99
|
+
exports.getUrlString = getUrlString;
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@routr/location",
|
|
3
|
+
"version": "2.0.5-alpha.10",
|
|
4
|
+
"description": "Creates and maintains a list of routes to all registered endpoints",
|
|
5
|
+
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
|
+
"homepage": "https://github.com/fonoster/routr#readme",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "dist/index",
|
|
9
|
+
"types": "dist/index",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"prebuild": "rimraf ./dist tsconfig.tsbuildinfo",
|
|
12
|
+
"build": "tsc -b tsconfig.json"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"run_location": "dist/runner.js"
|
|
16
|
+
},
|
|
17
|
+
"directories": {
|
|
18
|
+
"src": "src",
|
|
19
|
+
"test": "test"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@grpc/grpc-js": "^1.5.9",
|
|
29
|
+
"@routr/common": "^2.0.5-alpha.10",
|
|
30
|
+
"@routr/processor": "^2.0.5-alpha.10",
|
|
31
|
+
"ajv": "^6.12.6",
|
|
32
|
+
"fp-ts": "^2.11.8",
|
|
33
|
+
"redis": "^4.0.4"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/fonoster/routr.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/fonoster/routr/issues"
|
|
41
|
+
},
|
|
42
|
+
"gitHead": "10c6ff135a370a4a4634478fdaf5f42c5c13e28c"
|
|
43
|
+
}
|