Server.cpp 17.8 KB
Newer Older
legoc's avatar
legoc committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 * Copyright 2015 Institut Laue-Langevin
 *
 * Licensed under the EUPL, Version 1.1 only (the "License");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://joinup.ec.europa.eu/software/page/eupl
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 */

17
18
#include "Server.h"
#include "Application.h"
19
#include "ConnectionChecker.h"
20
#include "impl/ServicesImpl.h"
21
#include "EventThread.h"
22
#include "impl/StreamSocketImpl.h"
legoc's avatar
legoc committed
23
#include "impl/RequestSocketImpl.h"
24
#include "message/Message.h"
25
26
#include "UndefinedApplicationException.h"
#include "UndefinedKeyException.h"
27
28
#include <iostream>
#include <sstream>
29
#include "JSON.h"
legoc's avatar
legoc committed
30
31
32
33
34

using namespace std;

namespace cameo {

legoc's avatar
legoc committed
35
Server::Server(const std::string& endpoint, int timeoutMs) :
36
	Services() {
legoc's avatar
legoc committed
37

38
	Services::init();
legoc's avatar
legoc committed
39
40
41
42

	vector<string> tokens = split(endpoint);

	if (tokens.size() < 3) {
43
		throw InvalidArgumentException(endpoint + " is not a valid endpoint");
legoc's avatar
legoc committed
44
45
46
47
48
49
50
	}

	m_url = tokens[0] + ":" + tokens[1];
	string port = tokens[2];
	istringstream is(port);
	is >> m_port;
	m_serverEndpoint = m_url + ":" + port;
51

legoc's avatar
legoc committed
52
53
54
	// Set the timeout.
	Services::setTimeout(timeoutMs);

55
56
57
	// Create the request socket. The server endpoint has been defined.
	Services::initRequestSocket();

58
59
60
	// Retrieve the server version.
	Services::retrieveServerVersion();

legoc's avatar
legoc committed
61
62
63
64
65
66
67
68
69
70
	// Manage the ConnectionTimeout exception that can occur.
	try {
		// Start the event thread.
		unique_ptr<EventStreamSocket> socket = openEventStream();
		m_eventThread.reset(new EventThread(this, socket));
		m_eventThread->start();
	}
	catch (...) {
		// ...
	}
legoc's avatar
legoc committed
71
72
73
}

Server::~Server() {
74
75
76
77
	// Stop the event thread.
	if (m_eventThread.get() != nullptr) {
		m_eventThread->cancel();
	}
legoc's avatar
legoc committed
78
79
}

legoc's avatar
legoc committed
80
void Server::setTimeout(int timeoutMs) {
81
	Services::setTimeout(timeoutMs);
legoc's avatar
legoc committed
82
83
84
85
86
87
88
89
90
91
92
93
94
95
}

int Server::getTimeout() const {
	return Services::getTimeout();
}

const std::string& Server::getEndpoint() const {
	return Services::getEndpoint();
}

const std::string& Server::getUrl() const {
	return Services::getUrl();
}

legoc's avatar
legoc committed
96
97
98
99
std::array<int, 3> Server::getVersion() const {
	return Services::getVersion();
}

legoc's avatar
legoc committed
100
101
102
103
104
105
106
107
int Server::getPort() const {
	return Services::getPort();
}

bool Server::isAvailable(int timeout) const {
	return Services::isAvailable(timeout);
}

legoc's avatar
legoc committed
108
bool Server::isAvailable() const {
109
110
	return isAvailable(getAvailableTimeout());
}
legoc's avatar
legoc committed
111

112
int Server::getAvailableTimeout() const {
legoc's avatar
legoc committed
113
114
	int timeout = getTimeout();
	if (timeout > 0) {
115
116
117
118
		return timeout;
	}
	else {
		return 10000;
legoc's avatar
legoc committed
119
120
121
	}
}

legoc's avatar
legoc committed
122
std::unique_ptr<application::Instance> Server::makeInstance() {
123
	return unique_ptr<application::Instance>(new application::Instance(this));
legoc's avatar
legoc committed
124
125
}

legoc's avatar
legoc committed
126
std::unique_ptr<application::Instance> Server::start(const std::string& name, Option options) {
legoc's avatar
legoc committed
127
128
129
	return start(name, vector<string>(), options);
}

legoc's avatar
legoc committed
130
131
int Server::getStreamPort(const std::string& name) {

132
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createOutputPortRequest(name));
legoc's avatar
legoc committed
133

134
135
136
	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
legoc's avatar
legoc committed
137

138
	return response[message::RequestResponse::VALUE].GetInt();
legoc's avatar
legoc committed
139
140
}

legoc's avatar
legoc committed
141
std::unique_ptr<application::Instance> Server::start(const std::string& name, const std::vector<std::string> & args, Option options) {
legoc's avatar
legoc committed
142

legoc's avatar
legoc committed
143
144
	bool outputStream = ((options & OUTPUTSTREAM) != 0);

legoc's avatar
legoc committed
145
	unique_ptr<application::Instance> instance = makeInstance();
legoc's avatar
legoc committed
146

147
	// Set the name and register the instance as event listener.
legoc's avatar
legoc committed
148
	instance->setName(name);
149
	registerEventListener(instance.get());
legoc's avatar
legoc committed
150
151

	try {
152
153
		unique_ptr<OutputStreamSocket> streamSocket;

legoc's avatar
legoc committed
154
		if (outputStream) {
155
156
157
			// We connect to the stream port before starting the application.
			// However that does NOT guarantee that the stream will be connected before the ENDSTREAM arrives in case of an application that terminates rapidly.
			streamSocket = createOutputStreamSocket(getStreamPort(name));
legoc's avatar
legoc committed
158
159
		}

160
		unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createStartRequest(name, args, application::This::getReference()));
legoc's avatar
legoc committed
161

162
163
164
		// Get the JSON response.
		json::Object response;
		json::parse(response, reply.get());
legoc's avatar
legoc committed
165

166
167
168
		int value = response[message::RequestResponse::VALUE].GetInt();
		if (value == -1) {
			instance->setErrorMessage(response[message::RequestResponse::MESSAGE].GetString());
legoc's avatar
legoc committed
169
		}
170
171
		else {
			instance->setId(value);
172
173
174
175

			if (outputStream) {
				instance->setOutputStreamSocket(streamSocket);
			}
176
177
178
		}
	}
	catch (const ConnectionTimeout& e) {
legoc's avatar
legoc committed
179
180
181
182
183
184
185
186
		instance->setErrorMessage(e.what());
	}

	return instance;
}

Response Server::stopApplicationAsynchronously(int id, bool immediately) const {

187
	string request;
legoc's avatar
legoc committed
188
189

	if (immediately) {
190
191
192
193
		request = m_impl->createKillRequest(id);
	}
	else {
		request = m_impl->createStopRequest(id);
legoc's avatar
legoc committed
194
195
	}

196
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(request);
legoc's avatar
legoc committed
197

198
199
200
	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
legoc's avatar
legoc committed
201

202
203
204
205
	int value = response[message::RequestResponse::VALUE].GetInt();
	string message = response[message::RequestResponse::MESSAGE].GetString();

	return Response(value, message);
legoc's avatar
legoc committed
206
207
}

legoc's avatar
legoc committed
208
209
210
application::InstanceArray Server::connectAll(const std::string& name, Option options) {

	bool outputStream = ((options & OUTPUTSTREAM) != 0);
legoc's avatar
legoc committed
211

212
213
214
215
216
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createConnectRequest(name));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
legoc's avatar
legoc committed
217

218
219
	application::InstanceArray instances;

220
221
222
	json::Value& applicationInfo = response[message::ApplicationInfoListResponse::APPLICATION_INFO];
	json::Value::Array array = applicationInfo.GetArray();
	size_t size = array.Size();
legoc's avatar
legoc committed
223

224
225
	// Allocate the array.
	instances.allocate(size);
legoc's avatar
legoc committed
226
227
228

	int aliveInstancesCount = 0;

229
230
	for (int i = 0; i < size; ++i) {
		json::Value::Object info = array[i].GetObject();
legoc's avatar
legoc committed
231

legoc's avatar
legoc committed
232
		unique_ptr<application::Instance> instance = makeInstance();
legoc's avatar
legoc committed
233

234
		// Set the name and register the instance as event listener.
235
236
		string name = info[message::ApplicationInfo::NAME].GetString();
		instance->setName(name);
237
238
		registerEventListener(instance.get());

239
		int applicationId = info[message::ApplicationInfo::ID].GetInt();
legoc's avatar
legoc committed
240
241
242
243
244

		// test if the application is still alive otherwise we could have missed a status message
		if (isAlive(applicationId)) {
			aliveInstancesCount++;

245
246
			// We connect to the stream port before starting the application.
			// However that does NOT guarantee that the stream will be connected before the ENDSTREAM arrives in case of an application that terminates rapidly.
legoc's avatar
legoc committed
247
			instance->setId(applicationId);
248
249
			instance->setInitialState(info[message::ApplicationInfo::APPLICATION_STATE].GetInt());
			instance->setPastStates(info[message::ApplicationInfo::PAST_APPLICATION_STATES].GetInt());
legoc's avatar
legoc committed
250

251
252
253
254
255
			if (outputStream) {
				unique_ptr<OutputStreamSocket> streamSocket = createOutputStreamSocket(getStreamPort(name));
				instance->setOutputStreamSocket(streamSocket);
			}

legoc's avatar
legoc committed
256
			instances.m_array[i] = std::move(instance);
legoc's avatar
legoc committed
257
258
259
		}
	}

260
	// Copy the alive instances.
legoc's avatar
legoc committed
261
262
263
264
	application::InstanceArray aliveInstances;
	aliveInstances.allocate(aliveInstancesCount);

	int j = 0;
265
	for (int i = 0; i < size; ++i) {
legoc's avatar
legoc committed
266
267

		if (instances.m_array[i].get() != 0) {
legoc's avatar
legoc committed
268
			aliveInstances[j] = std::move(instances.m_array[i]);
legoc's avatar
legoc committed
269
270
271
272
273
274
275
			j++;
		}
	}

	return aliveInstances;
}

legoc's avatar
legoc committed
276
std::unique_ptr<application::Instance> Server::connect(const std::string& name, Option options) {
legoc's avatar
legoc committed
277

legoc's avatar
legoc committed
278
	application::InstanceArray instances = connectAll(name, options);
legoc's avatar
legoc committed
279
280

	if (instances.size() == 0) {
legoc's avatar
legoc committed
281
		unique_ptr<application::Instance> instance = makeInstance();
legoc's avatar
legoc committed
282
283
284
		return instance;
	}

legoc's avatar
legoc committed
285
	return std::move(instances[0]);
legoc's avatar
legoc committed
286
287
}

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
std::unique_ptr<application::Instance> Server::connect(int id, Option options) {

	bool outputStream = ((options & OUTPUTSTREAM) != 0);

	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createConnectWithIdRequest(id));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());

	json::Value& applicationInfo = response[message::ApplicationInfoListResponse::APPLICATION_INFO];
	json::Value::Array array = applicationInfo.GetArray();
	size_t size = array.Size();

	if (size > 0) {
		json::Value::Object info = array[0].GetObject();

		unique_ptr<application::Instance> instance = makeInstance();

		// Set the name and register the instance as event listener.
		string name = info[message::ApplicationInfo::NAME].GetString();
		instance->setName(name);
		registerEventListener(instance.get());

		int applicationId = info[message::ApplicationInfo::ID].GetInt();

		// test if the application is still alive otherwise we could have missed a status message
		if (isAlive(applicationId)) {

			// We connect to the stream port before starting the application.
			// However that does NOT guarantee that the stream will be connected before the ENDSTREAM arrives in case of an application that terminates rapidly.
			instance->setId(applicationId);
			instance->setInitialState(info[message::ApplicationInfo::APPLICATION_STATE].GetInt());
			instance->setPastStates(info[message::ApplicationInfo::PAST_APPLICATION_STATES].GetInt());

			if (outputStream) {
				unique_ptr<OutputStreamSocket> streamSocket = createOutputStreamSocket(getStreamPort(name));
				instance->setOutputStreamSocket(streamSocket);
			}

			return instance;
		}
	}

	return makeInstance();
}

legoc's avatar
legoc committed
335
336
337
338
339
340
341
342
343
344
345
346
void Server::killAllAndWaitFor(const std::string& name) {

	application::InstanceArray instances = connectAll(name);

	for (int i = 0; i < instances.size(); ++i) {
		instances[i]->kill();
		instances[i]->waitFor();
	}
}

bool Server::isAlive(int id) const {

347
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createIsAliveRequest(id));
legoc's avatar
legoc committed
348

349
350
351
	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
legoc's avatar
legoc committed
352

353
	return response[message::IsAliveResponse::IS_ALIVE].GetBool();
legoc's avatar
legoc committed
354
355
356
357
}

std::vector<application::Configuration> Server::getApplicationConfigurations() const {

358
359
	vector<application::Configuration> configs;

360
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createListRequest());
legoc's avatar
legoc committed
361

362
363
364
	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
legoc's avatar
legoc committed
365

legoc's avatar
legoc committed
366
	json::Value& applicationConfigs = response[message::ListResponse::APPLICATION_CONFIG];
367
368
	json::Value::Array array = applicationConfigs.GetArray();
	size_t size = array.Size();
legoc's avatar
legoc committed
369

370
371
	for (int i = 0; i < size; ++i) {
		json::Value::Object config = array[i].GetObject();
legoc's avatar
legoc committed
372

373
374
375
376
377
378
		string name = config[message::ApplicationConfig::NAME].GetString();
		string description = config[message::ApplicationConfig::DESCRIPTION].GetString();
		bool runsSingle = config[message::ApplicationConfig::RUNS_SINGLE].GetBool();
		bool restart = config[message::ApplicationConfig::RESTART].GetBool();
		int startingTime = config[message::ApplicationConfig::STARTING_TIME].GetInt();
		int stoppingTime = config[message::ApplicationConfig::STOPPING_TIME].GetInt();
legoc's avatar
legoc committed
379

380
381
382
383
384
385
386
387
		application::Configuration applicationConfig(name,
				description,
				runsSingle,
				restart,
				startingTime,
				stoppingTime);

		configs.push_back(applicationConfig);
legoc's avatar
legoc committed
388
389
	}

390
	return configs;
legoc's avatar
legoc committed
391
392
393
394
}

std::vector<application::Info> Server::getApplicationInfos() const {

legoc's avatar
legoc committed
395
	vector<application::Info> infos;
legoc's avatar
legoc committed
396

legoc's avatar
legoc committed
397
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createAppsRequest());
398
399
400
401
402
403
404
405

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());

	json::Value& applicationInfo = response[message::ApplicationInfoListResponse::APPLICATION_INFO];
	json::Value::Array array = applicationInfo.GetArray();
	size_t size = array.Size();
legoc's avatar
legoc committed
406

407
408
	for (int i = 0; i < size; ++i) {
		json::Value::Object info = array[i].GetObject();
legoc's avatar
legoc committed
409

410
411
412
413
414
415
		string name = info[message::ApplicationInfo::NAME].GetString();
		int id = info[message::ApplicationInfo::ID].GetInt();
		int pid = info[message::ApplicationInfo::PID].GetInt();
		application::State state = info[message::ApplicationInfo::APPLICATION_STATE].GetInt();
		application::State pastStates = info[message::ApplicationInfo::PAST_APPLICATION_STATES].GetInt();
		string args = info[message::ApplicationInfo::ARGS].GetString();
legoc's avatar
legoc committed
416

417
418
419
420
421
422
		application::Info applicationInfo(name,
						id,
						pid,
						state,
						pastStates,
						args);
legoc's avatar
legoc committed
423

legoc's avatar
legoc committed
424
		infos.push_back(applicationInfo);
legoc's avatar
legoc committed
425
426
	}

legoc's avatar
legoc committed
427
	return infos;
legoc's avatar
legoc committed
428
429
430
431
}

std::vector<application::Info> Server::getApplicationInfos(const std::string& name) const {

432
433
	vector<application::Info> allInfos = getApplicationInfos();
	vector<application::Info> infos;
legoc's avatar
legoc committed
434

435
	for (vector<application::Info>::const_iterator i = allInfos.begin(); i != allInfos.end(); ++i) {
legoc's avatar
legoc committed
436
437
		application::Info const & info = *i;
		if (info.getName() == name) {
438
			infos.push_back(info);
legoc's avatar
legoc committed
439
440
441
		}
	}

442
	return infos;
legoc's avatar
legoc committed
443
444
}

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
application::State Server::getActualState(int id) const {

	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createGetStatusRequest(id));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());

	return response[message::StatusEvent::APPLICATION_STATE].GetInt();
}

std::set<application::State> Server::getPastStates(int id) const {

	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createGetStatusRequest(id));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());

	application::State applicationStates = response[message::StatusEvent::PAST_APPLICATION_STATES].GetInt();

	set<application::State> result;

	if ((applicationStates & application::STARTING) != 0) {
		result.insert(application::STARTING);
	}

	if ((applicationStates & application::RUNNING) != 0) {
		result.insert(application::RUNNING);
	}

	if ((applicationStates & application::STOPPING) != 0) {
		result.insert(application::STOPPING);
	}

	if ((applicationStates & application::KILLING) != 0) {
		result.insert(application::KILLING);
	}

	if ((applicationStates & application::PROCESSING_ERROR) != 0) {
		result.insert(application::PROCESSING_ERROR);
	}

	if ((applicationStates & application::FAILURE) != 0) {
		result.insert(application::FAILURE);
	}

	if ((applicationStates & application::SUCCESS) != 0) {
		result.insert(application::SUCCESS);
	}

	if ((applicationStates & application::STOPPED) != 0) {
		result.insert(application::STOPPED);
	}

	if ((applicationStates & application::KILLED) != 0) {
		result.insert(application::KILLED);
	}

	return result;
}

legoc's avatar
legoc committed
507
std::unique_ptr<EventStreamSocket> Server::openEventStream() {
legoc's avatar
legoc committed
508
509
510
	return Services::openEventStream();
}

511
std::unique_ptr<application::Subscriber> Server::createSubscriber(int id, const std::string& publisherName, const std::string& instanceName) {
legoc's avatar
legoc committed
512

513
	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createConnectPublisherRequest(id, publisherName));
legoc's avatar
legoc committed
514

515
516
517
	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
legoc's avatar
legoc committed
518

519
	int publisherPort = response[message::PublisherResponse::PUBLISHER_PORT].GetInt();
legoc's avatar
legoc committed
520
	if (publisherPort == -1) {
521
		throw SubscriberCreationException(response[message::PublisherResponse::MESSAGE].GetString());
legoc's avatar
legoc committed
522
523
	}

524
525
	int synchronizerPort = response[message::PublisherResponse::SYNCHRONIZER_PORT].GetInt();
	int numberOfSubscribers = response[message::PublisherResponse::NUMBER_OF_SUBSCRIBERS].GetInt();
legoc's avatar
legoc committed
526

legoc's avatar
legoc committed
527
	unique_ptr<application::Subscriber> subscriber(new application::Subscriber(this, getUrl(), publisherPort, synchronizerPort, publisherName, numberOfSubscribers, instanceName, id, m_serverEndpoint, m_serverStatusEndpoint));
legoc's avatar
legoc committed
528
529
530
531
532
	subscriber->init();

	return subscriber;
}

legoc's avatar
legoc committed
533
std::unique_ptr<ConnectionChecker> Server::createConnectionChecker(ConnectionCheckerType handler, int pollingTimeMs) {
534

legoc's avatar
legoc committed
535
	unique_ptr<ConnectionChecker> connectionChecker(new ConnectionChecker(this, handler));
536
	connectionChecker->startThread(getAvailableTimeout(), pollingTimeMs);
legoc's avatar
legoc committed
537

538
	return connectionChecker;
legoc's avatar
legoc committed
539
540
}

541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
void Server::storeKeyValue(int id, const std::string& key, const std::string& value) {

	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createStoreKeyValueRequest(id, key, value));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());
}

std::string Server::getKeyValue(int id, const std::string& key) {

	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createGetKeyValueRequest(id, key));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());

	int value = response[message::RequestResponse::VALUE].GetInt();
	if (value == 0) {
		return response[message::RequestResponse::MESSAGE].GetString();
	}
	else if (value == -1) {
		throw UndefinedApplicationException(response[message::RequestResponse::MESSAGE].GetString());
	}
	else if (value == -2) {
		throw UndefinedKeyException(response[message::RequestResponse::MESSAGE].GetString());
	}

	return "";
}

void Server::removeKey(int id, const std::string& key) {

	unique_ptr<zmq::message_t> reply = m_requestSocket->request(m_impl->createRemoveKeyRequest(id, key));

	// Get the JSON response.
	json::Object response;
	json::parse(response, reply.get());

	int value = response[message::RequestResponse::VALUE].GetInt();
	if (value == -1) {
		throw UndefinedApplicationException(response[message::RequestResponse::MESSAGE].GetString());
	}
	else if (value == -2) {
		throw UndefinedKeyException(response[message::RequestResponse::MESSAGE].GetString());
	}
}

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
std::vector<EventListener *> Server::getEventListeners() {
	std::unique_lock<std::mutex> lock(m_eventListenersMutex);
	return m_eventListeners;
}

void Server::registerEventListener(EventListener * listener) {
	std::unique_lock<std::mutex> lock(m_eventListenersMutex);
	m_eventListeners.push_back(listener);
}

void Server::unregisterEventListener(EventListener * listener) {
	std::unique_lock<std::mutex> lock(m_eventListenersMutex);

	// Iterate to find the listener.
	for (auto it = m_eventListeners.begin(); it != m_eventListeners.end(); ++it) {
		if (*it == listener) {
			m_eventListeners.erase(it);
			break;
		}
	}
}

legoc's avatar
legoc committed
611
612
613
614
615
616
617
618
std::ostream& operator<<(std::ostream& os, const cameo::Server& server) {

	os << "server@" << server.m_serverEndpoint;

	return os;
}

}