kopia lustrzana https://github.com/ukhas/habitat-cpp-connector
Use the update function 'add_listener'
rodzic
8dacdc32cd
commit
cc2408285d
|
@ -36,6 +36,10 @@ public:
|
||||||
Json::Value *operator[](const string &doc_id);
|
Json::Value *operator[](const string &doc_id);
|
||||||
Json::Value *view(const string &design_doc, const string &view_name,
|
Json::Value *view(const string &design_doc, const string &view_name,
|
||||||
const map<string,string> &options=view_default_options);
|
const map<string,string> &options=view_default_options);
|
||||||
|
string update_put(const string &design_doc, const string &update_name,
|
||||||
|
const string &doc_id, const Json::Value &payload);
|
||||||
|
string update_put(const string &design_doc, const string &update_name,
|
||||||
|
const string &doc_id, const string &payload);
|
||||||
static string json_query_value(Json::Value &value);
|
static string json_query_value(Json::Value &value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,6 @@ public:
|
||||||
UnmergeableError(const string &what) : runtime_error(what) {};
|
UnmergeableError(const string &what) : runtime_error(what) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class CollisionError : public runtime_error
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CollisionError() : runtime_error("habitat::CollisionError") {};
|
|
||||||
CollisionError(const string &what) : runtime_error(what) {};
|
|
||||||
};
|
|
||||||
|
|
||||||
class Uploader
|
class Uploader
|
||||||
{
|
{
|
||||||
EZ::Mutex mutex;
|
EZ::Mutex mutex;
|
||||||
|
|
|
@ -189,6 +189,49 @@ Json::Value *Database::view(const string &design_doc, const string &view_name,
|
||||||
return server.get_json(view_url);
|
return server.get_json(view_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Database::update_put(const string &design_doc,
|
||||||
|
const string &update_name,
|
||||||
|
const string &doc_id,
|
||||||
|
const Json::Value &payload)
|
||||||
|
{
|
||||||
|
Json::FastWriter writer;
|
||||||
|
string json_payload = writer.write(payload);
|
||||||
|
return update_put(design_doc, update_name, doc_id, json_payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
string Database::update_put(const string &design_doc,
|
||||||
|
const string &update_name,
|
||||||
|
const string &doc_id,
|
||||||
|
const string &payload)
|
||||||
|
{
|
||||||
|
string update_url(url);
|
||||||
|
|
||||||
|
update_url.append("_design/");
|
||||||
|
update_url.append(EZ::cURL::escape(design_doc));
|
||||||
|
update_url.append("/_update/");
|
||||||
|
update_url.append(EZ::cURL::escape(update_name));
|
||||||
|
|
||||||
|
if (doc_id.size())
|
||||||
|
{
|
||||||
|
update_url.append("/");
|
||||||
|
update_url.append(doc_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return server.curl.put(update_url, payload);
|
||||||
|
}
|
||||||
|
catch (EZ::HTTPResponse &e)
|
||||||
|
{
|
||||||
|
/* Catch HTTP 409 Resource Conflict */
|
||||||
|
|
||||||
|
if (e.response_code != 409)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
throw Conflict(doc_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string Database::json_query_value(Json::Value &value)
|
string Database::json_query_value(Json::Value &value)
|
||||||
{
|
{
|
||||||
Json::FastWriter writer;
|
Json::FastWriter writer;
|
||||||
|
|
|
@ -97,36 +97,6 @@ static void set_time(Json::Value &thing, long long int time_created)
|
||||||
RFC3339::timestamp_to_rfc3339_localoffset(time_created);
|
RFC3339::timestamp_to_rfc3339_localoffset(time_created);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void payload_telemetry_new(Json::Value &doc,
|
|
||||||
const string &data_b64,
|
|
||||||
const string &callsign,
|
|
||||||
Json::Value &receiver_info)
|
|
||||||
{
|
|
||||||
doc["data"] = Json::Value(Json::objectValue);
|
|
||||||
doc["receivers"] = Json::Value(Json::objectValue);
|
|
||||||
doc["type"] = "payload_telemetry";
|
|
||||||
|
|
||||||
doc["data"]["_raw"] = data_b64;
|
|
||||||
doc["receivers"][callsign] = receiver_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void payload_telemetry_merge(Json::Value &doc,
|
|
||||||
const string &data_b64,
|
|
||||||
const string &callsign,
|
|
||||||
Json::Value &receiver_info)
|
|
||||||
{
|
|
||||||
if (!doc.isObject() || !doc["data"].isObject() ||
|
|
||||||
!doc["receivers"].isObject())
|
|
||||||
throw runtime_error("Server gave us an invalid payload telemetry doc");
|
|
||||||
|
|
||||||
string other_b64 = doc["data"]["_raw"].asString();
|
|
||||||
|
|
||||||
if (!other_b64.length() || other_b64 != data_b64)
|
|
||||||
throw CollisionError();
|
|
||||||
|
|
||||||
doc["receivers"][callsign] = receiver_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
string Uploader::payload_telemetry(const string &data,
|
string Uploader::payload_telemetry(const string &data,
|
||||||
const Json::Value &metadata,
|
const Json::Value &metadata,
|
||||||
long long int time_created)
|
long long int time_created)
|
||||||
|
@ -142,7 +112,13 @@ string Uploader::payload_telemetry(const string &data,
|
||||||
if (time_created == -1)
|
if (time_created == -1)
|
||||||
time_created = time(NULL);
|
time_created = time(NULL);
|
||||||
|
|
||||||
Json::Value receiver_info;
|
Json::Value doc;
|
||||||
|
doc["data"] = Json::Value(Json::objectValue);
|
||||||
|
doc["data"]["_raw"] = data_b64;
|
||||||
|
doc["receivers"] = Json::Value(Json::objectValue);
|
||||||
|
doc["receivers"][callsign] = Json::Value(Json::objectValue);
|
||||||
|
|
||||||
|
Json::Value &receiver_info = doc["receivers"][callsign];
|
||||||
|
|
||||||
if (metadata.isObject())
|
if (metadata.isObject())
|
||||||
{
|
{
|
||||||
|
@ -169,39 +145,29 @@ string Uploader::payload_telemetry(const string &data,
|
||||||
if (latest_listener_telemetry.length())
|
if (latest_listener_telemetry.length())
|
||||||
receiver_info["latest_listener_telemetry"] = latest_listener_telemetry;
|
receiver_info["latest_listener_telemetry"] = latest_listener_telemetry;
|
||||||
|
|
||||||
try
|
for (int attempts = 0; attempts < max_merge_attempts; attempts++)
|
||||||
{
|
{
|
||||||
Json::Value doc(Json::objectValue);
|
try
|
||||||
set_time(receiver_info, time_created);
|
|
||||||
payload_telemetry_new(doc, data_b64, callsign, receiver_info);
|
|
||||||
doc["_id"] = doc_id;
|
|
||||||
database.save_doc(doc);
|
|
||||||
return doc_id;
|
|
||||||
}
|
|
||||||
catch (CouchDB::Conflict &e)
|
|
||||||
{
|
|
||||||
for (int attempts = 0; attempts < max_merge_attempts; attempts++)
|
|
||||||
{
|
{
|
||||||
try
|
set_time(receiver_info, time_created);
|
||||||
{
|
database.update_put("payload_telemetry", "add_listener", doc_id,
|
||||||
Json::Value *doc = database[doc_id];
|
doc);
|
||||||
auto_ptr<Json::Value> doc_destroyer(doc);
|
return doc_id;
|
||||||
|
}
|
||||||
set_time(receiver_info, time_created);
|
catch (CouchDB::Conflict &e)
|
||||||
payload_telemetry_merge(*doc, data_b64, callsign,
|
{
|
||||||
receiver_info);
|
continue;
|
||||||
database.save_doc(*doc);
|
}
|
||||||
|
catch (EZ::HTTPResponse &e)
|
||||||
return doc_id;
|
{
|
||||||
}
|
if (e.response_code == 403 || e.response_code == 401)
|
||||||
catch (CouchDB::Conflict &e)
|
break; // Unmergeable
|
||||||
{
|
else
|
||||||
continue;
|
throw;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw UnmergeableError();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw UnmergeableError();
|
||||||
}
|
}
|
||||||
|
|
||||||
string Uploader::listener_doc(const char *type, const Json::Value &data,
|
string Uploader::listener_doc(const char *type, const Json::Value &data,
|
||||||
|
|
|
@ -82,16 +82,16 @@ class Proxy:
|
||||||
self._proxy(init_args)
|
self._proxy(init_args)
|
||||||
|
|
||||||
def _write(self, command):
|
def _write(self, command):
|
||||||
print ">>", repr(command)
|
s = json.dumps(command)
|
||||||
self.p.stdin.write(json.dumps(command))
|
print ">>", s
|
||||||
|
self.p.stdin.write(s)
|
||||||
self.p.stdin.write("\n")
|
self.p.stdin.write("\n")
|
||||||
|
|
||||||
def _read(self):
|
def _read(self):
|
||||||
line = self.p.stdout.readline()
|
line = self.p.stdout.readline()
|
||||||
assert line and line.endswith("\n")
|
assert line and line.endswith("\n")
|
||||||
|
print "<<", line.strip()
|
||||||
obj = json.loads(line)
|
obj = json.loads(line)
|
||||||
|
|
||||||
print "<<", repr(obj)
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def _proxy(self, command):
|
def _proxy(self, command):
|
||||||
|
@ -380,6 +380,21 @@ class TestCPPConnector:
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def expect_add_listener_update(self, doc_id, protodoc, **kwargs):
|
||||||
|
if "code" not in kwargs:
|
||||||
|
kwargs["code"] = 200
|
||||||
|
if "respond" not in kwargs and "respond_json" not in kwargs:
|
||||||
|
kwargs["respond"] = "OK"
|
||||||
|
|
||||||
|
self.couchdb.expect_request(
|
||||||
|
method="PUT",
|
||||||
|
path=self.db_path + "_design/payload_telemetry/" +
|
||||||
|
"_update/add_listener/" + doc_id,
|
||||||
|
body_json=protodoc,
|
||||||
|
validate_body_json=False,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def test_uses_server_uuids(self):
|
def test_uses_server_uuids(self):
|
||||||
should_use_uuids = []
|
should_use_uuids = []
|
||||||
|
|
||||||
|
@ -501,12 +516,10 @@ class TestCPPConnector:
|
||||||
"8f83926278b23f5b813bdc75f6b9cdd6"
|
"8f83926278b23f5b813bdc75f6b9cdd6"
|
||||||
ptlm_string = "asdf blah \x12 binar\x04\x01 asdfasdfsz"
|
ptlm_string = "asdf blah \x12 binar\x04\x01 asdfasdfsz"
|
||||||
ptlm_metadata = {"frequency": 434075000, "misc": "Hi"}
|
ptlm_metadata = {"frequency": 434075000, "misc": "Hi"}
|
||||||
ptlm_doc = {
|
ptlm_doc_ish = {
|
||||||
"_id": ptlm_doc_id,
|
|
||||||
"data": {
|
"data": {
|
||||||
"_raw": "YXNkZiBibGFoIBIgYmluYXIEASBhc2RmYXNkZnN6"
|
"_raw": "YXNkZiBibGFoIBIgYmluYXIEASBhc2RmYXNkZnN6"
|
||||||
},
|
},
|
||||||
"type": "payload_telemetry",
|
|
||||||
"receivers": {
|
"receivers": {
|
||||||
"PROXYCALL": {
|
"PROXYCALL": {
|
||||||
"frequency": 434075000,
|
"frequency": 434075000,
|
||||||
|
@ -515,7 +528,19 @@ class TestCPPConnector:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_payload_telemetry(self):
|
def make_ptlm_doc_ish(self, time_created=0, time_uploaded=0, **extra):
|
||||||
|
receiver_info = {
|
||||||
|
"time_created": self.callbacks.fake_rfc3339(time_created),
|
||||||
|
"time_uploaded": self.callbacks.fake_rfc3339(time_uploaded),
|
||||||
|
}
|
||||||
|
receiver_info.update(extra)
|
||||||
|
|
||||||
|
doc_ish = copy.deepcopy(self.ptlm_doc_ish)
|
||||||
|
doc_ish["receivers"]["PROXYCALL"].update(receiver_info)
|
||||||
|
|
||||||
|
return doc_ish
|
||||||
|
|
||||||
|
def test_payload_telemetry_simple(self):
|
||||||
# WARNING: JsonCPP does not support strings with \0 in the middle of
|
# WARNING: JsonCPP does not support strings with \0 in the middle of
|
||||||
# them, because it does not store the length of the string and instead
|
# them, because it does not store the length of the string and instead
|
||||||
# later figures it out with strlen. This does not harm the uploader
|
# later figures it out with strlen. This does not harm the uploader
|
||||||
|
@ -523,16 +548,11 @@ class TestCPPConnector:
|
||||||
# to the json encoder. However, the json stdin proxy call interface
|
# to the json encoder. However, the json stdin proxy call interface
|
||||||
# isn't going to work with nulls in it.
|
# isn't going to work with nulls in it.
|
||||||
|
|
||||||
receiver_info = {
|
doc_ish = self.make_ptlm_doc_ish()
|
||||||
"time_created": self.callbacks.fake_rfc3339(0),
|
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = copy.deepcopy(self.ptlm_doc)
|
self.expect_add_listener_update(self.ptlm_doc_id, doc_ish)
|
||||||
doc["receivers"]["PROXYCALL"].update(receiver_info)
|
|
||||||
|
|
||||||
self.expect_save_doc(doc)
|
|
||||||
self.couchdb.run()
|
self.couchdb.run()
|
||||||
|
|
||||||
ret_doc_id = self.uploader.payload_telemetry(self.ptlm_string,
|
ret_doc_id = self.uploader.payload_telemetry(self.ptlm_string,
|
||||||
self.ptlm_metadata)
|
self.ptlm_metadata)
|
||||||
self.couchdb.check()
|
self.couchdb.check()
|
||||||
|
@ -542,193 +562,50 @@ class TestCPPConnector:
|
||||||
def test_adds_latest_listener_doc(self):
|
def test_adds_latest_listener_doc(self):
|
||||||
self.add_sample_listener_docs()
|
self.add_sample_listener_docs()
|
||||||
|
|
||||||
receiver_info = {
|
doc_ish = self.make_ptlm_doc_ish(
|
||||||
"time_created": self.callbacks.fake_rfc3339(0),
|
latest_listener_telemetry=self.sample_telemetry_doc_id,
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
latest_listener_information=self.sample_info_doc_id
|
||||||
"latest_listener_telemetry": self.sample_telemetry_doc_id,
|
)
|
||||||
"latest_listener_information": self.sample_info_doc_id
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = copy.deepcopy(self.ptlm_doc)
|
self.expect_add_listener_update(self.ptlm_doc_id, doc_ish)
|
||||||
doc["receivers"]["PROXYCALL"].update(receiver_info)
|
|
||||||
|
|
||||||
self.expect_save_doc(doc)
|
|
||||||
self.couchdb.run()
|
self.couchdb.run()
|
||||||
self.uploader.payload_telemetry(self.ptlm_string, self.ptlm_metadata)
|
self.uploader.payload_telemetry(self.ptlm_string, self.ptlm_metadata)
|
||||||
self.couchdb.check()
|
self.couchdb.check()
|
||||||
|
|
||||||
ptlm_doc_existing = {
|
def test_ptlm_retries_conflicts(self):
|
||||||
"_id": ptlm_doc_id,
|
doc_ish = self.make_ptlm_doc_ish()
|
||||||
"data": {
|
|
||||||
"_raw": "YXNkZiBibGFoIBIgYmluYXIEASBhc2RmYXNkZnN6",
|
|
||||||
"some_parsed_data": 12345
|
|
||||||
},
|
|
||||||
"type": "payload_telemetry",
|
|
||||||
"receivers": {
|
|
||||||
"SOMEONEELSE": {
|
|
||||||
"time_created": "2011-03-13T07:10:00+00:00",
|
|
||||||
"time_uploaded": "2011-03-13T07:10:40+00:00",
|
|
||||||
"frequency": 434074000,
|
|
||||||
"asdf": "World"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_ptlm_merges_payload_conflicts(self):
|
self.expect_add_listener_update(
|
||||||
receiver_info = {
|
self.ptlm_doc_id, doc_ish,
|
||||||
"time_created": self.callbacks.fake_rfc3339(0),
|
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = copy.deepcopy(self.ptlm_doc)
|
|
||||||
doc["receivers"]["PROXYCALL"].update(receiver_info)
|
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
|
||||||
method="PUT",
|
|
||||||
path=self.db_path + doc["_id"],
|
|
||||||
body_json=doc,
|
|
||||||
code=409,
|
code=409,
|
||||||
respond_json={"error": "conflict"},
|
respond_json={"error": "conflict"},
|
||||||
advance_time_after=5
|
advance_time_after=5
|
||||||
)
|
)
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
doc_ish = copy.deepcopy(doc_ish)
|
||||||
path=self.db_path + self.ptlm_doc_id,
|
doc_ish["receivers"]["PROXYCALL"]["time_uploaded"] = \
|
||||||
code=200,
|
self.callbacks.fake_rfc3339(5)
|
||||||
respond_json=self.ptlm_doc_existing,
|
|
||||||
advance_time_after=5,
|
|
||||||
)
|
|
||||||
|
|
||||||
doc_merged = copy.deepcopy(self.ptlm_doc_existing)
|
self.expect_add_listener_update(self.ptlm_doc_id, doc_ish)
|
||||||
doc_merged["receivers"]["PROXYCALL"] = \
|
|
||||||
copy.deepcopy(self.ptlm_doc["receivers"]["PROXYCALL"])
|
|
||||||
receiver_info = copy.deepcopy(receiver_info)
|
|
||||||
receiver_info["time_uploaded"] = self.callbacks.fake_rfc3339(10)
|
|
||||||
doc_merged["receivers"]["PROXYCALL"].update(receiver_info)
|
|
||||||
|
|
||||||
self.expect_save_doc(doc_merged, validate_old=self.ptlm_doc_existing)
|
|
||||||
|
|
||||||
self.couchdb.run()
|
self.couchdb.run()
|
||||||
self.uploader.payload_telemetry(self.ptlm_string, self.ptlm_metadata)
|
self.uploader.payload_telemetry(self.ptlm_string, self.ptlm_metadata)
|
||||||
self.couchdb.check()
|
self.couchdb.check()
|
||||||
|
|
||||||
def test_ptlm_refuses_to_merge_collision(self):
|
def test_ptlm_doesnt_retry_other_errors(self):
|
||||||
receiver_info = {
|
yield self.check_ptlm_doesnt_retry_code, 401
|
||||||
"time_created": self.callbacks.fake_rfc3339(0),
|
yield self.check_ptlm_doesnt_retry_code, 403
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = copy.deepcopy(self.ptlm_doc)
|
def check_ptlm_doesnt_retry_code(self, code):
|
||||||
doc["receivers"]["PROXYCALL"].update(receiver_info)
|
doc_ish = self.make_ptlm_doc_ish()
|
||||||
del doc["receivers"]["PROXYCALL"]["frequency"]
|
|
||||||
del doc["receivers"]["PROXYCALL"]["misc"]
|
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
self.expect_add_listener_update(
|
||||||
method="PUT",
|
self.ptlm_doc_id, doc_ish,
|
||||||
path=self.db_path + doc["_id"],
|
code=code,
|
||||||
body_json=doc,
|
respond_json={"error": "of some sort"},
|
||||||
code=409,
|
|
||||||
respond_json={"error": "conflict"},
|
|
||||||
advance_time_after=5
|
advance_time_after=5
|
||||||
)
|
)
|
||||||
|
|
||||||
doc_conflict = copy.deepcopy(self.ptlm_doc_existing)
|
|
||||||
doc_conflict["data"]["_raw"] = "cGluZWFwcGxlcw=="
|
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
|
||||||
path=self.db_path + self.ptlm_doc_id,
|
|
||||||
code=200,
|
|
||||||
respond_json=doc_conflict
|
|
||||||
)
|
|
||||||
|
|
||||||
self.couchdb.run()
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.uploader.payload_telemetry(self.ptlm_string)
|
|
||||||
except ProxyException, e:
|
|
||||||
if e.name == "runtime_error" and \
|
|
||||||
e.what == "habitat::CollisionError":
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
raise AssertionError("Did not raise CollisionError")
|
|
||||||
|
|
||||||
self.couchdb.check()
|
|
||||||
|
|
||||||
def add_mock_conflicts(self, n):
|
|
||||||
receiver_info = {
|
|
||||||
"time_created": self.callbacks.fake_rfc3339(0),
|
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
doc = copy.deepcopy(self.ptlm_doc)
|
|
||||||
doc["receivers"]["PROXYCALL"].update(receiver_info)
|
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
|
||||||
method="PUT",
|
|
||||||
path=self.db_path + self.ptlm_doc_id,
|
|
||||||
body_json=doc,
|
|
||||||
code=409,
|
|
||||||
respond_json={"error": "conflict"}
|
|
||||||
)
|
|
||||||
|
|
||||||
doc_existing = copy.deepcopy(self.ptlm_doc_existing)
|
|
||||||
|
|
||||||
doc_merged = copy.deepcopy(self.ptlm_doc_existing)
|
|
||||||
doc_merged["receivers"]["PROXYCALL"] = \
|
|
||||||
copy.deepcopy(self.ptlm_doc["receivers"]["PROXYCALL"])
|
|
||||||
doc_merged["receivers"]["PROXYCALL"].update(receiver_info)
|
|
||||||
|
|
||||||
for i in xrange(n):
|
|
||||||
self.couchdb.expect_request(
|
|
||||||
path=self.db_path + self.ptlm_doc_id,
|
|
||||||
code=200,
|
|
||||||
respond_json=doc_existing,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
|
||||||
method="PUT",
|
|
||||||
path=self.db_path + self.ptlm_doc_id,
|
|
||||||
body_json=doc_merged,
|
|
||||||
validate_old=doc_existing,
|
|
||||||
code=409,
|
|
||||||
respond_json={"error": "conflict"},
|
|
||||||
advance_time_after=1
|
|
||||||
)
|
|
||||||
|
|
||||||
doc_existing = copy.deepcopy(doc_existing)
|
|
||||||
doc_merged = copy.deepcopy(doc_merged)
|
|
||||||
|
|
||||||
new_call = "listener_{0}".format(i)
|
|
||||||
new_info = {"time_created": self.callbacks.fake_rfc3339(i),
|
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(i + 1)}
|
|
||||||
|
|
||||||
doc_existing["receivers"][new_call] = new_info
|
|
||||||
doc_merged["receivers"][new_call] = new_info
|
|
||||||
|
|
||||||
doc_merged["receivers"]["PROXYCALL"]["time_uploaded"] = \
|
|
||||||
self.callbacks.fake_rfc3339(i + 1)
|
|
||||||
|
|
||||||
return (doc_existing, doc_merged)
|
|
||||||
|
|
||||||
def test_merges_multiple_conflicts(self):
|
|
||||||
(final_doc_existing, final_doc_merged) = self.add_mock_conflicts(15)
|
|
||||||
|
|
||||||
self.couchdb.expect_request(
|
|
||||||
path=self.db_path + self.ptlm_doc_id,
|
|
||||||
code=200,
|
|
||||||
respond_json=final_doc_existing,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.expect_save_doc(final_doc_merged, validate_old=final_doc_existing)
|
|
||||||
|
|
||||||
self.couchdb.run()
|
|
||||||
self.uploader.payload_telemetry(self.ptlm_string, self.ptlm_metadata)
|
|
||||||
self.couchdb.check()
|
|
||||||
|
|
||||||
def test_gives_up_after_many_conflicts(self):
|
|
||||||
self.add_mock_conflicts(20)
|
|
||||||
self.couchdb.run()
|
self.couchdb.run()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -743,6 +620,64 @@ class TestCPPConnector:
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Did not raise UnmergeableError")
|
raise AssertionError("Did not raise UnmergeableError")
|
||||||
|
|
||||||
|
self.couchdb.check()
|
||||||
|
|
||||||
|
def add_mock_conflicts(self, n):
|
||||||
|
doc_ish = self.make_ptlm_doc_ish()
|
||||||
|
|
||||||
|
self.expect_add_listener_update(
|
||||||
|
self.ptlm_doc_id, doc_ish,
|
||||||
|
code=409,
|
||||||
|
respond_json={"error": "conflict"},
|
||||||
|
advance_time_after=1
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in xrange(n):
|
||||||
|
doc_ish = copy.deepcopy(doc_ish)
|
||||||
|
doc_ish["receivers"]["PROXYCALL"]["time_uploaded"] = \
|
||||||
|
self.callbacks.fake_rfc3339(i + 1)
|
||||||
|
|
||||||
|
self.expect_add_listener_update(
|
||||||
|
self.ptlm_doc_id, doc_ish,
|
||||||
|
code=409,
|
||||||
|
respond_json={"error": "conflict"},
|
||||||
|
advance_time_after=1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_merges_multiple_conflicts(self):
|
||||||
|
self.add_mock_conflicts(15)
|
||||||
|
doc_ish = self.make_ptlm_doc_ish(time_created=0, time_uploaded=16)
|
||||||
|
self.expect_add_listener_update(self.ptlm_doc_id, doc_ish)
|
||||||
|
|
||||||
|
self.couchdb.run()
|
||||||
|
self.uploader.payload_telemetry(self.ptlm_string, self.ptlm_metadata)
|
||||||
|
self.couchdb.check()
|
||||||
|
|
||||||
|
def test_gives_up_after_many_conflicts(self):
|
||||||
|
self.add_mock_conflicts(19)
|
||||||
|
self.couchdb.run()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.uploader.payload_telemetry(self.ptlm_string,
|
||||||
|
self.ptlm_metadata)
|
||||||
|
except ProxyException, e:
|
||||||
|
if e.name == "runtime_error" and \
|
||||||
|
e.what == "habitat::UnmergeableError":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
raise AssertionError("Did not raise UnmergeableError")
|
||||||
|
|
||||||
|
def test_update_func_likes_doc(self):
|
||||||
|
doc_ish = self.make_ptlm_doc_ish()
|
||||||
|
|
||||||
|
req = {
|
||||||
|
"body": json.dumps(doc_ish),
|
||||||
|
"id": self.ptlm_doc_id
|
||||||
|
}
|
||||||
|
views.payload_telemetry.add_listener_update(None, req)
|
||||||
|
|
||||||
def test_flights(self):
|
def test_flights(self):
|
||||||
rows = []
|
rows = []
|
||||||
expect_result = []
|
expect_result = []
|
||||||
|
@ -897,13 +832,13 @@ class TestCPPConnectorThreaded(TestCPPConnector):
|
||||||
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
"time_uploaded": self.callbacks.fake_rfc3339(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
doc = copy.deepcopy(self.ptlm_doc)
|
doc_ish = self.make_ptlm_doc_ish()
|
||||||
doc["receivers"]["PROXYCALL"].update(receiver_info)
|
doc_ish["receivers"]["NEWCALL"] = doc_ish["receivers"]["PROXYCALL"]
|
||||||
doc["receivers"]["NEWCALL"] = doc["receivers"]["PROXYCALL"]
|
del doc_ish["receivers"]["PROXYCALL"]
|
||||||
del doc["receivers"]["PROXYCALL"]
|
|
||||||
|
|
||||||
self.expect_save_doc(doc)
|
self.expect_add_listener_update(self.ptlm_doc_id, doc_ish)
|
||||||
self.couchdb.run()
|
self.couchdb.run()
|
||||||
|
|
||||||
ret_doc_id = self.uploader.payload_telemetry(self.ptlm_string,
|
ret_doc_id = self.uploader.payload_telemetry(self.ptlm_string,
|
||||||
self.ptlm_metadata)
|
self.ptlm_metadata)
|
||||||
self.couchdb.check()
|
self.couchdb.check()
|
||||||
|
|
Ładowanie…
Reference in New Issue