blob: 0bdf69182ff14ccc732e05b11395be506c7555da [file] [log] [blame]
Chip Bolingf5af85d2019-02-12 15:36:17 -06001# CCopyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from twisted.internet.defer import inlineCallbacks, returnValue
16import xmltodict
17import structlog
18from pyvoltha.protos.openflow_13_pb2 import OFPPF_1GB_FD, OFPPF_10GB_FD, OFPPF_40GB_FD, OFPPF_100GB_FD
19from pyvoltha.protos.openflow_13_pb2 import OFPPF_FIBER, OFPPF_COPPER
20from pyvoltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPC_PORT_DOWN, OFPPS_LINK_DOWN, OFPPF_OTHER
21from pyvoltha.protos.common_pb2 import OperStatus, AdminState
22
23log = structlog.get_logger()
24
25_ietf_interfaces_config_rpc = """
26 <filter xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
27 <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
28 <interface/>
29 </interfaces>
30 </filter>
31"""
32
33_ietf_interfaces_state_rpc = """
34 <filter xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
35 <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
36 <interface>
37 <name/>
38 <type/>
39 <admin-status/>
40 <oper-status/>
41 <last-change/>
42 <phys-address/>
43 <speed/>
44 </interface>
45 </interfaces-state>
46 </filter>
47"""
48
49_allowed_with_default_types = ['report-all', 'report-all-tagged', 'trim', 'explicit']
50
51# TODO: Centralize the item below as a function in a core util module
52
53
54def _with_defaults(default_type=None):
55 if default_type is None:
56 return ""
57
58 assert(default_type in _allowed_with_default_types)
59 return """
60 <with-defaults xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults">
61 {}</with-defaults>""".format(default_type)
62
63
64class IetfInterfacesConfig(object):
65 def __init__(self, session):
66 self._session = session
67
68 @inlineCallbacks
69 def get_config(self, source='running', with_defaults=None):
70
71 filter = _ietf_interfaces_config_rpc + _with_defaults(with_defaults)
72
73 request = self._session.get(source, filter=filter)
74 rpc_reply = yield request
75 returnValue(rpc_reply)
76
77 def get_interfaces(self, rpc_reply, interface_type=None):
78 """
79 Get the physical entities of a particular type
80 :param rpc_reply: Reply from previous get or request
81 :param interface_type: (String or List) The type of interface (case-insensitive)
82 :return: list) of OrderDict interface entries
83 """
84 result_dict = xmltodict.parse(rpc_reply.data_xml)
85
86 entries = result_dict['data']['interfaces']
87
88 if interface_type is None:
89 return entries
90
91 # for entry in entries:
92 # import pprint
93 # log.info(pprint.PrettyPrinter(indent=2).pformat(entry))
94
95 def _matches(entry, value):
96 if 'type' in entry and '#text' in entry['type']:
97 text_val = entry['type']['#text'].lower()
98 if isinstance(value, list):
99 return any(v.lower() in text_val for v in value)
100 return value.lower() in text_val
101 return False
102
103 return [entry for entry in entries if _matches(entry, interface_type)]
104
105
106class IetfInterfacesState(object):
107 def __init__(self, session):
108 self._session = session
109
110 @inlineCallbacks
111 def get_state(self):
112 try:
113 request = self._session.get(_ietf_interfaces_state_rpc)
114 rpc_reply = yield request
115 returnValue(rpc_reply)
116
117 except Exception as e:
118 log.exception('get_state', e=e)
119 raise
120
121 @staticmethod
122 def get_interfaces(self, rpc_reply, key='type', key_value=None):
123 """
124 Get the physical entities of a particular type
125 :param key_value: (String or List) The type of interface (case-insensitive)
126 :return: list) of OrderDict interface entries
127 """
128 result_dict = xmltodict.parse(rpc_reply.data_xml)
129 entries = result_dict['data']['interfaces-state']['interface']
130
131 if key_value is None:
132 return entries
133
134 for entry in entries:
135 import pprint
136 log.info(pprint.PrettyPrinter(indent=2).pformat(entry))
137
138 def _matches(entry, key, value):
139 if key in entry and '#text' in entry[key]:
140 text_val = entry[key]['#text'].lower()
141 if isinstance(value, list):
142 return any(v.lower() in text_val for v in value)
143 return value.lower() in text_val
144 return False
145
146 return [entry for entry in entries if _matches(entry, key, key_value)]
147
148 @staticmethod
149 def _get_admin_state(entry):
150 state_map = {
151 'up': AdminState.ENABLED,
152 'down': AdminState.DISABLED,
153 'testing': AdminState.DISABLED
154 }
155 return state_map.get(entry.get('admin-status', 'down'),
156 AdminState.UNKNOWN)
157
158 @staticmethod
159 def _get_oper_status(entry):
160 state_map = {
161 'up': OperStatus.ACTIVE,
162 'down': OperStatus.FAILED,
163 'testing': OperStatus.TESTING,
164 'unknown': OperStatus.UNKNOWN,
165 'dormant': OperStatus.DISCOVERED,
166 'not-present': OperStatus.UNKNOWN,
167 'lower-layer-down': OperStatus.FAILED
168 }
169 return state_map.get(entry.get('oper-status', 'down'),
170 OperStatus.UNKNOWN)
171
172 @staticmethod
173 def _get_mac_addr(entry):
174 mac_addr = entry.get('phys-address', None)
175 if mac_addr is None:
176 import random
177 # TODO: Get with qumram team about phys addr
178 mac_addr = '08:00:{}{}:{}{}:{}{}:00'.format(random.randint(0, 9),
179 random.randint(0, 9),
180 random.randint(0, 9),
181 random.randint(0, 9),
182 random.randint(0, 9),
183 random.randint(0, 9))
184 return mac_addr
185
186 @staticmethod
187 def _get_speed_value(entry):
188 speed = entry.get('speed') or IetfInterfacesState._get_speed_via_name(entry.get('name'))
189 if isinstance(speed, str):
190 return long(speed)
191 return speed
192
193 @staticmethod
194 def _get_speed_via_name(name):
195 speed_map = {
196 'terabit': 1000000000000,
197 'hundred-gigabit': 100000000000,
198 'fourty-gigabit': 40000000000,
199 'ten-gigabit': 10000000000,
200 'gigabit': 1000000000,
201 }
202 for n,v in speed_map.iteritems():
203 if n in name.lower():
204 return v
205 return 0
206
207 @staticmethod
208 def _get_of_state(entry):
209 # If port up and ready: OFPPS_LIVE
210 # If port config bit is down: OFPPC_PORT_DOWN
211 # If port state bit is down: OFPPS_LINK_DOWN
212 # if IetfInterfacesState._get_admin_state(entry) == AdminState.ENABLED:
213 # return OFPPS_LIVE \
214 # if IetfInterfacesState._get_oper_status(entry) == OperStatus.ACTIVE \
215 # else OFPPS_LINK_DOWN
216 #
217 # return OFPPC_PORT_DOWN
218 # TODO: Update of openflow port state is not supported, so always say we are alive
219 return OFPPS_LIVE
220
221 @staticmethod
222 def _get_of_capabilities(entry):
223 # The capabilities field is a bitmap that uses a combination of the following flags :
224 # Capabilities supported by the datapath
225 # enum ofp_capabilities {
226 # OFPC_FLOW_STATS = 1 << 0, /* Flow statistics. */
227 # OFPC_TABLE_STATS = 1 << 1, /* Table statistics. */
228 # OFPC_PORT_STATS = 1 << 2, /* Port statistics. */
229 # OFPC_GROUP_STATS = 1 << 3, /* Group statistics. */
230 # OFPC_IP_REASM = 1 << 5, /* Can reassemble IP fragments. */
231 # OFPC_QUEUE_STATS = 1 << 6, /* Queue statistics. */
232 # OFPC_PORT_BLOCKED = 1 << 8, /* Switch will block looping ports. */
233 # OFPC_BUNDLES = 1 << 9, /* Switch supports bundles. */
234 # OFPC_FLOW_MONITORING = 1 << 10, /* Switch supports flow monitoring. */
235 # }
236 # enum ofp_port_features {
237 # OFPPF_10MB_HD = 1 << 0, /* 10 Mb half-duplex rate support. */
238 # OFPPF_10MB_FD = 1 << 1, /* 10 Mb full-duplex rate support. */
239 # OFPPF_100MB_HD = 1 << 2, /* 100 Mb half-duplex rate support. */
240 # OFPPF_100MB_FD = 1 << 3, /* 100 Mb full-duplex rate support. */
241 # OFPPF_1GB_HD = 1 << 4, /* 1 Gb half-duplex rate support. */
242 # OFPPF_1GB_FD = 1 << 5, /* 1 Gb full-duplex rate support. */
243 # OFPPF_10GB_FD = 1 << 6, /* 10 Gb full-duplex rate support. */
244 # OFPPF_40GB_FD = 1 << 7, /* 40 Gb full-duplex rate support. */
245 # OFPPF_100GB_FD = 1 << 8, /* 100 Gb full-duplex rate support. */
246 # OFPPF_1TB_FD = 1 << 9, /* 1 Tb full-duplex rate support. */
247 # OFPPF_OTHER = 1 << 10, /* Other rate, not in the list. */
248 # OFPPF_COPPER = 1 << 11, /* Copper medium. */
249 # OFPPF_FIBER = 1 << 12, /* Fiber medium. */
250 # OFPPF_AUTONEG = 1 << 13, /* Auto-negotiation. */
251 # OFPPF_PAUSE = 1 << 14, /* Pause. */
252 # OFPPF_PAUSE_ASYM = 1 << 15 /* Asymmetric pause. */
253 # }
254 # TODO: Look into adtran-physical-entities and decode xSFP type any other settings
255 return IetfInterfacesState._get_of_speed(entry) | OFPPF_FIBER
256
257 @staticmethod
258 def _get_of_speed(entry):
259 speed = IetfInterfacesState._get_speed_value(entry)
260 speed_map = {
261 1000000000: OFPPF_1GB_FD,
262 10000000000: OFPPF_10GB_FD,
263 40000000000: OFPPF_40GB_FD,
264 100000000000: OFPPF_100GB_FD,
265 }
266 # return speed_map.get(speed, OFPPF_OTHER)
267 # TODO: For now, force 100 GB
268 return OFPPF_100GB_FD
269
270 @staticmethod
271 def _get_port_number(name, if_index):
272 import re
273
274 formats = [
275 'xpon \d/{1,2}\d', # OLT version 3 (Feb 2018++)
276 'Hundred-Gigabit-Ethernet \d/\d/{1,2}\d', # OLT version 2
277 'XPON \d/\d/{1,2}\d', # OLT version 2
278 'hundred-gigabit-ethernet \d/{1,2}\d', # OLT version 1
279 'channel-termination {1,2}\d', # OLT version 1
280 ]
281 p2 = re.compile('\d+')
282
283 for regex in formats:
284 p = re.compile(regex, re.IGNORECASE)
285 match = p.match(name)
286 if match is not None:
287 return int(p2.findall(name)[-1])
288
289 @staticmethod
290 def get_port_entries(rpc_reply, port_type):
291 """
292 Get the port entries that make up the northbound and
293 southbound interfaces
294
295 :param rpc_reply:
296 :param port_type:
297 :return:
298 """
299 ports = dict()
300 result_dict = xmltodict.parse(rpc_reply.data_xml)
301 entries = result_dict['data']['interfaces-state']['interface']
302 if not isinstance(entries, list):
303 entries = [entries]
304 port_entries = [entry for entry in entries if 'name' in entry and
305 port_type.lower() in entry['name'].lower()]
306
307 for entry in port_entries:
308 port = {
309 'port_no': IetfInterfacesState._get_port_number(entry.get('name'),
310 entry.get('ifindex')),
311 'name': entry.get('name', 'unknown'),
312 'ifIndex': entry.get('ifIndex'),
313 # 'label': None,
314 'mac_address': IetfInterfacesState._get_mac_addr(entry),
315 'admin_state': IetfInterfacesState._get_admin_state(entry),
316 'oper_status': IetfInterfacesState._get_oper_status(entry),
317 'ofp_state': IetfInterfacesState._get_of_state(entry),
318 'ofp_capabilities': IetfInterfacesState._get_of_capabilities(entry),
319 'current_speed': IetfInterfacesState._get_of_speed(entry),
320 'max_speed': IetfInterfacesState._get_of_speed(entry),
321 }
322 port_no = port['port_no']
323 if port_no not in ports:
324 ports[port_no] = port
325 else:
326 ports[port_no].update(port)
327
328 return ports