blob: 1518137132deff0b0d273604233ec30471bfaeb9 [file] [log] [blame]
jaegonkim185299e2018-04-29 20:15:25 +09001/*
2 * Copyright 2015-present Open Networking Foundation
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 */
16package org.onosproject.cli.net;
17
18import com.google.common.base.MoreObjects;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.SetMultimap;
21import com.google.common.collect.Streams;
22import org.apache.karaf.shell.commands.Argument;
23import org.apache.karaf.shell.commands.Command;
24import org.apache.karaf.shell.commands.Option;
25import org.onosproject.cli.AbstractShellCommand;
26import org.onosproject.net.AnnotationKeys;
27import org.onosproject.net.ConnectPoint;
28import org.onosproject.net.DefaultDevice;
29import org.onosproject.net.Device;
30import org.onosproject.net.DeviceId;
31import org.onosproject.net.Link;
32import org.onosproject.net.LinkKey;
33import org.onosproject.net.Port;
34import org.onosproject.net.device.DeviceService;
35import org.onosproject.net.device.PortStatistics;
36import org.onosproject.net.flow.FlowEntry;
37import org.onosproject.net.flow.FlowRule;
38import org.onosproject.net.flow.FlowRuleService;
39import org.onosproject.net.intent.FlowRuleIntent;
40import org.onosproject.net.intent.Intent;
41import org.onosproject.net.intent.IntentService;
42import org.onosproject.net.intent.Key;
43import org.onosproject.net.intent.ObjectiveTrackerService;
44import org.onosproject.net.intent.PointToPointIntent;
45import org.onosproject.net.intent.WorkPartitionService;
46import org.onosproject.net.statistic.FlowStatisticService;
47
48import java.lang.reflect.Field;
49import java.util.ArrayList;
50import java.util.Collection;
51import java.util.HashMap;
52import java.util.HashSet;
53import java.util.List;
54import java.util.Map;
55import java.util.Objects;
56import java.util.Set;
57import java.util.stream.Stream;
58
59@Command(scope = "onos", name = "intents-diagnosis",
60 description = "Diagnosis intents")
61public class IntentsDiagnosisCommand extends AbstractShellCommand {
62
63 @Argument(index = 0, name = "key",
64 description = "Intent key",
65 required = false, multiValued = false)
66 String key = null;
67
68 @Option(name = "-d", aliases = "--details", description = "printing intent details",
69 required = false, multiValued = false)
70 private boolean dump = false;
71
72 @Option(name = "-l", aliases = "--link", description = "printing local intentsByLink",
73 required = false, multiValued = false)
74 private boolean dumpIntentByLink = false;
75
76 private static final int MAX_INTENT_PATH = 100;
77 private static final String FIELD_INTENTS_BY_LINK = "intentsByLink";
78
79 @Override
80 protected void execute() {
81
82 print("intents-diagnosis");
83 ServiceRefs svcRefs = buildServiceRefs();
84 if (svcRefs == null) {
85 return;
86 }
87 try {
88 for (Intent intent : svcRefs.intentsService().getIntents()) {
89 if (key != null && !intent.key().toString().equals(key)) {
90 continue;
91 }
92 print("");
93 printIntentHdr(intent, svcRefs);
94 if (intent instanceof PointToPointIntent) {
95 diagnosisP2Pintent((PointToPointIntent) intent, svcRefs);
96 } else {
97 // TODO : it needs to implement other types of intent
98 print(" It doesn't support %s intent.", intent.getClass().getSimpleName());
99 }
100 }
101 if (dumpIntentByLink) {
102 dumpIntentsByLink(svcRefs);
103 }
104 } catch (Exception e) {
105 print("error: " + e);
106 }
107
108 }
109
110 private void printIntentHdr(Intent intent, ServiceRefs svcRefs) {
111 print("* intent key: %s", intent.key());
112 print(" - state: %s", svcRefs.intentsService().getIntentState(intent.key()));
113 dump(" - leader: %s %s", svcRefs.getWorkPartitionService().getLeader(intent.key(), Key::hash),
114 svcRefs.workPartitionService.isMine(intent.key(), Key::hash) ? "(Mine)" : "");
115 }
116
117 private void dumpIntentsByLink(ServiceRefs svcRefs) throws Exception {
118 Set<Map.Entry<LinkKey, Key>> intentsByLink = getIntentsByLinkSet(svcRefs);
119
120 print("* intentsbylink:");
121 for (Map.Entry<LinkKey, Key> entry : intentsByLink) {
122 print(" - %s, Intents: %s ", entry.getKey(), entry.getValue());
123 }
124 }
125
126 private Set<Map.Entry<LinkKey, Key>> getIntentsByLinkSet(ServiceRefs svcRefs) throws Exception {
127
128 ObjectiveTrackerService objTracker = svcRefs.getObjectiveTrackerService();
129
130 // Utilizing reflection instead of adding new interface for getting intentsByLink
131 Field f = objTracker.getClass().getDeclaredField(FIELD_INTENTS_BY_LINK);
132 f.setAccessible(true);
133 SetMultimap<LinkKey, Key> intentsByLink = (SetMultimap<LinkKey, Key>) f.get(objTracker);
134
135 return ImmutableSet.copyOf(intentsByLink.entries());
136 }
137
138 private void diagnosisP2Pintent(PointToPointIntent intent, ServiceRefs svcRefs) throws Exception {
139
140 List<Intent> installableIntents = svcRefs.intentsService().getInstallableIntents(intent.key());
141
142 if (installableIntents.size() == 0) {
143 error("NO INSTALLABLE INTENTS");
144 return;
145 }
146
147 Set<String> notSupport = new HashSet<>();
148 for (Intent installable: installableIntents) {
149 if (installable instanceof FlowRuleIntent) {
150 checkP2PFlowRuleIntent(intent, (FlowRuleIntent) installable, svcRefs);
151 } else {
152 // TODO : it needs to implement other types of installables
153 notSupport.add(installable.getClass().getSimpleName());
154 }
155 }
156
157 if (notSupport.size() > 0) {
158 print(" It doesn't support %s.", notSupport);
159 }
160 }
161
162 private void checkP2PFlowRuleIntent(PointToPointIntent intent, FlowRuleIntent installable, ServiceRefs svcRefs)
163 throws Exception {
164
165 final Map<DeviceId, DeviceOnIntent> devs = createDevicesOnP2PIntent(intent, installable);
166
167 boolean errorOccurred = false;
168 // checking the number of links & CPs in P2P intent
169 for (DeviceOnIntent dev: devs.values()) {
170 if (dev.getIngressLinks().size() > 1) {
171 error("MULTIPLE NUMBER OF INGRESS LINKs on " + dev.deviceId()
172 + ": " + dev.getIngressLinks());
173 errorOccurred = true;
174 }
175 if (dev.getIngressCps().size() > 1) {
176 error("MULTIPLE NUMBER OF INGRESS CONNECT POINTs on " + dev.deviceId()
177 + ": " + dev.getIngressCps());
178 errorOccurred = true;
179 }
180 if (dev.getEgressLinks().size() > 1) {
181 error("MULTIPLE NUMBER OF EGRESS LINKs: on " + dev.deviceId()
182 + ": " + dev.getEgressLinks());
183 errorOccurred = true;
184 }
185 if (dev.getEgressCps().size() > 1) {
186 error("MULTIPLE NUMBER OF EGRESS CONNECT POINTs: on " + dev.deviceId()
187 + ": " + dev.getEgressCps());
188 errorOccurred = true;
189 }
190 }
191
192 ConnectPoint startCp = intent.filteredIngressPoint().connectPoint();
193 DeviceOnIntent startDev = devs.get(startCp.deviceId());
194 if (startDev == null) {
195 error("STARTING CONNECT POINT DEVICE: " + startCp.deviceId() + " is not on intent");
196 errorOccurred = true;
197 }
198
199 ConnectPoint endCp = intent.filteredEgressPoint().connectPoint();
200 DeviceOnIntent endDev = devs.get(endCp.deviceId());
201 if (endDev == null) {
202 error("END CONNECT POINT DEVICE: " + endCp.deviceId() + " is not on intent");
203 errorOccurred = true;
204 }
205
206 if (!errorOccurred) {
207 // Per device checking with path-order
208 DeviceOnIntent dev = startDev;
209 int i = 0;
210 for (; i < MAX_INTENT_PATH; i++) {
211 perDeviceChecking(dev, svcRefs);
212
213 // P2P intent has only 1 egress CP
214 ConnectPoint egressCp = dev.getEgressCps().stream().findFirst().orElse(null);
215 if (egressCp != null && Objects.equals(endCp, egressCp)) {
216 break;
217 }
218
219 // P2P intent has only 1 egress link
220 Link egressLink = dev.getEgressLinks().stream().findFirst().orElse(null);
221 if (egressLink == null) {
222 error("INVALID EGRESS LINK & CONNECT POINT for: " + dev);
223 errorOccurred = true;
224 break;
225 }
226 if (Objects.equals(egressLink.dst(), endCp)) {
227 break;
228 }
229
230 // P2P intent only 1 ingress link
231 dev = devs.values().stream()
232 .filter(nextDev -> Objects.equals(
233 egressLink, nextDev.getIngressLinks().stream().findFirst().orElse(null)))
234 .findAny().orElse(null);
235 if (dev == null) {
236 error("FAILED TO FIND NEXT DEV for: " + dev + ", LINK: " + egressLink);
237 errorOccurred = true;
238 break;
239 }
240 }
241 if (i == MAX_INTENT_PATH) {
242 error("MAX INTENT PATH WAS EXCEEDED");
243 errorOccurred = true;
244 }
245 }
246
247 if (errorOccurred) {
248 // Installable checking
249 dump("");
250 dump("ERROR OCCURRED. DO PER FLOW CHECKING");
251 perFlowRuleChecking(installable, svcRefs);
252 }
253
254 if (svcRefs.workPartitionService.isMine(intent.key(), Key::hash)) {
255 checkIntentsByLink(installable, svcRefs);
256 }
257 }
258
259 private void checkIntentsByLink(FlowRuleIntent installable, ServiceRefs svcRefs) throws Exception {
260
261 Set<Map.Entry<LinkKey, Key>> intentsByLink = getIntentsByLinkSet(svcRefs);
262
263 installable.resources().forEach(
264 rsrc -> {
265 if (rsrc instanceof Link) {
266 Link link = (Link) rsrc;
267 LinkKey linkKey = LinkKey.linkKey(link);
268 intentsByLink.stream()
269 .filter(entry -> Objects.equals(entry.getKey(), linkKey)
270 && Objects.equals(entry.getValue(), installable.key()))
271 .findAny()
272 .orElseGet(() -> {
273 error("FAILED TO FIND LINK(" + link + ") for intents: " + installable.key());
274 return null;
275 });
276 }
277 }
278 );
279 }
280
281 // TODO: It needs to consider FLowObjectiveIntent case
282 private void perDeviceChecking(DeviceOnIntent devOnIntent, ServiceRefs svcRefs) {
283
284 Collection<PortStatistics> portStats =
285 svcRefs.deviceService().getPortStatistics(devOnIntent.deviceId());
286 Collection<PortStatistics> portDeltaStats =
287 svcRefs.deviceService().getPortDeltaStatistics(devOnIntent.deviceId());
288
289 dump("");
290 dump(" ------------------------------------------------------------------------------------------");
291
292 Device device = svcRefs.deviceService.getDevice(devOnIntent.deviceId());
293 if (device == null) {
294 error("INVALID DEVICE for " + devOnIntent.deviceId());
295 return;
296 }
297
298 dump(" %s", getDeviceString(device));
299 dump(" %s", device.annotations());
300
301 devOnIntent.getIngressCps().stream()
302 .forEach(cp -> dumpCpStatistics(cp, portStats, portDeltaStats, "INGRESS", svcRefs));
303
304 Stream<FlowEntry> flowEntries = Streams.stream(svcRefs.flowService.getFlowEntries(devOnIntent.deviceId()));
305
306 devOnIntent.getFlowRules().stream()
307 .forEach(
308 intentFlowRule -> {
309 FlowEntry matchedEntry = flowEntries
310 .filter(entry -> Objects.equals(intentFlowRule.id(), entry.id()))
311 .findFirst().orElse(null);
312
313 if (matchedEntry == null) {
314 error("FAILED TO FIND FLOW ENTRY: for " + intentFlowRule);
315 return;
316 }
317
318 if (Objects.equals(intentFlowRule.selector(), matchedEntry.selector()) &&
319 Objects.equals(intentFlowRule.treatment(), matchedEntry.treatment())) {
320 dumpFlowEntry(matchedEntry, "FLOW ENTRY");
321 return;
322 }
323
324 error("INSTALLABLE-FLOW ENTRY mismatch");
325 dumpFlowRule(intentFlowRule, "INSTALLABLE");
326 dumpFlowEntry(matchedEntry, "FLOW ENTRY");
327 }
328 );
329
330 devOnIntent.getEgressCps().stream()
331 .forEach(
332 cp -> dumpCpStatistics(cp, portStats, portDeltaStats, "EGRESS", svcRefs)
333 );
334 }
335
336 // TODO: It needs to consider FLowObjectiveIntent case
337 private void perFlowRuleChecking(FlowRuleIntent installable, ServiceRefs svcRefs) {
338
339 installable.flowRules().forEach(
340 flowrule -> {
341 DeviceId devId = flowrule.deviceId();
342 if (devId == null) {
343 error("INVALID DEVICE ID for " + flowrule);
344 return;
345 }
346
347 Device dev = svcRefs.deviceService.getDevice(devId);
348 if (dev == null) {
349 error("INVALID DEVICE for " + flowrule);
350 return;
351 }
352
353 dump("");
354 dump(
355 " ------------------------------------------------------------------------------------------");
356 dump(" %s", getDeviceString(dev));
357 dump(" %s", dev.annotations());
358
359 svcRefs.flowService().getFlowEntries(devId)
360 .forEach(
361 entry -> {
362 dumpFlowRule(flowrule, "INSTALLABLE");
363 dumpFlowEntry(entry, "FLOW ENTRY");
364
365 if (!flowrule.selector().equals(entry.selector())) {
366 return;
367 }
368 if (flowrule.id().equals(entry.id()) &&
369 flowrule.treatment().equals(entry.treatment())) {
370 dumpFlowEntry(entry, "FLOW ENTRY");
371 return;
372 }
373 error("INSTALLABLE-FLOW ENTRY mismatch");
374 }
375 );
376 }
377 );
378 }
379
380 private Map<DeviceId, DeviceOnIntent> createDevicesOnP2PIntent(
381 PointToPointIntent intent, FlowRuleIntent flowRuleIntent) {
382
383 final Map<DeviceId, DeviceOnIntent> devMap = new HashMap<>();
384
385 flowRuleIntent.resources().forEach(
386 rsrc -> {
387 if (rsrc instanceof Link) {
388 Link link = (Link) rsrc;
389 ConnectPoint srcCp = link.src();
390 ConnectPoint dstCp = link.dst();
391 try {
392 DeviceOnIntent dev = devMap.computeIfAbsent(srcCp.deviceId(), DeviceOnIntent::new);
393 if (dev != null) {
394 dev.addEgressLink(link);
395 }
396 dev = devMap.computeIfAbsent(dstCp.deviceId(), DeviceOnIntent::new);
397 if (dev != null) {
398 dev.addIngressLink(link);
399 }
400 } catch (IllegalStateException e) {
401 print("error: " + e);
402 }
403 }
404 }
405 );
406
407 ConnectPoint startCp = intent.filteredIngressPoint().connectPoint();
408 DeviceOnIntent startDev = devMap.computeIfAbsent(startCp.deviceId(), DeviceOnIntent::new);
409 if (!startDev.hasIngressCp(startCp)) {
410 startDev.addIngressCp(startCp);
411 }
412
413 ConnectPoint endCp = intent.filteredEgressPoint().connectPoint();
414 DeviceOnIntent endDev = devMap.computeIfAbsent(endCp.deviceId(), DeviceOnIntent::new);
415 if (!endDev.hasEgressCp(endCp)) {
416 endDev.addEgessCp(endCp);
417 }
418
419 flowRuleIntent.flowRules().forEach(
420 flowRule -> {
421 DeviceId devId = flowRule.deviceId();
422 if (devId == null) {
423 error("INVALID DEVICE ID for " + flowRule);
424 return;
425 }
426 DeviceOnIntent dev = devMap.get(devId);
427 if (dev == null) {
428 error("DEVICE(" + devId + ") IS NOT ON INTENTS LINKS");
429 return;
430 }
431
432 dev.addFlowRule(flowRule);
433 }
434 );
435
436 return devMap;
437 }
438
439 private String getDeviceString(Device dev) {
440
441 StringBuilder buf = new StringBuilder();
442 if (dev != null) {
443 buf.append(String.format("Device: %s, ", dev.id()));
444 buf.append(String.format("%s, ", dev.type()));
445 buf.append(String.format("%s, ", dev.manufacturer()));
446 buf.append(String.format("%s, ", dev.hwVersion()));
447 buf.append(String.format("%s, ", dev.swVersion()));
448 if (dev instanceof DefaultDevice) {
449 DefaultDevice dfltDev = (DefaultDevice) dev;
450 if (dfltDev.driver() != null) {
451 buf.append(String.format("%s, ", dfltDev.driver().name()));
452 }
453 String channelId = dfltDev.annotations().value("channelId");
454 if (channelId != null) {
455 buf.append(String.format("%s, ", channelId));
456 }
457 }
458 }
459
460 return buf.toString();
461 }
462
463 private void dumpFlowRule(FlowRule rule, String hdr) {
464 dump(" " + hdr + ":");
465 dump(" - id=%s, priority=%d", rule.id(), rule.priority());
466 dump(" - %s", rule.selector());
467 dump(" - %s", rule.treatment());
468 }
469
470 private void dumpFlowEntry(FlowEntry entry, String hdr) {
471 dumpFlowRule(entry, hdr);
472 dump(" - packets=%d", entry.packets());
473 }
474
475
476 private void dumpCpStatistics(ConnectPoint cp, Collection<PortStatistics> devPortStats,
477 Collection<PortStatistics> devPortDeltaStats, String direction, ServiceRefs svcs) {
478 if (cp == null) {
479 return;
480 }
481
482 dump(" %s:", direction);
483
484 if (cp.port().isLogical()) {
485 dump(" - logical: device: %s, port: %s", cp.deviceId(), cp.port());
486 return;
487 }
488
489 Port port = svcs.deviceService.getPort(cp.deviceId(), cp.port());
490 if (port == null) {
491 return;
492 }
493
494 try {
495 devPortStats.stream()
496 .filter(stat -> stat.portNumber().equals(cp.port()))
497 .forEach(stat -> dump(" - stat : %s:", getPortStatStr(stat, port)));
498 } catch (IllegalStateException e) {
499 error("error: " + e);
500 return;
501 }
502
503 try {
504 devPortDeltaStats.stream()
505 .filter(stat -> stat.portNumber().equals(cp.port()))
506 .forEach(stat -> dump(" - delta : %s:", getPortStatStr(stat, port)));
507 } catch (IllegalStateException e) {
508 error("error: " + e);
509 }
510 }
511
512 private void dump(String format, Object ... args) {
513 if (dump) {
514 print(format, args);
515 }
516 }
517
518 private String getPortStatStr(PortStatistics stat, Port port) {
519
520 final String portName = port.annotations().value(AnnotationKeys.PORT_NAME);
521
522 return String.format("port: %s(%s), ", stat.portNumber(), portName) +
523 String.format("enabled: %b, ", port.isEnabled()) +
524 String.format("pktRx: %d, ", stat.packetsReceived()) +
525 String.format("pktTx: %d, ", stat.packetsSent()) +
526 String.format("pktRxErr: %d, ", stat.packetsRxErrors()) +
527 String.format("pktTxErr: %d, ", stat.packetsTxErrors()) +
528 String.format("pktRxDrp: %d, ", stat.packetsRxDropped()) +
529 String.format("pktTxDrp: %d", stat.packetsTxDropped());
530 }
531
532 private static class DeviceOnIntent {
533
534 private final DeviceId devId;
535
536 private Collection<Link> ingressLinks = new ArrayList<>();
537
538 private Collection<Link> egressLinks = new ArrayList<>();
539
540 private Collection<ConnectPoint> ingressCps = new ArrayList<>();
541
542 private Collection<ConnectPoint> egressCps = new ArrayList<>();
543
544 private Collection<FlowRule> flowRules = new ArrayList<>();
545
546 public DeviceOnIntent(DeviceId devId) {
547 this.devId = devId;
548 }
549
550 public DeviceId deviceId() {
551 return devId;
552 }
553
554 public Collection<Link> getIngressLinks() {
555 return ingressLinks;
556 }
557
558 public Collection<Link> getEgressLinks() {
559 return egressLinks;
560 }
561
562 public void addIngressLink(Link link) {
563 ingressLinks.add(link);
564 addIngressCp(link.dst());
565 }
566
567 public void addEgressLink(Link link) {
568 egressLinks.add(link);
569 addEgessCp(link.src());
570 }
571
572 public void addIngressCp(ConnectPoint cp) {
573 ingressCps.add(cp);
574 }
575
576 public void addEgessCp(ConnectPoint cp) {
577 egressCps.add(cp);
578 }
579
580 public boolean hasIngressCp(final ConnectPoint cp) {
581 return ingressCps.stream().anyMatch(icp -> Objects.equals(icp, cp));
582 }
583
584 public boolean hasEgressCp(ConnectPoint cp) {
585 return egressCps.stream().anyMatch(ecp -> Objects.equals(ecp, cp));
586 }
587
588 public Collection<ConnectPoint> getIngressCps() {
589 return ingressCps;
590 }
591
592 public Collection<ConnectPoint> getEgressCps() {
593 return egressCps;
594 }
595
596 public Collection<FlowRule> getFlowRules() {
597 return flowRules;
598 }
599
600 public void addFlowRule(FlowRule flowRule) {
601 flowRules.add(flowRule);
602 }
603
604 public String toString() {
605 return MoreObjects.toStringHelper(getClass())
606 .omitNullValues()
607 .add("devId", devId)
608 .add("ingressLinks", ingressLinks)
609 .add("egressLinks", egressLinks)
610 .add("flowRules", flowRules)
611 .toString();
612 }
613 }
614
615
616 private ServiceRefs buildServiceRefs() {
617 IntentService intentsService = get(IntentService.class);
618 if (intentsService == null) {
619 return null;
620 }
621 DeviceService deviceService = get(DeviceService.class);
622 if (deviceService == null) {
623 return null;
624 }
625 FlowStatisticService flowStatsService = get(FlowStatisticService.class);
626 if (flowStatsService == null) {
627 return null;
628 }
629 FlowRuleService flowService = get(FlowRuleService.class);
630 if (flowService == null) {
631 return null;
632 }
633 WorkPartitionService workPartitionService = get(WorkPartitionService.class);
634 if (workPartitionService == null) {
635 return null;
636 }
637 ObjectiveTrackerService objectiveTrackerService = get(ObjectiveTrackerService.class);
638 if (objectiveTrackerService == null) {
639 return null;
640 }
641
642 return new ServiceRefs(
643 intentsService,
644 deviceService,
645 flowService,
646 workPartitionService,
647 objectiveTrackerService
648 );
649 }
650
651 private static final class ServiceRefs {
652
653 private IntentService intentsService;
654 private DeviceService deviceService;
655 private FlowRuleService flowService;
656 private WorkPartitionService workPartitionService;
657 private ObjectiveTrackerService objectiveTrackerService;
658
659 private ServiceRefs(
660 IntentService intentsService,
661 DeviceService deviceService,
662 FlowRuleService flowService,
663 WorkPartitionService workPartitionService,
664 ObjectiveTrackerService objectiveTrackerService
665 ) {
666 this.intentsService = intentsService;
667 this.deviceService = deviceService;
668 this.flowService = flowService;
669 this.workPartitionService = workPartitionService;
670 this.objectiveTrackerService = objectiveTrackerService;
671 }
672
673 public IntentService intentsService() {
674 return intentsService;
675 }
676
677 public DeviceService deviceService() {
678 return deviceService;
679 }
680
681 public FlowRuleService flowService() {
682 return flowService;
683 }
684
685 public WorkPartitionService getWorkPartitionService() {
686 return workPartitionService;
687 }
688
689 public ObjectiveTrackerService getObjectiveTrackerService() {
690 return objectiveTrackerService;
691 }
692 }
693
694}