- Prerequisites
- Creating the Project Structure
- Connecting to the Platform
- Creating a Service
- Sending Requests to Another Service
- Posting Events when a Job is Submitted and Completed
- Subscribing to Service Events
- Sending REST Requests to Your Service
- Delivering the Service as an RPM On CentOS 6.x/RedHat 6.x
- Daemonizing the Service on CentOS 6.x/RedHat 6.x
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
Change directory:
1
> cd linux
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
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
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;
}If you are connecting to a remote host (using vagrant) you need to set
two environment variables.1
2ACS_GATEWAY_PORT=9500
ACS_GATEWAY_HOST=avid-platform-zone1
Depending on your platform you should build and run the above code:
Linux
Build the project:
1
> make
Run the application:
1
./example
You should see the following output:
1
Connected to the Bus!
Creating a Service
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
36
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];
};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
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}"}
};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
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;
}Now rebuild your project and run it. You should see the following output:
1
2Connected to the Bus!
Starting the ExampleService!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.
- Open the ACS Monitor, and scroll down to locate the
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.
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
63class 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() {
BOOST_SP_INTERLOCKED_INCREMENT(&m_referenceCount);
++m_referenceCount;
return (unsigned)m_referenceCount;
}
virtual unsigned Release() {
BOOST_INTERLOCKED_DECREMENT(&m_referenceCount);
--m_referenceCount;
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;
};Update ExampleClient.cpp to include the following after subscribing to channels, including an implementation for AsyncQueryResultImpl class:
1
2
3
4
5
6
7
8acs::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());Rebuild and start your example service. You should see the following output:
1
2
3Connected to the Bus!
querying calc
The calculator responded with the result 6You 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.
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);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;
}
};Rebuild the project to verify it builds without error.
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.
Set up ExampleClient:
- Linux
- Create a file called ExampleService.cpp.
- Linux
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
96// ExampleClient.cpp : Defines the entry point for the console application.
//
/**
* 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;
}Setup the project to build both Service and Client:
- Linux
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
67INCLUDEDIRS = \
-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 clientThe following now applies:
1
2
3
4
5
6# Build service
> make service
# Build Client
> make client
# Build both
> makeIn two different terminals start service and client:
1
2./cpp-service
./client
- Linux
Open the ACS Monitor.
Locate the
cpp.tutorial.service
and sendsubmitJob
a few times with different values
for thejobId
.Go back to the terminal running the client. You should see the following output for each
submitted job:1
2
3The 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
Log in to
avid-platform-zone1
with ssh (username: root; password: vagrant):1
ssh root@avid-platform-zone1
Send POST request to your service with
curl
command, use avidAccessToken generated before:1
2
3curl -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
}
}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.
Create
cpp-service.spec
file:1
touch cpp-service.spec
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
45%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}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
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
# 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"Build your project:
1
./create-rpm.sh cpp-service.spec
Make
create-rpm.sh
executable and create RPM:1
2chmod 755 create-rpm.sh
./create-rpm.sh cpp-service.specInstall your RPM:
1
yum install rpmbuild/RPMS/x86_64/cpp-service-1-1.x86_64.rpm
Run installed service:
1
/opt/avid/sbin/cpp-service
Test how service is working in the ACS Monitor
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
.
Create
etc
,etc/init.d
,etc/syconfig
andetc/logrotate.d
directories:1
2
3
4mkdir etc
mkdir etc/init.d
mkdir etc/sysconfig
mkdir etc/logrotate.dCreate a
cpp-service
file in each of theetc/init.d
,etc/syconfig
andetc/logrotate.d
directories:1
2
3touch etc/init.d/cpp-service
touch etc/sysconfig/cpp-service
touch etc/logrotate.d/cpp-serviceAdd 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=trueAdd 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
}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
#
# 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 "$@"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}Build your project:
1
make
Log in to
avid-platform-zone1
with ssh (username: root; password: vagrant):1
ssh root@avid-platform-zone1
In the
ssh
shell change your current directory:1
cd /root/cpp-service
Create the RPM:
1
./create-rpm.sh cpp-service.spec
Install your RPM:
1
yum install rpmbuild/RPMS/x86_64/cpp-service-2-1.x86_64.rpm
Run the installed service:
1
service cpp-service start
Test that the service is working using the ACS Monitor as previously outlined.
Monitor service logs:
1
2
3service cpp-service logs
#or
tail -f /var/log/avid/cpp-service/cpp-service.log
Congratulations! You have successfully completed the tutorial.