From bf51f8202f4518087bc086e77ff558fe08982532 Mon Sep 17 00:00:00 2001 From: Rob Adams <rob.adams@bigswitch.com> Date: Tue, 13 Mar 2012 18:10:42 -0700 Subject: [PATCH] Add device service rest API --- .../devicemanager/internal/Device.java | 3 + .../internal/DeviceManagerImpl.java | 59 ++------- .../web/AbstractDeviceResource.java | 124 ++++++++++++++++++ .../devicemanager/web/DeviceResource.java | 47 +++++++ .../devicemanager/web/DeviceRoutable.java | 43 ++++++ .../devicemanager/web/DeviceSerializer.java | 70 ++++++++++ .../restserver/RestApiServer.java | 13 ++ .../internal/DeviceManagerImplTest.java | 58 -------- 8 files changed, 313 insertions(+), 104 deletions(-) create mode 100644 src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java create mode 100644 src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java create mode 100644 src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java create mode 100644 src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java index 0952eeed4..22b7ed23a 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java @@ -25,9 +25,11 @@ import java.util.EnumSet; import java.util.Iterator; import java.util.TreeSet; +import org.codehaus.jackson.map.annotate.JsonSerialize; import org.openflow.util.HexString; import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField; +import net.floodlightcontroller.devicemanager.web.DeviceSerializer; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.SwitchPort; @@ -37,6 +39,7 @@ import net.floodlightcontroller.topology.ITopologyService; * Concrete implementation of {@link IDevice} * @author readams */ +@JsonSerialize(using=DeviceSerializer.class) public class Device implements IDevice { protected Long deviceKey; protected DeviceManagerImpl deviceManager; diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java index b506ccdd0..5a5e8a54b 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java @@ -41,7 +41,6 @@ import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; @@ -51,15 +50,16 @@ import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.IEntityClassifier; import net.floodlightcontroller.devicemanager.IDeviceListener; +import net.floodlightcontroller.devicemanager.web.DeviceRoutable; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.DHCP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.packet.UDP; +import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.IStorageSourceListener; import net.floodlightcontroller.topology.ITopologyService; -import net.floodlightcontroller.topology.ITopologyListener; import net.floodlightcontroller.util.MultiIterator; import static net.floodlightcontroller.devicemanager.internal. DeviceManagerImpl.DeviceUpdate.Change.*; @@ -67,7 +67,6 @@ import static net.floodlightcontroller.devicemanager.internal. import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFPortStatus; import org.openflow.protocol.OFType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +79,6 @@ import org.slf4j.LoggerFactory; */ public class DeviceManagerImpl implements IDeviceService, IOFMessageListener, - IOFSwitchListener, ITopologyListener, IStorageSourceListener, IFloodlightModule, IInfoProvider { protected static Logger logger = @@ -89,6 +87,7 @@ public class DeviceManagerImpl implements protected IFloodlightProviderService floodlightProvider; protected ITopologyService topology; protected IStorageSourceService storageSource; + protected IRestApiService restApi; /** * Time in milliseconds before entities will expire @@ -436,9 +435,6 @@ public class DeviceManagerImpl implements case PACKET_IN: return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx); - case PORT_STATUS: - return this.processPortStatusMessage(sw, - (OFPortStatus) msg); } logger.error("received an unexpected message {} from switch {}", @@ -462,31 +458,6 @@ public class DeviceManagerImpl implements } - // ***************** - // ITopologyListener - // ***************** - - @Override - public void toplogyChanged() { - // TODO Auto-generated method stub - - } - - // ***************** - // IOFSwitchListener - // ***************** - - @Override - public void addedSwitch(IOFSwitch sw) { - // TODO Auto-generated method stub - - } - - @Override - public void removedSwitch(IOFSwitch sw) { - // TODO Auto-generated method stub - } - // ***************** // IFloodlightModule // ***************** @@ -518,6 +489,7 @@ public class DeviceManagerImpl implements l.add(IFloodlightProviderService.class); l.add(IStorageSourceService.class); l.add(ITopologyService.class); + l.add(IRestApiService.class); return l; } @@ -535,6 +507,8 @@ public class DeviceManagerImpl implements fmc.getServiceImpl(IStorageSourceService.class); this.topology = fmc.getServiceImpl(ITopologyService.class); + this.restApi = fmc.getServiceImpl(IRestApiService.class); + } @Override @@ -580,17 +554,8 @@ public class DeviceManagerImpl implements return r; } }; - - if (topology != null) { - // Register to get updates from topology - topology.addListener(this); - } else { - logger.error("Could not add topology listener"); - } floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this); - floodlightProvider.addOFSwitchListener(this); Runnable ecr = new Runnable() { @Override @@ -605,16 +570,17 @@ public class DeviceManagerImpl implements entityCleanupTask = new SingletonTask(ses, ecr); entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, TimeUnit.SECONDS); + + if (restApi != null) { + restApi.addRestletRoutable(new DeviceRoutable()); + } else { + logger.error("Could not instantiate REST API"); + } } // **************** // Internal methods // **************** - - protected Command processPortStatusMessage(IOFSwitch sw, OFPortStatus ps) { - // XXX - TODO - return null; - } /** * This method is called for every packet-in and should be optimized for @@ -1045,6 +1011,7 @@ public class DeviceManagerImpl implements break; case DELETE: listener.deviceRemoved(update.device); + break; case CHANGE: for (DeviceField field : update.fieldsChanged) { switch (field) { diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java b/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java new file mode 100644 index 000000000..67a9db18d --- /dev/null +++ b/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java @@ -0,0 +1,124 @@ +/** +* Copyright 2012, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.devicemanager.web; + +import java.util.Iterator; + +import net.floodlightcontroller.devicemanager.IDevice; +import net.floodlightcontroller.devicemanager.IDeviceService; +import net.floodlightcontroller.packet.IPv4; + +import org.openflow.util.HexString; +import org.restlet.data.Form; +import org.restlet.data.Status; +import org.restlet.resource.ServerResource; + +/** + * Resource for querying and displaying devices that exist in the system + */ +public abstract class AbstractDeviceResource extends ServerResource { + public static final String MAC_ERROR = + "Invalid MAC address: must be a 48-bit quantity, " + + "expressed in hex as AA:BB:CC:DD:EE:FF"; + public static final String VLAN_ERROR = + "Invalid VLAN: must be an integer in the range 0-4095"; + public static final String IPV4_ERROR = + "Invalid IPv4 address: must be in dotted decimal format, " + + "234.0.59.1"; + public static final String DPID_ERROR = + "Invalid Switch DPID: must be a 64-bit quantity, expressed in " + + "hex as AA:BB:CC:DD:EE:FF:00:11"; + public static final String PORT_ERROR = + "Invalid Port: must be a positive integer"; + + public Iterator<? extends IDevice> getDevices() { + IDeviceService deviceManager = + (IDeviceService)getContext().getAttributes(). + get(IDeviceService.class.getCanonicalName()); + + Long macAddress = null; + Short vlan = null; + Integer ipv4Address = null; + Long switchDPID = null; + Integer switchPort = null; + + Form form = getQuery(); + String macAddrStr = form.getFirstValue("mac", true); + String vlanStr = form.getFirstValue("vlan", true); + String ipv4Str = form.getFirstValue("ipv4", true); + String dpid = form.getFirstValue("dpid", true); + String port = form.getFirstValue("port", true); + + if (macAddrStr != null) { + try { + macAddress = HexString.toLong(macAddrStr); + } catch (Exception e) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, MAC_ERROR); + return null; + } + } + if (vlanStr != null) { + try { + vlan = Short.parseShort(vlanStr); + if (vlan > 4095 || vlan < 0) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR); + return null; + } + } catch (Exception e) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR); + return null; + } + } + if (ipv4Str != null) { + try { + ipv4Address = IPv4.toIPv4Address(ipv4Str); + } catch (Exception e) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, IPV4_ERROR); + return null; + } + } + if (dpid != null) { + try { + switchDPID = HexString.toLong(dpid); + } catch (Exception e) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR); + return null; + } + } + if (port != null) { + try { + switchPort = Integer.parseInt(port); + if (switchPort < 0) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR); + return null; + } + } catch (Exception e) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR); + return null; + } + } + + Iterator<? extends IDevice> diter = + deviceManager.queryDevices(macAddress, + vlan, + ipv4Address, + switchDPID, + switchPort); + return diter; + } +} diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java new file mode 100644 index 000000000..1139d68d0 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java @@ -0,0 +1,47 @@ +/** +* Copyright 2012, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.devicemanager.web; + +import java.util.Iterator; + +import net.floodlightcontroller.devicemanager.IDevice; +import org.restlet.resource.Get; + +/** + * Resource for querying and displaying devices that exist in the system + */ +public class DeviceResource extends AbstractDeviceResource { + public static final String MAC_ERROR = + "Invalid MAC address: must be a 48-bit quantity, " + + "expressed in hex as AA:BB:CC:DD:EE:FF"; + public static final String VLAN_ERROR = + "Invalid VLAN: must be an integer in the range 0-4095"; + public static final String IPV4_ERROR = + "Invalid IPv4 address: must be in dotted decimal format, " + + "234.0.59.1"; + public static final String DPID_ERROR = + "Invalid Switch DPID: must be a 64-bit quantity, expressed in " + + "hex as AA:BB:CC:DD:EE:FF:00:11"; + public static final String PORT_ERROR = + "Invalid Port: must be a positive integer"; + + @Get("json") + public Iterator<? extends IDevice> getDevices() { + return super.getDevices(); + } +} diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java new file mode 100644 index 000000000..3dea45c8a --- /dev/null +++ b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java @@ -0,0 +1,43 @@ +/** +* Copyright 2012, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.devicemanager.web; + +import org.restlet.Context; +import org.restlet.Restlet; +import org.restlet.routing.Router; + +import net.floodlightcontroller.restserver.RestletRoutable; + +/** + * Routable for device rest api + */ +public class DeviceRoutable implements RestletRoutable { + + @Override + public String basePath() { + return "/wm/device"; + } + + @Override + public Restlet getRestlet(Context context) { + Router router = new Router(context); + router.attach("/", DeviceResource.class); + return router; + } + +} diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java new file mode 100644 index 000000000..fe93761e2 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java @@ -0,0 +1,70 @@ +/** +* Copyright 2012 Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.devicemanager.web; + +import java.io.IOException; + +import net.floodlightcontroller.devicemanager.SwitchPort; +import net.floodlightcontroller.devicemanager.internal.Device; +import net.floodlightcontroller.packet.IPv4; + +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; +import org.openflow.util.HexString; + +/** + * Serialize a device object + */ +public class DeviceSerializer extends JsonSerializer<Device> { + + @Override + public void serialize(Device device, JsonGenerator jGen, + SerializerProvider serializer) throws IOException, + JsonProcessingException { + jGen.writeStartObject(); + + jGen.writeArrayFieldStart("mac"); + jGen.writeString(HexString.toHexString(device.getMACAddress(), 6)); + jGen.writeEndArray(); + + jGen.writeArrayFieldStart("ipv4"); + for (Integer ip : device.getIPv4Addresses()) + jGen.writeString(IPv4.fromIPv4Address(ip)); + jGen.writeEndArray(); + + jGen.writeArrayFieldStart("vlan"); + for (Short ip : device.getVlanId()) + if (ip >= 0) + jGen.writeNumber(ip); + jGen.writeEndArray(); + jGen.writeArrayFieldStart("attachmentPoint"); + for (SwitchPort ap : device.getAttachmentPoints()) { + jGen.writeStartObject(); + jGen.writeStringField("dpid", + HexString.toHexString(ap.getSwitchDPID(), 8)); + jGen.writeNumberField("port", ap.getPort()); + jGen.writeEndObject(); + } + jGen.writeEndArray(); + + jGen.writeEndObject(); + } + +} diff --git a/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java b/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java index f4071c824..1588df22d 100644 --- a/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java +++ b/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java @@ -14,9 +14,13 @@ import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Protocol; import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.ext.jackson.JacksonRepresentation; +import org.restlet.representation.Representation; import org.restlet.routing.Filter; import org.restlet.routing.Router; import org.restlet.routing.Template; +import org.restlet.service.StatusService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,6 +77,15 @@ public class RestApiServer } public void run(FloodlightModuleContext fmlContext, int restPort) { + setStatusService(new StatusService() { + @Override + public Representation getRepresentation(Status status, + Request request, + Response response) { + return new JacksonRepresentation<Status>(status); + } + }); + // Add everything in the module context to the rest for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) { context.getAttributes().put(s.getCanonicalName(), diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java index 989de4741..c7fd7fe1d 100644 --- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java +++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java @@ -632,62 +632,4 @@ public class DeviceManagerImplTest extends FloodlightTestCase { deviceManager.readPortChannelConfigFromStorage(); } */ - /** - * The same test as testAttachmentPointFlapping except for port-channel - * @throws Exception - */ - @Test - public void testPortChannel() throws Exception { - fail(); - /* - OFPhysicalPort port1 = new OFPhysicalPort(); - OFPhysicalPort port2 = new OFPhysicalPort(); - port1.setName("port1"); - port2.setName("port2"); - - setupPortChannel(); - byte[] dataLayerSource = ((Ethernet)this.testPacket).getSourceMACAddress(); - - // Mock up our expected behavior - IOFSwitch mockSwitch = createMock(IOFSwitch.class); - expect(mockSwitch.getPort((short)1)).andReturn(port1).anyTimes(); - expect(mockSwitch.getPort((short)2)).andReturn(port2).anyTimes(); - expect(mockSwitch.getId()).andReturn(1L).anyTimes(); - expect(mockSwitch.getStringId()).andReturn("00:00:00:00:00:00:00:01").anyTimes(); - ITopologyService mockTopology = createMock(ITopologyService.class); - expect(mockTopology.isInternal(1L, (short)1)) - .andReturn(false).atLeastOnce(); - expect(mockTopology.isInternal(1L, (short)2)) - .andReturn(false).atLeastOnce(); - deviceManager.setTopology(mockTopology); - - // Start recording the replay on the mocks - replay(mockSwitch, mockTopology); - - // Get the listener and trigger the packet in - mockFloodlightProvider.dispatchMessage(mockSwitch, this.packetIn); - mockFloodlightProvider.dispatchMessage(mockSwitch, this.packetIn.setInPort((short)2)); - mockFloodlightProvider.dispatchMessage(mockSwitch, this.packetIn.setInPort((short)1)); - mockFloodlightProvider.dispatchMessage(mockSwitch, this.packetIn.setInPort((short)2)); - mockFloodlightProvider.dispatchMessage(mockSwitch, this.packetIn.setInPort((short)1)); - mockFloodlightProvider.dispatchMessage(mockSwitch, this.packetIn.setInPort((short)2)); - - Device device = deviceManager.getDeviceByDataLayerAddress(dataLayerSource); - - // Verify the replay matched our expectations - verify(mockSwitch, mockTopology); - - // Verify the device - assertEquals(device.getAttachmentPoints().size(), 1); - assertEquals(device.getOldAttachmentPoints().size(), 1); - for (DeviceAttachmentPoint ap : device.getOldAttachmentPoints()) { - assertFalse(ap.isBlocked()); - } - - // Reset the device cache - deviceManager.clearAllDeviceStateFromMemory(); - - teardownPortChannel(); - */ - } } -- GitLab