Jian Li
Committed by Gerrit Code Review

[ONOS-3618] Implement REST API for Meter query, insert, delete

* Implement encode & decode method for MeterBandCodec & MeterCodec
* Implement MetersWebResource
* Add unit test for MeterBandCodec & MeterCodec
* Add unit test for MetersWebResource
* Add meter insertion json example
* Add Swagger doc

Change-Id: I07284c6678c08b3cb9e109e86ffb2cf28bf36447
...@@ -51,7 +51,7 @@ public interface Band { ...@@ -51,7 +51,7 @@ public interface Band {
51 * 51 *
52 * @return the long value of the size 52 * @return the long value of the size
53 */ 53 */
54 - long burst(); 54 + Long burst();
55 55
56 /** 56 /**
57 * Only meaningful in the case of a REMARK band type. 57 * Only meaningful in the case of a REMARK band type.
...@@ -60,7 +60,7 @@ public interface Band { ...@@ -60,7 +60,7 @@ public interface Band {
60 * 60 *
61 * @return a short value 61 * @return a short value
62 */ 62 */
63 - short dropPrecedence(); 63 + Short dropPrecedence();
64 64
65 /** 65 /**
66 * Signals the type of band to create. 66 * Signals the type of band to create.
......
...@@ -45,12 +45,12 @@ public final class DefaultBand implements Band, BandEntry { ...@@ -45,12 +45,12 @@ public final class DefaultBand implements Band, BandEntry {
45 } 45 }
46 46
47 @Override 47 @Override
48 - public long burst() { 48 + public Long burst() {
49 return burstSize; 49 return burstSize;
50 } 50 }
51 51
52 @Override 52 @Override
53 - public short dropPrecedence() { 53 + public Short dropPrecedence() {
54 return prec; 54 return prec;
55 } 55 }
56 56
......
...@@ -50,6 +50,8 @@ import org.onosproject.net.intent.Constraint; ...@@ -50,6 +50,8 @@ import org.onosproject.net.intent.Constraint;
50 import org.onosproject.net.intent.HostToHostIntent; 50 import org.onosproject.net.intent.HostToHostIntent;
51 import org.onosproject.net.intent.Intent; 51 import org.onosproject.net.intent.Intent;
52 import org.onosproject.net.intent.PointToPointIntent; 52 import org.onosproject.net.intent.PointToPointIntent;
53 +import org.onosproject.net.meter.Band;
54 +import org.onosproject.net.meter.Meter;
53 import org.onosproject.net.statistic.Load; 55 import org.onosproject.net.statistic.Load;
54 import org.onosproject.net.topology.Topology; 56 import org.onosproject.net.topology.Topology;
55 import org.onosproject.net.topology.TopologyCluster; 57 import org.onosproject.net.topology.TopologyCluster;
...@@ -102,6 +104,8 @@ public class CodecManager implements CodecService { ...@@ -102,6 +104,8 @@ public class CodecManager implements CodecService {
102 registerCodec(Driver.class, new DriverCodec()); 104 registerCodec(Driver.class, new DriverCodec());
103 registerCodec(GroupBucket.class, new GroupBucketCodec()); 105 registerCodec(GroupBucket.class, new GroupBucketCodec());
104 registerCodec(Load.class, new LoadCodec()); 106 registerCodec(Load.class, new LoadCodec());
107 + registerCodec(Meter.class, new MeterCodec());
108 + registerCodec(Band.class, new MeterBandCodec());
105 registerCodec(TableStatisticsEntry.class, new TableStatisticsEntryCodec()); 109 registerCodec(TableStatisticsEntry.class, new TableStatisticsEntryCodec());
106 registerCodec(PortStatistics.class, new PortStatisticsCodec()); 110 registerCodec(PortStatistics.class, new PortStatisticsCodec());
107 registerCodec(Metric.class, new MetricCodec()); 111 registerCodec(Metric.class, new MetricCodec());
......
1 +/*
2 + * Copyright 2014-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.codec.impl;
17 +
18 +import com.fasterxml.jackson.databind.node.ObjectNode;
19 +import org.onosproject.codec.CodecContext;
20 +import org.onosproject.codec.JsonCodec;
21 +import org.onosproject.net.meter.Band;
22 +import org.onosproject.net.meter.DefaultBand;
23 +import org.slf4j.Logger;
24 +
25 +import static com.google.common.base.Preconditions.checkNotNull;
26 +import static org.onlab.util.Tools.nullIsIllegal;
27 +import static org.slf4j.LoggerFactory.getLogger;
28 +
29 +/**
30 + * Meter band JSON codec.
31 + */
32 +public final class MeterBandCodec extends JsonCodec<Band> {
33 + private final Logger log = getLogger(getClass());
34 +
35 + // JSON field names
36 + private static final String TYPE = "type";
37 + private static final String RATE = "rate";
38 + private static final String BURST_SIZE = "burstSize";
39 + private static final String PREC = "prec";
40 + private static final String PACKETS = "packets";
41 + private static final String BYTES = "bytes";
42 + private static final String MISSING_MEMBER_MESSAGE = " member is required in Band";
43 +
44 + @Override
45 + public ObjectNode encode(Band band, CodecContext context) {
46 + checkNotNull(band, "Band cannot be null");
47 +
48 + ObjectNode result = context.mapper().createObjectNode()
49 + .put(TYPE, band.type().toString())
50 + .put(RATE, band.rate())
51 + .put(PACKETS, band.packets())
52 + .put(BYTES, band.bytes())
53 + .put(BURST_SIZE, band.burst());
54 +
55 + if (band.dropPrecedence() != null) {
56 + result.put(PREC, band.dropPrecedence());
57 + }
58 +
59 + return result;
60 + }
61 +
62 + @Override
63 + public Band decode(ObjectNode json, CodecContext context) {
64 + if (json == null || !json.isObject()) {
65 + return null;
66 + }
67 +
68 + // parse rate
69 + long rate = nullIsIllegal(json.get(RATE), RATE + MISSING_MEMBER_MESSAGE).asLong();
70 +
71 + // parse burst size
72 + long burstSize = nullIsIllegal(json.get(BURST_SIZE), BURST_SIZE + MISSING_MEMBER_MESSAGE).asLong();
73 +
74 + // parse precedence
75 + Short precedence = null;
76 +
77 + // parse band type
78 + String typeStr = nullIsIllegal(json.get(TYPE), TYPE + MISSING_MEMBER_MESSAGE).asText();
79 + Band.Type type;
80 + switch (typeStr) {
81 + case "DROP":
82 + type = Band.Type.DROP;
83 + break;
84 + case "REMARK":
85 + type = Band.Type.REMARK;
86 + precedence = (short) nullIsIllegal(json.get(PREC), PREC + MISSING_MEMBER_MESSAGE).asInt();
87 + break;
88 + default:
89 + log.warn("The requested type {} is not defined for band.", typeStr);
90 + return null;
91 + }
92 +
93 + Band band = DefaultBand.builder()
94 + .ofType(type)
95 + .burstSize(burstSize)
96 + .withRate(rate)
97 + .dropPrecedence(precedence)
98 + .build();
99 +
100 + return band;
101 + }
102 +}
1 +/*
2 + * Copyright 2014-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.codec.impl;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.node.ArrayNode;
20 +import com.fasterxml.jackson.databind.node.ObjectNode;
21 +import org.onosproject.codec.CodecContext;
22 +import org.onosproject.codec.JsonCodec;
23 +import org.onosproject.core.ApplicationId;
24 +import org.onosproject.core.CoreService;
25 +import org.onosproject.net.DeviceId;
26 +import org.onosproject.net.meter.Band;
27 +import org.onosproject.net.meter.DefaultMeter;
28 +import org.onosproject.net.meter.Meter;
29 +import org.onosproject.net.meter.MeterId;
30 +import org.slf4j.Logger;
31 +
32 +import java.util.ArrayList;
33 +import java.util.List;
34 +import java.util.stream.IntStream;
35 +
36 +import static com.google.common.base.Preconditions.checkNotNull;
37 +import static org.onlab.util.Tools.nullIsIllegal;
38 +import static org.slf4j.LoggerFactory.getLogger;
39 +
40 +
41 +/**
42 + * Meter JSON codec.
43 + */
44 +public final class MeterCodec extends JsonCodec<Meter> {
45 + private final Logger log = getLogger(getClass());
46 +
47 + // JSON field names
48 + private static final String ID = "id";
49 + private static final String STATE = "state";
50 + private static final String LIFE = "life";
51 + private static final String PACKETS = "packets";
52 + private static final String BYTES = "bytes";
53 + private static final String REFERENCE_COUNT = "referenceCount";
54 + private static final String APP_ID = "appId";
55 + private static final String BURST = "burst";
56 + private static final String DEVICE_ID = "deviceId";
57 + private static final String UNIT = "unit";
58 + private static final String BANDS = "bands";
59 + public static final String REST_APP_ID = "org.onosproject.rest";
60 + private static final String MISSING_MEMBER_MESSAGE = " member is required in Meter";
61 +
62 + @Override
63 + public ObjectNode encode(Meter meter, CodecContext context) {
64 + checkNotNull(meter, "Meter cannot be null");
65 + ObjectNode result = context.mapper().createObjectNode()
66 + .put(ID, meter.id().toString())
67 + .put(LIFE, meter.life())
68 + .put(PACKETS, meter.packetsSeen())
69 + .put(BYTES, meter.bytesSeen())
70 + .put(REFERENCE_COUNT, meter.referenceCount())
71 + .put(UNIT, meter.unit().toString())
72 + .put(BURST, meter.isBurst())
73 + .put(DEVICE_ID, meter.deviceId().toString());
74 +
75 + if (meter.appId() != null) {
76 + result.put(APP_ID, meter.appId().toString());
77 + }
78 +
79 + if (meter.state() != null) {
80 + result.put(STATE, meter.state().toString());
81 + }
82 +
83 + ArrayNode bands = context.mapper().createArrayNode();
84 + meter.bands().forEach(band -> {
85 + ObjectNode bandJson = context.codec(Band.class).encode(band, context);
86 + bands.add(bandJson);
87 + });
88 + result.set(BANDS, bands);
89 + return result;
90 + }
91 +
92 + @Override
93 + public Meter decode(ObjectNode json, CodecContext context) {
94 + if (json == null || !json.isObject()) {
95 + return null;
96 + }
97 +
98 + final JsonCodec<Band> meterBandCodec = context.codec(Band.class);
99 + CoreService coreService = context.getService(CoreService.class);
100 +
101 + // parse meter id
102 + int meterIdInt = nullIsIllegal(json.get(ID), ID + MISSING_MEMBER_MESSAGE).asInt();
103 + MeterId meterId = MeterId.meterId(meterIdInt);
104 +
105 + // parse device id
106 + DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID),
107 + DEVICE_ID + MISSING_MEMBER_MESSAGE).asText());
108 +
109 + // application id
110 + ApplicationId appId = coreService.registerApplication(REST_APP_ID);
111 +
112 + // parse burst
113 + boolean burst = false;
114 + JsonNode burstJson = json.get("burst");
115 + if (burstJson != null) {
116 + burst = burstJson.asBoolean();
117 + }
118 +
119 + // parse unit type
120 + String unit = nullIsIllegal(json.get(UNIT), UNIT + MISSING_MEMBER_MESSAGE).asText();
121 + Meter.Unit meterUnit;
122 +
123 + switch (unit) {
124 + case "KB_PER_SEC":
125 + meterUnit = Meter.Unit.KB_PER_SEC;
126 + break;
127 + case "PKTS_PER_SEC":
128 + meterUnit = Meter.Unit.PKTS_PER_SEC;
129 + break;
130 + default:
131 + log.warn("The requested unit {} is not defined for meter.", unit);
132 + return null;
133 + }
134 +
135 + // parse meter bands
136 + List<Band> bandList = new ArrayList<>();
137 + JsonNode bandsJson = json.get(BANDS);
138 + checkNotNull(bandsJson);
139 + if (bandsJson != null) {
140 + IntStream.range(0, bandsJson.size()).forEach(i -> {
141 + ObjectNode bandJson = get(bandsJson, i);
142 + bandList.add(meterBandCodec.decode(bandJson, context));
143 + });
144 + }
145 +
146 + Meter meter;
147 + if (burst) {
148 + meter = DefaultMeter.builder()
149 + .withId(meterId)
150 + .fromApp(appId)
151 + .forDevice(deviceId)
152 + .withUnit(meterUnit)
153 + .withBands(bandList)
154 + .burst().build();
155 + } else {
156 + meter = DefaultMeter.builder()
157 + .withId(meterId)
158 + .fromApp(appId)
159 + .forDevice(deviceId)
160 + .withUnit(meterUnit)
161 + .withBands(bandList).build();
162 + }
163 +
164 + return meter;
165 + }
166 +}
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.codec.impl;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import org.hamcrest.Description;
20 +import org.hamcrest.TypeSafeDiagnosingMatcher;
21 +import org.onosproject.net.meter.Band;
22 +
23 +/**
24 + * Hamcrest matcher for bands.
25 + */
26 +public final class MeterBandJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
27 +
28 + private final Band band;
29 +
30 + private MeterBandJsonMatcher(Band band) {
31 + this.band = band;
32 + }
33 +
34 + /**
35 + * Matches the contents of a meter band.
36 + *
37 + * @param bandJson JSON representation of band to match
38 + * @param description Description object used for recording errors
39 + * @return true if contents match, false otherwise
40 + */
41 + @Override
42 + protected boolean matchesSafely(JsonNode bandJson, Description description) {
43 + // check type
44 + final String jsonType = bandJson.get("type").textValue();
45 + if (!band.type().name().equals(jsonType)) {
46 + description.appendText("type was " + jsonType);
47 + return false;
48 + }
49 +
50 + // check rate
51 + final long jsonRate = bandJson.get("rate").longValue();
52 + if (band.rate() != jsonRate) {
53 + description.appendText("rate was " + jsonRate);
54 + return false;
55 + }
56 +
57 + // check burst size
58 + final long jsonBurstSize = bandJson.get("burstSize").longValue();
59 + if (band.burst() != jsonBurstSize) {
60 + description.appendText("burst size was " + jsonBurstSize);
61 + return false;
62 + }
63 +
64 + // check precedence
65 + final JsonNode jsonNodePrec = bandJson.get("prec");
66 + if (jsonNodePrec != null) {
67 + if (band.dropPrecedence() != jsonNodePrec.shortValue()) {
68 + description.appendText("drop precedence was " + jsonNodePrec.shortValue());
69 + return false;
70 + }
71 + }
72 +
73 + // check packets
74 + final JsonNode jsonNodePackets = bandJson.get("packets");
75 + if (jsonNodePackets != null) {
76 + if (band.packets() != jsonNodePackets.asLong()) {
77 + description.appendText("packets was " + jsonNodePackets.asLong());
78 + return false;
79 + }
80 + }
81 +
82 + final JsonNode jsonNodeBytes = bandJson.get("bytes");
83 + if (jsonNodeBytes != null) {
84 + if (band.bytes() != jsonNodeBytes.asLong()) {
85 + description.appendText("bytes was " + jsonNodeBytes.asLong());
86 + return false;
87 + }
88 + }
89 +
90 + return true;
91 + }
92 +
93 + @Override
94 + public void describeTo(Description description) {
95 + description.appendText(band.toString());
96 + }
97 +
98 + /**
99 + * Factory to allocate a band matcher.
100 + *
101 + * @param band band object we are looking for
102 + * @return matcher
103 + */
104 + public static MeterBandJsonMatcher matchesMeterBand(Band band) {
105 + return new MeterBandJsonMatcher(band);
106 + }
107 +}
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.codec.impl;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.node.ObjectNode;
20 +import com.google.common.collect.ImmutableList;
21 +import org.junit.Before;
22 +import org.junit.Test;
23 +import org.onosproject.codec.JsonCodec;
24 +import org.onosproject.core.CoreService;
25 +import org.onosproject.net.NetTestTools;
26 +import org.onosproject.net.meter.Band;
27 +import org.onosproject.net.meter.DefaultBand;
28 +import org.onosproject.net.meter.DefaultMeter;
29 +import org.onosproject.net.meter.Meter;
30 +import org.onosproject.net.meter.MeterId;
31 +
32 +import java.io.IOException;
33 +import java.io.InputStream;
34 +
35 +import static org.easymock.EasyMock.createMock;
36 +import static org.easymock.EasyMock.expect;
37 +import static org.easymock.EasyMock.replay;
38 +import static org.hamcrest.MatcherAssert.assertThat;
39 +import static org.hamcrest.Matchers.is;
40 +import static org.hamcrest.Matchers.notNullValue;
41 +import static org.onosproject.codec.impl.MeterJsonMatcher.matchesMeter;
42 +import static org.onosproject.net.NetTestTools.APP_ID;
43 +
44 +/**
45 + * Unit tests for Meter codec.
46 + */
47 +public class MeterCodecTest {
48 +
49 + MockCodecContext context;
50 + JsonCodec<Meter> meterCodec;
51 + final CoreService mockCoreService = createMock(CoreService.class);
52 +
53 + /**
54 + * Sets up for each test. Creates a context and fetches the flow rule
55 + * codec.
56 + */
57 + @Before
58 + public void setUp() {
59 + context = new MockCodecContext();
60 + meterCodec = context.codec(Meter.class);
61 + assertThat(meterCodec, notNullValue());
62 +
63 + expect(mockCoreService.registerApplication(MeterCodec.REST_APP_ID))
64 + .andReturn(APP_ID).anyTimes();
65 + replay(mockCoreService);
66 + context.registerService(CoreService.class, mockCoreService);
67 + }
68 +
69 + /**
70 + * Tests encoding of a Meter object.
71 + */
72 + @Test
73 + public void testMeterEncode() {
74 + Band band1 = DefaultBand.builder()
75 + .ofType(Band.Type.DROP)
76 + .burstSize(10)
77 + .withRate(10).build();
78 + Band band2 = DefaultBand.builder()
79 + .ofType(Band.Type.REMARK)
80 + .burstSize(10)
81 + .withRate(10)
82 + .dropPrecedence((short) 10).build();
83 +
84 + Meter meter = DefaultMeter.builder()
85 + .fromApp(APP_ID)
86 + .withId(MeterId.meterId(1L))
87 + .forDevice(NetTestTools.did("d1"))
88 + .withBands(ImmutableList.of(band1, band2))
89 + .withUnit(Meter.Unit.KB_PER_SEC).build();
90 +
91 + ObjectNode meterJson = meterCodec.encode(meter, context);
92 + assertThat(meterJson, matchesMeter(meter));
93 + }
94 +
95 + /**
96 + * Test decoding of a Meter object.
97 + */
98 + @Test
99 + public void testMeterDecode() throws IOException {
100 + Meter meter = getMeter("simple-meter.json");
101 + checkCommonData(meter);
102 +
103 + assertThat(meter.bands().size(), is(1));
104 + Band band = meter.bands().iterator().next();
105 + assertThat(band.type().toString(), is("REMARK"));
106 + assertThat(band.rate(), is(10L));
107 + assertThat(band.dropPrecedence(), is((short) 20));
108 + assertThat(band.burst(), is(30L));
109 + }
110 +
111 + /**
112 + * Checks that the data shared by all the resource is correct for a given meter.
113 + *
114 + * @param meter meter to check
115 + */
116 + private void checkCommonData(Meter meter) {
117 + assertThat(meter.id().id(), is(1L));
118 + assertThat(meter.deviceId().toString(), is("of:0000000000000001"));
119 + assertThat(meter.appId(), is(APP_ID));
120 + assertThat(meter.unit().toString(), is("KB_PER_SEC"));
121 + }
122 +
123 + /**
124 + * Reads in a meter from the given resource and decodes it.
125 + *
126 + * @param resourceName resource to use to read the JSON for the rule
127 + * @return decoded meter
128 + * @throws IOException if processing the resource fails
129 + */
130 + private Meter getMeter(String resourceName) throws IOException {
131 + InputStream jsonStream = MeterCodecTest.class.getResourceAsStream(resourceName);
132 + JsonNode json = context.mapper().readTree(jsonStream);
133 + assertThat(json, notNullValue());
134 + Meter meter = meterCodec.decode((ObjectNode) json, context);
135 + assertThat(meter, notNullValue());
136 + return meter;
137 + }
138 +}
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.codec.impl;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import org.hamcrest.Description;
20 +import org.hamcrest.TypeSafeDiagnosingMatcher;
21 +import org.onosproject.net.meter.Band;
22 +import org.onosproject.net.meter.Meter;
23 +
24 +/**
25 + * Hamcrest matcher for meters.
26 + */
27 +public final class MeterJsonMatcher extends TypeSafeDiagnosingMatcher<JsonNode> {
28 +
29 + private final Meter meter;
30 +
31 + private MeterJsonMatcher(Meter meter) {
32 + this.meter = meter;
33 + }
34 +
35 + @Override
36 + protected boolean matchesSafely(JsonNode jsonMeter, Description description) {
37 + // check id
38 + String jsonMeterId = jsonMeter.get("id").asText();
39 + String meterId = meter.id().toString();
40 + if (!jsonMeterId.equals(meterId)) {
41 + description.appendText("meter id was " + jsonMeterId);
42 + return false;
43 + }
44 +
45 + // check unit
46 + String jsonUnit = jsonMeter.get("unit").asText();
47 + String unit = meter.unit().toString();
48 + if (!jsonUnit.equals(unit)) {
49 + description.appendText("unit was " + jsonUnit);
50 + return false;
51 + }
52 +
53 + // check burst
54 + boolean jsonBurst = jsonMeter.get("burst").asBoolean();
55 + boolean burst = meter.isBurst();
56 + if (jsonBurst != burst) {
57 + description.appendText("isBurst was " + jsonBurst);
58 + return false;
59 + }
60 +
61 + // check state
62 + JsonNode jsonNodeState = jsonMeter.get("state");
63 + if (jsonNodeState != null) {
64 + String state = meter.state().toString();
65 + if (!jsonNodeState.asText().equals(state)) {
66 + description.appendText("state was " + jsonNodeState.asText());
67 + return false;
68 + }
69 + }
70 +
71 + // check life
72 + JsonNode jsonNodeLife = jsonMeter.get("life");
73 + if (jsonNodeLife != null) {
74 + long life = meter.life();
75 + if (jsonNodeLife.asLong() != life) {
76 + description.appendText("life was " + jsonNodeLife.asLong());
77 + return false;
78 + }
79 + }
80 +
81 + // check bytes
82 + JsonNode jsonNodeBytes = jsonMeter.get("bytes");
83 + if (jsonNodeBytes != null) {
84 + long bytes = meter.bytesSeen();
85 + if (jsonNodeBytes.asLong() != bytes) {
86 + description.appendText("bytes was " + jsonNodeBytes.asLong());
87 + return false;
88 + }
89 + }
90 +
91 + // check packets
92 + JsonNode jsonNodePackets = jsonMeter.get("packets");
93 + if (jsonNodePackets != null) {
94 + long packets = meter.packetsSeen();
95 + if (jsonNodePackets.asLong() != packets) {
96 + description.appendText("packets was " + jsonNodePackets.asLong());
97 + return false;
98 + }
99 + }
100 +
101 + // check size of band array
102 + JsonNode jsonBands = jsonMeter.get("bands");
103 + if (jsonBands.size() != meter.bands().size()) {
104 + description.appendText("bands size was " + jsonBands.size());
105 + return false;
106 + }
107 +
108 + // check bands
109 + for (Band band : meter.bands()) {
110 + boolean bandFound = false;
111 + for (int bandIndex = 0; bandIndex < jsonBands.size(); bandIndex++) {
112 + MeterBandJsonMatcher bandMatcher = MeterBandJsonMatcher.matchesMeterBand(band);
113 + if (bandMatcher.matches(jsonBands.get(bandIndex))) {
114 + bandFound = true;
115 + break;
116 + }
117 + }
118 + if (!bandFound) {
119 + description.appendText("band not found " + band.toString());
120 + return false;
121 + }
122 + }
123 +
124 + return true;
125 + }
126 +
127 + @Override
128 + public void describeTo(Description description) {
129 + description.appendText(meter.toString());
130 + }
131 +
132 + /**
133 + * Factory to allocate a meter matcher.
134 + *
135 + * @param meter meter object we are looking for
136 + * @return matcher
137 + */
138 + public static MeterJsonMatcher matchesMeter(Meter meter) {
139 + return new MeterJsonMatcher(meter);
140 + }
141 +}
1 +{
2 + "id": 1,
3 + "deviceId": "of:0000000000000001",
4 + "unit": "KB_PER_SEC",
5 + "burst": true,
6 + "bands": [
7 + {
8 + "type": "REMARK",
9 + "rate": 10,
10 + "prec": 20,
11 + "burstSize": 30
12 + }
13 + ]
14 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -38,6 +38,7 @@ public class CoreWebApplication extends AbstractWebApplication { ...@@ -38,6 +38,7 @@ public class CoreWebApplication extends AbstractWebApplication {
38 IntentsWebResource.class, 38 IntentsWebResource.class,
39 FlowsWebResource.class, 39 FlowsWebResource.class,
40 GroupsWebResource.class, 40 GroupsWebResource.class,
41 + MetersWebResource.class,
41 TopologyWebResource.class, 42 TopologyWebResource.class,
42 ConfigWebResource.class, 43 ConfigWebResource.class,
43 PathsWebResource.class, 44 PathsWebResource.class,
......
1 +/*
2 + * Copyright 2014-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.rest.resources;
17 +
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.fasterxml.jackson.databind.node.ArrayNode;
20 +import com.fasterxml.jackson.databind.node.ObjectNode;
21 +import org.onosproject.net.DeviceId;
22 +import org.onosproject.net.meter.DefaultMeterRequest;
23 +import org.onosproject.net.meter.Meter;
24 +import org.onosproject.net.meter.MeterId;
25 +import org.onosproject.net.meter.MeterRequest;
26 +import org.onosproject.net.meter.MeterService;
27 +import org.onosproject.rest.AbstractWebResource;
28 +import org.slf4j.Logger;
29 +
30 +import javax.ws.rs.Consumes;
31 +import javax.ws.rs.DELETE;
32 +import javax.ws.rs.GET;
33 +import javax.ws.rs.POST;
34 +import javax.ws.rs.Path;
35 +import javax.ws.rs.PathParam;
36 +import javax.ws.rs.Produces;
37 +import javax.ws.rs.core.MediaType;
38 +import javax.ws.rs.core.Response;
39 +import java.io.IOException;
40 +import java.io.InputStream;
41 +import java.net.URI;
42 +import java.net.URISyntaxException;
43 +
44 +import static org.onlab.util.Tools.nullIsNotFound;
45 +import static org.slf4j.LoggerFactory.getLogger;
46 +
47 +/**
48 + * Query and program meter rules.
49 + */
50 +@Path("meters")
51 +public class MetersWebResource extends AbstractWebResource {
52 + private final Logger log = getLogger(getClass());
53 + public static final String DEVICE_INVALID = "Invalid deviceId in meter creation request";
54 +
55 + final MeterService meterService = get(MeterService.class);
56 + final ObjectNode root = mapper().createObjectNode();
57 + final ArrayNode metersNode = root.putArray("meters");
58 +
59 + /**
60 + * Returns all meters of all devices.
61 + *
62 + * @return array of all the meters in the system
63 + * @onos.rsModel Meters
64 + */
65 + @GET
66 + @Produces(MediaType.APPLICATION_JSON)
67 + public Response getMeters() {
68 + final Iterable<Meter> meters = meterService.getAllMeters();
69 + if (meters != null) {
70 + meters.forEach(meter -> metersNode.add(codec(Meter.class).encode(meter, this)));
71 + }
72 + return ok(root).build();
73 + }
74 +
75 + /**
76 + * Returns a meter by the meter id.
77 + *
78 + * @param deviceId device identifier
79 + * @return array of all the groups in the system
80 + * @onos.rsModel Meter
81 + */
82 + @GET
83 + @Produces(MediaType.APPLICATION_JSON)
84 + @Path("{deviceId}/{meterId}")
85 + public Response getMeterByDeviceIdAndMeterId(@PathParam("deviceId") String deviceId,
86 + @PathParam("meterId") String meterId) {
87 + DeviceId did = DeviceId.deviceId(deviceId);
88 + MeterId mid = MeterId.meterId(Long.valueOf(meterId));
89 +
90 + final Meter meter = nullIsNotFound(meterService.getMeter(did, mid),
91 + "Meter is not found for " + mid.id());
92 +
93 + metersNode.add(codec(Meter.class).encode(meter, this));
94 + return ok(root).build();
95 + }
96 +
97 + /**
98 + * Create new meter rule. Creates and installs a new meter rule for the
99 + * specified device.
100 + *
101 + * @param deviceId device identifier
102 + * @param stream meter rule JSON
103 + * @return status of the request - CREATED if the JSON is correct,
104 + * BAD_REQUEST if the JSON is invalid
105 + * @onos.rsModel MeterPost
106 + */
107 + @POST
108 + @Path("{deviceId}")
109 + @Consumes(MediaType.APPLICATION_JSON)
110 + @Produces(MediaType.APPLICATION_JSON)
111 + public Response createMeter(@PathParam("deviceId") String deviceId,
112 + InputStream stream) {
113 + URI location;
114 + try {
115 + ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
116 + JsonNode specifiedDeviceId = jsonTree.get("deviceId");
117 +
118 + if (specifiedDeviceId != null &&
119 + !specifiedDeviceId.asText().equals(deviceId)) {
120 + throw new IllegalArgumentException(DEVICE_INVALID);
121 + }
122 + jsonTree.put("deviceId", deviceId);
123 + final Meter tmpMeter = codec(Meter.class).decode(jsonTree, this);
124 + final MeterRequest meterRequest = meterToMeterRequest(tmpMeter, "ADD");
125 + final Meter meter = meterService.submit(meterRequest);
126 + location = new URI(Long.toString(meter.id().id()));
127 + } catch (IOException | URISyntaxException ex) {
128 + throw new IllegalArgumentException(ex);
129 + }
130 +
131 + return Response
132 + .created(location)
133 + .build();
134 + }
135 +
136 + /**
137 + * Removes the specified meter.
138 + *
139 + * @param deviceId device identifier
140 + * @param meterId meter identifier
141 + */
142 + @DELETE
143 + @Produces(MediaType.APPLICATION_JSON)
144 + @Path("{deviceId}/{meterId}")
145 + public void deleteMeterByDeviceIdAndMeterId(@PathParam("deviceId") String deviceId,
146 + @PathParam("meterId") String meterId) {
147 + DeviceId did = DeviceId.deviceId(deviceId);
148 + MeterId mid = MeterId.meterId(Long.valueOf(meterId));
149 + final Meter tmpMeter = meterService.getMeter(did, mid);
150 + if (tmpMeter != null) {
151 + final MeterRequest meterRequest = meterToMeterRequest(tmpMeter, "REMOVE");
152 + meterService.withdraw(meterRequest, tmpMeter.id());
153 + }
154 + }
155 +
156 + /**
157 + * Convert a meter instance to meterRequest instance with a certain operation.
158 + *
159 + * @param meter meter instance
160 + * @param operation operation
161 + * @return converted meterRequest instance
162 + */
163 + private MeterRequest meterToMeterRequest(Meter meter, String operation) {
164 + MeterRequest.Builder builder;
165 + MeterRequest meterRequest;
166 +
167 + if (meter == null) {
168 + return null;
169 + }
170 +
171 + if (meter.isBurst()) {
172 + builder = DefaultMeterRequest.builder()
173 + .fromApp(meter.appId())
174 + .forDevice(meter.deviceId())
175 + .withUnit(meter.unit())
176 + .withBands(meter.bands())
177 + .burst();
178 + } else {
179 + builder = DefaultMeterRequest.builder()
180 + .fromApp(meter.appId())
181 + .forDevice(meter.deviceId())
182 + .withUnit(meter.unit())
183 + .withBands(meter.bands());
184 + }
185 +
186 + switch (operation) {
187 + case "ADD":
188 + meterRequest = builder.add();
189 + break;
190 + case "REMOVE":
191 + meterRequest = builder.remove();
192 + break;
193 + default:
194 + log.warn("Invalid operation {}.", operation);
195 + return null;
196 + }
197 +
198 + return meterRequest;
199 + }
200 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "meter",
4 + "required": [
5 + "id",
6 + "appId",
7 + "deviceId",
8 + "unit",
9 + "burst",
10 + "state",
11 + "life",
12 + "refCount",
13 + "packets",
14 + "bytes",
15 + "bands"
16 + ],
17 + "properties": {
18 + "id": {
19 + "type": "string",
20 + "example": "1"
21 + },
22 + "appId": {
23 + "type": "string",
24 + "example": "1"
25 + },
26 + "deviceId": {
27 + "type": "string",
28 + "example": "of:0000000000000001"
29 + },
30 + "unit": {
31 + "type": "string",
32 + "example": "KB_PER_SEC"
33 + },
34 + "burst": {
35 + "type": "boolean",
36 + "example": true
37 + },
38 + "state": {
39 + "type": "string",
40 + "example": "ADDED"
41 + },
42 + "life": {
43 + "type": "integer",
44 + "format": "int64",
45 + "example": 0
46 + },
47 + "refCount": {
48 + "type": "integer",
49 + "format": "int64",
50 + "example": 0
51 + },
52 + "packets": {
53 + "type": "integer",
54 + "format": "int64",
55 + "example": 0
56 + },
57 + "bytes": {
58 + "type": "integer",
59 + "format": "int64",
60 + "example": 0
61 + },
62 + "bands": {
63 + "type": "array",
64 + "xml": {
65 + "name": "bands",
66 + "wrapped": true
67 + },
68 + "items": {
69 + "type": "object",
70 + "title": "bands",
71 + "required": [
72 + "type",
73 + "rate",
74 + "burstSize",
75 + "prec",
76 + "packets",
77 + "bytes"
78 + ],
79 + "properties": {
80 + "type": {
81 + "type": "string",
82 + "example": "REMARK"
83 + },
84 + "rate": {
85 + "type": "integer",
86 + "format": "int64",
87 + "example": 0
88 + },
89 + "burstSize": {
90 + "type": "integer",
91 + "format": "int64",
92 + "example": 0
93 + },
94 + "prec": {
95 + "type": "integer",
96 + "format": "int16",
97 + "example": 0
98 + },
99 + "packets": {
100 + "type": "integer",
101 + "format": "int64",
102 + "example": 0
103 + },
104 + "bytes": {
105 + "type": "integer",
106 + "format": "int64",
107 + "example": 0
108 + }
109 + }
110 + }
111 + }
112 + }
113 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "meter",
4 + "required": [
5 + "id",
6 + "deviceId",
7 + "unit",
8 + "burst",
9 + "bands"
10 + ],
11 + "properties": {
12 + "id": {
13 + "type": "string",
14 + "example": "1"
15 + },
16 + "deviceId": {
17 + "type": "string",
18 + "example": "of:0000000000000001"
19 + },
20 + "unit": {
21 + "type": "string",
22 + "example": "KB_PER_SEC"
23 + },
24 + "burst": {
25 + "type": "boolean",
26 + "example": true
27 + },
28 + "bands": {
29 + "type": "array",
30 + "xml": {
31 + "name": "bands",
32 + "wrapped": true
33 + },
34 + "items": {
35 + "type": "object",
36 + "title": "bands",
37 + "required": [
38 + "type",
39 + "rate",
40 + "burstSize",
41 + "prec"
42 + ],
43 + "properties": {
44 + "type": {
45 + "type": "string",
46 + "example": "REMARK"
47 + },
48 + "rate": {
49 + "type": "integer",
50 + "format": "int64",
51 + "example": "0"
52 + },
53 + "burstSize": {
54 + "type": "integer",
55 + "format": "int64",
56 + "example": "0"
57 + },
58 + "prec": {
59 + "type": "integer",
60 + "format": "int16",
61 + "example": "0"
62 + }
63 + }
64 + }
65 + }
66 + }
67 +}
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "type": "object",
3 + "title": "meters",
4 + "required": [
5 + "meters"
6 + ],
7 + "properties": {
8 + "groups": {
9 + "type": "array",
10 + "xml": {
11 + "name": "meters",
12 + "wrapped": true
13 + },
14 + "items": {
15 + "type": "object",
16 + "title": "meter",
17 + "required": [
18 + "id",
19 + "appId",
20 + "deviceId",
21 + "unit",
22 + "burst",
23 + "state",
24 + "life",
25 + "refCount",
26 + "packets",
27 + "bytes",
28 + "bands"
29 + ],
30 + "properties": {
31 + "id": {
32 + "type": "string",
33 + "example": "1"
34 + },
35 + "appId": {
36 + "type": "string",
37 + "example": "1"
38 + },
39 + "deviceId": {
40 + "type": "string",
41 + "example": "of:0000000000000001"
42 + },
43 + "unit": {
44 + "type": "string",
45 + "example": "KB_PER_SEC"
46 + },
47 + "burst": {
48 + "type": "boolean",
49 + "example": true
50 + },
51 + "state": {
52 + "type": "string",
53 + "example": "ADDED"
54 + },
55 + "life": {
56 + "type": "integer",
57 + "format": "int64",
58 + "example": 0
59 + },
60 + "refCount": {
61 + "type": "integer",
62 + "format": "int64",
63 + "example": 0
64 + },
65 + "packets": {
66 + "type": "integer",
67 + "format": "int64",
68 + "example": 0
69 + },
70 + "bytes": {
71 + "type": "integer",
72 + "format": "int64",
73 + "example": 0
74 + },
75 + "bands": {
76 + "type": "array",
77 + "xml": {
78 + "name": "bands",
79 + "wrapped": true
80 + },
81 + "items": {
82 + "type": "object",
83 + "title": "bands",
84 + "required": [
85 + "type",
86 + "rate",
87 + "burstSize",
88 + "prec",
89 + "packets",
90 + "bytes"
91 + ],
92 + "properties": {
93 + "type": {
94 + "type": "string",
95 + "example": "REMARK"
96 + },
97 + "rate": {
98 + "type": "integer",
99 + "format": "int64",
100 + "example": 0
101 + },
102 + "burstSize": {
103 + "type": "integer",
104 + "format": "int64",
105 + "example": 0
106 + },
107 + "prec": {
108 + "type": "integer",
109 + "format": "int16",
110 + "example": 0
111 + },
112 + "packets": {
113 + "type": "integer",
114 + "format": "int64",
115 + "example": 0
116 + },
117 + "bytes": {
118 + "type": "integer",
119 + "format": "int64",
120 + "example": 0
121 + }
122 + }
123 + }
124 + }
125 + }
126 + }
127 + }
128 + }
129 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2014-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 +
17 +package org.onosproject.rest;
18 +
19 +import com.eclipsesource.json.JsonArray;
20 +import com.eclipsesource.json.JsonObject;
21 +import com.google.common.collect.ImmutableSet;
22 +import com.sun.jersey.api.client.ClientResponse;
23 +import com.sun.jersey.api.client.WebResource;
24 +import org.hamcrest.Description;
25 +import org.hamcrest.TypeSafeMatcher;
26 +import org.junit.After;
27 +import org.junit.Before;
28 +import org.junit.Test;
29 +import org.onlab.osgi.ServiceDirectory;
30 +import org.onlab.osgi.TestServiceDirectory;
31 +import org.onlab.rest.BaseResource;
32 +import org.onosproject.codec.CodecService;
33 +import org.onosproject.codec.impl.CodecManager;
34 +import org.onosproject.codec.impl.MeterCodec;
35 +import org.onosproject.core.ApplicationId;
36 +import org.onosproject.core.CoreService;
37 +import org.onosproject.core.DefaultApplicationId;
38 +import org.onosproject.net.DefaultDevice;
39 +import org.onosproject.net.Device;
40 +import org.onosproject.net.DeviceId;
41 +import org.onosproject.net.NetTestTools;
42 +import org.onosproject.net.device.DeviceService;
43 +import org.onosproject.net.meter.Band;
44 +import org.onosproject.net.meter.DefaultBand;
45 +import org.onosproject.net.meter.Meter;
46 +import org.onosproject.net.meter.MeterId;
47 +import org.onosproject.net.meter.MeterService;
48 +import org.onosproject.net.meter.MeterState;
49 +
50 +import javax.ws.rs.core.MediaType;
51 +import java.io.InputStream;
52 +import java.net.HttpURLConnection;
53 +import java.util.ArrayList;
54 +import java.util.Collection;
55 +import java.util.HashMap;
56 +import java.util.HashSet;
57 +import java.util.List;
58 +import java.util.Set;
59 +
60 +import static org.easymock.EasyMock.anyObject;
61 +import static org.easymock.EasyMock.anyShort;
62 +import static org.easymock.EasyMock.createMock;
63 +import static org.easymock.EasyMock.expect;
64 +import static org.easymock.EasyMock.expectLastCall;
65 +import static org.easymock.EasyMock.replay;
66 +import static org.easymock.EasyMock.verify;
67 +import static org.hamcrest.Matchers.hasSize;
68 +import static org.hamcrest.Matchers.is;
69 +import static org.junit.Assert.assertThat;
70 +import static org.hamcrest.Matchers.notNullValue;
71 +import static org.onosproject.net.NetTestTools.APP_ID;
72 +
73 +/**
74 + * Unit tests for meters REST APIs.
75 + */
76 +public class MetersResourceTest extends ResourceTest {
77 + final MeterService mockMeterService = createMock(MeterService.class);
78 + CoreService mockCoreService = createMock(CoreService.class);
79 + final DeviceService mockDeviceService = createMock(DeviceService.class);
80 +
81 + final HashMap<DeviceId, Set<Meter>> meters = new HashMap<>();
82 +
83 + final DeviceId deviceId1 = DeviceId.deviceId("1");
84 + final DeviceId deviceId2 = DeviceId.deviceId("2");
85 + final DeviceId deviceId3 = DeviceId.deviceId("3");
86 + final Device device1 = new DefaultDevice(null, deviceId1, Device.Type.OTHER,
87 + "", "", "", "", null);
88 + final Device device2 = new DefaultDevice(null, deviceId2, Device.Type.OTHER,
89 + "", "", "", "", null);
90 +
91 + final MockMeter meter1 = new MockMeter(deviceId1, 1, 111, 1);
92 + final MockMeter meter2 = new MockMeter(deviceId1, 2, 222, 2);
93 + final MockMeter meter3 = new MockMeter(deviceId2, 3, 333, 3);
94 + final MockMeter meter4 = new MockMeter(deviceId2, 4, 444, 4);
95 + final MockMeter meter5 = new MockMeter(deviceId3, 5, 555, 5);
96 +
97 + /**
98 + * Mock class for a meter.
99 + */
100 + private static class MockMeter implements Meter {
101 +
102 + final DeviceId deviceId;
103 + final ApplicationId appId;
104 + final MeterId meterId;
105 + final long baseValue;
106 + final List<Band> bandList;
107 +
108 + public MockMeter(DeviceId deviceId, int appId, long meterId, int id) {
109 + this.deviceId = deviceId;
110 + this.appId = new DefaultApplicationId(appId, String.valueOf(appId));
111 + this.baseValue = id * 200;
112 + this.meterId = MeterId.meterId(meterId);
113 +
114 + Band band = DefaultBand.builder()
115 + .ofType(Band.Type.REMARK)
116 + .withRate(10)
117 + .dropPrecedence((short) 20)
118 + .burstSize(30).build();
119 +
120 + this.bandList = new ArrayList<>();
121 + this.bandList.add(band);
122 + }
123 +
124 + @Override
125 + public DeviceId deviceId() {
126 + return this.deviceId;
127 + }
128 +
129 + @Override
130 + public MeterId id() {
131 + return this.meterId;
132 + }
133 +
134 + @Override
135 + public ApplicationId appId() {
136 + return this.appId;
137 + }
138 +
139 + @Override
140 + public Unit unit() {
141 + return Unit.KB_PER_SEC;
142 + }
143 +
144 + @Override
145 + public boolean isBurst() {
146 + return false;
147 + }
148 +
149 + @Override
150 + public Collection<Band> bands() {
151 + return this.bandList;
152 + }
153 +
154 + @Override
155 + public MeterState state() {
156 + return MeterState.ADDED;
157 + }
158 +
159 + @Override
160 + public long life() {
161 + return baseValue + 11;
162 + }
163 +
164 + @Override
165 + public long referenceCount() {
166 + return baseValue + 22;
167 + }
168 +
169 + @Override
170 + public long packetsSeen() {
171 + return baseValue + 33;
172 + }
173 +
174 + @Override
175 + public long bytesSeen() {
176 + return baseValue + 44;
177 + }
178 + }
179 +
180 + /**
181 + * Populates some meters used as testing data.
182 + */
183 + private void setupMockMeters() {
184 + final Set<Meter> meters1 = new HashSet<>();
185 + meters1.add(meter1);
186 + meters1.add(meter2);
187 +
188 + final Set<Meter> meters2 = new HashSet<>();
189 + meters2.add(meter3);
190 + meters2.add(meter4);
191 +
192 + meters.put(deviceId1, meters1);
193 + meters.put(deviceId2, meters2);
194 +
195 + Set<Meter> allMeters = new HashSet<>();
196 + for (DeviceId deviceId : meters.keySet()) {
197 + allMeters.addAll(meters.get(deviceId));
198 + }
199 +
200 + expect(mockMeterService.getAllMeters()).andReturn(allMeters).anyTimes();
201 + }
202 +
203 + /**
204 + * Sets up the global values for all the tests.
205 + */
206 + @Before
207 + public void setUpTest() {
208 + // Mock device service
209 + expect(mockDeviceService.getDevice(deviceId1))
210 + .andReturn(device1);
211 + expect(mockDeviceService.getDevice(deviceId2))
212 + .andReturn(device2);
213 + expect(mockDeviceService.getDevices())
214 + .andReturn(ImmutableSet.of(device1, device2));
215 +
216 + // Mock Core Service
217 + expect(mockCoreService.getAppId(anyShort()))
218 + .andReturn(NetTestTools.APP_ID).anyTimes();
219 + expect(mockCoreService.registerApplication(MeterCodec.REST_APP_ID))
220 + .andReturn(APP_ID).anyTimes();
221 + replay(mockCoreService);
222 +
223 + // Register the services needed for the test
224 + final CodecManager codecService = new CodecManager();
225 + codecService.activate();
226 + ServiceDirectory testDirectory =
227 + new TestServiceDirectory()
228 + .add(MeterService.class, mockMeterService)
229 + .add(DeviceService.class, mockDeviceService)
230 + .add(CodecService.class, codecService)
231 + .add(CoreService.class, mockCoreService);
232 +
233 + BaseResource.setServiceDirectory(testDirectory);
234 + }
235 +
236 + /**
237 + * Cleans up and verifies the mocks.
238 + */
239 + @After
240 + public void tearDownTest() {
241 + verify(mockMeterService);
242 + verify(mockCoreService);
243 + }
244 +
245 + /**
246 + * Hamcrest matcher to check that a meter representation in JSON matches
247 + * the actual meter.
248 + */
249 + public static class MeterJsonMatcher extends TypeSafeMatcher<JsonObject> {
250 + private final Meter meter;
251 + private String reason = "";
252 +
253 + public MeterJsonMatcher(Meter meterValue) {
254 + this.meter = meterValue;
255 + }
256 +
257 + @Override
258 + protected boolean matchesSafely(JsonObject jsonMeter) {
259 +
260 + // check application id
261 + final String jsonAppId = jsonMeter.get("appId").asString();
262 + final String appId = meter.appId().toString();
263 + if (!jsonAppId.equals(appId)) {
264 + reason = "appId " + meter.appId().toString();
265 + return false;
266 + }
267 +
268 + // check device id
269 + final String jsonDeviceId = jsonMeter.get("deviceId").asString();
270 + if (!jsonDeviceId.equals(meter.deviceId().toString())) {
271 + reason = "deviceId " + meter.deviceId();
272 + return false;
273 + }
274 +
275 + // check band array
276 + if (meter.bands() != null) {
277 + final JsonArray jsonBands = jsonMeter.get("bands").asArray();
278 + if (meter.bands().size() != jsonBands.size()) {
279 + reason = "bands array size of " +
280 + Integer.toString(meter.bands().size());
281 + return false;
282 + }
283 + for (final Band band : meter.bands()) {
284 + boolean bandFound = false;
285 + for (int bandIndex = 0; bandIndex < jsonBands.size(); bandIndex++) {
286 + final String jsonType = jsonBands.get(bandIndex).asObject().get("type").asString();
287 + final String bandType = band.type().name();
288 + if (jsonType.equals(bandType)) {
289 + bandFound = true;
290 + }
291 + }
292 + if (!bandFound) {
293 + reason = "meter band " + band.toString();
294 + return false;
295 + }
296 + }
297 + }
298 +
299 + return true;
300 + }
301 +
302 + @Override
303 + public void describeTo(Description description) {
304 + description.appendText(reason);
305 + }
306 + }
307 +
308 + private static MeterJsonMatcher matchesMeter(Meter meter) {
309 + return new MeterJsonMatcher(meter);
310 + }
311 +
312 + /**
313 + * Hamcrest matcher to check that a meter is represented properly in a JSON
314 + * array of meters.
315 + */
316 + public static class MeterJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
317 + private final Meter meter;
318 + private String reason = "";
319 +
320 + public MeterJsonArrayMatcher(Meter meterValue) {
321 + meter = meterValue;
322 + }
323 +
324 + @Override
325 + protected boolean matchesSafely(JsonArray json) {
326 + boolean meterFound = false;
327 + for (int jsonMeterIndex = 0; jsonMeterIndex < json.size(); jsonMeterIndex++) {
328 + final JsonObject jsonMeter = json.get(jsonMeterIndex).asObject();
329 +
330 + final String meterId = meter.id().toString();
331 + final String jsonMeterId = jsonMeter.get("id").asString();
332 + if (jsonMeterId.equals(meterId)) {
333 + meterFound = true;
334 +
335 + assertThat(jsonMeter, matchesMeter(meter));
336 + }
337 + }
338 + if (!meterFound) {
339 + reason = "Meter with id " + meter.id().toString() + " not found";
340 + return false;
341 + } else {
342 + return true;
343 + }
344 + }
345 +
346 + @Override
347 + public void describeTo(Description description) {
348 + description.appendText(reason);
349 + }
350 + }
351 +
352 + /**
353 + * Factory to allocate a meter array matcher.
354 + *
355 + * @param meter meter object we are looking for
356 + * @return matcher
357 + */
358 + private static MeterJsonArrayMatcher hasMeter(Meter meter) {
359 + return new MeterJsonArrayMatcher(meter);
360 + }
361 +
362 + @Test
363 + public void testMeterEmptyArray() {
364 + expect(mockMeterService.getAllMeters()).andReturn(null).anyTimes();
365 + replay(mockMeterService);
366 + replay(mockDeviceService);
367 + final WebResource rs = resource();
368 + final String response = rs.path("meters").get(String.class);
369 + assertThat(response, is("{\"meters\":[]}"));
370 + }
371 +
372 + /**
373 + * Tests the result of the rest api GET when there are active meters.
374 + */
375 + @Test
376 + public void testMetersPopulatedArray() {
377 + setupMockMeters();
378 + replay(mockMeterService);
379 + replay(mockDeviceService);
380 + final WebResource rs = resource();
381 + final String response = rs.path("meters").get(String.class);
382 + final JsonObject result = JsonObject.readFrom(response);
383 + assertThat(result, notNullValue());
384 +
385 + assertThat(result.names(), hasSize(1));
386 + assertThat(result.names().get(0), is("meters"));
387 + final JsonArray jsonMeters = result.get("meters").asArray();
388 + assertThat(jsonMeters, notNullValue());
389 + assertThat(jsonMeters, hasMeter(meter1));
390 + assertThat(jsonMeters, hasMeter(meter2));
391 + assertThat(jsonMeters, hasMeter(meter3));
392 + assertThat(jsonMeters, hasMeter(meter4));
393 + }
394 +
395 + /**
396 + * Tests the result of a rest api GET for a device.
397 + */
398 + @Test
399 + public void testMeterSingleDeviceWithId() {
400 + setupMockMeters();
401 +
402 + expect(mockMeterService.getMeter(anyObject(), anyObject()))
403 + .andReturn(meter5).anyTimes();
404 + replay(mockMeterService);
405 + replay(mockDeviceService);
406 +
407 + final WebResource rs = resource();
408 + final String response = rs.path("meters/" + deviceId3.toString()
409 + + "/" + meter5.id().id()).get(String.class);
410 + final JsonObject result = JsonObject.readFrom(response);
411 + assertThat(result, notNullValue());
412 +
413 + assertThat(result.names(), hasSize(1));
414 + assertThat(result.names().get(0), is("meters"));
415 + final JsonArray jsonFlows = result.get("meters").asArray();
416 + assertThat(jsonFlows, notNullValue());
417 + assertThat(jsonFlows, hasMeter(meter5));
418 + }
419 +
420 + /**
421 + * Tests creating a meter with POST.
422 + */
423 + @Test
424 + public void testPost() {
425 + mockMeterService.submit(anyObject());
426 + expectLastCall().andReturn(meter5).anyTimes();
427 + replay(mockMeterService);
428 +
429 + WebResource rs = resource();
430 + InputStream jsonStream = MetersResourceTest.class
431 + .getResourceAsStream("post-meter.json");
432 +
433 + ClientResponse response = rs.path("meters/of:0000000000000001")
434 + .type(MediaType.APPLICATION_JSON_TYPE)
435 + .post(ClientResponse.class, jsonStream);
436 + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
437 + }
438 +
439 + /**
440 + * Tests deleting a meter.
441 + */
442 + @Test
443 + public void testDelete() {
444 + setupMockMeters();
445 + expect(mockMeterService.getMeter(anyObject(), anyObject()))
446 + .andReturn(meter5).anyTimes();
447 + mockMeterService.withdraw(anyObject(), anyObject());
448 + expectLastCall();
449 + replay(mockMeterService);
450 +
451 + WebResource rs = resource();
452 +
453 + String location = "/meters/3/555";
454 +
455 + ClientResponse deleteResponse = rs.path(location)
456 + .type(MediaType.APPLICATION_JSON_TYPE)
457 + .delete(ClientResponse.class);
458 + assertThat(deleteResponse.getStatus(),
459 + is(HttpURLConnection.HTTP_NO_CONTENT));
460 + }
461 +}
1 +{
2 + "id": 1,
3 + "deviceId": "of:0000000000000001",
4 + "unit": "KB_PER_SEC",
5 + "bands": [
6 + {
7 + "type": "REMARK",
8 + "rate": 10,
9 + "prec": 20,
10 + "burstSize": 30
11 + }
12 + ]
13 +}
...\ No newline at end of file ...\ No newline at end of file