blob: 0e7ebacefbca49868ff6ea48b27da9f1ac8ca38c [file] [log] [blame]
sanghoshin94872a12015-10-16 18:04:34 +09001/*
2 * Copyright 2015 Open Networking Laboratory
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.openstackswitching;
17
sanghoshin46297d22015-11-03 17:51:24 +090018import com.google.common.collect.ImmutableSet;
sanghoshin94872a12015-10-16 18:04:34 +090019import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.apache.felix.scr.annotations.Service;
27import org.onlab.packet.Ethernet;
sanghoshin94872a12015-10-16 18:04:34 +090028import org.onlab.packet.Ip4Address;
29import org.onlab.packet.Ip4Prefix;
30import org.onlab.packet.MacAddress;
sanghoshin94872a12015-10-16 18:04:34 +090031import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
danielbb83ebc2015-10-29 15:13:06 +090033import org.onosproject.dhcp.DhcpService;
sanghoshin94872a12015-10-16 18:04:34 +090034import org.onosproject.net.Device;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.Port;
sanghoshin46297d22015-11-03 17:51:24 +090037import org.onosproject.net.config.ConfigFactory;
38import org.onosproject.net.config.NetworkConfigEvent;
39import org.onosproject.net.config.NetworkConfigListener;
40import org.onosproject.net.config.NetworkConfigRegistry;
sanghoshin94872a12015-10-16 18:04:34 +090041import org.onosproject.net.device.DeviceEvent;
42import org.onosproject.net.device.DeviceListener;
43import org.onosproject.net.device.DeviceService;
44import org.onosproject.net.flowobjective.FlowObjectiveService;
45import org.onosproject.net.packet.InboundPacket;
46import org.onosproject.net.packet.PacketContext;
47import org.onosproject.net.packet.PacketProcessor;
48import org.onosproject.net.packet.PacketService;
49import org.slf4j.Logger;
50import org.slf4j.LoggerFactory;
sanghoshin94872a12015-10-16 18:04:34 +090051import java.util.List;
danielbb83ebc2015-10-29 15:13:06 +090052import java.util.Map;
sanghoshin46297d22015-11-03 17:51:24 +090053import java.util.Collection;
54import java.util.Set;
sanghoshin94872a12015-10-16 18:04:34 +090055import java.util.concurrent.ExecutorService;
56import java.util.concurrent.Executors;
sanghoshin46297d22015-11-03 17:51:24 +090057import java.util.stream.Collectors;
58
59import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
sanghoshin94872a12015-10-16 18:04:34 +090060
61@SuppressWarnings("ALL")
62@Service
63@Component(immediate = true)
64/**
sanghoshin46297d22015-11-03 17:51:24 +090065 * Populates forwarding rules for VMs created by Openstack.
sanghoshin94872a12015-10-16 18:04:34 +090066 */
67public class OpenstackSwitchingManager implements OpenstackSwitchingService {
68
69 private static Logger log = LoggerFactory
70 .getLogger(OpenstackSwitchingManager.class);
71
72 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
73 protected CoreService coreService;
74
75 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
76 protected PacketService packetService;
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected DeviceService deviceService;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected FlowObjectiveService flowObjectiveService;
83
danielbb83ebc2015-10-29 15:13:06 +090084 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
sanghoshin46297d22015-11-03 17:51:24 +090085 protected NetworkConfigRegistry cfgService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
danielbb83ebc2015-10-29 15:13:06 +090088 protected DhcpService dhcpService;
sanghoshin94872a12015-10-16 18:04:34 +090089
90 public static final int DHCP_PORT = 67;
91
92 private ApplicationId appId;
sanghoshin46297d22015-11-03 17:51:24 +090093 private boolean doNotPushFlows;
sanghoshin94872a12015-10-16 18:04:34 +090094 private OpenstackArpHandler arpHandler;
danielbb83ebc2015-10-29 15:13:06 +090095
sanghoshin94872a12015-10-16 18:04:34 +090096 private OpenstackSwitchingRulePopulator rulePopulator;
97 private ExecutorService deviceEventExcutorService = Executors.newFixedThreadPool(10);
98
99 private InternalPacketProcessor internalPacketProcessor = new InternalPacketProcessor();
100 private InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
sanghoshin46297d22015-11-03 17:51:24 +0900101 private InternalConfigListener internalConfigListener = new InternalConfigListener();
102 private final Set<ConfigFactory> factories = ImmutableSet.of(
103 new ConfigFactory<ApplicationId, OpenstackSwitchingConfig>(APP_SUBJECT_FACTORY,
104 OpenstackSwitchingConfig.class,
105 "openstackswitching") {
106 @Override
107 public OpenstackSwitchingConfig createConfig() {
108 return new OpenstackSwitchingConfig();
109 }
110 }
111 );
sanghoshin94872a12015-10-16 18:04:34 +0900112 // Map <port_id, OpenstackPort>
danielbb83ebc2015-10-29 15:13:06 +0900113 private Map<String, OpenstackPort> openstackPortMap;
sanghoshin94872a12015-10-16 18:04:34 +0900114 // Map <network_id, OpenstackNetwork>
danielbb83ebc2015-10-29 15:13:06 +0900115 private Map<String, OpenstackNetwork> openstackNetworkMap;
116 // Map <subnet_id, OpenstackSubner>
117 private Map<String, OpenstackSubnet> openstackSubnetMap;
sanghoshin94872a12015-10-16 18:04:34 +0900118 // Map <vni, List <Entry <portName, host ip>>
danielbb83ebc2015-10-29 15:13:06 +0900119 private Map<String, List<PortInfo>> vniPortMap;
120 private Map<Ip4Address, Port> tunnelPortMap;
sanghoshin94872a12015-10-16 18:04:34 +0900121
122
123 @Activate
124 protected void activate() {
125 appId = coreService
126 .registerApplication("org.onosproject.openstackswitching");
sanghoshin94872a12015-10-16 18:04:34 +0900127 packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(1));
128 deviceService.addListener(internalDeviceListener);
sanghoshin46297d22015-11-03 17:51:24 +0900129 cfgService.addListener(internalConfigListener);
130 factories.forEach(cfgService::registerConfigFactory);
131 OpenstackSwitchingConfig cfg =
132 cfgService.getConfig(appId, OpenstackSwitchingConfig.class);
133 if (cfg != null) {
134 doNotPushFlows = cfg.doNotPushFlows();
135 }
sanghoshin94872a12015-10-16 18:04:34 +0900136 openstackPortMap = Maps.newHashMap();
137 openstackNetworkMap = Maps.newHashMap();
danielbb83ebc2015-10-29 15:13:06 +0900138 openstackSubnetMap = Maps.newHashMap();
139
sanghoshin94872a12015-10-16 18:04:34 +0900140 vniPortMap = Maps.newHashMap();
141 tunnelPortMap = Maps.newHashMap();
sanghoshin46297d22015-11-03 17:51:24 +0900142
danielbb83ebc2015-10-29 15:13:06 +0900143 arpHandler = new OpenstackArpHandler(openstackPortMap, packetService);
sanghoshin46297d22015-11-03 17:51:24 +0900144
sanghoshin94872a12015-10-16 18:04:34 +0900145 log.info("Started");
146 }
147
148 @Deactivate
149 protected void deactivate() {
150 packetService.removeProcessor(internalPacketProcessor);
151 deviceService.removeListener(internalDeviceListener);
sanghoshin46297d22015-11-03 17:51:24 +0900152 cfgService.removeListener(internalConfigListener);
sanghoshin94872a12015-10-16 18:04:34 +0900153
154 deviceEventExcutorService.shutdown();
155
156 log.info("Stopped");
157 }
158
159 @Override
160 public void createPorts(OpenstackPort openstackPort) {
sanghoshin46297d22015-11-03 17:51:24 +0900161 registerDhcpInfo(openstackPort);
sanghoshin94872a12015-10-16 18:04:34 +0900162 openstackPortMap.put(openstackPort.id(), openstackPort);
163 }
164
165 @Override
166 public void deletePorts() {
167
168 }
169
170 @Override
171 public void updatePorts() {
172
173 }
174
175 @Override
176 public void createNetwork(OpenstackNetwork openstackNetwork) {
177 openstackNetworkMap.put(openstackNetwork.id(), openstackNetwork);
178 }
179
danielbb83ebc2015-10-29 15:13:06 +0900180 @Override
181 public void createSubnet(OpenstackSubnet openstackSubnet) {
182 openstackSubnetMap.put(openstackSubnet.id(), openstackSubnet);
183 log.debug("Added Subnet Info {}", openstackNetworkMap.get(openstackSubnet.id()));
184 }
185
sanghoshin46297d22015-11-03 17:51:24 +0900186 @Override
187 public Collection<OpenstackPort> ports(String networkId) {
188
189 List<OpenstackPort> portList = openstackPortMap.values().stream()
190 .filter(p -> p.networkId().equals(networkId))
191 .collect(Collectors.toList());
192
193 return portList;
194 }
195
196 @Override
197 public OpenstackPort port(String portName) {
198 String uuid = portName.substring(3);
199 return (OpenstackPort) openstackPortMap.values().stream()
200 .filter(p -> p.id().startsWith(uuid))
201 .findFirst().get().clone();
202 }
203
204 @Override
205 public OpenstackNetwork network(String networkId) {
206 OpenstackNetwork on = null;
207 try {
208 on = (OpenstackNetwork) openstackNetworkMap.get(networkId).clone();
209 } catch (CloneNotSupportedException e) {
210 log.error("Cloning is not supported {}", e);
211 }
212 return on;
213 }
214
sanghoshin94872a12015-10-16 18:04:34 +0900215 private void processDeviceAdded(Device device) {
danielbb83ebc2015-10-29 15:13:06 +0900216 log.debug("device {} is added", device.id());
sanghoshin94872a12015-10-16 18:04:34 +0900217 rulePopulator.populateDefaultRules(device.id());
218 }
219
220 private void processPortAdded(Device device, Port port) {
221 // TODO: Simplify the data structure to store the network info
222 // TODO: Make it stateless
223 // TODO: All the logics need to be processed inside of the rulePopulator class
224 synchronized (vniPortMap) {
danielbb83ebc2015-10-29 15:13:06 +0900225 log.debug("port {} is updated", port.toString());
sanghoshin94872a12015-10-16 18:04:34 +0900226
227 updatePortMaps(device, port);
228 if (!port.annotations().value("portName").equals("vxlan")) {
229 populateFlowRulesForTrafficToSameCnode(device, port);
230 populateFlowRulesForTrafficToDifferentCnode(device, port);
231 }
232 }
233 }
234
235 private void processPortRemoved(Device device, Port port) {
danielbb83ebc2015-10-29 15:13:06 +0900236 log.debug("port {} is removed", port.toString());
sanghoshin94872a12015-10-16 18:04:34 +0900237 // TODO: need to update the vniPortMap
238 }
239
sanghoshin46297d22015-11-03 17:51:24 +0900240 private void registerDhcpInfo(OpenstackPort openstackPort) {
241 Ip4Address ip4Address;
242 Ip4Address subnetMask;
243 Ip4Address dhcpServer;
244 Ip4Address gatewayIPAddress;
245 Ip4Address domainServer;
246 OpenstackSubnet openstackSubnet;
247
248 ip4Address = (Ip4Address) openstackPort.fixedIps().values().toArray()[0];
249
250 openstackSubnet = openstackSubnetMap.values().stream()
251 .filter(n -> n.networkId().equals(openstackPort.networkId()))
252 .findFirst().get();
253
254 subnetMask = Ip4Address.valueOf(buildSubnetMask(openstackSubnet.cidr()));
255 gatewayIPAddress = Ip4Address.valueOf(openstackSubnet.gatewayIp());
256 dhcpServer = gatewayIPAddress;
257 // TODO: supports multiple DNS servers
258 if (openstackSubnet.dnsNameservers().isEmpty()) {
259 domainServer = Ip4Address.valueOf("8.8.8.8");
260 } else {
261 domainServer = openstackSubnet.dnsNameservers().get(0);
262 }
263 List<Ip4Address> options = Lists.newArrayList();
264 options.add(subnetMask);
265 options.add(dhcpServer);
266 options.add(gatewayIPAddress);
267 options.add(domainServer);
268
269 dhcpService.setStaticMapping(openstackPort.macAddress(), ip4Address, true, options);
270 }
271
272 private byte[] buildSubnetMask(String cidr) {
273 int prefix;
274 String[] parts = cidr.split("/");
275 prefix = Integer.parseInt(parts[1]);
276 int mask = 0xffffffff << (32 - prefix);
277 byte[] bytes = new byte[]{(byte) (mask >>> 24),
278 (byte) (mask >> 16 & 0xff), (byte) (mask >> 8 & 0xff), (byte) (mask & 0xff)};
279
280 return bytes;
281 }
282
sanghoshin94872a12015-10-16 18:04:34 +0900283 /**
284 * Populates the flow rules for traffic to VMs in different Cnode using
285 * Nicira extention.
286 *
287 * @param device device to put rules
288 * @param port port information of the VM
289 */
290 private void populateFlowRulesForTrafficToDifferentCnode(Device device, Port port) {
291 String portName = port.annotations().value("portName");
292 String channelId = device.annotations().value("channelId");
293 Ip4Address hostIpAddress = Ip4Address.valueOf(channelId.split(":")[0]);
294 Ip4Address fixedIp = getFixedIpAddressForPort(portName);
295 // TODO: Avoid duplicate flow rule set up for VMs in other Cnode
296 // (possibly avoided by flowrule subsystem?)
297 if (tunnelPortMap.get(hostIpAddress) == null) {
danielbb83ebc2015-10-29 15:13:06 +0900298 log.debug("There is no tunnel port information");
sanghoshin94872a12015-10-16 18:04:34 +0900299 return;
300 }
301 String vni = getVniForPort(portName);
302 MacAddress vmMac = getVmMacAddressForPort(portName);
303 if (!vniPortMap.isEmpty() && vniPortMap.get(vni) != null) {
304 for (PortInfo portInfo : vniPortMap.get(vni)) {
305 if (!portInfo.portName.equals(portName) &&
306 !portInfo.hostIp.equals(hostIpAddress)) {
307 MacAddress vmMacx = getVmMacAddressForPort(portInfo.portName);
308 rulePopulator.populateForwardingRuleForOtherCnode(vni,
309 device.id(), portInfo.hostIp, portInfo.fixedIp, vmMacx,
310 tunnelPortMap.get(hostIpAddress).number(),
311 portInfo.deviceId, hostIpAddress, fixedIp, vmMac,
312 tunnelPortMap.get(portInfo.hostIp).number());
313 }
314 }
315 }
316 }
317
318 /**
319 * Populates the flow rules for traffic to VMs in the same Cnode as the sender.
320 *
321 * @param device device to put the rules
322 * @param port port info of the VM
323 */
324 private void populateFlowRulesForTrafficToSameCnode(Device device, Port port) {
325 Ip4Prefix cidr = getCidrForPort(port.annotations().value("portName"));
326 Ip4Address vmIp = getFixedIpAddressForPort(port.annotations().value("portName"));
327 if (vmIp != null) {
328 rulePopulator.populateForwardingRule(vmIp, device.id(), port, cidr);
329 }
330 }
331
332 /**
333 * Updates the port maps using the port information.
334 *
335 * @param device device info
336 * @param port port of the VM
337 */
338 private void updatePortMaps(Device device, Port port) {
339 String portName = port.annotations().value("portName");
340 String channelId = device.annotations().value("channelId");
341 Ip4Address hostIpAddress = Ip4Address.valueOf(channelId.split(":")[0]);
342 if (portName.startsWith("vxlan")) {
343 tunnelPortMap.put(hostIpAddress, port);
344 } else {
345 String vni = getVniForPort(portName);
346 Ip4Address fixedIp = getFixedIpAddressForPort(portName);
347 if (vniPortMap.get(vni) == null) {
348 vniPortMap.put(vni, Lists.newArrayList());
349 }
350 vniPortMap.get(vni).add(new PortInfo(device.id(), portName, fixedIp, hostIpAddress));
351 }
352 }
353
354 /**
355 * Returns CIDR information from the subnet map for the port.
356 *
357 * @param portName port name of the port of the VM
358 * @return CIDR of the VNI of the VM
359 */
360 private Ip4Prefix getCidrForPort(String portName) {
361 String networkId = null;
362 String uuid = portName.substring(3);
363 OpenstackPort port = openstackPortMap.values().stream()
364 .filter(p -> p.id().startsWith(uuid))
365 .findFirst().get();
366 if (port == null) {
danielbb83ebc2015-10-29 15:13:06 +0900367 log.debug("No port information for port {}", portName);
sanghoshin94872a12015-10-16 18:04:34 +0900368 return null;
369 }
370
danielbb83ebc2015-10-29 15:13:06 +0900371 OpenstackSubnet subnet = openstackSubnetMap.values().stream()
372 .filter(s -> s.networkId().equals(port.networkId()))
373 .findFirst().get();
374 if (subnet == null) {
Satish Ka91c8712015-11-20 21:13:17 +0530375 log.debug("No subnet information for network {}", port.networkId());
danielbb83ebc2015-10-29 15:13:06 +0900376 return null;
377 }
sanghoshin94872a12015-10-16 18:04:34 +0900378
danielbb83ebc2015-10-29 15:13:06 +0900379 return Ip4Prefix.valueOf(subnet.cidr());
sanghoshin94872a12015-10-16 18:04:34 +0900380 }
381
382 /**
383 * Returns the VNI of the VM of the port.
384 *
385 * @param portName VM port
386 * @return VNI
387 */
388 private String getVniForPort(String portName) {
389 String networkId = null;
390 String uuid = portName.substring(3);
391 OpenstackPort port = openstackPortMap.values().stream()
392 .filter(p -> p.id().startsWith(uuid))
393 .findFirst().get();
394 if (port == null) {
danielbb83ebc2015-10-29 15:13:06 +0900395 log.debug("No port information for port {}", portName);
sanghoshin94872a12015-10-16 18:04:34 +0900396 return null;
397 }
398 OpenstackNetwork network = openstackNetworkMap.values().stream()
399 .filter(n -> n.id().equals(port.networkId()))
400 .findFirst().get();
401 if (network == null) {
danielbb83ebc2015-10-29 15:13:06 +0900402 log.debug("No VNI information for network {}", network.id());
sanghoshin94872a12015-10-16 18:04:34 +0900403 return null;
404 }
405
406 return network.segmentId();
407 }
408
409 /**
410 * Returns the Fixed IP address of the VM.
411 *
412 * @param portName VM port info
413 * @return IP address of the VM
414 */
415 private Ip4Address getFixedIpAddressForPort(String portName) {
416
417 // FIXME - For now we use the information stored from neutron Rest API call.
418 // TODO - Later, the information needs to be extracted from Neutron on-demand.
419 String uuid = portName.substring(3);
420 OpenstackPort port = openstackPortMap.values().stream()
421 .filter(p -> p.id().startsWith(uuid))
422 .findFirst().get();
423
424 if (port == null) {
425 log.error("There is no port information for port name {}", portName);
426 return null;
427 }
428
429 if (port.fixedIps().isEmpty()) {
430 log.error("There is no fixed IP info in the port information");
431 return null;
432 }
433
434 return (Ip4Address) port.fixedIps().values().toArray()[0];
435 }
436
437 /**
438 * Returns the MAC address of the VM of the port.
439 *
440 * @param portName VM port
441 * @return MAC address of the VM
442 */
443 private MacAddress getVmMacAddressForPort(String portName) {
444
445 String uuid = portName.substring(3);
446 OpenstackPort port = openstackPortMap.values().stream()
447 .filter(p -> p.id().startsWith(uuid))
448 .findFirst().get();
449
450 if (port == null) {
451 log.error("There is no mac information for port name {}", portName);
452 return null;
453 }
454
455 return port.macAddress();
456 }
457
458 private class InternalPacketProcessor implements PacketProcessor {
459
460 @Override
461 public void process(PacketContext context) {
462
463 if (context.isHandled()) {
464 return;
465 }
466
467 InboundPacket pkt = context.inPacket();
468 Ethernet ethernet = pkt.parsed();
469
470 if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
471 arpHandler.processPacketIn(pkt);
sanghoshin94872a12015-10-16 18:04:34 +0900472 }
473 }
474 }
475
476 private class InternalDeviceListener implements DeviceListener {
477
478 @Override
sanghoshin46297d22015-11-03 17:51:24 +0900479 public void event(DeviceEvent deviceEvent) {
480 deviceEventExcutorService.execute(new InternalEventHandler(deviceEvent));
sanghoshin94872a12015-10-16 18:04:34 +0900481 }
482 }
483
484 private class InternalEventHandler implements Runnable {
485
486 volatile DeviceEvent deviceEvent;
487
488 InternalEventHandler(DeviceEvent deviceEvent) {
489 this.deviceEvent = deviceEvent;
490 }
491
492 @Override
493 public void run() {
sanghoshin46297d22015-11-03 17:51:24 +0900494
495 if (doNotPushFlows) {
496 return;
497 }
498
sanghoshin94872a12015-10-16 18:04:34 +0900499 switch (deviceEvent.type()) {
500 case DEVICE_ADDED:
501 processDeviceAdded((Device) deviceEvent.subject());
502 break;
503 case DEVICE_UPDATED:
504 Port port = (Port) deviceEvent.subject();
505 if (port.isEnabled()) {
506 processPortAdded((Device) deviceEvent.subject(), deviceEvent.port());
507 }
508 break;
509 case DEVICE_AVAILABILITY_CHANGED:
510 Device device = (Device) deviceEvent.subject();
511 if (deviceService.isAvailable(device.id())) {
512 processDeviceAdded(device);
513 }
514 break;
515 case PORT_ADDED:
516 processPortAdded((Device) deviceEvent.subject(), deviceEvent.port());
517 break;
518 case PORT_UPDATED:
519 processPortAdded((Device) deviceEvent.subject(), deviceEvent.port());
520 break;
521 case PORT_REMOVED:
522 processPortRemoved((Device) deviceEvent.subject(), deviceEvent.port());
523 break;
524 default:
525 break;
526 }
527 }
528 }
529
sanghoshin46297d22015-11-03 17:51:24 +0900530 private class InternalConfigListener implements NetworkConfigListener {
531
532 @Override
533 public void event(NetworkConfigEvent event) {
534 if (((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
535 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) &&
536 event.configClass().equals(OpenstackSwitchingConfig.class)) {
537 OpenstackSwitchingConfig cfg = cfgService.getConfig(appId,
538 OpenstackSwitchingConfig.class);
539 if (cfg != null) {
540 doNotPushFlows = cfg.doNotPushFlows();
541 log.info("Switching mode reconfigured");
542 }
543 }
544 }
545 }
546
sanghoshin94872a12015-10-16 18:04:34 +0900547 private final class PortInfo {
548 DeviceId deviceId;
549 String portName;
550 Ip4Address fixedIp;
551 Ip4Address hostIp;
552
553 private PortInfo(DeviceId deviceId, String portName, Ip4Address fixedIp,
554 Ip4Address hostIp) {
555 this.deviceId = deviceId;
556 this.portName = portName;
557 this.fixedIp = fixedIp;
558 this.hostIp = hostIp;
559 }
560 }
561
562}