1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
import { RequiredNetworkElementType } from '../models/requiredNetworkElements';
import { MountedNetworkElementType } from '../models/mountedNetworkElements';
import { Topology, TopologyNode } from '../models/topologyNetconf';
import { requestRest } from '../../../../framework/src/services/restService';
import { Result, HitEntry } from '../../../../framework/src/models/elasticSearch';
/**
* Represents a web api accessor service for all Network Elements actions.
*/
class ConnectService {
/**
* Gets all known required network elements from the backend.
*/
public async getAllRequiredNetworkElements(): Promise<(RequiredNetworkElementType & { _id: string })[] | null> {
const path = 'database/mwtn/required-networkelement/_search';
const query = { "query": { "match_all": {} } };
const result = await requestRest<Result<RequiredNetworkElementType>>(path, { method: "POST", body: JSON.stringify(query) });
return result && result.hits && result.hits.hits && result.hits.hits.map(ne => ({
_id: ne._id,
mountId: ne._source && ne._source.mountId,
host: ne._source && ne._source.host,
port: ne._source && ne._source.port,
username: ne._source && ne._source.username,
password: ne._source && ne._source.password,
})) || null;
}
public async getRequiredNetworkElementByMountId(mountId:string): Promise<(RequiredNetworkElementType & { _id: string }) | null> {
const path = `database/mwtn/required-networkelement/${mountId}`;
const result = await requestRest<HitEntry<RequiredNetworkElementType> & { found: boolean }>(path, { method: "GET" });
return result && result.found && result._source && {
_id: result._id,
mountId: result._source.mountId,
host: result._source.host,
port: result._source.port,
username: result._source.username,
password: result._source.password,
} || null;
}
/**
* Inserts data into the required network elements table.
*/
public async insertRequiredNetworkElement(element: RequiredNetworkElementType): Promise<RequiredNetworkElementType | null> {
const path = `database/mwtn/required-networkelement/${ element.mountId }`;
const result = await requestRest<RequiredNetworkElementType>(path, { method: "POST", body: JSON.stringify(element) });
return result || null;
}
/**
* Deletes data from the Required Network Elements backend.
*/
public async deleteRequiredNetworkElement(element: RequiredNetworkElementType): Promise<RequiredNetworkElementType | null> {
const path = `database/mwtn/required-networkelement/${ element.mountId }`;
const result = await requestRest<RequiredNetworkElementType>(path, { method: "DELETE", body: JSON.stringify(element) });
return result || null;
}
private static mapTopologyNode = (mountPoint: TopologyNode, required: boolean ) => {
// handle onfCapabilities
let onfCapabilities: { module: string, revision: string }[] | undefined = undefined;
const capId = 'netconf-node-topology:available-capabilities';
if (mountPoint[capId] && mountPoint[capId]['available-capability']) {
onfCapabilities = mountPoint[capId]['available-capability'].filter((cap) => {
return cap.capability.includes('?revision=');
}).map((cap) => {
return {
module: cap.capability.split(')')[1],
revision: cap.capability.split('?revision=')[1].substring(0, 10)
};
}).sort((a, b) => {
if (a.module < b.module) return -1;
if (a.module > b.module) return 1;
return 0;
});
}
// handle clustered-connection-status
const statusId = 'netconf-node-topology:clustered-connection-status';
let client = 'localhost';
if (mountPoint[statusId] && mountPoint[statusId]['netconf-master-node']) {
let node = mountPoint[statusId]['netconf-master-node'];
node = node.substring(node.indexOf('@'));
client = node.substring(1, node.indexOf(':'));
}
const mountId = mountPoint["node-id"];
return {
mountId: mountId,
host: mountPoint["netconf-node-topology:host"],
port: mountPoint["netconf-node-topology:port"],
connectionStatus: mountPoint['netconf-node-topology:connection-status'],
capabilities: onfCapabilities || [],
required: required,
client
}
}
/** Get all mounted network elements and fills the property required according to the database contents. */
public async getMountedNetworkElementsList(): Promise<MountedNetworkElementType[] | null> {
const path = 'restconf/operational/network-topology:network-topology/topology/topology-netconf';
const topologyRequestPomise = requestRest<{ topology: Topology[] | null }>(path, { method: "GET" }, true);
const requiredNetworkElementsPromise = this.getAllRequiredNetworkElements();
const [netconfResponse, requiredNetworkElements] = await Promise.all([topologyRequestPomise, requiredNetworkElementsPromise]);
// process topologyNetconf (get all known network elements)
const topologyNetconf = netconfResponse && netconfResponse.topology && netconfResponse.topology.find(topology => topology["topology-id"] === 'topology-netconf');
let mountPoints = topologyNetconf && topologyNetconf.node && topologyNetconf.node.filter(
mountPoint => mountPoint['node-id'] !== 'controller-config').map(mountedElement => {
const required = requiredNetworkElements && requiredNetworkElements.some(
requiredElement => requiredElement.mountId === mountedElement["node-id"]);
return ConnectService.mapTopologyNode(mountedElement, !!required);
});
return mountPoints || [];
}
/** Get one mounted network element. */
public async getMountedNetworkElementByMountId(mountId: string): Promise<MountedNetworkElementType | null> {
const path = 'restconf/operational/network-topology:network-topology/topology/topology-netconf/node/' + mountId;
const getMountedNetworkElementByMountIdPromise = requestRest<{ node: TopologyNode[] | null }>(path, { method: "GET" }, true);
const getRequiredNetworkElementByMountIdPromise = this.getRequiredNetworkElementByMountId(mountId);
const [mountedNetworkElement, requiredNetworkElement] = await Promise.all([getMountedNetworkElementByMountIdPromise, getRequiredNetworkElementByMountIdPromise]);
return mountedNetworkElement && mountedNetworkElement.node && ConnectService.mapTopologyNode(mountedNetworkElement.node[0], requiredNetworkElement && requiredNetworkElement.mountId === mountedNetworkElement.node[0]["node-id"] || false) || null;
}
/** Mounts an required network element. */
public async mountNetworkElement(networkElement: RequiredNetworkElementType): Promise<boolean> {
const path = 'restconf/config/network-topology:network-topology/topology/topology-netconf/node/' + networkElement.mountId;
const mountXml = [
'<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">',
`<node-id>${ networkElement.mountId }</node-id>`,
`<host xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.host }</host>`,
`<port xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.port }</port>`,
`<username xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.username }</username>`,
`<password xmlns="urn:opendaylight:netconf-node-topology">${ networkElement.password }</password>`,
' <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>',
' <!-- non-mandatory fields with default values, you can safely remove these if you do not wish to override any of these values-->',
' <reconnect-on-changed-schema xmlns="urn:opendaylight:netconf-node-topology">false</reconnect-on-changed-schema>',
' <connection-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">20000</connection-timeout-millis>',
' <max-connection-attempts xmlns="urn:opendaylight:netconf-node-topology">100</max-connection-attempts>',
' <between-attempts-timeout-millis xmlns="urn:opendaylight:netconf-node-topology">2000</between-attempts-timeout-millis>',
' <sleep-factor xmlns="urn:opendaylight:netconf-node-topology">1.5</sleep-factor>',
' <!-- keepalive-delay set to 0 turns off keepalives-->',
' <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">120</keepalive-delay>',
'</node>'].join('');
try {
const result = await requestRest<string>(path, {
method: 'PUT',
headers: {
'Content-Type': 'application/xml',
'Accept': 'application/xml'
},
body: mountXml
}, true);
// expect an empty answer
return result !== null;
} catch {
return false;
}
};
/** Unmounts a network element by its id. */
public async unmountNetworkElement(mountId: string): Promise<boolean> {
const path = 'restconf/config/network-topology:network-topology/topology/topology-netconf/node/' + mountId;
try {
const result = await requestRest<string>(path, {
method: 'DELETE',
headers: {
'Content-Type': 'application/xml',
'Accept': 'application/xml'
},
}, true);
// expect an empty answer
return result !== null;
} catch {
return false;
}
};
}
export const connectService = new ConnectService();
export default connectService;
|