From: Samuel Just Date: Thu, 5 Dec 2013 00:06:17 +0000 (-0800) Subject: test/osd: restructure Object/RadosModel in prep for append X-Git-Tag: v0.78~286^2~45 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=7fe22da29f13876c50a25a89e78680372172c4d7;p=ceph.git test/osd: restructure Object/RadosModel in prep for append Attribute handling no longer has special support in ContentsGenerator. The most recent operation information is now stored in a special attr rather than at the beginning of the object. ObjectDesc layers include their own ContentsGenerators to allow more flexibility. Also, writes truncate to the new object size rather than simply causing reads to stop at that object size. Signed-off-by: Samuel Just --- diff --git a/src/test/osd/Object.cc b/src/test/osd/Object.cc index 5ae2ded23cf..eb8e81d5861 100644 --- a/src/test/osd/Object.cc +++ b/src/test/osd/Object.cc @@ -40,15 +40,14 @@ ostream &operator<<(ostream &out, const ContDesc &rhs) void VarLenGenerator::get_ranges(const ContDesc &cont, interval_set &out) { RandWrap rand(cont.seqnum); - uint64_t pos = get_header_length(cont); + uint64_t pos = 0; uint64_t limit = get_length(cont); - out.insert(0, pos); bool include = false; while (pos < limit) { uint64_t segment_length = (rand() % (max_stride_size - min_stride_size)) + min_stride_size; assert(segment_length < max_stride_size); assert(segment_length >= min_stride_size); - if (segment_length + pos >= limit) { + if (segment_length + pos > limit) { segment_length = limit - pos; } if (include) { @@ -59,46 +58,11 @@ void VarLenGenerator::get_ranges(const ContDesc &cont, interval_set &o } pos += segment_length; } -} - -void VarLenGenerator::write_header(const ContDesc &in, bufferlist &output) { - int data[6]; - data[0] = 0xDEADBEEF; - data[1] = in.objnum; - data[2] = in.cursnap; - data[3] = (int)in.seqnum; - data[4] = in.prefix.size(); - data[5] = 0xDEADBEEF; - output.append((char *)data, sizeof(data)); - output.append(in.prefix.c_str(), in.prefix.size()); - output.append((char *)data, sizeof(data[0])); -} - -bool VarLenGenerator::read_header(bufferlist::iterator &p, ContDesc &out) { - try { - int data[6]; - p.copy(sizeof(data), (char *)data); - if ((unsigned)data[0] != 0xDEADBEEF || (unsigned)data[5] != 0xDEADBEEF) return false; - out.objnum = data[1]; - out.cursnap = data[2]; - out.seqnum = (unsigned) data[3]; - int prefix_size = data[4]; - if (prefix_size >= 1000 || prefix_size <= 0) { - std::cerr << "prefix size is " << prefix_size << std::endl; - return false; - } - char buffer[1000]; - p.copy(prefix_size, buffer); - buffer[prefix_size] = 0; - out.prefix = buffer; - unsigned test; - p.copy(sizeof(test), (char *)&test); - if (test != 0xDEADBEEF) return false; - } catch (ceph::buffer::end_of_buffer &e) { - std::cerr << "end_of_buffer" << endl; - return false; - } - return true; + // make sure we write up to the limit + if (limit > 0 && ( + out.empty() || + (out.rbegin()->first + out.rbegin()->second < limit))) + out[limit-1] = 1; } ObjectDesc::iterator &ObjectDesc::iterator::advance(bool init) { @@ -111,9 +75,9 @@ ObjectDesc::iterator &ObjectDesc::iterator::advance(bool init) { return *this; } while (pos == limit) { - limit = *stack.begin(); + cur_cont = stack.begin()->first; + limit = stack.begin()->second; stack.pop_front(); - --cur_cont; } if (cur_cont == obj.layers.end()) { @@ -121,65 +85,55 @@ ObjectDesc::iterator &ObjectDesc::iterator::advance(bool init) { } interval_set ranges; - cont_gen->get_ranges(*cur_cont, ranges); + cur_cont->first->get_ranges(cur_cont->second, ranges); while (!ranges.contains(pos)) { - stack.push_front(limit); + stack.push_front(make_pair(cur_cont, limit)); + uint64_t length = cur_cont->first->get_length(cur_cont->second); uint64_t next; - if (pos >= ranges.range_end()) { + if (pos >= length) { next = limit; + cur_cont = obj.layers.end(); + } else if (ranges.empty() || pos >= ranges.range_end()) { + next = length; + ++cur_cont; } else { next = ranges.start_after(pos); + ++cur_cont; } if (next < limit) { limit = next; } - ++cur_cont; if (cur_cont == obj.layers.end()) { break; } ranges.clear(); - cont_gen->get_ranges(*cur_cont, ranges); + cur_cont->first->get_ranges(cur_cont->second, ranges); } if (cur_cont == obj.layers.end()) { return *this; } - if (!cont_iters.count(*cur_cont)) { - cont_iters.insert(pair(*cur_cont, - cont_gen->get_iterator(*cur_cont))); + if (!cont_iters.count(cur_cont->second)) { + cont_iters.insert(pair( + cur_cont->second, + cur_cont->first->get_iterator(cur_cont->second))); } - map::iterator j = cont_iters.find(*cur_cont); + map::iterator j = cont_iters.find( + cur_cont->second); assert(j != cont_iters.end()); j->second.seek(pos); return *this; } const ContDesc &ObjectDesc::most_recent() { - return *layers.begin(); + return layers.begin()->second; } -void ObjectDesc::update(const ContDesc &next) { - layers.push_front(next); +void ObjectDesc::update(ContentsGenerator *gen, const ContDesc &next) { + layers.push_front(make_pair(gen, next)); return; - /* - interval_set fall_through; - fall_through.insert(0, cont_gen->get_length(next)); - for (list::iterator i = layers.begin(); - i != layers.end(); - ) { - interval_set valid; - cont_gen->get_ranges(*i, valid); - valid.intersection_of(fall_through); - if (valid.empty()) { - layers.erase(i++); - continue; - } - fall_through.subtract(valid); - ++i; - } - */ } bool ObjectDesc::check(bufferlist &to_check) { diff --git a/src/test/osd/Object.h b/src/test/osd/Object.h index c5ca8cac9e0..22446b2bdeb 100644 --- a/src/test/osd/Object.h +++ b/src/test/osd/Object.h @@ -1,4 +1,5 @@ // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include #include "include/interval_set.h" #include "include/buffer.h" #include "include/encoding.h" @@ -94,15 +95,8 @@ public: parent(parent), impl(impl) {} }; - virtual bool read_header(bufferlist::iterator& input, - ContDesc &output) = 0; - virtual uint64_t get_length(const ContDesc &in) = 0; - virtual uint64_t get_attr_length(const ContDesc &in) = 0; - - virtual bufferlist gen_attribute(const ContDesc &in) = 0; - virtual void get_ranges(const ContDesc &in, interval_set &ranges) = 0; virtual iterator_impl *get_iterator_impl(const ContDesc &in) = 0; @@ -118,7 +112,7 @@ public: } }; -class VarLenGenerator : public ContentsGenerator { +class RandGenerator : public ContentsGenerator { public: class RandWrap { public: @@ -139,29 +133,19 @@ public: uint64_t pos; ContDesc cont; RandWrap rand; - bufferlist header; - bufferlist::iterator header_pos; - VarLenGenerator *cont_gen; + RandGenerator *cont_gen; char current; - iterator_impl(const ContDesc &cont, VarLenGenerator *cont_gen) : + iterator_impl(const ContDesc &cont, RandGenerator *cont_gen) : pos(0), cont(cont), rand(cont.seqnum), cont_gen(cont_gen) { - cont_gen->write_header(cont, header); - header_pos = header.begin(); - current = *header_pos; - ++header_pos; + current = rand(); } - virtual ContDesc get_cont() const { return cont; } - virtual uint64_t get_pos() const { return pos; } + ContDesc get_cont() const { return cont; } + uint64_t get_pos() const { return pos; } iterator_impl &operator++() { pos++; - if (header_pos.end()) { - current = rand(); - } else { - current = *header_pos; - ++header_pos; - } + current = rand(); return *this; } @@ -185,10 +169,10 @@ public: } }; - void get_ranges(const ContDesc &cont, interval_set &out); + virtual void get_ranges(const ContDesc &cont, interval_set &out) = 0; ContentsGenerator::iterator_impl *get_iterator_impl(const ContDesc &in) { - VarLenGenerator::iterator_impl *i = new iterator_impl(in, this); + RandGenerator::iterator_impl *i = new iterator_impl(in, this); return i; } @@ -196,75 +180,94 @@ public: delete in; } - ContentsGenerator::iterator_impl *dup_iterator_impl(const ContentsGenerator::iterator_impl *in) { + ContentsGenerator::iterator_impl *dup_iterator_impl( + const ContentsGenerator::iterator_impl *in) { ContentsGenerator::iterator_impl *retval = get_iterator_impl(in->get_cont()); retval->seek(in->get_pos()); return retval; } +}; - int get_header_length(const ContDesc &in) { - return 7*sizeof(int) + in.prefix.size(); +class VarLenGenerator : public RandGenerator { + uint64_t max_length; + uint64_t min_stride_size; + uint64_t max_stride_size; +public: + VarLenGenerator( + uint64_t length, uint64_t min_stride_size, uint64_t max_stride_size) : + max_length(length), + min_stride_size(min_stride_size), + max_stride_size(max_stride_size) {} + void get_ranges(const ContDesc &cont, interval_set &out); + uint64_t get_length(const ContDesc &in) { + RandWrap rand(in.seqnum); + return (rand() % max_length); } +}; +class AttrGenerator : public RandGenerator { + uint64_t max_len; +public: + AttrGenerator(uint64_t max_len) : max_len(max_len) {} + void get_ranges(const ContDesc &cont, interval_set &out) { + out.insert(0, get_length(cont)); + } uint64_t get_length(const ContDesc &in) { RandWrap rand(in.seqnum); - return (rand() % length) + get_header_length(in); + return (rand() % max_len); } - - bufferlist gen_attribute(const ContDesc &in) { - bufferlist header; - write_header(in, header); - ContentsGenerator::iterator iter = get_iterator(in); - for (uint64_t to_write = get_attr_length(in); to_write > 0; - --to_write) { - header.append(*iter); - ++iter; + bufferlist gen_bl(const ContDesc &in) { + bufferlist bl; + for (iterator i = get_iterator(in); !i.end(); ++i) { + bl.append(*i); } - return header; + assert(bl.length() < max_len); + return bl; } +}; - - uint64_t get_attr_length(const ContDesc &in) { +class AppendGenerator : public RandGenerator { + uint64_t off; + uint64_t max_len; + uint64_t max_intervals; +public: + AppendGenerator(uint64_t off, uint64_t max_len, uint64_t max_intervals) : + off(off), max_len(max_len), max_intervals(max_intervals) {} + uint64_t get_length(const ContDesc &in) { RandWrap rand(in.seqnum); - return (rand() % attr_length) + get_header_length(in); + return off + (rand() % max_len); + } + void get_ranges(const ContDesc &cont, interval_set &out) { + out.insert(off, get_length(cont)); } - - void write_header(const ContDesc &in, bufferlist &output); - - bool read_header(bufferlist::iterator &p, ContDesc &out); - uint64_t length; - uint64_t attr_length; - uint64_t min_stride_size; - uint64_t max_stride_size; - VarLenGenerator(uint64_t length, uint64_t min_stride_size, uint64_t max_stride_size, uint64_t attr_length = 2000) : - length(length), attr_length(attr_length), - min_stride_size(min_stride_size), max_stride_size(max_stride_size) {} }; class ObjectDesc { public: - ObjectDesc(ContentsGenerator *cont_gen) + ObjectDesc() : exists(false), dirty(false), - version(0), layers(), cont_gen(cont_gen) {} + version(0) {} ObjectDesc(const ContDesc &init, ContentsGenerator *cont_gen) : exists(false), dirty(false), - version(0), layers(), cont_gen(cont_gen) { - layers.push_front(init); + version(0) { + layers.push_front(make_pair(cont_gen, init)); } class iterator { public: uint64_t pos; ObjectDesc &obj; - ContentsGenerator *cont_gen; - list stack; + list, + ContDesc> >::iterator, + uint64_t> > stack; map cont_iters; uint64_t limit; - list::iterator cur_cont; + list, + ContDesc> >::iterator cur_cont; - iterator(ObjectDesc &obj, ContentsGenerator *cont_gen) : - pos(0), obj(obj), cont_gen(cont_gen) { - limit = cont_gen->get_length(*obj.layers.begin()); + iterator(ObjectDesc &obj) : + pos(0), obj(obj) { + limit = obj.layers.begin()->first->get_length(obj.layers.begin()->second); cur_cont = obj.layers.begin(); advance(true); } @@ -278,14 +281,16 @@ public: if (cur_cont == obj.layers.end()) { return '\0'; } else { - map::iterator j = cont_iters.find(*cur_cont); + map::iterator j = cont_iters.find( + cur_cont->second); assert(j != cont_iters.end()); return *(j->second); } } bool end() { - return pos >= cont_gen->get_length(*obj.layers.begin()); + return pos >= obj.layers.begin()->first->get_length( + obj.layers.begin()->second); } void seek(uint64_t _pos) { @@ -297,9 +302,9 @@ public: } } }; - + iterator begin() { - return iterator(*this, this->cont_gen); + return iterator(*this); } bool deleted() { @@ -310,9 +315,13 @@ public: return layers.size(); } - void update(const ContDesc &next); + // takes ownership of gen + void update(ContentsGenerator *gen, const ContDesc &next); bool check(bufferlist &to_check); const ContDesc &most_recent(); + ContentsGenerator *most_recent_gen() { + return layers.begin()->first.get(); + } map attrs; // Both omap and xattrs bufferlist header; bool exists; @@ -320,9 +329,7 @@ public: uint64_t version; private: - list layers; - ContentsGenerator *cont_gen; - ObjectDesc(); + list, ContDesc> > layers; }; #endif diff --git a/src/test/osd/RadosModel.h b/src/test/osd/RadosModel.h index 0ff8fb3631d..306f6d5080b 100644 --- a/src/test/osd/RadosModel.h +++ b/src/test/osd/RadosModel.h @@ -162,18 +162,22 @@ public: string prefix; int errors; int max_in_flight; - ContentsGenerator &cont_gen; int seq_num; map snaps; uint64_t seq; const char *rados_id; bool initialized; map watches; - + const uint64_t max_size; + const uint64_t min_stride_size; + const uint64_t max_stride_size; + AttrGenerator attr_gen; RadosTestContext(const string &pool_name, int max_in_flight, - ContentsGenerator &cont_gen, + uint64_t max_size, + uint64_t min_stride_size, + uint64_t max_stride_size, const char *id = 0) : state_lock("Context Lock"), pool_obj_cont(), @@ -182,8 +186,11 @@ public: next_oid(0), errors(0), max_in_flight(max_in_flight), - cont_gen(cont_gen), seq_num(0), seq(0), - rados_id(id), initialized(false) + seq_num(0), seq(0), + rados_id(id), initialized(false), + max_size(max_size), + min_stride_size(min_stride_size), max_stride_size(max_stride_size), + attr_gen(2000) { } @@ -290,7 +297,7 @@ public: void rm_object_attrs(const string &oid, const set &attrs) { - ObjectDesc new_obj(&cont_gen); + ObjectDesc new_obj; for (map >::reverse_iterator i = pool_obj_cont.rbegin(); i != pool_obj_cont.rend(); @@ -312,7 +319,7 @@ public: void remove_object_header(const string &oid) { - ObjectDesc new_obj(&cont_gen); + ObjectDesc new_obj; for (map >::reverse_iterator i = pool_obj_cont.rbegin(); i != pool_obj_cont.rend(); @@ -331,7 +338,7 @@ public: void update_object_header(const string &oid, const bufferlist &bl) { - ObjectDesc new_obj(&cont_gen); + ObjectDesc new_obj; for (map >::reverse_iterator i = pool_obj_cont.rbegin(); i != pool_obj_cont.rend(); @@ -350,7 +357,7 @@ public: void update_object_attrs(const string &oid, const map &attrs) { - ObjectDesc new_obj(&cont_gen); + ObjectDesc new_obj; for (map >::reverse_iterator i = pool_obj_cont.rbegin(); i != pool_obj_cont.rend(); @@ -371,9 +378,10 @@ public: pool_obj_cont[current_snap].insert(pair(oid, new_obj)); } - void update_object(const string &oid, const ContDesc &contents) + void update_object(ContentsGenerator *cont_gen, + const string &oid, const ContDesc &contents) { - ObjectDesc new_obj(&cont_gen); + ObjectDesc new_obj; for (map >::reverse_iterator i = pool_obj_cont.rbegin(); i != pool_obj_cont.rend(); @@ -385,7 +393,8 @@ public: } } new_obj.exists = true; - new_obj.update(contents); + new_obj.update(cont_gen, + contents); pool_obj_cont[current_snap].erase(oid); pool_obj_cont[current_snap].insert(pair(oid, new_obj)); } @@ -423,7 +432,7 @@ public: void remove_object(const string &oid) { assert(!get_watch_context(oid)); - ObjectDesc new_obj(&cont_gen); + ObjectDesc new_obj; pool_obj_cont[current_snap].erase(oid); pool_obj_cont[current_snap].insert(pair(oid, new_obj)); } @@ -473,7 +482,7 @@ public: void roll_back(const string &oid, int snap) { assert(!get_watch_context(oid)); - ObjectDesc contents(&cont_gen); + ObjectDesc contents; find_object(oid, &contents, snap); pool_obj_cont.rbegin()->second.erase(oid); pool_obj_cont.rbegin()->second.insert(pair(oid, contents)); @@ -501,7 +510,7 @@ public: set to_remove; { Mutex::Locker l(context->state_lock); - ObjectDesc obj(&context->cont_gen); + ObjectDesc obj; if (!context->find_object(oid, &obj)) { context->kick(); done = true; @@ -513,7 +522,7 @@ public: context->oid_not_in_use.erase(oid); if (rand() % 30) { - ContentsGenerator::iterator iter = context->cont_gen.get_iterator(cont); + ContentsGenerator::iterator iter = context->attr_gen.get_iterator(cont); for (map::iterator i = obj.attrs.begin(); i != obj.attrs.end(); ++i, ++iter) { @@ -601,11 +610,12 @@ public: map omap_contents; map omap; bufferlist header; - ContentsGenerator::iterator keygen = context->cont_gen.get_iterator(cont); + ContentsGenerator::iterator keygen = context->attr_gen.get_iterator(cont); op.create(false); while (!*keygen) ++keygen; while (*keygen) { - header.append(*keygen); + if (*keygen != '_') + header.append(*keygen); ++keygen; } for (int i = 0; i < 20; ++i) { @@ -616,10 +626,10 @@ public: ++keygen; } ContDesc val(cont); - val.seqnum += context->cont_gen.get_length(cont); + val.seqnum += (unsigned)(*keygen); val.prefix = ("oid: " + oid); omap[key] = val; - bufferlist val_buffer = context->cont_gen.gen_attribute(val); + bufferlist val_buffer = context->attr_gen.gen_bl(val); omap_contents[key] = val_buffer; op.setxattr(key.c_str(), val_buffer); } @@ -676,6 +686,7 @@ public: uint64_t last_acked_tid; librados::ObjectReadOperation read_op; + librados::ObjectWriteOperation write_op; bufferlist rbuffer; WriteOp(int n, @@ -696,20 +707,23 @@ public: cont = ContDesc(context->seq_num, context->current_snap, context->seq_num, prefix); - context->update_object(oid, cont); + ContentsGenerator *cont_gen = new VarLenGenerator( + context->max_size, context->min_stride_size, context->max_stride_size); + context->update_object(cont_gen, oid, cont); context->oid_in_use.insert(oid); context->oid_not_in_use.erase(oid); interval_set ranges; - context->cont_gen.get_ranges(cont, ranges); + + cont_gen->get_ranges(cont, ranges); std::cout << num << ": seq_num " << context->seq_num << " ranges " << ranges << std::endl; context->seq_num++; context->state_lock.Unlock(); waiting_on = ranges.num_intervals(); //cout << " waiting_on = " << waiting_on << std::endl; - ContentsGenerator::iterator gen_pos = context->cont_gen.get_iterator(cont); + ContentsGenerator::iterator gen_pos = cont_gen->get_iterator(cont); uint64_t tid = 1; for (interval_set::iterator i = ranges.begin(); i != ranges.end(); @@ -733,10 +747,25 @@ public: to_write, i.get_len(), i.get_start()); } + bufferlist contbl; + ::encode(cont, contbl); pair *cb_arg = new pair( this, - new TestOp::CallbackInfo(tid)); + new TestOp::CallbackInfo(++tid)); + librados::AioCompletion *completion = context->rados.aio_create_completion( + (void*) cb_arg, &write_callback, NULL); + waiting.insert(completion); + waiting_on++; + write_op.setxattr("_header", contbl); + write_op.truncate(cont_gen->get_length(cont)); + context->io_ctx.aio_operate( + context->prefix+oid, completion, &write_op); + + cb_arg = + new pair( + this, + new TestOp::CallbackInfo(++tid)); rcompletion = context->rados.aio_create_completion( (void*) cb_arg, &write_callback, NULL); waiting_on++; @@ -828,7 +857,7 @@ public: return; } - ObjectDesc contents(&context->cont_gen); + ObjectDesc contents; context->find_object(oid, &contents); bool present = !contents.deleted(); @@ -890,7 +919,6 @@ public: : TestOp(n, context, stat), completion(NULL), oid(oid), - old_value(&context->cont_gen), snap(0), retval(0), attrretval(0) @@ -937,7 +965,7 @@ public: op.read(0, !old_value.has_contents() ? 0 : - context->cont_gen.get_length(old_value.most_recent()), + old_value.most_recent_gen()->get_length(old_value.most_recent()), &result, &retval); @@ -978,15 +1006,23 @@ public: assert(0); } } else { + map::iterator iter = xattrs.find("_header"); + bufferlist headerbl; + if (iter == xattrs.end()) { + cerr << num << ": Error: did not find header attr, has_contents: " + << old_value.has_contents() + << std::endl; + assert(!old_value.has_contents()); + } else { + headerbl = iter->second; + xattrs.erase(iter); + } cout << num << ": expect " << old_value.most_recent() << std::endl; assert(!old_value.deleted()); if (old_value.has_contents()) { ContDesc to_check; - bufferlist::iterator p = result.begin(); - if (!context->cont_gen.read_header(p, to_check)) { - cerr << num << ": Unable to decode oid " << oid << " at snap " << context->current_snap << std::endl; - context->errors++; - } + bufferlist::iterator p = headerbl.begin(); + ::decode(to_check, p); if (to_check != old_value.most_recent()) { cerr << num << ": oid " << oid << " found incorrect object contents " << to_check << ", expected " << old_value.most_recent() << std::endl; @@ -1031,7 +1067,7 @@ public: ++omap_iter) { assert(old_value.attrs.count(omap_iter->first)); assert(xattrs.count(omap_iter->first)); - bufferlist bl = context->cont_gen.gen_attribute( + bufferlist bl = context->attr_gen.gen_bl( old_value.attrs[omap_iter->first]); assert(bl.length() == omap_iter->second.length()); assert(bl.length() == xattrs[omap_iter->first].length()); @@ -1169,7 +1205,7 @@ public: void _begin() { context->state_lock.Lock(); - ObjectDesc contents(&context->cont_gen); + ObjectDesc contents; context->find_object(oid, &contents); if (contents.deleted()) { context->kick(); @@ -1326,7 +1362,6 @@ public: TestOpStat *stat) : TestOp(n, context, stat), oid(oid), oid_src(oid_src), - src_value(&context->cont_gen), comp(NULL), done(0), version(0), r(0) {} @@ -1588,8 +1623,7 @@ public: : TestOp(n, context, stat), completion(NULL), oid(oid), - dirty(false), - old_value(&context->cont_gen) + dirty(false) {} void _begin() diff --git a/src/test/osd/TestRados.cc b/src/test/osd/TestRados.cc index 9c3dbf2308a..4ef0de45d25 100644 --- a/src/test/osd/TestRados.cc +++ b/src/test/osd/TestRados.cc @@ -328,8 +328,13 @@ int main(int argc, char **argv) } char *id = getenv("CEPH_CLIENT_ID"); - VarLenGenerator cont_gen(size, min_stride_size, max_stride_size); - RadosTestContext context(pool_name, max_in_flight, cont_gen, id); + RadosTestContext context( + pool_name, + max_in_flight, + size, + min_stride_size, + max_stride_size, + id); TestOpStat stats; WeightedTestGenerator gen = WeightedTestGenerator(ops, objects,