sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 16 | package org.onosproject.openstackswitching; |
| 17 | |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 18 | import com.google.common.collect.ImmutableSet; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 19 | import com.google.common.collect.Lists; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 20 | import org.apache.felix.scr.annotations.Activate; |
| 21 | import org.apache.felix.scr.annotations.Component; |
| 22 | import org.apache.felix.scr.annotations.Deactivate; |
| 23 | import org.apache.felix.scr.annotations.Reference; |
| 24 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 25 | import org.apache.felix.scr.annotations.Service; |
| 26 | import org.onlab.packet.Ethernet; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 27 | import org.onlab.packet.Ip4Address; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 28 | import org.onosproject.core.ApplicationId; |
| 29 | import org.onosproject.core.CoreService; |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 30 | import org.onosproject.dhcp.DhcpService; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 31 | import org.onosproject.net.Device; |
| 32 | import org.onosproject.net.DeviceId; |
| 33 | import org.onosproject.net.Port; |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 34 | import org.onosproject.net.config.ConfigFactory; |
| 35 | import org.onosproject.net.config.NetworkConfigEvent; |
| 36 | import org.onosproject.net.config.NetworkConfigListener; |
| 37 | import org.onosproject.net.config.NetworkConfigRegistry; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 38 | import org.onosproject.net.device.DeviceEvent; |
| 39 | import org.onosproject.net.device.DeviceListener; |
| 40 | import org.onosproject.net.device.DeviceService; |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 41 | import org.onosproject.net.driver.DriverService; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 42 | import org.onosproject.net.flowobjective.FlowObjectiveService; |
| 43 | import org.onosproject.net.packet.InboundPacket; |
| 44 | import org.onosproject.net.packet.PacketContext; |
| 45 | import org.onosproject.net.packet.PacketProcessor; |
| 46 | import org.onosproject.net.packet.PacketService; |
| 47 | import org.slf4j.Logger; |
| 48 | import org.slf4j.LoggerFactory; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 49 | import java.util.List; |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 50 | import java.util.Collection; |
| 51 | import java.util.Set; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 52 | import java.util.concurrent.ExecutorService; |
| 53 | import java.util.concurrent.Executors; |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 54 | import java.util.stream.Collectors; |
| 55 | |
| 56 | import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 57 | |
| 58 | @SuppressWarnings("ALL") |
| 59 | @Service |
| 60 | @Component(immediate = true) |
| 61 | /** |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 62 | * Populates forwarding rules for VMs created by Openstack. |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 63 | */ |
| 64 | public class OpenstackSwitchingManager implements OpenstackSwitchingService { |
| 65 | |
| 66 | private static Logger log = LoggerFactory |
| 67 | .getLogger(OpenstackSwitchingManager.class); |
| 68 | |
| 69 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 70 | protected CoreService coreService; |
| 71 | |
| 72 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 73 | protected PacketService packetService; |
| 74 | |
| 75 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 76 | protected DeviceService deviceService; |
| 77 | |
| 78 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| 79 | protected FlowObjectiveService flowObjectiveService; |
| 80 | |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 81 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 82 | protected DhcpService dhcpService; |
| 83 | |
| 84 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 85 | protected NetworkConfigRegistry cfgService; |
| 86 | |
| 87 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 88 | protected DriverService driverService; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 89 | |
| 90 | private ApplicationId appId; |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 91 | private boolean doNotPushFlows; |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 92 | private Ip4Address neutronServer; |
| 93 | private Ip4Address keystoneServer; |
| 94 | private String userName; |
| 95 | private String password; |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 96 | private OpenstackArpHandler arpHandler; |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 97 | private OpenstackRestHandler restHandler; |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 98 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 99 | private ExecutorService deviceEventExcutorService = Executors.newFixedThreadPool(10); |
| 100 | |
| 101 | private InternalPacketProcessor internalPacketProcessor = new InternalPacketProcessor(); |
| 102 | private InternalDeviceListener internalDeviceListener = new InternalDeviceListener(); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 103 | private InternalConfigListener internalConfigListener = new InternalConfigListener(); |
| 104 | private final Set<ConfigFactory> factories = ImmutableSet.of( |
| 105 | new ConfigFactory<ApplicationId, OpenstackSwitchingConfig>(APP_SUBJECT_FACTORY, |
| 106 | OpenstackSwitchingConfig.class, |
| 107 | "openstackswitching") { |
| 108 | @Override |
| 109 | public OpenstackSwitchingConfig createConfig() { |
| 110 | return new OpenstackSwitchingConfig(); |
| 111 | } |
| 112 | } |
| 113 | ); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 114 | |
| 115 | @Activate |
| 116 | protected void activate() { |
| 117 | appId = coreService |
| 118 | .registerApplication("org.onosproject.openstackswitching"); |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 119 | |
| 120 | factories.forEach(cfgService::registerConfigFactory); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 121 | packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(1)); |
| 122 | deviceService.addListener(internalDeviceListener); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 123 | cfgService.addListener(internalConfigListener); |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 124 | |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 125 | internalConfigListener.configureNetwork(); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 126 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 127 | log.info("Started"); |
| 128 | } |
| 129 | |
| 130 | @Deactivate |
| 131 | protected void deactivate() { |
| 132 | packetService.removeProcessor(internalPacketProcessor); |
| 133 | deviceService.removeListener(internalDeviceListener); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 134 | cfgService.removeListener(internalConfigListener); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 135 | |
| 136 | deviceEventExcutorService.shutdown(); |
| 137 | |
| 138 | log.info("Stopped"); |
| 139 | } |
| 140 | |
| 141 | @Override |
| 142 | public void createPorts(OpenstackPort openstackPort) { |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 143 | registerDhcpInfo(openstackPort); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 144 | } |
| 145 | |
| 146 | @Override |
| 147 | public void deletePorts() { |
| 148 | |
| 149 | } |
| 150 | |
| 151 | @Override |
| 152 | public void updatePorts() { |
| 153 | |
| 154 | } |
| 155 | |
| 156 | @Override |
| 157 | public void createNetwork(OpenstackNetwork openstackNetwork) { |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 158 | } |
| 159 | |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 160 | @Override |
| 161 | public void createSubnet(OpenstackSubnet openstackSubnet) { |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 162 | } |
| 163 | |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 164 | @Override |
| 165 | public Collection<OpenstackPort> ports(String networkId) { |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 166 | Collection<OpenstackPort> ports = restHandler.getPorts(); |
| 167 | List<OpenstackPort> portList = ports.stream() |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 168 | .filter(p -> p.networkId().equals(networkId)) |
| 169 | .collect(Collectors.toList()); |
| 170 | |
| 171 | return portList; |
| 172 | } |
| 173 | |
| 174 | @Override |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 175 | public OpenstackPort port(Port port) { |
| 176 | Collection<OpenstackPort> ports = restHandler.getPorts(); |
| 177 | String uuid = port.annotations().value("portName").substring(3); |
| 178 | return ports.stream() |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 179 | .filter(p -> p.id().startsWith(uuid)) |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 180 | .findFirst().orElse(null); |
| 181 | } |
| 182 | |
| 183 | @Override |
| 184 | public OpenstackPort port(String portId) { |
| 185 | Collection<OpenstackPort> ports = restHandler.getPorts(); |
| 186 | return ports.stream() |
| 187 | .filter(p -> p.id().equals(portId)) |
| 188 | .findFirst().orElse(null); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | @Override |
| 192 | public OpenstackNetwork network(String networkId) { |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 193 | Collection<OpenstackNetwork> networks = restHandler.getNetworks(); |
| 194 | return networks.stream() |
| 195 | .filter(n -> n.id().equals(networkId)) |
| 196 | .findFirst().orElse(null); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 197 | } |
| 198 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 199 | private void processDeviceAdded(Device device) { |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 200 | log.debug("device {} is added", device.id()); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 201 | } |
| 202 | |
| 203 | private void processPortAdded(Device device, Port port) { |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 204 | if (!port.annotations().value("portName").equals("vxlan")) { |
| 205 | OpenstackSwitchingRulePopulator rulePopulator = |
| 206 | new OpenstackSwitchingRulePopulator(appId, flowObjectiveService, |
| 207 | deviceService, restHandler, driverService); |
| 208 | rulePopulator.populateSwitchingRules(device, port); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 209 | } |
| 210 | } |
| 211 | |
| 212 | private void processPortRemoved(Device device, Port port) { |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 213 | // TODO: Remove flow rules for the VM removed |
daniel | bb83ebc | 2015-10-29 15:13:06 +0900 | [diff] [blame] | 214 | log.debug("port {} is removed", port.toString()); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 215 | } |
| 216 | |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 217 | private void registerDhcpInfo(OpenstackPort openstackPort) { |
| 218 | Ip4Address ip4Address; |
| 219 | Ip4Address subnetMask; |
| 220 | Ip4Address dhcpServer; |
| 221 | Ip4Address gatewayIPAddress; |
| 222 | Ip4Address domainServer; |
| 223 | OpenstackSubnet openstackSubnet; |
| 224 | |
| 225 | ip4Address = (Ip4Address) openstackPort.fixedIps().values().toArray()[0]; |
| 226 | |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 227 | openstackSubnet = restHandler.getSubnets().stream() |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 228 | .filter(n -> n.networkId().equals(openstackPort.networkId())) |
| 229 | .findFirst().get(); |
| 230 | |
| 231 | subnetMask = Ip4Address.valueOf(buildSubnetMask(openstackSubnet.cidr())); |
| 232 | gatewayIPAddress = Ip4Address.valueOf(openstackSubnet.gatewayIp()); |
| 233 | dhcpServer = gatewayIPAddress; |
| 234 | // TODO: supports multiple DNS servers |
| 235 | if (openstackSubnet.dnsNameservers().isEmpty()) { |
| 236 | domainServer = Ip4Address.valueOf("8.8.8.8"); |
| 237 | } else { |
| 238 | domainServer = openstackSubnet.dnsNameservers().get(0); |
| 239 | } |
| 240 | List<Ip4Address> options = Lists.newArrayList(); |
| 241 | options.add(subnetMask); |
| 242 | options.add(dhcpServer); |
| 243 | options.add(gatewayIPAddress); |
| 244 | options.add(domainServer); |
| 245 | |
| 246 | dhcpService.setStaticMapping(openstackPort.macAddress(), ip4Address, true, options); |
| 247 | } |
| 248 | |
| 249 | private byte[] buildSubnetMask(String cidr) { |
| 250 | int prefix; |
| 251 | String[] parts = cidr.split("/"); |
| 252 | prefix = Integer.parseInt(parts[1]); |
| 253 | int mask = 0xffffffff << (32 - prefix); |
| 254 | byte[] bytes = new byte[]{(byte) (mask >>> 24), |
| 255 | (byte) (mask >> 16 & 0xff), (byte) (mask >> 8 & 0xff), (byte) (mask & 0xff)}; |
| 256 | |
| 257 | return bytes; |
| 258 | } |
| 259 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 260 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 261 | |
| 262 | private class InternalPacketProcessor implements PacketProcessor { |
| 263 | |
| 264 | @Override |
| 265 | public void process(PacketContext context) { |
| 266 | |
| 267 | if (context.isHandled()) { |
| 268 | return; |
| 269 | } |
| 270 | |
| 271 | InboundPacket pkt = context.inPacket(); |
| 272 | Ethernet ethernet = pkt.parsed(); |
| 273 | |
| 274 | if (ethernet.getEtherType() == Ethernet.TYPE_ARP) { |
| 275 | arpHandler.processPacketIn(pkt); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | private class InternalDeviceListener implements DeviceListener { |
| 281 | |
| 282 | @Override |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 283 | public void event(DeviceEvent deviceEvent) { |
| 284 | deviceEventExcutorService.execute(new InternalEventHandler(deviceEvent)); |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 285 | } |
| 286 | } |
| 287 | |
| 288 | private class InternalEventHandler implements Runnable { |
| 289 | |
| 290 | volatile DeviceEvent deviceEvent; |
| 291 | |
| 292 | InternalEventHandler(DeviceEvent deviceEvent) { |
| 293 | this.deviceEvent = deviceEvent; |
| 294 | } |
| 295 | |
| 296 | @Override |
| 297 | public void run() { |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 298 | |
| 299 | if (doNotPushFlows) { |
| 300 | return; |
| 301 | } |
| 302 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 303 | switch (deviceEvent.type()) { |
| 304 | case DEVICE_ADDED: |
| 305 | processDeviceAdded((Device) deviceEvent.subject()); |
| 306 | break; |
| 307 | case DEVICE_UPDATED: |
| 308 | Port port = (Port) deviceEvent.subject(); |
| 309 | if (port.isEnabled()) { |
| 310 | processPortAdded((Device) deviceEvent.subject(), deviceEvent.port()); |
| 311 | } |
| 312 | break; |
| 313 | case DEVICE_AVAILABILITY_CHANGED: |
| 314 | Device device = (Device) deviceEvent.subject(); |
| 315 | if (deviceService.isAvailable(device.id())) { |
| 316 | processDeviceAdded(device); |
| 317 | } |
| 318 | break; |
| 319 | case PORT_ADDED: |
| 320 | processPortAdded((Device) deviceEvent.subject(), deviceEvent.port()); |
| 321 | break; |
| 322 | case PORT_UPDATED: |
| 323 | processPortAdded((Device) deviceEvent.subject(), deviceEvent.port()); |
| 324 | break; |
| 325 | case PORT_REMOVED: |
| 326 | processPortRemoved((Device) deviceEvent.subject(), deviceEvent.port()); |
| 327 | break; |
| 328 | default: |
| 329 | break; |
| 330 | } |
| 331 | } |
| 332 | } |
| 333 | |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 334 | private class InternalConfigListener implements NetworkConfigListener { |
| 335 | |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 336 | public void configureNetwork() { |
| 337 | OpenstackSwitchingConfig cfg = |
| 338 | cfgService.getConfig(appId, OpenstackSwitchingConfig.class); |
| 339 | if (cfg == null) { |
| 340 | log.error("There is no openstack server information in config."); |
| 341 | return; |
| 342 | } |
| 343 | doNotPushFlows = cfg.doNotPushFlows(); |
| 344 | restHandler = new OpenstackRestHandler(cfg); |
| 345 | arpHandler = new OpenstackArpHandler(restHandler, packetService); |
| 346 | } |
| 347 | |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 348 | @Override |
| 349 | public void event(NetworkConfigEvent event) { |
| 350 | if (((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED || |
| 351 | event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) && |
| 352 | event.configClass().equals(OpenstackSwitchingConfig.class)) { |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 353 | configureNetwork(); |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 354 | } |
| 355 | } |
sanghoshin | f25d2e0 | 2015-11-11 23:07:17 +0900 | [diff] [blame^] | 356 | |
| 357 | } |
sanghoshin | 46297d2 | 2015-11-03 17:51:24 +0900 | [diff] [blame] | 358 | |
sanghoshin | 94872a1 | 2015-10-16 18:04:34 +0900 | [diff] [blame] | 359 | private final class PortInfo { |
| 360 | DeviceId deviceId; |
| 361 | String portName; |
| 362 | Ip4Address fixedIp; |
| 363 | Ip4Address hostIp; |
| 364 | |
| 365 | private PortInfo(DeviceId deviceId, String portName, Ip4Address fixedIp, |
| 366 | Ip4Address hostIp) { |
| 367 | this.deviceId = deviceId; |
| 368 | this.portName = portName; |
| 369 | this.fixedIp = fixedIp; |
| 370 | this.hostIp = hostIp; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | } |