Connecting to the Avid Platform using the Avid Connector API
The Avid Connector API connects to the Avid Platform over the Avid Secure Gateway. To connect your service to Avid Platform, the Avid Secure Gateway must be running on known host and port. The default port is 9900.
Default Connection Settings
To connect using default connection settings, create a BusAccess object and call the connect method.
Connection settings can also be specified via environment variables prior to calling ACSCreateBusAccess0: vv
Environment Variable
Description
ACS_BUS_QUERY_TIMEOUT
The default timeout (in ms) for Avid Platform queries. Default is 10000 ms
ACS_GATEWAY_HOST
Secure Gateway connection host. Default is 127.0.0.1
ACS_GATEWAY_PORT
Secure Gateway connection port. Default is 9900
ACS_GATEWAY_UNSECURE_PORT
Port for unsecured connection. Default is 9966
ACS_GATEWAY_PROTOCOL_SEQUENCE
Sequence of protocols (or just one protocol) separated by coma ‘,’ in which Avid Connector API will try to establish connection to Secure Gateway. Default is ‘wss’. Allowed protocols are ‘wss’ and ‘ws’. Possible combinations are ‘wss,ws’, ‘ws’, ‘wss’ or ‘ws,wss’.
ACS_PLATFORM_IDENTIFIER
Unique node identifier, where service is running. Must be provided by target platform, i.e. AWS, OpenStack, etc. (Default ‘unknown’)
ACS_SERVICE_BUILD_NUMBER
RPM version or any other version of the service (Default ‘unknown’)
ACS_ENVIRONMENT_IDENTIFIER
Environment identifier is basically chef generalized identifier for any collection of nodes (Default ‘unknown’)
ACS_BUS_INITIAL_CONNECTION_ATTEMPTS
The # of times to attempt the initial connection before failing (-1 means to try forever)
ACS_GATEWAY_CONNECTION_LOST_THRESHOLD
Amount of time, after which connection to Gateway considered as broken if we didn’t get ping from gateway. (Default 5000 ms)
ACS_BUS_RECONNECTION_ATTEMPTS
The # of times to attempt reconnecting a broken connection (-1 means to try forever)
ACS_BUS_RECONNECTION_DELAY
Delay in ms between connection attempts to Secure Gateway. This delay same both for initial connection attempts and reconnections attempts. Default is 1000 ms.
ACS_SECURITY_TRUST_SELF_SIGNED
Whether to trust (true) or not trust (false) to self signed certificates. Default is true.
When connecting to Avid Platform, authentication provider may be specified with valid clientId and client secret. If service is started from trusted IP address, it may be started w/o identity token, in this case authentication provider must not be provided:
Preventing Gateway Re-Connection Attempts while Debugging
The Avid Connector API and the Secure Gateway have an internal failover logic to validate the connection between them. If you are debugging your service in an IDE or on the command line, a breakpoint can block your service from receiving the information it needs to know that it is still connected to the Secure Gateway. To prevent the service from thinking it has lost its connection to the Secure Gateway, and thus failing over into reconnection mode, you should set the variable ACS_GATEWAY_CONNECTION_LOST_THRESHOLD=600000 (10 minutes). This should prevent the service from thinking it has lost connection while giving you enough time to inspect the information you need at the breakpoint.
Connection Events
If your application needs to react when a connection to the Avid Platform established/lost, pass in implementation of the acs::ConnectionEventHandler interface when creating a BusAccess object.
For processing incoming messages (e.g. service operations/requests, channel messages and incoming responses to query operations), the Avid Connector API for C++ uses a pool of threads. The size of the thread pool can be configured by setting the ACS_BUS_MAX_CONCURRENT_OPERATIONS environment variable (the default is 32).
All outgoing messages are sent on the currently executing thread.
Each instance of a acs::BusAccess has its own private thread pool.
Using the Avid Connector API as a Client
The Connector API can be used to send requests to services, and to subscribe and publish to channels. Requests and responses use the BusMessage interface.
An example of this can be found in the BusClient project, in the examples provided with the API.
Providing Message Options
With each operation (e.g. query, send, broadcast) you may provide message options, via a MessageOptions object. The following options are available:
Timeout - Specifies a message timeout in ms for query operations. Default is 10000ms.
Durable - Sets whether message is durable. Default is false. NOTE: This option is currently not implemented, and will be revised in the future releases.
AnyCompatibleVersion - Sets whether the message is delivered to any compatible version of the service or to an exact version of the service. Default is true.
The onError method is only called when there are acs::OperationError-type errors. The acs::BusMessage passed to onSuccess() will contain any errors that occurred while executing the service operation.
Sending to Services
Sending to a service is a one-way communication from the client to the service. There is no response. In comparison to a broadcast (described below), the client is also guaranteed that no more than one service instance will process the message.
OperationResult::pointer opResult(new OperationResult("Send \"add\"")); ACSResult e = busClient->send(request.get(), opResult.get());
Broadcasting to Services
Broadcasting to a service is a one-way communication from the client to all instances of a given service. There is no response. As opposed to a send, the message is processed by every available instance of the service.
OperationResult::pointer opResult(new OperationResult("Send \"add\"")); ACSResult e = busClient->broadcast(request.get(), opResult.get());
Remote Zone and Multi-Zone Communications
The default behavior of the Avid Connector API is to communicate within its own local zone. All the examples provided above use this default behavior. If the local zone has been initialized in a multi-zone environment, however, it is possible communicate with services in other zones.
Zone-Specific Communications
To communicate with a specific remote zone, use the bus.Zone(const char* zoneId) object. The following are examples of communications with other zones:
Similar methods can be used to send() and broadcast() messages to multizone services. In all of the above cases, only services in the remote zone with an ID of aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaa are invoked. In addition, only service instances registered with a scope of multi-zone are considered.
Multi-Zone Communications
To communicate across multiple zones, use the bus->multiZone() object. The following are examples of multi-zone communications:
Note that `bus->multiZone()->query()` and `bus->multiZone()->send()` only send to one service instance in one zone. If there is a service instance in the local zone, it sends to that one. Otherwise, it sends to a service instance in a remote zone(if one is available and registered with the `multi-zone` scope). This is particularly useful if you know the service is in a zone, but aren't sure which one `bus.MultiZone.Broadcast` broadcasts to _all_ matching service instances in _all_ zones. ### Local Zone Communications Note that there is also a `bus->localZone()` object. Invoking `query`, `send`, and `broadcast` on this object is functionally equivalent to invoking the same methods on the base `bus` object. ## Wildcards Usage in the Realm If multiple realms of the same service are registered, you can use wildcards to address any service instance satisfying a wildcard expression. Wildcards can substitute for any letters/digits between the dot delimiters. For example, consider an Avid Platform service registered with the following realms: montreal.workgroup1; montreal.workgroup2; montreal.workgroup1.id1; montreal.workgroup1.id2; montreal.workgroup2.id1; montreal.workgroup2.id2;
In the above case, you can address the services by supplying the following wildcarded realms in the request:
A service must declare its complete list of possible error codes when creating its acs::ServiceInfo, via an array of acs::ErrorCosde (see ACSCreateServiceInfo3 and ACSCreateServiceInfo4).
The error code is part of the service’s public API and therefore should be a short sentence in uppercase wtih an underscore used as a separator, e.g. BAD_REQUEST, BULK_DUPLICATION, MISSING_ARGUMENT, QUOTA_EXCEEDED. The status parameter is the appropriate corresponding HTTP error code. The error messageTemplate is text in en_US locale, that may include %{identifier} placeholders for error message parameters. The messageTemplate (and any parameters) should convery meaningful information e.g. “Quota on %{resourceName} exceeded for %{projectName}.” The description parameter may contain an elaborate description of an issue for internal use only that will only appear in error logs.
responder->error(ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE, "acs::try_value and acs::try_value_from_string for 'num1' failed", errorParameters.get());
}
Multi-Zone Services
The default behavior of the Avid Connector API is to register services in the local zone scope. This means that by default services only receive requests from clients within the same zone. If the local zone is initialized in a multi-zone environment, however, it is possible to register a service in the multi-zone scope. This allows the service to be invoked by clients in any connected zone.
To register a service in the multi-zone scope, use the bus->multiZone() object:
// Note: Passing a service type to a service is *very* unusual, but handy in order to test some scenarios. boost::shared_ptr<CalculatorService> theService(new CalculatorService());
// How the service connects to the bus is driven by environment variables: ACS_GATEWAY_PORT and ACS_GATEWAY_HOST. boost::shared_ptr<acs::ConnectionInfo> connectionInfo(ACSCreateConnectionInfo0(), ACSDestroyConnectionInfo); boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess7(connectionInfo.get()), ACSDestroyBusAccess); ACSResult result = bus->connect(); if (result) { boost::shared_ptr<constchar> msg(ACSErrorMessage(result), ACSFreeStr); std::cerr << "Failed to connect to the platform (" << msg << ")"; return-1; } // Register this service in multi-zone scope. This allows the service to be invoked by clients in any connected zone. boost::shared_ptr<acs::BusAccessHost> multiZoneBus(bus->multiZone(), ACSDestroyBusAccessHost);
acs::ServiceContext* s = 0; acs::ServiceEventHandlers handlers = {0}; handlers.serviceEventHandlersSize = sizeof(acs::ServiceEventHandlers); handlers.registeringEventHandler = boost::dynamic_pointer_cast<acs::RegisteringEventHandler>(theService).get(); handlers.serviceConfigurationEventHandler = boost::dynamic_pointer_cast<acs::ServiceConfigurationEventHandler>(theService).get(); result = multiZoneBus->registerService(theService->serviceInfo().get(), &handlers, &s); if (result) { std::cerr << "Failed to register service: " << ACSErrorMessage(result); return-1; } boost::shared_ptr<acs::ServiceContext> serviceContextPtr(s, ACSDestroyServiceContext); theService->serviceContext(serviceContextPtr);
Local Zone Scope
Registering a service using the bus->localZone() object is functionally equivalent to registering it using the base bus object. The service is accessible within the local zone.
Avid Platform Service Events
Your service can indicate an interest in onRegistering and onConfigureService notifications, by re-implementing the corresponding methods in RegisteringEventHandler and ServiceConfigurationEventHandler. In addition, there are ChannelEventHandler and ConnectionEventHandler for channel and connection-related “events”.
Service Configuration Changes
When a service’s configuration changes, the onConfigureService method is called. The current service context and configuration are passed as an acs::ServiceContext* and acs::DataSet respectively.
Providing Service Status
When a service is registered, it starts sending heartbeats to report its current status (in the background). The reported status contains a status code.
By default, as long as the process is still running, the Avid Connector API reports an “OK” status. While this may be acceptable for simple services (like our calculator example), more complex services can explicitly supply their own status information. The C++ Connector API supports 5 status statues (OK, WARNING, ERROR, SUSPENDED, OFFLINE). Each state has its own behavior which define whether the service is still visible on the platform, and if it is able to recieve messages. See the table below to see the behavior defined by each status.
Status
Visible on Platform
Receives Requests
Example Use Case
OK
Yes
Yes
Service is fully functional (default state)
WARNING
Yes
Yes
Service is still fully functioning, but want to warn about some resource (ie. high memory useage, large db latency, many timeouts to another service)
ERROR
Yes
No
Service is not functioning properly, needs some intervention to fix (ie. ran out of memory, but wish to keep the process alive for debugging)
SUSPENDED
Yes
No
Service is ok, but cannot function properly due to an external resource (ie. DB connection lost and service cannot proceed without persisting data)
OFFLINE
No
No
Service process should stay alive, but should not be visible or routed to (ie. Keep service process alive during DB migration)
To set service status, use the ACSStatusCode data type, and call acs::BusAccessHost::serviceStatus(acs::ServiceContext* serviceContext, ACSStatusCode state, acs::AsyncOpResult* aor) or acs::BusAccessHost::serviceStatus(acs::ServiceContext* serviceContext, ACSStatusCode state, const char* code, const char* details, acs::AsyncOpResult* aor).
Providing Custom Service Health Information
One of the core operations provided by the Avid Connector API is serviceHealth. By default if the service receives this operation request it replies back with the following resultSet:
Service developers may override the serviceHealth operation by providing specific healthStatus or additional customHealthInfo. In this case, serviceHealth response may look like:
To override the default implementation of the serviceHealth operation (which always returns ACS_HS_OK), indicate so by calling the acs::ServiceInfo::overrideOperation method when creating the service’s ServiceInfo:
1 2 3 4 5 6 7 8 9 10 11
voidCalculatorService::createServiceInfo() { m_serviceInfo = boost::shared_ptr<acs::ServiceInfo>( ACSCreateServiceInfo3(m_serviceType.c_str(), m_realm.c_str(), 5, "A calculator implemented in C++!", sizeof(g_ErrorCodes)/sizeof(g_ErrorCodes[0]), g_ErrorCodes), ACSDestroyServiceInfo);
if (boost::iequals(operation, acs::ServiceHealthOperation)) { acs::HealthStatusSP status(ACSCreateHealthStatus(), ACSDestroyHealthStatus); // Reply with custom health data (valid JSON text). status->setStatus(ACS_HS_OK, "{ \"str\": \"some string data\", \"flag\" : true }"); // silly example custom 'health' data results->addResult(responder->requestMessage(), status.get()); return; }
// ... responder->respond(results.get()); }
Providing Compatible Version
Services on the Avid platform provide the concept of ‘Compatible Versions’ to provide a way to remain backwards compatibility with older versions of your service. When you declare that a newer version of your service is compatible with previous versions, it gains the ability to receive messages which were sent to an older version. This way clients who are sending messages to an older version of your service, will get a valid response, even if there are no instances of the old service running. For example, if we declare a new version of our service to be version == 3, but we also declare that it is compatible with versions 2 & 1, then any message which is sent to version 1, 2, or 3 of your service will be routed to version 3 if there is no running instance of your service in the specified version. To declare your service as compatible with other versions we add a compatibleVersions array and size to ACSCreateServiceInfo4.
Channels are analogous to Java Message Service (JMS) API Topics. When a message is posted to a channel, it is broadcast as a one-way communication to all the subscribers listening to that channel. It is important to note that channel messages are not persisted, and if you post to a channel that has no subscribers, it will not result in an error.
Channels are identified by their name. Subscribers interested in listening on a channel must either receive the channel name from the service that owns the channel, or use a predefined or well-known channel name.
Note: The names of channels should be namespaced and end with a past-tense verb. The reason for using past tense is, generally speaking, channel messages convey “facts” - i.e. notifications of something that has already happened. For example, avid.protools.project.status.changed.
Posting to a Channel
Channel messages are very similar to regular Avid Platform messages, but rather than have Parameters or Results, they simply have a Data member. The BusAccess API provides a method for posting to a channel:
NOTE: Once a channel is registered, anyone knowing the channel name can post to it. Although the poster is often the creator of the channel, this is not enforced in any way. This treatment is subject to change in a future edition of the API.
Subscribing to a Channel
Services that need to subscribe to channels can do so by subscribing through the BusAccess interface. First, a channel subscriber must implement the `` interface:
Note: In versions of the API less than 3.6.1, the ChannelEventHandler interface was used to subscribe to channel messages (rather than ChannelMessageSubscriber).
A ChannelSubscriber can be subscribed to a channel using subscribeToChannel() in BusAccess. Note that a single subscriber may subscribe to multiple channels at once. There is enough data in each of the subscriber methods for the subscriber to identify the channel to which the message pertains.
Clients can also subscribe to a shared channel by shared name, channel name and bindings. In this case, when multiple instances are subscribed to the same channel with the same shared name, only one instance will receive each message.
Bindings can be used to filter which messages are sent to subscribers. When subscribing to a channel, pass along a list of bindings to specify which messages are relevant to the subscriber:
1 2 3 4 5 6 7 8 9 10 11 12
// "Bind" two different channel event handlers... boost::shared_ptr<acs::ChannelBindings> bindings(ACSCreateChannelBindings(), ACSDestroyChannelBindings); bindings->addBinding("add"); bus->subscribeToChannel(sServiceTypePerfChan.c_str(), bindings.get(), &addPerfReporter, opResult.get());
ACSResult r = bus->zone(sTargetZone.c_str())->send(ready.get(), opResult.get());
The binding string will filter messages based on their dot-separated subject. The ‘*‘ character can be used as a wildcard to match entire words. For example, “com.*.info” will match “com.MapService.info” but “com.Map*.info” will not.
Unsubscribing from a Channel
To unsubscribe your subscriber from a specific channel:
The default behavior of the Avid Connector API is to scope channel communications within the local zone. All the examples given above use this default behavior. If the local zone has been initialized in a multi-zone environment, however, it is possible to communicate using channels across multiple zones.
Interacting with Channel Subscribers in a Specific Remote Zone
To communicate with channel subscribers in a specific remote zone, the bus.Zone(String zoneID) object should be used. The following examples send channel events and messages to subscribers in a specific zone only:
acs::ChannelMessageSP message(ACSCreateChannelMessage(channelName.c_str(), msg.c_str()), ACSDestroyChannelMessage); ACSResult result = busClient->postToChannel(message.get(), opResult.get());
In the above case, only multi-zone subscribers to the channel in the remote zone with ID zoneId receive these channel events and messages. If there are local scope subscribers listening on the same channel in that zone, they do not receive the messages (since they only receive messages sourced from their local zone).
Interacting with Channel Subscribers in All Zones
To communicate with channel subscribers in all connected zones, use the return from bus.multiZone method.
In the above case, all multi-zone subscribers to the channel across all connected zones will receive these channel events and messages. The local scope subscribers in the poster’s zone will also receive these messages. The local scope subscribers in remote zones, however, will not receive these messages.
Subscribing to Multi-Zone Channels
If the local zone has been initialized in a multi-zone environment, channel subscribers can listen using multi-zone scope. This means that they can receive channel messages from posters in remote zones.
To subscribe to a channel in the multi-zone scope, use the bus->multiZone() method:
In the above example, all multi-zone subscribers to the channel across all connected zones receive the channel events and messages. The local scope subscribers in the poster’s zone also receive the messages. The local scope subscribers in remote zones, however, do not receive the messages.
Local Zone Communications
Note that there are also channel methods in the bus->localZone() object. Invoking channel methods on this object is functionally equivalent to invoking the same methods on the base bus object.
Exposing Operations as REST requests
To declare that an operation supports a REST request, declare the specifics via acs::ServiceInfo::addOperation():
1 2 3 4 5 6 7 8 9 10 11 12 13
classACSBUS_PUBLIC_CLASS acs::ServiceInfo { public: // ... virtual ACSResult ACSBUS_CALL addOperation(constchar* name, constchar* description, constchar* exampleName, constchar* exampleContent, constchar* restPath =0, // Template of the service URI, curly brackets must be used to declare template parameter in the path, i.e. my/service/{resourceId}/{subresourceId}. ACSHttpMethod restMethod =ACS_HTTP_GET, // HTTP method for given operation. constchar* restBodyParam =0, // Name of the body parameter. constchar* resultParam =0, // Name of the result parameter. size_t nRestQueryParams =0, // Number of strings in restQueryParams. constchar** restQueryParams =0)=0;// Array of query parameter names. // ... };
For more detailed information about how REST requests are mapped and delivered to your service, please view the upstream HTTP docs.
Debugging
Debug Symbols
Debug symbols for the Connector API DLL (libavid-cppacs.dll) can be obtained by installing the NuGet package “Avid Connector API for C++ Symbols” (avid-acs-proxybal-cpp.symbols) for Visual Studio 2015 or “Avid Connector API for C++ (vc120) Symbols” (avid-acs-proxybal-cpp-vc120.symbols) for Visual Studio 2013, then copying the libavid-cppacs.pdb that is deep in the packages sub-folder e.g. packages\avid-acs-proxybal-cpp-vc120.symbols.3.6.1-build-0024-g823640a\build\native\bin\x64\v120\Debug to your bin/output folder.
Migrating to the Asynchronous Methods of BusAccess
In version 3.5 of the C++ Connector API, asynchronous versions of the query, send, broadcast, and postToChannel methods were added to the BusAccess interface.
Overloaded versions of the query method were added that accept an additional AsyncQueryResult parameter. A method of AsyncQueryResult will eventually get called whenever the query method completes:
Migrating to OperationContext, ChannelMessageContext and ChannelMessageSubscriber interfaces
In version 4.0 of the C++ Connector API, the acs::Responder interface was replaced by the acs::OperationContext interface (which adds the concept of a “context” when processing a service operation). The context is propagated to other services through the BusAccess instance returned by the acs::OperationContext.getBusAccess() method. This instance must be used when making any subsequent calls to the bus during the processing of a service operation.
Migrating code from the acs::Responder interface to acs::OperationContext involves modifying your service to support the acs::DispatchOperation interface and replacing all calls to any private or global instance of acs::BusAccess with a call to the acs::OperationContext::getBusAccess() method. For example:
The acs::ChannelSubscriber interface was also replaced with a “context aware” acs::ChannelMessageSubscriber. The acs::ChannelMessageSubscriber::onChannelMessage() method is passed a reference to an acs::ChannelMessageContext, which has methods to access the incoming ChannelMessage and the context aware instance of acs::BusAccess. This instance must be used when making any subsequent calls to the bus during the processing of the channel message.
1 2 3 4 5 6
classACSBUS_PUBLIC_CLASS acs::ChannelMessageContext { public: virtual acs::ChannelMessage& ACSBUS_CALL getChannelMessage()const= 0; // Access the channel message. virtual acs::BusAccess& ACSBUS_CALL getBusAccess()const= 0; // Instance of BusAccess appropriate for the operation's context. };