From 3d81c2eabbdd3e5187a824fc5d2dff4e58f62a68 Mon Sep 17 00:00:00 2001 From: "Claudio D. Gasparini" Date: Mon, 3 May 2021 16:38:01 +0200 Subject: [PATCH] NF OAM Adopter PM Manager test coverage Issue-ID: OAM-206 Signed-off-by: Claudio D. Gasparini Change-Id: I4f2e68e4dedc462b7f5147661dc2eb977ba293c3 --- .../pm/rest/manager/HttpRestClientMock.java | 47 ++++++ .../nf/oam/adopter/pm/rest/manager/JsonUtils.java | 64 +++++++++ .../manager/PerformanceManagementManagerTest.java | 140 ++++++++++++++++++ .../pm/rest/manager/VesEventNotifierMock.java | 46 ++++++ .../src/test/resources/application.yaml | 4 + .../src/test/resources/json/PMVESMessage.json | 160 +++++++++++++++++++++ .../src/test/resources/pm-ves-message-mapping.yaml | 26 ++++ .../src/test/resources/zip/nfOamAdapter1.zip | Bin 0 -> 598 bytes 8 files changed, 487 insertions(+) create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/HttpRestClientMock.java create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/JsonUtils.java create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/PerformanceManagementManagerTest.java create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/VesEventNotifierMock.java create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/application.yaml create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/json/PMVESMessage.json create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/pm-ves-message-mapping.yaml create mode 100644 ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/zip/nfOamAdapter1.zip diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/HttpRestClientMock.java b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/HttpRestClientMock.java new file mode 100644 index 0000000..095eafd --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/HttpRestClientMock.java @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * O-RAN-SC + * ================================================================================ + * Copyright © 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.o.ran.oam.nf.oam.adopter.pm.rest.manager; + +import io.reactivex.rxjava3.core.Single; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.time.ZoneId; +import java.util.zip.ZipInputStream; +import org.eclipse.jdt.annotation.NonNull; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.api.HttpRestClient; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.pojos.Adapter; + +public class HttpRestClientMock implements HttpRestClient { + + @Override + public Single readFiles(@NonNull final Adapter adapter) { + final InputStream file = HttpRestClientMock.class.getResourceAsStream("/zip/nfOamAdapter1.zip"); + if (file == null) { + return Single.error(new Exception("Failed to read test file")); + } + final BufferedInputStream bis = new BufferedInputStream(file); + return Single.just(new ZipInputStream(bis)); + } + + @Override + public @NonNull Single getTimeZone(@NonNull final Adapter adapter) { + return Single.just(ZoneId.of("+02:00")); + } +} diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/JsonUtils.java b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/JsonUtils.java new file mode 100644 index 0000000..91bfb47 --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/JsonUtils.java @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * O-RAN-SC + * ================================================================================ + * Copyright © 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.o.ran.oam.nf.oam.adopter.pm.rest.manager; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import lombok.experimental.UtilityClass; +import org.apache.commons.io.IOUtils; + +@UtilityClass +final class JsonUtils { + private static final List WHITE_LIST = Arrays.asList("eventId", "startEpochMicrosec", "lastEpochMicrosec"); + private static final String EVENT_LIST = "eventList"; + private static final String COMMON_EVENT_HEADER = "commonEventHeader"; + + static String readJson(final String url) throws IOException { + return IOUtils.toString(JsonUtils.class.getResourceAsStream(url), StandardCharsets.UTF_8); + } + + public static void compareResult(final String expected, final String actual) { + final JsonObject expectedJO = JsonParser.parseString(expected).getAsJsonObject(); + final JsonObject actualJO = JsonParser.parseString(actual).getAsJsonObject(); + removeCommonEventHeaderFields(expectedJO.get(EVENT_LIST).getAsJsonArray(), + actualJO.get(EVENT_LIST).getAsJsonArray(), WHITE_LIST); + assertEquals(expectedJO, actualJO); + } + + private static void removeCommonEventHeaderFields(final JsonArray expectedJO, final JsonArray actualJO, + final List asList) { + asList.forEach(wipe -> { + expectedJO.forEach(jsonElement -> removeCommonEventHeaderFields(jsonElement, wipe)); + actualJO.forEach(jsonElement -> removeCommonEventHeaderFields(jsonElement, wipe)); + }); + } + + private static void removeCommonEventHeaderFields(final JsonElement jsonElement, final String wipe) { + jsonElement.getAsJsonObject().getAsJsonObject(COMMON_EVENT_HEADER).remove(wipe); + } +} diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/PerformanceManagementManagerTest.java b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/PerformanceManagementManagerTest.java new file mode 100644 index 0000000..eb0112e --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/PerformanceManagementManagerTest.java @@ -0,0 +1,140 @@ +/* + * ============LICENSE_START======================================================= + * O-RAN-SC + * ================================================================================ + * Copyright © 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.o.ran.oam.nf.oam.adopter.pm.rest.manager; + +import static java.lang.Thread.sleep; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.api.PerformanceManagementAdaptersDeployer; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.exceptions.AlreadyPresentException; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.exceptions.NotFoundException; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.mapper.PerformanceManagementFile2VesMapper; +import org.o.ran.oam.nf.oam.adopter.pm.rest.manager.properties.PerformanceManagementManagerProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {VesEventNotifierMock.class, PerformanceManagementMapperConfigProvider.class, + PerformanceManagementFile2VesMapper.class, PerformanceManagementAdaptersDeployer.class}) +public class PerformanceManagementManagerTest { + + @Autowired + @Qualifier("test") + private VesEventNotifierMock eventListener; + @Autowired + private PerformanceManagementFile2VesMapper fileMapper; + private PerformanceManagementAdaptersDeployer deployer; + + /** + * Initialize test. + */ + @BeforeEach + public void init() { + final HttpRestClientMock httpRestClientMock = new HttpRestClientMock(); + final PerformanceManagementManagerProperties properties = new PerformanceManagementManagerProperties(); + final ZonedDateTime now = ZonedDateTime.now(ZoneId.of("+02:00")).plusSeconds(5); + final String formattedString = now.format(DateTimeFormatter.ofPattern("HH:mm:ss")); + properties.setSynchronizationTimeStart(formattedString); + properties.setSynchronizationTimeFrequency(30); + deployer = new AdaptersDeployer( + new PerformanceManagementRestAgentFactory(eventListener, fileMapper, properties, httpRestClientMock)); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testMapping() throws IOException, InterruptedException, AlreadyPresentException { + assertTrue(deployer.getAll().isEmpty()); + deployer.create("172.0.10.2", "admin", "admin"); + assertFalse(deployer.getAll().isEmpty()); + + final String expected = JsonUtils.readJson("/json/PMVESMessage.json"); + final List notifications = getVesNotification(eventListener, 2); + final String actual = notifications.get(0); + JsonUtils.compareResult(expected, actual); + } + + @Test + public void testDelete() throws AlreadyPresentException, NotFoundException { + assertTrue(deployer.getAll().isEmpty()); + deployer.create("172.0.10.2", "admin", "admin"); + assertFalse(deployer.getAll().isEmpty()); + + deployer.delete("172.0.10.2"); + assertTrue(deployer.getAll().isEmpty()); + } + + @Test + public void testAlreadyPresent() throws AlreadyPresentException { + assertTrue(deployer.getAll().isEmpty()); + deployer.create("172.0.10.2", "admin", "admin"); + assertFalse(deployer.getAll().isEmpty()); + + final Exception alreadyPresentException = assertThrows(AlreadyPresentException.class, + () -> deployer.create("172.0.10.2", "admin", "admin")); + assertEquals(alreadyPresentException.getMessage(), "Adapter 172.0.10.2 already present."); + } + + @Test + public void testNotPresent() { + final Exception exception = assertThrows(NotFoundException.class, () -> deployer.delete("172.0.10.2")); + assertEquals(exception.getMessage(), "Adapter 172.0.10.2 is not present."); + } + + @Test + public void testTimeZone() throws AlreadyPresentException { + deployer.create("172.0.10.2", "admin", "admin"); + assertEquals(deployer.getTimeZone("172.0.10.2"), ZoneId.of("+02:00")); + } + + private static List getVesNotification(final VesEventNotifierMock listener, final int expectedSize) + throws InterruptedException { + List events = null; + for (int i = 0; i < 100000; i++) { + sleep(1000); + events = listener.getEvents(); + if (events != null && !events.isEmpty() && events.size() == expectedSize) { + break; + } + } + return events; + } + + @AfterEach + public final void after() { + ((AdaptersDeployer) deployer).close(); + assertTrue(deployer.getAll().isEmpty()); + } +} diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/VesEventNotifierMock.java b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/VesEventNotifierMock.java new file mode 100644 index 0000000..e6249ff --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/java/org/o/ran/oam/nf/oam/adopter/pm/rest/manager/VesEventNotifierMock.java @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * O-RAN-SC + * ================================================================================ + * Copyright © 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.o.ran.oam.nf.oam.adopter.pm.rest.manager; + +import com.google.gson.Gson; +import io.reactivex.rxjava3.core.Completable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.o.ran.oam.nf.oam.adopter.api.CommonEventFormat302ONAP; +import org.o.ran.oam.nf.oam.adopter.api.VesEventNotifier; +import org.springframework.stereotype.Service; + +@Service("test") +final class VesEventNotifierMock implements VesEventNotifier { + + private static final Gson GSON = new Gson(); + private final List event = new ArrayList<>(); + + @Override + public Completable notifyEvents(final CommonEventFormat302ONAP event) { + this.event.add(event); + return Completable.complete(); + } + + protected synchronized List getEvents() { + return event.stream().map(e -> GSON.toJson(e, CommonEventFormat302ONAP.class)).collect(Collectors.toList()); + } +} diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/application.yaml b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/application.yaml new file mode 100644 index 0000000..ea580db --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/application.yaml @@ -0,0 +1,4 @@ +pm-rest-manager: + synchronization-time-start: "00:05:00" + synchronization-time-frequency: 60 + mapping-config-path: "src/test/resources/pm-ves-message-mapping.yaml" diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/json/PMVESMessage.json b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/json/PMVESMessage.json new file mode 100644 index 0000000..c9c2b6b --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/json/PMVESMessage.json @@ -0,0 +1,160 @@ +{ + "eventList":[ + { + "commonEventHeader":{ + "domain":"measurement", + "eventId":"ba10db33-9e27-3296-9bfd-a8d232bbe0a8", + "eventName":"FAULT_NF-OAM-ADOPTER_PM_Notification", + "lastEpochMicrosec":1618483581744, + "nfVendorName":"SOME-VENDOR", + "priority":"High", + "reportingEntityId":"ONAP-NF-OAM-SOME-VENDOR-ADAPTER", + "reportingEntityName":"NF-OAM-ADOPTER", + "sequence":0, + "sourceName":"OAM-BOX", + "startEpochMicrosec":1618483581744, + "version":"4.0", + "vesEventListenerVersion":"7.1" + }, + "measurementFields":{ + "additionalFields":{ + "PortId":"2", + "Time":"12:02:12 AM", + "Date":"15/4/2021", + "Name":"OAM-BOX" + }, + "additionalMeasurements":[ + { + "name":"Port measurements", + "hashMap":{ + "C_Parameter":"y26", + "A_Parameter":"1100", + "E_Parameter":"+10.2", + "B_Parameter":"0.5", + "D_Parameter":"x25" + } + } + ], + "measurementInterval":40, + "measurementFieldsVersion":"4.0" + } + }, + { + "commonEventHeader":{ + "domain":"measurement", + "eventId":"f75fd549-18dc-389c-90b6-f975f3ff2a70", + "eventName":"FAULT_NF-OAM-ADOPTER_PM_Notification", + "lastEpochMicrosec":1618483581763, + "nfVendorName":"SOME-VENDOR", + "priority":"High", + "reportingEntityId":"ONAP-NF-OAM-SOME-VENDOR-ADAPTER", + "reportingEntityName":"NF-OAM-ADOPTER", + "sequence":1, + "sourceName":"OAM-BOX", + "startEpochMicrosec":1618483581763, + "version":"4.0", + "vesEventListenerVersion":"7.1" + }, + "measurementFields":{ + "additionalFields":{ + "PortId":"2", + "Time":"12:02:52 AM", + "Date":"15/4/2021", + "Name":"OAM-BOX" + }, + "additionalMeasurements":[ + { + "name":"Port measurements", + "hashMap":{ + "C_Parameter":"y26", + "A_Parameter":"2100", + "E_Parameter":"+10.01", + "B_Parameter":"0.5", + "D_Parameter":"x25" + } + } + ], + "measurementInterval":40, + "measurementFieldsVersion":"4.0" + } + }, + { + "commonEventHeader":{ + "domain":"measurement", + "eventId":"da259c3e-b66d-311d-9fb9-946b241f2fed", + "eventName":"FAULT_NF-OAM-ADOPTER_PM_Notification", + "lastEpochMicrosec":1618483581763, + "nfVendorName":"SOME-VENDOR", + "priority":"High", + "reportingEntityId":"ONAP-NF-OAM-SOME-VENDOR-ADAPTER", + "reportingEntityName":"NF-OAM-ADOPTER", + "sequence":2, + "sourceName":"OAM-BOX", + "startEpochMicrosec":1618483581763, + "version":"4.0", + "vesEventListenerVersion":"7.1" + }, + "measurementFields":{ + "additionalFields":{ + "PortId":"2", + "Time":"12:03:32 AM", + "Date":"15/4/2021", + "Name":"OAM-BOX" + }, + "additionalMeasurements":[ + { + "name":"Port measurements", + "hashMap":{ + "C_Parameter":"y26", + "A_Parameter":"3100", + "E_Parameter":"+10.02", + "B_Parameter":"0.5", + "D_Parameter":"x25" + } + } + ], + "measurementInterval":40, + "measurementFieldsVersion":"4.0" + } + }, + { + "commonEventHeader":{ + "domain":"measurement", + "eventId":"e0bedb4a-4ded-3480-92c4-9c8cec77b28e", + "eventName":"FAULT_NF-OAM-ADOPTER_PM_Notification", + "lastEpochMicrosec":1618483581764, + "nfVendorName":"SOME-VENDOR", + "priority":"High", + "reportingEntityId":"ONAP-NF-OAM-SOME-VENDOR-ADAPTER", + "reportingEntityName":"NF-OAM-ADOPTER", + "sequence":3, + "sourceName":"OAM-BOX", + "startEpochMicrosec":1618483581764, + "version":"4.0", + "vesEventListenerVersion":"7.1" + }, + "measurementFields":{ + "additionalFields":{ + "PortId":"2", + "Time":"12:04:12 AM", + "Date":"15/4/2021", + "Name":"OAM-BOX" + }, + "additionalMeasurements":[ + { + "name":"Port measurements", + "hashMap":{ + "C_Parameter":"y26", + "A_Parameter":"4100", + "E_Parameter":"+10.03", + "B_Parameter":"0.5", + "D_Parameter":"x25" + } + } + ], + "measurementInterval":40, + "measurementFieldsVersion":"4.0" + } + } + ] +} \ No newline at end of file diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/pm-ves-message-mapping.yaml b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/pm-ves-message-mapping.yaml new file mode 100644 index 0000000..aaeac4e --- /dev/null +++ b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/pm-ves-message-mapping.yaml @@ -0,0 +1,26 @@ +reporting-entity-name: "NF-OAM-ADOPTER" +reporting-entity-id: "ONAP-NF-OAM-SOME-VENDOR-ADAPTER" +nf-vendor-name: "SOME-VENDOR" +event-source-type: "SNMP Agent" +event-name: "PM_Notification" +measurement-interval: 40 +priority: "High" +batch-size: 5 +csv: + source-name: Name + event-id: + - PortId + - Date + - Time + additional-fields: + - PortId + - Name + - Date + - Time + additional-measurements-name: "Port measurements" + additional-measurements: + - A_Parameter + - B_Parameter + - C_Parameter + - D_Parameter + - E_Parameter diff --git a/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/zip/nfOamAdapter1.zip b/ves-nf-oam-adopter/ves-nf-oam-adopter-pm-manager/src/test/resources/zip/nfOamAdapter1.zip new file mode 100644 index 0000000000000000000000000000000000000000..6ea3cb87b922079036be237ac19d0de4de5717ee GIT binary patch literal 598 zcmWIWW@Zs#U|`^2kO-d;wDffrQwxx1#>l`R2BiHReRUmO90NjJgA8>8{DVRa^^%Lr za?dI9A2JYVOWdFGpYi5xms+|yTbHbCsOR65T$*20`Dt#okKHfT zB?1>yYftSu|7*_IFI_J`xx5cPkQ>LeZohM^p>U&AbHB_-v1Z#~Wq}QurP?YyV%|xS zWqAi)v^ksKd+p`bw}auV)GDPrdRnWm1&A-a8yx6$BnHzSig1FjHP z0fspUD1b>^;m-w?XJn9I*xY?Ef1c5GMi}iI`L*k=;dRCUZ-hOVA%twt4xn8a@eB?x kWP47nF3JSi1EZHVYR!e%!^#G7EE5o30MZ4(l*7OP03xHsRsaA1 literal 0 HcmV?d00001 -- 2.16.6