xref: /freebsd/contrib/kyua/model/metadata.cpp (revision 939fec44a79323ba06cf0ad60d4b69300a8abbc6)
1 // Copyright 2012 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "model/metadata.hpp"
30 
31 #include <memory>
32 
33 #include "engine/execenv/execenv.hpp"
34 #include "model/exceptions.hpp"
35 #include "model/types.hpp"
36 #include "utils/config/exceptions.hpp"
37 #include "utils/config/nodes.ipp"
38 #include "utils/config/tree.ipp"
39 #include "utils/datetime.hpp"
40 #include "utils/defs.hpp"
41 #include "utils/format/macros.hpp"
42 #include "utils/fs/exceptions.hpp"
43 #include "utils/fs/path.hpp"
44 #include "utils/noncopyable.hpp"
45 #include "utils/optional.ipp"
46 #include "utils/sanity.hpp"
47 #include "utils/text/exceptions.hpp"
48 #include "utils/text/operations.hpp"
49 #include "utils/units.hpp"
50 
51 namespace config = utils::config;
52 namespace datetime = utils::datetime;
53 namespace fs = utils::fs;
54 namespace text = utils::text;
55 namespace units = utils::units;
56 
57 using utils::optional;
58 
59 
60 namespace {
61 
62 
63 /// Global instance of defaults.
64 ///
65 /// This exists so that the getters in metadata can return references instead
66 /// of object copies.  Use get_defaults() to query.
67 static optional< config::tree > defaults;
68 
69 
70 /// A leaf node that holds a bytes quantity.
71 class bytes_node : public config::native_leaf_node< units::bytes > {
72 public:
73     /// Copies the node.
74     ///
75     /// \return A dynamically-allocated node.
76     virtual base_node*
deep_copy(void) const77     deep_copy(void) const
78     {
79         std::unique_ptr< bytes_node > new_node(new bytes_node());
80         new_node->_value = _value;
81         return new_node.release();
82     }
83 
84     /// Pushes the node's value onto the Lua stack.
85     void
push_lua(lutok::state &) const86     push_lua(lutok::state& /* state */) const
87     {
88         UNREACHABLE;
89     }
90 
91     /// Sets the value of the node from an entry in the Lua stack.
92     void
set_lua(lutok::state &,const int)93     set_lua(lutok::state& /* state */, const int /* index */)
94     {
95         UNREACHABLE;
96     }
97 };
98 
99 
100 /// A leaf node that holds a time delta.
101 class delta_node : public config::typed_leaf_node< datetime::delta > {
102 public:
103     /// Copies the node.
104     ///
105     /// \return A dynamically-allocated node.
106     virtual base_node*
deep_copy(void) const107     deep_copy(void) const
108     {
109         std::unique_ptr< delta_node > new_node(new delta_node());
110         new_node->_value = _value;
111         return new_node.release();
112     }
113 
114     /// Sets the value of the node from a raw string representation.
115     ///
116     /// \param raw_value The value to set the node to.
117     ///
118     /// \throw value_error If the value is invalid.
119     void
set_string(const std::string & raw_value)120     set_string(const std::string& raw_value)
121     {
122         unsigned int seconds;
123         try {
124             seconds = text::to_type< unsigned int >(raw_value);
125         } catch (const text::error& e) {
126             throw config::value_error(F("Invalid time delta %s") % raw_value);
127         }
128         set(datetime::delta(seconds, 0));
129     }
130 
131     /// Converts the contents of the node to a string.
132     ///
133     /// \pre The node must have a value.
134     ///
135     /// \return A string representation of the value held by the node.
136     std::string
to_string(void) const137     to_string(void) const
138     {
139         return F("%s") % value().seconds;
140     }
141 
142     /// Pushes the node's value onto the Lua stack.
143     void
push_lua(lutok::state &) const144     push_lua(lutok::state& /* state */) const
145     {
146         UNREACHABLE;
147     }
148 
149     /// Sets the value of the node from an entry in the Lua stack.
150     void
set_lua(lutok::state &,const int)151     set_lua(lutok::state& /* state */, const int /* index */)
152     {
153         UNREACHABLE;
154     }
155 };
156 
157 
158 /// A leaf node that holds a "required user" property.
159 ///
160 /// This node is just a string, but it provides validation of the only allowed
161 /// values.
162 class user_node : public config::string_node {
163     /// Copies the node.
164     ///
165     /// \return A dynamically-allocated node.
166     virtual base_node*
deep_copy(void) const167     deep_copy(void) const
168     {
169         std::unique_ptr< user_node > new_node(new user_node());
170         new_node->_value = _value;
171         return new_node.release();
172     }
173 
174     /// Checks a given user textual representation for validity.
175     ///
176     /// \param user The value to validate.
177     ///
178     /// \throw config::value_error If the value is not valid.
179     void
validate(const value_type & user) const180     validate(const value_type& user) const
181     {
182         if (!user.empty() && user != "root" && user != "unprivileged")
183             throw config::value_error("Invalid required user value");
184     }
185 };
186 
187 
188 /// A leaf node that holds a set of paths.
189 ///
190 /// This node type is used to represent the value of the required files and
191 /// required programs, for example, and these do not allow relative paths.  We
192 /// check this here.
193 class paths_set_node : public config::base_set_node< fs::path > {
194     /// Copies the node.
195     ///
196     /// \return A dynamically-allocated node.
197     virtual base_node*
deep_copy(void) const198     deep_copy(void) const
199     {
200         std::unique_ptr< paths_set_node > new_node(new paths_set_node());
201         new_node->_value = _value;
202         return new_node.release();
203     }
204 
205     /// Converts a single path to the native type.
206     ///
207     /// \param raw_value The value to parse.
208     ///
209     /// \return The parsed value.
210     ///
211     /// \throw config::value_error If the value is invalid.
212     fs::path
parse_one(const std::string & raw_value) const213     parse_one(const std::string& raw_value) const
214     {
215         try {
216             return fs::path(raw_value);
217         } catch (const fs::error& e) {
218             throw config::value_error(e.what());
219         }
220     }
221 
222     /// Checks a collection of paths for validity.
223     ///
224     /// \param paths The value to validate.
225     ///
226     /// \throw config::value_error If the value is not valid.
227     void
validate(const value_type & paths) const228     validate(const value_type& paths) const
229     {
230         for (value_type::const_iterator iter = paths.begin();
231              iter != paths.end(); ++iter) {
232             const fs::path& path = *iter;
233             if (!path.is_absolute() && path.ncomponents() > 1)
234                 throw config::value_error(F("Relative path '%s' not allowed") %
235                                           *iter);
236         }
237     }
238 };
239 
240 
241 /// Initializes a tree to hold test case requirements.
242 ///
243 /// \param [in,out] tree The tree to initialize.
244 static void
init_tree(config::tree & tree)245 init_tree(config::tree& tree)
246 {
247     tree.define< config::strings_set_node >("allowed_architectures");
248     tree.define< config::strings_set_node >("allowed_platforms");
249     tree.define_dynamic("custom");
250     tree.define< config::string_node >("description");
251     tree.define< config::string_node >("execenv");
252     tree.define< config::string_node >("execenv_jail_params");
253     tree.define< config::bool_node >("has_cleanup");
254     tree.define< config::bool_node >("is_exclusive");
255     tree.define< config::strings_set_node >("required_configs");
256     tree.define< bytes_node >("required_disk_space");
257     tree.define< paths_set_node >("required_files");
258     tree.define< bytes_node >("required_memory");
259     tree.define< config::strings_set_node >("required_kmods");
260     tree.define< paths_set_node >("required_programs");
261     tree.define< user_node >("required_user");
262     tree.define< delta_node >("timeout");
263 }
264 
265 
266 /// Sets default values on a tree object.
267 ///
268 /// \param [in,out] tree The tree to configure.
269 static void
set_defaults(config::tree & tree)270 set_defaults(config::tree& tree)
271 {
272     tree.set< config::strings_set_node >("allowed_architectures",
273                                          model::strings_set());
274     tree.set< config::strings_set_node >("allowed_platforms",
275                                          model::strings_set());
276     tree.set< config::string_node >("description", "");
277     tree.set< config::string_node >("execenv", "");
278     tree.set< config::string_node >("execenv_jail_params", "");
279     tree.set< config::bool_node >("has_cleanup", false);
280     tree.set< config::bool_node >("is_exclusive", false);
281     tree.set< config::strings_set_node >("required_configs",
282                                          model::strings_set());
283     tree.set< bytes_node >("required_disk_space", units::bytes(0));
284     tree.set< paths_set_node >("required_files", model::paths_set());
285     tree.set< bytes_node >("required_memory", units::bytes(0));
286     tree.set< config::strings_set_node >("required_kmods", model::strings_set());
287     tree.set< paths_set_node >("required_programs", model::paths_set());
288     tree.set< user_node >("required_user", "");
289     // TODO(jmmv): We shouldn't be setting a default timeout like this.  See
290     // Issue 5 for details.
291     tree.set< delta_node >("timeout", datetime::delta(300, 0));
292 }
293 
294 
295 /// Queries the global defaults tree object with lazy initialization.
296 ///
297 /// \return A metadata tree.  This object is statically allocated so it is
298 /// acceptable to obtain references to it and its members.
299 const config::tree&
get_defaults(void)300 get_defaults(void)
301 {
302     if (!defaults) {
303         config::tree props;
304         init_tree(props);
305         set_defaults(props);
306         defaults = props;
307     }
308     return defaults.get();
309 }
310 
311 
312 /// Looks up a value in a tree with error rewriting.
313 ///
314 /// \tparam NodeType The type of the node.
315 /// \param tree The tree in which to insert the value.
316 /// \param key The key to set.
317 ///
318 /// \return A read-write reference to the value in the node.
319 ///
320 /// \throw model::error If the key is not known or if the value is not valid.
321 template< class NodeType >
322 typename NodeType::value_type&
lookup_rw(config::tree & tree,const std::string & key)323 lookup_rw(config::tree& tree, const std::string& key)
324 {
325     try {
326         return tree.lookup_rw< NodeType >(key);
327     } catch (const config::unknown_key_error& e) {
328         throw model::error(F("Unknown metadata property %s") % key);
329     } catch (const config::value_error& e) {
330         throw model::error(F("Invalid value for metadata property %s: %s") %
331                             key % e.what());
332     }
333 }
334 
335 
336 /// Sets a value in a tree with error rewriting.
337 ///
338 /// \tparam NodeType The type of the node.
339 /// \param tree The tree in which to insert the value.
340 /// \param key The key to set.
341 /// \param value The value to set the node to.
342 ///
343 /// \throw model::error If the key is not known or if the value is not valid.
344 template< class NodeType >
345 void
set(config::tree & tree,const std::string & key,const typename NodeType::value_type & value)346 set(config::tree& tree, const std::string& key,
347     const typename NodeType::value_type& value)
348 {
349     try {
350         tree.set< NodeType >(key, value);
351     } catch (const config::unknown_key_error& e) {
352         throw model::error(F("Unknown metadata property %s") % key);
353     } catch (const config::value_error& e) {
354         throw model::error(F("Invalid value for metadata property %s: %s") %
355                             key % e.what());
356     }
357 }
358 
359 
360 }  // anonymous namespace
361 
362 
363 /// Internal implementation of the metadata class.
364 struct model::metadata::impl : utils::noncopyable {
365     /// Metadata properties.
366     config::tree props;
367 
368     /// Constructor.
369     ///
370     /// \param props_ Metadata properties of the test.
implmodel::metadata::impl371     impl(const utils::config::tree& props_) :
372         props(props_)
373     {
374     }
375 
376     /// Equality comparator.
377     ///
378     /// \param other The other object to compare this one to.
379     ///
380     /// \return True if this object and other are equal; false otherwise.
381     bool
operator ==model::metadata::impl382     operator==(const impl& other) const
383     {
384         return (get_defaults().combine(props) ==
385                 get_defaults().combine(other.props));
386     }
387 };
388 
389 
390 /// Constructor.
391 ///
392 /// \param props Metadata properties of the test.
metadata(const utils::config::tree & props)393 model::metadata::metadata(const utils::config::tree& props) :
394     _pimpl(new impl(props))
395 {
396 }
397 
398 
399 /// Destructor.
~metadata(void)400 model::metadata::~metadata(void)
401 {
402 }
403 
404 
405 /// Applies a set of overrides to this metadata object.
406 ///
407 /// \param overrides The overrides to apply.  Any values explicitly set in this
408 ///     other object will override any possible values set in this object.
409 ///
410 /// \return A new metadata object with the combination.
411 model::metadata
apply_overrides(const metadata & overrides) const412 model::metadata::apply_overrides(const metadata& overrides) const
413 {
414     return metadata(_pimpl->props.combine(overrides._pimpl->props));
415 }
416 
417 
418 /// Returns the architectures allowed by the test.
419 ///
420 /// \return Set of architectures, or empty if this does not apply.
421 const model::strings_set&
allowed_architectures(void) const422 model::metadata::allowed_architectures(void) const
423 {
424     if (_pimpl->props.is_set("allowed_architectures")) {
425         return _pimpl->props.lookup< config::strings_set_node >(
426             "allowed_architectures");
427     } else {
428         return get_defaults().lookup< config::strings_set_node >(
429             "allowed_architectures");
430     }
431 }
432 
433 
434 /// Returns the platforms allowed by the test.
435 ///
436 /// \return Set of platforms, or empty if this does not apply.
437 const model::strings_set&
allowed_platforms(void) const438 model::metadata::allowed_platforms(void) const
439 {
440     if (_pimpl->props.is_set("allowed_platforms")) {
441         return _pimpl->props.lookup< config::strings_set_node >(
442             "allowed_platforms");
443     } else {
444         return get_defaults().lookup< config::strings_set_node >(
445             "allowed_platforms");
446     }
447 }
448 
449 
450 /// Returns all the user-defined metadata properties.
451 ///
452 /// \return A key/value map of properties.
453 model::properties_map
custom(void) const454 model::metadata::custom(void) const
455 {
456     return _pimpl->props.all_properties("custom", true);
457 }
458 
459 
460 /// Returns the description of the test.
461 ///
462 /// \return Textual description; may be empty.
463 const std::string&
description(void) const464 model::metadata::description(void) const
465 {
466     if (_pimpl->props.is_set("description")) {
467         return _pimpl->props.lookup< config::string_node >("description");
468     } else {
469         return get_defaults().lookup< config::string_node >("description");
470     }
471 }
472 
473 
474 /// Returns execution environment name.
475 ///
476 /// \return Name of configured execution environment.
477 const std::string&
execenv(void) const478 model::metadata::execenv(void) const
479 {
480     if (_pimpl->props.is_set("execenv")) {
481         return _pimpl->props.lookup< config::string_node >("execenv");
482     } else {
483         return get_defaults().lookup< config::string_node >("execenv");
484     }
485 }
486 
487 
488 /// Returns execenv jail(8) parameters string to run a test with.
489 ///
490 /// \return String of jail parameters.
491 const std::string&
execenv_jail_params(void) const492 model::metadata::execenv_jail_params(void) const
493 {
494     if (_pimpl->props.is_set("execenv_jail_params")) {
495         return _pimpl->props.lookup< config::string_node >(
496             "execenv_jail_params");
497     } else {
498         return get_defaults().lookup< config::string_node >(
499             "execenv_jail_params");
500     }
501 }
502 
503 
504 /// Returns whether the test has a cleanup part or not.
505 ///
506 /// \return True if there is a cleanup part; false otherwise.
507 bool
has_cleanup(void) const508 model::metadata::has_cleanup(void) const
509 {
510     if (_pimpl->props.is_set("has_cleanup")) {
511         return _pimpl->props.lookup< config::bool_node >("has_cleanup");
512     } else {
513         return get_defaults().lookup< config::bool_node >("has_cleanup");
514     }
515 }
516 
517 
518 /// Returns whether the test has a specific execenv apart from default one.
519 ///
520 /// \return True if there is a non-host execenv configured; false otherwise.
521 bool
has_execenv(void) const522 model::metadata::has_execenv(void) const
523 {
524     const std::string& name = execenv();
525     return !name.empty() && name != engine::execenv::default_execenv_name;
526 }
527 
528 
529 /// Returns whether the test is exclusive or not.
530 ///
531 /// \return True if the test has to be run on its own, not concurrently with any
532 /// other tests; false otherwise.
533 bool
is_exclusive(void) const534 model::metadata::is_exclusive(void) const
535 {
536     if (_pimpl->props.is_set("is_exclusive")) {
537         return _pimpl->props.lookup< config::bool_node >("is_exclusive");
538     } else {
539         return get_defaults().lookup< config::bool_node >("is_exclusive");
540     }
541 }
542 
543 
544 /// Returns the list of configuration variables needed by the test.
545 ///
546 /// \return Set of configuration variables.
547 const model::strings_set&
required_configs(void) const548 model::metadata::required_configs(void) const
549 {
550     if (_pimpl->props.is_set("required_configs")) {
551         return _pimpl->props.lookup< config::strings_set_node >(
552             "required_configs");
553     } else {
554         return get_defaults().lookup< config::strings_set_node >(
555             "required_configs");
556     }
557 }
558 
559 
560 /// Returns the amount of free disk space required by the test.
561 ///
562 /// \return Number of bytes, or 0 if this does not apply.
563 const units::bytes&
required_disk_space(void) const564 model::metadata::required_disk_space(void) const
565 {
566     if (_pimpl->props.is_set("required_disk_space")) {
567         return _pimpl->props.lookup< bytes_node >("required_disk_space");
568     } else {
569         return get_defaults().lookup< bytes_node >("required_disk_space");
570     }
571 }
572 
573 
574 /// Returns the list of files needed by the test.
575 ///
576 /// \return Set of paths.
577 const model::paths_set&
required_files(void) const578 model::metadata::required_files(void) const
579 {
580     if (_pimpl->props.is_set("required_files")) {
581         return _pimpl->props.lookup< paths_set_node >("required_files");
582     } else {
583         return get_defaults().lookup< paths_set_node >("required_files");
584     }
585 }
586 
587 
588 /// Returns the amount of memory required by the test.
589 ///
590 /// \return Number of bytes, or 0 if this does not apply.
591 const units::bytes&
required_memory(void) const592 model::metadata::required_memory(void) const
593 {
594     if (_pimpl->props.is_set("required_memory")) {
595         return _pimpl->props.lookup< bytes_node >("required_memory");
596     } else {
597         return get_defaults().lookup< bytes_node >("required_memory");
598     }
599 }
600 
601 
602 /// Returns the list of kernel modules needed by the test.
603 ///
604 /// \return Set of kernel module names.
605 const model::strings_set&
required_kmods(void) const606 model::metadata::required_kmods(void) const
607 {
608     if (_pimpl->props.is_set("required_kmods")) {
609         return _pimpl->props.lookup< config::strings_set_node >(
610             "required_kmods");
611     } else {
612         return get_defaults().lookup< config::strings_set_node >(
613             "required_kmods");
614     }
615 }
616 
617 
618 /// Returns the list of programs needed by the test.
619 ///
620 /// \return Set of paths.
621 const model::paths_set&
required_programs(void) const622 model::metadata::required_programs(void) const
623 {
624     if (_pimpl->props.is_set("required_programs")) {
625         return _pimpl->props.lookup< paths_set_node >("required_programs");
626     } else {
627         return get_defaults().lookup< paths_set_node >("required_programs");
628     }
629 }
630 
631 
632 /// Returns the user required by the test.
633 ///
634 /// \return One of unprivileged, root or empty.
635 const std::string&
required_user(void) const636 model::metadata::required_user(void) const
637 {
638     if (_pimpl->props.is_set("required_user")) {
639         return _pimpl->props.lookup< user_node >("required_user");
640     } else {
641         return get_defaults().lookup< user_node >("required_user");
642     }
643 }
644 
645 
646 /// Returns the timeout of the test.
647 ///
648 /// \return A time delta; should be compared to default_timeout to see if it has
649 /// been overriden.
650 const datetime::delta&
timeout(void) const651 model::metadata::timeout(void) const
652 {
653     if (_pimpl->props.is_set("timeout")) {
654         return _pimpl->props.lookup< delta_node >("timeout");
655     } else {
656         return get_defaults().lookup< delta_node >("timeout");
657     }
658 }
659 
660 
661 /// Externalizes the metadata to a set of key/value textual pairs.
662 ///
663 /// \return A key/value representation of the metadata.
664 model::properties_map
to_properties(void) const665 model::metadata::to_properties(void) const
666 {
667     const config::tree fully_specified = get_defaults().combine(_pimpl->props);
668     return fully_specified.all_properties();
669 }
670 
671 
672 /// Equality comparator.
673 ///
674 /// \param other The other object to compare this one to.
675 ///
676 /// \return True if this object and other are equal; false otherwise.
677 bool
operator ==(const metadata & other) const678 model::metadata::operator==(const metadata& other) const
679 {
680     return _pimpl == other._pimpl || *_pimpl == *other._pimpl;
681 }
682 
683 
684 /// Inequality comparator.
685 ///
686 /// \param other The other object to compare this one to.
687 ///
688 /// \return True if this object and other are different; false otherwise.
689 bool
operator !=(const metadata & other) const690 model::metadata::operator!=(const metadata& other) const
691 {
692     return !(*this == other);
693 }
694 
695 
696 /// Injects the object into a stream.
697 ///
698 /// \param output The stream into which to inject the object.
699 /// \param object The object to format.
700 ///
701 /// \return The output stream.
702 std::ostream&
operator <<(std::ostream & output,const metadata & object)703 model::operator<<(std::ostream& output, const metadata& object)
704 {
705     output << "metadata{";
706 
707     bool first = true;
708     const model::properties_map props = object.to_properties();
709     for (model::properties_map::const_iterator iter = props.begin();
710          iter != props.end(); ++iter) {
711         if (!first)
712             output << ", ";
713         output << F("%s=%s") % (*iter).first %
714             text::quote((*iter).second, '\'');
715         first = false;
716     }
717 
718     output << "}";
719     return output;
720 }
721 
722 
723 /// Internal implementation of the metadata_builder class.
724 struct model::metadata_builder::impl : utils::noncopyable {
725     /// Collection of requirements.
726     config::tree props;
727 
728     /// Whether we have created a metadata object or not.
729     bool built;
730 
731     /// Constructor.
implmodel::metadata_builder::impl732     impl(void) :
733         built(false)
734     {
735         init_tree(props);
736     }
737 
738     /// Constructor.
739     ///
740     /// \param base The base model to construct a copy from.
implmodel::metadata_builder::impl741     impl(const model::metadata& base) :
742         props(base._pimpl->props.deep_copy()),
743         built(false)
744     {
745     }
746 };
747 
748 
749 /// Constructor.
metadata_builder(void)750 model::metadata_builder::metadata_builder(void) :
751     _pimpl(new impl())
752 {
753 }
754 
755 
756 /// Constructor.
metadata_builder(const model::metadata & base)757 model::metadata_builder::metadata_builder(const model::metadata& base) :
758     _pimpl(new impl(base))
759 {
760 }
761 
762 
763 /// Destructor.
~metadata_builder(void)764 model::metadata_builder::~metadata_builder(void)
765 {
766 }
767 
768 
769 /// Accumulates an additional allowed architecture.
770 ///
771 /// \param arch The architecture.
772 ///
773 /// \return A reference to this builder.
774 ///
775 /// \throw model::error If the value is invalid.
776 model::metadata_builder&
add_allowed_architecture(const std::string & arch)777 model::metadata_builder::add_allowed_architecture(const std::string& arch)
778 {
779     if (!_pimpl->props.is_set("allowed_architectures")) {
780         _pimpl->props.set< config::strings_set_node >(
781             "allowed_architectures",
782             get_defaults().lookup< config::strings_set_node >(
783                 "allowed_architectures"));
784     }
785     lookup_rw< config::strings_set_node >(
786         _pimpl->props, "allowed_architectures").insert(arch);
787     return *this;
788 }
789 
790 
791 /// Accumulates an additional allowed platform.
792 ///
793 /// \param platform The platform.
794 ///
795 /// \return A reference to this builder.
796 ///
797 /// \throw model::error If the value is invalid.
798 model::metadata_builder&
add_allowed_platform(const std::string & platform)799 model::metadata_builder::add_allowed_platform(const std::string& platform)
800 {
801     if (!_pimpl->props.is_set("allowed_platforms")) {
802         _pimpl->props.set< config::strings_set_node >(
803             "allowed_platforms",
804             get_defaults().lookup< config::strings_set_node >(
805                 "allowed_platforms"));
806     }
807     lookup_rw< config::strings_set_node >(
808         _pimpl->props, "allowed_platforms").insert(platform);
809     return *this;
810 }
811 
812 
813 /// Accumulates a single user-defined property.
814 ///
815 /// \param key Name of the property to define.
816 /// \param value Value of the property.
817 ///
818 /// \return A reference to this builder.
819 ///
820 /// \throw model::error If the value is invalid.
821 model::metadata_builder&
add_custom(const std::string & key,const std::string & value)822 model::metadata_builder::add_custom(const std::string& key,
823                                      const std::string& value)
824 {
825     _pimpl->props.set_string(F("custom.%s") % key, value);
826     return *this;
827 }
828 
829 
830 /// Accumulates an additional required configuration variable.
831 ///
832 /// \param var The name of the configuration variable.
833 ///
834 /// \return A reference to this builder.
835 ///
836 /// \throw model::error If the value is invalid.
837 model::metadata_builder&
add_required_config(const std::string & var)838 model::metadata_builder::add_required_config(const std::string& var)
839 {
840     if (!_pimpl->props.is_set("required_configs")) {
841         _pimpl->props.set< config::strings_set_node >(
842             "required_configs",
843             get_defaults().lookup< config::strings_set_node >(
844                 "required_configs"));
845     }
846     lookup_rw< config::strings_set_node >(
847         _pimpl->props, "required_configs").insert(var);
848     return *this;
849 }
850 
851 
852 /// Accumulates an additional required file.
853 ///
854 /// \param path The path to the file.
855 ///
856 /// \return A reference to this builder.
857 ///
858 /// \throw model::error If the value is invalid.
859 model::metadata_builder&
add_required_file(const fs::path & path)860 model::metadata_builder::add_required_file(const fs::path& path)
861 {
862     if (!_pimpl->props.is_set("required_files")) {
863         _pimpl->props.set< paths_set_node >(
864             "required_files",
865             get_defaults().lookup< paths_set_node >("required_files"));
866     }
867     lookup_rw< paths_set_node >(_pimpl->props, "required_files").insert(path);
868     return *this;
869 }
870 
871 
872 /// Accumulates an additional required program.
873 ///
874 /// \param path The path to the program.
875 ///
876 /// \return A reference to this builder.
877 ///
878 /// \throw model::error If the value is invalid.
879 model::metadata_builder&
add_required_program(const fs::path & path)880 model::metadata_builder::add_required_program(const fs::path& path)
881 {
882     if (!_pimpl->props.is_set("required_programs")) {
883         _pimpl->props.set< paths_set_node >(
884             "required_programs",
885             get_defaults().lookup< paths_set_node >("required_programs"));
886     }
887     lookup_rw< paths_set_node >(_pimpl->props,
888                                 "required_programs").insert(path);
889     return *this;
890 }
891 
892 
893 /// Sets the architectures allowed by the test.
894 ///
895 /// \param as Set of architectures.
896 ///
897 /// \return A reference to this builder.
898 ///
899 /// \throw model::error If the value is invalid.
900 model::metadata_builder&
set_allowed_architectures(const model::strings_set & as)901 model::metadata_builder::set_allowed_architectures(
902     const model::strings_set& as)
903 {
904     set< config::strings_set_node >(_pimpl->props, "allowed_architectures", as);
905     return *this;
906 }
907 
908 
909 /// Sets the platforms allowed by the test.
910 ///
911 /// \return ps Set of platforms.
912 ///
913 /// \return A reference to this builder.
914 ///
915 /// \throw model::error If the value is invalid.
916 model::metadata_builder&
set_allowed_platforms(const model::strings_set & ps)917 model::metadata_builder::set_allowed_platforms(const model::strings_set& ps)
918 {
919     set< config::strings_set_node >(_pimpl->props, "allowed_platforms", ps);
920     return *this;
921 }
922 
923 
924 /// Sets the user-defined properties.
925 ///
926 /// \param props The custom properties to set.
927 ///
928 /// \return A reference to this builder.
929 ///
930 /// \throw model::error If the value is invalid.
931 model::metadata_builder&
set_custom(const model::properties_map & props)932 model::metadata_builder::set_custom(const model::properties_map& props)
933 {
934     for (model::properties_map::const_iterator iter = props.begin();
935          iter != props.end(); ++iter)
936         _pimpl->props.set_string(F("custom.%s") % (*iter).first,
937                                  (*iter).second);
938     return *this;
939 }
940 
941 
942 /// Sets the description of the test.
943 ///
944 /// \param description Textual description of the test.
945 ///
946 /// \return A reference to this builder.
947 ///
948 /// \throw model::error If the value is invalid.
949 model::metadata_builder&
set_description(const std::string & description)950 model::metadata_builder::set_description(const std::string& description)
951 {
952     set< config::string_node >(_pimpl->props, "description", description);
953     return *this;
954 }
955 
956 
957 /// Sets execution environment name.
958 ///
959 /// \param name Execution environment name.
960 ///
961 /// \return A reference to this builder.
962 ///
963 /// \throw model::error If the value is invalid.
964 model::metadata_builder&
set_execenv(const std::string & name)965 model::metadata_builder::set_execenv(const std::string& name)
966 {
967     set< config::string_node >(_pimpl->props, "execenv", name);
968     return *this;
969 }
970 
971 
972 /// Sets execenv jail(8) parameters string to run the test with.
973 ///
974 /// \param params String of jail parameters.
975 ///
976 /// \return A reference to this builder.
977 ///
978 /// \throw model::error If the value is invalid.
979 model::metadata_builder&
set_execenv_jail_params(const std::string & params)980 model::metadata_builder::set_execenv_jail_params(const std::string& params)
981 {
982     set< config::string_node >(_pimpl->props, "execenv_jail_params", params);
983     return *this;
984 }
985 
986 
987 /// Sets whether the test has a cleanup part or not.
988 ///
989 /// \param cleanup True if the test has a cleanup part; false otherwise.
990 ///
991 /// \return A reference to this builder.
992 ///
993 /// \throw model::error If the value is invalid.
994 model::metadata_builder&
set_has_cleanup(const bool cleanup)995 model::metadata_builder::set_has_cleanup(const bool cleanup)
996 {
997     set< config::bool_node >(_pimpl->props, "has_cleanup", cleanup);
998     return *this;
999 }
1000 
1001 
1002 /// Sets whether the test is exclusive or not.
1003 ///
1004 /// \param exclusive True if the test is exclusive; false otherwise.
1005 ///
1006 /// \return A reference to this builder.
1007 ///
1008 /// \throw model::error If the value is invalid.
1009 model::metadata_builder&
set_is_exclusive(const bool exclusive)1010 model::metadata_builder::set_is_exclusive(const bool exclusive)
1011 {
1012     set< config::bool_node >(_pimpl->props, "is_exclusive", exclusive);
1013     return *this;
1014 }
1015 
1016 
1017 /// Sets the list of configuration variables needed by the test.
1018 ///
1019 /// \param vars Set of configuration variables.
1020 ///
1021 /// \return A reference to this builder.
1022 ///
1023 /// \throw model::error If the value is invalid.
1024 model::metadata_builder&
set_required_configs(const model::strings_set & vars)1025 model::metadata_builder::set_required_configs(const model::strings_set& vars)
1026 {
1027     set< config::strings_set_node >(_pimpl->props, "required_configs", vars);
1028     return *this;
1029 }
1030 
1031 
1032 /// Sets the amount of free disk space required by the test.
1033 ///
1034 /// \param bytes Number of bytes.
1035 ///
1036 /// \return A reference to this builder.
1037 ///
1038 /// \throw model::error If the value is invalid.
1039 model::metadata_builder&
set_required_disk_space(const units::bytes & bytes)1040 model::metadata_builder::set_required_disk_space(const units::bytes& bytes)
1041 {
1042     set< bytes_node >(_pimpl->props, "required_disk_space", bytes);
1043     return *this;
1044 }
1045 
1046 
1047 /// Sets the list of files needed by the test.
1048 ///
1049 /// \param files Set of paths.
1050 ///
1051 /// \return A reference to this builder.
1052 ///
1053 /// \throw model::error If the value is invalid.
1054 model::metadata_builder&
set_required_files(const model::paths_set & files)1055 model::metadata_builder::set_required_files(const model::paths_set& files)
1056 {
1057     set< paths_set_node >(_pimpl->props, "required_files", files);
1058     return *this;
1059 }
1060 
1061 
1062 /// Sets the amount of memory required by the test.
1063 ///
1064 /// \param bytes Number of bytes.
1065 ///
1066 /// \return A reference to this builder.
1067 ///
1068 /// \throw model::error If the value is invalid.
1069 model::metadata_builder&
set_required_memory(const units::bytes & bytes)1070 model::metadata_builder::set_required_memory(const units::bytes& bytes)
1071 {
1072     set< bytes_node >(_pimpl->props, "required_memory", bytes);
1073     return *this;
1074 }
1075 
1076 
1077 /// Sets the list of programs needed by the test.
1078 ///
1079 /// \param progs Set of paths.
1080 ///
1081 /// \return A reference to this builder.
1082 ///
1083 /// \throw model::error If the value is invalid.
1084 model::metadata_builder&
set_required_programs(const model::paths_set & progs)1085 model::metadata_builder::set_required_programs(const model::paths_set& progs)
1086 {
1087     set< paths_set_node >(_pimpl->props, "required_programs", progs);
1088     return *this;
1089 }
1090 
1091 
1092 /// Sets the user required by the test.
1093 ///
1094 /// \param user One of unprivileged, root or empty.
1095 ///
1096 /// \return A reference to this builder.
1097 ///
1098 /// \throw model::error If the value is invalid.
1099 model::metadata_builder&
set_required_user(const std::string & user)1100 model::metadata_builder::set_required_user(const std::string& user)
1101 {
1102     set< user_node >(_pimpl->props, "required_user", user);
1103     return *this;
1104 }
1105 
1106 
1107 /// Sets a metadata property by name from its textual representation.
1108 ///
1109 /// \param key The property to set.
1110 /// \param value The value to set the property to.
1111 ///
1112 /// \return A reference to this builder.
1113 ///
1114 /// \throw model::error If the value is invalid or the key does not exist.
1115 model::metadata_builder&
set_string(const std::string & key,const std::string & value)1116 model::metadata_builder::set_string(const std::string& key,
1117                                     const std::string& value)
1118 {
1119     try {
1120         _pimpl->props.set_string(key, value);
1121     } catch (const config::unknown_key_error& e) {
1122         throw model::format_error(F("Unknown metadata property %s") % key);
1123     } catch (const config::value_error& e) {
1124         throw model::format_error(
1125             F("Invalid value for metadata property %s: %s") % key % e.what());
1126     }
1127     return *this;
1128 }
1129 
1130 
1131 /// Sets the timeout of the test.
1132 ///
1133 /// \param timeout The timeout to set.
1134 ///
1135 /// \return A reference to this builder.
1136 ///
1137 /// \throw model::error If the value is invalid.
1138 model::metadata_builder&
set_timeout(const datetime::delta & timeout)1139 model::metadata_builder::set_timeout(const datetime::delta& timeout)
1140 {
1141     set< delta_node >(_pimpl->props, "timeout", timeout);
1142     return *this;
1143 }
1144 
1145 
1146 /// Creates a new metadata object.
1147 ///
1148 /// \pre This has not yet been called.  We only support calling this function
1149 /// once due to the way the internal tree works: we pass around references, not
1150 /// deep copies, so if we allowed a second build, we'd encourage reusing the
1151 /// same builder to construct different metadata objects, and this could have
1152 /// unintended consequences.
1153 ///
1154 /// \return The constructed metadata object.
1155 model::metadata
build(void) const1156 model::metadata_builder::build(void) const
1157 {
1158     PRE(!_pimpl->built);
1159     _pimpl->built = true;
1160 
1161     return metadata(_pimpl->props);
1162 }
1163