pax_global_header00006660000000000000000000000064132105562710014514gustar00rootroot0000000000000052 comment=21e59eabf4e112d2aab90aac6d6b2883952bd365 pycadf-2.7.0/000077500000000000000000000000001321055627100127705ustar00rootroot00000000000000pycadf-2.7.0/.gitignore000066400000000000000000000002211321055627100147530ustar00rootroot00000000000000AUTHORS ChangeLog *~ *.swp *.pyc *.log .tox .coverage *.egg-info/ build/ doc/build/ doc/source/api dist/ .testrepository/ .project .pydevproject pycadf-2.7.0/.gitreview000066400000000000000000000001131321055627100147710ustar00rootroot00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack/pycadf.git pycadf-2.7.0/.testr.conf000066400000000000000000000003221321055627100150530ustar00rootroot00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list pycadf-2.7.0/CONTRIBUTING.rst000066400000000000000000000010611321055627100154270ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/pycadf pycadf-2.7.0/LICENSE000066400000000000000000000266521321055627100140100ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. --- License for python-keystoneclient versions prior to 2.1 --- All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pycadf-2.7.0/README.rst000066400000000000000000000024421321055627100144610ustar00rootroot00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/pycadf.svg :target: https://governance.openstack.org/reference/tags/index.html .. Change things from this point on ====== PyCADF ====== .. image:: https://img.shields.io/pypi/v/pycadf.svg :target: https://pypi.python.org/pypi/pycadf/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/pycadf.svg :target: https://pypi.python.org/pypi/pycadf/ :alt: Downloads This library provides an auditing data model based on the `Cloud Auditing Data Federation `_ specification, primarily for use by OpenStack. The goal is to establish strict expectations about what auditors can expect from audit notifications. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ .. _PyPi: https://pypi.python.org/pypi/pycadf .. _Online Documentation: https://docs.openstack.org/developer/pycadf/ .. _Launchpad project: https://launchpad.net/pycadf .. _Blueprints: https://blueprints.launchpad.net/pycadf .. _Bugs: https://bugs.launchpad.net/pycadf .. _Source: https://git.openstack.org/cgit/openstack/pycadf pycadf-2.7.0/doc/000077500000000000000000000000001321055627100135355ustar00rootroot00000000000000pycadf-2.7.0/doc/Makefile000066400000000000000000000151411321055627100151770ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " wadl to build a WADL file for api.openstack.org" clean: rm -rf $(BUILDDIR)/* html: check-dependencies $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: check-dependencies check-dependencies: @python -c 'import sphinxcontrib.autohttp.flask' >/dev/null 2>&1 || (echo "ERROR: Missing Sphinx dependencies. Run: pip install sphinxcontrib-httpdomain" && exit 1) wadl: $(SPHINXBUILD) -b docbook $(ALLSPHINXOPTS) $(BUILDDIR)/wadl @echo @echo "Build finished. The WADL pages are in $(BUILDDIR)/wadl." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyCADF.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyCADF.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pyCADF" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyCADF" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." pycadf-2.7.0/doc/ext/000077500000000000000000000000001321055627100143355ustar00rootroot00000000000000pycadf-2.7.0/doc/ext/__init__.py000066400000000000000000000000001321055627100164340ustar00rootroot00000000000000pycadf-2.7.0/doc/ext/apidoc.py000066400000000000000000000025611321055627100161520ustar00rootroot00000000000000# Copyright 2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os.path as path from sphinx import apidoc # NOTE(gordc): pbr will run Sphinx multiple times when it generates # documentation. Once for each builder. To run this extension we use the # 'builder-inited' hook that fires at the beginning of a Sphinx build. # We use ``run_already`` to make sure apidocs are only generated once # even if Sphinx is run multiple times. run_already = False def run_apidoc(app): global run_already if run_already: return run_already = True package_dir = path.abspath(path.join(app.srcdir, '..', '..', 'pycadf')) source_dir = path.join(app.srcdir, 'api') apidoc.main(['apidoc', package_dir, '-f', '-H', 'pyCADF Modules', '-o', source_dir]) def setup(app): app.connect('builder-inited', run_apidoc) pycadf-2.7.0/doc/source/000077500000000000000000000000001321055627100150355ustar00rootroot00000000000000pycadf-2.7.0/doc/source/audit_maps.rst000066400000000000000000000043641321055627100177240ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _audit_maps: ============ Audit maps ============ The pyCADF library maintains a set of audit mapping files for OpenStack services. Currently, pyCADF supplies the following audit mapping files: * `cinder_api_audit_map.conf`_ * `glance_api_audit_map.conf`_ * `neutron_api_audit_map.conf`_ * `nova_api_audit_map.conf`_ * `trove_api_audit_map.conf`_ * `heat_api_audit_map.conf`_ * `ironic_api_audit_map.conf`_ These files are hosted under the `etc/pycadf`_ directory of pyCADF. For more information on how to use these mapping files, refer to the `Audit middleware`_ section of the `keystonemiddleware`_ project. .. _Audit middleware: https://docs.openstack.org/keystonemiddleware/latest/audit.html .. _keystonemiddleware: https://docs.openstack.org/keystonemiddleware/latest/ .. _`etc/pycadf`: https://github.com/openstack/pycadf/tree/master/etc/pycadf .. _`cinder_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/cinder_api_audit_map.conf .. _`glance_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/glance_api_audit_map.conf .. _`neutron_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/neutron_api_audit_map.conf .. _`nova_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/nova_api_audit_map.conf .. _`trove_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/trove_api_audit_map.conf .. _`heat_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/heat_api_audit_map.conf .. _`ironic_api_audit_map.conf`: https://github.com/openstack/pycadf/blob/master/etc/pycadf/ironic_api_audit_map.conf pycadf-2.7.0/doc/source/conf.py000066400000000000000000000206021321055627100163340ustar00rootroot00000000000000# # pyCADF documentation build configuration file, created by # sphinx-quickstart on Sun Mar 16 22:32:24 2014. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # NOTE(gordc): path for Sphinx ext.apidoc sys.path.insert(0, os.path.abspath('..')) # This is required for ReadTheDocs.org, but isn't a bad idea anyway. os.environ['DJANGO_SETTINGS_MODULE'] = 'openstack_dashboard.settings' # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'openstackdocstheme', 'ext.apidoc' ] # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pyCADF' copyright = u'2014, OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme = 'default' html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "nosidebar": "false" } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pyCADFdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pyCADF.tex', u'pyCADF Documentation', u'OpenStack Foundation', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pycadf', u'pyCADF Documentation', [u'OpenStack Foundation'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'pyCADF', u'pyCADF Documentation', u'OpenStack Foundation', 'pyCADF', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/pycadf' bug_project = 'pycadf' bug_tag = '' pycadf-2.7.0/doc/source/event_concept.rst000066400000000000000000000225311321055627100204260ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _event_concept: ======= Events ======= The principal goal of this specification is to ensure that similar auditable events, such as a "logon" or "critical resource update" resolve to the same data format with prescriptive data types, entities, and properties to facilitate reporting, query, federation, and aggregation. Defining Events =============== The event model is intended to describe the interactions between resources that compose a cloud service. Conceptually, the event is based upon the perspective of a single RESOURCE called the OBSERVER that is responsible for observing the Actual Event and creating the (initial) CADF Event Record. .. figure:: ./images/observer_cadf.png :width: 100% :align: center :alt: Figure 1: Observer perspective of an Event At a minimum, an Event must include the following attributes to be CADF-compliant: eventType, observer, initiator, target, action, and outcome. CADF's event model is extensible so any additional attributes that may better help describe the event can be added to the event model as an additional attribute. .. note:: In some cases, the OBSERVER, INITIATOR, and TARGET could reference the same resource. The precise interpretation of these components, therefore, will depend somewhat on the type of event being recorded, and the specific activity and resources involved. Use Case Examples ================= 1. Auditing access to a controlled resource Scenario: A cloud provider has a software component that manages identity and access control that we will call an "identity management service". This service is required, by the provider's security policy, to log all user activities including "logon" attempts against any servers within the provider's infrastructure. .. figure:: ./images/audit_event.png :width: 100% :align: center :alt: Figure 2: Conceptually mapping values of an audit event ================= ========================== ========================================================================================== Event Attribute Value Reason ================= ========================== ========================================================================================== eventType activity OBSERVER is required to report any user security activity observer.typeURI service/security/identity Value from the CADF Resource Taxonomy most closely describes an "Identity Manager Service" initiator.typeURI data/security/account/user Value from the CADF Resource Taxonomy most closely describes a "user" action authenticate/logon Value from the CADF Action Taxonomy most closely describes a user "logon" action. target.typeURI service/compute/node Value from the CADF Resource Taxonomy most closely describes a target "server" outcome success Any valid CADF Outcome Taxonomy value that describes result of action measurement N/A A MEASUREMENT component is not required for "activity" type events. REASON N/A A REASON component is not required for "activity" type events. ================= ========================== ========================================================================================== Event serialisation (including some optional attributes for additional details): .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", "eventTime": "2014-02-27T19:29:30.855665+0000", "target": { "typeURI": "service/compute/node", // optional Endpoints to describe compute node, "addresses": [ { "url": "http://9.26.26.250:8774/v2/e7e2bcc9c0df4f3eabcd412ae62503f6", "name": "admin" }, { "url": "http://9.26.26.250:8774/v2/e7e2bcc9c0df4f3eabcd412ae62503f6", "name": "private" }, { "url": "http://9.26.26.250:8774/v2/e7e2bcc9c0df4f3eabcd412ae62503f6", "name": "public" } ], "id": "06747855d62547d4bfd707f75b8a1c54", "name": "nova" }, "observer": { "id": "target" // shortform to show Observer Resource is the same as Target, }, // tags use to query events on, "tags": [ "correlation_id?value=56cdde6f-6b4e-48a4-94e6-defb40522fb2" ], "eventType": "activity", "initiator": { "typeURI": "data/security/account/user", "name": "admin", // optional Credential to describe resource, "credential": { "token": "MIIQzgYJKoZIhvcNAQcCoIIQvzCCELsC xxxxxxxx zqvD9OPWZm7VQpYNK2EvrZi-mTvb5A==", "identity_status": "Confirmed" }, // optional Host to describe resource, "host": { "agent": "python-novaclient", "address": "9.26.26.250" }, "project_id": "e7e2bcc9c0df4f3eabcd412ae62503f6", "id": "68a3f50705a54f799ce94380fc02ed8a" }, // optional Reason for activity event, "reason": { "reasonCode": "200", "reasonType": "HTTP" }, // list of Resources which edited event, "reporterchain": [ { "reporterTime": "2014-02-27T19:29:31.043902+0000", "role": "modifier", "reporter": { "id": "target" } } ], "action": "authenticate/logon", "outcome": "success", "id": "0a196053-95de-48f8-9890-4527b25b5007", // Event model is extensible so additional attributes may be added to describe model, "requestPath": "/v2/e7e2bcc9c0df4f3eabcd412ae62503f6/os-certificates" } 2. Periodic monitoring resource status Scenario: A cloud provider has software monitoring agents(Ceilometer) installed on every server(Nova) that it makes available as an IaaS resource to its customers. These agents are required to provide periodic informational status of each server's CPU utilisation along with metric data to their operations management software by using the CADF Event Record format. .. figure:: ./images/monitor_event.png :width: 100% :align: center :alt: Figure 3: Conceptually mapping values of an monitor event ================= ====================== ========================================================================================== Event Attribute Value Reason ================= ====================== ========================================================================================== eventType monitor OBSERVER is required to monitor a server's CPU utilization observer.typeURI service/oss/monitoring Value from the CADF Resource Taxonomy most closely describes a "software monitoring agent" initiator.typeURI service/oss/monitoring OBSERVER is also the INITIATOR of this monitoring event action monitor Value from the CADF Action Taxonomy target.typeURI service/compute/cpu Value from the CADF Resource Taxonomy most closely describes a server’s "cpu" outcome success OBSERVER successfully obtained and reported a CPU utilization measurement measurement 80% MEASUREMENT component is required and the observed value is 80% CPU utilisation reason N/A REASON component is not required for "monitor" type events. ================= ====================== ========================================================================================== Event serialisation: .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", "eventTime": "2014-02-27T19:29:30.855665+0000", "target": { "typeURI": "service/compute/cpu", "id": "06747855d62547d4bfd707f75b8a1c54", "name": "instance" }, "observer": { "id": "initiator" }, "eventType": "monitor", "initiator": { "typeURI": "service/oss/monitoring", "name": "ceilometer-pollster", "id": "68a3f50705a54f799ce94380fc02ed8a" }, "measurement": [ { "result": "80", "metric": { "metricId": "", "unit": "%", "name": "CPU utilisation metric" } } ], "action": "monitor", "outcome": "success", "id": "0a196053-95de-48f8-9890-4527b25b5007" } .. note:: Additional use cases can be found in the Full CADF specification. pycadf-2.7.0/doc/source/history.rst000066400000000000000000000000351321055627100172660ustar00rootroot00000000000000.. include:: ../../ChangeLog pycadf-2.7.0/doc/source/images/000077500000000000000000000000001321055627100163025ustar00rootroot00000000000000pycadf-2.7.0/doc/source/images/audit_event.png000066400000000000000000001074311321055627100213250ustar00rootroot00000000000000PNG  IHDR~A4sRGBgAMA a pHYsodIDATx^r#K iddf,djnhl[05%8s}ƟQ"&aѠ'sem$YKeاNVοK)1f-߇)IiNɹ#e{b'.PJ\OR*HBS1ƌv u /Ac1ƌ Fc1f,w+c5cȿonc#rk5ck1LcK13)^cZɢ-7Ƙ6&^czsss}}eiK1t[1 b5 tp}}}eݖ4cLSXziy%K՘F,[Wo8טvql6טFsJ)oQ݌?טs -<<-ƘFsЬ<_cK1b|_^^JcL Xz9\8"|5-,(uࠃ/˸46^c,k q;טs,9RYε+ 냯1 BzWY<>>H,g+k-߿~lL$ט[fU^؀5 Ugck̡U ͕+p'גsm5PrqVo2V1B\b1IV^K14To9,ƴD(+K՘F7F 4^cZckLKXz9,ƴ1c51S/UckL3Oƴטx.^cZckLKXz9,ƴ &^cZ (^sl0___N&VؠmX缽箌Xcň1n%gZzͧe~~l6ӯO|۷op ى';\(mX1$ȯU9|D5b l.72%<== mL|&gr.(@k(RJ3K0 +xd'" xK<5HܯeȓMԗW7Zzͧr| 7Trj dfsuh "*RD<}ְ J4Hn;9hL!8PfŇ +ƘqIk5=?Kv2)TsGDLJTў 3UԷKႢ%{=B)y5U E4OE$^Y zS+GIOgDJ3(]J`-k.(NE[2lT\;'-mئ.`]APu]8k$abW9[d S2OG8(Jb)8Bzh''㉃v'*(7PL'9X '^3AuJՒXCmPQP\/dZzͤQn$<\F ;&{.j$le7ed4U2 *,jJ!Qmșz~C5 Y uŐ^Trz 5 ͐|Q) +31EԎO12iSςɇ#Tč]W'Z.0P1z(ApKdHzxn.dF%UeTJ- Z-{:1GE=%Y;)R8}D4bH! K_EtK_Uގ$Ì3ebXhF h.L${({4YGZ٣.>w~qszõV8qn@/(SAp#v62Fp D.Ԉ$bENTG-GQ0>@:F iR5a:sQ3H%;M&Etl_4^Z ę+-T*n9(Xkm -M6te6/kw}%l䕌qt3 P)S8>Q e ЩH- @JAIB #zP/ 8 /;H)$bԢq^%/A'W*E@%ƿZXK]MMBSם҆˪TCSqP"2RQ(`+T"~$O͑Gv|R7Q9:\J^Tr1\Hj-r zLP_j21 qx4aYoCX띊mj5"k'uX&%PpOˍ1s1-42Sn1 b51Xq[yh68^ckLXziK1-b51X1Mc51cLXzi߿^cSoЄ?5E,4r+jLXziK1b51^__kV^ckLckVƴטư:^cV߷2ƴט`g4טV(^cp(^c_2u,4s|"^s$0o_O?>==qUo_7`5;'£nrcFKiN~Hܟ |D p1foXzMۜ:.^3 ^0§qyyyEc^OOOLrȯk59f5^3 c^]OT3:^3 cYy-]lL春״g6kAsk]o,f4,Xz4hYzAK1K9\f2,fYXV^K9Kt9k>\,fYXV^KTӱi@gcYy-Kw57l$yKm&|>}yy)Qkm9J1^35.;&{l6;??zxxؒabp}}}sss`e6Mw/+,hIiVn{ơ$KRL sk0^3)]HPt *[hImdI[Z,KBŷo߆&4}Ja_"9kg.\gggX9 &7JBWJ@sH/C. rMZ(&{Td4JI/UPzDG,@gcYy-fZF]w `P,{h%7F(Ip6/_(DU|((DuĐ:$^ ж/-IiȇkW; PJ/,_JS PQ|^jC#aH?TIITԽBu>ͱ-fBpWDA}+—TQGulrX1#x$!~%=H)S{p! 4+ f &ձLHo@m1i T`Uck p{{K Bp] k ͱ$^3c.Y FR NzY܆~V=:~Y Dƴ܈q%HwKK" |(GadX1{Tʤ* %}LfTjG$"iJSݝ ʧ :^p3z^sLhkˈ{z=bCXI/Y/[JLrRԄ֢FQrv*AŨe%tz;]-xnAVZ(j-69br,@vmR˿ծN!iH۹6IzCIF:ZHȒ#Nze b P$%f9L{ט^/]QA Eم4εNzSy~CxS4{]q =%V G*B/qUzKFJ<@ ySJhfA-GGP}"¶rXK .Yq@E/[ZUxtj(s6yU#Ij$Xz1aqXzOwU=llTIT CzGJ I$HAEȋCP/'BI*+tvBsez d^ # C.P-1hKV鎒pv%J s Q/F!`5+9UK ,fLq*ll衔 KjbG;@rPV^LCՉavqs5^bFZn*ET-\ZDjd }E{ jU`5 95V׌ƾϻ5l7 @3QLW؂O\ Fp8Jx\>埰bB0T8A|]i@$I^*.S?1Ju3 |[8W북j *>NC]UTGk 9c5 }S&I{ꖛ6ԡ\b2nkOLKtkƇYđptטmi`Atʍ1`5ӀZzXzMXz4^3>^"^3 謥׌״L:k5c5-b5ӀZzWTː<6~+Mձ^^__%_9DMBR~ 5f1]믄?r#C ^3 pX^KɤQ|^ӋqqܧT(DTZlmX u=$n )6+2*L~ڂoYRdF,1$L ,-f,f|^,3IiM`ZЋцދ並[/U nsw>f,ڐ.K@]K͆!8j(8,R5nS WDP,ANM=UKawVYΊB-F@+3r%,+ VA^3 ,X^KWiIV;i WJxD<2@uejWbW9  敨SM!naԭPLT[kr!Qg@zՌ7kE ^3/D|teCrzNuDf!![8U"ż$I:# KOzՀ5 GW {$Յ-Hn2YUߩF7+%0%@)MY䏈r.3U"kL*i`E0ZaߔjST֌HHj/ =-S阘HJ@P][ )#&吀@D]0p Qɸp'1:*ewμSTFJ-~[^+&C4&U7)? GOݗOsLBFemLZREFJ[rؿ5Zz4Lc\A1^3K/6$ lŧ-v&9~H]ZH8HrTfJ۫k/ע! ?~HMD1^X^p%hrHHհ L(D3$Q^ANUek)cW]i`90Ǹb,fެCb72 ǦO Lxy* AeT$MGa9 W7섩P1DN@ԸBpXjuM.ʭ!Ҁ(&ZEX8 d*!;E{z@ 1zjk ^z93aRAZwmeX!HªWIn01Iղ-br7uw^ J|XTT+*FJ//2ry@SU8IoJ4ɍ%dxPLs9c5#0Jޢ$dҕ6TBdSFcI/\o*M P< ùHUmy*gēj]F2)<HmFRt:>^3.0ҫk52)PL9c5#0w5;"@iUAyNfby@D|F"!W3R Xa`ZO{XFAUNI#?IM.jJMrCB6HoL3rhL9kFdzTF#U)u $ !"_hGpRҫPX,\{ˊ´ru]:"r[BbM"rR9 ()Q}J8*n)0KPi \מ@14p-E\UWH/>ZqSuς-fX1K׉=IR,uI;{O$I^[Ktkkyk5ӀZzXzMs0o-fYKKi步L:k5c5i@g-f|,9^3 l_z=DZ`Zz4n,9^3 謥׌4k`Zz4^3>^[Ktk}zzfL+^3,s,+׌H߶4H(r}i5" lFDŠ4c^?YXV^K(e0{yyw^c5#e>b⢻Ě`5 f5 .{uuly0EYK1m#tW_VoI3ƴ:k5ȋ1ט@g-4@}}5EYK1N+$>謥טCgxטAgW+_cZk̡++טVsl8 I4טfW_ckWkL[Xz9\Ȼ?5,(q-1777%@RkLXz9PtE_3///,W+>>>h3WK~c̡b5a)VXz i/acY-^Xz98PX@1\9dZlc5`I֫cs5NcZz9,&|wwWnV!(ƘkLc)ƘkLc ֫՘UK5֭1!X^c$yZiK1EW1-UkL Gʍ1A,4טֱ^cZk̾`Qt>7ZiVטpwwXi}ˋ(7Ƙπ}kfٙNj X(Y5 ZussS1KkpuE}NϟCib5f/pqcbj1fqB>==|{{RRYpU%IzIED///IϜ5AT́O!@ H Բyy.U1f$!^cFH1:O'cORQb\ک[t}F$IB}\-P)=;;Ç[PƘ} 3Zq+/0('J D!u bWT#VH`̾aUZzJ$ uA9kES2W({.H#j/8rfJCI8 >OkטA4\'DP9:dM*.ǷC,Z7~k{R3H$GaD%OX^cFEmnY%-_eV5DKZD1pe[,\u31{kc$gE`ݢPJtT%-w--)}fQo_F\˽1foXz9\J#gggz!>% V){zX:]ЪzgK123& a,*Q5PߓBI@M[ɮφf`};,ʋF&!GSCUWBM*Y^37e S8gugbc̈9Xz-}!qddL@-B0@o$0Y[FmP.N9s HP1g5fLXA^(2s'j 2ɭΦXTV PNs*Kz^uH/|c&k/L~%cZkaOv[9ZziK1eTiK1-cLsXziK1b5%kV^ckLKMc5Sc5#pdҋt!H`Jl*%I9!R,<$ ].:HRÁFha8ޢ _]]:N^hb JlzM ȶRhKʹ$^3"G& %6= PWCzjΑw٬FbP͐E0wwwL5& H5Lu6ж(B$DUB  b"Cq0}ͱqLfZ^K#пuI௿&^[n,3Nԧ!{qX):jZ׫s0IZzm-f!1g]{M貺SK/Kc8,fJ4Ӹb,fNYz6bBzX]PYM8䭝C&QP<%ݨ"sVu}ѻrK;I%^3%i\A1^3G&pІ2E gX]3B*uZ{͋f)hیБ=rt>2,fJ4ӒZz͈"?,[1֛ҫ{GCQ/$AVu[RuU$K,fJ4ӲZzxXzM[Xz͔he)Lk5a5ma5SkkkD3-+׌״LfZV^KKi Kʹ^3^^3%iYy-f<,-,fJ4ӲZzxƌ2`IkE3-+׌״LfZV^KKiKʹ^3^ ^3=iYy-fhi_vp{s6J*+( %+PLfZV^K-O;=CELQ*UO) #0}bmPwꦜ4>*GR8nq ERnSK\j9'{$-fJ4Ӹb,f^ܘ|yuon#))ŒXB "]:^փs)P <>*}R 'LUDW6T|`FK4K-7OL>vI6YH-*ȕ)>DjA<7˕>N /P7%V6#E.K+#Aqu$.Y/ʩv(*dQB <)F jJMh9`5fWP׌ҫ[45s$zj׌UEհޜޚsJJ™:E6bu7yS렪KҫV#z!(IoJ^} Ioz_٢ rRL Rz3a I+3YwS꼤S렩$pQ,HEU3I-zV3V՛RR WLI[dgn(J^,Q**$G%Û|[j\BT`]ZK!yl5,F(QZ~ߞħay{ER.*=JbKd4Z&2vI*|HK$#/KOk&D3+(kF`?l `taI;#>RC嬖*(0}@\(Lt "۾s'i*;؅Q$7"YÔ,g4[xrzȘ NԮrPQG(\guP-$GL i| hs笔',=\#CZۍ^0)~Ih5ӢV׌[?Qr);r)֧}˃X%h[ZǻgY;ƥBOUf@]W/5׼kD3+(kF} edgkڔ6b)1H1ک쟋FSA bOGQhά$JԂ܅T✺VzUgFKCLP2j4V8*OLuK^孝{w^Xz͔hqXz[zLJvX6Sژd@+tmxbI~꿟N&8-YFɪ.,K?r"rqHٓdn( RZ dJwau*ISiw+>(*"llN6VWvsJc}9Y%$ Hr])^jIzSƴ'(ڐF+>˭*%-䖰􃝝E$%Z>OP%x\?"DnCra+ּUp%&/Ft]c42]ݪff@V]v,fJ4Ӹb,f'KLm@$ |tXIrfSo$-.kÕbB'*O(Yh!aIR. 1]O3j?1;Hs>[ZSAB9΢]~"Wڦ^Ӆ22;N9A.K4KIsmb >RTUMad'KGN2U2 M*P)Js*s~ԩT M,ʪFuQTIt:Fưd~jmPrHc՗T^rSVenI 22Xz͔hqXz[z\;2)˩S ]n,/7&N%W/W٠%D& >tC^LʛM,[-z@fp97cJE$)\^:t`TLfWP׌ǥ gYvҭ֞TޑiNXzh%2<+MK(珄3k&F3- ׌7oki׏wk&F3- ׌4LfZV^KKiKʹ^3^^31iYy-f<,!,fb4ӲZzxXzMCXzheLk5a5 a5kkk&F3-+׌4LfZV^KI/;Zٌ9h,fb4ӲZzxkkD3-+׌ϓlM5KƖ^3^~r8LƲiHw{7^gOIf>А@o44c5ct$33Sn̨x4FbV^K(3 -ڒ`F%O[ckL3?W۷a1t3,!Y,CD[ƘӄkGapҕS˗/tߟ?Ĝz3Ɯ ^c>=˵w>>$%Qb 87(" Q1fXS^c>ɑW}H/FRAQ&߹?tzV>0aM!$"Q\iRd,d'LuԂ? 4S$ֱ;cF5e5C Z(oN)-""{l#Es%gχcrW-[Ch3Ww(K]1ftX^c+G("i\K/>(>sEQFI%n(hԫu.+J I_N)+cXZ-ƼwABYuxuNQJЛ@|Id|jMK![C%q3r@ҸqI<ׯ1cb~6H6.rd Qάl6#;pTed$@R]oЩZ4k ֵטFGا ̜,y$+bAGKN4`qEQh>v=~٘ai[z777hj1'E.kY9/jXY@ 넳dWN*+zqqGBzf1P,P$H1պMkn*@rq&PykVr`Y+)Hy $5A jh.Ix3 ^c> { DɐaHx7a^>Vq3+\a6Ihgf Zxfc+khuL0WXb^c1f:,c̤Xz1ƘIUPc%l5LRXOOO?wxC0f\XS^c$1-$R_Xz9\<kI0ƴo-.F~z^ƴ+kaa Vulh9X^c[_᤻RzY3^>SK1{sss}}}Z\%+Ush-kQ+kAY?(TC ...3/2PK1˒,ʺ h{ 1 SK1_ʺNyY(no=cVhoZz9X:]RCw}5AdZCm-o|Iȫe,~9pXj5`qn8*G^cՂ sXl8S^ak:-XK1f(ט@dfCm-||n}5EX5k5`>טvAdlCm-"O|)1j-[K14kL Z^c|5 L /u ^h!  /[1 u*`9Xnc9pXHg̉ZטwSD mHZF[ɦf;M{xx*Zٶ4VK-uOArvҫ6cd:+ԯS y4B+j -x,xߴ:{c[^xBTƬ&!_l6>+R}||@fZSu'Tt{cgl6ۻ-o`NwhtG4밤z Gl6A' Ut@Kdl6m~3C^ȫf&3'/G?$v!n|w0(6g~yjkO򷮊, Uq(s)6&K OAf{e[TH/]km6./R#6$IڴTK/ufm5p-Qdi/X_?0IFG%V6ym `h_ЪEkf7|+K/MGޗ(l6Lv2!+WStj\TK/]՞͖Z/CC/fr;IarLU`lC˺Rfhդc?>bz!$RYDe*fj^`lS6V>}}}eh(*_o$ E,,Vc2?_zRčůϜHx3?mz+ i)׺|b,6gZik$MXh6{Gd idMi c>ͤΕ?N6).e*lk-7Q jDT`K9;]5-[fvJzY;,FBlcR~1C dI;Znٶ5Kv" :'uW6nv"ҫNĖ1#>TEj64KvĖ˿u`fa^Mη_nx~jl7kǬD?~X}mfَк_xǪYtif0KvL1M2 撾xgkWRqD+mYzm1}J ; 77lYzm0/Yi\YdǎzmN,6[/f %5xkmݗb ~md^a~JP^l^UKUC(mr/IIfk8;2YWX/^/$k5fmi`S`avfZKU{%3TDwݴَ۲Sҫ7:)k;/U=5qR+əfk7pNxe;-lo_t:IJL/^N,6AKy?vXIOHf;2lk/DNFOm. k/3s,x*~*lGn^M_:2I^sX}mGk^ AwG-Iw=vtfʘLW̔ &hk϶c3Kv(̬/UeM( 4M_{Yzm|΂X#$wD^dl 뗑!1U;9j h҈)>l:ۡ1m[6[fȤ+,߄u-I/;"JKv?h6[skf$6`](ai}olkmѻJo:^DkmKoR{,{0XzmnJgYzmlGō5?g1THf@0^۩fkv^|X(+Eeٓl~U$|Kkm[Ko?noo|~*C0lrK-:`鵝YzmmeM_^^*L*dTǏ^۩fkv^9d8NVJ/ǻ8^۩۴ҫ0KkdH/+CzB$%@| ;'͟FvY )j,f"\ -?DKkm;J/Eh{QulNKkmH/:||8??f:W[J#<~a6p"f,S7Kֶ(UxP~ЊŬ#olv0Kֶm-8/..o߾-iU+>E3Wq~KkmH/Pv#+28R)b>8vfڶ]~}^۩fkv< daMJ(>v#L7?vfڶEf_~xSAQ^w6fڶNpNZ[]/7-OkV6۶fڶfu~~Ğs}Yp:nz+ "m[lmێҫo8]!Im[lmێyE AO!h0KZl.}Kkm;z/_.$oC-nvfڶ]>:ͤCPp(O+^۩fkvֿfŖށON,6[۶׬XI?0u` Rvr!Nl鵝YzmmGe-IzmkmH/j礫ׯ\///CwfD| KkmK`ͣҞOpr_eXzmn^m g}ī[긿G =XH/*[+,6fڶ˗/׺[@Z.OͶYzmmkŁ~qqʢ|O/9+~,Ik,SO^VXzmiH/P~Jl{]l^eԫ,e+ȇ"aI reLGKfZ? {uuu}}Rr–~Q3T`,6[˶8Jkk΢렮XJKWIR KfZ]r;8/Ag%$r_[HokYzmmG8kPR u[]ͶYzmmG}xx9suIKfZTq}}|ϰ8GA0>5fA$I mkl-ҫ:||9"nW^qFUEM8fl-vҋI\7p~~FfXԨUe{?KbYzmm;}yy9;;TVV WA0[ׯRz.',6fkٶځ~NTkYzmm;p{{ۣBe>X]o/6ɘfkٶ^($[zm-klzfD^__1>n9үK7l^eQzKޏ3o rF[Xzm6Kֲ~͊nkyCZob,6[z"P_%jG~kmm^m_ gSWy7;p#6阥fkۘ;Io e|U:_ gtw#/Xzm6Kֶ$WN@}QDbц-HXzm6fkض^9l|>۾l^m{wwwX\A vD1`,6[۶ {u+oFʇ`fOl%yKfڶ-____^^j A3zҧ#ÊKfڶmW+Rz ||>#8D 8W_ k!߀flmۛK|j2*BšS/r媤$'jf,6fk۶^|onnnooQu׽\9 MBz{NǦ^ҳlf% qEtXzm6Kֶm#,ޕp fz+L]%Xzm6Kֶ)G$6l%A3kg^mFz`!{fe*^ #;E^km[Jm%%9SflmNһ W,6fk,6[sf6K֜Yzmk5g^ml͙fk,6[sf6K֜Yzmk5g^ml͙fk,6[sf}g;M̈́5j2K&ctrFsr<>>{03\+vfd65sj{`3ʿEleMf=Yx?~͇4Ko(ygL6fdlӄ3hTaYz7dk H+1,{6!wYzO$0/̧/zN4{aYz7dIkڞ*^:~Zmt{aYz7dṃwL,l5/mgN tһDV^K^XzҞ6Pi9x%,f'S2R?Bř|MYz7dS^`1N,@/Gz{2Ӂg]kv"+o3ҋ[TͽFC++}[F4`c5;ѐb,c?~`c^zt8n}K&ji#Pނl6/777Lu$mGzϒ^ݷZzO&w#^Z%esxAɮ!noow*yg~热 c_>iK, һđI/LID݋;F[WJ@Dr2fBsJRD^Ko|Ǿӄg]n*jm1QB\):%u\kmޔ^3ǾÜf0$qK/D///y OOsJ8^œUAY777t(o߾I-z{陒9+;lgxO.Zg=j 1TaJ+iH4Ƨ##^1"ykEƘƓ~5&x쏌*YG4-OsɜL<=WKomLiD &"m@p`P8br!ijg Qׂ|:2~uƊf9F&njh@kqf][?"d4l :$BаX0a+_6 Q@ 3wHJ$%J/)& P_~M$%tt56͏4[z82U'ytٓ1(1!#,0&GFtQiq˴bq|Lx@*Ed45DxwwG!g:wD>, Pl69QǬt8@h$|W| .//Kh6G?1<M8,<}Sg#[:[q?˕m% #$P8$rB ԗ]2-!0W~rIr}}͌eKT#-KXzNE2u= R[%+;>jcr_;s.z*/HrT"#* Eerܨ*OZ_Api0QL%41Tm`5;sfd; 4c CUMM v^msJK踬C 䖢&J3nTW RCZ1b[ƴZh DuAy`]xGI(e! w Dq ;ERWw-KXzN$m gZKureg^$ѫ27D\*e;` {Nְ$U.-vUTPL7QpȤaIiWUVa<-zaKTdpz(%& S_zނXLhRzY\lܲd5u26ǁJ[GBV֩ҫ֠;dH@EuvLK6DjQ*LR8ˣxsaNO8R{^%C-KEf=Y&Ӱؿ݄3Lv+g`jȪ}Sž`r 8LeVd ?~a{ ҫۋ & 1Rb[r˲R*jp׈hZn]Cқ` 4Cxlr~*-B`L}HFY/Mei;[Ē~VhoJ/Cs/5uF!-J/T@c`UswaY@I9CSNM3J-p q(SUXzG£|wp/|BJ2$fGFxTffiH@chR @̺%{l T7;qQsY%2Jbzao^T]ZfrLcC8W*缫b)D{>ȭ; Ӽӕ^4K +r CV7:YjkkWKIې^)^uAɱi *z3-;%fE RME%ZzsgzCkI/*Fmn`ӌ]b2d)W10(3rܪƇBwOszXz6R "W^Ɗ:%Cܯ8+4JI:`PBIȠĩaH4W" *OY=R#O.40q$m^D&JBN"xPG0$xn噞E]S6eQ"uO Ĩ HMZiAi,ޡ"_M`Ia)A_tTS2] ̫#;l8;]B$ZaIBz%YH"S޷n'QO)ޒ;FQIqx@G44R ne+U矑 5TK@9n/vJVvhSr^Cp-R+'8Pl }~~٢ ebN wÔ^P9TD҂DfH|MzVq S "2^ɿ%[ʔJiY/mR]H:BKb@\}.2䱠4IōwH/1KO?Lj܎ؒie.07*mcõ&^&@̄u^g]kdbQ}Mp@N[EcCiܴÁ0I\u[INCFn1WrĥTE%UyI^[Brːۯ+yidK\XQT-$P  [!DuPڒ-Xў A-l;2B/$Z9c=Mx)>_>dwe*(Q%!3l[]*gɩsSxD(3+,4IϴS$wpt7FP0JKm^tv^Eޥ6婠NL%aw$YvmH/.qkٜ֡%'yGA^< kZ)]ήqؾ%a44\u=e:f玤/R]Nd@#nxL?ev":۰oDAr1 ,K|Pzg$aocدS̨-J?.L&}}b3 Ep8S%CjrȊU>;P%*hs܈%UpN_#![E֯WjPr:;>W^ ^ mˁwa*.ٻWY4 r>4:̜a90V3IMBED,<Ď!%Xz6 k.03?"l%"ɞvސ:u}RC%QWV2 5dI#YPZJCDd (2uvf)RʬGF:ґ,PBβ_)I Z=J+.4U>"z}F.H^Χ@;گWb7,"+!v|(IK^İSYF((#/<&>dInd'ſ2OCLdp4VɓUGO!WH$ t-UN^s6 ܈9č0{څ1qszgKBnvQ Y{ PwuG=F&Ko2K3S_mlj7TY j^;.@QOtM$!>X6Dș:+q!'FQ`KYrp&C.5rTu:ge,OӲ^"cJ~:+M 62,z@DI)UX%ڔ)RCs/Z8JU4=.ITn MՕkA%%'R)Aɡfn笨K-s1QQԕؠoXrӆ\af3&:ӤX}g(-+; _GnYuhPN“!1*VS9(T 2LnQsKtKI05U(EP Sj jzab˓;潢>U*zKěcha5=nLK=L+=Wzo!s^9jI2^_aJ{KzSf*N(0%uP/>jIyzD/LM]~i: WiJ%>1);KJSc7L^3fR`Pe\5ԴT|"y=IyjWm&&F%֞[`]dƔ^HLmtdQ%ݕ5jXH) PȈP` 0mYW{6wVHe-7-yJ{Ui[RhU%VӽjW;I]Hd$\b*6zk[k]]#Fȏ@!Tj]@]i4|J  yȞj*-0p۴6Rn7͞|KFvKEuEN35BXQ*nr-s -$LQR)d_/jRsۈLʑ3|LsMu({0EwJsF^X2hb2-R_WRŠZSv9-I]u_*+ .Gk7Ԓ2RIrSJ#"H6|_/ ԍ8èZzPAIoIB φ<~e$2w$%yyddaYOdLtf M٫^vN=¥:j(U1|K\c 83-kaBQ~=^{:0g!iOg$Hҝksx1)0cHMnyP%k ,&"OXRf#UUu8뤷*_'yCzsyȧN"krUhPQ-,I9 x{ MHieJN-Luaiy|aY6ۜUQ@&ӋI5+KJ?cahrx&M{n)eH}3+XեIzb$XHonאLT<#%Hkd]-%PԢa^O󿎐tkYzӺ[=W)U斨7Wٙ.+cJo *AUZbT,JpvCE,f?0{G]]MϻC.οisK)#H"%[6b\it+ǧ.a1 K8D+YNWJ3bZ-jeKW*IcjmJiFJ*kH]LԪSÐ޼VC֡uWhdA.02J4v׼7һGz a˖IXX4$!<8$.;n*SWJqx̑JFRtZR/nUKm΅2s.3RU{9)H%)& `4jL٘iA/](5(BJlmC^FkÆX߃ (rY,"/Sʜ]{- kimԶ],غYzOwuYz03-ڛ*l_YzOwuYz03-fEoGuf=Mxe5̴6j~'&S.f=Mxe5̴6jCe'HM, һ,f{F'nl{4.\\\|\__R{Fl0KͶGCo$Z簫b^^m' ϕtxN<6Sn ^ۉZ[d'׏۷D&;An/xZض)kB)ѡ8fJceb(??Ҍ߾ûVֈ齺ꥎeڹvk#&{)caIKͶCe#\aذs$/Wrə+(6%4|FH(!TG N5۸օы-╋:tPU"hPZ g^m/ƌӞj~YN ČL5Q[MjkjKoݞ|RB1aGn'@,Q]݆2m}fۋ-nkSLB6D}%tbTHN!{뤷M GrkI/r['EwE# #Y^MGol}+kl\6Y-9dVF7(|jޔ3JZ\|%K] TWxr&h _UK H'JBGJT4ڟ012fϘ^mdc}=5P`*UpJbLΒ(Etm/Cχ:RrdȰg\|"BJfe̖֘^md )-9!,A!"5VfE\ixF0[ka]Q|*Lb 12$UM-j[1H[ 64ƜlcZ@ R<)mv6R/I4*R)VAE2U=g(5 W%zrpQ$XzmSskiq8^|Pz7wVd/T6jBwD4kkŘ^mL m1`_ rXճ7I$IX8طRW|XK%al鵵bɤ>MzlX#V Q|CcӌBύzϕԓZendeI/)j>,V9iFZ[XJ hO _2"Q$rJGO ygN{Ul2F#KވmIK6 Qk ("$A ɩ8pUhvHO&Q\aK/-4/Pj*kQЇLJ|0JLq~荧Ͷ?cN^VĆw\(Q§V k^w@KBxBzwCغ7w(5SgEO2e4ueD]&u9uzkl61'OEz{kfBnxВ}N4:FJrVʺ"Ce!aN Qu-m١[CCPqz7xSKa4 `aߓz1$QhɘxH/x tkXQ^|­7-%z>TĸJ@n/ϡs]jE\i+ k㱈_׆ l{5f#^:EelCSgKfoT Q3LJᰤ+Fll M!4 Nzia6f{Iqo4eAh).H쟤ab?jl6 y~~ЂD$ErupK{&reԐa)flMv R 1Eo&A&M 1 ڀ̄rFp7[\cREW&2 Hocّ"m(ۜc1-b5c&k1LcK13)^c1fR,c̤Xz1ƘIc1b5c&k1LcK13)^c1fR,c̤-b1EycV YHc1f,7 ly=Ly}"tQ%=⳯l"}t,uޚ\P۾1+) sP?JBIENDB`pycadf-2.7.0/doc/source/images/middleware.png000066400000000000000000001371461321055627100211410ustar00rootroot00000000000000PNG  IHDRz6csRGBgAMA a pHYsodIDATx^ ]uVץ*}]]iKn:D6Nb:$81+ 16؀mB @(B$z=8z $a,3z~ծ5kZk9^{ PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgH7@!ut PgK7>ARnm6ɖF^n]b:AR2k׮ŋ[&LC%u'ZY#z4(Žy%$'s7.[7LZ H7ԇdK|b:ARH;Ȟx XO9)uۧMU fMCbjիb2L1oM=TdiW4JPF] f")lI"̧ k ԅybLIulHB#drZaWNDxH.fIEV7*oZrʢ<SqZ!Ea%dS8'[ik3fV,W<:iuՊϙHʅ$'‰b>P8V + GYlEaj A#%ްl;{9t6 4! ln0V&65)nz¬mú6 0Ä9DaVoAa _bLZ־NVLي")jb4H@O!3/`s11Ix!0 ؒYI\fIT8hm9}62,.1S$1 p#VV oV'M0[Y2ts&H)nxД>`-!,ppU }M'CÊ-V Oغ9'Y-zb:bEOX‰bA.08@b* ;)`xfI%)"SZ"1lIO8XRhfVUrʱZC׆Bb]$w4IuCLz9)iTS[71^~?XTIx5~`$ 0S XURș'u_qVICyNF{.M*jRRH8+&C ЧFDRƒ W)-N6$'Bj0#L 6i\@R"$\K)iTS4i›{R)rH%u9SprV,L\/^VrI]twCZ%`EK;'YĊ嫈;,(r]J b`ŀb"-w7T^K$1Iu`p fro?Y[lI'5҄U\BW ۸}ei}Ūpo 𦖔+m"qJi@4,xٝ&¦")ub͙&ݟvSujERNYaVPdhx}>[BbKIDqH?H 7Ҹ(!$dYDR.aI t˜p0Kdf,"\<"\Fb6{oY q"jER 1Yn`")WDEb' Iy`"[aA23o%mnb 3`EaEOXf0$E+Ƴ-Z"yJt‰ A$3tC}H WQ8'Ah6$YDR.aI t="04I7XrE:@- "1h-)(jby[*eay$N'B sdlGZx+5iaݤ*0Kj-pR,夺4j',KR?\-vDb\u冫Hd:ȘTXfr%)"Sdtl\LxrZ]!7_əh[!2Պ̢&D:10?t#S!l4uF̖8EnjG{d+2 .бXؙt2 "|Po[ #˗1"Ϟj%WiO\+gC;ڞ+/uo:㻷jj\MZm\j+6qqmqwq]UVkϥ=QKP)Λ#uτZw"sP\mϕBj]>'-qyڪWSqjj׾ڊkGC\[x]xpWոs'֮Mc?x[2ƚvyצ%n,czSiKzyC79sI;7m%n;yj()ɰ;{g[ {ړaO˹IԾÞ.9Sg^zڬ エWg{`ϑy<"91{fm~.Jgǵrvj0S}պX9fo-n\Ք-n\ZƵ/b7^p7>U5j\je -R4q)Ju>Q/}ȑZ}q7ͱ&.Ҋl!Bv^2۾~޲9=G_M>aYyUjno_\W9Q=B/}ܠyu+fH48. դP3wǺSn)qUg}#B[{'<JuH7p)N0H7L|Qt|~B׊rzdYB* bE; EChfy B!V<}/'YH&P!TJ@l ="րB {me.]8@1BjtBjt/1gj:Ba/XL!TJ@l{ˏ糗^f!'Xܼ*gB-疤OGϻ}M*Nk.]7w>Ҙ5wy BɻGL۾d]uxv"t{<ڋwRI=kWCC'(Zkq!fق۝'B!qO16@BL:]nX{gV ECnpH-nHҡ;$4Abߵ-+$YpB2:V:ھdfY TH7 T-pƼh䣥kˤ-3In40!qYD8[&Tخj'B!TA/o@P"݀PjtÃ/l1fM8i?h%Y,,op0i_#!B|A A<#\#KmEP2?X#{RO7$';خ}TOdԫ3}xXUˡ!PkCR TH7 T)Sg}kĔZ/-dm]87Ǭ^|Wpq%Dڎ8ΔuO"Í`jA*`,!/`bܥ${:h|g2Mt2 jqQ#\tԯSLIz"o;v MfQU vc^]͝O)h_|-!|x؆DVߊm"y:{Z[V.P?]rRR9X1V@ s jeywػ5.@LjYa5  DKonW2YHCQ5\fA-cB'᪤w UҗP!g=?ObQk|-L>bE;:+ >r 鹹_ݕ9WQtt#EUvrի6ݠxCVCn2WL%mN=qUR0jәTA9g)yMfEYԲ!s1Zrf'cٶJXTtC/EWYL6ci/dDŐnъO7jGvnb ;K2Bgeo@VU'~j?エM2ZVIzr~;`~ ?H5\R. ]!ّKD_gٙ4cȕ-ęXV+T0P5Buڬ #\Pgux5s )'`QvC*&,Z8 r`T! yڞZDHr ]\ E Fdk*tPgºev'=j<OڱNkĝ4U5bSɯf Mu؂U'Rō2*rzuN k$3YmLX0hpa]*yvI7M/ZYIzro7 K!$LaW` YH+#KjHw֚-'S_f4["{"dEaEeB-qO1墛6L& 0 b9y,'M5ٲ āQZE4anR~ Mä]<ׂdXr·֟ Zk: 7E^9uR*nu@5n>rЂZѫZ,zRF@naXy왝uih \:*Z1 0|6R޴rֲ/LY \˖]p۵coBC,sEh+;#waպ-g-Hf/x#h3K͗Ĭ5 @@`"ᾜQt l(kJx@cPCmY>J =3wV_Ҟw(ڣ/)gCUc!ZPSW1H& 0 blc\ml9&ޒͮ|dMM6I6Rf_qC-:ɳ IK{gUPb :Yܔu+KITʹOI6!L+NNV;bФ16O6vxs.0ej'8Xp!Sj!Bkv!>c,+NPEF\ؒ~%p^5_]k 75h!<|LjR r7/B-OW۪lC,bL%~[ݧؿ] 7֯OVekֿ^ BٳzŖGy2ihJog|A? A%for32Vc1,sG:aG2S`1E U1[7J:i+2;HRx7OZE*I-gR0b R[>!X4^6d2Jfm9KZdB_[6ɿbH뵢;l}P=I.a;$3_VbkE8q})o1bEv=f!Bz>1 )r<iг}MXi|ɒ]ܚX 8Ȣ$Ub,nJ -"Ił4}dɶh|gJSZkp!@6frci T U/'5Lƥ~Tpvt"_r'a'n>yvI7S.ZG'"BuɌbRFHsi7O""2o18Nn|*n(ELq bA پ[\aCtꕓdqSpT,MSG޴:{P2eq-6F#kCi%|؂PUbpRTW!]!EłjP>ivQN})ٳO/B!$H5B6.'h6bg amH]ov ~I!)jG*A$\!??8̖M;<CSMҺlCSI Rnho.Zov[=\RR)on@!p̻ǏY۽ܯP"=wpGȚH*B$-0M3} 6'a$^Ye֕KPq*v2dQn2}ܦ`aQdkJPnh>1ύoޱ%۵z˪E;ikvAbUt/>ōwUs*KD *>jT\*0>QҰL7lgN?yZjZ% ݀P%ݰPϊ痸EEWlY?~tB%ƵR\{Uڇ]jW/m\ ڊZm+-n\{jgLj%W:Zj+6UBm]ܸrx.n<}jT\*8'jPTU㜨RƇىzgCܧyY /47 Oڵi&~U e] ;[ 7(Ի=gۡ='^~ܡuaB;/vRvwaBjtBz@ϕIVtæcӆC{?dȑ/B!ԚzU]-r']ĉc%Pޭ-qM7\x߷c=ۥ?K#tB}WVd_eM?8}URDn>n\9MkrB>S;zd)3gZ9:1΢'Z[9wFh FvrgNpnk njyЙ,5*}}\D.ݠ )PH7]S Bm=ѧjO#z^ff6K׼]˯RPwagNZb;2iH7YY脮i.FlB J&vMP4M7}X B?w|/Psg};8kVR͌q4)Xom}8uk!^nԉ醆Z,׺W .o+yX[gAQUs9󿎏s WSB.7|/PXPWڦ R v+nY)/DIC5>;MnhQ7b5+yXt>kD~us<֮\r ҫ{6 B!ZP+?zgWyܤ&~&a ²zՊgO}!T鰹g V`w% <~c#Y|ńohA2O3h !oZ4)IPhU< 3OGLuyYfQpq,sC%לz>&r3I-M.` NBػ}- ?[O)hR&˦뚊kՌhAmE5sU?= ҒjC!EWtf,cLkypo-Y-HւͮhdKAkUH7UuO^mR//ׂV3r܉_"˯RM+O_EGզ+>UtSRnEzlv܅& B!ZPGz6x_RM9"5Ya6i'䚥'HA$OTqy} Bb-gUR.bsaZnt˝DYXHA_67u#ޮ@*%{^n(ADĉǶmK M#5ԝ7q]t|/>M7i گMkeǺZv"P\/w|>}_V18z{6ޯyʤkV,*_'&YBRЎT6^n#z~UmZx?8iÀK[T)\Cz|7Zv3c(Zq;2Bյbų-ryjt&Znp|G\9A3=7kL>HuٝQ7eAD(lT?Z ?p3lA?qavH܉*AQi2G}ЗMyeblt>eVq1}7\Gj?'5ߝu\ 'ǹLCJO>н -|dVa=1\H.rRw2wzC1K(Ʋ*aۍe=7/U6^nuX.Ԟqv1OK-9Yk˵Z$*VNjG]7syjST|\igN%3պm2kULپ9w~- MlY 2,*6Ϫ\xt\Y<ȒNSɯf M)> ӧE2[pbQ˙<.fK˙Q!pӆtsL_\9=qɒ yky fk0% !c x5ĜW (Y6b YUe3EpwIfX8{l=}h~~:xP(vr< cuo]oITfi-!ek!.6^_Naq YD'$fQ"8)cF a37'GV(9{A$t #xw5r,[ca aF7±؞l6ݏ}*v&oYXq+h!4Tug}Z]SN90ujR Z9ݐyr,A7k9- Cj ZۂPUK&Ϫ]x97YJijt2G2[p |ʬRmbfn*5tûǏ?s/\ziRn$% ]ɝ }⣗73 Y-G`yq ' |0' Ʌ` 6[V-!.S/K -*)8%۱܄#xZq@CfƼ}Cvb{ql_b,0OT^ll8c!,ۂaURA7F898!z;Vk8#D;3}>Wm۞;L/LCA+j!"HKWg7k9S SM*ulm@fMr\l]JZ.d)r ; i!z]bY n|_w"Rm㢫2>{qcܸrJ\2ݰzу-$ #X|kXH*%keevi-Aڊ򟐕 6e!vdpS&U6,9/K2 kY1^&Ge:w,\)j>}9>W-zlP\  i!^вBs+tqV+lq?K[GX$ss.PZŃl>NSWGWn(Kqg;-̣>h'l*RU=ʔ9\ ܹjѿN,nznq)X/2+ѲQOIn 0a d _kY`-VҦΉ g/-rڒ^NjZDw 6&8*^-ibtYb>9!<6tIm@7/4!Ė*S쐷i22=r NTѲngd)N!XRlĉ_[\5`eI;byj|m,r B$& Βn] lUrPFc1;`kb $0q1AqW-sybH3Ɗ媺aPLز iArJv&o9R<c/ѧUnPܙg6gCP*y'8Ut ^SNi][=OkR2\!֒6?݀Z;cHS!C!8BɲtEOQ 1BtB5~醾לzƿˤ\_Rs }.פw)XnMh*'?. u)a؈tB(hӚ=>jtaM7/O#q]Ior]P3guxܤ\GRs MѤdK]=[lbuX܂4,'݀ tܩ'~Zg?t腓 C]9msgf8M Avn@&5)6S%@k.?uWlu*ʞC(u&~pP#nqj{'YMM0?yc7CW \Ոtñm\/N}uMnbґ\r!I7 Bm{׻\Օw0zH;~F+R?PU+* IPw`T'JzlvɅH7 B MA:oj8W{_6bĈ;0SfJ@ak/uYB J7ԝ醽[\r!^}Wf3!jAgIMP}eȲ?L*AKԣ4[bc- 4ۦ0K7wK.tf3!jI5/꣏LE~pZ0OwvnHK8 eb7qję7ګ=uJ7[f3cM7e e\Hl!B-oOzWopiȊw&yȑ睷rĈFP.I-;Y:9+MX1VjGEt믾4|FyC42h`6`{<.U{J5P2u\H؏NpB}{oON50(^}5SN{uiX<l<%E:kW-=XbY[%(&F[aúr0) drpXWLF܈ :i*%Y!i Aj_r-ZrNPiF~A&Y zp)5, LLʃd%/>M7Au-|o>~'B OӉlG<9s={ܰvS;{k2:K?Aϝsgyl۶4\hr)E7~|IyĈq_B<}dXnUdB[ԍ."cja'>X;k;X#If^'M7$/7h_`uR`[t-t&ݠCbGHZvydGΆyE\**ʨUDN3/Qun*vZ't롡W{-zm5o(/>Xy=wmmU# BkN=5m +'.Ne% _rPKgdzGvoKGd+nԉ^t)L ._ۮ˰vS;^XW\2OM &4ԜYSPI UFͫ5ѵ)^Z6kPCF*`ZVWdo>[Ѻ,Z1 IKbc^/Nq0imYBhh vx -[{в^ v?}<ЗU o_SN5~|R4YK.vs՝_zӎ}l5)W#/ڣl>Z}Ws\QZPߺ[oŏ(BuԽ~ϙ8$w:n[G/'ԁ:my<}{?Z~L1\irAS`DW]T2K(q_B(d}\ r-j|4\ڤ,aZjfl)AlWA)LU:iE"A1<Ф416OvtyT<-Œ#I6ݹ+4mv`K۝tL#fzv񻀓t=Xg.tg'A[8Mw%2jF7io9ܳ4rE{t1;Mvpnߛ9_|ot|8a!?w?CULD}W_.w6TᮻT=c7~OLvCoO:r9?c۶uq'K nͦN!F\9&նtSTO 6T <]'=k VQ~:E*r]ܜi%mUܢ۝tL#[/~p*~qϋ}n׹B^ݳf- r ?)^ruǹCᮅ'$PP~Yg"C<[V-p'ae'7:]h?r>Uy`d*IrrߥμENK :_iDRxiXoRѭn>iB }Ssi<59}aTGFmƩiy(|~wi[om߾֝P94pz@eo:VtpՍ~lopz^OGpU[e ʧ]A75RF(sͺ5EMw+N+`y߀ڗ9;{ebj7$C&i[VUf'aE- XtJЊIUp<u%$'-Lܪ^B4"X؆wًg)~U,>M{`ҁSWʃ&Ҝ6$dMX0a\H?h'?쵹gmܘ4Bl7x^CԄCWw8&s]S.\:Ýuޠ6^2]S(yq٭nb̊z}*!8r][ܯʜ?nFmE5|Xءⴼb R1F>a~ɹ[ Ԧb,X[[;;)Uux/ĢW9ҝywCj+nQvsR Mu8%|v{snqL.iݰFqt>L?T knkdgd( ʼLݩKW4"Uz@ݗ (=nƇG,iT_5PKKik Οpt׮2gߜ6cˌS\WO=z6:J{qΙ]N:jW3_v-AӴ),R^ dYqT!OmKʌ]ԀuNs ^=j*UԜ}sl-Zf(;JN7.(DqEqZ;<8wï׫hئ &3- tûǏ?}iUPtW^=aڞLU^7WunxnhH7u-Jojtכ2zն2m-\.PrFVJ7Ȋw& %9w*nv+N~Bt݊?oGELQtJ&7ibJI7t]0m] 6sqP:`ƅo&4tH+n\vCܦK7İ醡U ͚?OO7H ιBؤ\} Zu\u@E%/!La%׭sLf3SLtðk䶠>vXRl4KM$VB/Ўfla\z)醊кr 5k{g>j-vhM tkx?uU VeLS|tgѪ޲iS^qg}' i=~\۽!T^_͎]٢t"dKa/qs cwi~RP]Hd6AKjZ% ݀PM{4,d GtC ǥeE!!D.aC ӧd.)V, 3xRH7hYxdCK7,3:  wi[Wk3|tCG% e7{,_#ʤ=|s~\H >P\c5 Un6} w鶬V7b~ms`s/[⣐ηc:H GY SQ;jH7?6ixg.?t1$7X>R\j)fla2LT+._(][r&HT\X-z 7=ygM& 01BW;~usXn6 . 1; iA8pO~ܫ}%^)f[lLD|kCHPhncUdY򔿤B*j3ZX7ctCPAuGs%.JoB|n 3'v* 1~>SzB,`oi ÊU\+/8bV9 JW_:R3H7 T!potH +_^z݄jS"U+Vn}-(U8Hc# ul~3">Baq [1mz|Ԃ#8( A6. =E \dã"gz 9tCx,V4S-醼MXm6k]4 v҄ܧSqm#Dq4-QD=bĈ;{4[} ؚZ529@nWݯ u^QN:X{u$CRtԦ Cl馌.oub{AcnVM] w~rխزVZtpwC}~I¢p@ JH.C>K7H#cՒn27vP^n-4 q319؃`ܧSqm#Dq49ݠI?Vs֔B&Ec?1qńo(*iͻ-Up;D_u*6Fy,vggtLҳfO1m"X"=D[ڷv"3p=ݠe{"RӲI?{qw\B@} ]%c*m*ZWi#s?Cmgv❤Po?yc7H7d{zsú4qiL*m 4[Ugl!дKqUWoq7K|թhP]@& K+䩷g^,Nubx2&,p˰Ul93pjO7TnŐ豢êsb:}͘>._dX1wFr(>.;׻vI7 B TId꨼M,|sUiUjL7|;?|ጛg?ftC;{K?W2{|9OllT!5kH7dj;v\4\rYD laB3go (ЏkTbrBگ]@-M1?ݹ+|ك1Ttz5n6SߝySL.K=jMuO7( CnbxCx ;Any]5P^ӟuK:YPzN5ntIį뢅mkH[q_BR7,QT[[oF+Jڐ,io%Rܓn؊DP8Y7sU sKy*1l4=0^x FXi% RkU**nT`Eaڠb7-_~*aul;fu-FZHDFETXdZ nos6ɽ+I]ScyGIKaOj6P13W͇t*7`|t;yz&RFȦBlM5A mlk9jCQ S55،֬!+jYP9X`R1T=z55iEZZWbUۈd jWŲ}P"ȶ:HhOW᭸y#,TH7UuiתCUF+6Xf ΛXnZPf R Z wfue| "%4%َX ʌUpD2PqZ.LPϊ痸ShP'bJuO7>ݹW >-nb]87?ɧ$]ҥ$G_sA B7GNѻJZ;׮}9owt%|ܨlW/_}lޛe?OUqg.aqZ7[c}4 Vo|LVoO&M'!}seqCiڢ8SS[q>iM~৉w49P<O+s\7݈[QoCeE'㙼S`f7̞ȒڨժXm#G%O/6TTk!쏍gj>捰d ڐL1P}yOm-y*pxj괖BU,5n0NUԺ(̊z A;:i]ҪlTCԨ ,r>{-9Syd3+S̿7,AnH%PRNSvQٯ^F>GYgθ[dO*1cӯY}ԴQ~=*83oMM/,OkOW*N7hGZz!pX1[m w7ܜuBn%w)RפQ;[hteƲ&hw>/Cޜ|f-\U,Ǖ8jڨf*F< gA48?Ԛ1yNH7h8=DRNU/YP2%1֡)n F=$0XyQDF$uR'- P |城)YgjxUaׂ>jYهVne(-ES sB94eoT6jNvJd37d9Ykl٤N_ڼncd_2SL捰NQ;On)sOɫ.F+6X-0 ȃ[S3z0ى0NU R.0-7`*3VCvg~Rs w{.lfrS851ŔjO7= =)VIK׺(6d}p1E,O1FŪ>捰Q;4|ܵZ xv حi.079*#3;mW&̬5+-IE2k Nn8Fϻ-K+՞n63)f:`b)7ߺ{A"x($'n0tWP=ݐh0YP-<Ol!c/_5BeB+GΚ*knوV,OM61vbV'ݐs%MT3H!1UlĶ.dQ- J;;C 5 7<o} XvT'n# C_2BUgъ ٢j9h[6iy5a׃^5ueVQ1muYԔ]&5.Neƪ}(.f_X yW!/`)8vهJJ7|s3 & MPRW1V:0=aϟp7:; emnwhZA;uljL78d7}3>|Qe[}?.:[mHώ%K "5k2.LBYCΒC\]a MfdfuyKJ0'yݐT@܂l+X܈dn) :@fvv2mch[qT~x`FXio % e;? 9vbKɫTqR+:ط<.'u'^Cnz-sl3j*lYTVűHe:00fL2PAktMCSA\aeY>|vݥF᮳+zզɫ'Zj6*a]sPkiξ9$h>/>ϭAZA횊z MS醊g;7ƹ B?Y5QNj7tZscg,#U<Vˬk!Bg7iDڴ?6jvTf,N{DTsF+6XơVnC^ʏI 3gci➔<":yb t6\HbG'L#T)fJ7Q2oZЄtÃ}%&̜1zr,xmᅱ"d U]d\8[Na Z"lK77,ijtC!-{QK7lA5M+~Vk~n g]q/[H%htjSRoD*q 7֯OVLk~3׹BZn(? |nheߴ8re~|܅bft_Gqٍ] *k_(6qYTU&-xmAp.M9A.AK7humBsA+ݠYH6AmnXpP+>eF-lP~gg kʚ ܥjOʢu{(ׂBTe9svقyȑ+'ߵP4[E;cy~si6-VvK}Ȋ*t"*#-`L;|%G8f3PŜY`ޜҌ6]˚ֲ-h5 avl8OlUS w~,VkjС|v K.5mLF?!S3̰ _*0yz2zZ+tCTH5ٺI-c?:}զLP8|?ksiaO|L $T3R; \,زA.떑#qɅ/ljPR VU~hkѰI7(Of/s86N-a bFqw~~͑a bdn '? yp'EKŠg)`>* h+BvLr*+6(YGjg7}%/a]ݚ Ζ 65Wl[V5O.*L+5tڬ  Avn@f tÝ71r9vl i> f3P]y99_4|^~UMԢngy#o`n1j-Nxsm-HTCoڸu2[cZ1GP{%C. ߚbrB7)8 B} Ivn*KUXpp?|!]J6,-i_hYUlPҲ9jtCxYl[$k*(庨{.OH7!Avn@fCi#Gڟ 4BH7db!hUG=DkH7=,7UM%DUij?[.μ?媤J7ؐWdʂgZoKlk\teBRPnm(t؊G9ڊ!RqX29V,n0$qBv1];Obͤ8=][o>"ݐE.'5pZv߂#y|2_pzeV7i0`?UMma&ZH7;;FRxB%$HTˡ|v㒋QFy8 ?,!N,eZ+-Id^AQ#eU@A-d:ʷ_L&M.10r;aE~+"s{Kh-w**}owinHCP"݀PM*y WmfK.r7inHVjhMԢng?Ё/8֊%B!1c#4Cz4_RjtBhġb~Q/7)B;oɌCX'C?Xd EeU_A)ӭ jt= X#:B=}ƥBS[2[3g[|*|[7-}E!ͻǏ+y!ʫѣ+~5[nRDnK.ջ %gaNiE*ղbz-"0E# G/.2)j*d%l+FZkow*`_ˇ?XD-r64Z|,a-Imne!J .a4 Ρ+6hҲ9jte3wf!~Y]FV;қ?w-M|D0ƭEV][}}gtC6Bj(Џk0K7_9Jf7i5,9e<T\j>MŰZ4l  +>T*y8Sx`KN-d((뮒re3+j .0B;a5Jɫ'6(<~ a [e C[ѾeITk` X%fz[18OTq5h C,Q*OR?P*5 _ Nnos6yf!pJ7t*ʥ3\mE U~?!FU އ'8ZF|)KFՇ¦rV߬(g !ڷ톢d!5(ق^a7hFⵚ# C-oK7Ȋw&&~U e] eSK1IJtÞϸL#S"0lT|('>rq4g^z/4Ty0aCLAu+ײVXWhk!ڐuCiI ȸk]꘭~3b{R:7CvۣPmhl-_L5:ݰ+^BA-;ּa ?G>*n@(_G% 7q)X/7sn6 pE{o#745'?KD$j3VϹj+89Oz@BzE.1Rg,t,qVZi 6'U~AZ:<8]]I/L ZM1r9>Wp2<UGEr)N0J7|cG.u)X_wlAзw?yU9/wo><, 5H|~c\{!]/q#<ٟl4}kʅKg:ܵp%#- } NUNp k.<3~.؏Np3][n^io\{=mWv(4ܺ:w jь~89z!jߛ4A؝o]SH#ozug/~O>s'CP"݀PF떑#q)X_8Ls_~Ưۗ7s'CP"݀PMz@ϕ*HVdΛy:e:ܳMe?fWAjX] 7_XO>];K\@PſuϷoMU94pOw Ͽ}gnpr2iq]I)6-d۱e^t+m-cuo7w-tc ~\P-8oy(Ĭ4 P#}U I O #yc_pwL6kB<">rgfz1_c:.WaCiŵV jƍYVlO'Tx|Z]!=[T{h+"[_W/; 4=t6jy(^w;$:G_|] l<9s7N-q'&!JͽϹOO`n6 5[/>R An/>=s8~"aM%\vqn8L<}?sٻ>W--ѯ?xK_yKgܽΝrԋݰ(mdoƾW)[I7@@I-2GV>%L]2?n*4)t_s쵹g ?ckw\u&˒;{&!J9uk)BҐѐ7! |O^>D OjnG#Yr5۱Y믺f4}|̼}`-B+z͏xqg2ah.ڛc#Gs]tsk*}w6/jmzwoI\DT׊~9SHC|Ayowg 4{ިiݸU!P+;yΦ司" ڛ |;oW{6 BRww?GMu= W;}| !t@{SUAzekxyv7Ahj)wyKU!P[Ȟ  MnrWluSz~cΈBm7w0DnhoM7qCk>7 4 >?/|!Wo}npr2ih.ڛj KBTVj9 P'{N50nho~us͝:{s_О׭LvSZg Ѐ~:Խ?w"C'5([=O_uա=/dovtqZK4 t )ڟֹEc+pWzE? K Sc=vPg3v9VR9ďPSoPܸ%񸶸Mkrݹ JōժP[qW+ŵōwqڑ-Zx?_̏?1iD5͉jj܉*Uu.U{x.54uK3H7B9[T;x?[ɏ]+cSj%CܸrU5~98Ƶ/vǵōoZV*n\ή6ڪbVkOƵ#qmLJ[rs}?x6/y9V[psD5DUչTZZNTNKo<%uԯnt@v7@hxkygɨi๫jBY=bEV2ah.ڟvB[}^W{;;BX>>bEg~2ah.ڞٹ0NZ􏦭!*j۞gֿb2ch.ڛ61KO(BCM[1q]cԴQW!Pel_2ch:ڛ_8bEg=0O/jsM^vn h@dtH77=G_1MVP+kz<!j[G@!~#\sBk=˜!upC2W H7=ytĔ=u È7 BC<!K|)Ȍdz0DnFpj=M[1CǾz`c !PupO _y1 _s{"BFLJ&CԧTM>jzw/5c7 !Ptxӊ;Y2-:H7 +v۾>bEs:?j4\t!ϽN_2'jH7 7n^5bEL͏YB Pe ?h@ד@ @`2n}#\tK{v !4zP2h H7 C 'r鳯ܶ ?A^6mıwтB!|ٳnZkM2 hH7 O3}Ĕ72.Р~7BC.}ğ6k>O}>I@k@`r_].7;AZyQF'<!ZDv=闌rse 09/{シQJ'<!ZA?=qS.~zփt@gw;zབྷֹ B4zgG!4 ?nڰ2h=H7tb Ϝ:ҩ縹 B ]jB n?}w\pC=;@KBtb>W_:uK$u๣srx!ZN/o}ﭣ4?xgʐn8|ai}/9o{f=y͢Է#jYs_:Y?~{?:/f_mIϹI{xe3nzG|f<{vt>p>|L{1dq>|LږTϝIw cҘtns05h Sy {sh=ϳ-=ɰ;u°y԰=eW ''mNgkn^~ثnc:qF|L\1s֦'iڇ,ǔVIr1S|LMr?3!i'̓awK{g[ {ړawala?U; '_W^L> w~ŝiJ癞yǴC 1T\cJ{j+ǔz|Lq1,3p:OsFM|Z״cÞR=ٞalaO{2N0y-5B DO/KL tpN ͂td㡍=q٨i;614 ÍQF{{@@s 0ܸx=y@ !ڞGę}G%!t@u6j9  Ж,ھ >5!~5mht@{ ݽ@Aձ4{LRhyH7. n -ʵO];jڨO/4ht@s@WRh+H7[$evt?Aˉ!0ds nJf`AytjU {H74wm_zԴQc۝T SH74[x+t@c Qn3xhcRHH7ԇ /8t$I7[ǎ{{OpQYx:tӺ@M7|/Bw>===/d/ 1togϞ$Pm~(t')ѺK=++Lu#Gر# vڄc ,\@U^~$ap22տ~#¤?soMNF%Ќ]9M7KI b[ )<*y&D:t(i%9p@4@d )nxW >VB䯵Om }8gc?Ӊ r7Hr'sӯ~7Q^Z+}O0ɣg P!ogC޸|K"T+5Ν;d{Cʧ|s'1@TH7C.}PԂIZF.^x ͛XAI9"ԁd)dOfѨ!#=<v oV`mO"֒vs78$q k=HZL',0aB(a|2%7Uq=_Lԝt7.xe 㶮֒v -,ȒLb~TW$N9$N'ԝM {@CׁDnᗿeԆVܭKZ|ݻ5aٲeϛ7dIjI9ER]:ݐK@tv6JjUZNPWr of  qAL}eQW?ϓKs7Z„ ]$e˖o/^Ik%K&w PbCzղTD+۷j̙I@g3gPr N-?qɂ+z!"|2ixWkY8mO 78nER 1EFiģ,@LR >I҆NCbh ~K/Nr K 2{d\AZ~k_|1Y֭(z˖-*ՊaFHL'3gZgG$pri,J-'7zj3*.nVᦆaρ}=_A@M7 I] ,bݸ|C={$+#dv]fIyt OԂYYDR.IH<*NPsR>vߊ"@0GM CBn! E].M𱾹[rrtÄGKݽsdrX,}7&吘rbT'T9i&S~t;dڵ+ PmW)V,)2&µ$NnGNIS{@w1醷~;I p}?ui<%N7\6HV.ŋ`: &~b̢"cR.\+MTx䴜Nn!17Tn݉0kZb?\:b%H)Cj;0KW>!)gx䴜Nn="Պ 0={ҋz$zTOlpi<%+N7LY|jy! t ˖-S7 a>_-T%L iB#pr )lB~$ۥ_y+<5M7߿?Itw/_ե \:ݰzdHVD/0cB d@x0D|D tģ0O ŒJ?ZN}PwowbHtM29OA濹O}=PI0܉0cKMtxݯ*SV>יsӓp޽fSq2jG%pgE{[ǖ@|ΝI`@;}+zV$K7~;fyvءuB Gz\ԵW*)@Qu/~K/%Yr:tHk%p$~@ôi:A~_?p@3d饗&00jJ7t!ԙJ.`E<ZQ%1ܰ憯<rjtB-Ƕ^h0m=֛A V~:P Kw-=oyNNL 3BFqNx@âx@ n{C3>qn@Ԉtdi tB- }G@!݀Pi;t՘s]V<L ?SL>qƖ]qv jyĈK. J5mԴQ<J7\bbًf;{-8i 櫨`3vrȮZנjtx5i$gGUrI@ޞ#=ItzuU"nXսJ'6<,*'8cAohЖl醊"O6]Uقj/xܱE-^V >ʴ9ZnF?S7zh-H  -k: P,ZX{C\+)Q1Jvv>Z;v'EL*URܠ+qσcxkD %l +j9mƽWwnb:yFa.ݵ4@ 8n qlIM>IkKܡLAa?x}Zn()<|(n׫:&*Z/Ѿ(J/1V] ԫ?񀫨Wu&{UmA+FD$ۨ*֬ O7&mnPj% E** ŢtEW>J0* VP؊kLOyrkDxpȔt7Yƫݟ%tvǭPnvsn4IY,p5)%%b(=hr>lGX5X~t#2K*Ag1y93zkw7; nvs`ɅUݫ8LM'Z'[%5X}Y2 {Lev_W L*GƼah 6FӛFM(Wpl5Xt?ҍdu,stCc+!c$BU6Փpd'Cl1PLK($ړNL7?G};BM-2{FzfT`瓎V# [etJFKF2enզ;|BE>zWU6T}(jL&M-HjE%ОtbAqEEn`RQڷB ci5eQnF,*WRaAT5X}2ZmLeGh2m^40\AJ6WUQQ3;j3yR([I]y.9cTh lS'RIFUzf#DڝJ7 0*8"A"H7ҦO4)t;j9nvtBhOZ( GK=!݀P+*$ K=!݀Pi뱭,;0ڍJ7;zk׭npB&ڝJ7H7@ChwH7#F^4٥3vj yN>koWptx2 Bt;ne1cǸ*jU5qD9?Ap+3 - tzaܩ#FאDJgvIxZ\uhh7Z(0$L1fI'&"3`Uݫc Olx"31|-$]bzUWCmh9HCPV$7k)sE8%W@{B^ *ߊ-`Opk% ]6AyX+!9(,V f/ ijz R`*ފjwnR1VXJ%8hO::0i$Ҷd-5JKXX^2ݠ0^k^P?ܧ`FkGEk6NF؊ZKXCtCŭXcskA2;jK=t ]ZB8`Yvp2n6X?IXBXLVwX,585kځ htC;}OxZ $K}%@!zd%$ 3L8P&Pq+vwC{&Ujg hwZ(ªU]?^8dAH7!NO񼌙 Mq6L,-ld޹_L^kq"N7;V20tjBo4tۄ WmcN<*$ !hw:4buWVb̟9IJ eVŭzb0c$I7 H7@ӡ醼8BsZqiFU:[qs/*?@Mhw:1`+n1V%i%j'x=#A.Kb-^4;5DPq+־UY qn,)itCw .ϊB;blZDA6ڱ%U;I% ĹaAK@̧`+va$Ё6 %W@{B^ :Avs3*dI@[hI%@{Bn@R%Оnh'n%@{B4isǝhvPt;-nha""ZN!݀Pˉt;j9m=tv'@AVTrU'-n&BhOH7hI;Qy%@{ҹ#F8iR|_1ٛ/A=hK;^1fTߵ+8l:Iv sƠ.-YD}@%@{ҡEl E4|Z$漢[uUIV*E&s~ba&8ݐҪU4'xRGE@B醣}Gou .j,Up(=P67GSNU5$ҡ; v#ӡ *7%ACRσOѨV&;-nh?Spbxz6+*^ve+J(7^WI'eA mLh;4\jrd?۴Yc7[7miZpОvjm`L֎vynv ,kf)Ea-(8wRo rvC*WFoZ~H|hV 놵2GIpًfqsw׆:oGE~7ȶ0,q>V;RAˋt;mnc:VTZ*Ȕ~o*񲄯-8p*2m䬢Ej6-eZ:(ޗ KLV2GbVx;4d{Nۢ킊GZъZ%K6P A֫mZjA vmNԀ6&lEU8H֎2]^itbW' -OF*VԲ|UxR 5b1]mbեtl[ڡXeHco ,N֦6x[A֦= #a94wA1A"w7 CreEh_;|vQm&׈S蒹n٦,!**$C;Ń*m7dn TbKj;t;\F  -k+E.pu$7EXZ]Pꌖ23v'2UݫԂɺotBciɡ=eqwI=ܱh2y>-[~!TI,ENXnr&qK**ړJ7\ ᮤx^EťZV.dB%kZQӑv݇I%/i; naBXrwG[H9ʼn˼hlB^ (oeq!뀤n2۠RK=!ݐXt^œ \7ZvAi?)HdBjt@eQϱGKO\l|,cu''*:SzUtHoΚ ר8RnTrI'vExRkpaZj'n|2݅Ay!8OSQAmw ԃCZ.Yz"63JZeLܶTtu;jA*շkZ״G$@B醾w>-z5BpWQ,kI+*T */ƭe!Uݫ,nKvftin|m9/N% ̶7U쮍P c!BdӃl뭥ԠV;jVH8H%]Q3;-n?S NE c'HI REGݢP˚@Uۗ8tw*v(GnXO Ź*%5":cEV1K^+a._+Z BiXny{.Yy"c[/X|Awowrm~A,wjUF$K";\6I'8߹u$Uze,OlxցtïT]1lAREerJ7b-ɂًf;d=o[HBL-# Ne<-`-Yq -ˁt@С3xXc(Je"2k9e6^ Uvo^]*Dg[LL84vZd"OutCY`Ѳ  erJ7bsk'H'#Ԕ-uڏM7HM>)s4 (np-=itC;}OxzW.w5BQB۝QWvź םd+Z#Y)݈gk^4ڱ{2Ia"O5%)J{_(R =vS [E(i*t!=έhOZ( sAW㤘ӂXHX!A!zTI |B#$Y)݈-`6P1B* eTAZ["&F3{]jYe 6 *vr!+5dAqnY%@{ҹ"fJr:uˬnTi bvvځuVSrI'n@o״iH. vC݀P+v g ZY!݀Pˉt;j9nvtB-' n@?S@Bc/>Mz!ԁJ >t!ԙJ. tBhOH7&Mts W{g_ׁ}G tÎw;f _1^Uέu4qDpLN ]{;Fai 0bγEDDڝN7(M7;^# % nBzUFI?ቼL9Wj%orURXEK.i[hE(醎hwH7ʘN7[.)bV¹M*8aa}XսJvsf1f/MD 'Y18W IU=cRjpa]*.g`JEۄ֕%#feYvea]4 _۝\NBf4uTCrAR/ˤ$KXP܄Lu-! {%W@{BѴ?Œ krkE%ܡUS A>bP準pF4\IGE;%ЂSfm%nQ8#NSrI'=[!J8%̡ nҺ^4;.n@J. tCb2ďQC1OptFSfՒKdԫ 2F4\ţ"itѾvݺz5Bt4i$ã3B@Y%Y"nHo"X N 0Uݫd V1g_6_~#f/ZjúVĆ'K N7}֔dI%phw:4`n\T/7e^e,!U!uղ!/ YP򴤃sCUtCR/9cЦÛ jUg(k+vhwH7 r"ZQUОP~ǝh5>?>u)W[/_yyUbVۊk^ڪ:5VrwqϥƋwNhOZ(N_ׁXݽImtZ%QMbI6s'q9Z5ԕhj[Iōk/\mUk Cq{W[p7^\*np7Dh_Z4 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C 3ΐn:C{dӬTIENDB`pycadf-2.7.0/doc/source/images/monitor_event.png000066400000000000000000003044301321055627100217040ustar00rootroot00000000000000PNG  IHDR&b!sRGBgAMA a pHYsodIDATx^xW`ܽ--n@KBZhq)RP.xBB-w3vhd{gٹsgvfvNF6?DDDdu>}?T?~d>|x߂Xp j-$ St,rWŲ-X7E¯ _~,HIX˒Og~)wޕLQ^^^/sLIx}R^~*Rz &J}!! .cDEE2>HX1r_'ވLWr߻~%~%|ȩok5YGXjbc"-~E?Z-ңG,ϓ'O`CȺ[ؐ'(E$ 5FDDDDDR9H8DD~͛KGO@@xY777_ F""JUk׮cU?r T""TADdΝS]۵kװk9r*D,j<ɐ7o3gNaq}]$IՏ~c3r5ذaC\|͈ՔPϘ1#wܨPsQNS.\pڴikF g[' >L2^z fP#Zܹx#G'|"W;,2e f˖mȐ!=zTK˗o̘1zLgL.&7Mbb믿y54;D^/rԨQxfϞݾ} cWĀP2~j<̚5Kn;{j5ᅬ;`}vդ:s}Ǎ7ƃʕ+ ft@rpqqQMFcǎEb*RՄ8 6Ĥ?I&dɒj<!r]E/j:tx:u*o޼@+V0?'"ADV'^'7d 9gᗤj}VttyW_}9a#G)P0wߕL`X ~ xGIٳOE.]BBՔŊ 2D??(f)TPB|F(0rQ>H@ݺue?D:֭[:,\0ga\2? .$@rر#f,_.]ٳgO"%KCÆ ո-[C֬Yo޼F6?PM/+#g|a8::66KO<_~#G&#_~} 4>WfR[N8w-Y+88xݺuZBӗVυ}F=w޼yQu2% ?6RIj߾}ժUynj~,Ç(6a5jԨYԩSCBBT?4-ڢE lf:uƍݫ?u֬Yȱ)޽{fVF{A|{ӱjժ3؟؜&MY&#KQQQR$XÏJ*xuoo `K|F%ƌQF"J&LRJR{{{C/^\._f]T)SsNbРA)i.]x%KT=}4cQǏzóL8Q51c:`<(ƒ|"ۇeb=?΀nXy|Z8/^$Пu} .lLd:wb\reQMxV\nݺ:A.œ_HH>o,_QU~F ̓'СCe5(U8vvv1&><.Pшn)/0 1@׶zhDٸqoǚҥKcɐ~={r f2fԩ^SS jm\#za mǎÇQjz(̞=[=~lٲÇ~믿޾}{.]VZ<~f͚艚Ν;5 ڵkcF2yE ]:u uڵK5?@[I)TP"Oѣ1;r| HYi~bv;ڋ-'-M6E#B~:^wy} Quꫯ̙/ ^iӦ!Hjժڟ7l؀bŊhG;dyϟ/5xq<]V5% <?){U=0r5ٳ'!'\v ,֪UK+AW^hG{  NsHE8wr*Ң'l.D,ժjzEh,^8o|@yݼys̎0cƌqssú'WdGQA5=}2ĉx Oӫbcch̟?+m6,mڴ ΀ɵ(KN7#SaDtUq,44C7]Νó W9sF5=}܂͚5+VE۷wޕC.C:k,%sK,wj۶~CBB}]ȭX~Av!"AD~s}mvUV)VO&vѣG1 ֯_j*B+Uܹ7iļ2֭&}ljTr:<i6J3{n Ae)K}vXX#F&ݻwFE-a,>px״NL\w} 9$Oz뭠  PMsnZ5%*::U;lj`LQI׮]Sas1) UMq.]Ģ]֭3[gVM:W\ B5t{ g͚%8Lԩ\T"[H TS|T% ذ~5jtK*TK|ժU{zg_-Z&19B ZݧO-ZT1O䤝^zi\.]İ˗$Jh"Ք={O% M0)sZzEAҗ'GK36a 6^V|8OJ.W۶mJ!|hI<-u'N׽@uA#(^D]t \ rq9 )SM:gϞQGqԎ&M%Ru!!!rxW􊈈 W+Qu+LȄT(/^&a+69ugƍ҂IEA of%/YݻO"3xˑ <ƬYP"@$&P!/Aފ}Mp ` toɒ%(}Pיj2B%T^=L*YdU15Js9}?o޼a\ ,5Gdz9pyvhbŊ ,PMO;s+ x7q *?y)!rvqѽXbڧjq)RQXc\re* Jd) ɧ~zId}HK,0CD#Y,|SHժU$^PJeٵkxS5JsTP2aÆxNΥO:k%,YR}||bqI@@*СWL_P [lA;*ѳgϪm6bx3.|4]}߿ N{2Q19&Lb9tcr6Jor/ 6URQĹe%(VONj\~D) 4q%eyf0YZqkrf,~}v?^ h_0nXxg1ޚL8N CF;f|jӥKLBJQƧ/XT)`00CD/F"JLի:pYh޼y(vǎspp=zL_|EoB%w=pן3gj}:W^A{ 6˗ˌ?cF)sŋRMv#Ե={DdF-5&M$ħ\mN%)SyL> PE̟7$$D`W^ϘL;v0`@ӦMU?Ҿ[Dî^ <)]%zE&M;wn߾} (!8;v,^ŋc+V %Jl,tXy9͛7WGA#^;obڻ>rڒ%Kbшm9r$o&}^}7x'N`=/^\VOζBB^G.駟Ը2pGGG, ;OH?݂79y"x1rQ_V5kDAyZByAԬYXӵ_`F\]]###~mT\Y[xəZ5ۼ3.,X#̘]uA */ ȳīhѢQf_}KI|0l v]{#DD0rQȡ]ˁhCDd59(G(**j0rea "2DDdY9 #0V^;""=wwwF""JK?޽{}&|<]H/xY*UP:bŊ{qJ''#خ]j #1F""ADDi$r`488ɓ'2JDD#1˗:tHF#""?~,DD1rea "2DDdY9 #cF""+ADDi asΕQF""ADDi&DDքȺ1rQc "nDDL"ǃUDDV#ADDѣGӦM۰aUDDVRzADdu Q*b "2DD=z]ADde9(!oL8qժU2ADdML"#*|Xɓc""DDL"YF""JcDD֍wc%7|tyܸqj<,,((H="۠K2YF" 9r &R.mժw}'s]fɓe^޽Ϟ=~̈1r%Ipp!CR xziddǏ8Y͛79rD5b "zÇ_z")Ix'kAd9#((hȑ/_V%Cc zDD >|+WTqD)O v r޽[]H7#"kAD?䍡CT㘘5B6c֭(GDPP $"kAD#F888R#c " {{{UQa zDD D1r= J5DV_rOE9#2|˗/q"J6mb nDDC qppPDZ9#|Ç3o F"ADȑ#8.F"AD6M` JCDVlСCy>Qb zG@F"n~B0rY=F"E#Fp,#K0rDGG.DDycذaO0rY=F"-f7\"JkDVl~T!" Ad9Vp/]" DV$r0rB2dϧ"@DV~ >y21rY=F"r(_F,#c "k2l0Dd6nAd9j:^5Dd9#Y'T-Çg |DVPpp#TECD1rA6loDV?*p0rQzbyHo߾}cVXjI9ݻwӭ#kTFze-7o… ?&' ٳbcc37o޼,ɪS|߿f0'j*9;;I|lˍ7Ԇőݨ:Y*Ud0*R>U%a zDd%BBBoxzz㏅ NoO4)<<\A_T)/C̙3#izaΝU?9r(Pt}A7<_ٲeS3ɚ5gɒEϠATtL2jtr̙?~ϏREʕe3x |VKAd񛖑!C\|y 9tPBtW޽UWϫqʗ/WMA_:%Cj8T=p15>m۶UЫW/ՒEѣg5֯_ZY^{5U C5UTl"j$DVO9A(r_RS ,(udʔ.[l%/k~!Ne&W\W\Qu#cƌXF(f*/P#G|^}믿驺Gx^<<ެYTWK3gNYnݺD#GK.L2hĶK;tE`9(*"JP9ŋxISQK0w?~ݾ}{Smڴ?>r_WU9dr!y^MxxTWK#BؓKzRG޼y-49D9(C9l0! `dʔID s"##1/bDǍ7iҤ۷i:ϟ߿jiŘ ѣ&D^l:th^!V*gO%gϞ2|^z25kV2G/f͚Iܹ&9PG|B]A0N>zj*h)RȂ E1aT`A??Yfjխ[W'{ٳgGg5x60W%H$r`e+ \ FNNNXpr̉WSă#Fϟ_ x? N H&VhGW0><<\u}mڴ^h=EGYs˗ׯ^~qzcM+Wj~Γ'|tR)E{i,X۷2iƌh94iuJ%K? xhk׮5QZk׮V* "sJ;fR""j+VLHiO1{{΀[͛xB+#NjiDEv4|2y 9O}dQ6lAdݼ9(o'^\*)2d(]4~7 B`xWd.C_Zӧj5Jb?a-xٳǵ?mV|Lʝ;ZHMߗB8L6M5͛7W2d8t4#bbvv^nԄ1JDdiP1BTJ?f?ך5k4Gؿj}x IO֬Y===1ȁ ^%DXg1 f~" hW5 SIj[Fv9 \]]c!(i|7诏xŵkHZsKj[% D d*6YmvM_lܸQZ`s<ٳgUkYŠ+Q9طo!VR߱#(Q]xQ)A>*6TE2Kl6o޼MLb'$%rٵ";o޼'Ү}A'$BO8Z}!J;ve֭҂-Zvt3x~nppp?h #믿J;`-x aUQXtiׯo3dXp,\F^hgggGrܢ?c~؄\#22Ru!"Jk SiP kGaz95!a~~~ڡ @iZrR"t,Үk֬Ƥ{qIܡ h"Ev4B|ޏG}4w\C#*W>_|c}]jҤ ,cɒ%2iLP[TzZ <ʕ+gr!9jTk0di-Zևt"HP.\P 8zv P; %PLɓ'K=zLx/iׁ'9fО/?/B0rQZa "˅1M0?Q cE *S FΨU,ĸqh}GTHpmTck rVj(QBw)W*o5 >OM0*5j#i_ψIGY߿LBx9rtÇk2vTʕ?yWX>nڵC^ٽ{,… Ugĉz"#S_`I9#Y"(,!or4iR͡C߹sG(˗/GIu9U~}5F5-ƈ#?C~t~EEc%9`ӦM2 0U'Nx# CǏn7on7liӦ#s#WFz={njDV9WU=b1<ؠAp((5#K(1лw.\8QݺucbbԜf&N"={h"_|o&6cƌ~;={۷$Cu,=zP]WRF-O?i:۸LF !TRA֭Ɨ x(Xd3x}YRIx d*ٳj15mOmɓo gU>YbҦ[ǎͫu,[mT?#lKYNNNիWGmi!{̆vے0rY=F",s>UB=K6vy_===Q]z5""B5% DD߷ոp8HM _*Q|wPb]{e쮹s.YdǎŤ<v |8W炛7o"hax]TkCBvZ댍+09O )/]tΜ9x'N`ijlή]PO9s&5_xebϨ oƊ%cW^=zTۓJ2ji9#Y6l% "JvDV, *C60rY=F" .-kP;A k

R)71u?<{՚+FZyg Kڌ8":aC殴4OeN8*4(d(^F5|,n93F=S]X|r{?.v ݲ|; Mz!^]^[v^2:*`KY2/~=Zm j$"5䨏Pr:ytӖˤ8/U>>"2ɤ?葋^Ys ^[n؟n57SIq XvcGdE~W=ww[4DVO~U` 4kIU'T5Y u^+ܼCTjOٍ݀5_m7g,DM3V/zۄy,Tvф0\~*㏢^|2ht4؛{<;<. X,GW)>9^E/dh/f? u߬:Oex݄c$4|&lF^:VBmk;H6'A[ldTuP6KkVXU]GDFE.z]v ',xkII1/-0Ucdor.DDb#Gj+V㘋~ >oYذt i{~]i!;V._0 | '@iȁ˙EFŌ6&]' ɉU ED*>O&#gϠ,NJrWZ$r`x7֭~jD,b+}x^Z6ᘴw,ggRhv{//aֈ5SNLXhAu҂R,-ņ np S&iN:kRvN[vƃIO0x?L]zpCTCfWC 9V;wtV5z3D9>kN{!]` zӭ#!t4Zjڥ;rр2H q򲷴:mE?6\hA@ҐFоjϑsĤPM \CG !pv}Eꓳ>Fe,-NB=kňgRXGDcW $4\qOfL -r LYz)ƘunΰBXt>zݤ}8aEiN'pO;1lYi"F'Ì{)22A?.kVzIk:I#Gj5j;LB|ua;ZYC[}h=P`EI9tADib#GKQV~pPÎ㮆_-vέBNõhDe)-[;cQ("LTÝL:-Z8rSZDXD}b\Xo9⬚bck܅_RqZj<2Iֆ;FchƴeD*,xВ,Qv9X$iG/Z2ר\=#- ao`˲Au*ehժ#GjݰUQ`pvOH9>m!}c zӭǽ{QQQD6aSù:{f.蝆k*pfq' G .:>,=Q1_wێ($VJ ,^quu$93P5Wl&6$KE&׍$ -rT~j2א0toIsJbȁJKW4O<.}Ah֛ui]\FuKS;T'niҒ1rY=VP03Yu6Q6O:.Sa&C[u?2jk~uO*GrpHUr5.lH9:1D&47MQf3\z7vPgj%ŋFK .09?L6aur%"vMU'1rQ ?*p0rQJב㜣OPt ~:ϸɱ \Ǣb\ɐԏXr3]bMc?'9*t5Րg8pFLMYtKd(j>q1rhg0ߠ(ڎPWT͘8R0rFhe30*:˸ a tA~U` 4qʕKD!)Yhg?t>n?(\Ms -wVM p qj2:G"7T51aEd Gij[ .ϬD]8w l ⹚ 2| ŏSNHGL_~v +vT]]%׈H9T^H}Fl|pѼ4"b1<%:/-(-!A9TSLLk;=bbV%UћV$Omyc`@ ƖCҒ[ȺO Du1 &m؜jѽµWf,MȋF\f,̕LR"fQqIKO2|Մ89W\`5}:YX9^$-s9݆k3+~5aԁ!߁({OI96#YZHw`ĘmQ4nnADtADib#+5V8r⒗~8ygt/KE+.r F(Qf4~-ׂB"#0YPk EEc'7=5-NR"GTTtΆ{ a&đȁN\x/ ~B ejA/&7 }uX[L5`K3W\Voo7P_|Pn9SܕaRNn1.rc:;ą>ь_/}G~{h~?9(]n89( YlHhL*i9JXT\>n>oUCqTwv Egx̕f?HJkd9EFPq$rlI1w7e->:rMH`6ZzbO::%9`iksL:}vGȨhdi!G%dUUW?̓`0rQ ?*p0rQePsvj?\jzbƃw IJ?lJ~?kwW;[^@G,Kj2< Vdsϕ*vݾ|7Xw"AzK2e9308blE(}M 8XPHL›ֽSO`˚ୂ0 m%cWM#Փn89( Y`H9(.GhgG(C"G_-׀,XvыIG2y^X$~iQ xI^5<6LGa?*),#O Dl*rX!T||}D|$r%#F"AD# }UŨ?ϫg1rPa zDd)9)-l?Ungi>ozPDgڵD֍,#G:zcswǀjzkRDɇ1r` MDV,#mb zDd)9l#c "KAd9#Y F"Ad9RH!")DV,#mb zG0F"JeD979(1r&F"AD61rY#GXXBD*9l#x#Ghh(U0r&ȡ"ԛ(0r&F"XP]R"˗#ƬYȺD3rQ` MDV,#mb zDd)9l#c "KAd9#Y F"Ad9R0r&F"ADҥKDdc9#Y F"Ad9R0r&F"nADi61rY= tKF"J3D!f[0rQa MDV1?ݒ7 #mb z閼Df9l#CO ` 4agg?DdSV^Ad3-y9(0r&F"nADi61rY= tKF"J37oׯ 1َW^jlL=:$y9b~%o#YKAݻǏ_j'Ad}3^0rE`)/^SN۶m]]]U0F"냘ADvChرՌV\ZɆ1rY F"8חѾ}{ggg4I b >DdbcccLd=WZ f9U""Zn]6ڷo/H>T(ouF""J=6mqW.Wt`;@` "4QN5tڴi$ "DD=z4}t2̬^',,L&" 1RKzT0Ӷm[ggg___ "DD⽊Ä\DDV1RK TH@э:b#S`իZ""DDzQ֭[É:5&g "TɁ^AD!f0rQ*qww7ĵm͍WtkDD=z8L@QzADDٹ~*I$YDD#~Gx*"􎑃RKڵsssçԣG∈(` "0e ^|9tGDD<<iƉȂ%9T""F""Jc摃gLYF""Jc111ݺuä7|S븸3| ~װ=<<\M6߿qα7n[믿^h:ܼySD3gVVH"Xr{q9j*,p؊ ,Y7h׮&/?#6 %KmbO.>UVTPa,d "J^DDHAAAQ2DZ4vvv9r$k9P_ &ʕ W3fU>uΝ;_|XTF2eϞwF7-r`#א`ix9sFxŊjD`]fʔɰ +v}(sX VlY,<[l9f͊3r%/F""[W\b}ݺu"*Wvׯ_WFfb;::xxx;qB0P<ٿ?}]DBɒ%Q|MN>ctիjꫯb| 6nجY3|gAAA&cٲerVզMTӓ'#F@ˇ~̞=rm~ŜJDD)F-.W2 6L*oMtttҥ1 ߿_@"Jx|v'O\\\r[lQOe߅ WcǎE#fٵkdÇϟ֭d![Vf䴮 D'{@_>gϞ(#Q2DDUŋf͚1cƵkJ'I!<9s&1Kj6lpC-X{U'Omۆ3gΌѣG+WΝ;L2hϞ=%7oޔ믏7n*?\eWÇH,hUӧVlٲ%;wV#^̙3Gf'6{}9#-BUtR9sDmҟ!z%KZiF.{C^x@M0رc 74 ScT8-;!tQ7yf߾}̍7\)#_q) h̖--[}#Qb "EϞ=ۯ_YfOܺu&gc޽{۷QFknݺׯ_orU P7oOt~(:aF>Tׯ_=zt۶m`Ĉ/_d 7nw!J[ƍvpBm3=<<~N:YC=}F"ADd%5"r5J5W#:H>H,Zŏ Yx;DгQ% F""[o\nݺ'O3ڵK5%Lnk~o4AD9lQF;v od͚Y5%1r٢Ǐϐ!CѢExL"GX1r%/F""[رc/>p@5smذ?222͏x0r%/F""[ˏNSc*4AD9lNFc ">DD#DDɋ~~~7nܐ2n""ADDioٲ%b#rc""DDf̘ѬYh5NDDV#c "d9>|(j0rQZ9s9|Q1r@8^"Dd*W~ {bbb":Dd)9l "8Q<ˊz1r` )D,#Ma Dd)9l # b "AdS9lGpp0#Ycl#` "KAdS9l#Y F"Ad;9R0rF"ADȦ0r!5R0rj":/@3"J=a D#ȡ F"Ad;9T` 4qO60rَ#U"TAdS9l#Y F"Ad;9R0roo7Λ7oڵ(1rF""::Oj8&R֥K q*VBM JID#lj(1r(6oܶmے%Kk .6mڬ\2,,Lu5TȐD@ 2e4d׷x⪟QΝ4"GTT>$ě2e(`C!i |M___5g #C͛ ƃ3,$ɓG:-[͛L2 9sDR9l#Y FC [o"7C㗶aÆFl@F̠ S=~|ܹsif>ݻ9DMN@ OZ&WXQ sQƖ۷9\r%{G sΕvDiӦքS>r|G%Kǂ#9l#Y D3g U =֬YU!CRԴ矪O?&<{D u۵k  /YǎՄ >;v߼ysҥG8zt|{w 7n܈ÇS̚5KcE`dɂq''| 2tEg̙jZ ymi?su8qtԩVZiO:~ZЯR [uD1rF"Iggg\ƍci @PuI@=x`| `>/+TLџ"zj”X>M6M[Λo~$@z4]H:.]B0={6SZ55xJDD٨J*jZ իWӡ{Yf5Cʕ+I돨Մg#@^8pcΝYuD1rF"IwU5o\MHѣGUooVM0ƍP!BBBJ(OGڣ-Z3d3f ^D%^Xĵk״@9s;w fG3jZU$+W2dߥͭ`5CM6IfѢEjZ |IhhD\.S)9lI` ȑt=rkNMx6r|G# bРAjё#GЈ"f͚xlSNϟYL a5!>w'GڵKMx6r+WN,Bs9rN2 ]|j=<ə3[dF1giAd;9R0r$^}^T^?0c V9٣&im?tuv-JR #Yfʔh wܹFZ ȡE/v0r`H:gӐ *%SN"ןґyڵjt9 o߮ZpNNN2#$1r pҤIhܿ7)X">/eFFAd;9R r>m8 W_}j^;wi/_]u͐`nnnjÇmڴQ#&&FM&!? q** 8@&'VT *U? U?^M09ŋf{$r 9lIDf9^O?j^zVL0A"VƍԴg#&w}&ܸqCM{>;*TIwѮՆ%K,Iȡoܹ/](dUViW\Q#0rQax!Νv,YOjr]v̙S3_{n5H9`ĈrhB~4^hSEw^}ubEQM2ԯ_XJB9x߆r !I3BcǪ ȑ\9l#Y Fߪ~NN<))ﯿ{,4kxIr:v(G'n߾]F 5[n4bccQӯXBO`< ͜9ya5nȁ/fګV:jjr  @6S`W^D,#NjBxU's̨xW_}UU?P1"o޼Ej(w2 DT'XW\nݺaÆU *0cR̘1Gݻwҥs̉ҫW/E$=rɍ4ꡳvZM~Qԩ3bҨQv%:5#Ga 7 #K8z;CDGeZ@64 V#\r={R AP3P@џ[%grV& Whdj*5#Ga GL "J/ͭe˖u^BFy9dɂ 3ydsQO>:uBQ=olڴIub L/P͛ FO? ETZUSzu-"""F&JXׯ"ܹszAD,#qYf!<-[8+Vر  b޽]tA~>|8>rݻ(Q Zn}ȑx/?|p~*W,XxMo%IUj۶_|!s+VFʕ+?}-ZԵkW^޽^Zvڵ!C4hZԴN6qXY%lHc(<<_#o7L/v0r0FM迈 ǪBUSYJy bbbԄUMaOdHeQ1_Sy,{?685W^qOM8l{* g>jn }vQiYouv]&?bqx>,.~]}h;obFDFFs& Fkeԟ]d'@,kMVcZ{aj~[seϩ8x T[V5x^<&M^kEgBU&lj>q Ux6o7XS?/sg%:_ݲxۍoT뱳HU6\tgݥ5gꏛ/Xm^Љ #dgpot Pl8vhX;n2kSnx^㵒{!lǍFpMj?i#xT#< <]7 ~.^ 2(n>tG:GEE08Z.\  rn&Įm8pNzӯgvzdK7|xb9F`x.jӖ]SϞ'xvU_"]86!"`QUY,G߾9P!T8{EFE B[j-]iiKx0g* U{n4t.upJK3Y[Hk7B[@:o93JҹݨCx^jF(e9^uXsv\USeOhތev"f}/T? ϥF3F"ۑXP]RErED+~3*Rm6mf)rc엮^fG#.k\V xvwi ^,A"|^KuڪA&iAHڇ7AddT r$k 2[]Za~lhQqvyF#RZM{̯*G:9"-Zh1Dm&ȁeTx6Sx4-b;iѠa AD""(9% pP#Gi'f8%aS/B4w<U]`ሄye."vy8xu0ZLz,fy,hq,k£vܚ"}NtvbQLe uq,%+5W`O=v'/;x7Hwł P#ȁd.^E;qQA`khؖFIˌ冻%ub"-9P;Jfۘ$F~Ryz-ԏݨEY+&ȁ *-z{ޜ_F]lyjjj80m%.~_+H:yXFQ㉤%! EZmԯZ=좭GeT|`wje57|s B\QMFx.yӘ#7S{z yo!`3WW6ܠvp\nQAdT#+].9zO{Ɗ2 kOC.zb91Eܥ?F+ !:V`*#%/F"9""PܫԬ#*9Wc l,s1_ OJ]G?k1R \h1"°7zq'6-y6X\L-Z~Cx7q ~*E^qǥEQ[M9(y1rHȡmd% 2~5Z<}Rm6y?S{pG1Inx) noRPީiFFkQ5SB ^#Cʼnh?Y \7(GiӖ]~0i6x?cjt {9xj;!'%!`arkX4Ap:G ^99B#+v5TxMF&a_zCNkAib~xNu ?&ejҘr~Nnv-XgD9RB{/dkwZsŌ嗯;{ to蜳+.A6`id, W6t#p宛R歲l)<7rr+ c:ZG@]+Wud7\<ihCh='/XI[n\&z:{bUiƃ*ek~3JOĨ8QYwWS\o Gtюx~(\{寫ܐ=yސgQRc CgG/x,b7n8}tγɠ}a7.lHWp\ۯcx|gElo Cr?"=U =Z ; 'R4rx lxg2}j=vʍAD+,,,B\awGC<  ~گ]` (~Ye]YL{%+vpDR"q}ß{R3]2[gImR4rdn۵ ,z6ϊ#Y DcRWЈgU~jc!6pBi٠ߌe;nEO _v{ jrKڍ<*{5T6|Y59:wjA:qdybUMq~|Ф%"2ߌShY &-`G=:tfy%<"R./lb9l#YT%> J (Kda9$4B5ՔD}/$08`1y:mŞ'>jžY1u^s_d!+{k2eWM-[6ĕGb4<<|ǎ+WܼysHH1C,aժUi&[Mg &mٲԩSW`FGGG, ۂW^bԑ;{>Y ܺu SUS˗/^ϸ~z77xѣG׮][naݱXX""z!DDVKjqߦMd6^~]o[~["}ǎ?#o# `QQQQs]z{Q>|XT)7kL,!gΜNBOY?0<<\{>ݻ^^^vvvXڳDD"9~*uЯ0l0D-i {Xp91m4i }w2qDi1ҪU+)VɅٳgGزesr ϝ!CժUccc] ȁ$%dty9vm6iy+1cƘ\ѿ{ڱ> -:2);^#/sToj+bT hܸ1z0k׮!D&Lܿ_Opʔ),ղe˄ʕ+YdA$rM6ż#ó-uEgխ[5qN8arٳ':+W.Ӵ/_F1C5kt_~}r=|ym۶9sHG\Xb{Y3r#BDD~kG9/tPRW^ȷOH|3gJnԨ&-[6޴y'N(qC]\\ʐ͛7KK"+V|&"##宲 U(db}W^kǧO̠ėӨV*-Zغu$"((H7oj2-YݻJsFYbl$?[#+W{0rY-)+UvX=___fVM6e̘E… U,x㍀i4?TݥMN.k׮agΜQMf 'O\fM<[YX#GD;70?} 1o(#a "ZeBy˖-(!o޼SNuuuQL#̝;Wnk\]Qq?;o>SL ,FK.œJ Y0/bKXhK.I#`}L$?K,AOsHHj2+Tn rrrR:tHjӦMttj<׾}-[&XF:`͛7ONh1rF""%utFN@~x׋)Ec…/_nrS֭[Wz+>2d2tP@ rțo6l{aF'u!` 9r@R 2oFUhh?Xgt+Y$6D5ŧ{VR:?evvv&i!899'`!zaaa$J| cPЄ&=m! ݾ}[ɳcW+F""%i #a "Zɋ0rY-FADr9#Gb "z9DDV#y1rHF""ȑ9^" DDɓ'}nԈ՞%"ADDilܸqC L"""+ADDi #00bccQ- 0r` "2DDdA9#n߾]3rYD$ҠA#cccy8?r?DD:ػwo5BDDVRիW/_CIXڥKDDR[hѣG;w޽{W1QQQꑑ}ӸqcOOOJDD#e˖U3jԨQ~V^}U#F 0@c ڵk6l@AҨ^:0g^AD1rQ GK-$uh?:w8p`O? 5jȑc&Mرʕ+AAAlMl.r`-ZOC7w͛;î^ wrρdu+5op)d*8XpFc}Ѕ |}!&o>^P䈎>tРAG8p[7o޻}9p`냫ݝ?çN쬊t&"GLL b~YyzɁ,gpqEpu}l΁<94x'ݾ}[銕Gl7F/KBM^<8p`y;w!8p0\\}:8w8pڵgϛ7gNog,0C)J[RN)E %!KC( ! #n#P{3NB,~yg盻;avf[ BxDZ&11C|>Pjr>}J[BP$ 8|QQу횙X%(HԒBP($$x%!!!kmsr ̛7?((_iS(6ƍ3@6w988̛5Q=BP(UG&$$dffѲ6P# `ݺ?5B|%?LP(:>fdC­,XIȿOP(JtF???(І#/W9D*fw$*mb Byƹ{xݺ?+Y#33sMwGF* BT2v^v)Gjj*? =?r,Y?OiS(ʳG .I.}ҥdJEP(Jebn~L9aT_cw=^(s˵kw̙UrP|y )x$w5ᆡ7YxN:S( Bʡ1H9(KD? ΐB 1IBM`”:os^J+OP() P(O)!!%;vo߾#==c;pPF)W\mdt>zDfpwLRpP&pÐ{.\|cDJBP(RAAP4ܻwovv^H;&)C 7 ^Hdeem޼?:( EGCʡ1H9(Jᑦܹs  I^H;&)C 7 ^H閖Kn  P(])qqq\8H9(NڵٳofpwLR0Pn*pzzKnߎWz B!  ,a!BRI j Cf0s]}E/)} Brh :P( ?-7lؘ4C I^H;&)C 7 ^H4C -vP(JM͹sB>o~xx8|C74kkaJۇBP(5)BT&aa>|zUof0c;pP.$¡ 11q%Ǐ;)m( BԘrh R otp}60م$\/!Ԅ \/`v! R6mڲuh#-FP(RAAP*l.jBfpwLRpPn*pم$\5D`:~^XvP(Ju) P(۷os( I]HB 1IBMaB fpP… +BPuH94)B)/jqPم$.$z!&0Tz! IaY^^^sܼ ) R}Ô#$~zNKAPjN65lي8LCR0wRI j CR(BnR8 -8s3<~BPjBH94)BQJh]Llْ z!dffX&P(JuLQD㭴Y/B˖/lff|(BRp>Rp;pP.$z!SUݷoߦM-Qڶ B^! EkҫW}(BRp>R0;pP.$z!Xҥ||r0BPQH94S)9sp ^HB HB jCR06DܼySOoۉJۙBP(%B ǎ9,Q_HB RG RpPn*pم$\/Q9s삔6BPE_9p"P( ƍsqPAp!Ԅ .$z! )dxRsaa+my BhyH94) (XÇdffrBp!Ԅ .$z! )aH }ݻCB)m BhsH94)20Xu<\/\/z!}z!w5ᆡ I^HB R?X[(( !n!z7 )~! )^H{)^HBMa i^HB R@98::Ν-MoAP( ) Pt-aa:宯 ,,ۆR(BRp>Rp;pPم$\/z! Ir0^GP(CcrP(:п ϭZ:!!F)\/P\/z!}z!w5ᆡ I^HB Rp(EZ[: BѪrh R Ew׆ sOFF \/P\/z!}z!w5ᆡ I^HB Rpw͚*( =!;g5666999\2B _HB RG RpPn*0\/z!`i^~O``_BP(ZmS^I9({K^HB &0!Ԅ .$av! )^H%C<@/lBP!Bt)@OowRp *.$z!}z!w5ᆡ I^HBRpÐ //ٳ j By!J}9s*,ezn۶meYV=L2eذJ; B<ߐrh aaR( "IU7fe@9{WҖbsQQ( 9YR AA`BCKӂ?AgϞ1wA``ҖzVcQ( yCcrP(⠠TnC  }'P(sIQLrP(IHHWݐrn*0 Sr8rP(_C'6lxYL+Gvvرc1 ){ܐ_mYaW\lL0Tz! )H9( EBʡ1H9(ZrXݱ_6lئM5j &_]v ݐ__2!{vQt8BP4RAAPJՕ#!!e˖+*wީUI2:sO(` )^HAAP(:RAAPJՕ= @<^y+WpPBM`B RrP(NCcrP(4̙4hЀ{FZu}GUdx ޞ ◴nVvY9\/z!)BTH94S) 2 Ԍr0IYZZ8p!**KFYB<}'c{&M/?x۪v;öܻ CR# 90>KH#DP(3gH94)BQ+U%^HbtAGИ VA_|iӏ|<ؾXU\/ P( ) P(jE=0nJW%WjɈ{YM:"1\2B R Bѩrh R V4hm۶I&)'Or᨜rřO?xp/9u~`A^CS7d S61BDyyyK.}53~x.S1pj3dB`nW4;`%,\/ P( ) P(jqurqq᨜rDl՞i K}bAO& Q4%,\/ P( ) P(j駟rPxrx,Y…rQT\{wk{e2N\ )H9( EmCkr#R RR/VmQn];;;B j4CLQ¢bRpBPt*F9RRRH9(JMJՕ̬^zCfeeH )C U^HB R Bѩrh R VvƎ[vmXo]JNY.VXK /3(*~p5?/dPZf> )^HAAP(:RAAPJՕ=Obb~͛7VXʱOIx}u; ʑVNFK8z!)BTH94)BQ+UW?;Q1.GBJ[}^461UpF':Oм1ZXKFY^HAAP(:RAAPJՕc޼y 믿r᨜r$`UdAM/rP SQRD~${NJAPt"|<^Htⱄd74N{&?6j}p8z%ngsd SNʑ[i%999 fYqGχDgs{#;A_l%,\/ P( ) P(jEʑ{n}}+WFDDp( ٴK]LF5{JdR^%,\/ P( ) P(jE#!LMM?cuiѢŸqùjbng8tUrIkNQRrP(NCcrP(RuׯW )>OOO& cy9.1s->]rLT(`.ez!)BTM9@ll, t  /p(o)o"cBsU(,zy(ih.ez!)BTH94)BQ+UWɓ'sUcǎ[l9y򤍍Ǘ-[֪U+6vFFF\8*qI9x`y/ T+h%,\/ P( ) P(j3>}zVVHNNٳ'k0fVr 7k5l5%|rgqn98}nKP(Bʡ1rܽ/BT&UR^z :i0ppp`BYIckHE?nn5k֭[o߾ycǾ+|^ZgC 7 ^HB R Bѩrh R Vi֬ݻwo##;ꆛ|CS1 G3.Zr==+ \/ P( ) P(jQTTdooorI&L6(;~;tZ \5J)94X7 ^HAAP(:RAAPԊFDDDL>_gW 4nܸ_~hdbDzn{[މUCRoHJq( )H9( EBʡ1H9(Zєr%3b\:jIp_Pݯk3vKFY^HAAP(:RAAPԊCf0"j59О{FYF/GC\2B R BѩhrrP)Br:uj7jb;;;4?~`` X,G!/r-\(pBPt*Zqqq6HAPj`UV:u.^Uf&&&' EZO 3g8ݠqvFd S! E<pppWڴi&//NjZm w5[}3&1;'ɓAH6 tKFY^HAAP(:RAAP+Grr2:kЯ_?'NHRյhZn.%a @^hO<|1zN&aB R Bѩrh AAR(Je$hذamL'TϮUK.XZ 1ڠ  q3jUz!X9=RJ BFHNN!(GZZ)BIy2qFݺuIT72'*G~Aݔ-G}/yw9]PRRB RrP(NCcrP(l|pPvɸ*\/z!)BTH94)BQ+O111>v={d1hhѢ˗/ 3C 7 ^HB R Bѩrh R VL9o"es( )H9( EBʡ1H9(Zrpم$**$,6şp.ez!)BTH94)BQ+ڬ)&xI! r:7bpu[)tBP(CcrP(Ru1cFʱvZ[ȓɕ +\Ͻ^ο_)䆡 )H9( EBʡ1H9(ZrkNq#3n8+K)(,åV[C]wʻc[!srJ:>t񬀀O;zR]]s-}|XYVj8@%OWj;իG+535qFY@_Ny+5C,,n[z{YZRj<鎍 nff +MXf%\Ri&_Iq30Vj`%ˬWJx=u+Tb%/^ 7 م4S%VƋ˝:sJkOCcrP(,cҤI7w5!"%CV_f]McsdJynѢMM0`H7#G(U=zYJ!w*1C(KoߎZJaFEG-/^t]z% V ͐;*^=9|FYBnnBK_Jȑ?-Ƈ~xرH;pk5%,\/ PTϲeە\cA-\C O>VqY[[F \]]Ǐg8qo_~E̜93//7ߢ+Wy" 6F NgJ>}7Rp-ɕ޽?chh(gϖdݿ{\|9'ݾ};o-_=|8>={d[w'$R vCcrP(Ru HhѢ proj=CD~AqNe )H9(`hԩ#F`رc 1>샬>C^ޢ sl 0-x:-/0WyR|V.D[ W)))0F{{la׮7sR=rh R(Je[UAf$*_~ 1}GdOs ) P(jʑSX7|){ァr$f<},%ГƟY=".م$\/ les`=mڴp>T$ݵkW^13f_JgRAAPJՕ㕦ФI:u0}A+-S DVP/('9p};cʔycǎyyy|H}իWH9$ P(:+G%nݺOfR 7 ^HB R-wi޽{c.7AɩGݺu۷< Y[*ǽ{H9(JOՕ uM2L!*-0^zWPq+ ?%/rIziВl.ez!)EwrNv\'[B9@TCʡ1H9(ZrdeenԨTV hѢSN֭bS9q5v1s3RGN.u\Ū :Gq( )񏔃Rcm5dȈwsW+tRaWR{W_}ś*]Sl?1700^ &t:)}nRP9/OOO'P(5)OI|7|~-ZhժF$?|饗Ŀ;ǻvⶡIE\,+=̿trxn%%9yGZ8 OFJJowRz뭤$ޢ, HѣGl?;ӠAFj9w>{qqK (Q>T'M)B|@9lllAqƄ cccvǏ%7HLLdU<ǞN~?\iӳN6QQmsdGAqrJ۷vs֭uԱ-D`4hk,># -/ N8W ӦM?78tEJާRAAP(رcٱ!+++`3gά]6sᨴr܂cvKo5Jb!0}clOߐv?o1;RѧO֭[ y M4\8ܹsYK@QyJHI):grh R VU/{&OY\/ZmۖYQ-=CDAaq^ ?IAAgP-[}+F__=nڴ)7*e׮]lǏ@U9QVVoT .۷o_Hz 6uGtݲe 0fkkk^} >|8Vݑ_?O=XݻwW|%,;;;"o 66vڴiܴi&_|WZ'<<oeгgOe y ol}M7noQFFۡ^z/-9IҥK(ޮ >HA~! E_}Uv \H۷mڴBIN/(=CfÚoѣC9]?U@I9݅w[.9s+WcluY|nݺS}R aLҠAޢkweu5Jlְaxѣŋ"`۶mg/rff&k߲-#+ Yw 30RmsNvHޅ~/HAZZСCٷ`M~GyG6n˫CJkK/]t5vvvV]&OfͪHPP3{I94)BQ+*f͚x#*\/`v!\odee"w̾|#%`B ~LRcmQr`w})y&V/>ӵjuޝ7)l5PO?e'B+ڡlU$>>J`CcrP(8zxxBfQ̬B:ig~kA\>[ U]HiRrPj|bc{B`HXM___ ̘1 x&&&Ç;v`Et&04icY~lmmsrrX7дiSg]0`+~'s}DI pv~:[ "VVd`-V^}VÊ =zĉ56fb^x .wx:ʖ"xhѢϜ93p@9T ( X`[X,pҥ2x]hrq@@u!ӧOZJ|:Fx_Nu/ eV!vM [crh R VU>{vލ Iذ`Æ g}=+,Jv 7j<<@>pz!)'))*/f0yXqʕH*Ʋ۷gȆ Y6mbEt!!!p$YKBвsάءCTQ9gϞE:^o| &( N:Ŋbo8::zFFF˖-Yk.66͂/ 6aE:,?SlXV,͂X9 ]:A||q^x;J`=X! Ex,ޠ(r! 8 0`"PYK5l|&c\/ }:k RRR0 pW=ɐ!C\\\В=Q*ʁŮ[NЙw_xk׮eʁ"c̘1%>(TZŊ O/YEcX9oAy)+w=tT9uBu>|{@q4b F8>j^ Dfb}=B ~쒂Pe0RR2`z q|׬rξR1&f{4i:tτL9p`ݦMC3XPy&bdd&Mw_Y{rM"cڴiިQΪUVl·TpSN^x!Fppx)+GXXX^w:)Gj@?JdM:)>VBAP(Gjj{?7400ٳg,Xf@;ᙻ)y|Z n*c\/ i5aD P50RUat Ʀ۶mcm$̙3T!X&k^h…kI"(ss7xPm۶—4ds+F8)@*3`thmʁH޽{}Gi':aRAAP('"@ CYf1`pz>n19"D! UK RrPjvƎԭ[7T >|8+s:u4k,$$|2+(+^p%lb%^[oׯGWkp5fDGGXO>,u\=)(СCyDNNNZX9Fނ;v,b`EX9"##y$VVgN_I!kN:AAAbՒrL0W5Dbb"q钛~aTH94)BQ+O`)ҽ{Tn CH_jkV_~=,g+(^HB RJΘ1~R5$ܹ1c7g $_/^̊偱p׏>(66$ `ƫ"\sssnDpp0 ! IIIOl.@ȫ)k;rHVjO rR_ BLP9Kˣy˗/GLC; 7ڎ?îGZ:g((^HBVPcr F (GW>Zn4KqӧOՒ SLauf| [lam٨d2ឳXxhh(kڷo_VW 5ׯz9>4(iӆaV%V/'P[U !~~JfMZەR RS/\0s~e˖o(xaÆ8p &&9L!$Im ?9{m^UQ*pgwW^y%>>.G9; ]رccccѥ`F2aaa K@[OmuMMM0p u,dĈ֭ a ^I&7z͊=ٳg?޾}{{Jʁѣ` ޽{Μ9B?)K.eE%ʠ[XX?m?? (hۑ#GUC5Q'\aTH94)BQ+UTnNCN', fB$7O~G]>Tz! )H9(5;F9}X?\>6k֌P6tO_z%4h@Kt&GfPoӦ/kh,ZnI,"ѫW?\dK>,~᭷b+ͣ:uzc'@^{5ahP9 qƿ曟~ oB0 [gϞ7n800o߶:T aTH94)BQ+TUnQ!ԄTz! )H9(5;L9N8$ ġc&S .]3jb?<'`oo/*Fcd-޽+$'NօqZS@E"""F0>˗  .dE(#HQJ|B'V={"&P c'(WܹsG U^|EvPA9͛ǫ ; 4i? ]eBB҄/O铬rh R VH9H9(55U)0`ALLLx[8sg̘!x#**j̘15b///^-͛7CZlټysȧ~:xm۶F 09sf6mZhb5.^7a,_~deJJʦMM.]`cڵm۶}WytPvvvx LMMy_k=:&&WUj/ZSN_@݋.oAttQ!C8;;kkk;̚4iRZZ>x::Vl׮]Z 5ؒ}$ ?ׯߪUxƏـUb3b^ڵkZ /w߭\?WeH9prP(RRJM>}ܼy?Z 0 -^· 0^ǿxU tX&X ^2xE$^H3>}222%'l%Ұp`z!H9oTH9(J ))5/00{"B+G488EI9H9(J ))'/O~o(ZpY|̙zB'Gw"P(5+RBIJJ۷O>ZX R RCAA! r~}\#>>BrrPjj||ݻwJH9H9(NRSc`gϞo *H9H9(NRS3fD .A< H9III\8~1C_ BLH9{VPc{Ah3xM )}5'rdffrP(RRJM )rrP())~0p5jԠACJ;?"Bʡ1H9(Z! Ԑro|U$k0BRRJM )Q]{7M}}ַRAAP ))'1;$Ah+BLAAPj`H9H9(556C*oK_Pno6,_ $喷wGeiBR.:VV'Ϛ>]B:u-J| ))l7eIϒ8},_iFovȒ "eLth|5:CԌ<>1c !/<I_mdwx(KJJ[faTH94P{O;:: AH"##/ޤבrhrXXܹ#S*R(Ob7"s̹\QJA8Y' C_ @_0b5Ta=W 7nNN!N8_铬rhxQSINN^d'_GBʡm1sYxx<{DRt* N. :~ 7lFL<:1P3[zܠ1&ցhѾ׬K;juŋ `ԬgP2/e ! j<ڣs055]bCY|wTB; d`"t 8Ś-\2Ypvv^NCk<%sQsILL\d_Ai I-Z8mj\ qqɵD) X;Q;l6fm&v`mobɐM3/*ǂ.l?^iyߎ;aWdgٳo~ׯ:aR]UWWCūNӧr1))nj Ҳo~ݱ_ƳiIn6nU!'Q9}#Q'K|6~Z*V3![LOrw3VL_nr먏\t ގ۵oLdB7ov}n:aRjj0{r5U}#xzr(+O ===///##cٲVV7|}VjCT&[A9~7g 3?P$6?L> r(l鹟8]^v`t!Zx_(;aBTc)5粝p҇YS~ j,Z 99Y_Y/_Q( 'L {vaKf7_E 8DOGXoBma, ;jX;3Zw3Ou/;>825#+-m6|JgURRrrhrx{z999L9@DDzׯG*ڸ;zl+=z޽?ߓR};vN{#HEn9n|6RU16l~08l`x { @7m4(ǦM{E=0Աh* RRQLdC۷b0&&zzz`Ȑ!ϙ=P׼~0rꝘUi60V1>Ğsd/uU+oiZ/И4,N;Wz>I+huw92qWz̧ rh RxH9A!|K9[(_v GLBغu+c޼yY6/TbZFnoůj 82 lڀ{I-ؠq\kVLN\ dkyeZTJJV%8))9^^ yԫh0 RR-Q/GSq/MIDAT<}== m!*pr;<<:O ~HLON\ cHkL.ӕM[agxw[e唹B&&!հ9ۜd>hi|Fn5w= NttEҞk RR-Q3T8|֭V=Zwf'^z=ͻŤjA3.b>N{yYKEB'X_W9ni9ȔcI4AL9wA%2Y~@Bߋ+u) )xJA?Hlrp.$&0Tz! )rرs߾c)=Q{z*yB  x">rwVwtɓ'/\RdH9Bم$\/z!w5ᆡ )^HV4g吜79R wok7x/u1}U Oew:H&˿uo|w[͛},{μxFDn'yq+sr>~#7xedy kvmĈQP9<Dx?" )^HB j Cfp*5kJO׆rh-%ٞNIIIJY* B鲄FE7*K=weI׵rxy=? ifF٩`˖-gϯ^wvj+/Ck7o3Dσ_~[n'u,OGYH94)A<SDZE}1_ď̏͛K<|ݻe˖˗/WYfas4)>>vLt6⢆r={vٲMJy14$RެY0#xV222ѣ>{tHR8QMzRFII iLLL>|gIQQQNj)hذQ!=߿?{V]\\ B! )R=ahhe!oJz^aw-B˸uV^0[paFF 3!00w-X;YM[?xH94Se5A_|E6O|l9uurr3 iѼT ^}{3^"<<u yn}g s(>>)-CAwgkkˬcɒ%cDOĩSgϟ5FE :RAʡ.b㏟X90޽;[Nf޽gך5k7nܠA͛GFF򪂒Ç׮]z饗 59t{&M*QiH9D)QX윳pzWZmaa !r>|hbbҳgOXǼy0#x慄yz&zy%,T=Ccrcaaahhd3f`ˇZj)'N3xyylܸr %aʁC$''ϝws%Raccӽ{wX|TIK JdQCjH94)hP9ʣbU%y3kɒ"%66v9WF(-u\6.$ uʼn'>l'^ԁ}i,g/Y`dY`UJf]BVs[*ōAgs9yx1ʕ1E\]蟻{cQ8iUQ䤯krT/DFFo&LǨ  @Y-ZݢGg/vOtx'KxQUu>?r`Q^-[Jj Og큪r@XE3f(=Kެ7nꫯpI`-,XK*`L9E꼪oYI? $hƖ)V֭[ Rgc=B -- ,*ÎJOixzz8TJpȑ]r,Yd^Y .]R) gΜ+ܹ P Sgt q]~7{RΝ;d/"لځ}`tRwaaa+`8fO17X ?#C] <0Yz5܀8@U9;֣GV];P!!!J+v&Mt"55P kk%;I%N+W9pׯm^/H={ParOv(},= ZRڰ-[zy( Pځ k͚5cnnnn3,p]T -3y"_)!ёbZZoOlx9HA`` W`Bv:<aooc1okgprrb^v[sy T` gJb)HII\ɛ7o*$+[J`C |`Xl={3ӫW)SfYZ^LUU| X###N|򨆂?r5:|PeXlٲbGѹj)bϊ%%%aaapH6)0}tW=ڰa;;;^UP\\,8P' 62o[رcxK$l 8/|y#.\4h ի7CN&:߸qO߾B~[„f3fawE$yR3_;w 3 Tj 2Ah]Cr%esΝ):6f͚BJ' WNYCcrKecڴi auh"^} [8zYܹs/b+~~~uej)|'`Er >ِr5yϟ}AQ)**¡$''6!ellメ9Qd?ǓBH94)hr߽{w-o=)Qypss3j՚:u*ꫯwy'55REK؍ '͜9ڵgc5bhCj RJLK;+,) )dzWkkkī:lذu+ϊWwww[~7^-%77cs?s|P$@_AT[?֑Է?r}Xa899-qw)uO#5t999^l-*msaRWHg,oGs>C$cU>> ĕ(dPj_eRT,jY8Ԇ%] Si $Gܲ•,,V\+PgВrh RuyʡPyUO?e^zf׳rl߾Ձ *VXPvZ6 lݺI9H9Dž ,YYk<5v;6.R0GʂE)yexR ||>|[($|<(+ ={cR @ViÂH J-KTڰUJL0W1Z\2)G(ǎ;X 4( ڵklr/Xe˖P|ŋ_}UVWRkkkS]vvvvtk``f:c=m߾}/c###Y{RA9.]DX֭T C\+A00T5 c-rEia PA9Zjduw:rÇfE a2𡓓S,Bec}V;w.+Մ zѨQ#L2`h \0`̍y7Z4h[o+ϙ׭[w۶m1{1l0V٠ߑTeVAT0NڰaŧDCcr3POJ*v? h Uc%'OfXgggfJ4nxڵ) QK)QsIMM?s^ONRCQ}Cʡ1H9u:п?~8+2RRRFf]|W>466V,ϔ)SX^Udɒoٲe~MjiiYx9X$&&Ο?/c]v=vXQQ\G7UYڼyw}^G8y ;vr{=͛oΙ3_05uT[sa^ѠQU''V8AT[QQQ3gur{JAAxH9oH94)6#222?A/U%wRu4nO6>0`rzB?W䥧>{YbŁ`JTwww=ŮJFBAxH9oH94)A<wH9*?|W>ضmUfݺ^^J~CAxH9oP9<Dr,_[rT6mpPкuT>2zhJjM2WQPPg)u*rThr$&&rr5;ʱv>@#JqttW7k׾x"D={aÆX,=qxbBh`bb«D̄TX[[###^-%##Nbnn˫&5k}/%gw-] 'L:?[tRbxJʁ j.qqqaofz>4#7|٘i„ cSRRx#+ѣGyx&Θ1%Yr?X~w:VZ5˻=@Q}Sm#77Z(׿ )QaKףk^Hs!^u$֭/QΝTpss={6,͚5{۴irJuYfߟ- }}}SSӂ8qfΜXM2C~mǎyR/_tRthLMM=r.\05]d:wr䈎*غu֭[uZFUTIk| ])Wp!CiGhgj2 j,F9jƍcÎO>$33ɓl?7gػwkbkt+V*@p\Bv*d#*} o`ƍl.Oυ/Ϙ1- ؃пR^C^* B!Ccr@Q' Î ~R 4` 0oԨ]}zTTʕ+\U:uꄞ Yt)@$_Ζ /5oޜumذX?|̀޺h֬Y1b#G:no03PnH9*CǎW_>֭ۮ]CF 0<9rdϞ=̙Qh7߄6pIn֭{ѿ;wdDDD,^C[j5bĈ7n_gcܹx𝱦MCFGGf)G ) '<ذa3ܜ !`# 0̙3%m۲ϟ??$$$11?cƍcăQp=zK.H*^]v0zj``-F`Z?\PP2sL^U?ka8ղeK^R{auy.^[(VH9y` ۷7}&p kkkAl ؼy L]gIwz C4(~;)[AQ}Cʡ1H9 (O9lƇc:}aCVZaVVVד~W0 6kРA\"Tp`Ȋaoo/nϞ=WoռypV+[o[`lK/^AfΜҁ!xr7N"ѦM>L| :D7)Nj/0{`2Ҿ!CtܙTxШQ#Mlinnn78pƞw^47dE%*XMCcr W&m޼ta$h"^U|iӦꫯEE֭cu`nnΫR} u RRAAOr\|oӦ-| ݻ 80(wttW@P~ҤI4*(5cE۷`((GÆ 1DcEĉ٬ruvyy* *(4Zrp>Q cYx AX]p+WYÔ/ݷoK/`Š 8n:Vdco߾͊X%XmVY0$娦!OO9DM%66v"RrAإK6 1M4aX9EE{f^x!::WPW99…Hf->׳")G%dΝY{y(*R*Gy?ٴiHV+G>}bujmڴasCjI=+Ξ=s簣8y$kNC )G )MHX@N^!KatU< ϏW K%-b|}}תVoN=K8+Jrڵ++bT g6`tŊ'''g667R:"<6Q?>裖-[ b*O9V\ɊbF~AAAqqq%XPYY˗"hҤ ^N@?wX{$)RRʑ_P4vw <g0;]j5ҁO??SrgUmD^2S͛tBVZF6(Q#upن%r`ll̊`ĈX^=GGGV_RhРFx`|8{ /ժգGaKϏzvXM&>|Z(O9vt'''NǏղcڼqqq(b 7-9c&WWCnݺQF-,,X3еkWwww777;;;6WR9|}};bQ'N`Je5 X{Pr0(_aɒ2B!X9lW_}g̘1+8טd˖-XGYxRvŖΞ=˫NC )G )A|ԧN;"2ߌΰIgKrVNdSo`W^z~6jְ3xOr;c.>aт3gjY7oߞ 6l$H}^@?˚w\(C )**\I\?6m@xxV-X-*PaQJ`pyȑ]L 兔CbpW_esOΫ%%cV^9f_®ǫvC0yd"Q>>kocHī )G )`Zk{bl]QXlȑ#:uN`G-Z}K!X6mHë ϟ.oٲ%քWRp7oŋ+ Ttߵtr\zWE|MZn-vo(GD:Q-Xb+ .l)Zj%^'N/{?^b; k1/^uRjRʁ!ll]\=\JOGXvrkVvᲂ">[AAa1 m>r̋O*s DrZU m#O|SOO﫯/;xQff@Q}Cʡ1H9'`kE!1a[ද(3XӼ)ÛbpE,p^='姃bqb "e⥱a9EcFﺃd@Z3ZKyʱA#5ؼyGaBR'jQP 7z㏕LܹslضmŸOBQ}Cʡ1H9Ĝn$"0s%1 ;7|iVaʁt<$\Sa1pUvFXEU9B_b瞺*,𽩭>۹_6?/tGZigdQ dv&-~VWsX܃mTLXj9bkx.lUhgS$UAR9N۷o?oAN!-[,Q:R!x.tؑI[odͪ[~JrTߐrh R1.~Iзn~?bF޼TJm1B(dʁ꽰reo?҃[},k)ٻմ)szv2ZJ\s+=-TOk(1a>H*ݽ{A(Qf⥧Nw)9)sA|3{oڴiVZh_-խK.0 rTߐrh R1Ǩ^e* +4|A 0Xsؓdʡ(}:;k3*cR1oYNhZ/ (Z*jŭw5/ߣ>~U{𒈀T8ʘtζ(*`VJqlwxl8ZiQy̚5!B\n׮ҝۣ֭GHH"ԅCcr(=PJ!?Bn^AvbȾ܂ >IU9-ʬ٤*QYͻ5̛},듞cw|OK#o^\r,لՎ 7FL̾|t#4&StpRoƝƫCAj;{k!x^ddd:tho]UӦM?1c BQ}Cʡ1H9T).jꆛZ.\L9:~3j|BRdAʑ'+z)4n.W{x%YG5dOG2:+?aW{/֤˴ lRQU`###~!,kLNK8dr5RT* {a|BD}BrdisrpA[@Raxr`u7׮N{FY(F@U9⒲m5p>"Zы?se{(.>hE}>]hBl]xww5/*J߷4>EY-Gw29l3w٤#'N9Bg9Byŗ2Xr5Rjyyyگӧ{x]]c[^rcmlsn gOq8D _= v[c-glX>Enk؛WKQU냧pf^eݿk[Qα9y/tU=w&?l߫t<*>Y~r~^=F.. kɤ׬K;,Zt}Ɋ/+n4t܊P#4oXS%U^^8lxx<:~ $ 5K֭H.d{xC)Qa$|==N) (w^BUCj?g K!xXuS8{]͒U{IhPqnfeή_%`Mvaݟ ̾,>9)a.4gp2k'6b* 7[@'V Oviah[t7[wZoU,.$H dQ\@ $IJJӛ钗h,!rTߐrh R%re.~&B[c<lv!cǯ$ednuW$dT OO|,!@z /:;`u9 !D'dٹbBcmf[ajpY`Դg}VN?+wYjxa2 ;|::-+ox3u  W%v!Yo DCc<%yfA\$UG#^֋?}M;q|6&W#ƑǜmyIC9Yщx|#N{o=\"((h*bI[| j<7 W{cОB`t?XqqEaVr+DgDRVZ-0 ɫ`»c<18*74(.)ޖu*,, ]p'Rsٳt3\ Azx$3G~"aNՂ^ 8Z^~nz)Y1EzlaOtV%[M}lAH9oH94)A<)EI !U#r P/4k,H1%?м;0HJ&l :_tnW61ٯxf0gl`]^HLh>1 Yp]LΦͺ]qe=7xXxv1- 5oǾǠ%xM~2}Alm?,.; .%;xz>P:^ OI936j=҆ ɷZ9$: |>82m6ĮdD:,} ڼZzBtxAH9oH94)A<:OFtgpfڀ<^|_}俠ߴ wQ&M1_Q0fP 0qL E" 0{YAu? #okuF㢢C\csLX|gp|%%VYM!yf6/A{tm۶>l-5p*<%{>y=RrkyT0`#nk68Q6ig)iCcrPr?~ :uA9.DgaoU&,6SVP4it~=C->*EŻubQy7d/f˂b4SRuxDme0U09~#qYA9 F,>P d(nQ|`yJav!;1EnI/u6uJȓٺʨ%Ոr@`&mgC#'CdǗw%瘟~??*AH9or&Z )A<abrƆtrt^r+B u2AO )x'^o0WK1l2=@_N:A9LwĔ f棢ᙻlQ;-Y@Q0@r|D1i,&An;rUk.eu;;e6Hޢӳ-x"ߵ4ǟl40"k\/t29g*L9zeE>ٲɕ` /IY9ǹy'񄋪H9iH94)A<ah@e{V,,w2*<>,,zmY۾ c"0)(Gי@+,Ii6b帟rwyK|udT=rQ1۷E; [T)))N AGcTugHN KMʓ p?!/ǿlKFИԌ'~L"XǞz|}1,()́VǾwM-ӕAzrTߐrh RxH9*@PF"X2ah*~ 0NrW1Y>ͩ@rZ^./86LAAuMLΉR-\#4&Sh'lYNn~htzQl-| F^ig4zQiٹY92..e.)G -<_hΦ~;◘x+mf>|ʁqN&{O`PLϒ>TgOAs*xrN;|W⓳?%_÷Z_ ź`lIwjx&O*672 y8J'#征+umc'uQVElV~hEX9\|_&_H1nq򷃿:`;>+H9oUӇ㿐r5֖tx PHe8d*sKʏ;u-Tβsr`Ԣ EA9^eSl7+# !%4ԀߜeUreEz̼.S\\lhhsgh_)sQ)!k=-RxI?Y:c26!g@=;k1aCO_.bʁtM/$3 h#]Rpw0z0e|_iήdWk{[K B%!K>ʦO}/)6:I^U4?O_ -z u? b(GnaA~a_h ,*B"x^ F,h~8<H9oG\)| /DGR9q…:Ohjr[~(B9`x7'%Ur DݏnLT~0qH@ v0BRgʁoҽnT@} F`jdaX ۏKyv~s/+9+LRQQD\vo~9%̃); JrG>qbܔ0T gT/:sMa./w3`@PИtVYg*b0 :w) 7L9x$33xl70,/mrlGR9:2qY7jk¢v?{ͺT94f>"TوEj`[<|gag A9 _'62P@7jYN"VY $twwyB2*ȳWSZ8/;^+S,-5)ǻlctv8J;rwM̖}>ގ۬RuHcoQus v r;X+ ,(G}uI9~K) c7?ZqbBP"-`rBJoqMd]eN()Aܴ^RP\\iy9mᙻwseE|(% $\rH`Ek%^KDv.|џnK _m0ng 'r<QKK>sοqh}APq.\3+?u/abaY>@/y[2g3&?zn*tT%){ė)&?og1e~&Ŝ,$4& G)ĀӅ 0ć꫆k=šjS\bw;UGK.U,Lgq2G!(ǩUyWD(&?M/*UW{7.`opyy>95HJ-s^)iJsb sp4˂Mrl2$)<ڲU候+팥%) fXKONAVKOIkc'J[uޖGj)\_<<ɳW>^*ym~Dg汋ѠrΑMb/OL9^$3[yJf!+vم^7P^RPyMbWe `.髸tIY1WP'g9A|gmCc<%prrBgDTˡ|ϜKi9TPp6~V8iM+e}AǷS ?`«-O6cllR" t_<<̳W{ld/8y5 9/fd+[uн8=3l2=9H6 윣bMc.6>osD\:oT `\`Z8地R:iSMT;O988<%Aܼ! otP+E9d=fʯeZ\ &p&=B/EşbXjŐWp?ȿk1KKc-U;H9vZ5"׷N#VBcoeQsd~=c$^]]?3>+]RwRK9CXIԱ&8t[Bfmq8$ϷZ=q>bB]ɩ9@ccQ>=S(frh?nEKE7*ȫ=( }oRgWPoo^`?_OHl0 {!x%Xes-7R?at&AEwf1(q٣?\p`?7X)u0JBcDdtS9W)`36: ?g=.& *h0m^GG{Iܩw+ ؀xwx6`3*QPp7Oe_xxw٥&bHa7a_zJVLK88 7sl1, &Ũy&8(ł&co Ʋ6{K 61s+L y`S4ORj~rh?L9tS9qGԿF9ݏK_{ؓ 9w %=ůxp^rjvrj-N/ȾJ8 (٦]LbݨTZrذ ~r~XTax:án/Z`z/ ιu#(O9w5{G2l"w/-oWPp'(h[QyU&ۢxQSYUGH9+W[Y]}څ<-”Zz=-:r5 gmuf|\36jpe=O'ka[Mcin @QxQpv,P3Vsnpª%Oq~2{Vl$^ޯYgV~{^Ea+"Z)¢XP !J $ TH@EN ^/w2's}q1}3gZ&)'#`X܏&Dzf[]xxuIy99}:T (y uz#gȡ"r("!F:gNh^ NyKF6@y@Akf?٫]Kn 5z7(iyO*yT#tu((Ӓw9D*{2!zE 6%>9U9đ{WA}i1ԁLXXZ $ik9@c޽{k֬ݸqP/39lȡNȡ"r("!F.ﺜ=Al^§ܓ&%?^Gxpڲ"a,\go|(lР9o,+EFcWɛL(p"_/kW{.fcT9^W=+#@#~ ⠈p/6 rƞ={.]'$3E@0<9WD@ _;\*wɿ/ɆK\|񋐈+مÅA'1rwRq/RX=Y aSǚ$C(rOxo}lXK9 ˺Lj"8QYx /yi4pԌ|qq!7OeOc*ϸ8VP73[xYv3*NDDCEP EhZw}!(,s('S`å,gWu1>1rOSS ի׆ D(r$aʆ_w >Ic(rQׯl-fcå|nYreKdd<7ƍZwl>J9A/s1 _C%9"$&̪+898,wֆ@_9c)|zt3ٹYy" GAGxMT9~;!oEo"ޱ-IO 15hҘ 7"GBBƒa*ҥKSޒm_w984_pU+"b ruTLbBc.*_X^]w=r,9Y@6\[IZo;>FzarQ^~ɕBw1n:%M EP;X- tOVV̙&ٱ]F+89ȡ"r("QAxj:U.J%9m*#X {p(Y]ȧAFv.+Ylq^z|NZA[O]5~yN[`s _7y=urS9[ܩ^))r- S9RD@ 9oG4XN 4a?x|wPYDduog,v^OO<ў)bi1 o>9 'PF.rKUl>?sB3KbPYpuuTÇOTSNV:V:ݻwUT͛7.qr e&'ryE"OXUA'\hӣwOe?I}}p)KkZE))M9E6gic ilT)ViVlXxB_!f+UoS:ڵ~Q)+f׿w[;Yv݃p8N}^͆3$>W|G); 4}CKBY. 挃1u ]sqI֓&zIUVy;KYM.ZdOYZ.ZV&&sgͲ3g~Ξ=xӦͪI&NdΙcaa1O[s&&%,Yx-Z ik~W^.cڵ[g5ػwox5PD| PAGr*՜rfooq Q^;+("0x9WDŠpW;q,%s򊨤>q6 z%?ӹ|"r1-/JsOʯNHCirFV6B-mp9?OūJv'QuP]8x𑞏>H##H?H}|w}NN[z>u\VH)-\ꑚ/25;om9MM͜i.r!\4113gny-L6yӍg2%gϞ͢C{*y ~G"rCEP E(1dWya=El>b+.]CZ^aeB9ȡ"r(9R5k9) rh7-daU6ڵ?R_I|!6J#GrR3 P 9BBB,,l.ș -?u-#F.|jQPx?sBth5ıgf3+xw{ۿD#ex9#us9shYՓRl2J_rֿ`ee%rh4Y,rJJ[֕A_9(KN^?F9jW\IG&?)djD|X=TGnxjx')v5thk4W5"?yMH 5[s(Qr˩\.]阩_~ILk[qI#ѧ fҎ:tu%"0x9WD@!r@zRP("ZY_ar(L63,.R P1r- lD=C19P%ZYӎUeb2?Qvt] DC19P%4ro%33 7H[Fs= DC19P%(rܾ}1@v薩 k9ȡ"r("U ӧO#r@5X!| vˎ25$"0x9WD@ r@Xq}+Vlh2d-Sng^?"rCEP Dj"TՉ<񱶶KLL9«9כA_U92229A*:#::ԪjC<+"b rP 9J|dHJJ266;xpBG C$++ COUgHMM"Ǎ7huQ%D)rpĉ3DEU DUo"͛7iuQ%DӦ >";P%)ofP"rCEP 1r$$0rp?փE: Ȏ2si@99+C_DP E" SN!rA{"l(+Wn+ "0x9WDENٶhv, BsW9`(Eb LJ*+k$5U9D LD=C1t9PpE #E4QQQs,>tI|87zD"ȡ*!"lq3bb L]n*U DC19 TBD0R䈊22^@Ѣ#67trJ42eii]| IsysX#r裈Q/#BC7'!A7Cn]kEZ{!.,?>EtE>r!lr=>>gsCU_1^m5ekAȡ/"r(FD!:W9rz""l"r苈!9 "l"r9 B^pWCILHp BVH;w*B r@apO=t!v"r("B!"r("B!"r("B!"r("B!"r("B!"r("B!"r("B!ڪ0rP@B!@DP D!BՎWb Xu"B!zD۷o7``V%B!rY`]T EɓM B!B(ȡB!jȡB!jȡB!jȡB!jȡB؀FD\:}tpp !EP D!l(®ЦM=VB,BaCȡBR~}6s:קѮ]'!o0nMT-m^w3!V!"b r@ugpp^>6oޢiӦTOӿnVPcح[VTv::;! 9B;ml4i҄U2>GFDpw?뱐:F_&Kxqsw( ڹ(n/ѷBX9B;zk vӽBݨ[?m 6NT;Z%i ME;tk}rLQQ !F"r("PΘ?`C/fo5UnBΘ66n{ܬYs6Y)`}s,ՠ=w 9͛`ͥЄ{8gmv?:$w83$f>~Cd䭅 ;Z>iѢ#&$$rPޡCŴ*B4iڡCW/ V߯@Nsԩ"$۷'nݖI~tr:B>vb#2h#G~)Jhr}$ロ/ Bk*"b r@r?1BrO/C>!gDčѣJJR[aѺu;w3̝`chGKቋ;KT֭۹Z%߇Nw^[cy\܃c:w~E{СCW6] +{YAl߾SϞ32{4FU0hȸhڡC&]ot iNzVS:2MhHRb+)Fv:N:00vӚ5O<4kܲyAqryif)wiBk*"b r@u-RZw<֬Y[hOZ%l9icj M۞=-,+ ^+U$r8~5gU ǓUX}ElYE䈏}Yaf alv2-[nrόY"9plbFΟ†hkP^ر窖|BC1O;!NryxaaץYq S))5UmH96nVDׇXRS``N.YѪ{RrƌM@(ZȱeKC~%9T.]zX6,@}/a̘\Gn9>[oE_m!Έ !TĠ\('G3/~l+m836Vy{IrCC=#ʕAR\/K-eBظs}v8ܱc/G'#l@˖#"nI*ڷ/[Mmہ ?"7ĉY_Z"v&3/ED@aEP D".]>֭TPDJۇȫy KĈ_EEkQɾsȰݻ?æXjKjtIWޤ#uظbmrX6V‰$s'fEܽU{'0hT4k֜X֪Vȱp;khժO*Bk-"b r@w`uUݺ=z {߾|58WAIÆgÆ8ws{X[oBUKӷ$"]>icz|͚DF{RW@F,_OQ_|a*c}=4߾}g++Wq$ ::Kh;/^%oR*rpτÆ}uk13ǹBX#9BXw̶ۙq}|c|_Jvx]&.tTiӞ{ϒ]Y G,Wv:zxzN\lZb#Gh5;;a|_,\́ԶmwKh7/JiӲ >Jo9f (=T- ROT ㅠ.&#CaDP)SL !5r]mnng>RT-Z45TEL.>Hn30VOv҃ kXC43f@>l;sEcbD֯g&\A7|gY!C>Caȱn:89ȴisjzwtWfxͮ]˪yK4Rz J$9 FZ>{+ c?O~asON-fިQٰґcʔl1kFn[Es=/pWH-.+VAzx~F:CatpHضm utnD nCa7'zuح[O?\ʜ4iYv8m~BB,[O}ά,i.=jU0LhD-##^^hޜӬYsqq~?G߾}'*vnM;SѩO>{3驧pw>رk>}͛qϠ{xݺ=E׽3Æ}ɿ시>' Z0ʎݻO 9͊} /m[BkQ,p rԑyߟmb!Tzݛ[a°4Mo_^e=HիCo5 mh5*׬ v-00 IaC/[i|}3.Iʕh4oI^XAp<ҩUP)m"ZNZh7m:@O]I'5y@Lzkb72i sMriѬ(%XH۟BXW"GYw]C!Bx\#Gjj*V\]]m61B!٩S;vD:rAk ޼!Ba4&)Ϝ9ŋj8rrOVB!6f=m%m܎B!Pmڶm8P B7D ?ryyy/_>w\PP)66..GnrB!†?sÆHSSɓ_>11FePޠ"4A.s vvO9mY/޹tn!B_kksfͲ?ť2*D6''VT? @=vС,OT bMr*aA;֓v]iiiyyy]),,5|2ok+lT9g]  ´ڴT46*mu䐠AV>++T" z ь *srrRe5ނ!EDb rt"@ rt"@ rt"@ rt"@dee!rtEő DADADADADA@*Izz:"PVbiR'Jub233oA U-T+\4A3PgąTysV ΩjEhC<83J$6%lJ> ;dUxVQ-,V7aT  a!5E޸qZ)-8M0씣Vء "vBEҷacJh؃n$;;@=$ؘa'zN]{l^N氓}=Bʎuڿrss 0"v*ϮCs]r{j9 "dBS'⫇|.+*FnvT%TJXy_eQtZIENDB`pycadf-2.7.0/doc/source/images/observer_cadf.png000066400000000000000000000770521321055627100216270ustar00rootroot00000000000000PNG  IHDRO*LsRGBgAMA a pHYsod}IDATx^v人h}A B. j4<08,P4(1z=~pFʕ]R9ÖeY%MU-"{>/'|CF_ W>|@. 䂾@.k䲾@. d?GJ`頗奊PEDD`Q"""ݢEDDϟ?V&r5/""-j^DD[ԼHynQ"""ݢEDDE͋tK>Ih~戈a Ij^DD9I͋A6|"'y>ȆO$5/""E:-rTd'r?,""G@6|"'y^|}}";E<ۜ$" Ij^Kb*899I͋GL?7Hd'rz*`BOwB/7! 'yN僗HEzbT> ]cBANR"=j* H߄EaT>pB/7! 'ynX?ЋtL>Ij^F_|ԼHl2ЋJ>Ij^6NEz%$5/O'"]r9t>5ʽHEN'"r9hNE#$5/rЬ_%!NE:#$5/rS-c?<<,b}r/! 'y___v|)'Hy(;ANR" p&y%AJ ^4! CNE vԼH7A) ZC&DNR"}P<ɽd#SE`$5/t5ٺ,Ez." Ij^W+xyEDlDNR"ElDNR"ElDNR"r}}~NȆOynܞ.=!@͋j^ȆOy);Yޞ.9 5/9///777L/}^Dz%>E:ּ 5/9yzWDȆOyQ"F6|B͋t9ϋHp5/rDyc@͋)j^P"]=Լ1ChwwwU>#5' `w2dfm v\ӭB͙Ҩ^^$ל+ԴϢ@41珿9??_*r"êoKdh8;;@9S9p9ӀR!~1yܝ4 \_~zprr"sԼ|Sqm^Ze:3<mԿlҘ~t6ٱymα65L?kjEZ(6$sF$K'"E Qz}fT)xN 9Tه=@ X35VW߯EzRifFyEDv]C뢵ń=D>-֥lSxO5e,MG &yurzzQ,B" sLz&2l)D JX] U_jBU@d+'5,*պ6%>H)˩^/q2GⰴBH5m]xQ`)jGd&JԼM'4~1 62^&~NW5 9z-=T٫~\hXd23c+(MIrH)<ܲMΚXQroM4sٛ:P&1.Rלc^ًS>Eq:qh8|OÎT`]>Ubkf9ߩ|5/;vX[Y|S#&OY`Ha1\"\Ab8hc`&?Ml!akatx)F"x%e"7Ԫ:s\@J7ݢacM'T[jkLJ)qY '˹ck\ٔVOqpQ+x>`1FfDsQٚkN:<و̇o Fϡ֌ $#5' !x)kh茳/0jaUz5?*D)s.KHD-'5 @ r=)U.&r"1DyxD[y}Q dI+(\v`!䤏J(+ٜ]FYm r N-'^yr,Q Bi.fQHKhq״aP Č'IMPÎk24İ[@ ygB~ Y7WtNVI5ԍV'H̀o[K n#8S4NFJ!HlL\_(i)>!nDdSDv y4{jp_jW`xe@tv'XHĥ[b@/@ aq(QዄfngQZtj<{] XIt6jJ^_!Mkb_NQ6S9׶~J@:חH:7(^_8zKkndK钯IdVԼf,5S3H:n0Db=@G=X^x[no^_Ad WԙOM?qF͋n}3Qo2P7@%9aD͜8xFi._u-'jK!y=ON!8}5BdBԼ.S_#}B! a8niw@:)(1\='Q8\;x%mhs!_?,R{H3e/pgͭ\QHW܋1%])'9C4EĜSTܑͣf[#2j^vM|=țU5]:5ZvTþluz->pC5k ((QyOT:\2FTf|VB]uUKb\p&kQ"% ͷ=b_sdE2<ۣ C<:nC]}UL]Cp Mc.rb Jewb ĨMX34 !Cl(0jCʜ;΢gJ`.( b[E N%sjqjܠ-NdHՀCc{5@3@Ԫi Hy0|Rj[ӊDvmҀռHiz%l1.3 Zl<:%01viU.vbw*ԭ)S}'jq1>&}@UBTP.8{A(V){ĿdOH@d渱;4@T,%y= ;8So\R@C:O<>@IbD")DhG>*|UL]CiyyCLIM2X TIPII)PH ? 4ޅ. !Np*jH\dRHoNAbC9u5Ջ]t<4d<y)kC\ۺ͍f.Q*Nss ^£)$4rzE5ל3n(곭BdZݴ@6WGܺcݚ ΚKNMUw+~[)(d͙B42'e}MDzJR5/'hs͋Hd*t~>n75/r\wuvsQ"GJ\sԼȑ,o75/rD=t9"x9"os7" j^@ͯHߨy#"+j^(][͋j^HQ"ǀ9"ME5/rDo"ݣEi/rly#B͋j^䈨5Ot9"L7X^Q"G9ԼȑE5/rDе?Gj^sԼ|n.= 5/9Aȱl^P"dž:6O|,$bYp9dԼȔR}K+i5/o!E&><<䕽nyE+Լ4<=&!,ÕQj^Y3B_W{rE%>V4jp98ԼmEy]zAK7y-x:K^P j^d^^^&Լȧ?tnP"pN@K7yMS߿u5/ݠE6Nj^A͋C.ԼtOry5/;j^A͋9j^A͋,g?+5ko|j^A͋cӓb.r^\\rߟUPگ_(p_)gۜcՠ̜osQKyԯ/H2շTa]"?iAYssss Qϟ?(;tƒ.%D5/}qd5/G 9:* >>>F?(KF~gԿ Ǘ C(tLjΚ͓!9!~s|TZAso"[qd5/lLLO[ő%,}!\y%AGy61;.@|^%x65<y}j@͋|ztnj^,DoAx Ǿ%6լ<94OU/Ϲzl_TAa '777d#R> 2GL#;xa@Tj^ݔ_o"4j'>ŏ"ivL3i>>#]TPrLsABB64X FZ~J=*fjՆ8k-b$jN 3pKM  (zj^@B#g(pjeDդkl_)M582.N-0/%%gQ2"OWPEq3bFM%'Rj^;֯N'W?gD J1 j~Hy>! ptv/\__ʰVun3~)b@ Eq&d5/Լ#4SIյ1;B!z#E_ړN95k*5aĠ.o j}1烚x>( EBс_q->OEQޔALPZM|0X0+>W縀q?U:!EFq1Sa#,5WWnWo9(Z^S8B%eCoC;xTcHΤEv#~ W?29~R/І/oF éyQr,0$nظKkE^!p*ޤ˭?k_yB>@N gQ+ ~ I$I؄P=5/}h x>8Vp-@b|J?5Q zF}¾o[;2y~zcnS`Pf)d>>P94f2έfA؋ř6+1MgCԨQj3}@ɱ 0=/r=gM4pAb&>P349pnD]1Ll>< 5lNb6\@{l;mCN@Q7êrvy~46.PHmt2;'ҜB\(-bkd0\[N"nA3tFKy&I0 y5//mò5 j^@Ko<>>Ʒa@Tj^F{?6M`@Tj^:F;v0 y5/=@ss% j^ 7ℚCˠWD >ȍ8PQ2- j^ 74ϒZ|[Àr#NPL[^?ռAnĉҒռiϟ?%>M;8c?fp(0 y܈%y90mjNOOsA͋|#'JKVr`yy^_"g:Լ7qd5/F߼#$론E܈%y90D񤿼䤊7ҁl9iYΩg՗H}99)* &M͋|#'JKVr`K5_bpss]ўE`hr9ysUX8==e#RA65/F(-Yˁq$||$q%3*%O=j1t4 9)l ⱃqPRb(yo!7Dij^5 /3 ։\0}I0. I |:"sIE܈%y90z<;`FLI?;;ܝyyA CS~^q!^ }h* ?gA5/-F(-Yˁѫ.nlţN[x!k2t.VC$V ܂x|ol^ɍ8QZWb+ zX,H\E<{ZT=$LL3Tj'auhD@͋|#'JKVr`tyx{{ <}^u͗Q"EnĉҒռǠy^__}^_j){MmU' +r#N84sW@,C&[YDža7 Jo^ c|fE܈%y90D&&UZR'f {{20S ~[Fq___yo!7Dij^#vl777*p*y`D♃'vX&Q͋| 'JKVr`b q.e&1n)!zuo<UT~62D|B~F ]1g:8k5/}qd5/F%|'KAj^ 7Dij^ ,ҟa@T'JKVr`y!j^܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZC˴y܈%y90ԼLnȍ8QZ$y܈%y90hh^6aX%Y<4y9dr#N倡-^^^nnnu C9\r#N倡-noo///l.'JKVrtsa* gq<%7Dij^hR+Y,k KɆOƬEz5>h>oN j^[E6 =]oBAj^+|pssCwB/7!@͋p*8|P:釥S H5/!z yNX3oЋJ6|B͋|^cBAj^FzɆOyd*8|P9x6~^WB_Ë4O'"]ԼH'|j*8|E:S H5/[L'"JV"̪UX^3B9xT>=$F'g>BRЋtF@͋ȆOyyyy"ݓ (]^͋ ӗeHd'JW",o7lDj^" (]^͋bdyHd'JW"}7El5/rld'ԼHyc#>E:6^{j^ړռHd'ԼEl韢dy5/9 5/9y~~e5/r d'ԼHyEDl5/rld'Լ}+j^ȆOyǀsL|)L$gԼ7@w'^I<ׯ}Oλ俬Y+)"_$>Ed^w:www9ijy*I^JWU";:Ą~___cBNK"9Vg?=gldy(ϟ?QY/e( ޫ"39 :`oaxO^iޛ7<4ߣ:etjҁVoooOOOs5?|X"{=>>r }OmbbEd7$gԼ7F|xxp?ڎ!11:$7;bbVr.y:x-ad+o <.P,BmcX,ؗ ]pԊq,6y">E+d/CȜ)l8Hdsdl]̊I,'"y p㠔T&X;VIy=E|Ev )6ļLw3)V񪟔ڬo&,cn SEd$g@ͣF$<&kB⡡`>ޮC^&|kNJ H a|I"[BAj^dy=ƣcOqsgӟ#Ch˴x#('' 24)O?Eu c6͋|#z@͋i^O ggg7 Կ{0>ԪFo@@y]Cԇg/%?!XjžJ6|B͋\#uқőh=͢OjmИct̾ 7~)P&Jnqh2CCNJ΃E|]8z94T;ȷԼȮE^* 1{[5zulZEξ8pw+"$+ռȁA5'mEɆOyC%WTE'|EU$N6|B͋*yxxnj^ȆOyQ"GE6|B͋Oy?l6,kz j^丠۫y j^丠۫y j^ռH߄5/?E'>EּtO6|B͋9*j^<2"} P"Cצ2 j^o3j^丠۫y j^Et,"]ԼH<>>ټ P" P"ElN*ByEHd'Լt#&g26OOcA tA6|BK'0Rۀ7%.ȆOy5!j~ȆOy5!j~9IɆOy5!l#$][sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[sBK'y5/[s??GI9\Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9mj^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NP5j^:#愚NPx]Kg֜P {yxdP5'Լt.݉ 9oFK֜P <^g:~vvu[ԼGn 5/0Uj~;S5/[sBK'̪WT&[ԼGn 5/0'Cǟ]__>j~oQ5'Լt|GY kLJ(p~~^Q{ȭ9fk6s䄔oHgkΗ]J GO%q[#92ǎ԰d`y5yUbB %q(MKg֜(Qa3ooo%oXb777y2Ϳ]__O?mMf8==t㜩"!gMDv Hyz]^N(X]z(ׯ_%'7$(+ټ %23r:y"_DK֜P shge`5Qo4mcɡt!o[m4IPgΫ,2Dy65o>Đ%713rkNycEC+Y> ͯ67%4e|"gMHȏ`zN!Ji,tʉs ̓K35TԼzrkNy94()xް Q \lZy1\5 r z+8s $l s7^C`xXy; 9zo#J$]\\ o,61`P3j^:#愚NC뻻a5OYP777tZPLAsҏTk~]%YL|GE+7Mzռ!Qoj>oO73rkNk"Qa39Dް&9wbvǼ-Q)j73nLpyy.{ W88N͉xOԕr#Xnٗgߙ@";#愚N`\Իf6_Th~Q2.{)/݇'K s)i}kث>D&9Vް l%7k75p6|}Aث~cjs mi6S7@͋|ܚj^:a&Ϳ>».oK4|%6 MA-xyVy(7|޾SRQԼWȭ9f< _ctFN Tfy`GJy4{[;]rD(a১ߥ&ԁp@5Ic4yRaR8j^+֜P 1RǨ=-t3Q[n|nH wA':oñ ǭ?5ັ5o@{%,͇>+}5/rkNy4>t˯Ԛg:|CPh:jް?6ox1o^M}vAyok  5/rkNyY50е LL1ӚϞ[_!fdn>guX;xPʹYH\惀7S`}Ȝ&ޜ;A7C!gGмԢd_<̄ȭ9vध pXy 숕^),oBIE%&Gd_g_W=p&]`+.!"C  j^:#)ռ6;Ӽ􇚗ȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լlȭ9Լl@Ay5/[3ԼtQj^:D֨y 5/ekԼtQ5j^:CKy5/CԼlP!j^FKgy5/[3ԼtQj^:D֨y 5/e;ռtQj^CKy5/CԼlP!j^CKy5/ۡ?ԼtPj^:Dvy5/HYŀj^:@Kz_?x 4Լ t4n-Pj^zFͯik!,EKy5Ǜ"+Pj^:Z<=FyEVې!E."Eyz~"pwt5/?dy5/'p|s$Q"Ev ,}}]\ ^^^rҿ}wwGbS@/yQ";g_\\ 6xRӳ5~ݖH!Tn[䷷7=< Qud@dދ̊H(|)1YYz%^v`P*>f#cv!-"?Rꍜw#sqԳ$DbG9;5/#S#H4sA< X |l/8O$R=?4,R;e> /S $ql>Ed>ԼȎYšo‘:He\<:6Sy6aY=<<4F'no y5Ef!aDh˛vl&B1=hٝaɆɀ#%< g Q&P2 Uo?|1'vrS=*IcK{HT"@Ě1/5؅<^,cǘC|RNN]^^UK&gQD2  d3~/F} Q>)l2Ed&ԼNOQo)ly<rD\, ,4 J`sb<;yN ]؀)S~<0_-̾uAFdycC^P"FN/)j^WԼq_ګy^5_ռH`wP"G9.Mm^Q"K!Hwy%K^͋9.^__˗:^oԼqqNEG͋E{ԼqE 5/oook^ך79Y?+?u"rpy___>??ӋX3.e,CG͋tj^ÿ d;\^It z23,W2Hgy'nj j^Ck&|ؽD@)JV" NE5/'OM诮cAPF9\ԼH|jBAHynKo8'[] "rШypB"Ez^=:w*/1j^sF'NE:F͋t{̈́ީHߨyY3w*/7j^K'$"}Ez"ݣEw3gD5/r,4z"ǀ9eBE5/rD Sy#A͋tr"G±haMD&+Sܛ燇 "tn"" 8/ޫ+͇7W^^^^_am/t0C,aPkBy΄v火Ia,}hƺx}*+y~~94=LG>ި anޤab3|EQaƮߏ8f'yyag\n0 u{XfEy*Іa-LE71xoaW{|Woac$cl=42afiy>oaaO/.aaQW{у<}]s) x>N_\m Ø)b^þoE&nѼSy"8KSHY^__]yfp Xi2=zu@oz||mf0z} ~04Gtal8-t ̱X,Bo04dG|]8ާgØ5k1to'g0~?]z)qLlPaD|y<۔z%xO瞓. 2wڢ Ø"p<+]r_aƊft5'^aL-<y X񕺥_ң0 Ø>;+n[Լav\|D!&nMalj~wz cIoV]zC@alj~Լa,_dѥ7]xnK1MMPbx{{˝d5ѥ? =Ώ cP 0~]z Ø &y(o鷹{]z;>&yx4^0c3Ko %9}M'c~3Jt/PVa5 j8 ܭQK?0 5 j8X7D?0>j~Լqߌ]z*/4&yc_%Pf_hMPƿ~3JtɡWQa5oU|oF.=~a5oO|oF.=ԓ!i+Co7"p_%|p?0V5o(ѥ17A}?sߌ]z9$&yߌ]z7ҜaoMPF:D 0rMPF:_]zpPg9&y6!Kyj~ԼS?S&D~)8PnvٹK<)8P>7.[N.p Es5 (Bo7>_nGtj0=50&yc_]zj0:5 j8ȿ 7nHt=̐# 5 j8Р~vCKZF7AsD+[0kEMPƁEcnJt}1kFߡ7AWWl7$~“ojzP捃oRRK-&yMPAĎvCK3^O!&ycY)ڟ^S.pxBKyF7A{;oMt2ӍBo77Rf*7ǃ#jG7 5 jؿ/0'>:9M){$&ycupЫ73DiI~)$&yc߂6y}}W]]]‡NI$RH? bFj~jدx}Ŀyi۹Oe RyrWL#q*n\a5o@Oa"oC󷷷!o`Qҗ/.."tr`sD':7;&d c_#{3̤bt,*}N,E$+'MT 0C͏}N 3iϟ!e]9j4qQFg|N_/ٚ&ּ6<8j2hshbej_.//#q*ԼaPy˘I+x'''OO#<ݾbH2ԼaPy˘Iމ_~a2qgb5`S G]L, `.BF 5?74Y*NOO߻Ԩy(QF1^__g D5o%8j2f|@|= O7j~5ot;<ЗsJ|y(횇|DtR5o4{ԼaPyPq QF1QS?ޮGϟ 5o%bWȝs'yØ=f<#KIQ=*ԼaPy˘OJ|QF1QlEϟ 5o%8j2f_=]S G]L??;;{xx@ѽW^BF 5?7hK5o%8j2f|yizz:ٮGF 5?74gQF1/..J%j0JqԼe̤yӳ] fS G]|D2Gϟ 5o%8j2<+x1_qԼe̤yA|QF1ooo7a(zTy(QF1샫+FϫռaPy˘I[RW5o%8j2͟.cԼaPy˘I"4rrbK5o%8j2fgq cPy˘IWWW!ҥУ"c1_qԼe̤Cs@]cG]L/h:j0JqԼe̤[ҞCG6WN5o%8j2fMץWOPQB͏.c&,oooP/0 >`kPQB͏.c&Cyo ?,=*ԼaPy˘Off'DF 5?74t(O7j~5otj///3~>PQB͏.cnɵ_9gBF 5?7h>QQB͏.c&ӟVjt!==^m;Լa_A/G]L_, };7B͏.c&ކk^|QFGdaj~5otj>=(yxxx(u0Կ cPy˘Pڷ&zTy(QF1Gn=O7j~5otjׯ_؟O7j~5otj|nCN @~BԼaPy˘Pp*ߴ|:Do5o%8j2&|͆?3+j0JqԼe̤yʼ&y(QF1K5o%8j2ԼaCqԼe̤yʼ$=*ԼaPy˘I:?j0JqԼe̤lY,BF 5?77B͏.c&_.ROE B͏.c&7D!=i<׌87|y(QF1Myj>Uj0JqԼeLyef0g~rԼaPy˘Jt:0ռaPy˘J "Y|NNN޻Ԩy(ogN cDtxΰB)"'[,ﯯKPQB͏.c;777MM :_iBPQB͏.c׿`6iΤ?C0j0JqԼeL}ZԞwKrDy(QF1zrNH]2N7j~5oth5;ԼMhG<)G6|ݡenLyJg`\fM8 LjKP27j~Z<04?Mo 5%j~wy5?-SiϤ'v _ 5%j~wy5?-sk(y^wj^FO4bO^wQh~OLenLV]Cwj^FO4tgqǡD5/s]ÈX,vyocz5%j~wyoǢC.%7ӪB,qa9\ H_ʚU"bkgHs+ Δ=H5/B?Cp&7և5;Լ̍;{sC5%j~wy5G{0-|ݡen|0aNjKP27jcs^wj^F Cwj^FwlLD5/sF"7D9p$|ݡen1ƍfhnCwj^F qWwj^Fw.1|ݡenQ _7+D5/s o[5;Լ̍?B-.|ݡenvww7/ޫ.QCܨㄱ-Bw Q;BܨU]wQ #Wwj^F9hq5;Լ>kn#&>9/www]s74O(3\__ӆ5P27{yFϟ?Yfޙ ビƋ/0f<#07=_2y^夝w'2{ 9I*Vyq";hۙ~>x K/']sM+8 S[.Z\Psꗹ=xGuN  0oSܸk p֌yz.Oz>p]nu4O}yt.O, GpM3X3i(6e hyB޹ 8| 5j~2sc1 7|W.17$>m`ռ* gh4w^h.5oG|}KBK5M{2 i kb>*3"w]c޶df/XW)c^;R| 5qV9;5%sh+P"oHp]g7T*kwL:sF5뛼WlƠ{sjfC {y3ϩ~b8,_\\PEi x$CXʼn@f ΣXv8(>4NJh>3"gLeޭ0'j~wFCPM45dGv7|Ƀq',qev|=Ld@?~|+D؋{/ h Q`@&odva561S1RfŬya.l9^ј#[V1scO[Ufp=K S@$v_m2!g{8] %gN_k~ u5?+tEOswę?ZWdLBc4$Jv[3vi(lR揯p N48 Q0jKc5OK^+̙}OxhJ(cΨ':Vy4kxrFGΙPs"\hNsa9DHo ?ܗUvKFIQ%g94p#2Qa(kI0RA>Bg/ &BP%.N:3sPc>VoޞKs PHF_ .W^m+@!99ʯquk($2KMeoMr "KGgnn%Sg\&3#`SNz$\Β#'9)A1uwyAoZErcvRsx@;'8a湏T] g\ y4/!J^듢K sñZwy"oH.lj =3 aQfʐr,PjD޸9 caC~"$O_ZiWpVDR}ӛPۼ9ywb c{Ok5_?+;RTx ueA?@fwqUo9[J3tlqZPF>g8DyCj@]46tɜg>8\4|lVkC@#7CuFV=1 DNR?}C/2OY͐Gze7 Nlnhb u#Z7.ĦVgs;ByC7tHkF|2k4{ܸԛw?~<4;UjCgcL&C!"Z;6}Ġʹ͑d#Ys,9:08ZEN^+*]6תU13qG\z^nw@_C/+FD5uR~6~>.h\Ϧm[s%9գOqItz<9&:29Fkt.QN[&t-QtɜPۡ'SgnOF:͸t˥Csu= k wS7'X܌VMj~GlB\ɥyǚoDPνyo_ͦ19k&z.o7ۺ pМx73MYs\;?A }i39u3hyO5?j~26|Ӭ]^l65N@ ˾7z^fYhyDNvO4__v`˛6ɀAcr/t{Դ>Q UmZeܰ[} 5OK;i@!IBF `u[ A!'BO暧 fb4ϩ<@=0.H1n]2XU9Xj~GpA|\a^-4_6k|}7\nh;=h>*u5j,y*IcXHbZֺ[YsP\WY{ j+oN_<˛|zY͓y М#g{y6LpO;MhK/>0 H_gs'^Um>wQ?8sm;kI;`͓Jдjw 6G cPۼ[K7gwzr碧~@>h4Ssj閜]P qz$\27M}.Ѫ|gyQ5nR /+ysc4m ohZfNT 4:y_uoq1J#q%w0_Rẇjs=g5uț49,R7.RNpfD8p1sRq^d|f/K4H&|Y]zj:CVDǩv`܋1rOcSshȩQ8b"gg8"pt.KYe\T^lm^g4yAi?9;L?ԍ [&s೚5 KӖ>fRdyqy<v1\k57i~ilP̧%=b4oC]%ƎZQ_ :pᡱU#oXF}&Uw)ͩg恋7|@otPu`DԙnR8lys= n[k858J]=7y>>yԃpj~Gp|0< F𼡂F7'67MmY3|=L95QoZͣZmhKrDsUuh 'ox`/*7|@҉ޑ//-ᣉalj~5oLa Q2՛V3]Us޵~>PƬQƴQvvVoyCb%Ldp~_LydLCC ;$ʁVUIg6`ի|{p1jޘ58jޘ0%?Rꨥ P'؁9 i5k bS 9m*6J9 jPyc¨GMnxh4<[4O˩jSXS^768jޘ0jl(6D`&|]8/"`d]ojȱęPU4;yc?QTшdCJkB7_`Vi~8hBvXҨ /MEذsmQ445)yc?VQT 73j_G)Z7Ws'|M9)Q{y#kS;U)LM cP捩bC4DE獒)6=:aZSNk "2srRq[S7jI3M]:ر~Egggy[#dbМ8jޘ$hEy,O ePG(6 EWo󟟟3ib@dkv j/9:dL5a,P"""]ԼHW5/""!@͋tE>P"""]ԼHW5/""!@͋tE>P"""]ԼHW5/""!@͋tHE6ϟ?y|A憫urYےKEhGXyfy,j^D'!7F-{BGK4˞|"ɃcfHyIENDB`pycadf-2.7.0/doc/source/index.rst000066400000000000000000000053541321055627100167050ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =============================== PyCADF developer documentation =============================== The `CADF (Cloud Audit Data Federation Working Group)`_ is working to develop open standards for audit data which can be federated from cloud providers, with the intent to elevate customer's trust in cloud hosted applications. Specifications and profiles produced by the CADF will help protect the investments of companies seeking to move their applications to cloud deployment models and preserve their ability to audit operational processes, regardless of their chosen cloud provider. The CADF develops specifications for audit event data and interface models and a compatible interaction model that will describe interactions between IT resources for cloud deployment models. pyCADF is the python implementation of the CADF specification. This documentation offers information on how CADF works and how to contribute to the project. .. _CADF (Cloud Audit Data Federation Working Group): http://www.dmtf.org/standards/cadf Getting Started =============== .. toctree:: :maxdepth: 1 event_concept specification/index middleware audit_maps Contributing ============ pyCADF utilizes all of the usual OpenStack processes and requirements for contributions. The code is hosted `on OpenStack's Git server`_. `Bug reports`_ and `blueprints`_ may be submitted to the :code:`pycadf` project on `Launchpad`_. Code may be submitted to the :code:`openstack/pycadf` project using `Gerrit`_. .. _`on OpenStack's Git server`: https://git.openstack.org/cgit/openstack/pycadf/tree .. _Launchpad: https://launchpad.net/pycadf .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://bugs.launchpad.net/pycadf/+bugs .. _blueprints: https://blueprints.launchpad.net/pycadf .. _PyPi: https://pypi.python.org/pypi/pycadf .. _tarball: https://tarballs.openstack.org/pycadf Code Documentation ================== .. toctree:: :maxdepth: 1 api/modules Release Notes ============= .. toctree:: :maxdepth: 1 history Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pycadf-2.7.0/doc/source/middleware.rst000066400000000000000000000017231321055627100177070ustar00rootroot00000000000000.. Copyright 2014 IBM Corp Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _middleware: ================= Audit middleware ================= pyCADF's version of the audit middleware has been deprecated as of pyCADF 0.8.0. For continued support, the middleware is now maintained under the Identity (Keystone) umbrella. Related documentation can be found here_. .. _here: https://docs.openstack.org/keystonemiddleware/latest/audit.html pycadf-2.7.0/doc/source/specification/000077500000000000000000000000001321055627100176555ustar00rootroot00000000000000pycadf-2.7.0/doc/source/specification/attachments.rst000066400000000000000000000043741321055627100227320ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _attachments: ============ Attachments ============ An attachment is a container for data or "content" that may follow any structure - from an atomic type to a complex hierarchy. However, it is desirable for processing and interoperability that the type - or structure - of the content be identified by a simple value. To this end the attachment also contains a "content type", i.e., a URI that identifies the kind of content. Attachments are intended to be used for inclusion of domain-specific, informative, or descriptive information. =========== ========= ======== ====================================================================================== Property Type Required Description =========== ========= ======== ====================================================================================== typeURI xs:anyURI Yes The URI that identifies the type of data contained in the "content" property. content xs:any Yes A container that contains any type of data (as defined by the "contentType" property). contentType xs:string Yes An optional name that can be used to provide an identifying name for the content. =========== ========= ======== ====================================================================================== Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "attachments": [ { "content": "xs:any", "contentType": "xs:anyURI" }, { "content": "xs:any", "contentType": "xs:anyURI" } ] } pycadf-2.7.0/doc/source/specification/credentials.rst000066400000000000000000000044671321055627100227170ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _credentials: ============ Credentials ============ This type provides a means to describe various credentials along with any information about the authority that is responsible for maintaining them. This is intended to be associated with a CADF Resource's identity and reflects any authorizations or identity assertions the resource may use to gain access to other resources. ========== ========= ======== =================================================================================================== Property Type Required Description ========== ========= ======== =================================================================================================== type xs:anyURI No Type of credential. (e.g., auth. token, identity token, etc.) token xs:any Yes The primary opaque or non-opaque identity or security token (e.g., an opaque or obfuscated user ID) authority xs:anyURI No The trusted authority (a service) that understands and can verify the credential. assertions cadf:Map No Optional list of additional assertions or attributes that belong to the credential ========== ========= ======== =================================================================================================== Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", "action": "authenticate", ..., "initiator": { "id": "joe.user@example.com", "typeURI": "data/security/account/user", ..., "credential": { "type": "https://mycloud.com/v2/token", "token": "myuuid:1ef0-abdf-xxxx-xxxx" } } } pycadf-2.7.0/doc/source/specification/endpoints.rst000066400000000000000000000035671321055627100224250ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _endpoints: ========== Endpoints ========== The Endpoint type is used to provide information about a resource's location on a network. ======== ========= ======== ================================================================================= Property Type Required Description ======== ========= ======== ================================================================================= url xs:anyURI Yes The network address of the endpoint; for IP-based addresses name xs:string No An optional property to provide a logical name for the endpoint port xs:string No An optional property to provide the port value separate from the address property ======== ========= ======== ================================================================================= Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "target": { "id": "myscheme://mydomain/resource/id/0001", "name": "server_0001", "addresses": [ { "name": "public", "url": "http://mydomain/mypath/server-0001/" }, ... ], ... } } pycadf-2.7.0/doc/source/specification/events.rst000066400000000000000000000133261321055627100217200ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _events: ======= Events ======= The CADF Event Model applies semantics to the activities, resources, information, and changes within a cloud provider's infrastructure and models these using the concept of an event. ============= =================== ========= ============================================================================================================================================================= Property Type Required Description ============= =================== ========= ============================================================================================================================================================= id cadf:Identifier Yes The unique identifier of the CADF Event Record typeURI cadf:Path Dependent Can be used to declare versioning of Events. eventType xs:string Yes The classification of the type of event eventTime cadf:Timestamp Yes The OBSERVER's best estimate as to the time the Actual Event occurred or began action cadf:Path Yes This property represents the event's ACTION outcome cadf:Path Yes A valid classification value from the CADF Outcome Taxonomy initiator cadf:Resource Dependent The event's INITIATOR. Required if not initiatorId initiatorId cadf:Identifier Dependent The event's INITIATOR resource by reference. Required if not initiator target cadf:Resource Dependent The event's TARGET. Required if not targetId targetId cadf:Identifier Dependent The event's TARGET by reference. Required if not target observer cadf:Resource Dependent The event's OBSERVER. Required if not observerId observerId cadf:Identifier Dependent The event's OBSERVER by reference. Required if not observer reason cadf:Reason No Domain-specific reason code and policy data that provides an additional level of detail to the outcome value. Required if the eventType property is "control" severity xs:string No Describes domain-relative severity assigned to the event by the OBSERVER. This property's value is non-normative measurements cadf:Measurement[] Dependent Any measurement (values) associated with the event. Required if the eventType property is "monitor" name xs:string No A descriptive name for the event tags cadf:Tag[] No Array of Tags that MAY be used to further qualify or categorize the CADF Event Record attachments cadf:Attachment[] No Array of extended or domain-specific information about the event or its context reporterchain cadf:Reporterstep[] No Array of Reporterstep typed data that contains information about the sequenced handling of or change to the associated CADF Event Record by any REPORTER ============= =================== ========= ============================================================================================================================================================= Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", "id": "a80dc5ee-be83-48ad-ad5e-6577f2217637", "eventType": "activity", "action": "read", "outcome": "success", "reason": { "reasonCode": "200", "reasonType": "HTTP" }, "eventTime": "2014-01-17T23:23:38.109989+0000", "initiator": { "id": "95f12d248a234a969f456cd2c794f29a", "typeURI": "service/security/account/user", "name": "admin", "project_id": "e55b158759854ea6a7852aa76632c6c1", "credential": { "token": "MIIQBgYJKoZIhvcNAQcCoIIP9z xxxxxx KoZIhvcIP9z=", "identity_status": "Confirmed" }, "host": { "agent": "python-novaclient", "address": "9.26.27.109" } }, "target": { "id": "0f126160203748a5b4923f2eb6e3b7db", "typeURI": "service/compute/servers", "name": "nova", "addresses": [ { "url": "http://9.26.27.109:8774/v2/e55b158759854ea6a7852aa76632c6c1", "name": "admin" }, { "url": "http://9.26.27.109:8774/v2/e55b158759854ea6a7852aa76632c6c1", "name": "private" }, { "url": "http://9.26.27.109:8774/v2/e55b158759854ea6a7852aa76632c6c1", "name": "public" } ] }, "observer": { "id": "target" }, "reporterchain": [ { "reporterTime": "2014-01-17T23:23:38.154152+0000", "role": "modifier", "reporter": { "id": "target" } } ], "requestPath": "/v2/56600971-90f3-4370-807f-ab79339381a9/servers", "tags": [ "correlation_id?value=bcac04dc-e0be-4110-862c-347088a7836a" ] } pycadf-2.7.0/doc/source/specification/geolocations.rst000066400000000000000000000075031321055627100231020ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _geolocations: ============= Geolocations ============= Geolocation information, which reveals a resource's physical location, is obtained by using tracking technologies such as global positioning system (GPS) devices, or IP geolocation by using databases that map IP addresses to geographic locations. Geolocation information is widely used in context-sensitive content delivery, enforcing location-based access restrictions on services, and fraud detection and prevention. Due to the intense concerns about security and privacy, countries and regions introduced various legislation and regulation. To determine whether an event is compliant sometimes depends on the geolocation of the event. Therefore, it is crucial to report geolocation information unambiguously in an audit trail. =========== ========= ======== =============================================================================================================== Property Type Required Description =========== ========= ======== =============================================================================================================== id xs:anyURI No Optional identifier for a geolocation latitude xs:string No The latitude of a geolocation longitude xs:string No The longitude of a geolocation elevation xs:double No The elevation of a geolocation in meters accuracy xs:double No The accuracy of a geolocation in meters city xs:string No The city of a geolocation state xs:string No The state/province of a geolocation regionICANN xs:string No A region (e.g., a country, a sovereign state, a dependent territory or a special area of geographical interest) annotations cadf:Map No User-defined geolocation information (e.g., building name, room number) =========== ========= ======== =============================================================================================================== Usage Requirements ================== 1. Geolocation typed data SHALL contain at least one valid property and associated value. 2. Geolocation typed data SHALL NOT be used to represent virtual or logical locations (e.g. network zone). 3. For each geolocation data instance, the properties SHALL be consistent. That is, all properties SHALL consistently represent the same geographic location and SHALL NOT provide conflicting value data. .. note:: `latitude`, `longitude` and `region` are all supplied as properties describing the same geolocation, the `latitude` and `longitude` properties coordinate values should resolve to the same geographic location as described by the value of the `region` property. 4. ICANN's implementation plan states "Upper and lower case characters are considered to be syntactically and semantically identical"; therefore, the "regionICANN" property's values MAY be either upper or lower case. Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "target": { ..., "geolocation": { "latitude": "+372207.90", "longitude": "-1220210.20", "elevation": "10" } } } pycadf-2.7.0/doc/source/specification/hosts.rst000066400000000000000000000044651321055627100215600ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _hosts: ====== Hosts ====== Most resources that are referenced in an IT or cloud infrastructure are conceptually "hosted on" or "hosted by" other resources. For example, "applications" are hosted on "web servers" or "users" may be hosted on a "network connected device" or a "terminal". In addition, networked resources are "hosted" by some device attached to some network. The host resource often provides context or location information for the resource it is hosting at the time the Actual Event was observed and recorded (e.g., an IP address, software agent, platform, etc.). Providing a means to record host information with a CADF Event Record is valuable for audit purposes because compliance policies and rules are often based on such information. ======== =============== ======== ============================================== Property Type Required Description ======== =============== ======== ============================================== id cadf:Identifier No The optional identifier of the host RESOURCE address xs:anyURI No The optional address of the host RESOURCE agent xs:string No The optional agent (name) of the host RESOURCE platform xs:string No The optional platform of the host RESOURCE ======== =============== ======== ============================================== Serialisation ============= .. code-block:: javascript { "id": "myuuid:1234-5678-90abc-defg-0000", "address": "10.0.2.15", "agent": "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:18.0)", "platform": "Linux version 3.5.0-23-generic (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #35~precise1-Ubuntu SMP Fri Jan 25 17:15:33 UTC 2013" } pycadf-2.7.0/doc/source/specification/identifiers.rst000066400000000000000000000024151321055627100227160ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _identifiers: ============ Identifiers ============ This specification defines an Identifier type that is based upon the Uniform Resource Identifier Reference (URI) as specified in RFC3986. Any value that represents a CADF Identifier type in this specification, its extensions, or profiles SHALL adhere to the requirements listed in this section: .. note:: CADF Identifier type values SHALL be created to be Universally Unique Identifiers (UUIDs) so that when CADF data (e.g., CADF Event Records, Logs, Reports, Resources, Metrics, etc.) are federated it will be uniquely identifiable to the source (e.g., cloud provider, service, etc.) that created them. pycadf-2.7.0/doc/source/specification/index.rst000066400000000000000000000012621321055627100215170ustar00rootroot00000000000000============== Specification ============== The following is a high-level description of components in the CADF specification. The basic component of the CADF specification are Events. The full CADF specification document can be found here_. Additional details on the CADF specification are accessible via the `DMTF CADF`_ page. .. _here: http://dmtf.org/sites/default/files/standards/documents/DSP0262_1.0.0.pdf .. _DMTF CADF: http://www.dmtf.org/standards/cadf .. toctree:: :maxdepth: 1 events attachments credentials endpoints geolocations hosts identifiers measurements paths reasons reportersteps resources tags timestamps taxonomy pycadf-2.7.0/doc/source/specification/measurements.rst000066400000000000000000000065471321055627100231330ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _measurements: ============= Measurements ============= A component that contains statistical or measurement information for TARGET resources that are being monitored. The measurement should be based upon a defined metric (a method of measurement). ============ =============== ========= ================================================================================================================= Property Type Required Description ============ =============== ========= ================================================================================================================= result xs:any Yes The quantitative or qualitative result of a measurement from applying the associated metric metric cadf:Metric Dependent The property describes the metric used in generating the measurement result. Required if not metricId metricId cadf:Identifier Dependent This property identifies a CADF Metric by reference and whose definition exists elsewhere. Required if not metric calculatedBy cadf:Resource No An optional description of the resource that calculated the measurement ============ =============== ========= ================================================================================================================= Metrics ======= The Metric data type describes the rules and processes for measuring some activity or resource, resulting in the generation of some values (captured by the Measurement type). =========== =============== ======== ================================================== Property Type Required Description =========== =============== ======== ================================================== metricId cadf:identifier Yes The identifier for the metric. unit xs:string Yes The metrics unit (e.g., "ms", "Hz", "GB", etc.) name xs:string No A descriptive name for metric annotations cadf:map No User-defined metric information. =========== =============== ======== ================================================== Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/log", ..., "metrics": [ { "metricId": "myuuid://metric.org/1234", "unit": "GB", "name": "Storage Capacity in Gigabytes" } ], ..., "events": [ { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "measurements": [ { "result": "10", "metricId": "myuuid://metric.org/1234" } ] } ] } pycadf-2.7.0/doc/source/specification/paths.rst000066400000000000000000000016011321055627100215240ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _paths: ====== Paths ====== This clause describes how to represent values that are elements of hierarchies. This construct is used for example when providing values from CADF Taxonomies that classify components of the CADF Event Model within CADF Event Records as path values. pycadf-2.7.0/doc/source/specification/reasons.rst000066400000000000000000000042651321055627100220700ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _reasons: ======== Reasons ======== A component that contains a means to provide additional details and further classify the top-level OUTCOME of the ACTION included in a CADF Event Record. ========== ========= ======== ===================================================================================================================== Property Type Required Description ========== ========= ======== ===================================================================================================================== reasonType xs:anyURI No The domain URI that defines the "reasonCode" property's value reasonCode xs:string No An optional detailed result code as described by the domain identified in the "reasonType" property policyType xs:anyURI No The domain URI that defines the "policyId" property’s value policyId xs:string No An optional identifier that indicates which policy or algorithm was applied in order to achieve the described OUTCOME ========== ========= ======== ===================================================================================================================== Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "reason": { "reasonType": "http://www.iana.org/assignments/http-status-codes/http-status-codes.xml", "reasonCode": "408", "policyType": "http://schemas.xmlsoap.org/ws/2002/12/policy", "policyId": "http://10.0.3.4/firewall-ruleset/rule0012" }, ... } pycadf-2.7.0/doc/source/specification/reportersteps.rst000066400000000000000000000055131321055627100233340ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _reportersteps: ============== Reportersteps ============== This type represents a step in the REPORTERCHAIN that captures information about any notable REPORTER (in addition to the OBSERVER) that modified or relayed the CADF Event Record and any details regarding any modification it performed on the CADF Event Record it is contained within. The Reporterstep data type should capture information about the resources that have had a role in modifying, or relaying the CADF Event Record during its lifecycle after having been created by the OBSERVER. ============ ================= ========= ========================================================================================================================== Property Type Required Description ============ ================= ========= ========================================================================================================================== role xs:string Yes The role the REPORTER performed on the CADF Event Record (e.g., an "observer", "modifier" or "relay" role) reporter cadf:Resource Dependent This property defines the resource that acted as a REPORTER on a CADF Event Record. Required if not reporterId reporterId cadf:Identifier Dependent This property identifies a resource that acted as a REPORTER on a CADF Event Record by reference. Required if not reporter reporterTime cadf:Timestamp No The time a REPORTER adds its Reporterstep entry into the REPORTERCHAIN attachments cadf:Attachment[] No An optional array of additional data containing information about the reporter or any action it performed ============ ================= ========= ========================================================================================================================== Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "reporterchain": [ { "role": "modifier", "reporterTime": "2012-03-22T13:00:00-04:00", "reporter": { "id": "myscheme://mydomain/resource/monitor/id/0002" } }, ... ] }pycadf-2.7.0/doc/source/specification/resources.rst000066400000000000000000000062721321055627100224300ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _resources: ========== Resources ========== Resources in general can be used to describe traditional IT components (e.g., servers, network devices, etc.), software components (e.g., platforms, databases, applications, etc.), operational and business data (e.g., accounts, users, etc.) and roles, which can be assigned to persons, that describe the authority to access capabilities. ============= ================= ========= =================================================================================================================================== Property Type Required Description ============= ================= ========= =================================================================================================================================== id cadf:Identifier Yes The identifier for the resource typeURI cadf:Path Yes The classification (i.e., type) of the resource using the CADF Resource Taxonomy name xs:string No The optional local name for the resource (not necessarily unique) domain xs:string No The optional name of the domain that qualifies the name of the resource credential cadf:Credential No The optional security credentials associated with the resource’s identity addresses cadf:Endpoint[] No The optional descriptive addresses (including URLs) of the resource host cadf:Host No The optional information about the (network) host of the resource geolocation cadf:Geolocation Dependent This optional property describes the geographic location of the resource using Geolocation data type. Required if not geolocationId geolocationId cadf:Identifier Dependent This optional property identifies a CADF Geolocation by reference. Required if not geolocation attachments cadf:Attachment[] No An optional array of extended or domain-specific information about the resource or its contex ============= ================= ========= =================================================================================================================================== Serialisation ============= .. code-block:: javascript { "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", ..., "target": { "id": "myscheme://mydomain/resource/id/0001", "typeURI": "service/compute", "name": "server_0001", ..., "geolocation": { "city": "Austin", "state": "TX", "regionICANN": "US" } } } pycadf-2.7.0/doc/source/specification/tags.rst000066400000000000000000000021331321055627100213440ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _tags: ===== Tags ===== A "tag" is a label that can be added to a CADF Event Record to qualify or categorize an event. Tags provide a powerful mechanism for adding domain-specific identifiers and classifications to CADF Event Records that can be referenced by the CADF Query Interface. This allows customers to construct custom reports or views on the event data held by a provider for a specific domain of interest. A CADF Event Record can have multiple tags that enable cross-domain analysis.pycadf-2.7.0/doc/source/specification/taxonomy.rst000066400000000000000000000030161321055627100222650ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _taxonomy: ========= Taxonomy ========= The CADF Resource Taxonomy describes resources that are commonly used in cloud and enterprise infrastructures. This list was developed based on surveys of existing cloud architectures, deployments, and implementations. The Resource Taxonomy, however, is fully intended to be extensible by profiles that may define additional resource nodes as child nodes to the ones specified below. When doing so, however, vendors and cloud providers should be aware that this places an additional burden on the consumer to correctly comprehend the new node type. Therefore, vendors and providers of CADF audit data should be careful to provide classification values that extend the existing tree from the most granular node that closely matches the functions of any newly-defined resource types. This approach will provide consumers with a baseline understanding of the function of the new resource type.pycadf-2.7.0/doc/source/specification/timestamps.rst000066400000000000000000000021231321055627100225730ustar00rootroot00000000000000.. Copyright 2014 IBM Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. _timestamps: =========== Timestamps =========== The following example shows the required Lexical representation of the Timestamp type used in this specification; all Timestamp typed values SHALL be formatted accordingly: :: yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)('+' | '-') hh ':' mm .. note:: The UTC offset is always required (not optional) and the use of the character 'Z' (or 'Zulu' time) as an abbreviation for UTC offset +00:00 or -00:00 is NOT permitted.pycadf-2.7.0/etc/000077500000000000000000000000001321055627100135435ustar00rootroot00000000000000pycadf-2.7.0/etc/pycadf/000077500000000000000000000000001321055627100150115ustar00rootroot00000000000000pycadf-2.7.0/etc/pycadf/ceilometer_api_audit_map.conf000066400000000000000000000005701321055627100226660ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None # possible end path of api requests [path_keywords] meters = meter_name resources = resource_id statistics = None samples = sample_id # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] metering = service/metering pycadf-2.7.0/etc/pycadf/cinder_api_audit_map.conf000066400000000000000000000012611321055627100220000ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None # map urls ending with specific text to a unique action [custom_actions] associate = update/associate disassociate = update/disassociate disassociate_all = update/disassociate_all associations = read/list/associations # possible end path of api requests [path_keywords] defaults = None detail = None limits = None os-quota-specs = project qos-specs = qos-spec snapshots = snapshot types = type volumes = volume # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] volume = service/storage/block volumev2 = service/storage/blockpycadf-2.7.0/etc/pycadf/glance_api_audit_map.conf000066400000000000000000000005541321055627100217710ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None # possible end path of api requests [path_keywords] detail = None file = None images = image members = member tags = tag # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] image = service/storage/imagepycadf-2.7.0/etc/pycadf/gnocchi_api_audit_map.conf000066400000000000000000000010311321055627100221410ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = metric # possible end path of api requests [path_keywords] metric = metric_id measures = None archive_policy = archive_policy_name archive_policy_rule = archive_policy_rule_name generic = generic_id instance = instance_id history = None resource_type = resource_type_name capabilities = None status = None # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] metric=service/metric pycadf-2.7.0/etc/pycadf/heat_api_audit_map.conf000066400000000000000000000013271321055627100214600ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None # possible end path of api requests [path_keywords] stacks = stack resources = resource preview = None detail = None abandon = None snapshots = snapshot restore = None outputs = output metadata = server signal = None events = event template = None template_versions = template_version functions = None validate = None resource_types = resource_type build_info = None actions = None software_configs = software_config software_deployments = software_deployment services = None # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] orchestration = service/orchestrationpycadf-2.7.0/etc/pycadf/ironic_api_audit_map.conf000066400000000000000000000010241321055627100220140ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None # possible end path of api requests [path_keywords] nodes = node drivers = driver chassis = chassis ports = port states = state power = None provision = None maintenance = None validate = None boot_device = None supported = None console = None vendor_passthrus = vendor_passthru # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] baremetal = service/compute/baremetal pycadf-2.7.0/etc/pycadf/neutron_api_audit_map.conf000066400000000000000000000013061321055627100222260ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None [custom_actions] add_router_interface = update/add remove_router_interface = update/remove # possible end path of api requests [path_keywords] floatingips = ip healthmonitors = healthmonitor health_monitors = health_monitor lb = None members = member metering-labels = label metering-label-rules = rule networks = network pools = pool ports = port routers = router quotas = quota security-groups = security-group security-group-rules = rule subnets = subnet vips = vip # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] network = service/network pycadf-2.7.0/etc/pycadf/nova_api_audit_map.conf000066400000000000000000000030701321055627100214770ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None [custom_actions] enable = enable disable = disable delete = delete startup = start/startup shutdown = stop/shutdown reboot = start/reboot os-migrations/get = read os-server-password/post = update # possible end path of api requests [path_keywords] add = None action = None enable = None disable = None configure-project = None defaults = None delete = None detail = None diagnostics = None entries = entry extensions = alias flavors = flavor images = image ips = label limits = None metadata = key os-agents = os-agent os-aggregates = os-aggregate os-availability-zone = None os-certificates = None os-cloudpipe = None os-fixed-ips = ip os-extra_specs = key os-flavor-access = None os-floating-ip-dns = domain os-floating-ips-bulk = host os-floating-ip-pools = None os-floating-ips = floating-ip os-hosts = host os-hypervisors = hypervisor os-instance-actions = instance-action os-keypairs = keypair os-migrations = None os-networks = network os-quota-sets = tenant os-security-groups = security_group os-security-group-rules = rule os-server-password = None os-services = None os-simple-tenant-usage = tenant os-virtual-interfaces = None os-volume_attachments = attachment os-volumes_boot = None os-volumes = volume os-volume-types = volume-type os-snapshots = snapshot reboot = None servers = server shutdown = None startup = None statistics = None # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] compute = service/compute pycadf-2.7.0/etc/pycadf/panko_api_audit_map.conf000066400000000000000000000005641321055627100216510ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = event # possible end path of api requests [path_keywords] events = message_id capabilities = None event_types = event_type traits = event_type # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] event=service/event pycadf-2.7.0/etc/pycadf/trove_api_audit_map.conf000066400000000000000000000007641321055627100217020ustar00rootroot00000000000000[DEFAULT] # default target endpoint type # should match the endpoint type defined in service catalog target_endpoint_type = None # possible end path of api requests [path_keywords] instances=instance configuration=None root=None action=None databases=database users=user flavors=flavor backups=backup configurations=configuration versions=version datastores=datastore parameters=parameter # map endpoint type defined in service catalog to CADF typeURI [service_endpoints] database=service/database pycadf-2.7.0/pycadf/000077500000000000000000000000001321055627100142365ustar00rootroot00000000000000pycadf-2.7.0/pycadf/__init__.py000066400000000000000000000000001321055627100163350ustar00rootroot00000000000000pycadf-2.7.0/pycadf/attachment.py000066400000000000000000000050271321055627100167440ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype ATTACHMENT_KEYNAME_TYPEURI = "typeURI" ATTACHMENT_KEYNAME_CONTENT = "content" ATTACHMENT_KEYNAME_NAME = "name" ATTACHMENT_KEYNAMES = [ATTACHMENT_KEYNAME_TYPEURI, ATTACHMENT_KEYNAME_CONTENT, ATTACHMENT_KEYNAME_NAME] class Attachment(cadftype.CADFAbstractType): # TODO(mrutkows): OpenStack / Ceilometer may want to define # the set of approved attachment types in order to # limit and validate them. typeURI = cadftype.ValidatorDescriptor(ATTACHMENT_KEYNAME_TYPEURI, lambda x: isinstance( x, six.string_types)) content = cadftype.ValidatorDescriptor(ATTACHMENT_KEYNAME_CONTENT) name = cadftype.ValidatorDescriptor(ATTACHMENT_KEYNAME_NAME, lambda x: isinstance(x, six.string_types)) def __init__(self, typeURI=None, content=None, name=None): """Create Attachment data type :param typeURI: uri that identifies type of data in content :param content: container that contains any type of data :param contentType: name used to identify content. """ # Attachment.typeURI if typeURI is not None: setattr(self, ATTACHMENT_KEYNAME_TYPEURI, typeURI) # Attachment.content if content is not None: setattr(self, ATTACHMENT_KEYNAME_CONTENT, content) # Attachment.name if name is not None: setattr(self, ATTACHMENT_KEYNAME_NAME, name) # self validate cadf:Attachment type against schema def is_valid(self): """Validation to ensure Attachment required attributes are set. """ return ( self._isset(ATTACHMENT_KEYNAME_TYPEURI) and self._isset(ATTACHMENT_KEYNAME_NAME) and self._isset(ATTACHMENT_KEYNAME_CONTENT) ) pycadf-2.7.0/pycadf/cadftaxonomy.py000066400000000000000000000126531321055627100173130ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. from pycadf import cadftype TYPE_URI_ACTION = cadftype.CADF_VERSION_1_0_0 + 'action' UNKNOWN = 'unknown' # Commonly used (valid) Event.action values from Nova ACTION_CREATE = 'create' ACTION_READ = 'read' ACTION_UPDATE = 'update' ACTION_DELETE = 'delete' # Other CADF actions ACTION_AUTHENTICATE = 'authenticate' ACTION_EVALUATE = 'evaluate' # OpenStack specific, Profile or change CADF spec. to add this action ACTION_LIST = 'read/list' # TODO(mrutkows): Make global using WSGI mechanism ACTION_TAXONOMY = frozenset([ 'backup', 'capture', ACTION_CREATE, 'configure', ACTION_READ, ACTION_LIST, ACTION_UPDATE, ACTION_DELETE, 'monitor', 'start', 'stop', 'deploy', 'undeploy', 'enable', 'disable', 'send', 'receive', ACTION_AUTHENTICATE, 'authenticate/login', 'revoke', 'renew', 'restore', ACTION_EVALUATE, 'allow', 'deny', 'notify', UNKNOWN ]) # TODO(mrutkows): validate absolute URIs as well def is_valid_action(value): for type in ACTION_TAXONOMY: if value.startswith(type): return True return False TYPE_URI_OUTCOME = cadftype.CADF_VERSION_1_0_0 + 'outcome' # Valid Event.outcome values OUTCOME_SUCCESS = 'success' OUTCOME_FAILURE = 'failure' OUTCOME_PENDING = 'pending' # TODO(mrutkows): Make global using WSGI mechanism OUTCOME_TAXONOMY = frozenset([ OUTCOME_SUCCESS, OUTCOME_FAILURE, OUTCOME_PENDING, UNKNOWN ]) # TODO(mrutkows): validate absolute URIs as well def is_valid_outcome(value): return value in OUTCOME_TAXONOMY SERVICE_SECURITY = 'service/security' SERVICE_KEYMGR = 'service/security/keymanager' ACCOUNT_USER = 'service/security/account/user' CADF_AUDIT_FILTER = 'service/security/audit/filter' SECURITY_ACCOUNT = 'data/security/account' SECURITY_CREDENTIAL = 'data/security/credential' SECURITY_DOMAIN = 'data/security/domain' SECURITY_ENDPOINT = 'data/security/endpoint' SECURITY_GROUP = 'data/security/group' SECURITY_IDENTITY = 'data/security/identity' SECURITY_KEY = 'data/security/key' SECURITY_LICENCE = 'data/security/license' SECURITY_POLICY = 'data/security/policy' SECURITY_PROFILE = 'data/security/profile' SECURITY_PROJECT = 'data/security/project' SECURITY_REGION = 'data/security/region' SECURITY_ROLE = 'data/security/role' SECURITY_SERVICE = 'data/security/service' SECURITY_TRUST = 'data/security/trust' SECURITY_ACCOUNT_USER = 'data/security/account/user' KEYMGR_SECRET = 'data/security/keymanager/secret' KEYMGR_CONTAINER = 'data/security/keymanager/container' KEYMGR_ORDER = 'data/security/keymanager/order' KEYMGR_OTHERS = 'data/security/keymanager' # TODO(mrutkows): Make global using WSGI mechanism RESOURCE_TAXONOMY = frozenset([ 'storage', 'storage/node', 'storage/volume', 'storage/memory', 'storage/container', 'storage/directory', 'storage/database', 'storage/queue', 'compute', 'compute/node', 'compute/cpu', 'compute/machine', 'compute/process', 'compute/thread', 'network', 'network/node', 'network/node/host', 'network/connection', 'network/domain', 'network/cluster', 'service', 'service/oss', 'service/bss', 'service/bss/metering', 'service/composition', 'service/compute', 'service/database', SERVICE_SECURITY, SERVICE_KEYMGR, 'service/security/account', ACCOUNT_USER, CADF_AUDIT_FILTER, 'service/storage', 'service/storage/block', 'service/storage/image', 'service/storage/object', 'service/network', 'data', 'data/message', 'data/workload', 'data/workload/app', 'data/workload/service', 'data/workload/task', 'data/workload/job', 'data/file', 'data/file/catalog', 'data/file/log', 'data/template', 'data/package', 'data/image', 'data/module', 'data/config', 'data/directory', 'data/database', 'data/security', SECURITY_ACCOUNT, SECURITY_CREDENTIAL, SECURITY_DOMAIN, SECURITY_ENDPOINT, SECURITY_GROUP, SECURITY_IDENTITY, SECURITY_KEY, SECURITY_LICENCE, SECURITY_POLICY, SECURITY_PROFILE, SECURITY_PROJECT, SECURITY_REGION, SECURITY_ROLE, SECURITY_SERVICE, SECURITY_TRUST, SECURITY_ACCOUNT_USER, 'data/security/account/user/privilege', 'data/database/alias', 'data/database/catalog', 'data/database/constraints', 'data/database/index', 'data/database/instance', 'data/database/key', 'data/database/routine', 'data/database/schema', 'data/database/sequence', 'data/database/table', 'data/database/trigger', 'data/database/view', KEYMGR_CONTAINER, KEYMGR_ORDER, KEYMGR_SECRET, KEYMGR_OTHERS, UNKNOWN ]) # TODO(mrutkows): validate absolute URIs as well def is_valid_resource(value): for type in RESOURCE_TAXONOMY: if value.startswith(type): return True return False pycadf-2.7.0/pycadf/cadftype.py000066400000000000000000000057621321055627100164210ustar00rootroot00000000000000# Copyright (c) 2013 IBM Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import abc from oslo_serialization import jsonutils import six CADF_SCHEMA_1_0_0 = 'cadf:' CADF_VERSION_1_0_0 = 'http://schemas.dmtf.org/cloud/audit/1.0/' # Valid cadf:Event record "types" EVENTTYPE_ACTIVITY = 'activity' EVENTTYPE_MONITOR = 'monitor' EVENTTYPE_CONTROL = 'control' VALID_EVENTTYPES = frozenset([ EVENTTYPE_ACTIVITY, EVENTTYPE_MONITOR, EVENTTYPE_CONTROL ]) def is_valid_eventType(value): return value in VALID_EVENTTYPES # valid cadf:Event record "Reporter" roles REPORTER_ROLE_OBSERVER = 'observer' REPORTER_ROLE_MODIFIER = 'modifier' REPORTER_ROLE_RELAY = 'relay' VALID_REPORTER_ROLES = frozenset([ REPORTER_ROLE_OBSERVER, REPORTER_ROLE_MODIFIER, REPORTER_ROLE_RELAY ]) def is_valid_reporter_role(value): return value in VALID_REPORTER_ROLES class ValidatorDescriptor(object): def __init__(self, name, func=None): self.name = name self.func = func def __set__(self, instance, value): if value is not None: if self.func is not None: if self.func(value): instance.__dict__[self.name] = value else: raise ValueError('%s failed validation: %s' % (self.name, self.func)) else: instance.__dict__[self.name] = value else: raise ValueError('%s must not be None.' % self.name) @six.add_metaclass(abc.ABCMeta) class CADFAbstractType(object): """The abstract base class for all CADF (complex) data types (classes).""" @abc.abstractmethod def is_valid(self, value): pass def as_dict(self): """Return dict representation of Event.""" return jsonutils.to_primitive(self, convert_instances=True) def _isset(self, attr): """Check to see if attribute is defined.""" try: if isinstance(getattr(self, attr), ValidatorDescriptor): return False return True except AttributeError: return False # TODO(mrutkows): Eventually, we want to use the OrderedDict (introduced # in Python 2.7) type for all CADF classes to store attributes in a # canonical form. Currently, OpenStack/Jenkins requires 2.6 compatibility # The reason is that we want to be able to support signing all or parts # of the event record and need to guarantee order. # def to_ordered_dict(self, value): # pass pycadf-2.7.0/pycadf/credential.py000066400000000000000000000065511321055627100167310ustar00rootroot00000000000000# Copyright (c) 2013 IBM Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype from pycadf import utils TYPE_URI_CRED = cadftype.CADF_VERSION_1_0_0 + 'credential' CRED_KEYNAME_TYPE = "type" CRED_KEYNAME_TOKEN = "token" CRED_KEYNAMES = [CRED_KEYNAME_TYPE, CRED_KEYNAME_TOKEN] FED_CRED_KEYNAME_IDENTITY_PROVIDER = "identity_provider" FED_CRED_KEYNAME_USER = "user" FED_CRED_KEYNAME_GROUPS = "groups" FED_CRED_KEYNAMES = CRED_KEYNAMES + [FED_CRED_KEYNAME_IDENTITY_PROVIDER, FED_CRED_KEYNAME_USER, FED_CRED_KEYNAME_GROUPS] class Credential(cadftype.CADFAbstractType): type = cadftype.ValidatorDescriptor( CRED_KEYNAME_TYPE, lambda x: isinstance(x, six.string_types)) token = cadftype.ValidatorDescriptor( CRED_KEYNAME_TOKEN, lambda x: isinstance(x, six.string_types)) def __init__(self, token, type=None): """Create Credential data type :param token: identity or security token :param type: type of credential (ie. identity token) """ # Credential.token setattr(self, CRED_KEYNAME_TOKEN, utils.mask_value(token)) # Credential.type if type is not None: setattr(self, CRED_KEYNAME_TYPE, type) # TODO(mrutkows): validate this cadf:Credential type against schema def is_valid(self): """Validation to ensure Credential required attributes are set.""" # TODO(mrutkows): validate specific attribute type/format return self._isset(CRED_KEYNAME_TOKEN) class FederatedCredential(Credential): identity_provider = cadftype.ValidatorDescriptor( FED_CRED_KEYNAME_IDENTITY_PROVIDER, lambda x: isinstance(x, six.string_types)) user = cadftype.ValidatorDescriptor( FED_CRED_KEYNAME_USER, lambda x: isinstance(x, six.string_types)) groups = cadftype.ValidatorDescriptor( FED_CRED_KEYNAME_GROUPS, lambda x: isinstance(x, list)) def __init__(self, token, type, identity_provider, user, groups): super(FederatedCredential, self).__init__( token=token, type=type) # FederatedCredential.identity_provider setattr(self, FED_CRED_KEYNAME_IDENTITY_PROVIDER, identity_provider) # FederatedCredential.user setattr(self, FED_CRED_KEYNAME_USER, user) # FederatedCredential.groups setattr(self, FED_CRED_KEYNAME_GROUPS, groups) def is_valid(self): """Validation to ensure Credential required attributes are set.""" return ( super(FederatedCredential, self).is_valid() and self._isset(CRED_KEYNAME_TYPE) and self._isset(FED_CRED_KEYNAME_IDENTITY_PROVIDER) and self._isset(FED_CRED_KEYNAME_USER) and self._isset(FED_CRED_KEYNAME_GROUPS)) pycadf-2.7.0/pycadf/endpoint.py000066400000000000000000000037601321055627100164360ustar00rootroot00000000000000# Copyright (c) 2013 IBM Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype TYPE_URI_ENDPOINT = cadftype.CADF_VERSION_1_0_0 + 'endpoint' ENDPOINT_KEYNAME_URL = "url" ENDPOINT_KEYNAME_NAME = "name" ENDPOINT_KEYNAME_PORT = "port" ENDPOINT_KEYNAMES = [ENDPOINT_KEYNAME_URL, ENDPOINT_KEYNAME_NAME, ENDPOINT_KEYNAME_PORT] class Endpoint(cadftype.CADFAbstractType): url = cadftype.ValidatorDescriptor( ENDPOINT_KEYNAME_URL, lambda x: isinstance(x, six.string_types)) name = cadftype.ValidatorDescriptor( ENDPOINT_KEYNAME_NAME, lambda x: isinstance(x, six.string_types)) port = cadftype.ValidatorDescriptor( ENDPOINT_KEYNAME_PORT, lambda x: isinstance(x, six.string_types)) def __init__(self, url, name=None, port=None): """Create Endpoint data type :param url: address of endpoint :param name: name of endpoint :param port: port of endpoint """ # ENDPOINT.url setattr(self, ENDPOINT_KEYNAME_URL, url) # ENDPOINT.name if name is not None: setattr(self, ENDPOINT_KEYNAME_NAME, name) # ENDPOINT.port if port is not None: setattr(self, ENDPOINT_KEYNAME_PORT, port) # TODO(mrutkows): validate this cadf:ENDPOINT type against schema def is_valid(self): """Validation to ensure Endpoint required attributes are set. """ return self._isset(ENDPOINT_KEYNAME_URL) pycadf-2.7.0/pycadf/event.py000066400000000000000000000264671321055627100157500ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import attachment from pycadf import cadftaxonomy from pycadf import cadftype from pycadf import identifier from pycadf import measurement from pycadf import reason from pycadf import reporterstep from pycadf import resource from pycadf import tag from pycadf import timestamp TYPE_URI_EVENT = cadftype.CADF_VERSION_1_0_0 + 'event' # Event.eventType EVENT_KEYNAME_TYPEURI = "typeURI" EVENT_KEYNAME_EVENTTYPE = "eventType" EVENT_KEYNAME_ID = "id" EVENT_KEYNAME_EVENTTIME = "eventTime" EVENT_KEYNAME_INITIATOR = "initiator" EVENT_KEYNAME_INITIATORID = "initiatorId" EVENT_KEYNAME_ACTION = "action" EVENT_KEYNAME_TARGET = "target" EVENT_KEYNAME_TARGETID = "targetId" EVENT_KEYNAME_OUTCOME = "outcome" EVENT_KEYNAME_REASON = "reason" EVENT_KEYNAME_SEVERITY = "severity" EVENT_KEYNAME_NAME = "name" EVENT_KEYNAME_MEASUREMENTS = "measurements" EVENT_KEYNAME_TAGS = "tags" EVENT_KEYNAME_ATTACHMENTS = "attachments" EVENT_KEYNAME_OBSERVER = "observer" EVENT_KEYNAME_OBSERVERID = "observerId" EVENT_KEYNAME_REPORTERCHAIN = "reporterchain" EVENT_KEYNAMES = [EVENT_KEYNAME_TYPEURI, EVENT_KEYNAME_EVENTTYPE, EVENT_KEYNAME_ID, EVENT_KEYNAME_EVENTTIME, EVENT_KEYNAME_INITIATOR, EVENT_KEYNAME_INITIATORID, EVENT_KEYNAME_ACTION, EVENT_KEYNAME_TARGET, EVENT_KEYNAME_TARGETID, EVENT_KEYNAME_OUTCOME, EVENT_KEYNAME_REASON, EVENT_KEYNAME_SEVERITY, EVENT_KEYNAME_NAME, EVENT_KEYNAME_MEASUREMENTS, EVENT_KEYNAME_TAGS, EVENT_KEYNAME_ATTACHMENTS, EVENT_KEYNAME_OBSERVER, EVENT_KEYNAME_OBSERVERID, EVENT_KEYNAME_REPORTERCHAIN] class Event(cadftype.CADFAbstractType): eventType = cadftype.ValidatorDescriptor( EVENT_KEYNAME_EVENTTYPE, lambda x: cadftype.is_valid_eventType(x)) id = cadftype.ValidatorDescriptor(EVENT_KEYNAME_ID, lambda x: identifier.is_valid(x)) eventTime = cadftype.ValidatorDescriptor(EVENT_KEYNAME_EVENTTIME, lambda x: timestamp.is_valid(x)) initiator = cadftype.ValidatorDescriptor( EVENT_KEYNAME_INITIATOR, (lambda x: isinstance(x, resource.Resource) and x.is_valid() and x.id != 'initiator')) initiatorId = cadftype.ValidatorDescriptor( EVENT_KEYNAME_INITIATORID, lambda x: identifier.is_valid(x)) action = cadftype.ValidatorDescriptor( EVENT_KEYNAME_ACTION, lambda x: cadftaxonomy.is_valid_action(x)) target = cadftype.ValidatorDescriptor( EVENT_KEYNAME_TARGET, (lambda x: isinstance(x, resource.Resource) and x.is_valid() and x.id != 'target')) targetId = cadftype.ValidatorDescriptor( EVENT_KEYNAME_TARGETID, lambda x: identifier.is_valid(x)) outcome = cadftype.ValidatorDescriptor( EVENT_KEYNAME_OUTCOME, lambda x: cadftaxonomy.is_valid_outcome(x)) reason = cadftype.ValidatorDescriptor( EVENT_KEYNAME_REASON, lambda x: isinstance(x, reason.Reason) and x.is_valid()) name = cadftype.ValidatorDescriptor(EVENT_KEYNAME_NAME, lambda x: isinstance( x, six.string_types)) severity = cadftype.ValidatorDescriptor(EVENT_KEYNAME_SEVERITY, lambda x: isinstance( x, six.string_types)) observer = cadftype.ValidatorDescriptor( EVENT_KEYNAME_OBSERVER, (lambda x: isinstance(x, resource.Resource) and x.is_valid())) observerId = cadftype.ValidatorDescriptor( EVENT_KEYNAME_OBSERVERID, lambda x: identifier.is_valid(x)) def __init__(self, eventType=cadftype.EVENTTYPE_ACTIVITY, id=None, eventTime=None, action=cadftaxonomy.UNKNOWN, outcome=cadftaxonomy.UNKNOWN, initiator=None, initiatorId=None, target=None, targetId=None, severity=None, reason=None, observer=None, observerId=None, name=None): """Create an Event :param eventType: eventType of Event. Defaults to 'activity' type :param id: id of event. will generate uuid if None :param eventTime: time of event. will take current utc if None :param action: event's action (see Action taxonomy) :param outcome: Event's outcome (see Outcome taxonomy) :param initiator: Event's Initiator Resource :param initiatorId: Event's Initiator Resource id :param target: Event's Target Resource :param targetId: Event's Target Resource id :param severity: domain-relative severity of Event :param reason: domain-specific Reason type :param observer: Event's Observer Resource :param observerId: Event's Observer Resource id :param name: descriptive name for the event """ # Establish typeURI for the CADF Event data type # TODO(mrutkows): support extended typeURIs for Event subtypes setattr(self, EVENT_KEYNAME_TYPEURI, TYPE_URI_EVENT) # Event.eventType (Mandatory) setattr(self, EVENT_KEYNAME_EVENTTYPE, eventType) # Event.id (Mandatory) setattr(self, EVENT_KEYNAME_ID, id or identifier.generate_uuid()) # Event.eventTime (Mandatory) setattr(self, EVENT_KEYNAME_EVENTTIME, eventTime or timestamp.get_utc_now()) # Event.action (Mandatory) setattr(self, EVENT_KEYNAME_ACTION, action) # Event.outcome (Mandatory) setattr(self, EVENT_KEYNAME_OUTCOME, outcome) # Event.observer (Mandatory if no observerId) if observer is not None: setattr(self, EVENT_KEYNAME_OBSERVER, observer) # Event.observerId (Dependent) if observerId is not None: setattr(self, EVENT_KEYNAME_OBSERVERID, observerId) # Event.initiator (Mandatory if no initiatorId) if initiator is not None: setattr(self, EVENT_KEYNAME_INITIATOR, initiator) # Event.initiatorId (Dependent) if initiatorId is not None: setattr(self, EVENT_KEYNAME_INITIATORID, initiatorId) # Event.target (Mandatory if no targetId) if target is not None: setattr(self, EVENT_KEYNAME_TARGET, target) # Event.targetId (Dependent) if targetId is not None: setattr(self, EVENT_KEYNAME_TARGETID, targetId) # Event.name (Optional) if name is not None: setattr(self, EVENT_KEYNAME_NAME, name) # Event.severity (Optional) if severity is not None: setattr(self, EVENT_KEYNAME_SEVERITY, severity) # Event.reason (Optional) if reason is not None: setattr(self, EVENT_KEYNAME_REASON, reason) # Event.reporterchain def add_reporterstep(self, step): """Add a Reporterstep :param step: Reporterstep to be added to reporterchain """ if step is not None and isinstance(step, reporterstep.Reporterstep): if step.is_valid(): # Create the list of Reportersteps if needed if not hasattr(self, EVENT_KEYNAME_REPORTERCHAIN): setattr(self, EVENT_KEYNAME_REPORTERCHAIN, list()) reporterchain = getattr(self, EVENT_KEYNAME_REPORTERCHAIN) reporterchain.append(step) else: raise ValueError('Invalid reporterstep') else: raise ValueError('Invalid reporterstep. ' 'Value must be a Reporterstep') # Event.measurements def add_measurement(self, measure_val): """Add a measurement value :param measure_val: Measurement data type to be added to Event """ if (measure_val is not None and isinstance(measure_val, measurement.Measurement)): if measure_val.is_valid(): # Create the list of event.Measurements if needed if not hasattr(self, EVENT_KEYNAME_MEASUREMENTS): setattr(self, EVENT_KEYNAME_MEASUREMENTS, list()) measurements = getattr(self, EVENT_KEYNAME_MEASUREMENTS) measurements.append(measure_val) else: raise ValueError('Invalid measurement') else: raise ValueError('Invalid measurement. ' 'Value must be a Measurement') # Event.tags def add_tag(self, tag_val): """Add Tag to Event :param tag_val: Tag to add to event """ if tag.is_valid(tag_val): if not hasattr(self, EVENT_KEYNAME_TAGS): setattr(self, EVENT_KEYNAME_TAGS, list()) getattr(self, EVENT_KEYNAME_TAGS).append(tag_val) else: raise ValueError('Invalid tag') # Event.attachments def add_attachment(self, attachment_val): """Add Attachment to Event :param attachment_val: Attachment to add to Event """ if (attachment_val is not None and isinstance(attachment_val, attachment.Attachment)): if attachment_val.is_valid(): # Create the list of Attachments if needed if not hasattr(self, EVENT_KEYNAME_ATTACHMENTS): setattr(self, EVENT_KEYNAME_ATTACHMENTS, list()) attachments = getattr(self, EVENT_KEYNAME_ATTACHMENTS) attachments.append(attachment_val) else: raise ValueError('Invalid attachment') else: raise ValueError('Invalid attachment. ' 'Value must be an Attachment') # self validate cadf:Event record against schema def is_valid(self): """Validation to ensure Event required attributes are set. """ # TODO(mrutkows): Eventually, make sure all attributes are # from either the CADF spec. (or profiles thereof) # TODO(mrutkows): validate all child attributes that are CADF types return ( self._isset(EVENT_KEYNAME_TYPEURI) and self._isset(EVENT_KEYNAME_EVENTTYPE) and self._isset(EVENT_KEYNAME_ID) and self._isset(EVENT_KEYNAME_EVENTTIME) and self._isset(EVENT_KEYNAME_ACTION) and self._isset(EVENT_KEYNAME_OUTCOME) and (self._isset(EVENT_KEYNAME_INITIATOR) ^ self._isset(EVENT_KEYNAME_INITIATORID)) and (self._isset(EVENT_KEYNAME_TARGET) ^ self._isset(EVENT_KEYNAME_TARGETID)) and (self._isset(EVENT_KEYNAME_OBSERVER) ^ self._isset(EVENT_KEYNAME_OBSERVERID)) ) pycadf-2.7.0/pycadf/eventfactory.py000066400000000000000000000046411321055627100173260ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. from pycadf import cadftype from pycadf import event ERROR_UNKNOWN_EVENTTYPE = 'Unknown CADF EventType requested on factory method' class EventFactory(object): """Factory class to create different required attributes for the following CADF event types: 'activity': for tracking any interesting system activities for audit 'monitor': Events that carry Metrics and Measurements and support standards such as NIST 'control': For audit events that are based upon (security) policies and reflect some policy decision. """ def new_event(self, eventType=cadftype.EVENTTYPE_ACTIVITY, **kwargs): """Create new event :param eventType: eventType of event. Defaults to 'activity' """ # for now, construct a base ('activity') event as the default event_val = event.Event(**kwargs) if not cadftype.is_valid_eventType(eventType): raise ValueError(ERROR_UNKNOWN_EVENTTYPE) event_val.eventType = eventType # TODO(mrutkows): CADF is only being used for basic # 'activity' auditing (on APIs). An IF-ELIF will # become more meaningful as we add support for other # event types. # elif eventType == cadftype.EVENTTYPE_MONITOR: # # TODO(mrutkows): If we add support for standard (NIST) # # monitoring messages, we will would have a "monitor" # # subclass of the CADF Event type and create it here # event_val.set_eventType(cadftype.EVENTTYPE_MONITOR) # elif eventType == cadftype.EVENTTYPE_CONTROL: # # TODO(mrutkows): If we add support for standard (NIST) # # monitoring messages, we will would have a "control" # # subclass of the CADF Event type and create it here # event_val.set_eventType(cadftype.EVENTTYPE_CONTROL) return event_val pycadf-2.7.0/pycadf/geolocation.py000066400000000000000000000120171321055627100171140ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype from pycadf import identifier # Geolocation types can appear outside a cadf:Event record context, in these # cases a typeURI may be used to identify the cadf:Geolocation data type. TYPE_URI_GEOLOCATION = cadftype.CADF_VERSION_1_0_0 + 'geolocation' GEO_KEYNAME_ID = "id" GEO_KEYNAME_LATITUDE = "latitude" GEO_KEYNAME_LONGITUDE = "longitude" GEO_KEYNAME_ELEVATION = "elevation" GEO_KEYNAME_ACCURACY = "accuracy" GEO_KEYNAME_CITY = "city" GEO_KEYNAME_STATE = "state" GEO_KEYNAME_REGIONICANN = "regionICANN" # GEO_KEYNAME_ANNOTATIONS = "annotations" GEO_KEYNAMES = [GEO_KEYNAME_ID, GEO_KEYNAME_LATITUDE, GEO_KEYNAME_LONGITUDE, GEO_KEYNAME_ELEVATION, GEO_KEYNAME_ACCURACY, GEO_KEYNAME_CITY, GEO_KEYNAME_STATE, GEO_KEYNAME_REGIONICANN # GEO_KEYNAME_ANNOTATIONS ] class Geolocation(cadftype.CADFAbstractType): id = cadftype.ValidatorDescriptor(GEO_KEYNAME_ID, lambda x: identifier.is_valid(x)) # TODO(mrutkows): we may want to do more validation to make # sure numeric range represented by string is valid latitude = cadftype.ValidatorDescriptor(GEO_KEYNAME_LATITUDE, lambda x: isinstance( x, six.string_types)) longitude = cadftype.ValidatorDescriptor(GEO_KEYNAME_LONGITUDE, lambda x: isinstance( x, six.string_types)) elevation = cadftype.ValidatorDescriptor(GEO_KEYNAME_ELEVATION, lambda x: isinstance( x, six.string_types)) accuracy = cadftype.ValidatorDescriptor(GEO_KEYNAME_ACCURACY, lambda x: isinstance( x, six.string_types)) city = cadftype.ValidatorDescriptor(GEO_KEYNAME_CITY, lambda x: isinstance( x, six.string_types)) state = cadftype.ValidatorDescriptor(GEO_KEYNAME_STATE, lambda x: isinstance( x, six.string_types)) regionICANN = cadftype.ValidatorDescriptor( GEO_KEYNAME_REGIONICANN, lambda x: isinstance(x, six.string_types)) def __init__(self, id=None, latitude=None, longitude=None, elevation=None, accuracy=None, city=None, state=None, regionICANN=None): """Create Geolocation data type :param id: id of geolocation :param latitude: latitude of geolocation :param longitude: longitude of geolocation :param elevation: elevation of geolocation in meters :param accuracy: accuracy of geolocation in meters :param city: city of geolocation :param state: state/province of geolocation :param regionICANN: region of geolocation (ie. country) """ # Geolocation.id if id is not None: setattr(self, GEO_KEYNAME_ID, id) # Geolocation.latitude if latitude is not None: setattr(self, GEO_KEYNAME_LATITUDE, latitude) # Geolocation.longitude if longitude is not None: setattr(self, GEO_KEYNAME_LONGITUDE, longitude) # Geolocation.elevation if elevation is not None: setattr(self, GEO_KEYNAME_ELEVATION, elevation) # Geolocation.accuracy if accuracy is not None: setattr(self, GEO_KEYNAME_ACCURACY, accuracy) # Geolocation.city if city is not None: setattr(self, GEO_KEYNAME_CITY, city) # Geolocation.state if state is not None: setattr(self, GEO_KEYNAME_STATE, state) # Geolocation.regionICANN if regionICANN is not None: setattr(self, GEO_KEYNAME_REGIONICANN, regionICANN) # TODO(mrutkows): add mechanism for annotations, OpenStack may choose # not to support this "extension mechanism" and is not required (and not # critical in many audit contexts) def set_annotations(self, value): raise NotImplementedError() # setattr(self, GEO_KEYNAME_ANNOTATIONS, value) # self validate cadf:Geolocation type def is_valid(self): return True pycadf-2.7.0/pycadf/helper/000077500000000000000000000000001321055627100155155ustar00rootroot00000000000000pycadf-2.7.0/pycadf/helper/__init__.py000066400000000000000000000000001321055627100176140ustar00rootroot00000000000000pycadf-2.7.0/pycadf/helper/api.py000066400000000000000000000026431321055627100166450ustar00rootroot00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftaxonomy def convert_req_action(method, details=None): """Maps standard HTTP methods to equivalent CADF action :param method: HTTP request method :param details: Extra details to append to action. """ mapping = {'get': cadftaxonomy.ACTION_READ, 'head': cadftaxonomy.ACTION_READ, 'post': cadftaxonomy.ACTION_CREATE, 'put': cadftaxonomy.ACTION_UPDATE, 'delete': cadftaxonomy.ACTION_DELETE, 'patch': cadftaxonomy.ACTION_UPDATE, 'options': cadftaxonomy.ACTION_READ, 'trace': 'capture'} action = None if isinstance(method, six.string_types): action = mapping.get(method.lower()) if action and isinstance(details, six.string_types): action += '/%s' % details return action or cadftaxonomy.UNKNOWN pycadf-2.7.0/pycadf/host.py000066400000000000000000000045051321055627100155710ustar00rootroot00000000000000# Copyright (c) 2013 IBM Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype from pycadf import identifier TYPE_URI_HOST = cadftype.CADF_VERSION_1_0_0 + 'host' HOST_KEYNAME_ID = "id" HOST_KEYNAME_ADDR = "address" HOST_KEYNAME_AGENT = "agent" HOST_KEYNAME_PLATFORM = "platform" HOST_KEYNAMES = [HOST_KEYNAME_ID, HOST_KEYNAME_ADDR, HOST_KEYNAME_AGENT, HOST_KEYNAME_PLATFORM] class Host(cadftype.CADFAbstractType): id = cadftype.ValidatorDescriptor( HOST_KEYNAME_ID, lambda x: identifier.is_valid(x)) address = cadftype.ValidatorDescriptor( HOST_KEYNAME_ADDR, lambda x: isinstance(x, six.string_types)) agent = cadftype.ValidatorDescriptor( HOST_KEYNAME_AGENT, lambda x: isinstance(x, six.string_types)) platform = cadftype.ValidatorDescriptor( HOST_KEYNAME_PLATFORM, lambda x: isinstance(x, six.string_types)) def __init__(self, id=None, address=None, agent=None, platform=None): """Create Host data type :param id: id of Host :param address: optional Address of Host :param agent: agent (name) of Host :param platform: platform of Host """ # Host.id if id is not None: setattr(self, HOST_KEYNAME_ID, id) # Host.address if address is not None: setattr(self, HOST_KEYNAME_ADDR, address) # Host.agent if agent is not None: setattr(self, HOST_KEYNAME_AGENT, agent) # Host.platform if platform is not None: setattr(self, HOST_KEYNAME_PLATFORM, platform) # TODO(mrutkows): validate this cadf:Host type against schema def is_valid(self): """Validation to ensure Host required attributes are set. """ return True pycadf-2.7.0/pycadf/identifier.py000066400000000000000000000042261321055627100167360ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import hashlib import re import uuid import warnings from oslo_config import cfg import six CONF = cfg.CONF opts = [ cfg.StrOpt('namespace', default='openstack', help='namespace prefix for generated id'), ] CONF.register_opts(opts, group='audit') AUDIT_NS = None if CONF.audit.namespace: md5_hash = hashlib.md5(CONF.audit.namespace.encode('utf-8')) AUDIT_NS = uuid.UUID(md5_hash.hexdigest()) VALID_EXCEPTIONS = ['default', 'initiator', 'observer', 'target'] def generate_uuid(): """Generate a CADF identifier.""" if AUDIT_NS: return str(uuid.uuid5(AUDIT_NS, str(uuid.uuid4()))) return str(uuid.uuid4()) def _check_valid_uuid(value): """Checks a value for one or multiple valid uuids joined together.""" if not value: raise ValueError value = re.sub('[{}-]|urn:uuid:', '', value) for val in [value[i:i + 32] for i in range(0, len(value), 32)]: uuid.UUID(val) def is_valid(value): """Validation to ensure Identifier is correct. If the Identifier value is a string type but not a valid UUID string, warn against interoperability issues and return True. This relaxes the requirement of having strict UUID checking. """ if value in VALID_EXCEPTIONS: return True try: _check_valid_uuid(value) except (ValueError, TypeError): if not isinstance(value, six.string_types) or not value: return False warnings.warn(('Invalid uuid: %s. To ensure interoperability, ' 'identifiers should be a valid uuid.' % (value))) return True pycadf-2.7.0/pycadf/measurement.py000066400000000000000000000054711321055627100171440ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. from pycadf import cadftype from pycadf import identifier from pycadf import metric from pycadf import resource MEASUREMENT_KEYNAME_RESULT = "result" MEASUREMENT_KEYNAME_METRIC = "metric" MEASUREMENT_KEYNAME_METRICID = "metricId" MEASUREMENT_KEYNAME_CALCBY = "calculatedBy" MEASUREMENT_KEYNAMES = [MEASUREMENT_KEYNAME_RESULT, MEASUREMENT_KEYNAME_METRICID, MEASUREMENT_KEYNAME_METRIC, MEASUREMENT_KEYNAME_CALCBY] class Measurement(cadftype.CADFAbstractType): result = cadftype.ValidatorDescriptor(MEASUREMENT_KEYNAME_RESULT) metric = cadftype.ValidatorDescriptor( MEASUREMENT_KEYNAME_METRIC, lambda x: isinstance(x, metric.Metric)) metricId = cadftype.ValidatorDescriptor(MEASUREMENT_KEYNAME_METRICID, lambda x: identifier.is_valid(x)) calculatedBy = cadftype.ValidatorDescriptor( MEASUREMENT_KEYNAME_CALCBY, (lambda x: isinstance(x, resource.Resource) and x.is_valid())) def __init__(self, result=None, metric=None, metricId=None, calculatedBy=None): """Create Measurement data type :param result: value of measurement :param metric: Metric data type of current measurement :param metricId: id of Metric data type of current measurement :param calculatedBy: Resource that calculated measurement """ # Measurement.result if result is not None: setattr(self, MEASUREMENT_KEYNAME_RESULT, result) # Measurement.metricId if metricId is not None: setattr(self, MEASUREMENT_KEYNAME_METRICID, metricId) # Measurement.metric if metric is not None: setattr(self, MEASUREMENT_KEYNAME_METRIC, metric) # Measurement.calculaedBy if calculatedBy is not None: setattr(self, MEASUREMENT_KEYNAME_CALCBY, calculatedBy) # self validate this cadf:Measurement type against schema def is_valid(self): """Validation to ensure Measurement required attributes are set. """ return (self._isset(MEASUREMENT_KEYNAME_RESULT) and (self._isset(MEASUREMENT_KEYNAME_METRIC) ^ self._isset(MEASUREMENT_KEYNAME_METRICID))) pycadf-2.7.0/pycadf/metric.py000066400000000000000000000057331321055627100161030ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype from pycadf import identifier # Metric types can appear outside a cadf:Event record context, in these cases # a typeURI may be used to identify the cadf:Metric data type. TYPE_URI_METRIC = cadftype.CADF_VERSION_1_0_0 + 'metric' METRIC_KEYNAME_METRICID = "metricId" METRIC_KEYNAME_UNIT = "unit" METRIC_KEYNAME_NAME = "name" # METRIC_KEYNAME_ANNOTATIONS = "annotations" METRIC_KEYNAMES = [METRIC_KEYNAME_METRICID, METRIC_KEYNAME_UNIT, METRIC_KEYNAME_NAME # METRIC_KEYNAME_ANNOTATIONS ] class Metric(cadftype.CADFAbstractType): metricId = cadftype.ValidatorDescriptor(METRIC_KEYNAME_METRICID, lambda x: identifier.is_valid(x)) unit = cadftype.ValidatorDescriptor(METRIC_KEYNAME_UNIT, lambda x: isinstance(x, six.string_types)) name = cadftype.ValidatorDescriptor(METRIC_KEYNAME_NAME, lambda x: isinstance(x, six.string_types)) def __init__(self, metricId=None, unit=None, name=None): """Create metric data type :param metricId: id of metric. uuid generated if not provided :param unit: unit of metric :param name: name of metric """ # Metric.id setattr(self, METRIC_KEYNAME_METRICID, metricId or identifier.generate_uuid()) # Metric.unit if unit is not None: setattr(self, METRIC_KEYNAME_UNIT, unit) # Metric.name if name is not None: setattr(self, METRIC_KEYNAME_NAME, name) # TODO(mrutkows): add mechanism for annotations, OpenStack may choose # not to support this "extension mechanism" and is not required (and not # critical in many audit contexts) def set_annotations(self, value): raise NotImplementedError() # setattr(self, METRIC_KEYNAME_ANNOTATIONS, value) # self validate cadf:Metric type against schema def is_valid(self): """Validation to ensure Metric required attributes are set. """ # Existence test, id, and unit attributes must both exist return ( self._isset(METRIC_KEYNAME_METRICID) and self._isset(METRIC_KEYNAME_UNIT) ) pycadf-2.7.0/pycadf/path.py000066400000000000000000000022151321055627100155440ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype class Path(cadftype.CADFAbstractType): def set_path_absolute(self): # TODO(mrutkows): validate absolute path format, else Type error raise NotImplementedError() def set_path_relative(self): # TODO(mrutkows); validate relative path format, else Type error raise NotImplementedError() # TODO(mrutkows): validate any cadf:Path (type) record against CADF schema @staticmethod def is_valid(value): if not isinstance(value, six.string_types): raise TypeError return True pycadf-2.7.0/pycadf/reason.py000066400000000000000000000056451321055627100161110ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import cadftype TYPE_URI_REASON = cadftype.CADF_VERSION_1_0_0 + 'reason' REASON_KEYNAME_REASONTYPE = "reasonType" REASON_KEYNAME_REASONCODE = "reasonCode" REASON_KEYNAME_POLICYTYPE = "policyType" REASON_KEYNAME_POLICYID = "policyId" REASON_KEYNAMES = [REASON_KEYNAME_REASONTYPE, REASON_KEYNAME_REASONCODE, REASON_KEYNAME_POLICYTYPE, REASON_KEYNAME_POLICYID] class Reason(cadftype.CADFAbstractType): reasonType = cadftype.ValidatorDescriptor( REASON_KEYNAME_REASONTYPE, lambda x: isinstance(x, six.string_types)) reasonCode = cadftype.ValidatorDescriptor( REASON_KEYNAME_REASONCODE, lambda x: isinstance(x, six.string_types)) policyType = cadftype.ValidatorDescriptor( REASON_KEYNAME_POLICYTYPE, lambda x: isinstance(x, six.string_types)) policyId = cadftype.ValidatorDescriptor( REASON_KEYNAME_POLICYID, lambda x: isinstance(x, six.string_types)) def __init__(self, reasonType=None, reasonCode=None, policyType=None, policyId=None): """Create Reason data type :param reasonType: domain URI which describes reasonCode :param reasonCode: detailed result code :param policyType: domain URI which describes policyId :param policyId: id of policy applied that describes outcome """ # Reason.reasonType if reasonType is not None: setattr(self, REASON_KEYNAME_REASONTYPE, reasonType) # Reason.reasonCode if reasonCode is not None: setattr(self, REASON_KEYNAME_REASONCODE, reasonCode) # Reason.policyType if policyType is not None: setattr(self, REASON_KEYNAME_POLICYTYPE, policyType) # Reason.policyId if policyId is not None: setattr(self, REASON_KEYNAME_POLICYID, policyId) # TODO(mrutkows): validate this cadf:Reason type against schema def is_valid(self): """Validation to ensure Reason required attributes are set. """ # MUST have at least one valid pairing of reason+code or policy+id return ((self._isset(REASON_KEYNAME_REASONTYPE) and self._isset(REASON_KEYNAME_REASONCODE)) or (self._isset(REASON_KEYNAME_POLICYTYPE) and self._isset(REASON_KEYNAME_POLICYID))) pycadf-2.7.0/pycadf/reporterstep.py000066400000000000000000000060541321055627100173530ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. from pycadf import cadftype from pycadf import identifier from pycadf import resource from pycadf import timestamp REPORTERSTEP_KEYNAME_ROLE = "role" REPORTERSTEP_KEYNAME_REPORTER = "reporter" REPORTERSTEP_KEYNAME_REPORTERID = "reporterId" REPORTERSTEP_KEYNAME_REPORTERTIME = "reporterTime" # REPORTERSTEP_KEYNAME_ATTACHMENTS = "attachments" REPORTERSTEP_KEYNAMES = [REPORTERSTEP_KEYNAME_ROLE, REPORTERSTEP_KEYNAME_REPORTER, REPORTERSTEP_KEYNAME_REPORTERID, REPORTERSTEP_KEYNAME_REPORTERTIME, # REPORTERSTEP_KEYNAME_ATTACHMENTS ] class Reporterstep(cadftype.CADFAbstractType): role = cadftype.ValidatorDescriptor( REPORTERSTEP_KEYNAME_ROLE, lambda x: cadftype.is_valid_reporter_role(x)) reporter = cadftype.ValidatorDescriptor( REPORTERSTEP_KEYNAME_REPORTER, (lambda x: isinstance(x, resource.Resource) and x.is_valid())) reporterId = cadftype.ValidatorDescriptor( REPORTERSTEP_KEYNAME_REPORTERID, lambda x: identifier.is_valid(x)) reporterTime = cadftype.ValidatorDescriptor( REPORTERSTEP_KEYNAME_REPORTERTIME, lambda x: timestamp.is_valid(x)) def __init__(self, role=cadftype.REPORTER_ROLE_MODIFIER, reporterTime=None, reporter=None, reporterId=None): """Create ReporterStep data type :param role: optional role of Reporterstep. Defaults to 'modifier' :param reporterTime: utc time of Reporterstep. :param reporter: CADF Resource of reporter :param reporterId: id of CADF resource for reporter """ # Reporterstep.role setattr(self, REPORTERSTEP_KEYNAME_ROLE, role) # Reporterstep.reportTime if reporterTime is not None: setattr(self, REPORTERSTEP_KEYNAME_REPORTERTIME, reporterTime) # Reporterstep.reporter if reporter is not None: setattr(self, REPORTERSTEP_KEYNAME_REPORTER, reporter) # Reporterstep.reporterId if reporterId is not None: setattr(self, REPORTERSTEP_KEYNAME_REPORTERID, reporterId) # self validate this cadf:Reporterstep type against schema def is_valid(self): """Validation to ensure Reporterstep required attributes are set. """ return ( self._isset(REPORTERSTEP_KEYNAME_ROLE) and (self._isset(REPORTERSTEP_KEYNAME_REPORTER) ^ self._isset(REPORTERSTEP_KEYNAME_REPORTERID)) ) pycadf-2.7.0/pycadf/resource.py000066400000000000000000000164531321055627100164500ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six from pycadf import attachment from pycadf import cadftaxonomy from pycadf import cadftype from pycadf import credential from pycadf import endpoint from pycadf import geolocation from pycadf import host from pycadf import identifier TYPE_URI_RESOURCE = cadftype.CADF_VERSION_1_0_0 + 'resource' RESOURCE_KEYNAME_TYPEURI = "typeURI" RESOURCE_KEYNAME_ID = "id" RESOURCE_KEYNAME_NAME = "name" RESOURCE_KEYNAME_DOMAIN = "domain" RESOURCE_KEYNAME_CRED = "credential" RESOURCE_KEYNAME_REF = "ref" RESOURCE_KEYNAME_GEO = "geolocation" RESOURCE_KEYNAME_GEOID = "geolocationId" RESOURCE_KEYNAME_HOST = "host" RESOURCE_KEYNAME_ADDRS = "addresses" RESOURCE_KEYNAME_ATTACHMENTS = "attachments" RESOURCE_KEYNAMES = [RESOURCE_KEYNAME_TYPEURI, RESOURCE_KEYNAME_ID, RESOURCE_KEYNAME_NAME, RESOURCE_KEYNAME_DOMAIN, RESOURCE_KEYNAME_CRED, RESOURCE_KEYNAME_REF, RESOURCE_KEYNAME_GEO, RESOURCE_KEYNAME_GEOID, RESOURCE_KEYNAME_HOST, RESOURCE_KEYNAME_ADDRS, RESOURCE_KEYNAME_ATTACHMENTS] class Resource(cadftype.CADFAbstractType): typeURI = cadftype.ValidatorDescriptor( RESOURCE_KEYNAME_TYPEURI, lambda x: cadftaxonomy.is_valid_resource(x)) id = cadftype.ValidatorDescriptor(RESOURCE_KEYNAME_ID, lambda x: identifier.is_valid(x)) name = cadftype.ValidatorDescriptor(RESOURCE_KEYNAME_NAME, lambda x: isinstance(x, six.string_types)) domain = cadftype.ValidatorDescriptor(RESOURCE_KEYNAME_DOMAIN, lambda x: isinstance( x, six.string_types)) credential = cadftype.ValidatorDescriptor( RESOURCE_KEYNAME_CRED, (lambda x: isinstance(x, credential.Credential) and x.is_valid())) host = cadftype.ValidatorDescriptor( RESOURCE_KEYNAME_HOST, lambda x: isinstance(x, host.Host)) # TODO(mrutkows): validate the "ref" attribute is indeed a URI (format), # If it is a URL, we do not need to validate it is accessible/working, # for audit purposes this could have been a valid URL at some point # in the past or a URL that is only valid within some domain (e.g. a # private cloud) ref = cadftype.ValidatorDescriptor(RESOURCE_KEYNAME_REF, lambda x: isinstance(x, six.string_types)) geolocation = cadftype.ValidatorDescriptor( RESOURCE_KEYNAME_GEO, lambda x: isinstance(x, geolocation.Geolocation)) geolocationId = cadftype.ValidatorDescriptor( RESOURCE_KEYNAME_GEOID, lambda x: identifier.is_valid(x)) def __init__(self, id=None, typeURI=cadftaxonomy.UNKNOWN, name=None, ref=None, domain=None, credential=None, host=None, geolocation=None, geolocationId=None): """Resource data type :param id: id of resource :param typeURI: typeURI of resource, defaults to 'unknown' if not set :param name: name of resource :param domain: domain to qualify name of resource :param credential: optional security Credential data type :param host: optional Host data type information relating to resource :param geolocation: optional CADF Geolocation of resource :param geolocationId: optional id of CADF Geolocation for resource """ # Resource.id setattr(self, RESOURCE_KEYNAME_ID, id or identifier.generate_uuid()) # Resource.typeURI if (getattr(self, RESOURCE_KEYNAME_ID) != "target" and getattr(self, RESOURCE_KEYNAME_ID) != "initiator"): setattr(self, RESOURCE_KEYNAME_TYPEURI, typeURI) # Resource.name if name is not None: setattr(self, RESOURCE_KEYNAME_NAME, name) # Resource.ref if ref is not None: setattr(self, RESOURCE_KEYNAME_REF, ref) # Resource.domain if domain is not None: setattr(self, RESOURCE_KEYNAME_DOMAIN, domain) # Resource.credential if credential is not None: setattr(self, RESOURCE_KEYNAME_CRED, credential) # Resource.host if host is not None: setattr(self, RESOURCE_KEYNAME_HOST, host) # Resource.geolocation if geolocation is not None: setattr(self, RESOURCE_KEYNAME_GEO, geolocation) # Resource.geolocationId if geolocationId: setattr(self, RESOURCE_KEYNAME_GEOID, geolocationId) # Resource.address def add_address(self, addr): """Add CADF endpoints to Resource :param addr: CADF Endpoint to add to Resource """ if (addr is not None and isinstance(addr, endpoint.Endpoint)): if addr.is_valid(): # Create the list of Endpoints if needed if not hasattr(self, RESOURCE_KEYNAME_ADDRS): setattr(self, RESOURCE_KEYNAME_ADDRS, list()) addrs = getattr(self, RESOURCE_KEYNAME_ADDRS) addrs.append(addr) else: raise ValueError('Invalid endpoint') else: raise ValueError('Invalid endpoint. Value must be an Endpoint') # Resource.attachments def add_attachment(self, attach_val): """Add CADF attachment to Resource :param attach_val: CADF Attachment to add to Resource """ if (attach_val is not None and isinstance(attach_val, attachment.Attachment)): if attach_val.is_valid(): # Create the list of Attachments if needed if not hasattr(self, RESOURCE_KEYNAME_ATTACHMENTS): setattr(self, RESOURCE_KEYNAME_ATTACHMENTS, list()) attachments = getattr(self, RESOURCE_KEYNAME_ATTACHMENTS) attachments.append(attach_val) else: raise ValueError('Invalid attachment') else: raise ValueError('Invalid attachment. Value must be an Attachment') # self validate this cadf:Resource type against schema def is_valid(self): """Validation to ensure Resource required attributes are set """ return (self._isset(RESOURCE_KEYNAME_ID) and (self._isset(RESOURCE_KEYNAME_TYPEURI) or ((getattr(self, RESOURCE_KEYNAME_ID) == "target" or getattr(self, RESOURCE_KEYNAME_ID) == "initiator") and len(vars(self).keys()) == 1))) # TODO(mrutkows): validate the Resource's attribute types pycadf-2.7.0/pycadf/tag.py000066400000000000000000000022061321055627100153630ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six def generate_name_value_tag(name, value): """Generate a CADF tag in the format name?value= :param name: name of tag :param valuue: optional value tag """ if name is None or value is None: raise ValueError('Invalid name and/or value. Values cannot be None') tag = name + "?value=" + value return tag # TODO(mrutkows): validate any Tag's name?value= format def is_valid(value): """Validation check to ensure proper Tag format """ if not isinstance(value, six.string_types): raise TypeError return True pycadf-2.7.0/pycadf/tests/000077500000000000000000000000001321055627100154005ustar00rootroot00000000000000pycadf-2.7.0/pycadf/tests/__init__.py000066400000000000000000000000001321055627100174770ustar00rootroot00000000000000pycadf-2.7.0/pycadf/tests/base.py000066400000000000000000000031711321055627100166660ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. """Test base classes.""" import os.path import fixtures from oslo_config import cfg from oslotest import moxstubout import testtools class TestCase(testtools.TestCase): def setUp(self): super(TestCase, self).setUp() self.tempdir = self.useFixture(fixtures.TempDir()) moxfixture = self.useFixture(moxstubout.MoxStubout()) self.mox = moxfixture.mox self.stubs = moxfixture.stubs cfg.CONF([], project='pycadf') def path_get(self, project_file=None): root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', ) ) if project_file: return os.path.join(root, project_file) else: return root def temp_config_file_path(self, name='api_audit_map.conf'): return os.path.join(self.tempdir.path, name) def tearDown(self): cfg.CONF.reset() super(TestCase, self).tearDown() pycadf-2.7.0/pycadf/tests/helper/000077500000000000000000000000001321055627100166575ustar00rootroot00000000000000pycadf-2.7.0/pycadf/tests/helper/__init__.py000066400000000000000000000000001321055627100207560ustar00rootroot00000000000000pycadf-2.7.0/pycadf/tests/helper/test_api.py000066400000000000000000000036031321055627100210430ustar00rootroot00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. from pycadf import cadftaxonomy from pycadf.helper import api from pycadf.tests import base class TestApiHelper(base.TestCase): def test_convert_req_action(self): self.assertEqual(cadftaxonomy.ACTION_READ, api.convert_req_action('get')) self.assertEqual(cadftaxonomy.ACTION_CREATE, api.convert_req_action('POST')) self.assertEqual(cadftaxonomy.ACTION_DELETE, api.convert_req_action('deLetE')) def test_convert_req_action_invalid(self): self.assertEqual(cadftaxonomy.UNKNOWN, api.convert_req_action(124)) self.assertEqual(cadftaxonomy.UNKNOWN, api.convert_req_action('blah')) def test_convert_req_action_with_details(self): detail = 'compute/instance' self.assertEqual(cadftaxonomy.ACTION_READ + '/%s' % detail, api.convert_req_action('GET', detail)) self.assertEqual(cadftaxonomy.ACTION_DELETE + '/%s' % detail, api.convert_req_action('DELETE', detail)) def test_convert_req_action_with_details_invalid(self): detail = 123 self.assertEqual(cadftaxonomy.ACTION_READ, api.convert_req_action('GET', detail)) self.assertEqual(cadftaxonomy.ACTION_DELETE, api.convert_req_action('DELETE', detail)) pycadf-2.7.0/pycadf/tests/test_cadf_spec.py000066400000000000000000000406751321055627100207340ustar00rootroot00000000000000# Copyright 2013 OpenStack LLC # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import time import uuid import mock from pycadf import attachment from pycadf import cadftype from pycadf import credential from pycadf import endpoint from pycadf import event from pycadf import geolocation from pycadf import host from pycadf import identifier from pycadf import measurement from pycadf import metric from pycadf import reason from pycadf import reporterstep from pycadf import resource from pycadf import tag from pycadf.tests import base from pycadf import timestamp class TestCADFSpec(base.TestCase): @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_generated_uuid(self, warning_mock): # generated uuid self.assertTrue(identifier.is_valid(identifier.generate_uuid())) self.assertFalse(warning_mock.called) @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_empty_string_is_invalid(self, warning_mock): # empty string self.assertFalse(identifier.is_valid('')) self.assertFalse(warning_mock.called) @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_any_string_is_invalid(self, warning_mock): # any string self.assertTrue(identifier.is_valid('blah')) self.assertTrue(warning_mock.called) @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_joined_uuids_are_valid(self, warning_mock): # multiple uuids joined together long_128_uuids = [ ('3adce28e67e44544a5a9d5f1ab54f578a86d310aac3a465e9d' 'd2693a78b45c0e42dce28e67e44544a5a9d5f1ab54f578a86d' '310aac3a465e9dd2693a78b45c0e'), ('{3adce28e67e44544a5a9d5f1ab54f578a86d310aac3a465e9d' 'd2693a78b45c0e42dce28e67e44544a5a9d5f1ab54f578a86d' '310aac3a465e9dd2693a78b45c0e}'), ('{12345678-1234-5678-1234-567812345678' '12345678-1234-5678-1234-567812345678' '12345678-1234-5678-1234-567812345678' '12345678-1234-5678-1234-567812345678}'), ('urn:uuid:3adce28e67e44544a5a9d5f1ab54f578a86d310aac3a465e9d' 'd2693a78b45c0e42dce28e67e44544a5a9d5f1ab54f578a86d' '310aac3a465e9dd2693a78b45c0e')] for value in long_128_uuids: self.assertTrue(identifier.is_valid(value)) self.assertFalse(warning_mock.called) @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_long_nonjoined_uuid_is_invalid(self, warning_mock): # long uuid not of size % 32 char_42_id = '3adce28e67e44544a5a9d5f1ab54f578a86d310aac' self.assertTrue(identifier.is_valid(char_42_id)) self.assertTrue(warning_mock.called) @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_specific_exceptions_are_valid(self, warning_mock): # uuid exceptions for value in identifier.VALID_EXCEPTIONS: self.assertTrue(identifier.is_valid(value)) self.assertFalse(warning_mock.called) @mock.patch('pycadf.identifier.warnings.warn') def test_identifier_valid_id_extra_chars_is_valid(self, warning_mock): # valid uuid with additional characters according to: # https://docs.python.org/2/library/uuid.html valid_ids = [ '{1234567890abcdef1234567890abcdef}', '{12345678-1234-5678-1234-567812345678}', 'urn:uuid:12345678-1234-5678-1234-567812345678'] for value in valid_ids: self.assertTrue(identifier.is_valid(value)) self.assertFalse(warning_mock.called) def test_endpoint(self): endp = endpoint.Endpoint(url='http://192.168.0.1', name='endpoint name', port='8080') self.assertEqual(True, endp.is_valid()) dict_endp = endp.as_dict() for key in endpoint.ENDPOINT_KEYNAMES: self.assertIn(key, dict_endp) def test_host(self): h = host.Host(id=identifier.generate_uuid(), address='192.168.0.1', agent='client', platform='AIX') self.assertEqual(True, h.is_valid()) dict_host = h.as_dict() for key in host.HOST_KEYNAMES: self.assertIn(key, dict_host) def test_credential(self): cred = credential.Credential(type='auth token', token=identifier.generate_uuid()) self.assertEqual(True, cred.is_valid()) dict_cred = cred.as_dict() for key in credential.CRED_KEYNAMES: self.assertIn(key, dict_cred) def test_federated_credential(self): cred = credential.FederatedCredential( token=identifier.generate_uuid(), type='http://docs.oasis-open.org/security/saml/v2.0', identity_provider=identifier.generate_uuid(), user=identifier.generate_uuid(), groups=[ identifier.generate_uuid(), identifier.generate_uuid(), identifier.generate_uuid()]) self.assertEqual(True, cred.is_valid()) dict_cred = cred.as_dict() for key in credential.FED_CRED_KEYNAMES: self.assertIn(key, dict_cred) def test_geolocation(self): geo = geolocation.Geolocation(id=identifier.generate_uuid(), latitude='43.6481 N', longitude='79.4042 W', elevation='0', accuracy='1', city='toronto', state='ontario', regionICANN='ca') self.assertEqual(True, geo.is_valid()) dict_geo = geo.as_dict() for key in geolocation.GEO_KEYNAMES: self.assertIn(key, dict_geo) def test_metric(self): metric_val = metric.Metric(metricId=identifier.generate_uuid(), unit='b', name='bytes') self.assertEqual(True, metric_val.is_valid()) dict_metric_val = metric_val.as_dict() for key in metric.METRIC_KEYNAMES: self.assertIn(key, dict_metric_val) def test_measurement(self): measure_val = measurement.Measurement( result='100', metric=metric.Metric(), metricId=identifier.generate_uuid(), calculatedBy=resource.Resource(typeURI='storage')) self.assertEqual(False, measure_val.is_valid()) dict_measure_val = measure_val.as_dict() for key in measurement.MEASUREMENT_KEYNAMES: self.assertIn(key, dict_measure_val) measure_val = measurement.Measurement( result='100', metric=metric.Metric(), calculatedBy=resource.Resource(typeURI='storage')) self.assertEqual(True, measure_val.is_valid()) measure_val = measurement.Measurement( result='100', metricId=identifier.generate_uuid(), calculatedBy=resource.Resource(typeURI='storage')) self.assertEqual(True, measure_val.is_valid()) def test_reason(self): reason_val = reason.Reason(reasonType='HTTP', reasonCode='200', policyType='poltype', policyId=identifier.generate_uuid()) self.assertEqual(True, reason_val.is_valid()) dict_reason_val = reason_val.as_dict() for key in reason.REASON_KEYNAMES: self.assertIn(key, dict_reason_val) def test_reporterstep(self): step = reporterstep.Reporterstep( role='modifier', reporter=resource.Resource(typeURI='storage'), reporterId=identifier.generate_uuid(), reporterTime=timestamp.get_utc_now()) self.assertEqual(False, step.is_valid()) dict_step = step.as_dict() for key in reporterstep.REPORTERSTEP_KEYNAMES: self.assertIn(key, dict_step) step = reporterstep.Reporterstep( role='modifier', reporter=resource.Resource(typeURI='storage'), reporterTime=timestamp.get_utc_now()) self.assertEqual(True, step.is_valid()) step = reporterstep.Reporterstep( role='modifier', reporterId=identifier.generate_uuid(), reporterTime=timestamp.get_utc_now()) self.assertEqual(True, step.is_valid()) def test_attachment(self): attach = attachment.Attachment(typeURI='attachURI', content='content', name='attachment_name') self.assertEqual(True, attach.is_valid()) dict_attach = attach.as_dict() for key in attachment.ATTACHMENT_KEYNAMES: self.assertIn(key, dict_attach) def test_resource(self): res = resource.Resource(typeURI='storage', name='res_name', domain='res_domain', ref='res_ref', credential=credential.Credential( token=identifier.generate_uuid()), host=host.Host(address='192.168.0.1'), geolocation=geolocation.Geolocation(), geolocationId=identifier.generate_uuid()) res.add_attachment(attachment.Attachment(typeURI='attachURI', content='content', name='attachment_name')) res.add_address(endpoint.Endpoint(url='http://192.168.0.1')) self.assertEqual(True, res.is_valid()) dict_res = res.as_dict() for key in resource.RESOURCE_KEYNAMES: self.assertIn(key, dict_res) def test_resource_shortform(self): res = resource.Resource(id='target') self.assertEqual(True, res.is_valid()) res.add_attachment(attachment.Attachment(typeURI='attachURI', content='content', name='attachment_name')) self.assertEqual(False, res.is_valid()) def test_event(self): ev = event.Event(eventType='activity', id=identifier.generate_uuid(), eventTime=timestamp.get_utc_now(), initiator=resource.Resource(typeURI='storage'), initiatorId=identifier.generate_uuid(), action='read', target=resource.Resource(typeURI='storage'), targetId=identifier.generate_uuid(), observer=resource.Resource(id='target'), observerId=identifier.generate_uuid(), outcome='success', reason=reason.Reason(reasonType='HTTP', reasonCode='200'), severity='high', name='descriptive name') ev.add_measurement( measurement.Measurement(result='100', metricId=identifier.generate_uuid())), ev.add_tag(tag.generate_name_value_tag('name', 'val')) ev.add_attachment(attachment.Attachment(typeURI='attachURI', content='content', name='attachment_name')) ev.observer = resource.Resource(typeURI='service/security') ev.add_reporterstep(reporterstep.Reporterstep( role='observer', reporter=resource.Resource(typeURI='service/security'))) ev.add_reporterstep(reporterstep.Reporterstep( reporterId=identifier.generate_uuid())) self.assertEqual(False, ev.is_valid()) dict_ev = ev.as_dict() for key in event.EVENT_KEYNAMES: self.assertIn(key, dict_ev) ev = event.Event(eventType='activity', id=identifier.generate_uuid(), eventTime=timestamp.get_utc_now(), initiator=resource.Resource(typeURI='storage'), action='read', target=resource.Resource(typeURI='storage'), observer=resource.Resource(id='target'), outcome='success') self.assertEqual(True, ev.is_valid()) ev = event.Event(eventType='activity', id=identifier.generate_uuid(), eventTime=timestamp.get_utc_now(), initiatorId=identifier.generate_uuid(), action='read', targetId=identifier.generate_uuid(), observerId=identifier.generate_uuid(), outcome='success') self.assertEqual(True, ev.is_valid()) ev = event.Event(eventType='activity', id=identifier.generate_uuid(), eventTime=timestamp.get_utc_now(), initiator=resource.Resource(typeURI='storage'), action='read', targetId=identifier.generate_uuid(), observer=resource.Resource(id='target'), outcome='success') self.assertEqual(True, ev.is_valid()) def test_event_unique(self): ev = event.Event(eventType='activity', initiator=resource.Resource(typeURI='storage'), action='read', target=resource.Resource(typeURI='storage'), observer=resource.Resource(id='target'), outcome='success') time.sleep(1) ev2 = event.Event(eventType='activity', initiator=resource.Resource(typeURI='storage'), action='read', target=resource.Resource(typeURI='storage'), observer=resource.Resource(id='target'), outcome='success') self.assertNotEqual(ev.id, ev2.id) self.assertNotEqual(ev.eventTime, ev2.eventTime) def test_event_resource_shortform_not_self(self): self.assertRaises(ValueError, lambda: event.Event( eventType='activity', initiator=resource.Resource(typeURI='storage'), action='read', target=resource.Resource(id='target'), observer=resource.Resource(id='target'), outcome='success')) self.assertRaises(ValueError, lambda: event.Event( eventType='activity', initiator=resource.Resource(id='initiator'), action='read', target=resource.Resource(typeURI='storage'), observer=resource.Resource(id='target'), outcome='success')) def _create_none_validator_descriptor(self): class Owner(object): x = cadftype.ValidatorDescriptor(uuid.uuid4().hex) owner = Owner() owner.x = None def test_invalid_value_descriptor(self): """Test setting a ValidatorDescriptor to None results in ValueError""" self.assertRaises(ValueError, self._create_none_validator_descriptor) def test_cadfabstracttype_attribute_error(self): """Test an invalid CADFAbstractType attribute is set returns False""" h = host.Host(id=identifier.generate_uuid(), address='192.168.0.1', agent='client', platform='AIX') self.assertEqual(False, h._isset(uuid.uuid4().hex)) pycadf-2.7.0/pycadf/tests/test_utils.py000066400000000000000000000026231321055627100201540ustar00rootroot00000000000000# Copyright 2013 OpenStack LLC # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import uuid from pycadf.tests import base from pycadf import utils class TestUtils(base.TestCase): def test_mask_value(self): value = str(uuid.uuid4()) m_percent = 0.125 obfuscate = utils.mask_value(value, m_percent) visible = int(round(len(value) * m_percent)) self.assertEqual(value[:visible], obfuscate[:visible]) self.assertNotEqual(value[:visible + 1], obfuscate[:visible + 1]) self.assertEqual(value[-visible:], obfuscate[-visible:]) self.assertNotEqual(value[-visible - 1:], obfuscate[-visible - 1:]) def test_mask_value_nonstring(self): value = 12 # If a non-string parameter is given to mask_value(), the non-string # parameter is returned unmodified. obfuscate = utils.mask_value(value) self.assertEqual(value, obfuscate) pycadf-2.7.0/pycadf/timestamp.py000066400000000000000000000025541321055627100166210ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import datetime import pytz import six TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z" def get_utc_now(timezone=None): """Return the current UTC time. :param timezone: an optional timezone param to offset time to. """ utc_datetime = pytz.utc.localize(datetime.datetime.utcnow()) if timezone is not None: try: utc_datetime = utc_datetime.astimezone(pytz.timezone(timezone)) except Exception: utc_datetime.strftime(TIME_FORMAT) return utc_datetime.strftime(TIME_FORMAT) # TODO(mrutkows): validate any cadf:Timestamp (type) record against # CADF schema def is_valid(value): """Validation to ensure timestamp is a string. """ if not isinstance(value, six.string_types): raise ValueError('Timestamp should be a String') return True pycadf-2.7.0/pycadf/utils.py000066400000000000000000000020441321055627100157500ustar00rootroot00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import six def mask_value(value, s_percent=0.125): """Obfuscate a given string to show only a percentage of leading and trailing characters. :param s_percent: The percentage (in decimal) of characters to replace """ if isinstance(value, six.string_types): visible = (32 if int(round(len(value) * s_percent)) > 32 else int(round(len(value) * s_percent))) return value[:visible] + " xxxxxxxx " + value[-visible:] return value pycadf-2.7.0/requirements.txt000066400000000000000000000005521321055627100162560ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. oslo.config>=4.6.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 pytz>=2013.6 # MIT six>=1.10.0 # MIT debtcollector>=1.2.0 # Apache-2.0 pycadf-2.7.0/setup.cfg000066400000000000000000000023711321055627100146140ustar00rootroot00000000000000[metadata] name = pycadf author = OpenStack author-email = openstack-dev@lists.openstack.org summary = CADF Library description-file = README.rst home-page = https://docs.openstack.org/pycadf/latest/ classifier = Development Status :: 3 - Alpha Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = pycadf data_files = etc/pycadf = etc/pycadf/cinder_api_audit_map.conf etc/pycadf/glance_api_audit_map.conf etc/pycadf/neutron_api_audit_map.conf etc/pycadf/nova_api_audit_map.conf etc/pycadf/trove_api_audit_map.conf etc/pycadf/ceilometer_api_audit_map.conf [global] setup-hooks = pbr.hooks.setup_hook [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [pbr] warnerrors = True #autodoc_tree_index_modules = True #autodoc_tree_root = ./pycadf pycadf-2.7.0/setup.py000066400000000000000000000020061321055627100145000ustar00rootroot00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) pycadf-2.7.0/test-requirements.txt000066400000000000000000000011621321055627100172310ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # Hacking already pins down pep8, pyflakes and flake8 hacking<0.11,>=0.10.0 flake8-docstrings==0.2.1.post1 # MIT coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD testrepository>=0.0.18 # Apache-2.0/BSD testtools>=2.2.0 # MIT # this is required for the docs build jobs openstackdocstheme>=1.17.0 # Apache-2.0 sphinx>=1.6.2 # BSD pycadf-2.7.0/tox.ini000066400000000000000000000027731321055627100143140ustar00rootroot00000000000000[tox] minversion = 2.0 envlist = py35,py27,pep8 [testenv] install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:docs] commands = python setup.py build_sphinx [testenv:cover] commands = python setup.py testr --coverage [testenv:venv] commands = {posargs} [testenv:debug] commands = oslo_debug_helper {posargs} [flake8] show-source = True # H405: Multi line docstrings should start with a one line summary followed by # an empty line. # D100: Missing docstring in public module # D101: Missing docstring in public class # D102: Missing docstring in public method # D103: Missing docstring in public function # D104: Missing docstring in public package # D105: Missing docstring in magic method # D200: One-line docstring should fit on one line with quotes # D202: No blank lines allowed after function docstring # D203: 1 blank required before class docstring # D204: 1 blank line required after class docstring # D205: 1 blank line required between summary line and description # D208: Docstring is over-indented # D400: First line should end with a period # D401: First line should be in imperative mood ignore = H405,D100,D101,D102,D103,D104,D105,D200,D202,D203,D204,D205,D208,D400,D401 exclude = .tox,dist,doc,*.egg,build