Prechádzať zdrojové kódy

PROD-2707: Migrate from Postgres database to Nantum API

 * Use new `nantum-api` endpoints
 * Provisioning script for end-to-end integration test that sets up VEN & Events
Blake Schneider 5 rokov pred
rodič
commit
14704c8945

+ 19 - 73
__tests__/integration/end-to-end.spec.js

@@ -2,66 +2,8 @@
 
 const { expect } = require('chai');
 const sinon = require('sinon');
-const { pki } = require('node-forge');
-const {
-  calculatePartialFingerprintOfEscapedPemCertificate,
-} = require('../../modules/certificate');
-
-const { Ven } = require('../../client/ven');
 const app = require('../../server');
-const { port } = require('../../config');
-
-function getVenClient() {
-  const commonName = 'aabbccddeeff';
-  const keypair = pki.rsa.generateKeyPair({ bits: 1024, e: 0x10001 });
-  const cert = pki.createCertificate();
-  cert.publicKey = keypair.publicKey;
-  cert.serialNumber = '01';
-  cert.validity.notBefore = new Date('2000-01-01T00:00:00.000Z');
-  cert.validity.notAfter = new Date('2100-01-01T00:00:00.000Z');
-
-  const attrs = [
-    {
-      name: 'commonName',
-      value: commonName,
-    },
-    {
-      name: 'countryName',
-      value: 'US',
-    },
-    {
-      shortName: 'ST',
-      value: 'New York',
-    },
-    {
-      name: 'localityName',
-      value: 'New York',
-    },
-    {
-      name: 'organizationName',
-      value: 'Test',
-    },
-    {
-      shortName: 'OU',
-      value: 'Test',
-    },
-  ];
-  cert.setSubject(attrs);
-  cert.setIssuer(attrs);
-  cert.sign(keypair.privateKey);
-  const clientCrtPem = pki.certificateToPem(cert);
-  const fingerprint = calculatePartialFingerprintOfEscapedPemCertificate(
-    clientCrtPem,
-  );
-
-  return new Ven(
-    `http://127.0.0.1:${port}`,
-    clientCrtPem,
-    commonName,
-    fingerprint,
-    'ven.js1',
-  );
-}
+const { getVenClient } = require('../util/provision-ven');
 
 describe('VEN to VTN interactions', function() {
   describe('registration and event retrieval', async function() {
@@ -73,12 +15,13 @@ describe('VEN to VTN interactions', function() {
 
     let ven;
 
-    before(async () => {
+    before(async function () {
+      this.timeout(10000);
       clock = sinon.useFakeTimers(
         new Date('2020-04-26T01:00:00.000Z').getTime(),
       );
       await app.start();
-      ven = getVenClient();
+      ven = await getVenClient();
     });
 
     it('should successfully return a vtnId from queryRegistration', async () => {
@@ -101,7 +44,7 @@ describe('VEN to VTN interactions', function() {
 
     it('should return an event from requestEvents', async () => {
       const eventResponse = await ven.requestEvents();
-      expect(eventResponse.events.length).to.eql(1);
+      expect(eventResponse.events.length).to.eql(2);
       expect(eventResponse.vtnId).to.be.a('string');
     });
 
@@ -125,25 +68,26 @@ describe('VEN to VTN interactions', function() {
       clock.restore();
     });
 
-    before(async () => {
+    before(async function() {
+      this.timeout(10000);
       clock = sinon.useFakeTimers(
         new Date('2020-04-26T01:00:00.000Z').getTime(),
       );
       await app.start();
-      ven = getVenClient();
+      ven = await getVenClient();
       await ven.register();
     });
 
     it('should successfully poll for events', async () => {
       const pollResponse = await ven.poll();
       expect(pollResponse._type).to.eql('oadrDistributeEvent');
-      expect(pollResponse.events.length).to.eql(1);
-    });
+      expect(pollResponse.events.length).to.eql(2);
+    }).timeout(10000);
 
     it('should not return the same event twice', async () => {
       const pollResponse = await ven.poll();
       expect(pollResponse._type).to.eql('oadrResponse');
-    });
+    }).timeout(5000);
 
     after(async () => {
       await app.stop();
@@ -159,12 +103,13 @@ describe('VEN to VTN interactions', function() {
       clock.restore();
     });
 
-    before(async () => {
+    before(async function() {
+      this.timeout(10000);
       clock = sinon.useFakeTimers(
         new Date('2020-04-26T01:00:00.000Z').getTime(),
       );
       await app.start();
-      ven = getVenClient();
+      ven = await getVenClient();
       await ven.register();
       await ven.poll(); // poll for any events that may be waiting
     });
@@ -305,12 +250,13 @@ describe('VEN to VTN interactions', function() {
 
     let ven, events, pollResponse;
 
-    before(async () => {
+    before(async function() {
+      this.timeout(10000);
       clock = sinon.useFakeTimers(
         new Date('2020-04-26T01:00:00.000Z').getTime(),
       );
       await app.start();
-      ven = getVenClient();
+      ven = await getVenClient();
       await ven.register();
       pollResponse = await ven.poll();
       events = pollResponse.events;
@@ -320,13 +266,13 @@ describe('VEN to VTN interactions', function() {
       const eventId = events[0].eventDescriptor.eventId;
       const modificationNumber = events[0].eventDescriptor.modificationNumber;
       await ven.opt('optIn', eventId, modificationNumber);
-    });
+    }).timeout(5000);
 
     it('should be able to opt out after opting in', async () => {
       const eventId = events[0].eventDescriptor.eventId;
       const modificationNumber = events[0].eventDescriptor.modificationNumber;
       await ven.opt('optOut', eventId, modificationNumber);
-    });
+    }).timeout(5000);
 
     after(async () => {
       await app.stop();

+ 9 - 1
__tests__/unit/modules/nantum-responses.js

@@ -92,10 +92,17 @@ const sampleEvent1 = {
 };
 
 const sampleVen1 = {
+  dis: 'Test VEN 1',
+  oadr_ven_registrationId: '123123123123123123129999',
+  company: 'cyberdyne',
+  created_at: new Date(),
+};
+
+const sampleVenRegistration1 = {
   _id: '123123123123123123129999',
   company: 'cyberdyne',
   created_at: new Date(),
-  dis: 'Test VEN 1',
+  dis: 'Test VEN Registration 1',
   ns: 'cyberdyne_studios',
   profile_name: '2.0b',
   is_report_only: false,
@@ -157,4 +164,5 @@ module.exports = {
   sampleEvent1,
   sampleReport1,
   sampleVen1,
+  sampleVenRegistration1,
 };

+ 2 - 1
__tests__/unit/processes/event.spec.js

@@ -15,7 +15,7 @@ const {
 const { poll } = require('../xml/poll/js-requests');
 
 const { generatedFromNantumEvent1 } = require('../xml/event/js-responses');
-const { sampleEvent1, sampleVen1 } = require('../modules/nantum-responses');
+const { sampleEvent1, sampleVen1, sampleVenRegistration1 } = require('../modules/nantum-responses');
 
 describe('Event', function() {
   let clock;
@@ -31,6 +31,7 @@ describe('Event', function() {
     const nantum = new FakeNantumModule({
       events: [sampleEvent1],
       vens: [sampleVen1],
+      venRegistrations: [sampleVenRegistration1],
     });
 
     rewired = rewire('../../../processes/event.js');

+ 0 - 21
__tests__/unit/processes/registration.spec.js

@@ -307,26 +307,5 @@ describe('VEN registration', function() {
       expect(error.message).to.eql('VenID does not match certificate');
     });
 
-    it('fails if no current registration to cancel', async () => {
-      const cancelRequestId = v4().replace(/-/g, '');
-      const cancelRequest = {
-        requestId: cancelRequestId,
-        registrationId: registrationResponse.registrationId,
-        venId: venId,
-      };
-
-      // first cancellation
-      await rewired.cancelParty(cancelRequest, commonName, venId);
-
-      let error;
-      try {
-        // second cancellation
-        await rewired.cancelParty(cancelRequest, commonName, venId);
-      } catch (e) {
-        error = e;
-      }
-      expect(error).to.be.an('error');
-      expect(error.message).to.eql('No current registration for VenID');
-    });
   });
 });

+ 4 - 5
__tests__/unit/processes/report.spec.js

@@ -6,7 +6,7 @@ const sinon = require('sinon');
 const rewire = require('rewire');
 
 const FakeNantumModule = require('../utils/fake-nantum-module');
-const { sampleEvent1, sampleVen1 } = require('../modules/nantum-responses');
+const { sampleEvent1, sampleVenRegistration1, sampleReport1, sampleVen1 } = require('../modules/nantum-responses');
 
 const {
   registerReportMax,
@@ -21,8 +21,6 @@ const {
 
 const { poll: oadrPollMessage } = require('../xml/poll/js-requests');
 
-const { sampleReport1 } = require('../modules/nantum-responses');
-
 describe('Report', function() {
   let clock;
   let rewired;
@@ -41,6 +39,7 @@ describe('Report', function() {
   before(async () => {
     nantum = new FakeNantumModule({
       events: [sampleEvent1],
+      venRegistrations: [sampleVenRegistration1],
       vens: [sampleVen1],
     });
 
@@ -64,8 +63,8 @@ describe('Report', function() {
         venId,
       );
       expect(registeredReport.responseCode).to.eql('200');
-      expect(nantum.vens.length).to.eql(1);
-      expect(nantum.vens[0].reports).to.eql(sampleReport1);
+      expect(nantum.venReports.length).to.eql(1);
+      expect(nantum.venReports[0].reports).to.eql(sampleReport1);
     });
 
     it('requests reports on next poll', async () => {

+ 51 - 22
__tests__/unit/utils/fake-nantum-module.js

@@ -4,36 +4,39 @@ const { v4 } = require('uuid');
 const _ = require('lodash');
 
 class FakeNantumModule {
-  constructor({ events, vens } = {}) {
+  constructor({ vens, events, venRegistrations, venReports, seenEvents } = {}) {
     this.vens = vens || [];
+    this.venRegistrations = venRegistrations || [];
+    this.venReports = venReports || [];
     this.events = events || [];
+    this.seenEvents = seenEvents || [];
     this.eventResponses = [];
     this.reportReadings = [];
   }
 
-  async getVen(clientCertificateFingerprint) {
-    return _.find(this.vens, {
+  async getVenRegistration(clientCertificateFingerprint) {
+    return _.find(this.venRegistrations, {
       client_certificate_fingerprint: clientCertificateFingerprint,
     });
   }
 
-  async createVen(ven) {
+  async createVenRegistration(ven) {
     const venId = v4();
     const newVen = { ...ven, _id: venId };
-    this.vens.push(newVen);
+    this.venRegistrations.push(newVen);
   }
 
-  async updateVen(venId, newProperties) {
-    const venIndex = _.findIndex(this.vens, { _id: venId });
+  async deleteVenRegistration(venId) {
+    const venIndex = _.findIndex(this.venRegistrations, { _id: venId });
     if (venIndex == null)
       throw new Error(`Could not find ven with _id: ${venId}`);
-    this.vens[venIndex] = { ...this.vens[venIndex], ...newProperties };
+    this.venRegistrations = this.venRegistrations.filter(ven => ven._id !== venId);
   }
 
   async getEventResponse(ven, eventId, modificationNumber) {
     return _.find(this.eventResponses, {
-      ven_id: ven._id,
-      event_id: eventId,
+      oadr_ven_id: ven._id,
+      oadr_event_id: eventId,
       modification_number: modificationNumber,
     })[0];
   }
@@ -54,19 +57,26 @@ class FakeNantumModule {
     };
   }
 
-  async markEventAsSeen(ven, eventId, modificationNumber) {
-    const existing = ven.seen_events || [];
-    const newVen = {
-      ...ven,
-      seen_events: [
-        ...existing,
-        {
-          event_id: eventId,
-          modification_number: modificationNumber,
-        },
-      ],
+  markEventAsSeen(oadrVenRegistrationId, eventId, modificationNumber) {
+    const newSeenEvent = {
+      _id: v4(),
+      oadr_ven_registration_id: oadrVenRegistrationId,
+      oadr_event_id: eventId,
+      modification_number: modificationNumber
     };
-    await this.updateVen(ven._id, newVen);
+    this.seenEvents.push(newSeenEvent);
+  }
+
+  async getSeenEvents(venRegistrationId) {
+    return _.filter(this.seenEvents, {
+      oadr_ven_registration_id: venRegistrationId,
+    });
+  }
+
+  async getVen(venRegistrationId) {
+    return _.filter(this.seenEvents, {
+      oadr_ven_registration_id: venRegistrationId,
+    })[0];
   }
 
   async getEvents() {
@@ -76,6 +86,25 @@ class FakeNantumModule {
   async sendReportReadings(ven, readings) {
     this.reportReadings.push({ ven: ven._id, readings });
   }
+
+  async getVenReports(venRegistrationId) {
+    return _.find(this.venReports, {
+      oadr_ven_registration_id: venRegistrationId,
+    });
+  }
+
+  async updateVenReports(venReportId, newReportList) {
+    const venReportIndex = _.findIndex(this.venReports, { _id: venReportId });
+    if (venReportIndex == null)
+      throw new Error(`Could not find venReport with _id: ${venReportId}`);
+    this.venReports[venReportIndex].reports = newReportList;
+  }
+
+  async createVenReports(venReports) {
+    const venReportsId = v4();
+    const newVenReport = { ...venReports, _id: venReportsId };
+    this.venReports.push(newVenReport);
+  }
 }
 
 module.exports = FakeNantumModule;

+ 261 - 0
__tests__/util/provision-ven.js

@@ -0,0 +1,261 @@
+'use strict';
+
+const { API } = require('@hw/edge-sdks');
+const logger = require('../../logger');
+const { company, nantumUrl } = require('../../config');
+const { request } = API({ company, logger });
+
+const { pki } = require('node-forge');
+const {
+  calculatePartialFingerprintOfEscapedPemCertificate,
+} = require('../../modules/certificate');
+
+const { Ven } = require('../../client/ven');
+const { port } = require('../../config');
+
+async function generateEventForVen1(venId) {
+  const eventStartMillis = new Date().getTime() + ( 60 * 60 * 1000 );
+
+  const event = {
+    'cancelled': false,
+    'priority': 1,
+    'response_required': true,
+    'dis': `Test Event ${eventStartMillis}`,
+    'modification_number': 1,
+    'modification_reason': 'Updated market context',
+    'market_context': 'http://emix2',
+    'targets': [
+      {
+        'dis': 'ven target',
+        'target_type': 'ven',
+        'value': '1234567'
+      }
+    ],
+    'signals': {
+      'event': [
+        {
+          'signal_id': 'id1',
+          'signal_name': 'BID_LOAD',
+          'signal_type': 'setpoint',
+          'current_value': 45.5,
+          'duration_seconds': 1800,
+          'start_date': new Date(eventStartMillis).toISOString(),
+          'intervals': [
+            {
+              'duration_seconds': 1740,
+              'signal_payloads': [
+                50
+              ],
+              'uid': '0'
+            },
+            {
+              'duration_seconds': 60,
+              'signal_payloads': [
+                51
+              ],
+              'uid': '1'
+            }
+          ],
+          'item_base': {
+            'type': 'power-real',
+            'dis': 'RealPower',
+            'units': 'W',
+            'si_scale_code': 'none',
+            'power_attributes': {
+              'hertz': 60,
+              'voltage': 120,
+              'ac': true
+            }
+          }
+        }
+      ]
+    },
+    'active_period': {
+      'duration_seconds': 1800,
+      'notification_duration_seconds': 86400,
+      'ramp_up_duration_seconds': 3600,
+      'recovery_duration_seconds': 0,
+      'start_tolerance_duration_seconds': 0,
+      'start_date': new Date(eventStartMillis).toISOString()
+    }
+  };
+
+  // create test event
+  const newEvent = await request({
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_events`,
+    body: event,
+  });
+
+  // create event ven mapping
+  await request({
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_event_vens`,
+    body: {
+      oadr_event_id: newEvent[0]._id,
+      oadr_ven_id: venId,
+    },
+  });
+}
+
+async function generateEventForVen2(venId) {
+  const eventStartMillis = new Date().getTime() + ( 26 * 60 * 60 * 1000 );
+
+  const event = {
+    'cancelled': false,
+    'priority': 1,
+    'response_required': true,
+    'dis': `Test Event ${eventStartMillis}`,
+    'modification_number': 0,
+    'market_context': 'http://emix2',
+    'signals': {
+      'event': [
+        {
+          'signal_id': 'id1',
+          'signal_name': 'BID_LOAD',
+          'signal_type': 'setpoint',
+          'current_value': 47.5,
+          'duration_seconds': 1800,
+          'start_date': new Date(eventStartMillis).toISOString(),
+          'intervals': [
+            {
+              'duration_seconds': 1740,
+              'signal_payloads': [
+                50
+              ],
+              'uid': '0'
+            },
+            {
+              'duration_seconds': 60,
+              'signal_payloads': [
+                51
+              ],
+              'uid': '1'
+            }
+          ],
+          'item_base': {
+            'type': 'power-real',
+            'dis': 'RealPower',
+            'units': 'W',
+            'si_scale_code': 'none',
+            'power_attributes': {
+              'hertz': 60,
+              'voltage': 120,
+              'ac': true
+            }
+          }
+        }
+      ]
+    },
+    'active_period': {
+      'duration_seconds': 3600,
+      'notification_duration_seconds': 86400,
+      'ramp_up_duration_seconds': 3600,
+      'recovery_duration_seconds': 0,
+      'start_tolerance_duration_seconds': 0,
+      'start_date': new Date(eventStartMillis).toISOString()
+    }
+  };
+
+  // create test event
+  const newEvent = await request({
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_events`,
+    body: event,
+  });
+
+  // create event ven mapping
+  await request({
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_event_vens`,
+    body: {
+      oadr_event_id: newEvent[0]._id,
+      oadr_ven_id: venId,
+    },
+  });
+}
+
+async function getVenClient() {
+  const commonName = 'aabbccddeeff';
+  const keypair = pki.rsa.generateKeyPair({ bits: 1024, e: 0x10001 });
+  const cert = pki.createCertificate();
+  cert.publicKey = keypair.publicKey;
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date('2000-01-01T00:00:00.000Z');
+  cert.validity.notAfter = new Date('2100-01-01T00:00:00.000Z');
+
+  const attrs = [
+    {
+      name: 'commonName',
+      value: commonName,
+    },
+    {
+      name: 'countryName',
+      value: 'US',
+    },
+    {
+      shortName: 'ST',
+      value: 'New York',
+    },
+    {
+      name: 'localityName',
+      value: 'New York',
+    },
+    {
+      name: 'organizationName',
+      value: 'Test',
+    },
+    {
+      shortName: 'OU',
+      value: 'Test',
+    },
+  ];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.sign(keypair.privateKey);
+  const clientCrtPem = pki.certificateToPem(cert);
+  const fingerprint = calculatePartialFingerprintOfEscapedPemCertificate(
+    clientCrtPem,
+  );
+
+  const ven = new Ven(
+    `http://127.0.0.1:${port}`,
+    clientCrtPem,
+    commonName,
+    fingerprint,
+    'ven.js1',
+  );
+
+  // register VEN, this will create an oadr_ven_registrations
+  await ven.register();
+
+  // retrieve the oadr_ven_registration_id
+  const registrations = await request({
+    uri: `${nantumUrl}/oadr_ven_registrations`,
+    query: {
+      client_certificate_fingerprint: fingerprint,
+    },
+  });
+
+  const oadrVenRegistrationId = registrations[0]._id;
+
+  // create the VEN tied to the ven registration
+  const newVen = await request({
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_vens`,
+    body: {
+      dis: `VEN ${fingerprint}`,
+      oadr_ven_registration_id: oadrVenRegistrationId
+    },
+  });
+
+  // generate 2 events
+  await generateEventForVen1(newVen[0]._id);
+  await generateEventForVen2(newVen[0]._id);
+
+  return ven;
+}
+
+module.exports = {
+  getVenClient
+};

+ 87 - 29
modules/nantum.js

@@ -7,9 +7,9 @@ const { company, nantumUrl } = require('../config');
 
 const { request } = API({ company, logger });
 
-async function getVen(clientCertificateFingerprint) {
+async function getVenRegistration(clientCertificateFingerprint) {
   const results = await request({
-    uri: `${nantumUrl}/oadr_vens`,
+    uri: `${nantumUrl}/oadr_ven_registrations`,
     query: {
       client_certificate_fingerprint: clientCertificateFingerprint,
     },
@@ -17,28 +17,63 @@ async function getVen(clientCertificateFingerprint) {
   return results[0];
 }
 
-async function createVen(ven) {
+async function createVenRegistration(ven) {
   await request({
     method: 'POST',
-    uri: `${nantumUrl}/oadr_vens`,
+    uri: `${nantumUrl}/oadr_ven_registrations`,
     body: ven,
   });
 }
 
-async function updateVen(id, newProperties) {
+async function deleteVenRegistration(id) {
+  await request({
+    method: 'DELETE',
+    uri: `${nantumUrl}/oadr_ven_registrations/${id}`
+  });
+}
+
+async function getVenReports(oadrVenRegistrationId) {
+  const results = await request({
+    uri: `${nantumUrl}/oadr_ven_reports`,
+    query: {
+      oadr_ven_registration_id: oadrVenRegistrationId,
+    },
+  });
+  return results[0];
+}
+
+async function getSeenEvents(oadrVenRegistrationId) {
+  const results = await request({
+    uri: `${nantumUrl}/oadr_ven_seen_events`,
+    query: {
+      oadr_ven_registration_id: oadrVenRegistrationId,
+    },
+  });
+  return results;
+}
+
+async function updateVenReports(id, reports) {
   await request({
     method: 'PUT',
-    uri: `${nantumUrl}/oadr_vens/${id}`,
-    body: newProperties,
+    uri: `${nantumUrl}/oadr_ven_reports/${id}`,
+    body: { reports },
   });
 }
 
-async function getEventResponse(ven, eventId, modificationNumber) {
+async function createVenReports(venReports) {
+  await request({
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_ven_reports`,
+    body: venReports,
+  });
+}
+
+async function getEventResponse(venId, eventId, modificationNumber) {
   const results = await request({
     uri: `${nantumUrl}/oadr_event_responses`,
     query: {
-      ven_id: ven._id,
-      event_id: eventId,
+      oadr_ven_id: venId,
+      oadr_event_id: eventId,
       modification_number: modificationNumber,
     },
   });
@@ -53,36 +88,44 @@ async function createEventResponse(eventResponse) {
   });
 }
 
-async function updateEventResponse(id, newProperties) {
+async function updateEventResponse(oadrEventResponseId, newProperties) {
   await request({
     method: 'PUT',
-    uri: `${nantumUrl}/oadr_event_responses/${id}`,
+    uri: `${nantumUrl}/oadr_event_responses/${oadrEventResponseId}`,
     body: newProperties,
   });
 }
 
-async function markEventAsSeen(ven, eventId, modificationNumber) {
-  //TODO: potentially racy. Consider breaking out `seen_events` into its own document.
-  const existing = ven.seen_events || [];
+async function markEventAsSeen(oadrVenRegistrationId, eventId, modificationNumber) {
   await request({
-    method: 'PUT',
-    uri: `${nantumUrl}/oadr_vens/${ven._id}`,
+    method: 'POST',
+    uri: `${nantumUrl}/oadr_ven_seen_events`,
     body: {
-      seen_events: [
-        ...existing,
-        {
-          event_id: eventId,
-          modification_number: modificationNumber,
-        },
-      ],
+      oadr_ven_registration_id: oadrVenRegistrationId,
+      oadr_event_id: eventId,
+      modification_number: modificationNumber
     },
   });
 }
 
-async function getEvents() {
+async function getEvents(venRegistrationId) {
+  const ven = await getVen(venRegistrationId);
+  if (!ven) return []; // no ven: no events
+
+  const eventVens = await request({
+    uri: `${nantumUrl}/oadr_event_vens`,
+    query: {
+      oadr_ven_id: ven._id
+    },
+  });
+
+  const eventIds = eventVens.map(eventVen => eventVen.oadr_event_id);
+
   return await request({
     uri: `${nantumUrl}/oadr_events`,
-    query: {},
+    query: {
+      _id: eventIds
+    },
   });
 }
 
@@ -94,14 +137,29 @@ async function sendReportReadings(ven, readings) {
   );
 }
 
+async function getVen(venRegistrationId) {
+  const results = await request({
+    uri: `${nantumUrl}/oadr_vens`,
+    query: {
+      oadr_ven_registration_id: venRegistrationId,
+    },
+  });
+  return results[0];
+}
+
 module.exports = {
   getEvents,
   getEventResponse,
   createEventResponse,
   updateEventResponse,
   markEventAsSeen,
-  getVen,
-  updateVen,
-  createVen,
+  getVenRegistration,
+  deleteVenRegistration,
+  createVenRegistration,
   sendReportReadings,
+  getVenReports,
+  updateVenReports,
+  createVenReports,
+  getSeenEvents,
+  getVen
 };

+ 39 - 26
processes/event.js

@@ -54,6 +54,7 @@ function calculateEventIntervals(intervals) {
 }
 
 function calculateItemBase(itemBase) {
+  if (!itemBase) return;
   return {
     type: itemBase.type,
     description: itemBase.dis,
@@ -88,6 +89,7 @@ function calculateEventSignals(signals) {
 }
 
 function calculateBaselineSignal(nantumBaseline) {
+  if (!nantumBaseline) return;
   return {
     baselineName: nantumBaseline.baseline_name,
     baselineId: nantumBaseline.baseline_id,
@@ -97,7 +99,7 @@ function calculateBaselineSignal(nantumBaseline) {
   };
 }
 
-function convertToOadrEvent(ven, event) {
+function convertToOadrEvent(event) {
   return {
     eventDescriptor: {
       eventId: event._id,
@@ -175,13 +177,13 @@ async function retrieveEvents(
     throw error;
   }
 
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const ven = await nantum.getVenRegistration(clientCertificateFingerprint);
   if (!ven) {
     const error = new Error('VEN is not registered');
     error.responseCode = 452;
     throw error;
   }
-  const events = await getPrunedOadrEvents(ven);
+  const events = await getOadrEvents(ven, false);
 
   return {
     _type: 'oadrDistributeEvent',
@@ -209,9 +211,9 @@ function eventResponseMatchesValidEvent(eventResponse, oadrEvents) {
   );
 }
 
-async function validateEventResponses(ven, eventResponses) {
-  const events = await nantum.getEvents();
-  const oadrEvents = events.map(event => convertToOadrEvent(events, event));
+async function validateEventResponses(venRegistration, eventResponses) {
+  const events = await nantum.getEvents(venRegistration._id);
+  const oadrEvents = events.map(event => convertToOadrEvent(event));
   const staleResponses = eventResponses.filter(
     eventResponse => !eventResponseMatchesValidEvent(eventResponse, oadrEvents),
   );
@@ -238,13 +240,25 @@ async function updateOptType(
   const requestVenId = oadrCreatedEvent.venId;
   validateVenId(requestVenId, clientCertificateFingerprint, true);
 
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const venRegistration = await nantum.getVenRegistration(clientCertificateFingerprint);
+  if (!venRegistration) {
+    const error = new Error('VEN is not registered');
+    error.responseCode = 452;
+    throw error;
+  }
+
+  const ven = await nantum.getVen(venRegistration._id);
+  if (!ven) {
+    const error = new Error('VEN registration is not linked to VEN');
+    error.responseCode = 452;
+    throw error;
+  }
 
   try {
-    await validateEventResponses(ven, oadrCreatedEvent.eventResponses);
+    await validateEventResponses(venRegistration, oadrCreatedEvent.eventResponses);
     for (const eventResponse of oadrCreatedEvent.eventResponses) {
       const existingResponse = await nantum.getEventResponse(
-        ven,
+        ven._id,
         eventResponse.eventId,
         eventResponse.modificationNumber,
       );
@@ -254,8 +268,8 @@ async function updateOptType(
         });
       } else {
         await nantum.createEventResponse({
-          event_id: eventResponse.eventId,
-          ven_id: ven._id,
+          oadr_event_id: eventResponse.eventId,
+          oadr_ven_id: ven._id,
           modification_number: eventResponse.modificationNumber,
           opt_type: eventResponse.optType,
         });
@@ -278,11 +292,11 @@ async function updateOptType(
   }
 }
 
-function eventHasBeenSeenByVen(ven, event) {
+function eventHasBeenSeenByVen(seenEvents, event) {
   return (
-    (ven.seen_events || []).filter(
+    seenEvents.filter(
       seenEvent =>
-        seenEvent.event_id === event.eventDescriptor.eventId &&
+        seenEvent.oadr_event_id === event.eventDescriptor.eventId &&
         seenEvent.modification_number ===
           event.eventDescriptor.modificationNumber,
     ).length > 0
@@ -293,28 +307,27 @@ function eventIsVisible(event) {
   return event.status !== 'completed' && event.status !== 'none';
 }
 
-function pruneEvents(ven, events) {
+async function pruneEvents(venRegistrationId, events) {
+  const seenEvents = await nantum.getSeenEvents(venRegistrationId);
   return events.filter(
-    event => !eventHasBeenSeenByVen(ven, event) && eventIsVisible(event),
+    event => !eventHasBeenSeenByVen(seenEvents, event) && eventIsVisible(event),
   );
 }
 
-async function markEventsAsSeen(ven, events) {
+async function markEventsAsSeen(venRegistration, events) {
   for (const event of events) {
     await nantum.markEventAsSeen(
-      ven,
+      venRegistration._id,
       event.eventDescriptor.eventId,
       event.eventDescriptor.modificationNumber,
     );
   }
 }
 
-async function getPrunedOadrEvents(ven) {
-  const events = await nantum.getEvents();
-  return pruneEvents(
-    ven,
-    events.map(event => convertToOadrEvent(ven, event)),
-  );
+async function getOadrEvents(venRegistration, pruneSeen) {
+  const events = await nantum.getEvents(venRegistration._id);
+  const oadrEvents = events.map(event => convertToOadrEvent(event));
+  return pruneSeen ? pruneEvents(venRegistration._id,oadrEvents) : oadrEvents;
 }
 
 async function pollForEvents(
@@ -331,12 +344,12 @@ async function pollForEvents(
 
   const requestVenId = oadrPoll.venId;
   validateVenId(requestVenId, clientCertificateFingerprint, true);
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const ven = await nantum.getVenRegistration(clientCertificateFingerprint);
   if (ven == null) {
     throw new Error(`Ven ${clientCertificateFingerprint} must be registered`);
   }
 
-  const events = await getPrunedOadrEvents(ven);
+  const events = await getOadrEvents(ven, true);
 
   await markEventsAsSeen(ven, events);
 

+ 5 - 13
processes/registration.js

@@ -21,7 +21,7 @@ async function registerParty(
   validateVenId(requestVenId, clientCertificateFingerprint, true);
   validateCreatePartyRegistration(oadrCreatePartyRegistration);
 
-  let ven = await nantum.getVen(requestVenId);
+  let ven = await nantum.getVenRegistration(requestVenId);
 
   if (ven) {
     if (ven.client_certificate_common_name !== clientCertificateCn) {
@@ -29,12 +29,6 @@ async function registerParty(
       error.responseCode = 452;
       throw error;
     }
-    if (ven.registration_id == null) {
-      const registrationId = v4().replace(/-/g, '');
-      await nantum.updateVen(ven._id, {
-        registration_id: registrationId,
-      });
-    }
   } else {
     const registrationId = v4().replace(/-/g, '');
 
@@ -49,7 +43,7 @@ async function registerParty(
       uses_http_pull: oadrCreatePartyRegistration.oadrHttpPullModel,
       dis: oadrCreatePartyRegistration.oadrVenName,
     };
-    await nantum.createVen(ven);
+    await nantum.createVenRegistration(ven);
   }
 
   return venToOadrRegistrationCreated(
@@ -113,7 +107,7 @@ async function query(
 
   const requestVenId = clientCertificateFingerprint;
 
-  let ven = await nantum.getVen(requestVenId);
+  let ven = await nantum.getVenRegistration(requestVenId);
 
   if (ven) {
     if (ven.client_certificate_common_name !== clientCertificateCn) {
@@ -145,7 +139,7 @@ async function cancelParty(
   validateVenId(requestVenId, clientCertificateFingerprint, false);
   const venId = clientCertificateFingerprint;
 
-  let ven = await nantum.getVen(requestVenId);
+  let ven = await nantum.getVenRegistration(requestVenId);
 
   let cancelledRegistrationId;
 
@@ -164,9 +158,7 @@ async function cancelParty(
     }
 
     // clear all registration data
-    await nantum.updateVen(ven._id, {
-      registration_id: null,
-    });
+    await nantum.deleteVenRegistration(ven._id);
   }
 
   return {

+ 174 - 160
processes/report.js

@@ -36,107 +36,110 @@ async function pollForReports(
     clientCertificateFingerprint,
   );
 
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const ven = await nantum.getVenRegistration(clientCertificateFingerprint);
   if (!ven) {
     // not an error, we shouldn't fail polling because the VEN hasn't registered yet
     return;
   }
 
-  const createRequests = [];
-
-  if (ven.reports) {
-    for (const reportMetadata of ven.reports) {
-      let sendCreate = false;
-
-      if (!reportMetadata.last_sent_create) {
-        // if we've never sent a subscription request, do it
-        logger.info(
-          'sending create because we never have',
-          reportMetadata.report_specifier_id,
-        );
-        sendCreate = true;
-      } else {
-        // have sent a create > 5s ago, not received a created
-        if (
-          !reportMetadata.last_received_created &&
-          getSecondsSince('last_sent_create', reportMetadata) > 5
-        ) {
-          logger.info(
-            'no reply to creation request, send another',
-            reportMetadata.report_specifier_id,
-          );
-          sendCreate = true;
-        }
-      }
+  const venReports = await nantum.getVenReports(ven._id);
+  if (!venReports) {
+    // not an error if no reports have been defined
+    return;
+  }
 
-      if (
-        getSecondsSince('last_received_update', reportMetadata) >
-        reportSubscriptionParameters.resubscribeAfterNoDataForSeconds
-      ) {
-        // previously received data, silent now
-        sendCreate = true;
-      }
+  const createRequests = [];
 
+  for (const reportMetadata of venReports.reports) {
+    let sendCreate = false;
+
+    if (!reportMetadata.last_sent_create) {
+      // if we've never sent a subscription request, do it
+      logger.info(
+        'sending create because we never have',
+        reportMetadata.report_specifier_id,
+      );
+      sendCreate = true;
+    } else {
+      // have sent a create > 5s ago, not received a created
       if (
-        !reportMetadata.last_received_update &&
-        getSecondsSince('last_received_created', reportMetadata) >
-          reportSubscriptionParameters.reportBackSeconds + 5
+        !reportMetadata.last_received_created &&
+        getSecondsSince('last_sent_create', reportMetadata) > 5
       ) {
-        // if we haven't received any data but we've waited long enough for one data interval + 5 seconds
         logger.info(
-          'sending create because have not received data',
+          'no reply to creation request, send another',
           reportMetadata.report_specifier_id,
         );
         sendCreate = true;
       }
+    }
 
-      if (
-        getSecondsSince('last_received_created', reportMetadata) >
-        reportSubscriptionParameters.resubscribeDurationSeconds
-      ) {
-        // when we're close to the end of the subscription, trigger a resubscribe
-        logger.info(
-          'sending create because close to end of subscription',
-          reportMetadata.report_specifier_id,
-        );
-        sendCreate = true;
-      }
+    if (
+      getSecondsSince('last_received_update', reportMetadata) >
+      reportSubscriptionParameters.resubscribeAfterNoDataForSeconds
+    ) {
+      // previously received data, silent now
+      sendCreate = true;
+    }
 
-      if (sendCreate) {
-        const newReportRequestId = v4();
-        // track the last 10 registration ids
-        reportMetadata.report_request_ids = [
-          newReportRequestId,
-          ...reportMetadata.report_request_ids,
-        ].slice(0, 10);
-        createRequests.push({
-          reportRequestId: newReportRequestId,
-          reportSpecifierId: reportMetadata.report_specifier_id,
-          granularityDuration: `PT${reportSubscriptionParameters.dataGranularitySeconds}S`,
-          reportBackDuration: `PT${reportSubscriptionParameters.reportBackSeconds}S`,
-          startDate: new Date().toISOString(),
-          duration: `PT${reportSubscriptionParameters.subscriptionDurationSeconds}S`,
-          specifiers: reportMetadata.descriptions.map(description => ({
-            reportId: description.report_id,
-            readingType: 'x-notApplicable',
-          })),
-        });
-        reportMetadata.last_sent_create = new Date().toISOString();
-      }
+    if (
+      !reportMetadata.last_received_update &&
+      getSecondsSince('last_received_created', reportMetadata) >
+        reportSubscriptionParameters.reportBackSeconds + 5
+    ) {
+      // if we haven't received any data but we've waited long enough for one data interval + 5 seconds
+      logger.info(
+        'sending create because have not received data',
+        reportMetadata.report_specifier_id,
+      );
+      sendCreate = true;
     }
-    if (createRequests.length > 0) {
-      const createReport = {
-        _type: 'oadrCreateReport',
-        requestId: v4(),
-        requests: createRequests,
-      };
-
-      await nantum.updateVen(ven._id, {
-        reports: ven.reports,
+
+    if (
+      getSecondsSince('last_received_created', reportMetadata) >
+      reportSubscriptionParameters.resubscribeDurationSeconds
+    ) {
+      // when we're close to the end of the subscription, trigger a resubscribe
+      logger.info(
+        'sending create because close to end of subscription',
+        reportMetadata.report_specifier_id,
+      );
+      sendCreate = true;
+    }
+
+    if (sendCreate) {
+      const newReportRequestId = v4();
+      // track the last 10 registration ids
+      reportMetadata.report_request_ids = [
+        newReportRequestId,
+        ...reportMetadata.report_request_ids,
+      ].slice(0, 10);
+      createRequests.push({
+        reportRequestId: newReportRequestId,
+        reportSpecifierId: reportMetadata.report_specifier_id,
+        granularityDuration: `PT${reportSubscriptionParameters.dataGranularitySeconds}S`,
+        reportBackDuration: `PT${reportSubscriptionParameters.reportBackSeconds}S`,
+        startDate: new Date().toISOString(),
+        duration: `PT${reportSubscriptionParameters.subscriptionDurationSeconds}S`,
+        specifiers: reportMetadata.descriptions.map(description => ({
+          reportId: description.report_id,
+          readingType: 'x-notApplicable',
+        })),
       });
-      return createReport;
+      reportMetadata.last_sent_create = new Date().toISOString();
     }
   }
+  if (createRequests.length > 0) {
+    const createReport = {
+      _type: 'oadrCreateReport',
+      requestId: v4(),
+      requests: createRequests,
+    };
+
+    await nantum.updateVenReports(venReports._id, venReports.reports);
+
+    return createReport;
+  }
 }
 
 async function registerReports(
@@ -153,11 +156,13 @@ async function registerReports(
 
   const requestVenId = oadrRegisterReport.venId;
   validateVenId(requestVenId, clientCertificateFingerprint, false);
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const ven = await nantum.getVenRegistration(clientCertificateFingerprint);
   if (!ven) {
     throw new Error('VEN is not registered');
   }
 
+  const venReports = await nantum.getVenReports(ven._id);
+
   const venReportMetadata = (oadrRegisterReport.reports || []).map(report => {
     const { reportSpecifierId, descriptions } = report;
 
@@ -181,11 +186,17 @@ async function registerReports(
     };
   });
 
-  //TODO: whitelist based off Nantum API sensors
+  if (venReports) {
+    //TODO: can we do an upsert here?
+    await nantum.updateVenReports(venReports._id, venReportMetadata);
+  } else {
+    await nantum.createVenReports({
+      oadr_ven_registration_id: ven._id,
+      reports: venReportMetadata
+    });
+  }
+
 
-  await nantum.updateVen(ven._id, {
-    reports: venReportMetadata,
-  });
 
   return {
     _type: 'oadrRegisteredReport',
@@ -209,30 +220,31 @@ async function createdReports(
   );
 
   validateVenId(oadrCreatedReport.venId, clientCertificateFingerprint, false);
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const ven = await nantum.getVenRegistration(clientCertificateFingerprint);
   if (!ven) {
     throw new Error('VEN is not registered');
   }
 
+  const venReports = await nantum.getVenReports(ven._id);
+  if (!venReports) {
+    throw new Error('VEN does not have registered reports');
+  }
+
   if (oadrCreatedReport.pendingReports) {
     // flag reports as having been created
-    if (ven.reports) {
-      for (const pendingReport of oadrCreatedReport.pendingReports) {
-        const reportRequestId = pendingReport['reportRequestId'];
-        const match = ven.reports.filter(x =>
-          x.report_request_ids.includes(reportRequestId),
-        )[0];
-        if (match) {
-          match.last_received_created = new Date().toISOString();
-        } else {
-          logger.info('could not match', reportRequestId, ven.reports);
-        }
+    for (const pendingReport of oadrCreatedReport.pendingReports) {
+      const reportRequestId = pendingReport['reportRequestId'];
+      const match = venReports.reports.filter(x =>
+        x.report_request_ids.includes(reportRequestId),
+      )[0];
+      if (match) {
+        match.last_received_created = new Date().toISOString();
+      } else {
+        logger.info('could not match', reportRequestId, venReports.reports);
       }
     }
 
-    await nantum.updateVen(ven._id, {
-      reports: ven.reports,
-    });
+    await nantum.updateVenReports(venReports._id, venReports.reports);
   }
 
   return {
@@ -257,77 +269,79 @@ async function receiveReportData(
 
   const requestVenId = oadrUpdateReport.venId;
   validateVenId(requestVenId, clientCertificateFingerprint, false);
-  const ven = await nantum.getVen(clientCertificateFingerprint);
+  const ven = await nantum.getVenRegistration(clientCertificateFingerprint);
   if (!ven) {
     throw new Error('VEN is not registered');
   }
 
-  if (ven.reports) {
-    const readings = [];
-    for (const updateReport of oadrUpdateReport.reports) {
-      const reportRequestId = updateReport.reportRequestId;
-      const match = ven.reports.filter(x =>
-        x.report_request_ids.includes(reportRequestId),
-      )[0];
-      if (!match) {
-        logger.info('could not match', reportRequestId, ven.reports);
-        continue;
+  const venReports = await nantum.getVenReports(ven._id);
+  if (!venReports) {
+    throw new Error('VEN does not have registered reports');
+  }
+
+  const readings = [];
+  for (const updateReport of oadrUpdateReport.reports) {
+    const reportRequestId = updateReport.reportRequestId;
+    const match = venReports.reports.filter(x =>
+      x.report_request_ids.includes(reportRequestId),
+    )[0];
+    if (!match) {
+      logger.info('could not match', reportRequestId, venReports.reports);
+      continue;
+    }
+    match.last_received_update = new Date().toISOString();
+    for (const interval of updateReport.intervals || []) {
+      const reportId = interval.reportPayloads[0].reportId;
+      const reportDefinition = _.find(match.descriptions, {
+        report_id: reportId,
+      });
+      if (!reportDefinition) {
+        logger.info('Received data for unknown report ' + reportId);
+        return;
       }
-      match.last_received_update = new Date().toISOString();
-      for (const interval of updateReport.intervals || []) {
-        const reportId = interval.reportPayloads[0].reportId;
-        const reportDefinition = _.find(match.descriptions, {
+      const date = interval.startDate;
+
+      if (interval.reportPayloads[0].payloadFloat) {
+        const value = interval.reportPayloads[0].payloadFloat;
+        readings.push({
+          date,
+          report_specifier_id: updateReport.reportSpecifierId,
+          report_name: updateReport.reportName,
           report_id: reportId,
+          report_type: reportDefinition.report_type,
+          value,
         });
-        if (!reportDefinition) {
-          logger.info('Received data for unknown report ' + reportId);
-          return;
-        }
-        const date = interval.startDate;
-
-        if (interval.reportPayloads[0].payloadFloat) {
-          const value = interval.reportPayloads[0].payloadFloat;
-          readings.push({
-            date,
-            report_specifier_id: updateReport.reportSpecifierId,
-            report_name: updateReport.reportName,
-            report_id: reportId,
-            report_type: reportDefinition.report_type,
-            value,
-          });
-        }
-        if (
-          interval.reportPayloads[0].payloadStatus &&
-          interval.reportPayloads[0].payloadStatus.loadControlState
-        ) {
-          const loadControlState =
-            interval.reportPayloads[0].payloadStatus.loadControlState;
-          Object.keys(loadControlState).forEach(type => {
-            const typeObj = loadControlState[type];
-            Object.keys(typeObj).forEach(subType => {
-              const value = typeObj[subType];
-              readings.push({
-                date,
-                report_specifier_id: updateReport.reportSpecifierId,
-                report_name: updateReport.reportName,
-                report_id: reportId,
-                report_type: reportDefinition.report_type,
-                type,
-                sub_type: subType,
-                value,
-              });
+      }
+      if (
+        interval.reportPayloads[0].payloadStatus &&
+        interval.reportPayloads[0].payloadStatus.loadControlState
+      ) {
+        const loadControlState =
+          interval.reportPayloads[0].payloadStatus.loadControlState;
+        Object.keys(loadControlState).forEach(type => {
+          const typeObj = loadControlState[type];
+          Object.keys(typeObj).forEach(subType => {
+            const value = typeObj[subType];
+            readings.push({
+              date,
+              report_specifier_id: updateReport.reportSpecifierId,
+              report_name: updateReport.reportName,
+              report_id: reportId,
+              report_type: reportDefinition.report_type,
+              type,
+              sub_type: subType,
+              value,
             });
           });
-        }
+        });
       }
     }
-    if (readings.length > 0) {
-      await nantum.sendReportReadings(ven, readings);
-    }
   }
-  await nantum.updateVen(ven._id, {
-    reports: ven.reports,
-  });
+  if (readings.length > 0) {
+    await nantum.sendReportReadings(ven, readings);
+  }
+
+  await nantum.updateVenReports(venReports._id, venReports.reports);
 
   return {
     _type: 'oadrUpdatedReport',

+ 1 - 1
server/controllers/event.js

@@ -43,7 +43,7 @@ exports.postController = async (req, res) => {
     logger.warn('Error occurred processing', parsedRequest || req.xml, e);
     const responseRequestId =
       parsedRequest != null ? parsedRequest.requestId : '';
-    xmlResponse = serialize({
+    xmlResponse = serializeOadrResponse({
       responseCode: e.responseCode || '454',
       responseDescription: e.message || 'Unknown error',
       responseRequestId: responseRequestId || '',

+ 1 - 1
xml/event/distribute-event.js

@@ -411,7 +411,7 @@ function parseEiTarget(eiTarget) {
   const result = [];
   for (const eiTargetMapping of eiTargetMappings) {
     const unNamespacedAttribute = eiTargetMapping.xmlElement;
-    if (eiTarget[unNamespacedAttribute]) {
+    if (eiTarget && eiTarget[unNamespacedAttribute]) {
       const eiTargetValue = eiTarget[unNamespacedAttribute];
       let newValues;
       if (eiTargetMapping.xmlChildElement) {