Prerequisites

Before proceeding with the tutorial, please install the following software:

  • Oracle VM VirtualBox
  • HashiCorp Vagrant
  • Avid Platform Virtual Machine

For instructions on setting up VirtualBox, Vagrant, and the Avid Platform Virtual Machine, see the README included in the Avid Platform Virtual Machine download file (avid-platform-vagrant-vm-YYYY-MM-DD-XXXX.zip).

In addition:

1. Install all the RPMs delivered in the Avid Connector API ZIP file (located in the RPMS directory) using the Linux *yum* command:

    
1
2
> cd RPMS
> yum -y --disablerepo=* install [full RPM file name]
2. Install the following from *base*:
1
> yum -y install libuuid libuuid-devel log4cpp log4cpp-devel gcc gcc-c++ git rpm-build createrepo
3. c++14 support There is support for c++14 in the form of libraries, both static and shared, that have been built to support c++14. THey are delivered in the corresponding RPM packages. Legacy packages: avid-acs-proxybal-cpp (Shared libraries) avid-acs-proxybal-cpp-devel (Static libs and headers) c++14 packages: avid-acs-proxybal-cpp-cpp14 (Shared libraries) avid-acs-proxybal-cpp-devel-spp14 (Static libs and headers) 4. Boost Currently the Linux BAL is built against boost 1.56.0 If you want to compile against the same headers, you can pull from http://muc-srvrepo1:8081/nexus/service/local/repositories/cloud.releases.rpm/content/com/avid/3rdparty/boost1/56/0/boost-devel/1.56.0-1.el6.avid/boost-devel-1.56.0-1.el6.avid.rpm

Creating the Project Structure

Switch to the examples directory, which contains a makefile and three stub files: main.cpp, ExampleService.h and ExampleService.cpp.

  • Linux
    1. Change directory:

      1
      > cd linux
    2. Make sure the stubbed files compile on your machine:

      1
      2
      3
      4
      > make
      # verify we have executable 'example'
      > ls
      > make clean

Connecting to the Platform

  1. Add the following content to main.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <iostream>

    #include "BusAPI.h"
    #include "BusAccess.h"

    int main(int argc , char* argv[] ) {
    boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess0(), ACSDestroyBusAccess);

    ACSResult result = bus->connect();
    if (result) {
    std::cerr << "Failed to connect to the broker (" << ACSErrorMessage(result) << ")";
    return -1;
    }

    std::cout << "Connected to the Bus!" << std::endl;

    return 0;
    }
  2. If you are connecting to a remote host (using vagrant) you need to set two environment variables.

    1
    2
    ACS_GATEWAY_PORT=9500
    ACS_GATEWAY_HOST=avid-platform-zone1

Depending on your platform you should build and run the above code:

  • Linux

    1. Build the project:

      1
      > make
    2. Run the application:

    1
    ./example
    1. You should see the following output:

      1
      Connected to the Bus!

Creating a Service

  1. Add the following to ExampleService.h:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #ifndef _EXAMPLE_SERVICE_H
    #define _EXAMPLE_SERVICE_H

    #include <iostream>

    #include "BusAPI.h"
    #include "BusAccess.h"

    #define SERVICE_TYPE "avid.tutorial.cpp.service"
    #define SERVICE_VERSION 1
    #define SERVICE_REALM "global"

    class ExampleService : public acs::DispatchOperation {
    public:

    ExampleService();
    ~ExampleService();
    virtual void ACSBUS_CALL invoke(const char* operation, acs::Parameters* parameters, acs::Responder* responder);
    bool submitJob(acs::Parameters *parameters, acs::ResultsSP results, std::string &errorCode, acs::ParametersSP errorParameters);
    int getParameterAsInt(acs::Parameters *parameters, const char *name);
    acs::ServiceInfoSP getServiceInfo();
    acs::ServiceInfoSP createServiceInfo();

    private:
    acs::ServiceInfoSP serviceInfo;
    const std::string serviceType;
    const std::string serviceRealm;
    const int serviceVersion;

    static const char* INVALID_JOB_ID;
    static const char *INVALID_EXECUTION_TIME;
    static acs::ErrorCode errorCodes[2];
    };

    #endif
  2. Add the following to ExampleService.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    #include <iostream>
    #include <boost/shared_ptr.hpp>
    #include <boost/algorithm/string/predicate.hpp>

    #include "ExampleService.h"
    #include "BusAPI.h"
    #include "BusAccess.h"

    ExampleService::ExampleService() :serviceType(SERVICE_TYPE), serviceRealm(SERVICE_REALM), serviceVersion(SERVICE_VERSION) {
    serviceInfo = createServiceInfo();
    };

    ExampleService::~ExampleService();

    void ExampleService::invoke(const char *operation, acs::Parameters *parameters, acs::Responder *responder) {
    acs::ResultsSP results = acs::ResultsSP(ACSCreateResults());
    std::string errorCode;
    acs::ParametersSP errorParameters = acs::ParametersSP(ACSCreateParameters(), ACSDestroyParameters);

    if (boost::iequals(operation, "submitJob")) {
    if(submitJob(parameters, results, errorCode, errorParameters)) {
    responder->respond(results.get());
    } else {
    responder->error(errorCode.c_str(), "", errorParameters.get());
    }
    } else {
    std::string op(operation);
    errorCode = "The operation " + op + " is not supported.";
    responder->error(errorCode.c_str(), "", errorParameters.get());
    }
    };

    int ExampleService::getParameterAsInt(acs::Parameters *parameters, const char *name) {

    acs::Parameter *param = parameters->find(name);
    const ACSVariant *var = param->value();
    return ACSVariant_cast<int>(var);
    };

    bool ExampleService::submitJob(acs::Parameters *parameters, acs::ResultsSP results, std::string &errorCode, acs::ParametersSP errorParameters) {

    int jobId = getParameterAsInt(parameters, "jobId");
    int execTime = getParameterAsInt(parameters, "execTime");

    if (jobId < 1) {
    errorCode = INVALID_JOB_ID;
    errorParameters->addParameter("jobId", acs::Variant(jobId));
    return false;
    } else if (execTime <= 0) {
    errorCode = INVALID_EXECUTION_TIME;
    errorParameters->addParameter("execTime", acs::Variant(execTime));
    return false;
    } else {
    results->addResult("status", acs::Variant("submitted"));
    return true;
    }
    };

    acs::ServiceInfoSP ExampleService::getServiceInfo() {
    return serviceInfo;
    };

    acs::ServiceInfoSP ExampleService::createServiceInfo() {
    acs::ServiceInfoSP serviceInfo = boost::shared_ptr<acs::ServiceInfo>(
    ACSCreateServiceInfo3(serviceType.c_str(), serviceRealm.c_str(), serviceVersion, "An example c++ service!", sizeof(this->errorCodes) / sizeof(errorCodes[0]), errorCodes),
    ACSDestroyServiceInfo);

    serviceInfo->addOperation("submitJob",
    "Submit job for Execution, respond with info whether job submitted successfully or not",
    "submitJob",
    (std::string("{") +
    "\"serviceType\":\"" + serviceType + "\","
    "\"serviceRealm\": \"" + serviceRealm + "\","
    "\"serviceVersion\": 1,"
    "\"op\": \"submitJob\","
    "\"paramSet\": {"
    "\"jobId\": 12345,"
    "\"execTime\": 5"
    "}}").c_str(),
    "example/submitJob/{jobId}", ACS_HTTP_POST, 0, "");
    return serviceInfo;

    };

    const char* ExampleService::INVALID_JOB_ID = "InvalidJobId";

    const char* ExampleService::INVALID_EXECUTION_TIME = "InvalidExecutionTime";

    acs::ErrorCode ExampleService::errorCodes[2] = {
    {INVALID_JOB_ID, 500, ACS_SEV_ERROR, "Invalid jobId: %{jobId}"},
    {INVALID_EXECUTION_TIME, 500, ACS_SEV_ERROR, "Invalid execution time: %{execTime}"}
    };
  3. Next, update the main.cpp file to create the service and start it:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include <iostream>
    #include <boost/shared_ptr.hpp>

    #include "BusAPI.h"
    #include "BusAccess.h"
    #include "ExampleService.h"

    class AsyncRegisterServiceResultImpl : public acs::AsyncRegisterServiceResult
    {
    public:
    virtual void onSuccess()
    {
    // do something successful
    }

    virtual void onError(const acs::OperationError* error)
    {
    // do somethign with the error
    }

    virtual void addRef() const
    {
    }

    virtual void release() const
    {
    }
    };

    int main(int argc , char* argv[] ) {
    boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess0(), ACSDestroyBusAccess);

    ACSResult result = bus->connect();
    if (result) {
    std::cerr << "Failed to connect to the broker (" << ACSErrorMessage(result) << ")";
    return -1;
    }

    std::cout << "Connected to the Bus!" << std::endl;

    acs::ServiceContext *serviceContext = 0;
    ExampleService exampleService;
    boost::shared_ptr<acs::AsyncRegisterServiceResult> arsr = boost::shared_ptr<acs::AsyncRegisterServiceResult>(new AsyncRegisterServiceResultImpl());

    result = bus->registerService(exampleService.getServiceInfo().get(), &serviceContext, arsr.get());
    if (result) {
    std::cerr << "Failed to register the service: " << ACSErrorMessage(result);
    return -1;
    }

    acs::ServiceContextSP serviceContextPtr(serviceContext, ACSDestroyServiceContext);
    std::cout << "Starting the ExampleService!" << std::endl;
    bus->runService(serviceContextPtr.get(), static_cast<acs::DispatchOperation*>(&exampleService));


    return 0;
    }
  4. Now rebuild your project and run it. You should see the following output:

    1
    2
    Connected to the Bus!
    Starting the ExampleService!
  5. Test that the service is working:

    • Open the ACS Monitor, and scroll down to locate the avid.tutorial.cpp.service.
    • Expand the Service Operations List for the service. The list should include the submitJob operation.
    • Click the 'submitJob' link. Its corresponding message should appear in the code editor area, under the Request to field.
    • Click the 'Query' button. Your service's response appears in the Response field.
  6. Press Ctrl+C in the terminal to terminate the service.

Congratulations! You have successfully registered your service on the Platform.

Sending Requests to Another Service

Before starting this procedure, complete the following procedure first: Subscribing to Service Events.

  1. Add the following response handler to ExampleClient.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    class GetNumberOfSubmittedJobsResponseHandler : public acs::ResponseHandler {
    public:
    GetNumberOfSubmittedJobsResponseHandler() : m_referenceCount(1), m_done(false), m_value(0), m_printed(false) {
    }

    ~GetNumberOfSubmittedJobsResponseHandler() {
    }

    virtual void onResponse(acs::BusMessage* response) {
    const ACSVariant *result = response->getResults()->at(0)->value();
    int value = ACSVariant_cast<int>(result);
    std::cout << "The calculator responded with the result " << value << std::endl;
    m_done = true;
    }

    virtual void onMessageProcessingError(const char* error) {
    m_error = error;
    std::cout << "The calculator responded with an error: " << error << std::endl;
    m_done = true;
    }

    virtual void onTimeout() {
    std::cout << "The query timed out." << std::endl;
    m_done = true;
    }

    virtual unsigned AddRef() {
    #if defined BOOST_INTERLOCKED_INCREMENT
    BOOST_SP_INTERLOCKED_INCREMENT(&m_referenceCount);
    #else
    ++m_referenceCount;
    #endif
    return (unsigned)m_referenceCount;
    }

    virtual unsigned Release() {
    #if defined BOOST_INTERLOCKED_DECREMENT
    BOOST_INTERLOCKED_DECREMENT(&m_referenceCount);
    #else
    --m_referenceCount;
    #endif
    unsigned r = (unsigned)m_referenceCount;
    if (!r)
    delete this;
    return r;
    }

    protected:
    GetNumberOfSubmittedJobsResponseHandler(unsigned initialRefCount)
    : m_referenceCount(initialRefCount)
    , m_done(false)
    , m_value(0)
    , m_printed(false)
    {
    }

    private:
    volatile long m_referenceCount;
    bool m_done;
    int m_value;
    bool m_printed;
    std::string m_error;
    };
  2. Update ExampleClient.cpp to include the following after subscribing to channels, including an implementation for AsyncQueryResultImpl class:

    1
    2
    3
    4
    5
    6
    7
    8
    acs::BusMessageSP request(ACSCreateBusMessage2("avid.acs.calculator", "global.test", 3, "add"), ACSDestroyBusMessage);
    request->getParameters()->addParameter("num1", acs::Variant(2));
    request->getParameters()->addParameter("num2", acs::Variant(4));

    boost::shared_ptr<acs::AsyncQueryResult> aqr = boost::shared_ptr<acs::AsyncQueryResult>(new AsyncQueryResultImpl());

    std::cout << "querying calc" << std::endl;
    bus->query(request.get(), aqr.get());
  3. Rebuild and start your example service. You should see the following output:

    1
    2
    3
    Connected to the Bus!
    querying calc
    The calculator responded with the result 6
  4. You have now successfully queried a service.

Note: It is important to note that DispatchOperation does not have a reference to the busAccess that is currently connected to the Platform. In order for a service to query/send/broadcast to other services, as well as post/subscribe to channels we must pass the serviceContext (which contains a pointer to the busAccess) for use by the service. The most convenient way to do this is by adding a new member and function to ExampleService.

1
2
3
4
5
6
7
8
9
10
11
12
// ExampleService.h
// Add this function delcaration
void serviceContext(acs::ServiceContext *context);

// Also add the coresponding member variable
acs::ServiceContext *m_context;

// ExampleService.cpp
// Add this function definition
void ExampleService::serviceContext(acs::ServiceContext *context) {
m_context = context;
}

Posting Events when a Job Submitted And Completed

To post to a channel when a job has started/completed, edit ExampleService.

  1. Update ExampleService.h:

    1
    2
    3
    // Add these declations to ExampleService.h
    void postJobStartToChannel(const int jobId, const int execTime);
    void postJobCompletedToChannel(const int jobId, const int execTime);
  2. Update ExampleService.cpp, including an implementation of AsyncOpResultImpl class:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // Add these definitions to ExampleService.cpp
    // Make sure to include boost/thread/thread.cpp
    void ExampleService::postJobStartToChannel(const int jobId, const int execTime) {
    acs::ChannelMessageSP startJobMessage(ACSCreateChannelMessage("cpp.service.jobs", "submit"), ACSDestroyChannelMessage);
    startJobMessage->dataSet()->add("jobId", acs::Variant(jobId));
    boost::shared_ptr<acs::AsyncOpResult> aor = boost::shared_ptr<acs::AsyncOpResult>(new AsyncOpResultImpl());

    m_context->bus()->postToChannel(startJobMessage.get(), aor.get());

    boost::thread t(&ExampleService::postJobCompletedToChannel, this, jobId, execTime);
    };

    void ExampleService::postJobCompletedToChannel(const int jobId, const int execTime) {

    boost::this_thread::sleep(boost::posix_time::seconds(execTime));
    boost::shared_ptr<acs::AsyncOpResult> aor = boost::shared_ptr<acs::AsyncOpResult>(new AsyncOpResultImpl());

    acs::ChannelMessageSP jobDoneMessage(ACSCreateChannelMessage("cpp.service.jobs", "complete"), ACSDestroyChannelMessage);
    jobDoneMessage->dataSet()->add("jobId", acs::Variant(jobId));

    m_context->bus()->postToChannel(jobDoneMessage.get(), aor.get());
    }

    // Update submitJob in ExampleService.cpp
    bool ExampleService::submitJob(acs::Parameters *parameters, acs::ResultsSP results, std::string &errorCode, acs::ParametersSP errorParameters) {

    int jobId = getParameterAsInt(parameters, "jobId");
    int execTime = getParameterAsInt(parameters, "execTime");

    if (jobId < 1) {
    errorCode = "JobId is invalid";
    errorParameters->addParameter("jobId", acs::Variant(jobId));
    return false;
    }
    else if (execTime <= 0) {
    errorCode = "ExecTime is invalid";
    errorParameters->addParameter("execTime", acs::Variant(execTime));
    return false;
    }
    else {
    postJobStartToChannel(jobId, execTime);

    results->addResult("status", acs::Variant("submitted"));
    return true;
    }
    };
  3. Rebuild the project to verify it builds without error.

  4. Verify the service in the ACS Monitor.

Congratulations! You have successfully published events to the Platform.

At this point service behavior in the ACS Monitor is unchanged. Next, we create a consumer of our service events.

Subscribing to Service Events

Other services and clients may wish to listen on the channel to which our service posts, so they can receive event information. We demonstrate this functionality by creating a new client that listens on the channel.

  1. Set up ExampleClient:

    • Linux
      1. Create a file called ExampleService.cpp.
  2. Add the following to ExampleClient.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    // ExampleClient.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <boost/shared_ptr.hpp>

    #include "BusAPI.h"
    #include "BusAccess.h"

    /**
    * When listening on a channel we must pass in a ChannelEventHandler which will
    * delgate what we do with the information recieved on the channel.
    */

    class JobStartedEventHandler : public acs::ChannelEventHandler {
    public:
    virtual void onChannelMessage(const acs::ChannelMessage& message) {
    const acs::DataSet *data = message.dataSet();
    int jobId = ACSVariant_cast<int>(data->value("jobId"));

    std::cout << "The job " << jobId << " has been submitted" << std::endl;
    };

    virtual void onEndOfChannel(const char* channelName) {};
    };

    class JobCompletedEventHandler : public acs::ChannelEventHandler {
    public:
    virtual void onChannelMessage(const acs::ChannelMessage& message) {
    const acs::DataSet *data = message.dataSet();
    int jobId = ACSVariant_cast<int>(data->value("jobId"));

    std::cout << "The job " << jobId << " has been completed" << std::endl;
    }

    virtual void onEndOfChannel(const char *channelName) {};
    };

    class AsyncOpResultImpl : public acs::AsyncOpResult
    {
    public:
    virtual void onSuccess()
    {
    // do something successful
    }

    virtual void onError(const acs::OperationError* error)
    {
    // do somethign with the error
    }

    virtual void addRef() const
    {
    }

    virtual void release() const
    {
    }
    };

    int main(int argc, char* argv[]) {
    boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess0(), ACSDestroyBusAccess);

    ACSResult result = bus->connect();
    if (result) {
    std::cerr << "Failed to connect to the broker (" << ACSErrorMessage(result) << ")";
    return -1;
    }

    std::cout << "Connected to the Bus!" << std::endl;
    boost::shared_ptr<acs::AsyncOpResult> aor = boost::shared_ptr<acs::AsyncOpResult>(new AsyncOpResultImpl());

    acs::ChannelEventHandler *jobStartedEventHandler = new JobStartedEventHandler();
    acs::ChannelBindingsSP submittedBinding(ACSCreateChannelBindings(), ACSDestroyChannelBindings);
    submittedBinding->addBinding("submit");

    bus->subscribeToChannel("cpp.service.jobs", submittedBinding.get(), jobStartedEventHandler, aor.get());

    acs::ChannelEventHandler *jobCompletedEventHandler = new JobCompletedEventHandler();
    acs::ChannelBindingsSP completedBinding(ACSCreateChannelBindings(), ACSDestroyChannelBindings);
    completedBinding->addBinding("complete");

    bus->subscribeToChannel("cpp.service.jobs", completedBinding.get(), jobCompletedEventHandler, aor.get());


    std::cout << "Enter any key to quit client" << std::endl;
    char key;
    std::cin >> key;

    delete jobStartedEventHandler;
    delete jobCompletedEventHandler;

    return 0;
    }
  3. Setup the project to build both Service and Client:

    • Linux
      1. Update makefile to look like this:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        INCLUDEDIRS = \
        -I/usr/local/include \
        -I/opt/avid/include \
        -I/opt/avid/include/avid \
        -I/opt/avid/include/avid/acs-proxybal-cpp \
        -I/opt/avid/include/avid/acs-proxybal-cpp/message \
        -I/opt/avid/include/avid/acs-proxybal-cpp/message/Serializer \
        -I/opt/avid/include/avid/acs-proxybal-cpp/registry \
        -I/opt/avid/include/avid/acs-proxybal-cpp/wqueue \
        -I/opt/avid/include/avid/acs-proxybal-cpp/log \

        LIBDIRS = \
        -L/usr/local/lib \
        -L/usr/lib \
        -L/usr/lib64 \
        -L/opt/avid/lib \
        -L/opt/avid/lib64 \
        -L../../../libs \

        CPPBUSACCESS_LIB = -lavid-acs-proxybal-cpp
        LIBRARY_NAME_JSON = -ljson
        LIBRARY_NAME_LOG4CPP = -llog4cpp
        LIBRARY_NAME_PTHREAD = -lpthread
        LIBRARY_NAME_UUID = -luuid
        LIBRARY_NAME_BOOST_FILESYSTEM = -lboost_filesystem
        LIBRARY_NAME_BOOST_DATE_TIME = -lboost_date_time
        LIBRARY_NAME_BOOST_THREAD = -lboost_thread
        LIBRARY_NAME_BOOST_CHRONO = -lboost_chrono
        LIBRARY_NAME_THRIFT = -lthrift
        LIBRARY_NAME_PROTOBUF = -lprotobuf
        LIBRARY_NAME_PROTOBUF_LITE = -lprotobuf-lite
        LIBRARY_NAME_PROTOC = -lprotoc

        CXX = g++
        CXXFLAGS = -Wall -g -DNDEBUG

        EXAMPLESVC_CFLAGS = ${CXXFLAGS} \
        $(INCLUDEDIRS)
        EXAMPLESVC_SRCS := ExampleService.cpp main.cpp
        EXAMPLESVC_OBJS = $(EXAMPLESVC_SRCS:.cpp=.o) # expands to list of object files

        EXAMPLE_CLIENT_SRCS := ExampleClient.cpp
        EXAMPLE_CLIENT_OBJS = $(EXAMPLE_CLIENT_SRCS:.cpp=.o)

        EXAMPLESVC_LDFLAGS = $(LIBDIRS) $(LIBRARY_NAME_PTHREAD) $(LIBRARY_NAME_UUID) $(CPPBUSACCESS_LIB) $(LIBRARY_NAME_JSON) $(LIBRARY_NAME_LOG4CPP) $(LIBRARY_NAME_BOOST_DATE_TIME) $(LIBRARY_NAME_BOOST_FILESYSTEM) $(LIBRARY_NAME_BOOST_CHRONO) $(LIBRARY_NAME_BOOST_THREAD) $(LIBRARY_NAME_THRIFT) $(LIBRARY_NAME_PROTOBUF)

        all : service client

        client : client.o
        $(CXX) $(EXAMPLESVC_CFLAGS) -o client $(EXAMPLE_CLIENT_OBJS) $(EXAMPLESVC_LDFLAGS)

        client.o : ExampleClient.cpp
        $(CXX) $(EXAMPLESVC_CFLAGS) -c -o ExampleClient.o ExampleClient.cpp

        service : main.o ExampleService.o
        $(CXX) $(EXAMPLESVC_CFLAGS) -o cpp-service $(EXAMPLESVC_OBJS) $(EXAMPLESVC_LDFLAGS)

        main.o: main.cpp ExampleService.h
        $(CXX) $(EXAMPLESVC_CFLAGS) -c main.cpp

        ExampleService.o: ExampleService.cpp
        $(CXX) $(EXAMPLESVC_CFLAGS) -c -o ExampleService.o ExampleService.cpp

        clean:
        rm -f *.o
        rm -f cpp-service
        rm -f client
      2. The following now applies:

        1
        2
        3
        4
        5
        6
        # Build service
        > make service
        # Build Client
        > make client
        # Build both
        > make
      3. In two different terminals start service and client:

        1
        2
        ./cpp-service
        ./client
  4. Open the ACS Monitor.

  5. Locate the cpp.tutorial.service and send submitJob a few times with different values for the jobId.

  6. Go back to the terminal running the client. You should see the following output for each submitted job:

    1
    2
    3
    The job <jobId> has been submitted
    # After <execTime> seconds
    The job <jobId> has completed

Congratulations! You have created client, which is subscribed to events being posted by your service

Sending REST Requests to Your Service

  1. Log in to avid-platform-zone1 with ssh (username: root; password: vagrant):

    1
    ssh root@avid-platform-zone1
  2. Send POST request to your service with curl command, use avidAccessToken generated before:

    1
    2
    3
    curl -H "Content-Type: application/json" --data '{"execTime":15000}' "http://avid-platform-zone1:8080/apis/avid.tutorial.cpp.service;version=1/jobs/10?_avidAccessToken=ZWEwZDhlOTItYmJhZS00YTQ5LWFiN2QtNTAyYWMwZjhjZjJj"

    #NOTE: request should be routed via NGINX and use HTTPS protocol: https://avid-platform-zone1/apis/...

    Given HTTP request will send following message to your service:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "serviceType": "avid.tutorial.cpp.service",
    "serviceRealm": "global",
    "serviceVersion": 1,
    "op": "submitJob",
    "paramSet": {
    "jobId": 10,
    "execTime": 15000
    }
    }
  3. Press Ctrl+C in the shells to terminate both service and client.

Delivering the Service as an RPM On CentOS 6.x/RedHat 6.x

To try this and the next section in action, you need CentOS 6.x/RedHat 6.x system. You may use your VM, created on the first steps of this tutorial, with the host name avid-platform-zone1. The following description is based on assumption, that you are using this VM, to test RPM creation.

  1. Create cpp-service.spec file:

    1
    touch cpp-service.spec
  2. Add similar content to the following to this file:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
       %global debug_package %{nil}

    Name: cpp-service
    Version: 1
    Release: 1
    Summary: Tutorial service based on Avid Connector API for Cpp
    License: Avid EULA
    Vendor: Avid Technology Inc.
    URL: http://avid.com
    Source: %{name}-src.tgz
    BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
    Prefix: /opt/avid

    AutoReqProv: no

    Requires: avid-acs-proxybal-cpp

    %description
    Tutorial service based on Avid Connector API for Cpp

    %build
    if [ ! -f cpp-service ]
    then
    echo "You have to call 'make' first"
    exit -1
    fi

    %prep
    %setup -c -n %{name}

    %install
    mkdir -p "${RPM_BUILD_ROOT}/opt/avid/sbin"
    mkdir -p "${RPM_BUILD_ROOT}/opt/avid/lib/cpp_modules/%{name}/bin"

    mv cpp-service "${RPM_BUILD_ROOT}/opt/avid/lib/cpp_modules/%{name}/bin/"
    ln -s /opt/avid/lib/cpp_modules/%{name}/bin/%{name} "${RPM_BUILD_ROOT}/opt/avid/sbin/%{name}"

    # Exit clean
    exit 0

    %files
    %attr(755, root, root) /opt/avid/lib/cpp_modules/%{name}/bin/%{name}
    /opt/avid/sbin/%{name}
    /opt/avid/lib/cpp_modules/%{name}
  1. Create file create-rmp.sh file. THere is a template in the examples/full directory that can also be modified:

    1
    touch create-rpm.sh
  2. Add the following content to this file:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #!/bin/bash

    # Print usage if we have no args
    if [ $# -eq 0 ]; then
    echo 'Usage:'
    echo ' create-rpm <spec-file>'
    echo 'Description:'
    echo ' Used by jenkins jobs to create rpms. Specify'
    echo ' the spec file and this script will tar the'
    echo ' directory containing that file and run rpmbuild'
    echo ' in the parent dir. See source for details.'
    echo 'Examples:'
    echo ' ./create-rpm.sh controllers/registry/avid-acs-registry.spec'
    echo ' Creates ./rpmbuild/RPMS/x86_64/<rpm-name>.rpm'
    exit 0
    fi

    RPM_VERSION=${RPM_VERSION-"0.0.0_DEV"}
    if [ -f ./VERSION ]; then
    # file generated by jenkins job
    RPM_VERSION="$(cat ./VERSION)"
    fi

    ORIGINAL_DIR="$(pwd)"

    # The directory that we are going to tar
    SOURCE_DIR="$(dirname $1)"

    # The base name of the spec file. This name will be used
    # for the tar file.
    SPEC_NAME="$(basename $1 .spec)"

    # Set up rpmbuild tree
    mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}

    pushd "$SOURCE_DIR"

    # Compress sources and move into rpmbuild tree
    tar -czf "$SPEC_NAME-src.tgz" ./* --exclude=.acignore --exclude=node_modules --exclude=*.swp --exclude=*~ --exclude=bal-thrift
    mv "$SPEC_NAME-src.tgz" "$ORIGINAL_DIR/rpmbuild/SOURCES/"

    # Copy spec into rpmbuild tree
    cp "$SPEC_NAME.spec" "$ORIGINAL_DIR/rpmbuild/SPECS/"

    popd

    # Build rpm
    rpmbuild --define "_rpmversion $RPM_VERSION" --define "_topdir `pwd`/rpmbuild" -bb "rpmbuild/SPECS/$SPEC_NAME.spec"
  3. Build your project:

    1
    ./create-rpm.sh cpp-service.spec
  1. Make create-rpm.sh executable and create RPM:

    1
    2
    chmod 755 create-rpm.sh
    ./create-rpm.sh cpp-service.spec
  2. Install your RPM:

    1
    yum install rpmbuild/RPMS/x86_64/cpp-service-1-1.x86_64.rpm
  3. Run installed service:

    1
    /opt/avid/sbin/cpp-service
  4. Test how service is working in the ACS Monitor

  5. Press Ctrl+C to terminate the service.

Congratulations! You have successfully created an RPM for your service, installed and started it.

Daemonizing the Service on CentOS 6.x/RedHat 6.x

In this section we daemonize the service, provide a default configuration for it, output logs into a file and add log rotation. If you are still in the ssh shell from the previous section, exit and return to your project directory. Your current directory must be cpp-service.

  1. Create etc, etc/init.d, etc/syconfig and etc/logrotate.d directories:

    1
    2
    3
    4
    mkdir etc
    mkdir etc/init.d
    mkdir etc/sysconfig
    mkdir etc/logrotate.d
  2. Create a cpp-service file in each of the etc/init.d, etc/syconfig and etc/logrotate.d directories:

    1
    2
    3
    touch etc/init.d/cpp-service
    touch etc/sysconfig/cpp-service
    touch etc/logrotate.d/cpp-service
  3. Add the following content to etc/sysconfig/cpp-service:

    1
    2
    3
    4
    5
    #export ACS_GATEWAY_HOST=127.0.0.1
    #export ACS_GATEWAY_PORT=9500
    #export ACS_BUS_QUERY_TIMEOUT=10000
    #export ACS_LOG_LEVEL=info
    export ACS_IPC_PROTOBUF=true
  4. Add the following content to etc/logrotate.d/cpp-service:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /var/log/avid/cpp-service/cpp-service.log {
    size 100M
    rotate 10
    copytruncate
    missingok
    notifempty
    compress
    delaycompress
    }
  5. Add the following content to etc/init.d/cpp-service:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #!/bin/sh
    #
    # cpp-service Tutorial C++ service
    #
    # chkconfig: - 86 15
    # description: Tutorial service based on Avid Connector API for C++
    #

    ### BEGIN INIT INFO
    # Provides: cpp-service
    # Required-Start: $local_fs $network $remote_fs
    # Required-Stop: $local_fs $network $remote_fs
    # Short-Description: Tutorial C++ service
    # Description: Tutorial service based on Avid Connector API for C++
    #
    ### END INIT INFO

    AVID_SERVICE_NAME="cpp-service"
    AVID_SERVICE_PID_FILE="/var/run/avid/${AVID_SERVICE_NAME}/${AVID_SERVICE_NAME}.pid"
    AVID_SERVICE_START_CMD="/opt/avid/sbin/${AVID_SERVICE_NAME}"
    AVID_SERVICE_STDOUT_FILE="/var/log/avid/${AVID_SERVICE_NAME}/${AVID_SERVICE_NAME}.log"
    AVID_SERVICE_STDERR_FILE="/var/log/avid/${AVID_SERVICE_NAME}/${AVID_SERVICE_NAME}.err"

    # Source service config file if it exists
    [ -e /etc/sysconfig/$AVID_SERVICE_NAME ] && . /etc/sysconfig/$AVID_SERVICE_NAME

    # Source function library.
    . /opt/avid/share/avid-daemonizer/avid-daemonizer.sh

    # Call into the generic case statement in acs-functions
    avid_svc_ctrl "$@"
  6. Modify cpp-service.spec:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    %global debug_package %{nil}

    Name: cpp-service
    Version: 2
    Release: 1
    Summary: Tutorial service based on Avid Connector API for Cpp
    License: Avid EULA
    Vendor: Avid Technology Inc.
    URL: http://avid.com
    Source: %{name}-src.tgz
    BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
    Prefix: /opt/avid

    AutoReqProv: no

    Requires: avid-acs-proxybal-cpp

    %description
    Tutorial service based on Avid Connector API for Cpp

    %build
    if [ ! -f cpp-service ]
    then
    echo "You have to call 'make' first"
    exit -1
    fi

    %prep
    %setup -c -n %{name}

    %install
    mkdir -p "${RPM_BUILD_ROOT}/opt/avid/sbin"
    mkdir -p "${RPM_BUILD_ROOT}/opt/avid/lib/cpp_modules/%{name}/bin"

    mv etc "${RPM_BUILD_ROOT}/"
    mv cpp-service "${RPM_BUILD_ROOT}/opt/avid/lib/cpp_modules/%{name}/bin/"
    ln -s /opt/avid/lib/cpp_modules/%{name}/bin/%{name} "${RPM_BUILD_ROOT}/opt/avid/sbin/%{name}"

    %post
    chkconfig --add %{name}

    # Exit clean
    exit 0

    %preun
    service %{name} stop ||:

    # If uninstalling, remove chkconfig entry
    if [ $1 -eq 0 ]; then
    chkconfig --del %{name}
    fi

    %postun
    # If upgrading, restart the service if it was already running
    if [ $1 -eq 1 ]; then
    chkconfig %{name} off
    service %{name} condrestart ||:
    fi

    %files
    %attr(755, root, root) /etc/init.d/%{name}
    %attr(644, root, root) /etc/logrotate.d/%{name}
    %attr(644, root, root) /etc/sysconfig/%{name}
    %attr(755, root, root) /opt/avid/lib/cpp_modules/%{name}/bin/%{name}
    /opt/avid/sbin/%{name}
    /opt/avid/lib/cpp_modules/%{name}
  7. Build your project:

    1
    make
  8. Log in to avid-platform-zone1 with ssh (username: root; password: vagrant):

    1
    ssh root@avid-platform-zone1
  9. In the ssh shell change your current directory:

    1
    cd /root/cpp-service
  10. Create the RPM:

    1
    ./create-rpm.sh cpp-service.spec
  11. Install your RPM:

    1
    yum install rpmbuild/RPMS/x86_64/cpp-service-2-1.x86_64.rpm
  12. Run the installed service:

    1
    service cpp-service start
  13. Test that the service is working using the ACS Monitor as previously outlined.

  14. Monitor service logs:

    1
    2
    3
    service cpp-service logs
    #or
    tail -f /var/log/avid/cpp-service/cpp-service.log

Congratulations! You have successfully completed the tutorial.