blob: 619beb17eea1fd566ca78b6724a95c95d143145e [file] [log] [blame]
Zsolt Haraszti656ecc62016-12-28 15:08:23 -08001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Simple PON Simulator which would not be needed if openvswitch could do
19802.1ad (QinQ), which it cannot (the reason is beyond me), or if CPQD could
20handle 0-tagged packets (no comment).
21"""
22import structlog
Stephane Barbarie4475a252017-03-31 13:49:20 -040023import random
24import arrow
25import json
26from scapy.layers.inet import IP, UDP, TCP, Raw
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080027from scapy.layers.l2 import Ether, Dot1Q
28from scapy.packet import Packet
29
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080030from voltha.protos import third_party
Sergio Slobodrian98eff412017-03-15 14:46:30 -040031from voltha.protos.ponsim_pb2 import PonSimMetrics, PonSimPortMetrics, \
Khen Nursimulu49792142017-03-17 12:34:05 -040032 PonSimPacketCounter
Stephane Barbarie4475a252017-03-31 13:49:20 -040033from voltha.protos.events_pb2 import AlarmEventType, AlarmEventSeverity, \
34 AlarmEventState, AlarmEventCategory
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080035from voltha.core.flow_decomposer import *
Sergio Slobodrian98eff412017-03-15 14:46:30 -040036from twisted.internet.task import LoopingCall
Stephane Barbarie4475a252017-03-31 13:49:20 -040037from twisted.internet import reactor
Khen Nursimulu49792142017-03-17 12:34:05 -040038
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080039_ = third_party
40
41
42def ipv4int2str(ipv4int):
43 return '{}.{}.{}.{}'.format(
44 (ipv4int >> 24) & 0xff,
45 (ipv4int >> 16) & 0xff,
46 (ipv4int >> 8) & 0xff,
47 ipv4int & 0xff
48 )
49
50
Khen Nursimulu49792142017-03-17 12:34:05 -040051class _FlowMatchMask(object):
52 """
53 Enum of mask values based on flow match priority. For instance, a port
54 match has higher priority when match that a UDP match.
55 """
56 UDP_DST = 1
57 UDP_SRC = 2
58 IPV4_DST = 4
59 VLAN_PCP = 8
60 VLAN_VID = 16
61 IP_PROTO = 34
62 ETH_TYPE = 64
63 IN_PORT = 128
64
65
Sergio Slobodrian98eff412017-03-15 14:46:30 -040066class FrameIOCounter(object):
67 class SingleFrameCounter(object):
Khen Nursimulu49792142017-03-17 12:34:05 -040068 def __init__(self, name, min, max):
Sergio Slobodrian98eff412017-03-15 14:46:30 -040069 # Currently there are 2 values, one for the PON interface (port 1)
70 # and one for the Network Interface (port 2). This can be extended if
71 # the virtual devices extend the number of ports.
Khen Nursimulu49792142017-03-17 12:34:05 -040072 self.value = [0, 0] # {PON,NI}
Sergio Slobodrian98eff412017-03-15 14:46:30 -040073 self.name = name
Khen Nursimulu49792142017-03-17 12:34:05 -040074 self.min = min
Sergio Slobodrian98eff412017-03-15 14:46:30 -040075 self.max = max
76
77 def __init__(self, device):
78 self.device = device
Khen Nursimulu49792142017-03-17 12:34:05 -040079 self.tx_counters = dict(
Sergio Slobodrian85200812017-04-03 19:09:11 -040080 tx_64_pkts=self.SingleFrameCounter("tx_64_pkts", 1, 64),
81 tx_65_127_pkts=self.SingleFrameCounter("tx_65_127_pkts", 65, 127),
82 tx_128_255_pkts=self.SingleFrameCounter("tx_128_255_pkts", 128, 255),
83 tx_256_511_pkts=self.SingleFrameCounter("tx_256_511_pkts", 256, 511),
84 tx_512_1023_pkts=self.SingleFrameCounter("tx_512_1023_pkts", 512, 1024),
85 tx_1024_1518_pkts=self.SingleFrameCounter("tx_1024_1518_pkts", 1024, 1518),
86 tx_1519_9k_pkts=self.SingleFrameCounter("tx_1519_9k_pkts", 1519, 9216),
Sergio Slobodrian98eff412017-03-15 14:46:30 -040087 )
88 self.rx_counters = dict(
Sergio Slobodrian85200812017-04-03 19:09:11 -040089 rx_64_pkts=self.SingleFrameCounter("rx_64_pkts", 1, 64),
90 rx_65_127_pkts=self.SingleFrameCounter("rx_65_127_pkts", 65, 127),
91 rx_128_255_pkts=self.SingleFrameCounter("rx_128_255_pkts", 128, 255),
92 rx_256_511_pkts=self.SingleFrameCounter("rx_256_511_pkts", 256, 511),
93 rx_512_1023_pkts=self.SingleFrameCounter("rx_512_1023_pkts", 512, 1024),
94 rx_1024_1518_pkts=self.SingleFrameCounter("rx_1024_1518_pkts", 1024, 1518),
95 rx_1519_9k_pkts=self.SingleFrameCounter("rx_1519_9k_pkts", 1519, 9216)
Sergio Slobodrian98eff412017-03-15 14:46:30 -040096 )
97
98 def count_rx_frame(self, port, size):
99 log.info("counting-rx-frame", size=size, port=port)
Khen Nursimulu49792142017-03-17 12:34:05 -0400100 for k, v in self.rx_counters.iteritems():
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400101 if size >= v.min and size <= v.max:
Khen Nursimulu49792142017-03-17 12:34:05 -0400102 self.rx_counters[k].value[port - 1] += 1
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400103 return
104 log.warn("unsupported-packet-size", size=size)
105
106 def count_tx_frame(self, port, size):
107 for k, v in self.tx_counters.iteritems():
108 if size >= v.min and size <= v.max:
Khen Nursimulu49792142017-03-17 12:34:05 -0400109 self.tx_counters[k].value[port - 1] += 1
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400110 return
111 log.warn("unsupported-packet-size", size=size)
112
113 def log_counts(self):
Khen Nursimulu49792142017-03-17 12:34:05 -0400114 rx_ct_list = [(v.name, v.value[0], v.value[1]) for v in
115 self.rx_counters.values()]
116 tx_ct_list = [(v.name, v.value[0], v.value[1]) for v in
117 self.tx_counters.values()]
118 log.info("rx-counts", rx_ct_list=rx_ct_list)
119 log.info("tx-counts", tx_ct_list=tx_ct_list)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400120
121 def make_proto(self):
122 sim_metrics = PonSimMetrics(
Khen Nursimulu49792142017-03-17 12:34:05 -0400123 device=self.device
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400124 )
Khen Nursimulu49792142017-03-17 12:34:05 -0400125 pon_port_metrics = PonSimPortMetrics(
126 port_name="pon"
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400127 )
Khen Nursimulu49792142017-03-17 12:34:05 -0400128 ni_port_metrics = PonSimPortMetrics(
129 port_name="nni"
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400130 )
131 for c in sorted(self.rx_counters):
132 ctr = self.rx_counters[c]
133 pon_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400134 PonSimPacketCounter(name=ctr.name, value=ctr.value[0])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400135 ])
136 # Since they're identical keys, save some time and cheat
137 ni_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400138 PonSimPacketCounter(name=ctr.name, value=ctr.value[1])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400139 ])
140
141 for c in sorted(self.tx_counters):
142 ctr = self.tx_counters[c]
143 pon_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400144 PonSimPacketCounter(name=ctr.name, value=ctr.value[0])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400145 ])
146 # Since they're identical keys, save some time and cheat
147 ni_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400148 PonSimPacketCounter(name=ctr.name, value=ctr.value[1])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400149 ])
150 sim_metrics.metrics.extend([pon_port_metrics])
151 sim_metrics.metrics.extend([ni_port_metrics])
152
153 return sim_metrics
154
155
Stephane Barbarie4475a252017-03-31 13:49:20 -0400156class SimAlarms:
157 def __init__(self):
158 self.lc = None
159
160 @staticmethod
161 def _prepare_alarm():
162 alarm_event = dict()
163
164 try:
165 # Randomly choose values for each enum types
166 alm_severity = random.choice(list(
167 v for k, v in
168 AlarmEventSeverity.DESCRIPTOR.enum_values_by_name.items()))
169
170 alm_type = random.choice(list(
171 v for k, v in
172 AlarmEventType.DESCRIPTOR.enum_values_by_name.items()))
173
174 alm_category = random.choice(list(
175 v for k, v in
176 AlarmEventCategory.DESCRIPTOR.enum_values_by_name.items()))
177
178 alarm_event['severity'] = alm_severity.number
179 alarm_event['type'] = alm_type.number
180 alarm_event['category'] = alm_category.number
181 alarm_event['state'] = AlarmEventState.RAISED
182 alarm_event['ts'] = arrow.utcnow().timestamp
183 alarm_event['description'] = "{}.{} alarm".format(alm_type.name, alm_category.name)
184
185 return alarm_event
186
187 except Exception as e:
188 log.exception('failed-to-prepare-alarm', e=e)
189
190 @staticmethod
191 def _raise_alarm(alarm_event, olt, egress):
192 try:
193 frame = Ether() / Dot1Q(vlan=4000) / IP() / TCP() / Raw(load=json.dumps(alarm_event))
194 egress(0, frame)
195
196 except Exception as e:
197 log.exception('failed-to-raise-alarm', e=e)
198
199 @staticmethod
200 def _clear_alarm(alarm_event, olt, egress):
201 try:
202 alarm_event['state'] = AlarmEventState.CLEARED
203 frame = Ether() / Dot1Q(vlan=4000) / IP() / TCP() / Raw(load=json.dumps(alarm_event))
204 egress(0, frame)
205
206 except Exception as e:
207 log.exception('failed-to-clear-alarm', e=e)
208
209 def _generate_alarm(self, olt, egress):
210 try:
211 alarm = self._prepare_alarm()
212 self._raise_alarm(alarm, olt, egress)
213 reactor.callLater(random.randint(20, 60), self._clear_alarm, alarm, olt, egress)
214 except Exception as e:
215 log.exception(e=e)
216
217 def start_simulation(self, olt, egress, config):
218 log.info("starting-alarm-simulation")
219
220 """Simulate periodic device alarms"""
221 self.lc = LoopingCall(self._generate_alarm, olt, egress)
222 self.lc.start(config['frequency'])
223
224 def stop_simulation(self):
225 log.info("stopping-alarm-simulation")
226 self.lc.stop()
227
228
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800229class SimDevice(object):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800230 def __init__(self, name, logical_port_no):
231 self.name = name
232 self.logical_port_no = logical_port_no
233 self.links = dict()
234 self.flows = list()
235 self.log = structlog.get_logger(name=name,
236 logical_port_no=logical_port_no)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400237 self.counter = FrameIOCounter(name)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800238
239 def link(self, port, egress_fun):
240 self.links.setdefault(port, []).append(egress_fun)
241
242 def ingress(self, port, frame):
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400243 self.log.debug('ingress', ingress_port=port, name=self.name)
244 self.counter.count_rx_frame(port, len(frame["Ether"].payload))
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800245 outcome = self.process_frame(port, frame)
246 if outcome is not None:
247 egress_port, egress_frame = outcome
248 forwarded = 0
249 links = self.links.get(egress_port)
250 if links is not None:
Khen Nursimulu49792142017-03-17 12:34:05 -0400251 self.counter.count_tx_frame(egress_port,
252 len(egress_frame["Ether"].payload))
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800253 for fun in links:
254 forwarded += 1
255 self.log.debug('forwarding', egress_port=egress_port)
256 fun(egress_port, egress_frame)
257 if not forwarded:
258 self.log.debug('no-one-to-forward-to', egress_port=egress_port)
259 else:
260 self.log.debug('dropped')
261
262 def install_flows(self, flows):
263 # store flows in precedence order so we can roll down on frame arrival
264 self.flows = sorted(flows, key=lambda fm: fm.priority, reverse=True)
265
266 def process_frame(self, ingress_port, ingress_frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400267 matched_mask = 0
Khen Nursimulu4f940622017-03-17 17:45:02 -0400268 highest_priority = 0
Khen Nursimulu49792142017-03-17 12:34:05 -0400269 matched_flow = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800270 for flow in self.flows:
Khen Nursimulu4f940622017-03-17 17:45:02 -0400271 # flows are sorted by highest priority.
272 if matched_flow and flow.priority < highest_priority:
273 break
274
275 highest_priority = flow.priority
Khen Nursimulu49792142017-03-17 12:34:05 -0400276 current_mask = self.is_match(flow, ingress_port, ingress_frame)
277 if current_mask > matched_mask:
278 matched_mask = current_mask
279 matched_flow = flow
280
281 if matched_flow:
282 egress_port, egress_frame = self.process_actions(
283 matched_flow, ingress_frame)
284 return egress_port, egress_frame
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800285 return None
286
287 @staticmethod
288 def is_match(flow, ingress_port, frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400289 matched_mask = 0
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800290
291 def get_non_shim_ether_type(f):
292 if f.haslayer(Dot1Q):
293 f = f.getlayer(Dot1Q)
294 return f.type
295
296 def get_vlan_pcp(f):
297 if f.haslayer(Dot1Q):
298 return f.getlayer(Dot1Q).prio
299
300 def get_ip_proto(f):
301 if f.haslayer(IP):
302 return f.getlayer(IP).proto
303
304 def get_ipv4_dst(f):
305 if f.haslayer(IP):
306 return f.getlayer(IP).dst
307
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800308 def get_udp_src(f):
309 if f.haslayer(UDP):
310 return f.getlayer(UDP).sport
311
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800312 def get_udp_dst(f):
313 if f.haslayer(UDP):
314 return f.getlayer(UDP).dport
315
316 for field in get_ofb_fields(flow):
317
318 if field.type == IN_PORT:
319 if field.port != ingress_port:
Khen Nursimulu49792142017-03-17 12:34:05 -0400320 return 0
321 matched_mask |= _FlowMatchMask.IN_PORT
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800322
323 elif field.type == ETH_TYPE:
324 if field.eth_type != get_non_shim_ether_type(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400325 return 0
326 matched_mask |= _FlowMatchMask.ETH_TYPE
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800327
328 elif field.type == IP_PROTO:
329 if field.ip_proto != get_ip_proto(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400330 return 0
331 matched_mask |= _FlowMatchMask.IP_PROTO
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800332
333 elif field.type == VLAN_VID:
334 expected_vlan = field.vlan_vid
335 tagged = frame.haslayer(Dot1Q)
336 if bool(expected_vlan & 4096) != bool(tagged):
Khen Nursimulu49792142017-03-17 12:34:05 -0400337 return 0
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800338 if tagged:
339 actual_vid = frame.getlayer(Dot1Q).vlan
340 if actual_vid != expected_vlan & 4095:
Khen Nursimulu49792142017-03-17 12:34:05 -0400341 return 0
342 matched_mask |= _FlowMatchMask.VLAN_VID
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800343
344 elif field.type == VLAN_PCP:
345 if field.vlan_pcp != get_vlan_pcp(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400346 return 0
347 matched_mask |= _FlowMatchMask.VLAN_PCP
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800348
349 elif field.type == IPV4_DST:
350 if ipv4int2str(field.ipv4_dst) != get_ipv4_dst(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400351 return 0
352 matched_mask |= _FlowMatchMask.IPV4_DST
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800353
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800354 elif field.type == UDP_SRC:
355 if field.udp_src != get_udp_src(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400356 return 0
357 matched_mask |= _FlowMatchMask.UDP_SRC
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800358
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800359 elif field.type == UDP_DST:
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800360 if field.udp_dst != get_udp_dst(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400361 return 0
362 matched_mask |= _FlowMatchMask.UDP_DST
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800363
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800364 elif field.type == METADATA:
365 pass # safe to ignore
366
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800367 else:
368 raise NotImplementedError('field.type=%d' % field.type)
369
Khen Nursimulu49792142017-03-17 12:34:05 -0400370 return matched_mask
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800371
372 @staticmethod
373 def process_actions(flow, frame):
374 egress_port = None
375 for action in get_actions(flow):
376
377 if action.type == OUTPUT:
378 egress_port = action.output.port
379
380 elif action.type == POP_VLAN:
381 if frame.haslayer(Dot1Q):
382 shim = frame.getlayer(Dot1Q)
383 frame = Ether(
384 src=frame.src,
385 dst=frame.dst,
386 type=shim.type) / shim.payload
387
388 elif action.type == PUSH_VLAN:
389 frame = (
390 Ether(src=frame.src, dst=frame.dst,
391 type=action.push.ethertype) /
392 Dot1Q(type=frame.type) /
393 frame.payload
394 )
395
396 elif action.type == SET_FIELD:
397 assert (action.set_field.field.oxm_class ==
398 ofp.OFPXMC_OPENFLOW_BASIC)
399 field = action.set_field.field.ofb_field
400
401 if field.type == VLAN_VID:
402 shim = frame.getlayer(Dot1Q)
403 shim.vlan = field.vlan_vid & 4095
404
405 elif field.type == VLAN_PCP:
406 shim = frame.getlayer(Dot1Q)
407 shim.prio = field.vlan_pcp
408
409 else:
410 raise NotImplementedError('set_field.field.type=%d'
411 % field.type)
412
413 else:
414 raise NotImplementedError('action.type=%d' % action.type)
415
416 return egress_port, frame
417
418
419class PonSim(object):
Stephane Barbarie4475a252017-03-31 13:49:20 -0400420 def __init__(self, onus, egress_fun, alarm_config):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800421 self.egress_fun = egress_fun
422
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400423 self.log = structlog.get_logger()
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800424 # Create OLT and hook NNI port up for egress
425 self.olt = SimDevice('olt', 0)
426 self.olt.link(2, lambda _, frame: self.egress_fun(0, frame))
427 self.devices = dict()
428 self.devices[0] = self.olt
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400429 # TODO: This can be removed, it's for debugging purposes
430 self.lc = LoopingCall(self.olt.counter.log_counts)
Khen Nursimulu49792142017-03-17 12:34:05 -0400431 self.lc.start(90) # To correlate with Kafka
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800432
433 # Create ONUs of the requested number and hook them up with OLT
434 # and with egress fun
435 def mk_egress_fun(port_no):
436 return lambda _, frame: self.egress_fun(port_no, frame)
437
438 def mk_onu_ingress(onu):
439 return lambda _, frame: onu.ingress(1, frame)
440
441 for i in range(onus):
442 port_no = 128 + i
443 onu = SimDevice('onu%d' % i, port_no)
Khen Nursimulu49792142017-03-17 12:34:05 -0400444 onu.link(1, lambda _, frame: self.olt.ingress(1,
445 frame)) # Send to the OLT
446 onu.link(2,
447 mk_egress_fun(port_no)) # Send from the ONU to the world
448 self.olt.link(1, mk_onu_ingress(onu)) # Internal send to the ONU
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800449 self.devices[port_no] = onu
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400450 for d in self.devices:
451 self.log.info("pon-sim-init", port=d, name=self.devices[d].name,
452 links=self.devices[d].links)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800453
Stephane Barbarie4475a252017-03-31 13:49:20 -0400454 if alarm_config['simulation']:
455 self.alarms = SimAlarms()
456 self.alarms.start_simulation(self.olt, self.egress_fun, alarm_config)
457
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800458 def get_ports(self):
459 return sorted(self.devices.keys())
460
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400461 def get_stats(self):
462 return self.olt.counter.make_proto()
463
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800464 def olt_install_flows(self, flows):
465 self.olt.install_flows(flows)
466
467 def onu_install_flows(self, onu_port, flows):
468 self.devices[onu_port].install_flows(flows)
469
470 def ingress(self, port, frame):
471 if not isinstance(frame, Packet):
472 frame = Ether(frame)
473 self.devices[port].ingress(2, frame)
Nikolay Titov89004ec2017-06-19 18:22:42 -0400474
475class XPonSim(object):
476 def __init__(self):
477 self.log = structlog.get_logger()
478
479 def CreateInterface(self, request):
480 self.log.info("create-interface-request", interface_type = request.WhichOneof("interface_type"), data = request)
481 return
482
483 def UpdateInterface(self, request):
484 self.log.info("update-interface-request", interface_type = request.WhichOneof("interface_type"), data = request)
485 return
486
487 def RemoveInterface(self, request):
488 self.log.info("remove-interface-request", interface_type = request.WhichOneof("interface_type"), data = request)
489 return