util/ssh remoto now returns strings, not bytes on Python 3 485/head
authorAlfredo Deza <adeza@redhat.com>
Tue, 16 Apr 2019 15:35:39 +0000 (11:35 -0400)
committerAlfredo Deza <adeza@redhat.com>
Tue, 16 Apr 2019 15:35:39 +0000 (11:35 -0400)
Signed-off-by: Alfredo Deza <adeza@redhat.com>
Resolves: rm#39322

180 files changed:
.gitignore [new file with mode: 0644]
CONTRIBUTING.rst [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
README.rst [new file with mode: 0644]
bootstrap [new file with mode: 0755]
ceph-deploy.spec [new file with mode: 0644]
ceph_deploy/__init__.py [new file with mode: 0644]
ceph_deploy/admin.py [new file with mode: 0644]
ceph_deploy/cli.py [new file with mode: 0644]
ceph_deploy/cliutil.py [new file with mode: 0644]
ceph_deploy/conf/__init__.py [new file with mode: 0644]
ceph_deploy/conf/ceph.py [new file with mode: 0644]
ceph_deploy/conf/cephdeploy.py [new file with mode: 0644]
ceph_deploy/config.py [new file with mode: 0644]
ceph_deploy/connection.py [new file with mode: 0644]
ceph_deploy/exc.py [new file with mode: 0644]
ceph_deploy/forgetkeys.py [new file with mode: 0644]
ceph_deploy/gatherkeys.py [new file with mode: 0644]
ceph_deploy/hosts/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/alt/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/alt/install.py [new file with mode: 0644]
ceph_deploy/hosts/alt/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/alt/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/arch/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/arch/install.py [new file with mode: 0644]
ceph_deploy/hosts/arch/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/arch/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/centos/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/centos/install.py [new file with mode: 0644]
ceph_deploy/hosts/centos/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/centos/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/common.py [new file with mode: 0644]
ceph_deploy/hosts/debian/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/debian/install.py [new file with mode: 0644]
ceph_deploy/hosts/debian/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/debian/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/fedora/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/fedora/install.py [new file with mode: 0644]
ceph_deploy/hosts/fedora/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/fedora/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/remotes.py [new file with mode: 0644]
ceph_deploy/hosts/rhel/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/rhel/install.py [new file with mode: 0644]
ceph_deploy/hosts/rhel/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/rhel/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/suse/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/suse/install.py [new file with mode: 0644]
ceph_deploy/hosts/suse/mon/__init__.py [new file with mode: 0644]
ceph_deploy/hosts/suse/uninstall.py [new file with mode: 0644]
ceph_deploy/hosts/util.py [new file with mode: 0644]
ceph_deploy/install.py [new file with mode: 0644]
ceph_deploy/lib/__init__.py [new file with mode: 0644]
ceph_deploy/lib/vendor/__init__.py [new file with mode: 0644]
ceph_deploy/mds.py [new file with mode: 0644]
ceph_deploy/mgr.py [new file with mode: 0644]
ceph_deploy/misc.py [new file with mode: 0644]
ceph_deploy/mon.py [new file with mode: 0644]
ceph_deploy/new.py [new file with mode: 0644]
ceph_deploy/osd.py [new file with mode: 0644]
ceph_deploy/pkg.py [new file with mode: 0644]
ceph_deploy/repo.py [new file with mode: 0644]
ceph_deploy/rgw.py [new file with mode: 0644]
ceph_deploy/tests/__init__.py [new file with mode: 0644]
ceph_deploy/tests/conftest.py [new file with mode: 0644]
ceph_deploy/tests/directory.py [new file with mode: 0644]
ceph_deploy/tests/fakes.py [new file with mode: 0644]
ceph_deploy/tests/parser/__init__.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_admin.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_config.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_disk.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_gatherkeys.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_install.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_main.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_mds.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_mon.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_new.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_osd.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_pkg.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_purge.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_purgedata.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_repo.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_rgw.py [new file with mode: 0644]
ceph_deploy/tests/parser/test_uninstall.py [new file with mode: 0644]
ceph_deploy/tests/test_cli_admin.py [new file with mode: 0644]
ceph_deploy/tests/test_cli_mon.py [new file with mode: 0644]
ceph_deploy/tests/test_cli_new.py [new file with mode: 0644]
ceph_deploy/tests/test_cli_rgw.py [new file with mode: 0644]
ceph_deploy/tests/test_conf.py [new file with mode: 0644]
ceph_deploy/tests/test_gather_keys.py [new file with mode: 0644]
ceph_deploy/tests/test_gather_keys_missing.py [new file with mode: 0644]
ceph_deploy/tests/test_gather_keys_with_mon.py [new file with mode: 0644]
ceph_deploy/tests/test_install.py [new file with mode: 0644]
ceph_deploy/tests/test_keys_equivalent.py [new file with mode: 0644]
ceph_deploy/tests/test_mon.py [new file with mode: 0644]
ceph_deploy/tests/test_remotes.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_altlinux.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_centos.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_common.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_hosts.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_remotes.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_suse.py [new file with mode: 0644]
ceph_deploy/tests/unit/hosts/test_util.py [new file with mode: 0644]
ceph_deploy/tests/unit/test_cli.py [new file with mode: 0644]
ceph_deploy/tests/unit/test_conf.py [new file with mode: 0644]
ceph_deploy/tests/unit/test_exc.py [new file with mode: 0644]
ceph_deploy/tests/unit/test_mon.py [new file with mode: 0644]
ceph_deploy/tests/unit/test_new.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_arg_validators.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_constants.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_net.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_packages.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_paths.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_pkg_managers.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_system.py [new file with mode: 0644]
ceph_deploy/tests/unit/util/test_templates.py [new file with mode: 0644]
ceph_deploy/tests/util.py [new file with mode: 0644]
ceph_deploy/util/__init__.py [new file with mode: 0644]
ceph_deploy/util/arg_validators.py [new file with mode: 0644]
ceph_deploy/util/constants.py [new file with mode: 0644]
ceph_deploy/util/decorators.py [new file with mode: 0644]
ceph_deploy/util/files.py [new file with mode: 0644]
ceph_deploy/util/help_formatters.py [new file with mode: 0644]
ceph_deploy/util/log.py [new file with mode: 0644]
ceph_deploy/util/net.py [new file with mode: 0644]
ceph_deploy/util/packages.py [new file with mode: 0644]
ceph_deploy/util/paths/__init__.py [new file with mode: 0644]
ceph_deploy/util/paths/gpg.py [new file with mode: 0644]
ceph_deploy/util/paths/mon.py [new file with mode: 0644]
ceph_deploy/util/paths/osd.py [new file with mode: 0644]
ceph_deploy/util/pkg_managers.py [new file with mode: 0644]
ceph_deploy/util/ssh.py [new file with mode: 0644]
ceph_deploy/util/system.py [new file with mode: 0644]
ceph_deploy/util/templates.py [new file with mode: 0644]
ceph_deploy/util/versions.py [new file with mode: 0644]
ceph_deploy/validate.py [new file with mode: 0644]
debian/ceph-deploy.install [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
docs/Makefile [new file with mode: 0644]
docs/source/_static/.empty [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Book.eot [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Book.svg [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Book.ttf [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Book.woff [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Medium.eot [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Medium.svg [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Medium.ttf [new file with mode: 0644]
docs/source/_themes/ceph/static/font/ApexSans-Medium.woff [new file with mode: 0644]
docs/source/_themes/ceph/static/nature.css_t [new file with mode: 0644]
docs/source/_themes/ceph/theme.conf [new file with mode: 0644]
docs/source/admin.rst [new file with mode: 0644]
docs/source/changelog.rst [new file with mode: 0644]
docs/source/conf.py [new file with mode: 0644]
docs/source/conf.rst [new file with mode: 0644]
docs/source/contents.rst [new file with mode: 0644]
docs/source/gatherkeys.rst [new file with mode: 0644]
docs/source/index.rst [new file with mode: 0644]
docs/source/install.rst [new file with mode: 0644]
docs/source/mds.rst [new file with mode: 0644]
docs/source/mon.rst [new file with mode: 0644]
docs/source/new.rst [new file with mode: 0644]
docs/source/pkg.rst [new file with mode: 0644]
docs/source/repo.rst [new file with mode: 0644]
docs/source/rgw.rst [new file with mode: 0644]
requirements-dev.txt [new file with mode: 0644]
requirements.txt [new file with mode: 0644]
scripts/build-debian.sh [new file with mode: 0755]
scripts/build-rpm.sh [new file with mode: 0755]
scripts/ceph-deploy [new file with mode: 0755]
scripts/jenkins-build [new file with mode: 0755]
scripts/jenkins-pull-requests-build [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
tox.ini [new file with mode: 0644]
vendor.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..efd1a25
--- /dev/null
@@ -0,0 +1,22 @@
+*~
+.#*
+## the next line needs to start with a backslash to avoid looking like
+## a comment
+\#*#
+.*.swp
+
+*.pyc
+*.pyo
+*.egg-info
+/build
+/dist
+build
+
+/virtualenv
+/.tox
+
+/ceph-deploy
+/*.conf
+
+*/lib/vendor/remoto
+remoto
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644 (file)
index 0000000..60cead5
--- /dev/null
@@ -0,0 +1,48 @@
+Contributing to ceph-deploy
+===========================
+Before any contributions, a reference ticket *must* exist. The community issue
+tracker is hosted at tracker.ceph.com
+
+To open a new issue, requests can go to:
+
+http://tracker.ceph.com/projects/ceph-deploy/issues/new
+
+
+commits
+-------
+Once a ticket exists, commits should be prefaced by the ticket ID. This makes
+it easier for maintainers to keep track of why a given line changed, mapping
+directly to work done on a ticket.
+
+For tickets coming from tracker.ceph.com, we expect the following format::
+
+    [RM-0000] this is a commit message for tracker.ceph.com
+
+``RM`` stands for Redmine which is the software running tracker.ceph.com.
+Similarly, if a ticket was created in bugzilla.redhat.com, we expect the
+following format::
+
+    [BZ-0000] this is a commit message for bugzilla.redhat.com
+
+
+To automate this process, you can create a branch with the tracker identifier
+and id (replace "0000" with the ticket number)::
+
+    git checkout -b RM-0000
+
+And then use the follow prepare-commit-msg:
+https://gist.github.com/alfredodeza/6d62d99a95c9a7975fbe
+
+Copy that file to ``$GITREPOSITORY/.git/hooks/prepare-commit-msg``
+and mark it executable.
+
+Your commit messages should then be automatically prefixed with the branch name
+based off of the issue tracker.
+
+tests and documentation
+-----------------------
+Wherever it is feasible, tests must exist and documentation must be added or
+improved depending on the change.
+
+The build process not only runs tests but ensures that docs can be built from
+the proposed changes as well.
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..26624cf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Inktank Storage, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..370e3d9
--- /dev/null
@@ -0,0 +1,5 @@
+include *.rst
+include LICENSE
+include scripts/ceph-deploy
+include vendor.py
+include tox.ini
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..85aec15
--- /dev/null
@@ -0,0 +1,373 @@
+========================================================
+ ceph-deploy -- Deploy Ceph with minimal infrastructure
+========================================================
+
+``ceph-deploy`` is a way to deploy Ceph relying on just SSH access to
+the servers, ``sudo``, and some Python. It runs fully on your
+workstation, requiring no servers, databases, or anything like that.
+
+If you set up and tear down Ceph clusters a lot, and want minimal
+extra bureaucracy, this is for you.
+
+This ``README`` provides a brief overview of ceph-deploy, for thorough
+documentation please go to http://ceph.com/ceph-deploy/docs
+
+.. _what this tool is not:
+
+What this tool is not
+---------------------
+It is not a generic deployment system, it is only for Ceph, and is designed
+for users who want to quickly get Ceph running with sensible initial settings
+without the overhead of installing Chef, Puppet or Juju.
+
+It does not handle client configuration beyond pushing the Ceph config file
+and users who want fine-control over security settings, partitions or directory
+locations should use a tool such as Chef or Puppet.
+
+
+Installation
+============
+Depending on what type of usage you are going to have with ``ceph-deploy`` you
+might want to look into the different ways to install it. For automation, you
+might want to ``bootstrap`` directly. Regular users of ``ceph-deploy`` would
+probably install from the OS packages or from the Python Package Index.
+
+Python Package Index
+--------------------
+If you are familiar with Python install tools (like ``pip`` and
+``easy_install``) you can easily install ``ceph-deploy`` like::
+
+    pip install ceph-deploy
+
+or::
+
+    easy_install ceph-deploy
+
+
+It should grab all the dependencies for you and install into the current user's
+environment.
+
+We highly recommend using ``virtualenv`` and installing dependencies in
+a contained way.
+
+
+DEB
+---
+All new releases of ``ceph-deploy`` are pushed to all ``ceph`` DEB release
+repos.
+
+The DEB release repos are found at::
+
+     http://ceph.com/debian-{release}
+     http://ceph.com/debian-testing
+
+This means, for example, that installing ``ceph-deploy`` from
+http://ceph.com/debian-giant will install the same version as from
+http://ceph.com/debian-firefly or http://ceph.com/debian-testing.
+
+RPM
+---
+All new releases of ``ceph-deploy`` are pushed to all ``ceph`` RPM release
+repos.
+
+The RPM release repos are found at::
+
+     http://ceph.com/rpm-{release}
+     http://ceph.com/rpm-testing
+
+Make sure you add the proper one for your distribution (i.e. el7 vs rhel7).
+
+This means, for example, that installing ``ceph-deploy`` from
+http://ceph.com/rpm-giant will install the same version as from
+http://ceph.com/rpm-firefly or http://ceph.com/rpm-testing.
+
+bootstrapping
+-------------
+To get the source tree ready for use, run this once::
+
+  ./bootstrap
+
+You can symlink the ``ceph-deploy`` script in this somewhere
+convenient (like ``~/bin``), or add the current directory to ``PATH``,
+or just always type the full path to ``ceph-deploy``.
+
+
+SSH and Remote Connections
+==========================
+``ceph-deploy`` will attempt to connect via SSH to hosts when the hostnames do
+not match the current host's hostname. For example, if you are connecting to
+host ``node1`` it will attempt an SSH connection as long as the current host's
+hostname is *not* ``node1``.
+
+ceph-deploy at a minimum requires that the machine from which the script is
+being run can ssh as root without password into each Ceph node.
+
+To enable this generate a new ssh keypair for the root user with no passphrase
+and place the public key (``id_rsa.pub`` or ``id_dsa.pub``) in::
+
+    /root/.ssh/authorized_keys
+
+and ensure that the following lines are in the sshd config::
+
+    PermitRootLogin without-password
+    PubkeyAuthentication yes
+
+The machine running ceph-deploy does not need to have the Ceph packages
+installed unless it needs to admin the cluster directly using the ``ceph``
+command line tool.
+
+
+usernames
+---------
+When not specified the connection will be done with the same username as the
+one executing ``ceph-deploy``. This is useful if the same username is shared in
+all the nodes but can be cumbersome if that is not the case.
+
+A way to avoid this is to define the correct usernames to connect with in the
+SSH config, but you can also use the ``--username`` flag as well::
+
+    ceph-deploy --username ceph install node1
+
+``ceph-deploy`` then in turn would use ``ceph@node1`` to connect to that host.
+
+This would be the same expectation for any action that warrants a connection to
+a remote host.
+
+
+Managing an existing cluster
+============================
+
+You can use ceph-deploy to provision nodes for an existing cluster.
+To grab a copy of the cluster configuration file (normally
+``ceph.conf``)::
+
+ ceph-deploy config pull HOST
+
+You will usually also want to gather the encryption keys used for that
+cluster::
+
+    ceph-deploy gatherkeys MONHOST
+
+At this point you can skip the steps below that create a new cluster
+(you already have one) and optionally skip installation and/or monitor
+creation, depending on what you are trying to accomplish.
+
+
+Creating a new cluster
+======================
+
+Creating a new configuration
+----------------------------
+
+To create a new configuration file and secret key, decide what hosts
+will run ``ceph-mon``, and run::
+
+  ceph-deploy new MON [MON..]
+
+listing the hostnames of the monitors.  Each ``MON`` can be
+
+ * a simple hostname.  It must be DNS resolvable without the fully
+   qualified domain name.
+ * a fully qualified domain name.  The hostname is assumed to be the
+   leading component up to the first ``.``.
+ * a ``HOST:FQDN`` pair, of both the hostname and a fully qualified
+   domain name or IP address.  For example, ``foo``,
+   ``foo.example.com``, ``foo:something.example.com``, and
+   ``foo:1.2.3.4`` are all valid.  Note, however, that the hostname
+   should match that configured on the host ``foo``.
+
+The above will create a ``ceph.conf`` and ``ceph.mon.keyring`` in your
+current directory.
+
+
+Edit initial cluster configuration
+----------------------------------
+
+You want to review the generated ``ceph.conf`` file and make sure that
+the ``mon_host`` setting contains the IP addresses you would like the
+monitors to bind to.  These are the IPs that clients will initially
+contact to authenticate to the cluster, and they need to be reachable
+both by external client-facing hosts and internal cluster daemons.
+
+Installing packages
+===================
+
+To install the Ceph software on the servers, run::
+
+  ceph-deploy install HOST [HOST..]
+
+This installs the current default *stable* release. You can choose a
+different release track with command line options, for example to use
+a release candidate::
+
+  ceph-deploy install --testing HOST
+
+Or to test a development branch::
+
+  ceph-deploy install --dev=wip-mds-now-works-no-kidding HOST [HOST..]
+
+
+Proxy or Firewall Installs
+--------------------------
+If attempting to install behind a firewall or through a proxy you can
+use the ``--no-adjust-repos`` that will tell ceph-deploy to skip any changes
+to the distro's repository in order to install the packages and it will go
+straight to package installation.
+
+That will allow an environment without internet access to point to *its own
+repositories*. This means that those repositories will need to be properly
+setup (and mirrored with all the necessary dependencies) before attempting an
+install.
+
+Another alternative is to set the ``wget`` env variables to point to the right
+hosts, for example, put following lines into ``/root/.wgetrc`` on each node
+(since ceph-deploy runs wget as root)::
+
+    http_proxy=http://host:port
+    ftp_proxy=http://host:port
+    https_proxy=http://host:port
+
+
+
+Deploying monitors
+==================
+
+To actually deploy ``ceph-mon`` to the hosts you chose, run::
+
+  ceph-deploy mon create HOST [HOST..]
+
+Without explicit hosts listed, hosts in ``mon_initial_members`` in the
+config file are deployed. That is, the hosts you passed to
+``ceph-deploy new`` are the default value here.
+
+Gather keys
+===========
+
+To gather authenticate keys (for administering the cluster and
+bootstrapping new nodes) to the local directory, run::
+
+  ceph-deploy gatherkeys HOST [HOST...]
+
+where ``HOST`` is one of the monitor hosts.
+
+Once these keys are in the local directory, you can provision new OSDs etc.
+
+
+Deploying OSDs
+==============
+
+To prepare a node for running OSDs, run::
+
+  ceph-deploy osd create HOST:DISK[:JOURNAL] [HOST:DISK[:JOURNAL] ...]
+
+After that, the hosts will be running OSDs for the given data disks.
+If you specify a raw disk (e.g., ``/dev/sdb``), partitions will be
+created and GPT labels will be used to mark and automatically activate
+OSD volumes.  If an existing partition is specified, the partition
+table will not be modified.  If you want to destroy the existing
+partition table on DISK first, you can include the ``--zap-disk``
+option.
+
+If there is already a prepared disk or directory that is ready to become an
+OSD, you can also do::
+
+    ceph-deploy osd activate HOST:DIR[:JOURNAL] [...]
+
+This is useful when you are managing the mounting of volumes yourself.
+
+
+Admin hosts
+===========
+
+To prepare a host with a ``ceph.conf`` and ``ceph.client.admin.keyring``
+keyring so that it can administer the cluster, run::
+
+  ceph-deploy admin HOST [HOST ...]
+
+Forget keys
+===========
+
+The ``new`` and ``gatherkeys`` put some Ceph authentication keys in keyrings in
+the local directory.  If you are worried about them being there for security
+reasons, run::
+
+  ceph-deploy forgetkeys
+
+and they will be removed.  If you need them again later to deploy additional
+nodes, simply re-run::
+
+  ceph-deploy gatherkeys HOST [HOST...]
+
+and they will be retrieved from an existing monitor node.
+
+Multiple clusters
+=================
+
+All of the above commands take a ``--cluster=NAME`` option, allowing
+you to manage multiple clusters conveniently from one workstation.
+For example::
+
+  ceph-deploy --cluster=us-west new
+  vi us-west.conf
+  ceph-deploy --cluster=us-west mon
+
+FAQ
+===
+
+Before anything
+---------------
+Make sure you have the latest version of ``ceph-deploy``. It is actively
+developed and releases are coming weekly (on average). The most recent versions
+of ``ceph-deploy`` will have a ``--version`` flag you can use, otherwise check
+with your package manager and update if there is anything new.
+
+Why is feature X not implemented?
+---------------------------------
+Usually, features are added when/if it is sensible for someone that wants to
+get started with ceph and said feature would make sense in that context.  If
+you believe this is the case and you've read "`what this tool is not`_" and
+still think feature ``X`` should exist in ceph-deploy, open a feature request
+in the ceph tracker: http://tracker.ceph.com/projects/ceph-deploy/issues
+
+A command gave me an error, what is going on?
+---------------------------------------------
+Most of the commands for ``ceph-deploy`` are meant to be run remotely in a host
+that you have configured when creating the initial config. If a given command
+is not working as expected try to run the command that failed in the remote
+host and assert the behavior there.
+
+If the behavior in the remote host is the same, then it is probably not
+something wrong with ``ceph-deploy`` per-se. Make sure you capture the output
+of both the ``ceph-deploy`` output and the output of the command in the remote
+host.
+
+Issues with monitors
+--------------------
+If your monitors are not starting, make sure that the ``{hostname}`` you used
+when you ran ``ceph-deploy mon create {hostname}`` match the actual ``hostname -s``
+in the remote host.
+
+Newer versions of ``ceph-deploy`` should warn you if the results are different
+but that might prevent the monitors from reaching quorum.
+
+Developing ceph-deploy
+======================
+Now that you have cracked your teeth on Ceph, you might find that you want to
+contribute to ceph-deploy.
+
+Resources
+---------
+Bug tracking: http://tracker.ceph.com/projects/ceph-deploy/issues
+
+Mailing list and IRC info is the same as ceph http://ceph.com/resources/mailing-list-irc/
+
+Submitting Patches
+------------------
+Please add test cases to cover any code you add. You can test your changes
+by running ``tox`` (You will also need ``mock`` and ``pytest`` ) from inside
+the git clone
+
+When creating a commit message please use ``git commit -s`` or otherwise add
+``Signed-off-by: Your Name <email@address.dom>`` to your commit message.
+
+Patches can then be submitted by a pull request on GitHub.
diff --git a/bootstrap b/bootstrap
new file mode 100755 (executable)
index 0000000..c41c274
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,101 @@
+#!/bin/sh
+set -e
+
+# Use `./bootstrap 3` for Python 3
+python_executable="python$1"
+
+if ! [ -d virtualenv ]; then
+    if command -v lsb_release >/dev/null 2>&1; then
+        if [ "$1" = "2" ]; then
+            python_package="python"
+        else
+            python_package="python$1"
+        fi
+
+        case "$(lsb_release --id --short)" in
+        Ubuntu|Debian)
+            for package in "$python_package" python-virtualenv; do
+            if [ "$(dpkg --status -- "$package" 2>/dev/null|sed -n 's/^Status: //p')" != "install ok installed" ]; then
+                # add a space after old values
+                missing="${missing:+$missing }$package"
+            fi
+            done
+            if [ -n "$missing" ]; then
+                echo "$0: missing required packages, please install them:" 1>&2
+                echo "  sudo apt-get install $missing"
+                exit 1
+            fi
+            ;;
+
+        Arch)
+            for package in "$python_package" python-virtualenv; do
+            if ! pacman -Qs -- "$package" >/dev/null 2>&1; then
+                # add a space after old values
+                missing="${missing:+$missing }$package"
+            fi
+            done
+            if [ -n "$missing" ]; then
+                echo "$0: missing required packages, please install them:" 1>&2
+                echo "  pacman -Sy $missing"
+                exit 1
+            fi
+            ;;
+        esac
+
+        case "$(lsb_release --id --short | awk '{print $1}')" in
+        openSUSE|SUSE)
+            for package in "$python_package" python-virtualenv; do
+                if [ "$(rpm -qa "$package" 2>/dev/null)" == "" ]; then
+                    missing="${missing:+$missing }$package"
+                fi
+            done
+            if [ -n "$missing" ]; then
+                echo "$0: missing required packages, please install them:" 1>&2
+                echo "  sudo zypper install $missing"
+                exit 1
+            fi
+            ;;
+        esac
+
+    fi
+
+    if [ -f /etc/redhat-release ]; then
+        case "$(cat /etc/redhat-release | awk '{print $1}')" in
+        CentOS)
+            for package in python-virtualenv; do
+                if [ "$(rpm -qa "$package" 2>/dev/null)" == "" ]; then
+                    missing="${missing:+$missing }$package"
+                fi
+            done
+            if [ -n "$missing" ]; then
+                echo "$0: missing required packages, please install them:" 1>&2
+                echo "  sudo yum install $missing"
+                exit 1
+            fi
+
+            if [ "${1:-2}" -ge 3 ]; then
+                if ! command -v "$python_executable" >/dev/null 2>&1; then
+                    echo "$0: missing Python ($python_executable), please install it"
+                    exit 1
+                fi
+
+                # Make a temporary virtualenv to get a fresh version of virtualenv
+                # and use it to make a Python 3 virtualenv,
+                # because CentOS 7 has buggy old virtualenv (v1.10.1)
+                # https://github.com/pypa/virtualenv/issues/463
+
+                virtualenv virtualenv_tmp
+                virtualenv_tmp/bin/pip install --upgrade virtualenv
+                virtualenv_tmp/bin/virtualenv -p "$python_executable" virtualenv
+                rm -rf virtualenv_tmp
+            else
+                virtualenv virtualenv
+            fi
+            ;;
+        esac
+    fi
+fi
+
+test -d virtualenv || virtualenv -p "$python_executable" virtualenv
+./virtualenv/bin/python setup.py develop
+test -e ceph-deploy || ln -s virtualenv/bin/ceph-deploy .
diff --git a/ceph-deploy.spec b/ceph-deploy.spec
new file mode 100644 (file)
index 0000000..8218266
--- /dev/null
@@ -0,0 +1,71 @@
+#
+# spec file for package ceph-deploy
+#
+
+%if ! (0%{?fedora} > 12 || 0%{?rhel} > 5)
+%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
+%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
+%endif
+
+#################################################################################
+# common
+#################################################################################
+Name:           ceph-deploy
+Version:       2.0.1
+Release:        0
+Summary:        Admin and deploy tool for Ceph
+License:        MIT
+Group:          System/Filesystems
+URL:            http://ceph.com/
+Source0:        %{name}-%{version}.tar.bz2
+BuildRoot:      %{_tmppath}/%{name}-%{version}-build
+BuildRequires:  python-devel
+BuildRequires:  python-setuptools
+BuildRequires:  python-virtualenv
+BuildRequires:  python-mock
+BuildRequires:  python-tox
+%if 0%{?suse_version}
+BuildRequires:  python-pytest
+%else
+BuildRequires:  pytest
+%endif
+BuildRequires:  git
+Requires:       python-argparse
+Requires:       python-remoto
+%if 0%{?suse_version} && 0%{?suse_version} <= 1110
+%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+%else
+BuildArch:      noarch
+%endif
+
+#################################################################################
+# specific
+#################################################################################
+%if 0%{defined suse_version}
+%py_requires
+%endif
+
+%description
+An easy to use admin tool for deploy ceph storage clusters.
+
+%prep
+#%%setup -q -n %%{name}
+%setup -q
+
+%build
+#python setup.py build
+
+%install
+python setup.py install --prefix=%{_prefix} --root=%{buildroot}
+install -m 0755 -D scripts/ceph-deploy $RPM_BUILD_ROOT/usr/bin
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf "$RPM_BUILD_ROOT"
+
+%files
+%defattr(-,root,root)
+%doc LICENSE README.rst
+%{_bindir}/ceph-deploy
+%{python_sitelib}/*
+
+%changelog
diff --git a/ceph_deploy/__init__.py b/ceph_deploy/__init__.py
new file mode 100644 (file)
index 0000000..5f4a28a
--- /dev/null
@@ -0,0 +1,3 @@
+
+__version__ = '2.0.2'
+
diff --git a/ceph_deploy/admin.py b/ceph_deploy/admin.py
new file mode 100644 (file)
index 0000000..7212a1b
--- /dev/null
@@ -0,0 +1,61 @@
+import logging
+from ceph_deploy import exc
+from ceph_deploy import conf
+from ceph_deploy.cliutil import priority
+from ceph_deploy import hosts
+
+LOG = logging.getLogger(__name__)
+
+
+def admin(args):
+    conf_data = conf.ceph.load_raw(args)
+
+    try:
+        with open('%s.client.admin.keyring' % args.cluster, 'rb') as f:
+            keyring = f.read()
+    except:
+        raise RuntimeError('%s.client.admin.keyring not found' %
+                           args.cluster)
+
+    errors = 0
+    for hostname in args.client:
+        LOG.debug('Pushing admin keys and conf to %s', hostname)
+        try:
+            distro = hosts.get(hostname, username=args.username)
+
+            distro.conn.remote_module.write_conf(
+                args.cluster,
+                conf_data,
+                args.overwrite_conf,
+            )
+
+            distro.conn.remote_module.write_file(
+                '/etc/ceph/%s.client.admin.keyring' % args.cluster,
+                keyring,
+                0o600,
+            )
+
+            distro.conn.exit()
+
+        except RuntimeError as e:
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        raise exc.GenericError('Failed to configure %d admin hosts' % errors)
+
+
+@priority(70)
+def make(parser):
+    """
+    Push configuration and client.admin key to a remote host.
+    """
+    parser.add_argument(
+        'client',
+        metavar='HOST',
+        nargs='+',
+        help='host to configure for Ceph administration',
+        )
+    parser.set_defaults(
+        func=admin,
+        )
diff --git a/ceph_deploy/cli.py b/ceph_deploy/cli.py
new file mode 100644 (file)
index 0000000..ccd6df8
--- /dev/null
@@ -0,0 +1,184 @@
+import pkg_resources
+import argparse
+import logging
+import textwrap
+import os
+import sys
+
+import ceph_deploy
+from ceph_deploy import exc
+from ceph_deploy.util import log
+from ceph_deploy.util.decorators import catches
+
+LOG = logging.getLogger(__name__)
+
+
+__header__ = textwrap.dedent("""
+    -^-
+   /   \\
+   |O o|  ceph-deploy v%s
+   ).-.(
+  '/|||\`
+  | '|` |
+    '|`
+
+Full documentation can be found at: http://ceph.com/ceph-deploy/docs
+""" % ceph_deploy.__version__)
+
+
+def log_flags(args, logger=None):
+    logger = logger or LOG
+    logger.info('ceph-deploy options:')
+
+    for k, v in args.__dict__.items():
+        if k.startswith('_'):
+            continue
+        logger.info(' %-30s: %s' % (k, v))
+
+
+def get_parser():
+    epilog_text = "See 'ceph-deploy <command> --help' for help on a specific command"
+    parser = argparse.ArgumentParser(
+        prog='ceph-deploy',
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        description='Easy Ceph deployment\n\n%s' % __header__,
+        epilog=epilog_text
+        )
+    verbosity = parser.add_mutually_exclusive_group(required=False)
+    verbosity.add_argument(
+        '-v', '--verbose',
+        action='store_true', dest='verbose', default=False,
+        help='be more verbose',
+        )
+    verbosity.add_argument(
+        '-q', '--quiet',
+        action='store_true', dest='quiet',
+        help='be less verbose',
+        )
+    parser.add_argument(
+        '--version',
+        action='version',
+        version='%s' % ceph_deploy.__version__,
+        help='the current installed version of ceph-deploy',
+        )
+    parser.add_argument(
+        '--username',
+        help='the username to connect to the remote host',
+        )
+    parser.add_argument(
+        '--overwrite-conf',
+        action='store_true',
+        help='overwrite an existing conf file on remote host (if present)',
+        )
+    parser.add_argument(
+        '--ceph-conf',
+        dest='ceph_conf',
+        help='use (or reuse) a given ceph.conf file',
+    )
+    sub = parser.add_subparsers(
+        title='commands',
+        metavar='COMMAND',
+        help='description',
+        )
+    sub.required = True
+    entry_points = [
+        (ep.name, ep.load())
+        for ep in pkg_resources.iter_entry_points('ceph_deploy.cli')
+        ]
+    entry_points.sort(
+        key=lambda name_fn: getattr(name_fn[1], 'priority', 100),
+        )
+    for (name, fn) in entry_points:
+        p = sub.add_parser(
+            name,
+            description=fn.__doc__,
+            help=fn.__doc__,
+            )
+        if not os.environ.get('CEPH_DEPLOY_TEST'):
+            p.set_defaults(cd_conf=ceph_deploy.conf.cephdeploy.load())
+
+        # flag if the default release is being used
+        p.set_defaults(default_release=False)
+        fn(p)
+        p.required = True
+    parser.set_defaults(
+        cluster='ceph',
+        )
+
+    return parser
+
+
+@catches((KeyboardInterrupt, RuntimeError, exc.DeployError,), handle_all=True)
+def _main(args=None, namespace=None):
+    # Set console logging first with some defaults, to prevent having exceptions
+    # before hitting logging configuration. The defaults can/will get overridden
+    # later.
+
+    # Console Logger
+    sh = logging.StreamHandler()
+    sh.setFormatter(log.color_format())
+    sh.setLevel(logging.WARNING)
+
+    # because we're in a module already, __name__ is not the ancestor of
+    # the rest of the package; use the root as the logger for everyone
+    root_logger = logging.getLogger()
+
+    # allow all levels at root_logger, handlers control individual levels
+    root_logger.setLevel(logging.DEBUG)
+    root_logger.addHandler(sh)
+
+    parser = get_parser()
+    if len(sys.argv) < 2:
+        parser.print_help()
+        sys.exit()
+    else:
+        args = parser.parse_args(args=args, namespace=namespace)
+
+    console_loglevel = logging.DEBUG  # start at DEBUG for now
+    if args.quiet:
+        console_loglevel = logging.WARNING
+    if args.verbose:
+        console_loglevel = logging.DEBUG
+
+    # Console Logger
+    sh.setLevel(console_loglevel)
+
+    # File Logger
+    fh = logging.FileHandler('ceph-deploy-{cluster}.log'.format(cluster=args.cluster))
+    fh.setLevel(logging.DEBUG)
+    fh.setFormatter(logging.Formatter(log.FILE_FORMAT))
+
+    root_logger.addHandler(fh)
+
+    # Reads from the config file and sets values for the global
+    # flags and the given sub-command
+    # the one flag that will never work regardless of the config settings is
+    # logging because we cannot set it before hand since the logging config is
+    # not ready yet. This is the earliest we can do.
+    args = ceph_deploy.conf.cephdeploy.set_overrides(args)
+
+    LOG.info("Invoked (%s): %s" % (
+        ceph_deploy.__version__,
+        ' '.join(sys.argv))
+    )
+    log_flags(args)
+
+    return args.func(args)
+
+
+def main(args=None, namespace=None):
+    try:
+        _main(args=args, namespace=namespace)
+    finally:
+        # This block is crucial to avoid having issues with
+        # Python spitting non-sense thread exceptions. We have already
+        # handled what we could, so close stderr and stdout.
+        if not os.environ.get('CEPH_DEPLOY_TEST'):
+            try:
+                sys.stdout.close()
+            except:
+                pass
+            try:
+                sys.stderr.close()
+            except:
+                pass
diff --git a/ceph_deploy/cliutil.py b/ceph_deploy/cliutil.py
new file mode 100644 (file)
index 0000000..d273f31
--- /dev/null
@@ -0,0 +1,8 @@
+def priority(num):
+    """
+    Decorator to add a `priority` attribute to the function.
+    """
+    def add_priority(fn):
+        fn.priority = num
+        return fn
+    return add_priority
diff --git a/ceph_deploy/conf/__init__.py b/ceph_deploy/conf/__init__.py
new file mode 100644 (file)
index 0000000..8599a6a
--- /dev/null
@@ -0,0 +1,2 @@
+from . import ceph  # noqa
+from . import cephdeploy  # noqa
diff --git a/ceph_deploy/conf/ceph.py b/ceph_deploy/conf/ceph.py
new file mode 100644 (file)
index 0000000..e14ad6d
--- /dev/null
@@ -0,0 +1,108 @@
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+import contextlib
+import sys
+
+from ceph_deploy import exc
+
+
+class _TrimIndentFile(object):
+    def __init__(self, fp):
+        self.fp = fp
+
+    def readline(self):
+        line = self.fp.readline()
+        return line.lstrip(' \t')
+
+    def __iter__(self):
+        return iter(self.readline, '')
+
+class CephConf(configparser.RawConfigParser):
+    def __init__(self, *args, **kwargs):
+        if sys.version_info >= (3, 2):
+            kwargs.setdefault('strict', False)
+        # super() cannot be used with an old-style class
+        configparser.RawConfigParser.__init__(self, *args, **kwargs)
+
+    def optionxform(self, s):
+        s = s.replace('_', ' ')
+        s = '_'.join(s.split())
+        return s
+
+    def safe_get(self, section, key):
+        """
+        Attempt to get a configuration value from a certain section
+        in a ``cfg`` object but returning None if not found. Avoids the need
+        to be doing try/except {ConfigParser Exceptions} every time.
+        """
+        try:
+            #Use full parent function so we can replace it in the class
+            # if desired
+            return configparser.RawConfigParser.get(self, section, key)
+        except (configparser.NoSectionError,
+                configparser.NoOptionError):
+            return None
+
+
+def parse(fp):
+    cfg = CephConf()
+    ifp = _TrimIndentFile(fp)
+    cfg.readfp(ifp)
+    return cfg
+
+
+def load(args):
+    """
+    :param args: Will be used to infer the proper configuration name, or
+    if args.ceph_conf is passed in, that will take precedence
+    """
+    path = args.ceph_conf or '{cluster}.conf'.format(cluster=args.cluster)
+
+    try:
+        f = open(path)
+    except IOError as e:
+        raise exc.ConfigError(
+            "%s; has `ceph-deploy new` been run in this directory?" % e
+        )
+    else:
+        with contextlib.closing(f):
+            return parse(f)
+
+
+def load_raw(args):
+    """
+    Read the actual file *as is* without parsing/modifiying it
+    so that it can be written maintaining its same properties.
+
+    :param args: Will be used to infer the proper configuration name
+    :paran path: alternatively, use a path for any configuration file loading
+    """
+    path = args.ceph_conf or '{cluster}.conf'.format(cluster=args.cluster)
+    try:
+        with open(path) as ceph_conf:
+            return ceph_conf.read()
+    except (IOError, OSError) as e:
+        raise exc.ConfigError(
+            "%s; has `ceph-deploy new` been run in this directory?" % e
+        )
+
+
+def write_conf(cluster, conf, overwrite):
+    """ write cluster configuration to /etc/ceph/{cluster}.conf """
+    import os
+
+    path = '/etc/ceph/{cluster}.conf'.format(cluster=cluster)
+    tmp = '{path}.{pid}.tmp'.format(path=path, pid=os.getpid())
+
+    if os.path.exists(path):
+        with open(path) as f:
+            old = f.read()
+            if old != conf and not overwrite:
+                raise RuntimeError('config file %s exists with different content; use --overwrite-conf to overwrite' % path)
+    with open(tmp, 'w') as f:
+        f.write(conf)
+        f.flush()
+        os.fsync(f)
+    os.rename(tmp, path)
diff --git a/ceph_deploy/conf/cephdeploy.py b/ceph_deploy/conf/cephdeploy.py
new file mode 100644 (file)
index 0000000..15854fd
--- /dev/null
@@ -0,0 +1,218 @@
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+import logging
+import os
+from os import path
+import re
+
+from ceph_deploy.util.paths import gpg
+
+logger = logging.getLogger('ceph_deploy.conf')
+
+cd_conf_template = """
+#
+# ceph-deploy configuration file
+#
+
+[ceph-deploy-global]
+# Overrides for some of ceph-deploy's global flags, like verbosity or cluster
+# name
+
+[ceph-deploy-install]
+# Overrides for some of ceph-deploy's install flags, like version of ceph to
+# install
+
+
+#
+# Repositories section
+#
+
+# yum repos:
+# [myrepo]
+# baseurl = http://gitbuilder.ceph.com/ceph-rpm-centos7-x86_64-basic/ref/hammer
+# gpgurl = https://download.ceph.com/keys/autobuild.asc
+# default = True
+# extra-repos = cephrepo  # will install the cephrepo file too
+#
+# [cephrepo]
+# name=ceph repo noarch packages
+# baseurl=http://download.ceph.com/rpm-hammer/el6/noarch
+# enabled=1
+# gpgcheck=1
+# type=rpm-md
+# gpgkey=https://download.ceph.com/keys/release.asc
+
+# apt repos:
+# [myrepo]
+# baseurl = http://gitbuilder.ceph.com/ceph-deb-trusty-x86_64-basic/ref/hammer
+# gpgurl = https://download.ceph.com/keys/autobuild.asc
+# default = True
+# extra-repos = cephrepo  # will install the cephrepo file too
+#
+# [cephrepo]
+# baseurl=http://download.ceph.com/debian-hammer
+# gpgkey=https://download.ceph.com/keys/release.asc
+""".format(gpgurl=gpg.url('release'))
+
+
+def location():
+    """
+    Find and return the location of the ceph-deploy configuration file. If this
+    file does not exist, create one in a default location.
+    """
+    return _locate_or_create()
+
+
+def load():
+    parser = Conf()
+    parser.read(location())
+    return parser
+
+
+def _locate_or_create():
+    home_config = path.expanduser('~/.cephdeploy.conf')
+    # With order of importance
+    locations = [
+        path.join(os.getcwd(), 'cephdeploy.conf'),
+        home_config,
+    ]
+
+    for location in locations:
+        if path.exists(location):
+            logger.debug('found configuration file at: %s' % location)
+            return location
+    logger.info('could not find configuration file, will create one in $HOME')
+    create_stub(home_config)
+    return home_config
+
+
+def create_stub(_path=None):
+    _path = _path or path.expanduser('~/.cephdeploy.conf')
+    logger.debug('creating new configuration file: %s' % _path)
+    with open(_path, 'w') as cd_conf:
+        cd_conf.write(cd_conf_template)
+
+
+def set_overrides(args, _conf=None):
+    """
+    Read the configuration file and look for ceph-deploy sections
+    to set flags/defaults from the values found. This will alter the
+    ``args`` object that is created by argparse.
+    """
+    # Get the subcommand name to avoid overwritting values from other
+    # subcommands that are not going to be used
+    subcommand = args.func.__name__
+    command_section = 'ceph-deploy-%s' % subcommand
+    conf = _conf or load()
+
+    for section_name in conf.sections():
+        if section_name in ['ceph-deploy-global', command_section]:
+            override_subcommand(
+                section_name,
+                conf.items(section_name),
+                args
+            )
+    return args
+
+
+def override_subcommand(section_name, section_items, args):
+    """
+    Given a specific section in the configuration file that maps to
+    a subcommand (except for the global section) read all the keys that are
+    actual argument flags and slap the values for that one subcommand.
+
+    Return the altered ``args`` object at the end.
+    """
+    # XXX We are not coercing here any int-like values, so if ArgParse
+    # does that in the CLI we are totally non-compliant with that expectation
+    # but we will try and infer a few boolean values
+
+    # acceptable boolean states for flags
+    _boolean_states = {'yes': True, 'true': True, 'on': True,
+                       'no': False, 'false': False, 'off': False}
+
+    for k, v, in section_items:
+        # get the lower case value of `v`, fallback to the booleanized
+        # (original) value of `v`
+        try:
+            normalized_value = v.lower()
+        except AttributeError:
+            # probably not a string object that has .lower
+            normalized_value = v
+        value = _boolean_states.get(normalized_value, v)
+        setattr(args, k, value)
+    return args
+
+
+class Conf(configparser.SafeConfigParser):
+    """
+    Subclasses from SafeConfigParser to give a few helpers for the ceph-deploy
+    configuration. Specifically, it addresses the need to work with custom
+    sections that signal the usage of custom repositories.
+    """
+
+    reserved_sections = ['ceph-deploy-global', 'ceph-deploy-install']
+
+    def get_safe(self, section, key, default=None):
+        """
+        Attempt to get a configuration value from a certain section
+        in a ``cfg`` object but returning None if not found. Avoids the need
+        to be doing try/except {ConfigParser Exceptions} every time.
+        """
+        try:
+            return self.get(section, key)
+        except (configparser.NoSectionError, configparser.NoOptionError):
+            return default
+
+    def get_repos(self):
+        """
+        Return all the repo sections from the config, excluding the ceph-deploy
+        reserved sections.
+        """
+        return [
+            section for section in self.sections()
+            if section not in self.reserved_sections
+        ]
+
+    @property
+    def has_repos(self):
+        """
+        boolean to reflect having (or not) any repository sections
+        """
+        for section in self.sections():
+            if section not in self.reserved_sections:
+                return True
+        return False
+
+    def get_list(self, section, key):
+        """
+        Assumes that the value for a given key is going to be a list
+        separated by commas. It gets rid of trailing comments.
+        If just one item is present it returns a list with a single item, if no
+        key is found an empty list is returned.
+        """
+        value = self.get_safe(section, key, [])
+        if value == []:
+            return value
+
+        # strip comments
+        value = re.split(r'\s+#', value)[0]
+
+        # split on commas
+        value = value.split(',')
+
+        # strip spaces
+        return [x.strip() for x in value]
+
+    def get_default_repo(self):
+        """
+        Go through all the repositories defined in the config file and search
+        for a truthy value for the ``default`` key. If there isn't any return
+        None.
+        """
+        for repo in self.get_repos():
+            if self.get_safe(repo, 'default') and self.getboolean(repo, 'default'):
+                return repo
+        return False
diff --git a/ceph_deploy/config.py b/ceph_deploy/config.py
new file mode 100644 (file)
index 0000000..1f85ed7
--- /dev/null
@@ -0,0 +1,111 @@
+import logging
+import os.path
+
+from ceph_deploy import exc
+from ceph_deploy import conf
+from ceph_deploy.cliutil import priority
+from ceph_deploy import hosts
+
+LOG = logging.getLogger(__name__)
+
+
+def config_push(args):
+    conf_data = conf.ceph.load_raw(args)
+
+    errors = 0
+    for hostname in args.client:
+        LOG.debug('Pushing config to %s', hostname)
+        try:
+            distro = hosts.get(hostname, username=args.username)
+
+            distro.conn.remote_module.write_conf(
+                args.cluster,
+                conf_data,
+                args.overwrite_conf,
+            )
+
+            distro.conn.exit()
+
+        except RuntimeError as e:
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        raise exc.GenericError('Failed to config %d hosts' % errors)
+
+
+def config_pull(args):
+
+    topath = '{cluster}.conf'.format(cluster=args.cluster)
+    frompath = '/etc/ceph/{cluster}.conf'.format(cluster=args.cluster)
+
+    errors = 0
+    for hostname in args.client:
+        try:
+            LOG.debug('Checking %s for %s', hostname, frompath)
+            distro = hosts.get(hostname, username=args.username)
+            conf_file_contents = distro.conn.remote_module.get_file(frompath)
+
+            if conf_file_contents is not None:
+                LOG.debug('Got %s from %s', frompath, hostname)
+                if os.path.exists(topath):
+                    with open(topath, 'rb') as f:
+                        existing = f.read()
+                        if existing != conf_file_contents and not args.overwrite_conf:
+                            LOG.error('local config file %s exists with different content; use --overwrite-conf to overwrite' % topath)
+                            raise
+
+                with open(topath, 'wb') as f:
+                    f.write(conf_file_contents)
+                return
+            distro.conn.exit()
+            LOG.debug('Empty or missing %s on %s', frompath, hostname)
+        except:
+            LOG.error('Unable to pull %s from %s', frompath, hostname)
+        finally:
+            errors += 1
+
+    raise exc.GenericError('Failed to fetch config from %d hosts' % errors)
+
+
+def config(args):
+    if args.subcommand == 'push':
+        config_push(args)
+    elif args.subcommand == 'pull':
+        config_pull(args)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+
+
+@priority(70)
+def make(parser):
+    """
+    Copy ceph.conf to/from remote host(s)
+    """
+    config_parser = parser.add_subparsers(dest='subcommand')
+    config_parser.required = True
+
+    config_push = config_parser.add_parser(
+        'push',
+        help='push Ceph config file to one or more remote hosts'
+        )
+    config_push.add_argument(
+        'client',
+        metavar='HOST',
+        nargs='+',
+        help='host(s) to push the config file to',
+        )
+
+    config_pull = config_parser.add_parser(
+        'pull',
+        help='pull Ceph config file from one or more remote hosts'
+        )
+    config_pull.add_argument(
+        'client',
+        metavar='HOST',
+        nargs='+',
+        help='host(s) to pull the config file from',
+        )
+    parser.set_defaults(
+        func=config,
+        )
diff --git a/ceph_deploy/connection.py b/ceph_deploy/connection.py
new file mode 100644 (file)
index 0000000..fd71983
--- /dev/null
@@ -0,0 +1,44 @@
+import socket
+from ceph_deploy.lib import remoto
+
+
+def get_connection(hostname, username, logger, threads=5, use_sudo=None, detect_sudo=True):
+    """
+    A very simple helper, meant to return a connection
+    that will know about the need to use sudo.
+    """
+    if username:
+        hostname = "%s@%s" % (username, hostname)
+    try:
+        conn = remoto.Connection(
+            hostname,
+            logger=logger,
+            threads=threads,
+            detect_sudo=detect_sudo,
+        )
+
+        # Set a timeout value in seconds to disconnect and move on
+        # if no data is sent back.
+        conn.global_timeout = 300
+        logger.debug("connected to host: %s " % hostname)
+        return conn
+
+    except Exception as error:
+        msg = "connecting to host: %s " % hostname
+        errors = "resulted in errors: %s %s" % (error.__class__.__name__, error)
+        raise RuntimeError(msg + errors)
+
+
+def get_local_connection(logger, use_sudo=False):
+    """
+    Helper for local connections that are sometimes needed to operate
+    on local hosts
+    """
+    return get_connection(
+        socket.gethostname(),  # cannot rely on 'localhost' here
+        None,
+        logger=logger,
+        threads=1,
+        use_sudo=use_sudo,
+        detect_sudo=False
+    )
diff --git a/ceph_deploy/exc.py b/ceph_deploy/exc.py
new file mode 100644 (file)
index 0000000..064fb9b
--- /dev/null
@@ -0,0 +1,127 @@
+class DeployError(Exception):
+    """
+    Unknown deploy error
+    """
+
+    def __str__(self):
+        doc = self.__doc__.strip()
+        return ': '.join([doc] + [str(a) for a in self.args])
+
+
+class UnableToResolveError(DeployError):
+    """
+    Unable to resolve host
+    """
+
+
+class ClusterExistsError(DeployError):
+    """
+    Cluster config exists already
+    """
+
+
+class ConfigError(DeployError):
+    """
+    Cannot load config
+    """
+
+
+class NeedHostError(DeployError):
+    """
+    No hosts specified to deploy to.
+    """
+
+
+class NeedMonError(DeployError):
+    """
+    Cannot find nodes with ceph-mon.
+    """
+
+
+class NeedDiskError(DeployError):
+    """
+    Must supply disk/path argument
+    """
+
+
+class UnsupportedPlatform(DeployError):
+    """
+    Platform is not supported
+    """
+    def __init__(self, distro, codename, release):
+        self.distro = distro
+        self.codename = codename
+        self.release = release
+
+    def __str__(self):
+        return '{doc}: {distro} {codename} {release}'.format(
+            doc=self.__doc__.strip(),
+            distro=self.distro,
+            codename=self.codename,
+            release=self.release,
+        )
+
+
+class ExecutableNotFound(DeployError):
+    """
+    Could not locate executable
+    """
+    def __init__(self, executable, host):
+        self.executable = executable
+        self.host = host
+
+    def __str__(self):
+        return "{doc} '{executable}' make sure it is installed and available on {host}".format(
+            doc=self.__doc__.strip(),
+            executable=self.executable,
+            host=self.host,
+        )
+
+
+class MissingPackageError(DeployError):
+    """
+    A required package or command is missing
+    """
+    def __init__(self, message):
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+
+class GenericError(DeployError):
+    def __init__(self, message):
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+
+class ClusterNameError(DeployError):
+    """
+    Problem encountered with custom cluster name
+    """
+    def __init__(self, message):
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+
+class KeyNotFoundError(DeployError):
+    """
+    Could not find keyring file
+    """
+    def __init__(self, keyring, hosts):
+        self.keyring = keyring
+        self.hosts = hosts
+
+    def __str__(self):
+        return '{doc}: {keys}'.format(
+            doc=self.__doc__.strip(),
+            keys=', '.join(
+                [self.keyring.format(hostname=host) +
+                 " on host {hostname}".format(hostname=host)
+                 for host in self.hosts]
+            )
+        )
diff --git a/ceph_deploy/forgetkeys.py b/ceph_deploy/forgetkeys.py
new file mode 100644 (file)
index 0000000..faa376e
--- /dev/null
@@ -0,0 +1,37 @@
+import logging
+import errno
+
+from .cliutil import priority
+
+
+LOG = logging.getLogger(__name__)
+
+
+def forgetkeys(args):
+    import os
+    for f in [
+        'mon',
+        'client.admin',
+        'bootstrap-osd',
+        'bootstrap-mds',
+        'bootstrap-rgw',
+        ]:
+        try:
+            os.unlink('{cluster}.{what}.keyring'.format(
+                    cluster=args.cluster,
+                    what=f,
+                    ))
+        except OSError as e:
+            if e.errno == errno.ENOENT:
+                pass
+            else:
+                raise
+
+@priority(100)
+def make(parser):
+    """
+    Remove authentication keys from the local directory.
+    """
+    parser.set_defaults(
+        func=forgetkeys,
+        )
diff --git a/ceph_deploy/gatherkeys.py b/ceph_deploy/gatherkeys.py
new file mode 100644 (file)
index 0000000..ebd4158
--- /dev/null
@@ -0,0 +1,284 @@
+import errno
+import os.path
+import logging
+import json
+import tempfile
+import shutil
+import time
+
+from ceph_deploy import hosts
+from ceph_deploy.cliutil import priority
+from ceph_deploy.lib import remoto
+import ceph_deploy.util.paths.mon
+
+LOG = logging.getLogger(__name__)
+
+
+def _keyring_equivalent(keyring_one, keyring_two):
+    """
+    Check two keyrings are identical
+    """
+    def keyring_extract_key(file_path):
+        """
+        Cephx keyring files may or may not have white space before some lines.
+        They may have some values in quotes, so a safe way to compare is to
+        extract the key.
+        """
+        with open(file_path) as f:
+            for line in f:
+                content = line.strip()
+                if len(content) == 0:
+                    continue
+                split_line = content.split('=')
+                if split_line[0].strip() == 'key':
+                    return "=".join(split_line[1:]).strip()
+        raise RuntimeError("File '%s' is not a keyring" % file_path)
+    key_one = keyring_extract_key(keyring_one)
+    key_two = keyring_extract_key(keyring_two)
+    return key_one == key_two
+
+
+def keytype_path_to(args, keytype):
+    """
+    Get the local filename for a keyring type
+    """
+    if keytype == "admin":
+        return '{cluster}.client.admin.keyring'.format(
+            cluster=args.cluster)
+    if keytype == "mon":
+        return '{cluster}.mon.keyring'.format(
+            cluster=args.cluster)
+    return '{cluster}.bootstrap-{what}.keyring'.format(
+            cluster=args.cluster,
+            what=keytype)
+
+
+def keytype_identity(keytype):
+    """
+    Get the keyring identity from keyring type.
+
+    This is used in authentication with keyrings and generating keyrings.
+    """
+    ident_dict = {
+        'admin' : 'client.admin',
+        'mds' : 'client.bootstrap-mds',
+        'mgr' : 'client.bootstrap-mgr',
+        'osd' : 'client.bootstrap-osd',
+        'rgw' : 'client.bootstrap-rgw',
+        'mon' : 'mon.'
+    }
+    return ident_dict.get(keytype, None)
+
+
+def keytype_capabilities(keytype):
+    """
+    Get the capabilities of a keyring from keyring type.
+    """
+    cap_dict = {
+        'admin' : [
+            'osd', 'allow *',
+            'mds', 'allow *',
+            'mon', 'allow *',
+            'mgr', 'allow *'
+            ],
+        'mds' : [
+            'mon', 'allow profile bootstrap-mds'
+            ],
+        'mgr' : [
+            'mon', 'allow profile bootstrap-mgr'
+            ],
+        'osd' : [
+            'mon', 'allow profile bootstrap-osd'
+            ],
+        'rgw': [
+            'mon', 'allow profile bootstrap-rgw'
+            ]
+        }
+    return cap_dict.get(keytype, None)
+
+
+def gatherkeys_missing(args, distro, rlogger, keypath, keytype, dest_dir):
+    """
+    Get or create the keyring from the mon using the mon keyring by keytype and
+    copy to dest_dir
+    """
+    args_prefix = [
+        '/usr/bin/ceph',
+        '--connect-timeout=25',
+        '--cluster={cluster}'.format(
+            cluster=args.cluster),
+        '--name', 'mon.',
+        '--keyring={keypath}'.format(
+            keypath=keypath),
+        ]
+
+    identity = keytype_identity(keytype)
+    if identity is None:
+        raise RuntimeError('Could not find identity for keytype:%s' % keytype)
+    capabilites = keytype_capabilities(keytype)
+    if capabilites is None:
+        raise RuntimeError('Could not find capabilites for keytype:%s' % keytype)
+
+    # First try getting the key if it already exists, to handle the case where
+    # it exists but doesn't match the caps we would pass into get-or-create.
+    # This is the same behvaior as in newer ceph-create-keys
+    out, err, code = remoto.process.check(
+        distro.conn,
+        args_prefix + ['auth', 'get', identity]
+        )
+    if code == errno.ENOENT:
+        out, err, code = remoto.process.check(
+            distro.conn,
+            args_prefix + ['auth', 'get-or-create', identity] + capabilites
+            )
+    if code != 0:
+        rlogger.error(
+            '"ceph auth get-or-create for keytype %s returned %s',
+            keytype, code
+        )
+        for line in err:
+            rlogger.debug(line)
+        return False
+    keyring_name_local = keytype_path_to(args, keytype)
+    keyring_path_local = os.path.join(dest_dir, keyring_name_local)
+    with open(keyring_path_local, 'wb') as f:
+        for line in out:
+            f.write(line + b'\n')
+    return True
+
+
+def gatherkeys_with_mon(args, host, dest_dir):
+    """
+    Connect to mon and gather keys if mon is in quorum.
+    """
+    distro = hosts.get(host, username=args.username)
+    remote_hostname = distro.conn.remote_module.shortname()
+    dir_keytype_mon = ceph_deploy.util.paths.mon.path(args.cluster, remote_hostname)
+    path_keytype_mon = "%s/keyring" % (dir_keytype_mon)
+    mon_key = distro.conn.remote_module.get_file(path_keytype_mon)
+    if mon_key is None:
+        LOG.warning("No mon key found in host: %s", host)
+        return False
+    mon_name_local = keytype_path_to(args, "mon")
+    mon_path_local = os.path.join(dest_dir, mon_name_local)
+    with open(mon_path_local, 'wb') as f:
+        f.write(mon_key)
+    rlogger = logging.getLogger(host)
+    path_asok = ceph_deploy.util.paths.mon.asok(args.cluster, remote_hostname)
+    out, err, code = remoto.process.check(
+        distro.conn,
+            [
+                "/usr/bin/ceph",
+                "--connect-timeout=25",
+                "--cluster={cluster}".format(
+                    cluster=args.cluster),
+                "--admin-daemon={asok}".format(
+                    asok=path_asok),
+                "mon_status"
+            ]
+        )
+    if code != 0:
+        rlogger.error('"ceph mon_status %s" returned %s', host, code)
+        for line in err:
+            rlogger.debug(line)
+        return False
+    try:
+        mon_status = json.loads(b''.join(out).decode('utf-8'))
+    except ValueError:
+        rlogger.error('"ceph mon_status %s" output was not json', host)
+        for line in out:
+            rlogger.error(line)
+        return False
+    mon_number = None
+    mon_map = mon_status.get('monmap')
+    if mon_map is None:
+        rlogger.error("could not find mon map for mons on '%s'", host)
+        return False
+    mon_quorum = mon_status.get('quorum')
+    if mon_quorum is None:
+        rlogger.error("could not find quorum for mons on '%s'" , host)
+        return False
+    mon_map_mons = mon_map.get('mons')
+    if mon_map_mons is None:
+        rlogger.error("could not find mons in monmap on '%s'", host)
+        return False
+    for mon in mon_map_mons:
+        if mon.get('name') == remote_hostname:
+           mon_number = mon.get('rank')
+           break
+    if mon_number is None:
+        rlogger.error("could not find '%s' in monmap", remote_hostname)
+        return False
+    if not mon_number in mon_quorum:
+        rlogger.error("Not yet quorum for '%s'", host)
+        return False
+    for keytype in ["admin", "mds", "mgr", "osd", "rgw"]:
+        if not gatherkeys_missing(args, distro, rlogger, path_keytype_mon, keytype, dest_dir):
+            # We will return failure if we fail to gather any key
+            rlogger.error("Failed to return '%s' key from host %s", keytype, host)
+            return False
+    return True
+
+
+def gatherkeys(args):
+    """
+    Gather keys from any mon and store in current working directory.
+
+    Backs up keys from previous installs and stores new keys.
+    """
+    oldmask = os.umask(0o77)
+    try:
+        try:
+            tmpd = tempfile.mkdtemp()
+            LOG.info("Storing keys in temp directory %s", tmpd)
+            sucess = False
+            for host in args.mon:
+                sucess = gatherkeys_with_mon(args, host, tmpd)
+                if sucess:
+                    break
+            if not sucess:
+                LOG.error("Failed to connect to host:%s" ,', '.join(args.mon))
+                raise RuntimeError('Failed to connect any mon')
+            had_error = False
+            date_string = time.strftime("%Y%m%d%H%M%S")
+            for keytype in ["admin", "mds", "mgr", "mon", "osd", "rgw"]:
+                filename = keytype_path_to(args, keytype)
+                tmp_path = os.path.join(tmpd, filename)
+                if not os.path.exists(tmp_path):
+                    LOG.error("No key retrived for '%s'" , keytype)
+                    had_error = True
+                    continue
+                if not os.path.exists(filename):
+                    LOG.info("Storing %s" % (filename))
+                    shutil.move(tmp_path, filename)
+                    continue
+                if _keyring_equivalent(tmp_path, filename):
+                    LOG.info("keyring '%s' already exists" , filename)
+                    continue
+                backup_keyring = "%s-%s" % (filename, date_string)
+                LOG.info("Replacing '%s' and backing up old key as '%s'", filename, backup_keyring)
+                shutil.copy(filename, backup_keyring)
+                shutil.move(tmp_path, filename)
+            if had_error:
+                raise RuntimeError('Failed to get all key types')
+        finally:
+            LOG.info("Destroy temp directory %s" %(tmpd))
+            shutil.rmtree(tmpd)
+    finally:
+        os.umask(oldmask)
+
+
+@priority(40)
+def make(parser):
+    """
+    Gather authentication keys for provisioning new nodes.
+    """
+    parser.add_argument(
+        'mon',
+        metavar='HOST',
+        nargs='+',
+        help='monitor host to pull keys from',
+        )
+    parser.set_defaults(
+        func=gatherkeys,
+        )
diff --git a/ceph_deploy/hosts/__init__.py b/ceph_deploy/hosts/__init__.py
new file mode 100644 (file)
index 0000000..6bb5209
--- /dev/null
@@ -0,0 +1,149 @@
+"""
+We deal (mostly) with remote hosts. To avoid special casing each different
+commands (e.g. using `yum` as opposed to `apt`) we can make a one time call to
+that remote host and set all the special cases for running commands depending
+on the type of distribution/version we are dealing with.
+"""
+import logging
+from ceph_deploy import exc
+from ceph_deploy.util import versions
+from ceph_deploy.hosts import debian, centos, fedora, suse, remotes, rhel, arch, alt
+from ceph_deploy.connection import get_connection
+
+logger = logging.getLogger()
+
+
+def get(hostname,
+        username=None,
+        fallback=None,
+        detect_sudo=True,
+        use_rhceph=False,
+        callbacks=None):
+    """
+    Retrieve the module that matches the distribution of a ``hostname``. This
+    function will connect to that host and retrieve the distribution
+    information, then return the appropriate module and slap a few attributes
+    to that module defining the information it found from the hostname.
+
+    For example, if host ``node1.example.com`` is an Ubuntu server, the
+    ``debian`` module would be returned and the following would be set::
+
+        module.name = 'ubuntu'
+        module.release = '12.04'
+        module.codename = 'precise'
+
+    :param hostname: A hostname that is reachable/resolvable over the network
+    :param fallback: Optional fallback to use if no supported distro is found
+    :param use_rhceph: Whether or not to install RH Ceph on a RHEL machine or
+                       the community distro.  Changes what host module is
+                       returned for RHEL.
+    :params callbacks: A list of callables that accept one argument (the actual
+                       module that contains the connection) that will be
+                       called, in order at the end of the instantiation of the
+                       module.
+    """
+    conn = get_connection(
+        hostname,
+        username=username,
+        logger=logging.getLogger(hostname),
+        detect_sudo=detect_sudo
+    )
+    try:
+        conn.import_module(remotes)
+    except IOError as error:
+        if 'already closed' in getattr(error, 'message', ''):
+            raise RuntimeError('remote connection got closed, ensure ``requiretty`` is disabled for %s' % hostname)
+    distro_name, release, codename = conn.remote_module.platform_information()
+    if not codename or not _get_distro(distro_name):
+        raise exc.UnsupportedPlatform(
+            distro=distro_name,
+            codename=codename,
+            release=release)
+
+    machine_type = conn.remote_module.machine_type()
+    module = _get_distro(distro_name, use_rhceph=use_rhceph)
+    module.name = distro_name
+    module.normalized_name = _normalized_distro_name(distro_name)
+    module.normalized_release = _normalized_release(release)
+    module.distro = module.normalized_name
+    module.is_el = module.normalized_name in ['redhat', 'centos', 'fedora', 'scientific', 'oracle', 'virtuozzo']
+    module.is_rpm = module.normalized_name in ['redhat', 'centos',
+                                               'fedora', 'scientific', 'suse', 'oracle', 'virtuozzo', 'alt']
+    module.is_deb = module.normalized_name in ['debian', 'ubuntu']
+    module.is_pkgtarxz = module.normalized_name in ['arch']
+    module.release = release
+    module.codename = codename
+    module.conn = conn
+    module.machine_type = machine_type
+    module.init = module.choose_init(module)
+    module.packager = module.get_packager(module)
+    # execute each callback if any
+    if callbacks:
+        for c in callbacks:
+            c(module)
+    return module
+
+
+def _get_distro(distro, fallback=None, use_rhceph=False):
+    if not distro:
+        return
+
+    distro = _normalized_distro_name(distro)
+    distributions = {
+        'debian': debian,
+        'ubuntu': debian,
+        'centos': centos,
+        'scientific': centos,
+        'oracle': centos,
+        'redhat': centos,
+        'fedora': fedora,
+        'suse': suse,
+        'virtuozzo': centos,
+        'arch': arch,
+        'alt': alt
+        }
+
+    if distro == 'redhat' and use_rhceph:
+        return rhel
+    else:
+        return distributions.get(distro) or _get_distro(fallback)
+
+
+def _normalized_distro_name(distro):
+    distro = distro.lower()
+    if distro.startswith(('redhat', 'red hat')):
+        return 'redhat'
+    elif distro.startswith(('scientific', 'scientific linux')):
+        return 'scientific'
+    elif distro.startswith('oracle'):
+        return 'oracle'
+    elif distro.startswith(('suse', 'opensuse', 'sles')):
+        return 'suse'
+    elif distro.startswith('centos'):
+        return 'centos'
+    elif distro.startswith('linuxmint'):
+        return 'ubuntu'
+    elif distro.startswith('virtuozzo'):
+        return 'virtuozzo'
+    elif distro.startswith('arch'):
+        return 'arch'
+    elif distro.startswith(('alt', 'altlinux', 'basealt', 'alt linux')):
+        return 'alt'
+    return distro
+
+
+def _normalized_release(release):
+    """
+    A normalizer function to make sense of distro
+    release versions.
+
+    Returns an object with: major, minor, patch, and garbage
+
+    These attributes can be accessed as ints with prefixed "int"
+    attribute names, for example:
+
+        normalized_version.int_major
+    """
+    # TODO: at some point deprecate this function so that we just
+    # use this class directly (and update every test that calls it
+    return versions.NormalizedVersion(release)
diff --git a/ceph_deploy/hosts/alt/__init__.py b/ceph_deploy/hosts/alt/__init__.py
new file mode 100644 (file)
index 0000000..a4e53f3
--- /dev/null
@@ -0,0 +1,30 @@
+from . import mon  # noqa
+from .install import install  # noqa
+from .uninstall import uninstall  # noqa
+from ceph_deploy.util import pkg_managers
+from ceph_deploy.util.system import is_systemd
+
+# Allow to set some information about this distro
+#
+
+distro = None
+release = None
+codename = None
+
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (systemd, sysvinit ...).
+    """
+
+
+    if is_systemd(module.conn):
+        return 'systemd'
+
+    return 'sysvinit'
+
+
+def get_packager(module):
+    return pkg_managers.AptRpm(module)
diff --git a/ceph_deploy/hosts/alt/install.py b/ceph_deploy/hosts/alt/install.py
new file mode 100644 (file)
index 0000000..e795b1b
--- /dev/null
@@ -0,0 +1,43 @@
+from ceph_deploy.hosts.centos.install import repo_install, mirror_install  # noqa
+from ceph_deploy.hosts.common import map_components
+from ceph_deploy.util.system import enable_service, start_service
+
+
+NON_SPLIT_PACKAGES = [
+    'ceph-osd',
+    'ceph-mds',
+    'ceph-mon',
+    'ceph-mgr',
+]
+
+SYSTEMD_UNITS = [
+    'ceph.target',
+    'ceph-mds.target',
+    'ceph-mon.target',
+    'ceph-mgr.target',
+    'ceph-osd.target',
+]
+SYSTEMD_UNITS_SKIP_START = [
+    'ceph-mgr.target',
+    'ceph-mon.target',
+]
+SYSTEMD_UNITS_SKIP_ENABLE = [
+]
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+
+    if packages:
+        distro.packager.clean()
+        distro.packager.install(packages)
+
+   # Start and enable services
+    for unit in SYSTEMD_UNITS:
+        if unit not in SYSTEMD_UNITS_SKIP_START:
+            start_service(distro.conn, unit)
+        if unit not in SYSTEMD_UNITS_SKIP_ENABLE:
+            enable_service(distro.conn, unit)
diff --git a/ceph_deploy/hosts/alt/mon/__init__.py b/ceph_deploy/hosts/alt/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/alt/uninstall.py b/ceph_deploy/hosts/alt/uninstall.py
new file mode 100644 (file)
index 0000000..00c808a
--- /dev/null
@@ -0,0 +1,11 @@
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph-common',
+        'ceph-base',
+        'ceph-radosgw',
+        'python-module-cephfs',
+        'python-module-rados',
+        'python-module-rbd',
+        'python-module-rgw',
+        ]
+    distro.packager.remove(packages)
diff --git a/ceph_deploy/hosts/arch/__init__.py b/ceph_deploy/hosts/arch/__init__.py
new file mode 100644 (file)
index 0000000..2e8c0ab
--- /dev/null
@@ -0,0 +1,26 @@
+from . import mon  # noqa
+from ceph_deploy.hosts.centos.install import repo_install  # noqa
+from .install import install, mirror_install  # noqa
+from .uninstall import uninstall  # noqa
+from ceph_deploy.util import pkg_managers
+
+# Allow to set some information about this distro
+#
+
+distro = None
+release = None
+codename = None
+
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (upstart, sysvinit ...).
+    """
+
+    return 'systemd'
+
+
+def get_packager(module):
+    return pkg_managers.Pacman(module)
diff --git a/ceph_deploy/hosts/arch/install.py b/ceph_deploy/hosts/arch/install.py
new file mode 100644 (file)
index 0000000..7625298
--- /dev/null
@@ -0,0 +1,49 @@
+from ceph_deploy.hosts.centos.install import repo_install, mirror_install  # noqa
+from ceph_deploy.hosts.common import map_components
+from ceph_deploy.util.system import enable_service, start_service
+
+
+NON_SPLIT_PACKAGES = [
+    'ceph-osd',
+    'ceph-radosgw',
+    'ceph-mds',
+    'ceph-mon',
+    'ceph-mgr',
+    'ceph-common',
+    'ceph-test'
+]
+
+SYSTEMD_UNITS = [
+    'ceph.target',
+    'ceph-radosgw.target',
+    'ceph-rbd-mirror.target',
+    'ceph-fuse.target',
+    'ceph-mds.target',
+    'ceph-mon.target',
+    'ceph-mgr.target',
+    'ceph-osd.target',
+]
+SYSTEMD_UNITS_SKIP_START = [
+    'ceph-mgr.target',
+    'ceph-mon.target',
+]
+SYSTEMD_UNITS_SKIP_ENABLE = [
+]
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+
+    distro.packager.install(
+        packages
+    )
+
+    # Start and enable services
+    for unit in SYSTEMD_UNITS:
+        if unit not in SYSTEMD_UNITS_SKIP_START:
+            start_service(distro.conn, unit)
+        if unit not in SYSTEMD_UNITS_SKIP_ENABLE:
+            enable_service(distro.conn, unit)
diff --git a/ceph_deploy/hosts/arch/mon/__init__.py b/ceph_deploy/hosts/arch/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/arch/uninstall.py b/ceph_deploy/hosts/arch/uninstall.py
new file mode 100644 (file)
index 0000000..0787392
--- /dev/null
@@ -0,0 +1,50 @@
+import logging
+
+from ceph_deploy.util.system import disable_service, stop_service
+from ceph_deploy.lib import remoto
+
+
+SYSTEMD_UNITS = [
+    'ceph-mds.target',
+    'ceph-mon.target',
+    'ceph-osd.target',
+    'ceph-radosgw.target',
+    'ceph-fuse.target',
+    'ceph-mgr.target',
+    'ceph-rbd-mirror.target',
+    'ceph.target',
+]
+
+
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph',
+    ]
+
+    hostname = distro.conn.hostname
+    LOG = logging.getLogger(hostname)
+
+    # I need to stop and disable services prior package removal
+    LOG.info('stopping and disabling services on {}'.format(hostname))
+    for unit in SYSTEMD_UNITS:
+        stop_service(distro.conn, unit)
+        disable_service(distro.conn, unit)
+
+    # remoto.process.run(
+    #     distro.conn,
+    #     [
+    #         'systemctl',
+    #         'daemon-reload',
+    #     ]
+    # )
+
+    LOG.info('uninstalling packages on {}'.format(hostname))
+    distro.packager.remove(packages)
+
+    remoto.process.run(
+        distro.conn,
+        [
+            'systemctl',
+            'reset-failed',
+        ]
+    )
diff --git a/ceph_deploy/hosts/centos/__init__.py b/ceph_deploy/hosts/centos/__init__.py
new file mode 100644 (file)
index 0000000..daeae4c
--- /dev/null
@@ -0,0 +1,34 @@
+from . import mon  # noqa
+from .install import install, mirror_install, repo_install, repository_url_part, rpm_dist  # noqa
+from .uninstall import uninstall  # noqa
+from ceph_deploy.util import pkg_managers
+from ceph_deploy.util.system import is_systemd
+
+# Allow to set some information about this distro
+#
+
+distro = None
+release = None
+codename = None
+
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (upstart, sysvinit ...).
+    """
+
+    if module.normalized_release.int_major < 7:
+        return 'sysvinit'
+
+    if is_systemd(module.conn):
+        return 'systemd'
+
+    if not module.conn.remote_module.path_exists("/usr/lib/systemd/system/ceph.target"):
+        return 'sysvinit'
+
+    return 'systemd'
+
+def get_packager(module):
+    return pkg_managers.Yum(module)
diff --git a/ceph_deploy/hosts/centos/install.py b/ceph_deploy/hosts/centos/install.py
new file mode 100644 (file)
index 0000000..d622635
--- /dev/null
@@ -0,0 +1,219 @@
+import logging
+from ceph_deploy.util import templates
+from ceph_deploy.lib import remoto
+from ceph_deploy.hosts.common import map_components
+from ceph_deploy.util.paths import gpg
+from ceph_deploy.util import net
+
+
+LOG = logging.getLogger(__name__)
+NON_SPLIT_PACKAGES = ['ceph-osd', 'ceph-mon', 'ceph-mds']
+
+
+def rpm_dist(distro):
+    if distro.normalized_name in ['redhat', 'centos', 'scientific', 'oracle', 'virtuozzo'] and distro.normalized_release.int_major >= 6:
+        return 'el' + distro.normalized_release.major
+    return 'el6'
+
+
+def repository_url_part(distro):
+    """
+    Historically everything CentOS, RHEL, and Scientific has been mapped to
+    `el6` urls, but as we are adding repositories for `rhel`, the URLs should
+    map correctly to, say, `rhel6` or `rhel7`.
+
+    This function looks into the `distro` object and determines the right url
+    part for the given distro, falling back to `el6` when all else fails.
+
+    Specifically to work around the issue of CentOS vs RHEL::
+
+        >>> import platform
+        >>> platform.linux_distribution()
+        ('Red Hat Enterprise Linux Server', '7.0', 'Maipo')
+
+    """
+    if distro.normalized_release.int_major >= 6:
+        if distro.normalized_name == 'redhat':
+            return 'rhel' + distro.normalized_release.major
+        if distro.normalized_name in ['centos', 'scientific', 'oracle', 'virtuozzo']:
+            return 'el' + distro.normalized_release.major
+
+    return 'el6'
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+
+    gpgcheck = kw.pop('gpgcheck', 1)
+    logger = distro.conn.logger
+    machine = distro.machine_type
+    repo_part = repository_url_part(distro)
+    dist = rpm_dist(distro)
+
+    distro.packager.clean()
+
+    # Get EPEL installed before we continue:
+    if adjust_repos:
+        distro.packager.install('epel-release')
+        distro.packager.install('yum-plugin-priorities')
+        distro.conn.remote_module.enable_yum_priority_obsoletes()
+        logger.warning('check_obsoletes has been enabled for Yum priorities plugin')
+    if version_kind in ['stable', 'testing']:
+        key = 'release'
+    else:
+        key = 'autobuild'
+
+    if adjust_repos:
+        if version_kind in ['stable', 'testing']:
+            distro.packager.add_repo_gpg_key(gpg.url(key))
+
+            if version_kind == 'stable':
+                url = 'https://download.ceph.com/rpm-{version}/{repo}/'.format(
+                    version=version,
+                    repo=repo_part,
+                    )
+            elif version_kind == 'testing':
+                url = 'https://download.ceph.com/rpm-testing/{repo}/'.format(repo=repo_part)
+
+            # remove any old ceph-release package from prevoius release
+            remoto.process.run(
+                distro.conn,
+                [
+                    'yum',
+                    'remove',
+                    '-y',
+                    'ceph-release'
+                ],
+            )
+            remoto.process.run(
+                distro.conn,
+                [
+                    'yum',
+                    'install',
+                    '-y',
+                    '{url}noarch/ceph-release-1-0.{dist}.noarch.rpm'.format(url=url, dist=dist),
+                ],
+            )
+
+        elif version_kind in ['dev', 'dev_commit']:
+            logger.info('skipping install of ceph-release package')
+            logger.info('repo file will be created manually')
+            shaman_url = 'https://shaman.ceph.com/api/repos/ceph/{version}/{sha1}/{distro}/{distro_version}/repo/?arch={arch}'.format(
+                distro=distro.normalized_name,
+                distro_version=distro.normalized_release.major,
+                version=kw['args'].dev,
+                sha1=kw['args'].dev_commit or 'latest',
+                arch=machine
+                )
+            LOG.debug('fetching repo information from: %s' % shaman_url)
+            content = net.get_chacra_repo(shaman_url)
+            mirror_install(
+                distro,
+                '',  # empty repo_url
+                None,  # no need to use gpg here, repos are unsigned
+                adjust_repos=True,
+                extra_installs=False,
+                gpgcheck=gpgcheck,
+                repo_content=content
+            )
+
+        else:
+            raise Exception('unrecognized version_kind %s' % version_kind)
+
+        # set the right priority
+        logger.warning('ensuring that /etc/yum.repos.d/ceph.repo contains a high priority')
+        distro.conn.remote_module.set_repo_priority(['Ceph', 'Ceph-noarch', 'ceph-source'])
+        logger.warning('altered ceph.repo priorities to contain: priority=1')
+
+    if packages:
+        distro.packager.install(packages)
+
+
+def mirror_install(distro, repo_url, gpg_url, adjust_repos, extra_installs=True, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+    repo_url = repo_url.strip('/')  # Remove trailing slashes
+    gpgcheck = kw.pop('gpgcheck', 1)
+
+    distro.packager.clean()
+
+    if adjust_repos:
+        if gpg_url:
+            distro.packager.add_repo_gpg_key(gpg_url)
+
+        ceph_repo_content = templates.ceph_repo.format(
+            repo_url=repo_url,
+            gpg_url=gpg_url,
+            gpgcheck=gpgcheck,
+        )
+        content = kw.get('repo_content', ceph_repo_content)
+
+        distro.conn.remote_module.write_yum_repo(content)
+        # set the right priority
+        if distro.packager.name == 'yum':
+            distro.packager.install('yum-plugin-priorities')
+        distro.conn.remote_module.set_repo_priority(['Ceph', 'Ceph-noarch', 'ceph-source'])
+        distro.conn.logger.warning('altered ceph.repo priorities to contain: priority=1')
+
+
+    if extra_installs and packages:
+        distro.packager.install(packages)
+
+
+def repo_install(distro, reponame, baseurl, gpgkey, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+    logger = distro.conn.logger
+    # Get some defaults
+    name = kw.pop('name', '%s repo' % reponame)
+    enabled = kw.pop('enabled', 1)
+    gpgcheck = kw.pop('gpgcheck', 1)
+    install_ceph = kw.pop('install_ceph', False)
+    proxy = kw.pop('proxy', '') # will get ignored if empty
+    _type = 'repo-md'
+    baseurl = baseurl.strip('/')  # Remove trailing slashes
+
+    distro.packager.clean()
+
+    if gpgkey:
+        distro.packager.add_repo_gpg_key(gpgkey)
+
+    repo_content = templates.custom_repo(
+        reponame=reponame,
+        name=name,
+        baseurl=baseurl,
+        enabled=enabled,
+        gpgcheck=gpgcheck,
+        _type=_type,
+        gpgkey=gpgkey,
+        proxy=proxy,
+        **kw
+    )
+
+    distro.conn.remote_module.write_yum_repo(
+        repo_content,
+        "%s.repo" % reponame
+    )
+
+    repo_path = '/etc/yum.repos.d/{reponame}.repo'.format(reponame=reponame)
+
+    # set the right priority
+    if kw.get('priority'):
+        if distro.packager.name == 'yum':
+            distro.packager.install('yum-plugin-priorities')
+
+        distro.conn.remote_module.set_repo_priority([reponame], repo_path)
+        logger.warning('altered {reponame}.repo priorities to contain: priority=1'.format(
+            reponame=reponame)
+        )
+
+    # Some custom repos do not need to install ceph
+    if install_ceph and packages:
+        distro.packager.install(packages)
diff --git a/ceph_deploy/hosts/centos/mon/__init__.py b/ceph_deploy/hosts/centos/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/centos/uninstall.py b/ceph_deploy/hosts/centos/uninstall.py
new file mode 100644 (file)
index 0000000..758c34c
--- /dev/null
@@ -0,0 +1,10 @@
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph',
+        'ceph-release',
+        'ceph-common',
+        'ceph-radosgw',
+        ]
+
+    distro.packager.remove(packages)
+    distro.packager.clean()
diff --git a/ceph_deploy/hosts/common.py b/ceph_deploy/hosts/common.py
new file mode 100644 (file)
index 0000000..70345dc
--- /dev/null
@@ -0,0 +1,248 @@
+from ceph_deploy.util import paths
+from ceph_deploy import conf
+from ceph_deploy.lib import remoto
+from ceph_deploy.util import constants
+from ceph_deploy.util import system
+
+
+def ceph_version(conn):
+    """
+    Log the remote ceph-version by calling `ceph --version`
+    """
+    return remoto.process.run(conn, ['ceph', '--version'])
+
+
+def mon_create(distro, args, monitor_keyring):
+    hostname = distro.conn.remote_module.shortname()
+    logger = distro.conn.logger
+    logger.debug('remote hostname: %s' % hostname)
+    path = paths.mon.path(args.cluster, hostname)
+    uid = distro.conn.remote_module.path_getuid(constants.base_path)
+    gid = distro.conn.remote_module.path_getgid(constants.base_path)
+    done_path = paths.mon.done(args.cluster, hostname)
+    init_path = paths.mon.init(args.cluster, hostname, distro.init)
+
+    conf_data = conf.ceph.load_raw(args)
+
+    # write the configuration file
+    distro.conn.remote_module.write_conf(
+        args.cluster,
+        conf_data,
+        args.overwrite_conf,
+    )
+
+    # if the mon path does not exist, create it
+    distro.conn.remote_module.create_mon_path(path, uid, gid)
+
+    logger.debug('checking for done path: %s' % done_path)
+    if not distro.conn.remote_module.path_exists(done_path):
+        logger.debug('done path does not exist: %s' % done_path)
+        if not distro.conn.remote_module.path_exists(paths.mon.constants.tmp_path):
+            logger.info('creating tmp path: %s' % paths.mon.constants.tmp_path)
+            distro.conn.remote_module.makedir(paths.mon.constants.tmp_path)
+        keyring = paths.mon.keyring(args.cluster, hostname)
+
+        logger.info('creating keyring file: %s' % keyring)
+        distro.conn.remote_module.write_monitor_keyring(
+            keyring,
+            monitor_keyring,
+            uid, gid,
+        )
+
+        user_args = []
+        if uid != 0:
+            user_args = user_args + [ '--setuser', str(uid) ]
+        if gid != 0:
+            user_args = user_args + [ '--setgroup', str(gid) ]
+
+        remoto.process.run(
+            distro.conn,
+            [
+                'ceph-mon',
+                '--cluster', args.cluster,
+                '--mkfs',
+                '-i', hostname,
+                '--keyring', keyring,
+            ] + user_args
+        )
+
+        logger.info('unlinking keyring file %s' % keyring)
+        distro.conn.remote_module.unlink(keyring)
+
+    # create the done file
+    distro.conn.remote_module.create_done_path(done_path, uid, gid)
+
+    # create init path
+    distro.conn.remote_module.create_init_path(init_path, uid, gid)
+
+    # start mon service
+    start_mon_service(distro, args.cluster, hostname)
+
+
+def mon_add(distro, args, monitor_keyring):
+    hostname = distro.conn.remote_module.shortname()
+    logger = distro.conn.logger
+    path = paths.mon.path(args.cluster, hostname)
+    uid = distro.conn.remote_module.path_getuid(constants.base_path)
+    gid = distro.conn.remote_module.path_getgid(constants.base_path)
+    monmap_path = paths.mon.monmap(args.cluster, hostname)
+    done_path = paths.mon.done(args.cluster, hostname)
+    init_path = paths.mon.init(args.cluster, hostname, distro.init)
+
+    conf_data = conf.ceph.load_raw(args)
+
+    # write the configuration file
+    distro.conn.remote_module.write_conf(
+        args.cluster,
+        conf_data,
+        args.overwrite_conf,
+    )
+
+    # if the mon path does not exist, create it
+    distro.conn.remote_module.create_mon_path(path, uid, gid)
+
+    logger.debug('checking for done path: %s' % done_path)
+    if not distro.conn.remote_module.path_exists(done_path):
+        logger.debug('done path does not exist: %s' % done_path)
+        if not distro.conn.remote_module.path_exists(paths.mon.constants.tmp_path):
+            logger.info('creating tmp path: %s' % paths.mon.constants.tmp_path)
+            distro.conn.remote_module.makedir(paths.mon.constants.tmp_path)
+        keyring = paths.mon.keyring(args.cluster, hostname)
+
+        logger.info('creating keyring file: %s' % keyring)
+        distro.conn.remote_module.write_monitor_keyring(
+            keyring,
+            monitor_keyring,
+            uid, gid,
+        )
+
+        # get the monmap
+        remoto.process.run(
+            distro.conn,
+            [
+                'ceph',
+                '--cluster', args.cluster,
+                'mon',
+                'getmap',
+                '-o',
+                monmap_path,
+            ],
+        )
+
+        # now use it to prepare the monitor's data dir
+        user_args = []
+        if uid != 0:
+            user_args = user_args + [ '--setuser', str(uid) ]
+        if gid != 0:
+            user_args = user_args + [ '--setgroup', str(gid) ]
+
+        remoto.process.run(
+            distro.conn,
+            [
+                'ceph-mon',
+                '--cluster', args.cluster,
+                '--mkfs',
+                '-i', hostname,
+                '--monmap',
+                monmap_path,
+                '--keyring', keyring,
+            ] + user_args
+        )
+
+        logger.info('unlinking keyring file %s' % keyring)
+        distro.conn.remote_module.unlink(keyring)
+
+    # create the done file
+    distro.conn.remote_module.create_done_path(done_path, uid, gid)
+
+    # create init path
+    distro.conn.remote_module.create_init_path(init_path, uid, gid)
+
+    # start mon service
+    start_mon_service(distro, args.cluster, hostname)
+
+
+def map_components(notsplit_packages, components):
+    """
+    Returns a list of packages to install based on component names
+
+    This is done by checking if a component is in notsplit_packages,
+    if it is, we know we need to install 'ceph' instead of the
+    raw component name.  Essentially, this component hasn't been
+    'split' from the master 'ceph' package yet.
+    """
+    packages = set()
+
+    for c in components:
+        if c in notsplit_packages:
+            packages.add('ceph')
+        else:
+            packages.add(c)
+
+    return list(packages)
+
+
+def start_mon_service(distro, cluster, hostname):
+    """
+    start mon service depending on distro init
+    """
+    if distro.init == 'sysvinit':
+        service = distro.conn.remote_module.which_service()
+        remoto.process.run(
+            distro.conn,
+            [
+                service,
+                'ceph',
+                '-c',
+                '/etc/ceph/{cluster}.conf'.format(cluster=cluster),
+                'start',
+                'mon.{hostname}'.format(hostname=hostname)
+            ],
+            timeout=7,
+        )
+        system.enable_service(distro.conn)
+
+    elif distro.init == 'upstart':
+        remoto.process.run(
+             distro.conn,
+             [
+                 'initctl',
+                 'emit',
+                 'ceph-mon',
+                 'cluster={cluster}'.format(cluster=cluster),
+                 'id={hostname}'.format(hostname=hostname),
+             ],
+             timeout=7,
+         )
+
+    elif distro.init == 'systemd':
+       # enable ceph target for this host (in case it isn't already enabled)
+        remoto.process.run(
+            distro.conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph.target'
+            ],
+            timeout=7,
+        )
+
+        # enable and start this mon instance
+        remoto.process.run(
+            distro.conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph-mon@{hostname}'.format(hostname=hostname),
+            ],
+            timeout=7,
+        )
+        remoto.process.run(
+            distro.conn,
+            [
+                'systemctl',
+                'start',
+                'ceph-mon@{hostname}'.format(hostname=hostname),
+            ],
+            timeout=7,
+        )
diff --git a/ceph_deploy/hosts/debian/__init__.py b/ceph_deploy/hosts/debian/__init__.py
new file mode 100644 (file)
index 0000000..e3e747b
--- /dev/null
@@ -0,0 +1,35 @@
+from . import mon  # noqa
+from .install import install, mirror_install, repo_install  # noqa
+from .uninstall import uninstall  # noqa
+from ceph_deploy.util import pkg_managers
+from ceph_deploy.util.system import is_systemd, is_upstart
+
+# Allow to set some information about this distro
+#
+
+distro = None
+release = None
+codename = None
+
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (upstart, sysvinit ...).
+    """
+    # Upstart checks first because when installing ceph, the
+    # `/lib/systemd/system/ceph.target` file may be created, fooling this
+    # detection mechanism.
+    if is_upstart(module.conn):
+        return 'upstart'
+
+    if is_systemd(module.conn) or module.conn.remote_module.path_exists(
+            "/lib/systemd/system/ceph.target"):
+        return 'systemd'
+
+    return 'sysvinit'
+
+
+def get_packager(module):
+    return pkg_managers.Apt(module)
diff --git a/ceph_deploy/hosts/debian/install.py b/ceph_deploy/hosts/debian/install.py
new file mode 100644 (file)
index 0000000..1aa4431
--- /dev/null
@@ -0,0 +1,125 @@
+try:
+    from urllib.parse import urlparse
+except ImportError:
+    from urlparse import urlparse
+import logging
+from ceph_deploy.util.paths import gpg
+from ceph_deploy.util import net
+
+
+LOG = logging.getLogger(__name__)
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = kw.pop('components', [])
+    codename = distro.codename
+    machine = distro.machine_type
+    extra_install_flags = []
+
+    if version_kind in ['stable', 'testing']:
+        key = 'release'
+    else:
+        key = 'autobuild'
+
+    distro.packager.clean()
+    distro.packager.install(['ca-certificates', 'apt-transport-https'])
+
+    if adjust_repos:
+        # Wheezy does not like the download.ceph.com SSL cert
+        protocol = 'https'
+        if codename == 'wheezy':
+            protocol = 'http'
+
+        if version_kind in ['dev', 'dev_commit']:
+            shaman_url = 'https://shaman.ceph.com/api/repos/ceph/{version}/{sha1}/{distro}/{distro_version}/repo/?arch={arch}'.format(
+                distro=distro.normalized_name,
+                distro_version=distro.codename,
+                version=kw['args'].dev,
+                sha1=kw['args'].dev_commit or 'latest',
+                arch=machine
+                )
+            LOG.debug('fetching repo information from: %s' % shaman_url)
+            chacra_url = net.get_request(shaman_url).geturl()
+            content = net.get_chacra_repo(shaman_url)
+            # set the repo priority for the right domain
+            fqdn = urlparse(chacra_url).hostname
+            distro.conn.remote_module.set_apt_priority(fqdn)
+            distro.conn.remote_module.write_sources_list_content(content)
+            extra_install_flags = ['-o', 'Dpkg::Options::=--force-confnew', '--allow-unauthenticated']
+        else:
+            distro.packager.add_repo_gpg_key(gpg.url(key, protocol=protocol))
+            if version_kind == 'stable':
+                url = '{protocol}://download.ceph.com/debian-{version}/'.format(
+                    protocol=protocol,
+                    version=version,
+                    )
+            elif version_kind == 'testing':
+                url = '{protocol}://download.ceph.com/debian-testing/'.format(
+                    protocol=protocol,
+                    )
+            else:
+                raise RuntimeError('Unknown version kind: %r' % version_kind)
+
+            # set the repo priority for the right domain
+            fqdn = urlparse(url).hostname
+            distro.conn.remote_module.set_apt_priority(fqdn)
+            distro.conn.remote_module.write_sources_list(url, codename)
+            extra_install_flags = ['-o', 'Dpkg::Options::=--force-confnew']
+
+    distro.packager.clean()
+
+    # TODO this does not downgrade -- should it?
+    if packages:
+        distro.packager.install(
+            packages,
+            extra_install_flags=extra_install_flags
+        )
+
+
+def mirror_install(distro, repo_url, gpg_url, adjust_repos, **kw):
+    packages = kw.pop('components', [])
+    version_kind = kw['args'].version_kind
+    repo_url = repo_url.strip('/')  # Remove trailing slashes
+
+    if adjust_repos:
+        distro.packager.add_repo_gpg_key(gpg_url)
+
+        # set the repo priority for the right domain
+        fqdn = urlparse(repo_url).hostname
+        distro.conn.remote_module.set_apt_priority(fqdn)
+
+        distro.conn.remote_module.write_sources_list(repo_url, distro.codename)
+
+    extra_install_flags = ['--allow-unauthenticated'] if version_kind in 'dev' else []
+
+    if packages:
+        distro.packager.clean()
+        distro.packager.install(
+            packages,
+            extra_install_flags=extra_install_flags)
+
+
+def repo_install(distro, repo_name, baseurl, gpgkey, **kw):
+    packages = kw.pop('components', [])
+    # Get some defaults
+    safe_filename = '%s.list' % repo_name.replace(' ', '-')
+    install_ceph = kw.pop('install_ceph', False)
+    baseurl = baseurl.strip('/')  # Remove trailing slashes
+
+    distro.packager.add_repo_gpg_key(gpgkey)
+
+    distro.conn.remote_module.write_sources_list(
+        baseurl,
+        distro.codename,
+        safe_filename
+    )
+
+    # set the repo priority for the right domain
+    fqdn = urlparse(baseurl).hostname
+    distro.conn.remote_module.set_apt_priority(fqdn)
+
+    # repo is not operable until an update
+    distro.packager.clean()
+
+    if install_ceph and packages:
+        distro.packager.install(packages)
diff --git a/ceph_deploy/hosts/debian/mon/__init__.py b/ceph_deploy/hosts/debian/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/debian/uninstall.py b/ceph_deploy/hosts/debian/uninstall.py
new file mode 100644 (file)
index 0000000..b3a01b2
--- /dev/null
@@ -0,0 +1,15 @@
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph',
+        'ceph-mds',
+        'ceph-common',
+        'ceph-fs-common',
+        'radosgw',
+        ]
+    extra_remove_flags = []
+    if purge:
+        extra_remove_flags.append('--purge')
+    distro.packager.remove(
+        packages,
+        extra_remove_flags=extra_remove_flags
+    )
diff --git a/ceph_deploy/hosts/fedora/__init__.py b/ceph_deploy/hosts/fedora/__init__.py
new file mode 100644 (file)
index 0000000..81d8aca
--- /dev/null
@@ -0,0 +1,30 @@
+from . import mon  # noqa
+from ceph_deploy.hosts.centos.install import repo_install  # noqa
+from .install import install, mirror_install  # noqa
+from .uninstall import uninstall  # noqa
+from ceph_deploy.util import pkg_managers
+
+# Allow to set some information about this distro
+#
+
+distro = None
+release = None
+codename = None
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (upstart, sysvinit ...).
+    """
+
+    if not module.conn.remote_module.path_exists("/usr/lib/systemd/system/ceph.target"):
+        return 'sysvinit'
+
+    return 'systemd'
+
+def get_packager(module):
+    if module.normalized_release.int_major >= 22:
+        return pkg_managers.DNF(module)
+    else:
+        return pkg_managers.Yum(module)
diff --git a/ceph_deploy/hosts/fedora/install.py b/ceph_deploy/hosts/fedora/install.py
new file mode 100644 (file)
index 0000000..b2806f4
--- /dev/null
@@ -0,0 +1,87 @@
+from ceph_deploy.lib import remoto
+from ceph_deploy.hosts.centos.install import repo_install, mirror_install  # noqa
+from ceph_deploy.util.paths import gpg
+from ceph_deploy.hosts.common import map_components
+
+
+NON_SPLIT_PACKAGES = ['ceph-osd', 'ceph-mon', 'ceph-mds']
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+    gpgcheck = kw.pop('gpgcheck', 1)
+
+    logger = distro.conn.logger
+    release = distro.release
+    machine = distro.machine_type
+
+    if version_kind in ['stable', 'testing']:
+        key = 'release'
+    else:
+        key = 'autobuild'
+
+    if adjust_repos:
+        if distro.packager.name == 'yum':
+            distro.packager.install('yum-plugin-priorities')
+            # haven't been able to determine necessity of check_obsoletes with DNF
+            distro.conn.remote_module.enable_yum_priority_obsoletes()
+            logger.warning('check_obsoletes has been enabled for Yum priorities plugin')
+
+        if version_kind in ['stable', 'testing']:
+            distro.packager.add_repo_gpg_key(gpg.url(key))
+
+            if version_kind == 'stable':
+                url = 'https://download.ceph.com/rpm-{version}/fc{release}/'.format(
+                    version=version,
+                    release=release,
+                    )
+            elif version_kind == 'testing':
+                url = 'https://download.ceph.com/rpm-testing/fc{release}'.format(
+                    release=release,
+                    )
+
+            remoto.process.run(
+                distro.conn,
+                [
+                    'rpm',
+                    '-Uvh',
+                    '--replacepkgs',
+                    '--force',
+                    '--quiet',
+                    '{url}noarch/ceph-release-1-0.fc{release}.noarch.rpm'.format(
+                        url=url,
+                        release=release,
+                        ),
+                ]
+            )
+
+            # set the right priority
+            logger.warning('ensuring that /etc/yum.repos.d/ceph.repo contains a high priority')
+            distro.conn.remote_module.set_repo_priority(['Ceph', 'Ceph-noarch', 'ceph-source'])
+            logger.warning('altered ceph.repo priorities to contain: priority=1')
+
+        elif version_kind in ['dev', 'dev_commit']:
+            logger.info('skipping install of ceph-release package')
+            logger.info('repo file will be created manually')
+            mirror_install(
+                distro,
+                'http://gitbuilder.ceph.com/ceph-rpm-fc{release}-{machine}-basic/{sub}/{version}/'.format(
+                    release=release.split(".", 1)[0],
+                    machine=machine,
+                    sub='ref' if version_kind == 'dev' else 'sha1',
+                    version=version),
+                gpg.url(key),
+                adjust_repos=True,
+                extra_installs=False,
+                gpgcheck=gpgcheck,
+            )
+
+        else:
+            raise Exception('unrecognized version_kind %s' % version_kind)
+
+    distro.packager.install(
+        packages
+    )
diff --git a/ceph_deploy/hosts/fedora/mon/__init__.py b/ceph_deploy/hosts/fedora/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/fedora/uninstall.py b/ceph_deploy/hosts/fedora/uninstall.py
new file mode 100644 (file)
index 0000000..8d40909
--- /dev/null
@@ -0,0 +1,8 @@
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph',
+        'ceph-common',
+        'ceph-radosgw',
+        ]
+
+    distro.packager.remove(packages)
diff --git a/ceph_deploy/hosts/remotes.py b/ceph_deploy/hosts/remotes.py
new file mode 100644 (file)
index 0000000..72dd1f6
--- /dev/null
@@ -0,0 +1,413 @@
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+import errno
+import socket
+import os
+import shutil
+import tempfile
+import platform
+import re
+
+
+def platform_information(_linux_distribution=None):
+    """ detect platform information from remote host """
+    linux_distribution = _linux_distribution or platform.linux_distribution
+    distro, release, codename = linux_distribution()
+    if not distro:
+        distro, release, codename = parse_os_release()
+    if not codename and 'debian' in distro.lower():  # this could be an empty string in Debian
+        debian_codenames = {
+            '10': 'buster',
+            '9': 'stretch',
+            '8': 'jessie',
+            '7': 'wheezy',
+            '6': 'squeeze',
+        }
+        major_version = release.split('.')[0]
+        codename = debian_codenames.get(major_version, '')
+
+        # In order to support newer jessie/sid or wheezy/sid strings we test this
+        # if sid is buried in the minor, we should use sid anyway.
+        if not codename and '/' in release:
+            major, minor = release.split('/')
+            if minor == 'sid':
+                codename = minor
+            else:
+                codename = major
+    if not codename and 'oracle' in distro.lower():  # this could be an empty string in Oracle linux
+        codename = 'oracle'
+    if not codename and 'virtuozzo linux' in distro.lower():  # this could be an empty string in Virtuozzo linux
+        codename = 'virtuozzo'
+    if not codename and 'arch' in distro.lower():  # this could be an empty string in Arch linux
+        codename = 'arch'
+
+    return (
+        str(distro).rstrip(),
+        str(release).rstrip(),
+        str(codename).rstrip()
+    )
+
+
+def parse_os_release(release_path='/etc/os-release'):
+    """ Extract (distro, release, codename) from /etc/os-release if present """
+    release_info = {}
+    if os.path.isfile(release_path):
+        for line in open(release_path, 'r').readlines():
+            line = line.strip()
+            if line.startswith('#'):
+                continue
+            parts = line.split('=')
+            if len(parts) != 2:
+                continue
+            release_info[parts[0].strip()] = parts[1].strip("\"'\n\t ")
+    # In theory, we want ID/NAME, VERSION_ID and VERSION_CODENAME (with a
+    # possible fallback to VERSION on the latter), based on information at:
+    # https://www.freedesktop.org/software/systemd/man/os-release.html
+    # However, after reviewing several distros /etc/os-release, getting
+    # the codename is a bit of a mess.  It's usually in parentheses in
+    # VERSION, with some exceptions.
+    distro = release_info.get('ID', '')
+    release = release_info.get('VERSION_ID', '')
+    codename = release_info.get('UBUNTU_CODENAME', release_info.get('VERSION', ''))
+    match = re.match(r'^[^(]+ \(([^)]+)\)', codename)
+    if match:
+        codename = match.group(1).lower()
+    if not codename and release_info.get('NAME', '') == 'openSUSE Tumbleweed':
+        codename = 'tumbleweed'
+    return (distro, release, codename)
+
+def machine_type():
+    """ detect machine type """
+    return platform.machine()
+
+
+def write_sources_list(url, codename, filename='ceph.list', mode=0o644):
+    """add deb repo to /etc/apt/sources.list.d/"""
+    repo_path = os.path.join('/etc/apt/sources.list.d', filename)
+    content = 'deb {url} {codename} main\n'.format(
+        url=url,
+        codename=codename,
+    )
+    write_file(repo_path, content.encode('utf-8'), mode)
+
+
+def write_sources_list_content(content, filename='ceph.list', mode=0o644):
+    """add deb repo to /etc/apt/sources.list.d/ from content"""
+    repo_path = os.path.join('/etc/apt/sources.list.d', filename)
+    if not isinstance(content, str):
+        content = content.decode('utf-8')
+    write_file(repo_path, content.encode('utf-8'), mode)
+
+
+def write_yum_repo(content, filename='ceph.repo'):
+    """add yum repo file in /etc/yum.repos.d/"""
+    repo_path = os.path.join('/etc/yum.repos.d', filename)
+    if not isinstance(content, str):
+        content = content.decode('utf-8')
+    write_file(repo_path, content.encode('utf-8'))
+
+
+def set_apt_priority(fqdn, path='/etc/apt/preferences.d/ceph.pref'):
+    template = "Package: *\nPin: origin {fqdn}\nPin-Priority: 999\n"
+    content = template.format(fqdn=fqdn)
+    with open(path, 'w') as fout:
+        fout.write(content)
+
+
+def set_repo_priority(sections, path='/etc/yum.repos.d/ceph.repo', priority='1'):
+    Config = configparser.ConfigParser()
+    Config.read(path)
+    Config.sections()
+    for section in sections:
+        try:
+            Config.set(section, 'priority', priority)
+        except configparser.NoSectionError:
+            # Emperor versions of Ceph used all lowercase sections
+            # so lets just try again for the section that failed, maybe
+            # we are able to find it if it is lower
+            Config.set(section.lower(), 'priority', priority)
+
+    with open(path, 'w') as fout:
+        Config.write(fout)
+
+    # And now, because ConfigParser is super duper, we need to remove the
+    # assignments so this looks like it was before
+    def remove_whitespace_from_assignments():
+        separator = "="
+        lines = open(path).readlines()
+        fp = open(path, "w")
+        for line in lines:
+            line = line.strip()
+            if not line.startswith("#") and separator in line:
+                assignment = line.split(separator, 1)
+                assignment = tuple(map(str.strip, assignment))
+                fp.write("%s%s%s\n" % (assignment[0], separator, assignment[1]))
+            else:
+                fp.write(line + "\n")
+
+    remove_whitespace_from_assignments()
+
+
+def write_conf(cluster, conf, overwrite):
+    """ write cluster configuration to /etc/ceph/{cluster}.conf """
+    path = '/etc/ceph/{cluster}.conf'.format(cluster=cluster)
+    tmp_file = tempfile.NamedTemporaryFile('w', dir='/etc/ceph', delete=False)
+    err_msg = 'config file %s exists with different content; use --overwrite-conf to overwrite' % path
+
+    if os.path.exists(path):
+        with open(path, 'r') as f:
+            old = f.read()
+            if old != conf and not overwrite:
+                raise RuntimeError(err_msg)
+        tmp_file.write(conf)
+        tmp_file.close()
+        shutil.move(tmp_file.name, path)
+        os.chmod(path, 0o644)
+        return
+    if os.path.exists('/etc/ceph'):
+        with open(path, 'w') as f:
+            f.write(conf)
+        os.chmod(path, 0o644)
+    else:
+        err_msg = '/etc/ceph/ does not exist - could not write config'
+        raise RuntimeError(err_msg)
+
+
+def write_keyring(path, key, uid=-1, gid=-1):
+    """ create a keyring file """
+    # Note that we *require* to avoid deletion of the temp file
+    # otherwise we risk not being able to copy the contents from
+    # one file system to the other, hence the `delete=False`
+    tmp_file = tempfile.NamedTemporaryFile('wb', delete=False)
+    tmp_file.write(key)
+    tmp_file.close()
+    keyring_dir = os.path.dirname(path)
+    if not path_exists(keyring_dir):
+        makedir(keyring_dir, uid, gid)
+    shutil.move(tmp_file.name, path)
+
+
+def create_mon_path(path, uid=-1, gid=-1):
+    """create the mon path if it does not exist"""
+    if not os.path.exists(path):
+        os.makedirs(path)
+        os.chown(path, uid, gid);
+
+
+def create_done_path(done_path, uid=-1, gid=-1):
+    """create a done file to avoid re-doing the mon deployment"""
+    with open(done_path, 'wb'):
+        pass
+    os.chown(done_path, uid, gid);
+
+
+def create_init_path(init_path, uid=-1, gid=-1):
+    """create the init path if it does not exist"""
+    if not os.path.exists(init_path):
+        with open(init_path, 'wb'):
+            pass
+        os.chown(init_path, uid, gid);
+
+
+def append_to_file(file_path, contents):
+    """append contents to file"""
+    with open(file_path, 'a') as f:
+        f.write(contents)
+
+def path_getuid(path):
+    return os.stat(path).st_uid
+
+def path_getgid(path):
+    return os.stat(path).st_gid
+
+def readline(path):
+    with open(path) as _file:
+        return _file.readline().strip('\n')
+
+
+def path_exists(path):
+    return os.path.exists(path)
+
+
+def get_realpath(path):
+    return os.path.realpath(path)
+
+
+def listdir(path):
+    return os.listdir(path)
+
+
+def makedir(path, ignored=None, uid=-1, gid=-1):
+    ignored = ignored or []
+    try:
+        os.makedirs(path)
+    except OSError as error:
+        if error.errno in ignored:
+            pass
+        else:
+            # re-raise the original exception
+            raise
+    else:
+        os.chown(path, uid, gid);
+
+
+def unlink(_file):
+    os.unlink(_file)
+
+
+def write_monitor_keyring(keyring, monitor_keyring, uid=-1, gid=-1):
+    """create the monitor keyring file"""
+    write_file(keyring, monitor_keyring, 0o600, None, uid, gid)
+
+
+def write_file(path, content, mode=0o644, directory=None, uid=-1, gid=-1):
+    if directory:
+        if path.startswith("/"):
+            path = path[1:]
+        path = os.path.join(directory, path)
+    if os.path.exists(path):
+        # Delete file in case we are changing its mode
+        os.unlink(path)
+    with os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT, mode), 'wb') as f:
+        f.write(content)
+    os.chown(path, uid, gid)
+
+
+def touch_file(path):
+    with open(path, 'wb') as f:  # noqa
+        pass
+
+
+def get_file(path):
+    """ fetch remote file """
+    try:
+        with open(path, 'rb') as f:
+            return f.read()
+    except IOError:
+        pass
+
+
+def object_grep(term, file_object):
+    for line in file_object.readlines():
+        if term in line:
+            return True
+    return False
+
+
+def grep(term, file_path):
+    # A small grep-like function that will search for a word in a file and
+    # return True if it does and False if it does not.
+
+    # Implemented initially to have a similar behavior as the init system
+    # detection in Ceph's init scripts::
+
+    #     # detect systemd
+    #     # SYSTEMD=0
+    #     grep -qs systemd /proc/1/comm && SYSTEMD=1
+
+    # .. note:: Because we intent to be operating in silent mode, we explicitly
+    # return ``False`` if the file does not exist.
+    if not os.path.isfile(file_path):
+        return False
+
+    with open(file_path) as _file:
+        return object_grep(term, _file)
+
+
+def shortname():
+    """get remote short hostname"""
+    return socket.gethostname().split('.', 1)[0]
+
+
+def which_service():
+    """ locating the `service` executable... """
+    # XXX This should get deprecated at some point. For now
+    # it just bypasses and uses the new helper.
+    return which('service')
+
+
+def which(executable):
+    """find the location of an executable"""
+    locations = (
+        '/usr/local/bin',
+        '/bin',
+        '/usr/bin',
+        '/usr/local/sbin',
+        '/usr/sbin',
+        '/sbin',
+    )
+
+    for location in locations:
+        executable_path = os.path.join(location, executable)
+        if os.path.exists(executable_path) and os.path.isfile(executable_path):
+            return executable_path
+
+
+def make_mon_removed_dir(path, file_name):
+    """ move old monitor data """
+    try:
+        os.makedirs('/var/lib/ceph/mon-removed')
+    except OSError as e:
+        if e.errno != errno.EEXIST:
+            raise
+    shutil.move(path, os.path.join('/var/lib/ceph/mon-removed/', file_name))
+
+
+def safe_mkdir(path, uid=-1, gid=-1):
+    """ create path if it doesn't exist """
+    try:
+        os.mkdir(path)
+    except OSError as e:
+        if e.errno == errno.EEXIST:
+            pass
+        else:
+            raise
+    else:
+        os.chown(path, uid, gid)
+
+
+def safe_makedirs(path, uid=-1, gid=-1):
+    """ create path recursively if it doesn't exist """
+    try:
+        os.makedirs(path)
+    except OSError as e:
+        if e.errno == errno.EEXIST:
+            pass
+        else:
+            raise
+    else:
+        os.chown(path, uid, gid)
+
+
+def zeroing(dev):
+    """ zeroing last few blocks of device """
+    # this kills the crab
+    #
+    # sgdisk will wipe out the main copy of the GPT partition
+    # table (sorry), but it doesn't remove the backup copies, and
+    # subsequent commands will continue to complain and fail when
+    # they see those.  zeroing the last few blocks of the device
+    # appears to do the trick.
+    lba_size = 4096
+    size = 33 * lba_size
+    return True
+    with open(dev, 'wb') as f:
+        f.seek(-size, os.SEEK_END)
+        f.write(size*b'\0')
+
+
+def enable_yum_priority_obsoletes(path="/etc/yum/pluginconf.d/priorities.conf"):
+    """Configure Yum priorities to include obsoletes"""
+    config = configparser.ConfigParser()
+    config.read(path)
+    config.set('main', 'check_obsoletes', '1')
+    with open(path, 'w') as fout:
+        config.write(fout)
+
+
+# remoto magic, needed to execute these functions remotely
+if __name__ == '__channelexec__':
+    for item in channel:  # noqa
+        channel.send(eval(item))  # noqa
diff --git a/ceph_deploy/hosts/rhel/__init__.py b/ceph_deploy/hosts/rhel/__init__.py
new file mode 100644 (file)
index 0000000..a86ad50
--- /dev/null
@@ -0,0 +1,33 @@
+from . import mon  # noqa
+from .install import install, mirror_install, repo_install  # noqa
+from .uninstall import uninstall  # noqa
+from ceph_deploy.util import pkg_managers
+from ceph_deploy.util.system import is_systemd
+
+# Allow to set some information about this distro
+#
+
+distro = None
+release = None
+codename = None
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (upstart, sysvinit ...).
+    """
+
+    if module.normalized_release.int_major < 7:
+        return 'sysvinit'
+
+    if not module.conn.remote_module.path_exists("/usr/lib/systemd/system/ceph.target"):
+        return 'sysvinit'
+
+    if is_systemd(module.conn):
+        return 'systemd'
+
+    return 'systemd'
+
+def get_packager(module):
+    return pkg_managers.Yum(module)
diff --git a/ceph_deploy/hosts/rhel/install.py b/ceph_deploy/hosts/rhel/install.py
new file mode 100644 (file)
index 0000000..bf44a03
--- /dev/null
@@ -0,0 +1,71 @@
+from ceph_deploy.util import templates
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = kw.get('components', [])
+    distro.packager.clean()
+    distro.packager.install(packages)
+
+
+def mirror_install(distro, repo_url,
+                   gpg_url, adjust_repos, extra_installs=True, **kw):
+    packages = kw.get('components', [])
+    repo_url = repo_url.strip('/')  # Remove trailing slashes
+    gpgcheck = kw.pop('gpgcheck', 1)
+
+    distro.packager.clean()
+
+    if adjust_repos:
+        distro.packager.add_repo_gpg_key(gpg_url)
+
+        ceph_repo_content = templates.ceph_repo.format(
+            repo_url=repo_url,
+            gpg_url=gpg_url,
+            gpgcheck=gpgcheck,
+        )
+
+        distro.conn.remote_module.write_yum_repo(ceph_repo_content)
+
+    if extra_installs and packages:
+        distro.packager.install(packages)
+
+
+def repo_install(distro, reponame, baseurl, gpgkey, **kw):
+    # do we have specific components to install?
+    # removed them from `kw` so that we don't mess with other defaults
+    packages = kw.pop('components', [])
+
+    # Get some defaults
+    name = kw.pop('name', '%s repo' % reponame)
+    enabled = kw.pop('enabled', 1)
+    gpgcheck = kw.pop('gpgcheck', 1)
+    install_ceph = kw.pop('install_ceph', False)
+    proxy = kw.pop('proxy', '') # will get ignored if empty
+    _type = 'repo-md'
+    baseurl = baseurl.strip('/')  # Remove trailing slashes
+
+    distro.packager.clean()
+
+    if gpgkey:
+        distro.packager.add_repo_gpg_key(gpgkey)
+
+    repo_content = templates.custom_repo(
+        reponame=reponame,
+        name=name,
+        baseurl=baseurl,
+        enabled=enabled,
+        gpgcheck=gpgcheck,
+        _type=_type,
+        gpgkey=gpgkey,
+        proxy=proxy,
+        **kw
+    )
+
+    distro.conn.remote_module.write_yum_repo(
+        repo_content,
+        "%s.repo" % reponame
+    )
+
+    # Some custom repos do not need to install ceph
+    if install_ceph and packages:
+        distro.packager.install(packages)
diff --git a/ceph_deploy/hosts/rhel/mon/__init__.py b/ceph_deploy/hosts/rhel/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/rhel/uninstall.py b/ceph_deploy/hosts/rhel/uninstall.py
new file mode 100644 (file)
index 0000000..17ae208
--- /dev/null
@@ -0,0 +1,11 @@
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph',
+        'ceph-common',
+        'ceph-mon',
+        'ceph-osd',
+        'ceph-radosgw'
+        ]
+
+    distro.packager.remove(packages)
+    distro.packager.clean()
diff --git a/ceph_deploy/hosts/suse/__init__.py b/ceph_deploy/hosts/suse/__init__.py
new file mode 100644 (file)
index 0000000..af66a15
--- /dev/null
@@ -0,0 +1,31 @@
+from . import mon  # noqa
+from .install import install, mirror_install, repo_install  # noqa
+from .uninstall import uninstall  # noqa
+import logging
+
+from ceph_deploy.util import pkg_managers
+
+# Allow to set some information about this distro
+#
+
+log = logging.getLogger(__name__)
+
+distro = None
+release = None
+codename = None
+
+def choose_init(module):
+    """
+    Select a init system
+
+    Returns the name of a init system (upstart, sysvinit ...).
+    """
+    init_mapping = { '11' : 'sysvinit', # SLE_11
+        '12' : 'systemd',               # SLE_12
+        '13.1' : 'systemd',             # openSUSE_13.1
+        }
+    return init_mapping.get(release, 'systemd')
+
+
+def get_packager(module):
+    return pkg_managers.Zypper(module)
diff --git a/ceph_deploy/hosts/suse/install.py b/ceph_deploy/hosts/suse/install.py
new file mode 100644 (file)
index 0000000..7a23f38
--- /dev/null
@@ -0,0 +1,98 @@
+import logging
+
+from ceph_deploy.util import templates
+from ceph_deploy.lib import remoto
+from ceph_deploy.hosts.common import map_components
+
+LOG = logging.getLogger(__name__)
+
+NON_SPLIT_PACKAGES = ['ceph-osd', 'ceph-mon', 'ceph-mds']
+
+
+def install(distro, version_kind, version, adjust_repos, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.get('components', [])
+    )
+
+    distro.packager.clean()
+    if packages:
+        distro.packager.install(packages)
+
+
+def mirror_install(distro, repo_url, gpg_url, adjust_repos, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.get('components', [])
+    )
+    repo_url = repo_url.strip('/')  # Remove trailing slashes
+    gpg_url_path = gpg_url.split('file://')[-1]  # Remove file if present
+    gpgcheck = kw.pop('gpgcheck', 1)
+
+    if adjust_repos:
+        remoto.process.run(
+            distro.conn,
+            [
+                'rpm',
+                '--import',
+                gpg_url_path,
+            ]
+        )
+
+        ceph_repo_content = templates.zypper_repo.format(
+            repo_url=repo_url,
+            gpg_url=gpg_url,
+            gpgcheck=gpgcheck,
+        )
+        distro.conn.remote_module.write_file(
+            '/etc/zypp/repos.d/ceph.repo',
+            ceph_repo_content.encode('utf-8'))
+        distro.packager.clean()
+
+    if packages:
+        distro.packager.install(packages)
+
+
+def repo_install(distro, reponame, baseurl, gpgkey, **kw):
+    packages = map_components(
+        NON_SPLIT_PACKAGES,
+        kw.pop('components', [])
+    )
+    # Get some defaults
+    name = kw.get('name', '%s repo' % reponame)
+    enabled = kw.get('enabled', 1)
+    gpgcheck = kw.get('gpgcheck', 1)
+    install_ceph = kw.pop('install_ceph', False)
+    proxy = kw.get('proxy')
+    _type = 'repo-md'
+    baseurl = baseurl.strip('/')  # Remove trailing slashes
+
+    if gpgkey:
+        remoto.process.run(
+            distro.conn,
+            [
+                'rpm',
+                '--import',
+                gpgkey,
+            ]
+        )
+
+    repo_content = templates.custom_repo(
+        reponame=reponame,
+        name = name,
+        baseurl = baseurl,
+        enabled = enabled,
+        gpgcheck = gpgcheck,
+        _type = _type,
+        gpgkey = gpgkey,
+        proxy = proxy,
+    )
+
+    distro.conn.remote_module.write_file(
+        '/etc/zypp/repos.d/%s' % (reponame),
+        repo_content.encode('utf-8')
+    )
+
+    # Some custom repos do not need to install ceph
+    if install_ceph and packages:
+        distro.packager.install(packages)
diff --git a/ceph_deploy/hosts/suse/mon/__init__.py b/ceph_deploy/hosts/suse/mon/__init__.py
new file mode 100644 (file)
index 0000000..f266fb0
--- /dev/null
@@ -0,0 +1,2 @@
+from ceph_deploy.hosts.common import mon_add as add  # noqa
+from ceph_deploy.hosts.common import mon_create as create  # noqa
diff --git a/ceph_deploy/hosts/suse/uninstall.py b/ceph_deploy/hosts/suse/uninstall.py
new file mode 100644 (file)
index 0000000..163d891
--- /dev/null
@@ -0,0 +1,10 @@
+def uninstall(distro, purge=False):
+    packages = [
+        'ceph',
+        'ceph-common',
+        'libcephfs1',
+        'librados2',
+        'librbd1',
+        'ceph-radosgw',
+        ]
+    distro.packager.remove(packages)
diff --git a/ceph_deploy/hosts/util.py b/ceph_deploy/hosts/util.py
new file mode 100644 (file)
index 0000000..b943609
--- /dev/null
@@ -0,0 +1,31 @@
+"""
+A utility module that can host utilities that will be used by more than
+one type of distro and not common to all of them
+"""
+from ceph_deploy.util import pkg_managers
+
+
+def install_yum_priorities(distro, _yum=None):
+    """
+    EPEL started packaging Ceph so we need to make sure that the ceph.repo we
+    install has a higher priority than the EPEL repo so that when installing
+    Ceph it will come from the repo file we create.
+
+    The name of the package changed back and forth (!) since CentOS 4:
+
+    From the CentOS wiki::
+
+        Note: This plugin has carried at least two differing names over time.
+        It is named yum-priorities on CentOS-5 but was named
+        yum-plugin-priorities on CentOS-4. CentOS-6 has reverted to
+        yum-plugin-priorities.
+
+    :params _yum: Used for testing, so we can inject a fake yum
+    """
+    yum = _yum or pkg_managers.yum
+    package_name = 'yum-plugin-priorities'
+
+    if distro.normalized_name == 'centos':
+        if distro.release[0] != '6':
+            package_name = 'yum-priorities'
+    yum(distro.conn, package_name)
diff --git a/ceph_deploy/install.py b/ceph_deploy/install.py
new file mode 100644 (file)
index 0000000..12c5f79
--- /dev/null
@@ -0,0 +1,670 @@
+import argparse
+import logging
+import os
+
+from ceph_deploy import hosts
+from ceph_deploy.cliutil import priority
+from ceph_deploy.lib import remoto
+from ceph_deploy.util.constants import default_components
+from ceph_deploy.util.paths import gpg
+
+LOG = logging.getLogger(__name__)
+
+
+def sanitize_args(args):
+    """
+    args may need a bunch of logic to set proper defaults that argparse is
+    not well suited for.
+    """
+    if args.release is None:
+        args.release = 'nautilus'
+        args.default_release = True
+
+    # XXX This whole dance is because --stable is getting deprecated
+    if args.stable is not None:
+        LOG.warning('the --stable flag is deprecated, use --release instead')
+        args.release = args.stable
+    # XXX Tango ends here.
+
+    return args
+
+
+def detect_components(args, distro):
+    """
+    Since the package split, now there are various different Ceph components to
+    install like:
+
+    * ceph
+    * ceph-mon
+    * ceph-mgr
+    * ceph-osd
+    * ceph-mds
+
+    This helper function should parse the args that may contain specifics about
+    these flags and return the default if none are passed in (which is, install
+    everything)
+    """
+    # the flag that prevents all logic here is the `--repo` flag which is used
+    # when no packages should be installed, just the repo files, so check for
+    # that here and return an empty list (which is equivalent to say 'no
+    # packages should be installed')
+    if args.repo:
+        return []
+
+    flags = {
+        'install_osd': 'ceph-osd',
+        'install_rgw': 'ceph-radosgw',
+        'install_mds': 'ceph-mds',
+        'install_mon': 'ceph-mon',
+        'install_mgr': 'ceph-mgr',
+        'install_common': 'ceph-common',
+        'install_tests': 'ceph-test',
+    }
+
+    if distro.is_rpm:
+        defaults = default_components.rpm
+    elif distro.is_pkgtarxz:
+        # archlinux doesn't have components!
+        flags = {
+            'install_osd': 'ceph',
+            'install_rgw': 'ceph',
+            'install_mds': 'ceph',
+            'install_mon': 'ceph',
+            'install_mgr': 'ceph',
+            'install_common': 'ceph',
+            'install_tests': 'ceph',
+        }
+        defaults = default_components.pkgtarxz
+    else:
+        defaults = default_components.deb
+        # different naming convention for deb than rpm for radosgw
+        flags['install_rgw'] = 'radosgw'
+
+    if args.install_all:
+        return defaults
+    else:
+        components = []
+        for k, v in flags.items():
+            if getattr(args, k, False):
+                components.append(v)
+        # if we have some components selected from flags then return that,
+        # otherwise return defaults because no flags and no `--repo` means we
+        # should get all of them by default
+        return components or defaults
+
+
+def install(args):
+    args = sanitize_args(args)
+
+    if args.repo:
+        return install_repo(args)
+
+    gpgcheck = 0 if args.nogpgcheck else 1
+
+    if args.version_kind == 'stable':
+        version = args.release
+    else:
+        version = getattr(args, args.version_kind)
+
+    version_str = args.version_kind
+
+    if version:
+        version_str += ' version {version}'.format(version=version)
+    LOG.debug(
+        'Installing %s on cluster %s hosts %s',
+        version_str,
+        args.cluster,
+        ' '.join(args.host),
+    )
+
+    for hostname in args.host:
+        LOG.debug('Detecting platform for host %s ...', hostname)
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            # XXX this should get removed once ceph packages are split for
+            # upstream. If default_release is True, it means that the user is
+            # trying to install on a RHEL machine and should expect to get RHEL
+            # packages. Otherwise, it will need to specify either a specific
+            # version, or repo, or a development branch. Other distro users
+            # should not see any differences.
+            use_rhceph=args.default_release,
+            )
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        components = detect_components(args, distro)
+        if distro.init == 'sysvinit' and args.cluster != 'ceph':
+            LOG.error('refusing to install on host: %s, with custom cluster name: %s' % (
+                    hostname,
+                    args.cluster,
+                )
+            )
+            LOG.error('custom cluster names are not supported on sysvinit hosts')
+            continue
+
+        rlogger = logging.getLogger(hostname)
+        rlogger.info('installing Ceph on %s' % hostname)
+
+        cd_conf = getattr(args, 'cd_conf', None)
+
+        # custom repo arguments
+        repo_url = os.environ.get('CEPH_DEPLOY_REPO_URL') or args.repo_url
+        gpg_url = os.environ.get('CEPH_DEPLOY_GPG_URL') or args.gpg_url
+        gpg_fallback = gpg.url('release')
+
+        if gpg_url is None and repo_url:
+            LOG.warning('--gpg-url was not used, will fallback')
+            LOG.warning('using GPG fallback: %s', gpg_fallback)
+            gpg_url = gpg_fallback
+
+        if args.local_mirror:
+            if args.username:
+                hostname = "%s@%s" % (args.username, hostname)
+            remoto.rsync(hostname, args.local_mirror, '/opt/ceph-deploy/repo', distro.conn.logger, sudo=True)
+            repo_url = 'file:///opt/ceph-deploy/repo'
+            gpg_url = 'file:///opt/ceph-deploy/repo/release.asc'
+
+        if repo_url:  # triggers using a custom repository
+            # the user used a custom repo url, this should override anything
+            # we can detect from the configuration, so warn about it
+            if cd_conf:
+                if cd_conf.get_default_repo():
+                    rlogger.warning('a default repo was found but it was \
+                        overridden on the CLI')
+                if args.release in cd_conf.get_repos():
+                    rlogger.warning('a custom repo was found but it was \
+                        overridden on the CLI')
+
+            rlogger.info('using custom repository location: %s', repo_url)
+            distro.mirror_install(
+                distro,
+                repo_url,
+                gpg_url,
+                args.adjust_repos,
+                components=components,
+                gpgcheck=gpgcheck,
+                args=args
+            )
+
+        # Detect and install custom repos here if needed
+        elif should_use_custom_repo(args, cd_conf, repo_url):
+            LOG.info('detected valid custom repositories from config file')
+            custom_repo(distro, args, cd_conf, rlogger)
+
+        else:  # otherwise a normal installation
+            distro.install(
+                distro,
+                args.version_kind,
+                version,
+                args.adjust_repos,
+                components=components,
+                gpgcheck = gpgcheck,
+                args=args
+            )
+
+        # Check the ceph version we just installed
+        hosts.common.ceph_version(distro.conn)
+        distro.conn.exit()
+
+
+def should_use_custom_repo(args, cd_conf, repo_url):
+    """
+    A boolean to determine the logic needed to proceed with a custom repo
+    installation instead of cramming everything nect to the logic operator.
+    """
+    if repo_url:
+        # repo_url signals a CLI override, return False immediately
+        return False
+    if cd_conf:
+        if cd_conf.has_repos:
+            has_valid_release = args.release in cd_conf.get_repos()
+            has_default_repo = cd_conf.get_default_repo()
+            if has_valid_release or has_default_repo:
+                return True
+    return False
+
+
+def custom_repo(distro, args, cd_conf, rlogger, install_ceph=None):
+    """
+    A custom repo install helper that will go through config checks to retrieve
+    repos (and any extra repos defined) and install those
+
+    ``cd_conf`` is the object built from argparse that holds the flags and
+    information needed to determine what metadata from the configuration to be
+    used.
+    """
+    default_repo = cd_conf.get_default_repo()
+    components = detect_components(args, distro)
+    if args.release in cd_conf.get_repos():
+        LOG.info('will use repository from conf: %s' % args.release)
+        default_repo = args.release
+    elif default_repo:
+        LOG.info('will use default repository: %s' % default_repo)
+
+    # At this point we know there is a cd_conf and that it has custom
+    # repos make sure we were able to detect and actual repo
+    if not default_repo:
+        LOG.warning('a ceph-deploy config was found with repos \
+            but could not default to one')
+    else:
+        options = dict(cd_conf.items(default_repo))
+        options['install_ceph'] = False if install_ceph is False else True
+        extra_repos = cd_conf.get_list(default_repo, 'extra-repos')
+        rlogger.info('adding custom repository file')
+        try:
+            distro.repo_install(
+                distro,
+                default_repo,
+                options.pop('baseurl'),
+                options.pop('gpgkey'),
+                components=components,
+                **options
+            )
+        except KeyError as err:
+            raise RuntimeError('missing required key: %s in config section: %s' % (err, default_repo))
+
+        for xrepo in extra_repos:
+            rlogger.info('adding extra repo file: %s.repo' % xrepo)
+            options = dict(cd_conf.items(xrepo))
+            try:
+                distro.repo_install(
+                    distro,
+                    xrepo,
+                    options.pop('baseurl'),
+                    options.pop('gpgkey'),
+                    components=components,
+                    **options
+                )
+            except KeyError as err:
+                raise RuntimeError('missing required key: %s in config section: %s' % (err, xrepo))
+
+
+def install_repo(args):
+    """
+    For a user that only wants to install the repository only (and avoid
+    installing Ceph and its dependencies).
+    """
+    cd_conf = getattr(args, 'cd_conf', None)
+
+    for hostname in args.host:
+        LOG.debug('Detecting platform for host %s ...', hostname)
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            # XXX this should get removed once Ceph packages are split for
+            # upstream. If default_release is True, it means that the user is
+            # trying to install on a RHEL machine and should expect to get RHEL
+            # packages. Otherwise, it will need to specify either a specific
+            # version, or repo, or a development branch. Other distro users should
+            # not see any differences.
+            use_rhceph=args.default_release,
+        )
+        rlogger = logging.getLogger(hostname)
+
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        custom_repo(distro, args, cd_conf, rlogger, install_ceph=False)
+
+def remove(args, purge):
+    LOG.info('note that some dependencies *will not* be removed because they can cause issues with qemu-kvm')
+    LOG.info('like: librbd1 and librados2')
+    remove_action = 'Uninstalling'
+    if purge:
+        remove_action = 'Purging'
+    LOG.debug(
+        '%s on cluster %s hosts %s',
+        remove_action,
+        args.cluster,
+        ' '.join(args.host),
+        )
+
+    for hostname in args.host:
+        LOG.debug('Detecting platform for host %s ...', hostname)
+
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            use_rhceph=True)
+        LOG.info('Distro info: %s %s %s', distro.name, distro.release, distro.codename)
+        rlogger = logging.getLogger(hostname)
+        rlogger.info('%s Ceph on %s' % (remove_action, hostname))
+        distro.uninstall(distro, purge=purge)
+        distro.conn.exit()
+
+def uninstall(args):
+    remove(args, False)
+
+def purge(args):
+    remove(args, True)
+
+def purgedata(args):
+    LOG.debug(
+        'Purging data from cluster %s hosts %s',
+        args.cluster,
+        ' '.join(args.host),
+        )
+
+    installed_hosts = []
+    for hostname in args.host:
+        distro = hosts.get(hostname, username=args.username)
+        ceph_is_installed = distro.conn.remote_module.which('ceph')
+        if ceph_is_installed:
+            installed_hosts.append(hostname)
+        distro.conn.exit()
+
+    if installed_hosts:
+        LOG.error("Ceph is still installed on: %s", installed_hosts)
+        raise RuntimeError("refusing to purge data while Ceph is still installed")
+
+    for hostname in args.host:
+        distro = hosts.get(hostname, username=args.username)
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        rlogger = logging.getLogger(hostname)
+        rlogger.info('purging data on %s' % hostname)
+
+        # Try to remove the contents of /var/lib/ceph first, don't worry
+        # about errors here, we deal with them later on
+        remoto.process.check(
+            distro.conn,
+            [
+                'rm', '-rf', '--one-file-system', '--', '/var/lib/ceph',
+            ]
+        )
+
+        # If we failed in the previous call, then we probably have OSDs
+        # still mounted, so we unmount them here
+        if distro.conn.remote_module.path_exists('/var/lib/ceph'):
+            rlogger.warning(
+                'OSDs may still be mounted, trying to unmount them'
+            )
+            remoto.process.run(
+                distro.conn,
+                [
+                    'find', '/var/lib/ceph',
+                    '-mindepth', '1',
+                    '-maxdepth', '2',
+                    '-type', 'd',
+                    '-exec', 'umount', '{}', ';',
+                ]
+            )
+
+            # And now we try again to remove the contents, since OSDs should be
+            # unmounted, but this time we do check for errors
+            remoto.process.run(
+                distro.conn,
+                [
+                    'rm', '-rf', '--one-file-system', '--', '/var/lib/ceph',
+                ]
+            )
+
+        remoto.process.run(
+            distro.conn,
+            [
+                'rm', '-rf', '--one-file-system', '--', '/etc/ceph/',
+            ]
+        )
+
+        distro.conn.exit()
+
+
+class StoreVersion(argparse.Action):
+    """
+    Like ``"store"`` but also remember which one of the exclusive
+    options was set.
+
+    There are three kinds of versions: stable, testing and dev.
+    This sets ``version_kind`` to be the right one of the above.
+
+    This kludge essentially lets us differentiate explicitly set
+    values from defaults.
+    """
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, values)
+        if self.dest == 'release':
+            self.dest = 'stable'
+        namespace.version_kind = self.dest
+
+
+@priority(20)
+def make(parser):
+    """
+    Install Ceph packages on remote hosts.
+    """
+
+    version = parser.add_mutually_exclusive_group()
+
+    # XXX deprecated in favor of release
+    version.add_argument(
+        '--stable',
+        nargs='?',
+        action=StoreVersion,
+        metavar='CODENAME',
+        help='[DEPRECATED] install a release known as CODENAME\
+                (done by default) (default: %(default)s)',
+    )
+
+    version.add_argument(
+        '--release',
+        nargs='?',
+        action=StoreVersion,
+        metavar='CODENAME',
+        help='install a release known as CODENAME\
+                (done by default) (default: %(default)s)',
+    )
+
+    version.add_argument(
+        '--testing',
+        nargs=0,
+        action=StoreVersion,
+        help='install the latest development release',
+    )
+
+    version.add_argument(
+        '--dev',
+        nargs='?',
+        action=StoreVersion,
+        const='master',
+        metavar='BRANCH_OR_TAG',
+        help='install a bleeding edge build from Git branch\
+                or tag (default: %(default)s)',
+    )
+    parser.add_argument(
+        '--dev-commit',
+        nargs='?',
+        action=StoreVersion,
+        metavar='COMMIT',
+        help='install a bleeding edge build from Git commit (defaults to master branch)',
+    )
+
+    version.set_defaults(
+        stable=None,  # XXX deprecated in favor of release
+        release=None,  # Set the default release in sanitize_args()
+        dev='master',
+        version_kind='stable',
+    )
+
+    parser.add_argument(
+        '--mon',
+        dest='install_mon',
+        action='store_true',
+        help='install the mon component only',
+    )
+
+    parser.add_argument(
+        '--mgr',
+        dest='install_mgr',
+        action='store_true',
+        help='install the mgr component only',
+    )
+
+    parser.add_argument(
+        '--mds',
+        dest='install_mds',
+        action='store_true',
+        help='install the mds component only',
+    )
+
+    parser.add_argument(
+        '--rgw',
+        dest='install_rgw',
+        action='store_true',
+        help='install the rgw component only',
+    )
+
+    parser.add_argument(
+        '--osd',
+        dest='install_osd',
+        action='store_true',
+        help='install the osd component only',
+    )
+
+    parser.add_argument(
+        '--tests',
+        dest='install_tests',
+        action='store_true',
+        help='install the testing components',
+    )
+
+    parser.add_argument(
+        '--cli', '--common',
+        dest='install_common',
+        action='store_true',
+        help='install the common component only',
+    )
+
+    parser.add_argument(
+        '--all',
+        dest='install_all',
+        action='store_true',
+        help='install all Ceph components (mon, osd, mds, rgw) except tests. This is the default',
+    )
+
+    repo = parser.add_mutually_exclusive_group()
+
+    repo.add_argument(
+        '--adjust-repos',
+        dest='adjust_repos',
+        action='store_true',
+        help='install packages modifying source repos',
+    )
+
+    repo.add_argument(
+        '--no-adjust-repos',
+        dest='adjust_repos',
+        action='store_false',
+        help='install packages without modifying source repos',
+    )
+
+    repo.add_argument(
+        '--repo',
+        action='store_true',
+        help='install repo files only (skips package installation)',
+    )
+
+    repo.set_defaults(
+        adjust_repos=True,
+    )
+
+    parser.add_argument(
+        'host',
+        metavar='HOST',
+        nargs='+',
+        help='hosts to install on',
+    )
+
+    parser.add_argument(
+        '--local-mirror',
+        nargs='?',
+        const='PATH',
+        default=None,
+        help='Fetch packages and push them to hosts for a local repo mirror',
+    )
+
+    parser.add_argument(
+        '--repo-url',
+        nargs='?',
+        dest='repo_url',
+        help='specify a repo URL that mirrors/contains Ceph packages',
+    )
+
+    parser.add_argument(
+        '--gpg-url',
+        nargs='?',
+        dest='gpg_url',
+        help='specify a GPG key URL to be used with custom repos\
+                (defaults to ceph.com)'
+    )
+
+    parser.add_argument(
+        '--nogpgcheck',
+        action='store_true',
+        help='install packages without gpgcheck',
+    )
+
+    parser.set_defaults(
+        func=install,
+    )
+
+
+@priority(80)
+def make_uninstall(parser):
+    """
+    Remove Ceph packages from remote hosts.
+    """
+    parser.add_argument(
+        'host',
+        metavar='HOST',
+        nargs='+',
+        help='hosts to uninstall Ceph from',
+        )
+    parser.set_defaults(
+        func=uninstall,
+        )
+
+
+@priority(80)
+def make_purge(parser):
+    """
+    Remove Ceph packages from remote hosts and purge all data.
+    """
+    parser.add_argument(
+        'host',
+        metavar='HOST',
+        nargs='+',
+        help='hosts to purge Ceph from',
+        )
+    parser.set_defaults(
+        func=purge,
+        )
+
+
+@priority(80)
+def make_purge_data(parser):
+    """
+    Purge (delete, destroy, discard, shred) any Ceph data from /var/lib/ceph
+    """
+    parser.add_argument(
+        'host',
+        metavar='HOST',
+        nargs='+',
+        help='hosts to purge Ceph data from',
+        )
+    parser.set_defaults(
+        func=purgedata,
+        )
diff --git a/ceph_deploy/lib/__init__.py b/ceph_deploy/lib/__init__.py
new file mode 100644 (file)
index 0000000..fefb992
--- /dev/null
@@ -0,0 +1,27 @@
+"""
+This module is meant for vendorizing Python libraries. Most libraries will need
+to have some ``sys.path`` alterations done unless they are doing relative
+imports.
+
+Do **not** add anything to this module that does not represent a vendorized
+library.
+
+Vendored libraries should go into the ``vendor`` directory and imported from
+there. This is so we allow libraries that are installed normally to be imported
+if the vendored module is not available.
+
+The import dance here is done so that all other imports throught ceph-deploy
+are kept the same regardless of where the module comes from.
+
+The expected way to import remoto would look like this::
+
+    from ceph_deploy.lib import remoto
+
+"""
+
+try:
+    # vendored
+    from .vendor import remoto
+except ImportError:
+    # normally installed
+    import remoto  # noqa
diff --git a/ceph_deploy/lib/vendor/__init__.py b/ceph_deploy/lib/vendor/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ceph_deploy/mds.py b/ceph_deploy/mds.py
new file mode 100644 (file)
index 0000000..58f3eb8
--- /dev/null
@@ -0,0 +1,226 @@
+import logging
+import os
+
+from ceph_deploy import conf
+from ceph_deploy import exc
+from ceph_deploy import hosts
+from ceph_deploy.util import system
+from ceph_deploy.lib import remoto
+from ceph_deploy.cliutil import priority
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_bootstrap_mds_key(cluster):
+    """
+    Read the bootstrap-mds key for `cluster`.
+    """
+    path = '{cluster}.bootstrap-mds.keyring'.format(cluster=cluster)
+    try:
+        with open(path, 'rb') as f:
+            return f.read()
+    except IOError:
+        raise RuntimeError('bootstrap-mds keyring not found; run \'gatherkeys\'')
+
+
+def create_mds(distro, name, cluster, init):
+    conn = distro.conn
+
+    path = '/var/lib/ceph/mds/{cluster}-{name}'.format(
+        cluster=cluster,
+        name=name
+        )
+
+    conn.remote_module.safe_mkdir(path)
+
+    bootstrap_keyring = '/var/lib/ceph/bootstrap-mds/{cluster}.keyring'.format(
+        cluster=cluster
+        )
+
+    keypath = os.path.join(path, 'keyring')
+
+    stdout, stderr, returncode = remoto.process.check(
+        conn,
+        [
+            'ceph',
+            '--cluster', cluster,
+            '--name', 'client.bootstrap-mds',
+            '--keyring', bootstrap_keyring,
+            'auth', 'get-or-create', 'mds.{name}'.format(name=name),
+            'osd', 'allow rwx',
+            'mds', 'allow',
+            'mon', 'allow profile mds',
+            '-o',
+            os.path.join(keypath),
+        ]
+    )
+    if returncode > 0:
+        for line in stderr:
+            conn.logger.error(line)
+        for line in stdout:
+            # yes stdout as err because this is an error
+            conn.logger.error(line)
+        conn.logger.error('exit code from command was: %s' % returncode)
+        raise RuntimeError('could not create mds')
+
+    conn.remote_module.touch_file(os.path.join(path, 'done'))
+    conn.remote_module.touch_file(os.path.join(path, init))
+
+    if init == 'upstart':
+        remoto.process.run(
+            conn,
+            [
+                'initctl',
+                'emit',
+                'ceph-mds',
+                'cluster={cluster}'.format(cluster=cluster),
+                'id={name}'.format(name=name),
+            ],
+            timeout=7
+        )
+    elif init == 'sysvinit':
+        remoto.process.run(
+            conn,
+            [
+                'service',
+                'ceph',
+                'start',
+                'mds.{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        if distro.is_el:
+            system.enable_service(distro.conn)
+    elif init == 'systemd':
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph-mds@{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'start',
+                'ceph-mds@{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph.target',
+            ],
+            timeout=7
+        )
+
+
+
+def mds_create(args):
+    conf_data = conf.ceph.load_raw(args)
+    LOG.debug(
+        'Deploying mds, cluster %s hosts %s',
+        args.cluster,
+        ' '.join(':'.join(x or '' for x in t) for t in args.mds),
+        )
+
+    key = get_bootstrap_mds_key(cluster=args.cluster)
+
+    bootstrapped = set()
+    errors = 0
+    failed_on_rhel = False
+
+    for hostname, name in args.mds:
+        try:
+            distro = None
+            distro = hosts.get(hostname, username=args.username)
+            rlogger = distro.conn.logger
+            LOG.info(
+                'Distro info: %s %s %s',
+                distro.name,
+                distro.release,
+                distro.codename
+            )
+
+            LOG.debug('remote host will use %s', distro.init)
+
+            if hostname not in bootstrapped:
+                bootstrapped.add(hostname)
+                LOG.debug('deploying mds bootstrap to %s', hostname)
+                distro.conn.remote_module.write_conf(
+                    args.cluster,
+                    conf_data,
+                    args.overwrite_conf,
+                )
+
+                path = '/var/lib/ceph/bootstrap-mds/{cluster}.keyring'.format(
+                    cluster=args.cluster,
+                )
+
+                if not distro.conn.remote_module.path_exists(path):
+                    rlogger.warning('mds keyring does not exist yet, creating one')
+                    distro.conn.remote_module.write_keyring(path, key)
+
+            create_mds(distro, name, args.cluster, distro.init)
+            distro.conn.exit()
+        except RuntimeError as e:
+            if distro and distro.normalized_name == 'redhat':
+                LOG.error('this feature may not yet available for %s %s' % (distro.name, distro.release))
+                failed_on_rhel = True
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        if failed_on_rhel:
+            # because users only read the last few lines :(
+            LOG.error(
+                'RHEL RHCS systems do not have the ability to deploy MDS yet'
+            )
+
+        raise exc.GenericError('Failed to create %d MDSs' % errors)
+
+
+def mds(args):
+    if args.subcommand == 'create':
+        mds_create(args)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+
+
+def colon_separated(s):
+    host = s
+    name = s
+    if s.count(':') == 1:
+        (host, name) = s.split(':')
+    return (host, name)
+
+
+@priority(30)
+def make(parser):
+    """
+    Ceph MDS daemon management
+    """
+    mds_parser = parser.add_subparsers(dest='subcommand')
+    mds_parser.required = True
+
+    mds_create = mds_parser.add_parser(
+        'create',
+        help='Deploy Ceph MDS on remote host(s)'
+    )
+    mds_create.add_argument(
+        'mds',
+        metavar='HOST[:NAME]',
+        nargs='+',
+        type=colon_separated,
+        help='host (and optionally the daemon name) to deploy on',
+        )
+    parser.set_defaults(
+        func=mds,
+        )
diff --git a/ceph_deploy/mgr.py b/ceph_deploy/mgr.py
new file mode 100644 (file)
index 0000000..6d5ad13
--- /dev/null
@@ -0,0 +1,226 @@
+import logging
+import os
+
+from ceph_deploy import conf
+from ceph_deploy import exc
+from ceph_deploy import hosts
+from ceph_deploy.util import system
+from ceph_deploy.lib import remoto
+from ceph_deploy.cliutil import priority
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_bootstrap_mgr_key(cluster):
+    """
+    Read the bootstrap-mgr key for `cluster`.
+    """
+    path = '{cluster}.bootstrap-mgr.keyring'.format(cluster=cluster)
+    try:
+        with open(path, 'rb') as f:
+            return f.read()
+    except IOError:
+        raise RuntimeError('bootstrap-mgr keyring not found; run \'gatherkeys\'')
+
+
+def create_mgr(distro, name, cluster, init):
+    conn = distro.conn
+
+    path = '/var/lib/ceph/mgr/{cluster}-{name}'.format(
+        cluster=cluster,
+        name=name
+        )
+
+    conn.remote_module.safe_makedirs(path)
+
+    bootstrap_keyring = '/var/lib/ceph/bootstrap-mgr/{cluster}.keyring'.format(
+        cluster=cluster
+        )
+
+    keypath = os.path.join(path, 'keyring')
+
+    stdout, stderr, returncode = remoto.process.check(
+        conn,
+        [
+            'ceph',
+            '--cluster', cluster,
+            '--name', 'client.bootstrap-mgr',
+            '--keyring', bootstrap_keyring,
+            'auth', 'get-or-create', 'mgr.{name}'.format(name=name),
+            'mon', 'allow profile mgr',
+            'osd', 'allow *',
+            'mds', 'allow *',
+            '-o',
+            os.path.join(keypath),
+        ]
+    )
+    if returncode > 0:
+        for line in stderr:
+            conn.logger.error(line)
+        for line in stdout:
+            # yes stdout as err because this is an error
+            conn.logger.error(line)
+        conn.logger.error('exit code from command was: %s' % returncode)
+        raise RuntimeError('could not create mgr')
+
+    conn.remote_module.touch_file(os.path.join(path, 'done'))
+    conn.remote_module.touch_file(os.path.join(path, init))
+
+    if init == 'upstart':
+        remoto.process.run(
+            conn,
+            [
+                'initctl',
+                'emit',
+                'ceph-mgr',
+                'cluster={cluster}'.format(cluster=cluster),
+                'id={name}'.format(name=name),
+            ],
+            timeout=7
+        )
+    elif init == 'sysvinit':
+        remoto.process.run(
+            conn,
+            [
+                'service',
+                'ceph',
+                'start',
+                'mgr.{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        if distro.is_el:
+            system.enable_service(distro.conn)
+    elif init == 'systemd':
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph-mgr@{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'start',
+                'ceph-mgr@{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph.target',
+            ],
+            timeout=7
+        )
+
+
+
+def mgr_create(args):
+    conf_data = conf.ceph.load_raw(args)
+    LOG.debug(
+        'Deploying mgr, cluster %s hosts %s',
+        args.cluster,
+        ' '.join(':'.join(x or '' for x in t) for t in args.mgr),
+        )
+
+    key = get_bootstrap_mgr_key(cluster=args.cluster)
+
+    bootstrapped = set()
+    errors = 0
+    failed_on_rhel = False
+
+    for hostname, name in args.mgr:
+        try:
+            distro = None
+            distro = hosts.get(hostname, username=args.username)
+            rlogger = distro.conn.logger
+            LOG.info(
+                'Distro info: %s %s %s',
+                distro.name,
+                distro.release,
+                distro.codename
+            )
+
+            LOG.debug('remote host will use %s', distro.init)
+
+            if hostname not in bootstrapped:
+                bootstrapped.add(hostname)
+                LOG.debug('deploying mgr bootstrap to %s', hostname)
+                distro.conn.remote_module.write_conf(
+                    args.cluster,
+                    conf_data,
+                    args.overwrite_conf,
+                )
+
+                path = '/var/lib/ceph/bootstrap-mgr/{cluster}.keyring'.format(
+                    cluster=args.cluster,
+                )
+
+                if not distro.conn.remote_module.path_exists(path):
+                    rlogger.warning('mgr keyring does not exist yet, creating one')
+                    distro.conn.remote_module.write_keyring(path, key)
+
+            create_mgr(distro, name, args.cluster, distro.init)
+            distro.conn.exit()
+        except RuntimeError as e:
+            if distro and distro.normalized_name == 'redhat':
+                LOG.error('this feature may not yet available for %s %s' % (distro.name, distro.release))
+                failed_on_rhel = True
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        if failed_on_rhel:
+            # because users only read the last few lines :(
+            LOG.error(
+                'RHEL RHCS systems do not have the ability to deploy MGR yet'
+            )
+
+        raise exc.GenericError('Failed to create %d MGRs' % errors)
+
+
+def mgr(args):
+    if args.subcommand == 'create':
+        mgr_create(args)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+
+
+def colon_separated(s):
+    host = s
+    name = s
+    if s.count(':') == 1:
+        (host, name) = s.split(':')
+    return (host, name)
+
+
+@priority(30)
+def make(parser):
+    """
+    Ceph MGR daemon management
+    """
+    mgr_parser = parser.add_subparsers(dest='subcommand')
+    mgr_parser.required = True
+
+    mgr_create = mgr_parser.add_parser(
+        'create',
+        help='Deploy Ceph MGR on remote host(s)'
+    )
+    mgr_create.add_argument(
+        'mgr',
+        metavar='HOST[:NAME]',
+        nargs='+',
+        type=colon_separated,
+        help='host (and optionally the daemon name) to deploy on',
+        )
+    parser.set_defaults(
+        func=mgr,
+        )
diff --git a/ceph_deploy/misc.py b/ceph_deploy/misc.py
new file mode 100644 (file)
index 0000000..1620e1f
--- /dev/null
@@ -0,0 +1,22 @@
+
+def mon_hosts(mons):
+    """
+    Iterate through list of MON hosts, return tuples of (name, host).
+    """
+    for m in mons:
+        if m.count(':'):
+            (name, host) = m.split(':')
+        else:
+            name = m
+            host = m
+            if name.count('.') > 0:
+                name = name.split('.')[0]
+        yield (name, host)
+
+def remote_shortname(socket):
+    """
+    Obtains remote hostname of the socket and cuts off the domain part
+    of its FQDN.
+    """
+    return socket.gethostname().split('.', 1)[0]
+
diff --git a/ceph_deploy/mon.py b/ceph_deploy/mon.py
new file mode 100644 (file)
index 0000000..12f4a72
--- /dev/null
@@ -0,0 +1,596 @@
+import json
+import logging
+import re
+import os
+import time
+
+from ceph_deploy import conf, exc, admin
+from ceph_deploy.cliutil import priority
+from ceph_deploy.util.help_formatters import ToggleRawTextHelpFormatter
+from ceph_deploy.util import paths, net, files, packages, system
+from ceph_deploy.lib import remoto
+from ceph_deploy.new import new_mon_keyring
+from ceph_deploy import hosts
+from ceph_deploy.misc import mon_hosts
+from ceph_deploy import gatherkeys
+
+
+LOG = logging.getLogger(__name__)
+
+
+def mon_status_check(conn, logger, hostname, args):
+    """
+    A direct check for JSON output on the monitor status.
+
+    For newer versions of Ceph (dumpling and newer) a new mon_status command
+    was added ( `ceph daemon mon mon_status` ) and should be revisited if the
+    output changes as this check depends on that availability.
+
+    """
+    asok_path = paths.mon.asok(args.cluster, hostname)
+
+    out, err, code = remoto.process.check(
+        conn,
+        [
+            'ceph',
+            '--cluster={cluster}'.format(cluster=args.cluster),
+            '--admin-daemon',
+            asok_path,
+            'mon_status',
+        ],
+    )
+
+    for line in err:
+        logger.error(line)
+
+    try:
+        return json.loads(b''.join(out).decode('utf-8'))
+    except ValueError:
+        return {}
+
+
+def catch_mon_errors(conn, logger, hostname, cfg, args):
+    """
+    Make sure we are able to catch up common mishaps with monitors
+    and use that state of a monitor to determine what is missing
+    and warn apropriately about it.
+    """
+    monmap = mon_status_check(conn, logger, hostname, args).get('monmap', {})
+    mon_initial_members = get_mon_initial_members(args, _cfg=cfg)
+    public_addr = cfg.safe_get('global', 'public_addr')
+    public_network = cfg.safe_get('global', 'public_network')
+    mon_in_monmap = [
+        mon.get('name')
+        for mon in monmap.get('mons', [{}])
+        if mon.get('name') == hostname
+    ]
+    if mon_initial_members is None or not hostname in mon_initial_members:
+            logger.warning('%s is not defined in `mon initial members`', hostname)
+    if not mon_in_monmap:
+        logger.warning('monitor %s does not exist in monmap', hostname)
+        if not public_addr and not public_network:
+            logger.warning('neither `public_addr` nor `public_network` keys are defined for monitors')
+            logger.warning('monitors may not be able to form quorum')
+
+
+def mon_status(conn, logger, hostname, args, silent=False):
+    """
+    run ``ceph daemon mon.`hostname` mon_status`` on the remote end and provide
+    not only the output, but be able to return a boolean status of what is
+    going on.
+    ``False`` represents a monitor that is not doing OK even if it is up and
+    running, while ``True`` would mean the monitor is up and running correctly.
+    """
+    mon = 'mon.%s' % hostname
+
+    try:
+        out = mon_status_check(conn, logger, hostname, args)
+        if not out:
+            logger.warning('monitor: %s, might not be running yet' % mon)
+            return False
+
+        if not silent:
+            logger.debug('*'*80)
+            logger.debug('status for monitor: %s' % mon)
+            for line in json.dumps(out, indent=2, sort_keys=True).split('\n'):
+                logger.debug(line)
+            logger.debug('*'*80)
+        if out['rank'] >= 0:
+            logger.info('monitor: %s is running' % mon)
+            return True
+        if out['rank'] == -1 and out['state']:
+            logger.info('monitor: %s is currently at the state of %s' % (mon, out['state']))
+            return True
+        logger.info('monitor: %s is not running' % mon)
+        return False
+    except RuntimeError:
+        logger.info('monitor: %s is not running' % mon)
+        return False
+
+
+def keyring_parser(path):
+    """
+    This is a very, very, dumb parser that will look for `[entity]` sections
+    and return a list of those sections. It is not possible to parse this with
+    ConfigParser even though it is almost the same thing.
+
+    Since this is only used to spit out warnings, it is OK to just be naive
+    about the parsing.
+    """
+    sections = []
+    with open(path) as keyring:
+        lines = keyring.readlines()
+        for line in lines:
+            line = line.strip('\n')
+            if line.startswith('[') and line.endswith(']'):
+                sections.append(line.strip('[]'))
+    return sections
+
+
+def concatenate_keyrings(args):
+    """
+    A helper to collect all keyrings into a single blob that will be
+    used to inject it to mons with ``--mkfs`` on remote nodes
+
+    We require all keyring files to be concatenated to be in a directory
+    to end with ``.keyring``.
+    """
+    keyring_path = os.path.abspath(args.keyrings)
+    LOG.info('concatenating keyrings from %s' % keyring_path)
+    LOG.info('to seed remote monitors')
+
+    keyrings = [
+        os.path.join(keyring_path, f) for f in os.listdir(keyring_path)
+        if os.path.isfile(os.path.join(keyring_path, f)) and f.endswith('.keyring')
+    ]
+
+    contents = []
+    seen_sections = {}
+
+    if not keyrings:
+        path_from_arg = os.path.abspath(args.keyrings)
+        raise RuntimeError('could not find any keyrings in %s' % path_from_arg)
+
+    for keyring in keyrings:
+        path = os.path.abspath(keyring)
+
+        for section in keyring_parser(path):
+            if not seen_sections.get(section):
+                seen_sections[section] = path
+                LOG.info('adding entity "%s" from keyring %s' % (section, path))
+                with open(path) as k:
+                    contents.append(k.read())
+            else:
+                LOG.warning('will not add keyring: %s' % path)
+                LOG.warning('entity "%s" from keyring %s is a duplicate' % (section, path))
+                LOG.warning('already present in keyring: %s' % seen_sections[section])
+
+    return ''.join(contents)
+
+
+def mon_add(args):
+    cfg = conf.ceph.load(args)
+
+    # args.mon is a list with only one entry
+    mon_host = args.mon[0]
+
+    try:
+        with open('{cluster}.mon.keyring'.format(cluster=args.cluster),
+                  'rb') as f:
+            monitor_keyring = f.read()
+    except IOError:
+        raise RuntimeError(
+            'mon keyring not found; run \'new\' to create a new cluster'
+        )
+
+    LOG.info('ensuring configuration of new mon host: %s', mon_host)
+    args.client = args.mon
+    admin.admin(args)
+    LOG.debug(
+        'Adding mon to cluster %s, host %s',
+        args.cluster,
+        mon_host,
+    )
+
+    mon_section = 'mon.%s' % mon_host
+    cfg_mon_addr = cfg.safe_get(mon_section, 'mon addr')
+
+    if args.address:
+        LOG.debug('using mon address via --address %s' % args.address)
+        mon_ip = args.address
+    elif cfg_mon_addr:
+        LOG.debug('using mon address via configuration: %s' % cfg_mon_addr)
+        mon_ip = cfg_mon_addr
+    else:
+        mon_ip = net.get_nonlocal_ip(mon_host)
+        LOG.debug('using mon address by resolving host: %s' % mon_ip)
+
+    try:
+        LOG.debug('detecting platform for host %s ...', mon_host)
+        distro = hosts.get(
+            mon_host,
+            username=args.username,
+            callbacks=[packages.ceph_is_installed]
+        )
+        LOG.info('distro info: %s %s %s', distro.name, distro.release, distro.codename)
+        rlogger = logging.getLogger(mon_host)
+
+        # ensure remote hostname is good to go
+        hostname_is_compatible(distro.conn, rlogger, mon_host)
+        rlogger.debug('adding mon to %s', mon_host)
+        args.address = mon_ip
+        distro.mon.add(distro, args, monitor_keyring)
+
+        # tell me the status of the deployed mon
+        time.sleep(2)  # give some room to start
+        catch_mon_errors(distro.conn, rlogger, mon_host, cfg, args)
+        mon_status(distro.conn, rlogger, mon_host, args)
+        distro.conn.exit()
+
+    except RuntimeError as e:
+        LOG.error(e)
+        raise exc.GenericError('Failed to add monitor to host:  %s' % mon_host)
+
+
+def mon_create(args):
+
+    cfg = conf.ceph.load(args)
+    if not args.mon:
+        args.mon = get_mon_initial_members(args, error_on_empty=True, _cfg=cfg)
+
+    if args.keyrings:
+        monitor_keyring = concatenate_keyrings(args)
+    else:
+        keyring_path = '{cluster}.mon.keyring'.format(cluster=args.cluster)
+        try:
+            monitor_keyring = files.read_file(keyring_path)
+        except IOError:
+            LOG.warning('keyring (%s) not found, creating a new one' % keyring_path)
+            new_mon_keyring(args)
+            monitor_keyring = files.read_file(keyring_path)
+
+    LOG.debug(
+        'Deploying mon, cluster %s hosts %s',
+        args.cluster,
+        ' '.join(args.mon),
+        )
+
+    errors = 0
+    for (name, host) in mon_hosts(args.mon):
+        try:
+            # TODO add_bootstrap_peer_hint
+            LOG.debug('detecting platform for host %s ...', name)
+            distro = hosts.get(
+                host,
+                username=args.username,
+                callbacks=[packages.ceph_is_installed]
+            )
+            LOG.info('distro info: %s %s %s', distro.name, distro.release, distro.codename)
+            rlogger = logging.getLogger(name)
+
+            # ensure remote hostname is good to go
+            hostname_is_compatible(distro.conn, rlogger, name)
+            rlogger.debug('deploying mon to %s', name)
+            distro.mon.create(distro, args, monitor_keyring)
+
+            # tell me the status of the deployed mon
+            time.sleep(2)  # give some room to start
+            mon_status(distro.conn, rlogger, name, args)
+            catch_mon_errors(distro.conn, rlogger, name, cfg, args)
+            distro.conn.exit()
+
+        except RuntimeError as e:
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        raise exc.GenericError('Failed to create %d monitors' % errors)
+
+
+def hostname_is_compatible(conn, logger, provided_hostname):
+    """
+    Make sure that the host that we are connecting to has the same value as the
+    `hostname` in the remote host, otherwise mons can fail not reaching quorum.
+    """
+    logger.debug('determining if provided host has same hostname in remote')
+    remote_hostname = conn.remote_module.shortname()
+    if remote_hostname == provided_hostname:
+        return
+    logger.warning('*'*80)
+    logger.warning('provided hostname must match remote hostname')
+    logger.warning('provided hostname: %s' % provided_hostname)
+    logger.warning('remote hostname: %s' % remote_hostname)
+    logger.warning('monitors may not reach quorum and create-keys will not complete')
+    logger.warning('*'*80)
+
+
+def destroy_mon(conn, cluster, hostname):
+    import datetime
+    import time
+    retries = 5
+
+    path = paths.mon.path(cluster, hostname)
+
+    if conn.remote_module.path_exists(path):
+        # remove from cluster
+        remoto.process.run(
+            conn,
+            [
+                'ceph',
+                '--cluster={cluster}'.format(cluster=cluster),
+                '-n', 'mon.',
+                '-k', '{path}/keyring'.format(path=path),
+                'mon',
+                'remove',
+                hostname,
+            ],
+            timeout=7,
+        )
+
+        # stop
+        if conn.remote_module.path_exists(os.path.join(path, 'upstart')) or system.is_upstart(conn):
+            status_args = [
+                'initctl',
+                'status',
+                'ceph-mon',
+                'cluster={cluster}'.format(cluster=cluster),
+                'id={hostname}'.format(hostname=hostname),
+            ]
+
+        elif conn.remote_module.path_exists(os.path.join(path, 'sysvinit')):
+            status_args = [
+                'service',
+                'ceph',
+                'status',
+                'mon.{hostname}'.format(hostname=hostname),
+            ]
+        elif system.is_systemd(conn):
+            status_args = [
+                'systemctl',
+                'stop',
+                'ceph-mon@{hostname}.service'.format(hostname=hostname),
+            ]
+        else:
+            raise RuntimeError('could not detect a supported init system, cannot continue')
+
+        while retries:
+            conn.logger.info('polling the daemon to verify it stopped')
+            if is_running(conn, status_args):
+                time.sleep(5)
+                retries -= 1
+                if retries <= 0:
+                    raise RuntimeError('ceph-mon deamon did not stop')
+            else:
+                break
+
+        # archive old monitor directory
+        fn = '{cluster}-{hostname}-{stamp}'.format(
+            hostname=hostname,
+            cluster=cluster,
+            stamp=datetime.datetime.utcnow().strftime("%Y-%m-%dZ%H:%M:%S"),
+            )
+
+        remoto.process.run(
+            conn,
+            [
+                'mkdir',
+                '-p',
+                '/var/lib/ceph/mon-removed',
+            ],
+        )
+
+        conn.remote_module.make_mon_removed_dir(path, fn)
+
+
+def mon_destroy(args):
+    errors = 0
+    for (name, host) in mon_hosts(args.mon):
+        try:
+            LOG.debug('Removing mon from %s', name)
+
+            distro = hosts.get(
+                host,
+                username=args.username,
+                callbacks=[packages.ceph_is_installed]
+            )
+            hostname = distro.conn.remote_module.shortname()
+
+            destroy_mon(
+                distro.conn,
+                args.cluster,
+                hostname,
+            )
+            distro.conn.exit()
+
+        except RuntimeError as e:
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        raise exc.GenericError('Failed to destroy %d monitors' % errors)
+
+
+def mon_create_initial(args):
+    mon_initial_members = get_mon_initial_members(args, error_on_empty=True)
+
+    # create them normally through mon_create
+    args.mon = mon_initial_members
+    mon_create(args)
+
+    # make the sets to be able to compare late
+    mon_in_quorum = set([])
+    mon_members = set([host for host in mon_initial_members])
+
+    for host in mon_initial_members:
+        mon_name = 'mon.%s' % host
+        LOG.info('processing monitor %s', mon_name)
+        sleeps = [20, 20, 15, 10, 10, 5]
+        tries = 5
+        rlogger = logging.getLogger(host)
+        distro = hosts.get(
+            host,
+            username=args.username,
+            callbacks=[packages.ceph_is_installed]
+        )
+
+        while tries:
+            status = mon_status_check(distro.conn, rlogger, host, args)
+            has_reached_quorum = status.get('state', '') in ['peon', 'leader']
+            if not has_reached_quorum:
+                LOG.warning('%s monitor is not yet in quorum, tries left: %s' % (mon_name, tries))
+                tries -= 1
+                sleep_seconds = sleeps.pop()
+                LOG.warning('waiting %s seconds before retrying', sleep_seconds)
+                time.sleep(sleep_seconds)  # Magic number
+            else:
+                mon_in_quorum.add(host)
+                LOG.info('%s monitor has reached quorum!', mon_name)
+                break
+        distro.conn.exit()
+
+    if mon_in_quorum == mon_members:
+        LOG.info('all initial monitors are running and have formed quorum')
+        LOG.info('Running gatherkeys...')
+        gatherkeys.gatherkeys(args)
+    else:
+        LOG.error('Some monitors have still not reached quorum:')
+        for host in mon_members - mon_in_quorum:
+            LOG.error('%s', host)
+        raise SystemExit('cluster may not be in a healthy state')
+
+
+def mon(args):
+    if args.subcommand == 'create':
+        mon_create(args)
+    elif args.subcommand == 'add':
+        mon_add(args)
+    elif args.subcommand == 'destroy':
+        mon_destroy(args)
+    elif args.subcommand == 'create-initial':
+        mon_create_initial(args)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+
+
+@priority(30)
+def make(parser):
+    """
+    Ceph MON Daemon management
+    """
+    parser.formatter_class = ToggleRawTextHelpFormatter
+
+    mon_parser = parser.add_subparsers(dest='subcommand')
+    mon_parser.required = True
+
+    mon_add = mon_parser.add_parser(
+        'add',
+        help=('R|Add a monitor to an existing cluster:\n'
+              '\tceph-deploy mon add node1\n'
+              'Or:\n'
+              '\tceph-deploy mon add --address 192.168.1.10 node1\n'
+              'If the section for the monitor exists and defines a `mon addr` that\n'
+              'will be used, otherwise it will fallback by resolving the hostname to an\n'
+              'IP. If `--address` is used it will override all other options.')
+    )
+    mon_add.add_argument(
+        '--address',
+        nargs='?',
+    )
+    mon_add.add_argument(
+        'mon',
+        nargs=1,
+    )
+
+    mon_create = mon_parser.add_parser(
+        'create',
+        help=('R|Deploy monitors by specifying them like:\n'
+              '\tceph-deploy mon create node1 node2 node3\n'
+              'If no hosts are passed it will default to use the\n'
+              '`mon initial members` defined in the configuration.')
+    )
+    mon_create.add_argument(
+        '--keyrings',
+        nargs='?',
+        help='concatenate multiple keyrings to be seeded on new monitors',
+    )
+    mon_create.add_argument(
+        'mon',
+        nargs='*',
+    )
+
+    mon_create_initial = mon_parser.add_parser(
+        'create-initial',
+        help=('Will deploy for monitors defined in `mon initial members`, '
+              'wait until they form quorum and then gatherkeys, reporting '
+              'the monitor status along the process. If monitors don\'t form '
+              'quorum the command will eventually time out.')
+    )
+    mon_create_initial.add_argument(
+        '--keyrings',
+        nargs='?',
+        help='concatenate multiple keyrings to be seeded on new monitors',
+    )
+
+    mon_destroy = mon_parser.add_parser(
+        'destroy',
+        help='Completely remove Ceph MON from remote host(s)'
+    )
+    mon_destroy.add_argument(
+        'mon',
+        nargs='+',
+    )
+
+    parser.set_defaults(
+        func=mon,
+    )
+
+#
+# Helpers
+#
+
+
+def get_mon_initial_members(args, error_on_empty=False, _cfg=None):
+    """
+    Read the Ceph config file and return the value of mon_initial_members
+    Optionally, a NeedHostError can be raised if the value is None.
+    """
+    if _cfg:
+        cfg = _cfg
+    else:
+        cfg = conf.ceph.load(args)
+    mon_initial_members = cfg.safe_get('global', 'mon_initial_members')
+    if not mon_initial_members:
+        if error_on_empty:
+            raise exc.NeedHostError(
+                'could not find `mon initial members` defined in ceph.conf'
+            )
+    else:
+        mon_initial_members = re.split(r'[,\s]+', mon_initial_members)
+    return mon_initial_members
+
+
+def is_running(conn, args):
+    """
+    Run a command to check the status of a mon, return a boolean.
+
+    We heavily depend on the format of the output, if that ever changes
+    we need to modify this.
+    Check daemon status for 3 times
+    output of the status should be similar to::
+
+        mon.mira094: running {"version":"0.61.5"}
+
+    or when it fails::
+
+        mon.mira094: dead {"version":"0.61.5"}
+        mon.mira094: not running {"version":"0.61.5"}
+    """
+    stdout, stderr, _ = remoto.process.check(
+        conn,
+        args
+    )
+    result_string = b' '.join(stdout)
+    for run_check in [b': running', b' start/running']:
+        if run_check in result_string:
+            return True
+    return False
diff --git a/ceph_deploy/new.py b/ceph_deploy/new.py
new file mode 100644 (file)
index 0000000..842117b
--- /dev/null
@@ -0,0 +1,276 @@
+import errno
+import logging
+import os
+import uuid
+import struct
+import time
+import base64
+import socket
+
+from ceph_deploy.cliutil import priority
+from ceph_deploy import conf, hosts, exc
+from ceph_deploy.util import arg_validators, ssh, net
+from ceph_deploy.misc import mon_hosts
+from ceph_deploy.lib import remoto
+from ceph_deploy.connection import get_local_connection
+
+
+LOG = logging.getLogger(__name__)
+
+
+def generate_auth_key():
+    key = os.urandom(16)
+    header = struct.pack(
+        '<hiih',
+        1,                 # le16 type: CEPH_CRYPTO_AES
+        int(time.time()),  # le32 created: seconds
+        0,                 # le32 created: nanoseconds,
+        len(key),          # le16: len(key)
+    )
+    return base64.b64encode(header + key).decode('utf-8')
+
+
+def ssh_copy_keys(hostname, username=None):
+    LOG.info('making sure passwordless SSH succeeds')
+    if ssh.can_connect_passwordless(hostname):
+        return
+
+    LOG.warning('could not connect via SSH')
+
+    # Create the key if it doesn't exist:
+    id_rsa_pub_file = os.path.expanduser(u'~/.ssh/id_rsa.pub')
+    id_rsa_file = id_rsa_pub_file.split('.pub')[0]
+    if not os.path.exists(id_rsa_file):
+        LOG.info('creating a passwordless id_rsa.pub key file')
+        with get_local_connection(LOG) as conn:
+            remoto.process.run(
+                conn,
+                [
+                    'ssh-keygen',
+                    '-t',
+                    'rsa',
+                    '-N',
+                    "",
+                    '-f',
+                    id_rsa_file,
+                ]
+            )
+
+    # Get the contents of id_rsa.pub and push it to the host
+    LOG.info('will connect again with password prompt')
+    distro = hosts.get(hostname, username, detect_sudo=False)
+    auth_keys_path = '.ssh/authorized_keys'
+    if not distro.conn.remote_module.path_exists(auth_keys_path):
+        distro.conn.logger.warning(
+            '.ssh/authorized_keys does not exist, will skip adding keys'
+        )
+    else:
+        LOG.info('adding public keys to authorized_keys')
+        with open(os.path.expanduser('~/.ssh/id_rsa.pub'), 'r') as id_rsa:
+            contents = id_rsa.read()
+        distro.conn.remote_module.append_to_file(
+            auth_keys_path,
+            contents
+        )
+    distro.conn.exit()
+
+
+def validate_host_ip(ips, subnets):
+    """
+    Make sure that a given host all subnets specified will have at least one IP
+    in that range.
+    """
+    # Make sure we prune ``None`` arguments
+    subnets = [s for s in subnets if s is not None]
+    validate_one_subnet = len(subnets) == 1
+
+    def ip_in_one_subnet(ips, subnet):
+        """ ensure an ip exists in at least one subnet """
+        for ip in ips:
+            if net.ip_in_subnet(ip, subnet):
+                return True
+        return False
+
+    for subnet in subnets:
+        if ip_in_one_subnet(ips, subnet):
+            if validate_one_subnet:
+                return
+            else:  # keep going to make sure the other subnets are ok
+                continue
+        else:
+            msg = "subnet (%s) is not valid for any of the ips found %s" % (subnet, str(ips))
+            raise RuntimeError(msg)
+
+
+def get_public_network_ip(ips, public_subnet):
+    """
+    Given a public subnet, chose the one IP from the remote host that exists
+    within the subnet range.
+    """
+    for ip in ips:
+        if net.ip_in_subnet(ip, public_subnet):
+            return ip
+    msg = "IPs (%s) are not valid for any of subnet specified %s" % (str(ips), str(public_subnet))
+    raise RuntimeError(msg)
+
+
+def new(args):
+    if args.ceph_conf:
+        raise RuntimeError('will not create a Ceph conf file if attemtping to re-use with `--ceph-conf` flag')
+    LOG.debug('Creating new cluster named %s', args.cluster)
+    cfg = conf.ceph.CephConf()
+    cfg.add_section('global')
+
+    fsid = args.fsid or uuid.uuid4()
+    cfg.set('global', 'fsid', str(fsid))
+
+    # if networks were passed in, lets set them in the
+    # global section
+    if args.public_network:
+        cfg.set('global', 'public network', str(args.public_network))
+
+    if args.cluster_network:
+        cfg.set('global', 'cluster network', str(args.cluster_network))
+
+    mon_initial_members = []
+    mon_host = []
+
+    for (name, host) in mon_hosts(args.mon):
+        # Try to ensure we can ssh in properly before anything else
+        if args.ssh_copykey:
+            ssh_copy_keys(host, args.username)
+
+        # Now get the non-local IPs from the remote node
+        distro = hosts.get(host, username=args.username)
+        remote_ips = net.ip_addresses(distro.conn)
+
+        # custom cluster names on sysvinit hosts won't work
+        if distro.init == 'sysvinit' and args.cluster != 'ceph':
+            LOG.error('custom cluster names are not supported on sysvinit hosts')
+            raise exc.ClusterNameError(
+                'host %s does not support custom cluster names' % host
+            )
+
+        distro.conn.exit()
+
+        # Validate subnets if we received any
+        if args.public_network or args.cluster_network:
+            validate_host_ip(remote_ips, [args.public_network, args.cluster_network])
+
+        # Pick the IP that matches the public cluster (if we were told to do
+        # so) otherwise pick the first, non-local IP
+        LOG.debug('Resolving host %s', host)
+        if args.public_network:
+            ip = get_public_network_ip(remote_ips, args.public_network)
+        else:
+            ip = net.get_nonlocal_ip(host)
+        LOG.debug('Monitor %s at %s', name, ip)
+        mon_initial_members.append(name)
+        try:
+            socket.inet_pton(socket.AF_INET6, ip)
+            mon_host.append("[" + ip + "]")
+            LOG.info('Monitors are IPv6, binding Messenger traffic on IPv6')
+            cfg.set('global', 'ms bind ipv6', 'true')
+        except socket.error:
+            mon_host.append(ip)
+
+
+
+    LOG.debug('Monitor initial members are %s', mon_initial_members)
+    LOG.debug('Monitor addrs are %s', mon_host)
+
+    cfg.set('global', 'mon initial members', ', '.join(mon_initial_members))
+    # no spaces here, see http://tracker.newdream.net/issues/3145
+    cfg.set('global', 'mon host', ','.join(mon_host))
+
+    # override undesirable defaults, needed until bobtail
+
+    # http://tracker.ceph.com/issues/6788
+    cfg.set('global', 'auth cluster required', 'cephx')
+    cfg.set('global', 'auth service required', 'cephx')
+    cfg.set('global', 'auth client required', 'cephx')
+
+    path = '{name}.conf'.format(
+        name=args.cluster,
+        )
+
+    new_mon_keyring(args)
+
+    LOG.debug('Writing initial config to %s...', path)
+    tmp = '%s.tmp' % path
+    with open(tmp, 'w') as f:
+        cfg.write(f)
+    try:
+        os.rename(tmp, path)
+    except OSError as e:
+        if e.errno == errno.EEXIST:
+            raise exc.ClusterExistsError(path)
+        else:
+            raise
+
+
+def new_mon_keyring(args):
+    LOG.debug('Creating a random mon key...')
+    mon_keyring = '[mon.]\nkey = %s\ncaps mon = allow *\n' % generate_auth_key()
+
+    keypath = '{name}.mon.keyring'.format(
+        name=args.cluster,
+        )
+    oldmask = os.umask(0o77)
+    LOG.debug('Writing monitor keyring to %s...', keypath)
+    try:
+        tmp = '%s.tmp' % keypath
+        with open(tmp, 'w', 0o600) as f:
+            f.write(mon_keyring)
+        try:
+            os.rename(tmp, keypath)
+        except OSError as e:
+            if e.errno == errno.EEXIST:
+                raise exc.ClusterExistsError(keypath)
+            else:
+                raise
+    finally:
+        os.umask(oldmask)
+
+
+@priority(10)
+def make(parser):
+    """
+    Start deploying a new cluster, and write a CLUSTER.conf and keyring for it.
+    """
+    parser.add_argument(
+        'mon',
+        metavar='MON',
+        nargs='+',
+        help='initial monitor hostname, fqdn, or hostname:fqdn pair',
+        type=arg_validators.Hostname(),
+        )
+    parser.add_argument(
+        '--no-ssh-copykey',
+        dest='ssh_copykey',
+        action='store_false',
+        default=True,
+        help='do not attempt to copy SSH keys',
+    )
+
+    parser.add_argument(
+        '--fsid',
+        dest='fsid',
+        help='provide an alternate FSID for ceph.conf generation',
+    )
+
+    parser.add_argument(
+        '--cluster-network',
+        help='specify the (internal) cluster network',
+        type=arg_validators.Subnet(),
+    )
+
+    parser.add_argument(
+        '--public-network',
+        help='specify the public network for a cluster',
+        type=arg_validators.Subnet(),
+    )
+
+    parser.set_defaults(
+        func=new,
+        )
diff --git a/ceph_deploy/osd.py b/ceph_deploy/osd.py
new file mode 100644 (file)
index 0000000..6e7b3bd
--- /dev/null
@@ -0,0 +1,610 @@
+import argparse
+import json
+import logging
+import sys
+import time
+from textwrap import dedent
+
+from ceph_deploy import conf, exc, hosts
+from ceph_deploy.util import system, packages
+from ceph_deploy.cliutil import priority
+from ceph_deploy.lib import remoto
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_bootstrap_osd_key(cluster):
+    """
+    Read the bootstrap-osd key for `cluster`.
+    """
+    path = '{cluster}.bootstrap-osd.keyring'.format(cluster=cluster)
+    try:
+        with open(path, 'rb') as f:
+            return f.read()
+    except IOError:
+        raise RuntimeError('bootstrap-osd keyring not found; run \'gatherkeys\'')
+
+
+def create_osd_keyring(conn, cluster, key):
+    """
+    Run on osd node, writes the bootstrap key if not there yet.
+    """
+    logger = conn.logger
+    path = '/var/lib/ceph/bootstrap-osd/{cluster}.keyring'.format(
+        cluster=cluster,
+    )
+    if not conn.remote_module.path_exists(path):
+        logger.warning('osd keyring does not exist yet, creating one')
+        conn.remote_module.write_keyring(path, key)
+
+
+def osd_tree(conn, cluster):
+    """
+    Check the status of an OSD. Make sure all are up and in
+
+    What good output would look like::
+
+        {
+            "epoch": 8,
+            "num_osds": 1,
+            "num_up_osds": 1,
+            "num_in_osds": "1",
+            "full": "false",
+            "nearfull": "false"
+        }
+
+    Note how the booleans are actually strings, so we need to take that into
+    account and fix it before returning the dictionary. Issue #8108
+    """
+    ceph_executable = system.executable_path(conn, 'ceph')
+    command = [
+        ceph_executable,
+        '--cluster={cluster}'.format(cluster=cluster),
+        'osd',
+        'tree',
+        '--format=json',
+    ]
+
+    out, err, code = remoto.process.check(
+        conn,
+        command,
+    )
+
+    try:
+        loaded_json = json.loads(b''.join(out).decode('utf-8'))
+        # convert boolean strings to actual booleans because
+        # --format=json fails to do this properly
+        for k, v in loaded_json.items():
+            if v == 'true':
+                loaded_json[k] = True
+            elif v == 'false':
+                loaded_json[k] = False
+        return loaded_json
+    except ValueError:
+        return {}
+
+
+def osd_status_check(conn, cluster):
+    """
+    Check the status of an OSD. Make sure all are up and in
+
+    What good output would look like::
+
+        {
+            "epoch": 8,
+            "num_osds": 1,
+            "num_up_osds": 1,
+            "num_in_osds": "1",
+            "full": "false",
+            "nearfull": "false"
+        }
+
+    Note how the booleans are actually strings, so we need to take that into
+    account and fix it before returning the dictionary. Issue #8108
+    """
+    ceph_executable = system.executable_path(conn, 'ceph')
+    command = [
+        ceph_executable,
+        '--cluster={cluster}'.format(cluster=cluster),
+        'osd',
+        'stat',
+        '--format=json',
+    ]
+
+    try:
+        out, err, code = remoto.process.check(
+            conn,
+            command,
+        )
+    except TypeError:
+        # XXX This is a bug in remoto. If the other end disconnects with a timeout
+        # it will return a None, and here we are expecting a 3 item tuple, not a None
+        # so it will break with a TypeError. Once remoto fixes this, we no longer need
+        # this try/except.
+        return {}
+
+    try:
+        loaded_json = json.loads(b''.join(out).decode('utf-8'))
+        # convert boolean strings to actual booleans because
+        # --format=json fails to do this properly
+        for k, v in loaded_json.items():
+            if v == 'true':
+                loaded_json[k] = True
+            elif v == 'false':
+                loaded_json[k] = False
+        return loaded_json
+    except ValueError:
+        return {}
+
+
+def catch_osd_errors(conn, logger, args):
+    """
+    Look for possible issues when checking the status of an OSD and
+    report them back to the user.
+    """
+    logger.info('checking OSD status...')
+    status = osd_status_check(conn, args.cluster)
+    osds = int(status.get('num_osds', 0))
+    up_osds = int(status.get('num_up_osds', 0))
+    in_osds = int(status.get('num_in_osds', 0))
+    full = status.get('full', False)
+    nearfull = status.get('nearfull', False)
+
+    if osds > up_osds:
+        difference = osds - up_osds
+        logger.warning('there %s %d OSD%s down' % (
+            ['is', 'are'][difference != 1],
+            difference,
+            "s"[difference == 1:])
+        )
+
+    if osds > in_osds:
+        difference = osds - in_osds
+        logger.warning('there %s %d OSD%s out' % (
+            ['is', 'are'][difference != 1],
+            difference,
+            "s"[difference == 1:])
+        )
+
+    if full:
+        logger.warning('OSDs are full!')
+
+    if nearfull:
+        logger.warning('OSDs are near full!')
+
+
+def create_osd(
+        conn,
+        cluster,
+        data,
+        journal,
+        zap,
+        fs_type,
+        dmcrypt,
+        dmcrypt_dir,
+        storetype,
+        block_wal,
+        block_db,
+        **kw):
+    """
+    Run on osd node, creates an OSD from a data disk.
+    """
+    ceph_volume_executable = system.executable_path(conn, 'ceph-volume')
+    args = [
+        ceph_volume_executable,
+        '--cluster', cluster,
+        'lvm',
+        'create',
+        '--%s' % storetype,
+        '--data', data
+    ]
+    if zap:
+        LOG.warning('zapping is no longer supported when preparing')
+    if dmcrypt:
+        args.append('--dmcrypt')
+        # TODO: re-enable dmcrypt support once ceph-volume grows it
+        LOG.warning('dmcrypt is currently not supported')
+
+    if storetype == 'bluestore':
+        if block_wal:
+            args.append('--block.wal')
+            args.append(block_wal)
+        if block_db:
+            args.append('--block.db')
+            args.append(block_db)
+    elif storetype == 'filestore':
+        if not journal:
+            raise RuntimeError('A journal lv or GPT partition must be specified when using filestore')
+        args.append('--journal')
+        args.append(journal)
+
+    if kw.get('debug'):
+        remoto.process.run(
+            conn,
+            args,
+            extend_env={'CEPH_VOLUME_DEBUG': '1'}
+        )
+
+    else:
+        remoto.process.run(
+            conn,
+            args
+        )
+
+
+def create(args, cfg, create=False):
+    if not args.host:
+        raise RuntimeError('Required host was not specified as a positional argument')
+    LOG.debug(
+        'Creating OSD on cluster %s with data device %s',
+        args.cluster,
+        args.data
+        )
+
+    key = get_bootstrap_osd_key(cluster=args.cluster)
+
+    bootstrapped = set()
+    errors = 0
+    hostname = args.host
+
+    try:
+        if args.data is None:
+            raise exc.NeedDiskError(hostname)
+
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            callbacks=[packages.ceph_is_installed]
+        )
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        if hostname not in bootstrapped:
+            bootstrapped.add(hostname)
+            LOG.debug('Deploying osd to %s', hostname)
+
+            conf_data = conf.ceph.load_raw(args)
+            distro.conn.remote_module.write_conf(
+                args.cluster,
+                conf_data,
+                args.overwrite_conf
+            )
+
+            create_osd_keyring(distro.conn, args.cluster, key)
+
+        # default to bluestore unless explicitly told not to
+        storetype = 'bluestore'
+        if args.filestore:
+            storetype = 'filestore'
+
+        create_osd(
+            distro.conn,
+            cluster=args.cluster,
+            data=args.data,
+            journal=args.journal,
+            zap=args.zap_disk,
+            fs_type=args.fs_type,
+            dmcrypt=args.dmcrypt,
+            dmcrypt_dir=args.dmcrypt_key_dir,
+            storetype=storetype,
+            block_wal=args.block_wal,
+            block_db=args.block_db,
+            debug=args.debug,
+        )
+
+        # give the OSD a few seconds to start
+        time.sleep(5)
+        catch_osd_errors(distro.conn, distro.conn.logger, args)
+        LOG.debug('Host %s is now ready for osd use.', hostname)
+        distro.conn.exit()
+
+    except RuntimeError as e:
+        LOG.error(e)
+        errors += 1
+
+    if errors:
+        raise exc.GenericError('Failed to create %d OSDs' % errors)
+
+
+def disk_zap(args):
+
+    hostname = args.host
+    for disk in args.disk:
+        if not disk or not hostname:
+            raise RuntimeError('zap command needs both HOSTNAME and DISK but got "%s %s"' % (hostname, disk))
+        LOG.debug('zapping %s on %s', disk, hostname)
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            callbacks=[packages.ceph_is_installed]
+        )
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        distro.conn.remote_module.zeroing(disk)
+
+        ceph_volume_executable = system.executable_path(distro.conn, 'ceph-volume')
+        if args.debug:
+            remoto.process.run(
+                distro.conn,
+                [
+                    ceph_volume_executable,
+                    'lvm',
+                    'zap',
+                    disk,
+                ],
+                env={'CEPH_VOLUME_DEBUG': '1'}
+            )
+        else:
+            remoto.process.run(
+                distro.conn,
+                [
+                    ceph_volume_executable,
+                    'lvm',
+                    'zap',
+                    disk,
+                ],
+            )
+
+        distro.conn.exit()
+
+
+def disk_list(args, cfg):
+    command = ['fdisk', '-l']
+
+    for hostname in args.host:
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            callbacks=[packages.ceph_is_installed]
+        )
+        out, err, code = remoto.process.check(
+            distro.conn,
+            command,
+        )
+        for line in out:
+            if line.startswith('Disk /'):
+                distro.conn.logger.info(line)
+
+
+def osd_list(args, cfg):
+    for hostname in args.host:
+        distro = hosts.get(
+            hostname,
+            username=args.username,
+            callbacks=[packages.ceph_is_installed]
+        )
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        LOG.debug('Listing disks on {hostname}...'.format(hostname=hostname))
+        ceph_volume_executable = system.executable_path(distro.conn, 'ceph-volume')
+        if args.debug:
+            remoto.process.run(
+                distro.conn,
+                [
+                    ceph_volume_executable,
+                    'lvm',
+                    'list',
+                ],
+                env={'CEPH_VOLUME_DEBUG': '1'}
+
+            )
+        else:
+            remoto.process.run(
+                distro.conn,
+                [
+                    ceph_volume_executable,
+                    'lvm',
+                    'list',
+                ],
+            )
+        distro.conn.exit()
+
+
+def osd(args):
+    cfg = conf.ceph.load(args)
+
+    if args.subcommand == 'list':
+        osd_list(args, cfg)
+    elif args.subcommand == 'create':
+        create(args, cfg)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+        sys.exit(1)
+
+
+def disk(args):
+    cfg = conf.ceph.load(args)
+
+    if args.subcommand == 'list':
+        disk_list(args, cfg)
+    elif args.subcommand == 'create':
+        create(args, cfg)
+    elif args.subcommand == 'zap':
+        disk_zap(args)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+        sys.exit(1)
+
+
+@priority(50)
+def make(parser):
+    """
+    Prepare a data disk on remote host.
+    """
+    sub_command_help = dedent("""
+    Create OSDs from a data disk on a remote host:
+
+        ceph-deploy osd create {node} --data /path/to/device
+
+    For bluestore, optional devices can be used::
+
+        ceph-deploy osd create {node} --data /path/to/data --block-db /path/to/db-device
+        ceph-deploy osd create {node} --data /path/to/data --block-wal /path/to/wal-device
+        ceph-deploy osd create {node} --data /path/to/data --block-db /path/to/db-device --block-wal /path/to/wal-device
+
+    For filestore, the journal must be specified, as well as the objectstore::
+
+        ceph-deploy osd create {node} --filestore --data /path/to/data --journal /path/to/journal
+
+    For data devices, it can be an existing logical volume in the format of:
+    vg/lv, or a device. For other OSD components like wal, db, and journal, it
+    can be logical volume (in vg/lv format) or it must be a GPT partition.
+    """
+    )
+    parser.formatter_class = argparse.RawDescriptionHelpFormatter
+    parser.description = sub_command_help
+
+    osd_parser = parser.add_subparsers(dest='subcommand')
+    osd_parser.required = True
+
+    osd_list = osd_parser.add_parser(
+        'list',
+        help='List OSD info from remote host(s)'
+        )
+    osd_list.add_argument(
+        'host',
+        nargs='+',
+        metavar='HOST',
+        help='remote host(s) to list OSDs from'
+        )
+    osd_list.add_argument(
+        '--debug',
+        action='store_true',
+        help='Enable debug mode on remote ceph-volume calls',
+        )
+    osd_create = osd_parser.add_parser(
+        'create',
+        help='Create new Ceph OSD daemon by preparing and activating a device'
+    )
+    osd_create.add_argument(
+        '--data',
+        metavar='DATA',
+        help='The OSD data logical volume (vg/lv) or absolute path to device'
+    )
+    osd_create.add_argument(
+        '--journal',
+        help='Logical Volume (vg/lv) or path to GPT partition',
+        )
+    osd_create.add_argument(
+        '--zap-disk',
+        action='store_true',
+        help='DEPRECATED - cannot zap when creating an OSD'
+    )
+    osd_create.add_argument(
+        '--fs-type',
+        metavar='FS_TYPE',
+        choices=['xfs',
+                 'btrfs'
+                 ],
+        default='xfs',
+        help='filesystem to use to format DEVICE (xfs, btrfs)',
+        )
+    osd_create.add_argument(
+        '--dmcrypt',
+        action='store_true',
+        help='use dm-crypt on DEVICE',
+        )
+    osd_create.add_argument(
+        '--dmcrypt-key-dir',
+        metavar='KEYDIR',
+        default='/etc/ceph/dmcrypt-keys',
+        help='directory where dm-crypt keys are stored',
+        )
+    osd_create.add_argument(
+        '--filestore',
+        action='store_true', default=None,
+        help='filestore objectstore',
+        )
+    osd_create.add_argument(
+        '--bluestore',
+        action='store_true', default=None,
+        help='bluestore objectstore',
+        )
+    osd_create.add_argument(
+        '--block-db',
+        default=None,
+        help='bluestore block.db path'
+        )
+    osd_create.add_argument(
+        '--block-wal',
+        default=None,
+        help='bluestore block.wal path'
+        )
+    osd_create.add_argument(
+        'host',
+        nargs='?',
+        metavar='HOST',
+        help='Remote host to connect'
+        )
+    osd_create.add_argument(
+        '--debug',
+        action='store_true',
+        help='Enable debug mode on remote ceph-volume calls',
+        )
+    parser.set_defaults(
+        func=osd,
+        )
+
+
+@priority(50)
+def make_disk(parser):
+    """
+    Manage disks on a remote host.
+    """
+    disk_parser = parser.add_subparsers(dest='subcommand')
+    disk_parser.required = True
+
+    disk_zap = disk_parser.add_parser(
+        'zap',
+        help='destroy existing data and filesystem on LV or partition',
+        )
+    disk_zap.add_argument(
+        'host',
+        nargs='?',
+        metavar='HOST',
+        help='Remote HOST(s) to connect'
+        )
+    disk_zap.add_argument(
+        'disk',
+        nargs='+',
+        metavar='DISK',
+        help='Disk(s) to zap'
+        )
+    disk_zap.add_argument(
+        '--debug',
+        action='store_true',
+        help='Enable debug mode on remote ceph-volume calls',
+        )
+    disk_list = disk_parser.add_parser(
+        'list',
+        help='List disk info from remote host(s)'
+        )
+    disk_list.add_argument(
+        'host',
+        nargs='+',
+        metavar='HOST',
+        help='Remote HOST(s) to list OSDs from'
+        )
+    disk_list.add_argument(
+        '--debug',
+        action='store_true',
+        help='Enable debug mode on remote ceph-volume calls',
+        )
+    parser.set_defaults(
+        func=disk,
+        )
diff --git a/ceph_deploy/pkg.py b/ceph_deploy/pkg.py
new file mode 100644 (file)
index 0000000..e40c17b
--- /dev/null
@@ -0,0 +1,86 @@
+import logging
+from . import hosts
+
+
+LOG = logging.getLogger(__name__)
+
+
+def install(args):
+    packages = args.install.split(',')
+    for hostname in args.hosts:
+        distro = hosts.get(hostname, username=args.username)
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+        rlogger = logging.getLogger(hostname)
+        rlogger.info('installing packages on %s' % hostname)
+        # Do not timeout on package install. If you we this command to install
+        # e.g. ceph-selinux or some other package with long post script we can
+        # easily timeout in the 5 minutes that we use as a default timeout,
+        # turning off the timeout completely for the time we run the command
+        # should make this much more safe.
+        distro.conn.global_timeout = None
+        distro.packager.install(packages)
+        distro.conn.exit()
+
+
+def remove(args):
+    packages = args.remove.split(',')
+    for hostname in args.hosts:
+        distro = hosts.get(hostname, username=args.username)
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        rlogger = logging.getLogger(hostname)
+        rlogger.info('removing packages from %s' % hostname)
+        # Do not timeout on package removal. If we use this command to remove
+        # e.g. ceph-selinux or some other package with long post script we can
+        # easily timeout in the 5 minutes that we use as a default timeout,
+        # turning off the timeout completely for the time we run the command
+        # should make this much more safe.
+        distro.conn.global_timeout = None
+        distro.packager.remove(packages)
+        distro.conn.exit()
+
+
+def pkg(args):
+    if args.install:
+        install(args)
+    elif args.remove:
+        remove(args)
+
+
+def make(parser):
+    """
+    Manage packages on remote hosts.
+    """
+
+    action = parser.add_mutually_exclusive_group()
+
+    action.add_argument(
+        '--install',
+        metavar='PKG(s)',
+        help='Comma-separated package(s) to install',
+    )
+
+    action.add_argument(
+        '--remove',
+        metavar='PKG(s)',
+        help='Comma-separated package(s) to remove',
+    )
+
+    parser.add_argument(
+        'hosts',
+        nargs='+',
+    )
+
+    parser.set_defaults(
+        func=pkg,
+    )
diff --git a/ceph_deploy/repo.py b/ceph_deploy/repo.py
new file mode 100644 (file)
index 0000000..9fd5c35
--- /dev/null
@@ -0,0 +1,113 @@
+import os
+import logging
+
+from ceph_deploy import hosts
+from ceph_deploy.cliutil import priority
+
+
+LOG = logging.getLogger(__name__)
+
+
+def install_repo(distro, args, cd_conf, rlogger):
+    if args.repo_name in cd_conf.get_repos():
+        LOG.info('will use repository %s from ceph-deploy config', args.repo_name)
+        options = dict(cd_conf.items(args.repo_name))
+        extra_repos = cd_conf.get_list(args.repo_name, 'extra-repos')
+        try:
+            repo_url = options.pop('baseurl')
+            gpg_url = options.pop('gpgkey', None)
+        except KeyError as err:
+            raise RuntimeError(
+                'missing required key: %s in config section: %s' % (err, args.repo_name)
+            )
+    else:
+        repo_url = os.environ.get('CEPH_DEPLOY_REPO_URL') or args.repo_url
+        gpg_url = os.environ.get('CEPH_DEPLOY_GPG_URL') or args.gpg_url
+        extra_repos = []
+
+    repo_url = repo_url.strip('/')  # Remove trailing slashes
+    distro.packager.add_repo(
+        args.repo_name,
+        repo_url,
+        gpg_url=gpg_url
+    )
+
+    for xrepo in extra_repos:
+        rlogger.info('adding extra repo: %s' % xrepo)
+        options = dict(cd_conf.items(xrepo))
+        try:
+            repo_url = options.pop('baseurl')
+            gpg_url = options.pop('gpgkey', None)
+        except KeyError as err:
+            raise RuntimeError(
+                'missing required key: %s in config section: %s' % (err, xrepo)
+            )
+        distro.packager.add_repo(
+            args.repo_name,
+            repo_url,
+            gpg_url=gpg_url
+        )
+
+
+def repo(args):
+    cd_conf = getattr(args, 'cd_conf', None)
+
+    for hostname in args.host:
+        LOG.debug('Detecting platform for host %s ...', hostname)
+        distro = hosts.get(
+            hostname,
+            username=args.username
+        )
+        rlogger = logging.getLogger(hostname)
+
+        LOG.info(
+            'Distro info: %s %s %s',
+            distro.name,
+            distro.release,
+            distro.codename
+        )
+
+        if args.remove:
+            distro.packager.remove_repo(args.repo_name)
+        else:
+            install_repo(distro, args, cd_conf, rlogger)
+
+
+@priority(70)
+def make(parser):
+    """
+    Repo definition management
+    """
+
+    parser.add_argument(
+        'repo_name',
+        metavar='REPO-NAME',
+        help='Name of repo to manage.  Can match an entry in cephdeploy.conf'
+    )
+
+    parser.add_argument(
+        '--repo-url',
+        help='a repo URL that mirrors/contains Ceph packages'
+    )
+
+    parser.add_argument(
+        '--gpg-url',
+        help='a GPG key URL to be used with custom repos'
+    )
+
+    parser.add_argument(
+        '--remove', '--delete',
+        action='store_true',
+        help='remove repo definition on remote host'
+    )
+
+    parser.add_argument(
+        'host',
+        metavar='HOST',
+        nargs='+',
+        help='host(s) to install on'
+    )
+
+    parser.set_defaults(
+        func=repo
+    )
diff --git a/ceph_deploy/rgw.py b/ceph_deploy/rgw.py
new file mode 100644 (file)
index 0000000..c6b9e0b
--- /dev/null
@@ -0,0 +1,233 @@
+import errno
+import logging
+import os
+
+from ceph_deploy import conf
+from ceph_deploy import exc
+from ceph_deploy import hosts
+from ceph_deploy.util import system
+from ceph_deploy.lib import remoto
+from ceph_deploy.cliutil import priority
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_bootstrap_rgw_key(cluster):
+    """
+    Read the bootstrap-rgw key for `cluster`.
+    """
+    path = '{cluster}.bootstrap-rgw.keyring'.format(cluster=cluster)
+    try:
+        with open(path, 'rb') as f:
+            return f.read()
+    except IOError:
+        raise RuntimeError('bootstrap-rgw keyring not found; run \'gatherkeys\'')
+
+
+def create_rgw(distro, name, cluster, init):
+    conn = distro.conn
+
+    path = '/var/lib/ceph/radosgw/{cluster}-{name}'.format(
+        cluster=cluster,
+        name=name
+        )
+
+    conn.remote_module.safe_makedirs(path)
+
+    bootstrap_keyring = '/var/lib/ceph/bootstrap-rgw/{cluster}.keyring'.format(
+        cluster=cluster
+        )
+
+    keypath = os.path.join(path, 'keyring')
+
+    stdout, stderr, returncode = remoto.process.check(
+        conn,
+        [
+            'ceph',
+            '--cluster', cluster,
+            '--name', 'client.bootstrap-rgw',
+            '--keyring', bootstrap_keyring,
+            'auth', 'get-or-create', 'client.{name}'.format(name=name),
+            'osd', 'allow rwx',
+            'mon', 'allow rw',
+            '-o',
+            os.path.join(keypath),
+        ]
+    )
+    if returncode > 0 and returncode != errno.EACCES:
+        for line in stderr:
+            conn.logger.error(line)
+        for line in stdout:
+            # yes stdout as err because this is an error
+            conn.logger.error(line)
+        conn.logger.error('exit code from command was: %s' % returncode)
+        raise RuntimeError('could not create rgw')
+
+        remoto.process.check(
+            conn,
+            [
+                'ceph',
+                '--cluster', cluster,
+                '--name', 'client.bootstrap-rgw',
+                '--keyring', bootstrap_keyring,
+                'auth', 'get-or-create', 'client.{name}'.format(name=name),
+                'osd', 'allow *',
+                'mon', 'allow *',
+                '-o',
+                os.path.join(keypath),
+            ]
+        )
+
+    conn.remote_module.touch_file(os.path.join(path, 'done'))
+    conn.remote_module.touch_file(os.path.join(path, init))
+
+    if init == 'upstart':
+        remoto.process.run(
+            conn,
+            [
+                'initctl',
+                'emit',
+                'radosgw',
+                'cluster={cluster}'.format(cluster=cluster),
+                'id={name}'.format(name=name),
+            ],
+            timeout=7
+        )
+    elif init == 'sysvinit':
+        remoto.process.run(
+            conn,
+            [
+                'service',
+                'ceph-radosgw',
+                'start',
+            ],
+            timeout=7
+        )
+        if distro.is_el:
+            system.enable_service(distro.conn, service='ceph-radosgw')
+    elif init == 'systemd':
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph-radosgw@{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'start',
+                'ceph-radosgw@{name}'.format(name=name),
+            ],
+            timeout=7
+        )
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                'ceph.target',
+            ],
+            timeout=7
+        )
+
+
+def rgw_create(args):
+    conf_data = conf.ceph.load_raw(args)
+    LOG.debug(
+        'Deploying rgw, cluster %s hosts %s',
+        args.cluster,
+        ' '.join(':'.join(x or '' for x in t) for t in args.rgw),
+        )
+
+    key = get_bootstrap_rgw_key(cluster=args.cluster)
+
+    bootstrapped = set()
+    errors = 0
+    for hostname, name in args.rgw:
+        try:
+            distro = hosts.get(hostname, username=args.username)
+            rlogger = distro.conn.logger
+            LOG.info(
+                'Distro info: %s %s %s',
+                distro.name,
+                distro.release,
+                distro.codename
+            )
+            LOG.debug('remote host will use %s', distro.init)
+
+            if hostname not in bootstrapped:
+                bootstrapped.add(hostname)
+                LOG.debug('deploying rgw bootstrap to %s', hostname)
+                distro.conn.remote_module.write_conf(
+                    args.cluster,
+                    conf_data,
+                    args.overwrite_conf,
+                )
+
+                path = '/var/lib/ceph/bootstrap-rgw/{cluster}.keyring'.format(
+                    cluster=args.cluster,
+                )
+
+                if not distro.conn.remote_module.path_exists(path):
+                    rlogger.warning('rgw keyring does not exist yet, creating one')
+                    distro.conn.remote_module.write_keyring(path, key)
+
+            create_rgw(distro, name, args.cluster, distro.init)
+            distro.conn.exit()
+            LOG.info(
+                ('The Ceph Object Gateway (RGW) is now running on host %s and '
+                 'default port %s'),
+                hostname,
+                '7480'
+            )
+        except RuntimeError as e:
+            LOG.error(e)
+            errors += 1
+
+    if errors:
+        raise exc.GenericError('Failed to create %d RGWs' % errors)
+
+
+def rgw(args):
+    if args.subcommand == 'create':
+        rgw_create(args)
+    else:
+        LOG.error('subcommand %s not implemented', args.subcommand)
+
+
+def colon_separated(s):
+    host = s
+    name = s
+    if s.count(':') == 1:
+        (host, name) = s.split(':')
+    name = 'rgw.' + name
+    return (host, name)
+
+
+@priority(30)
+def make(parser):
+    """
+    Ceph RGW daemon management
+    """
+    rgw_parser = parser.add_subparsers(dest='subcommand')
+    rgw_parser.required = True
+    rgw_create = rgw_parser.add_parser(
+        'create',
+        help='Create an RGW instance'
+        )
+    rgw_create.add_argument(
+        'rgw',
+        metavar='HOST[:NAME]',
+        nargs='+',
+        type=colon_separated,
+        help='host (and optionally the daemon name) to deploy on. \
+                NAME is automatically prefixed with \'rgw.\'',
+        )
+    parser.set_defaults(
+        func=rgw,
+        )
diff --git a/ceph_deploy/tests/__init__.py b/ceph_deploy/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ceph_deploy/tests/conftest.py b/ceph_deploy/tests/conftest.py
new file mode 100644 (file)
index 0000000..ee7fcf1
--- /dev/null
@@ -0,0 +1,98 @@
+import logging
+import os
+import subprocess
+import sys
+import pytest
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _prepend_path(env):
+    """
+    Make sure the PATH contains the location where the Python binary
+    lives. This makes sure cli tools installed in a virtualenv work.
+    """
+    if env is None:
+        env = os.environ
+    env = dict(env)
+    new = os.path.dirname(sys.executable)
+    path = env.get('PATH')
+    if path is not None:
+        new = new + ':' + path
+    env['PATH'] = new
+    return env
+
+
+class CLIFailed(Exception):
+    """CLI tool failed"""
+
+    def __init__(self, args, status):
+        self.args = args
+        self.status = status
+
+    def __str__(self):
+        return '{doc}: {args}: exited with status {status}'.format(
+            doc=self.__doc__,
+            args=self.args,
+            status=self.status,
+            )
+
+
+class CLIProcess(object):
+    def __init__(self, **kw):
+        self.kw = kw
+
+    def __enter__(self):
+        try:
+            self.p = subprocess.Popen(**self.kw)
+        except OSError as e:
+            raise AssertionError(
+                'CLI tool {args!r} does not work: {err}'.format(
+                    args=self.kw['args'],
+                    err=e,
+                    ),
+                )
+        else:
+            return self.p
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.p.wait()
+        if self.p.returncode != 0:
+            err = CLIFailed(
+                args=self.kw['args'],
+                status=self.p.returncode,
+                )
+            if exc_type is None:
+                # nothing else raised, so we should complain; if
+                # something else failed, we'll just log
+                raise err
+            else:
+                LOG.error(str(err))
+
+
+class CLITester(object):
+    # provide easy way for caller to access the exception class
+    # without importing us
+    Failed = CLIFailed
+
+    def __init__(self, tmpdir):
+        self.tmpdir = tmpdir
+
+    def __call__(self, **kw):
+        kw.setdefault('cwd', str(self.tmpdir))
+        kw['env'] = _prepend_path(kw.get('env'))
+        kw['env']['COLUMNS'] = '80'
+        return CLIProcess(**kw)
+
+
+@pytest.fixture
+def cli(request, tmpdir):
+    """
+    Test command line behavior.
+    """
+
+    # the tmpdir here will be the same value as the test function
+    # sees; we rely on that to let caller prepare and introspect
+    # any files the cli tool will read or create
+    return CLITester(tmpdir=tmpdir)
diff --git a/ceph_deploy/tests/directory.py b/ceph_deploy/tests/directory.py
new file mode 100644 (file)
index 0000000..81d3e19
--- /dev/null
@@ -0,0 +1,13 @@
+import contextlib
+import os
+
+
+@contextlib.contextmanager
+def directory(path):
+    prev = os.open('.', os.O_RDONLY | os.O_DIRECTORY)
+    try:
+        os.chdir(path)
+        yield
+    finally:
+        os.fchdir(prev)
+        os.close(prev)
diff --git a/ceph_deploy/tests/fakes.py b/ceph_deploy/tests/fakes.py
new file mode 100644 (file)
index 0000000..458ac6a
--- /dev/null
@@ -0,0 +1,9 @@
+
+
+def fake_getaddrinfo(*a, **kw):
+    return_host = kw.get('return_host', 'host1')
+    return [[0,0,0,0, return_host]]
+
+
+def fake_arg_val_hostname(self, host):
+    return host
diff --git a/ceph_deploy/tests/parser/__init__.py b/ceph_deploy/tests/parser/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ceph_deploy/tests/parser/test_admin.py b/ceph_deploy/tests/parser/test_admin.py
new file mode 100644 (file)
index 0000000..a86fa8e
--- /dev/null
@@ -0,0 +1,33 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserAdmin(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_admin_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('admin --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy admin' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_admin_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('admin'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_admin_one_host(self):
+        args = self.parser.parse_args('admin host1'.split())
+        assert args.client == ['host1']
+
+    def test_admin_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['admin'] + hostnames)
+        assert args.client == hostnames
diff --git a/ceph_deploy/tests/parser/test_config.py b/ceph_deploy/tests/parser/test_config.py
new file mode 100644 (file)
index 0000000..74ccb02
--- /dev/null
@@ -0,0 +1,60 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+SUBCMDS_WITH_ARGS = ['push', 'pull']
+
+
+class TestParserConfig(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_config_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('config --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy config' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITH_ARGS)
+    def test_config_subcommands_with_args(self, cmd):
+        self.parser.parse_args(['config'] + ['%s' % cmd] + ['host1'])
+
+    def test_config_invalid_subcommand(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('config bork'.split())
+        out, err = capsys.readouterr()
+        assert 'invalid choice' in err
+
+    def test_config_push_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('config push'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_config_push_one_host(self):
+        args = self.parser.parse_args('config push host1'.split())
+        assert args.client == ['host1']
+
+    def test_config_push_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('config push'.split() + hostnames)
+        assert args.client == hostnames
+
+    def test_config_pull_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('config pull'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_config_pull_one_host(self):
+        args = self.parser.parse_args('config pull host1'.split())
+        assert args.client == ['host1']
+
+    def test_config_pull_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('config pull'.split() + hostnames)
+        assert args.client == hostnames
diff --git a/ceph_deploy/tests/parser/test_disk.py b/ceph_deploy/tests/parser/test_disk.py
new file mode 100644 (file)
index 0000000..cedd858
--- /dev/null
@@ -0,0 +1,88 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+SUBCMDS_WITH_ARGS = ['list', 'zap']
+
+
+class TestParserDisk(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_disk_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('disk --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy disk' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITH_ARGS)
+    def test_disk_valid_subcommands_with_args(self, cmd):
+        self.parser.parse_args(['disk'] + ['%s' % cmd] + ['host1'])
+
+    def test_disk_invalid_subcommand(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('disk bork'.split())
+        out, err = capsys.readouterr()
+        assert 'invalid choice' in err
+
+    def test_disk_list_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('disk list --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy disk list' in out
+
+    def test_disk_list_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('disk list'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_disk_list_single_host(self):
+        args = self.parser.parse_args('disk list host1'.split())
+        assert args.host[0] == 'host1'
+        assert args.debug is False
+
+    def test_disk_list_single_host_debug(self):
+        args = self.parser.parse_args('disk list --debug host1'.split())
+        assert args.host[0] == 'host1'
+        assert args.debug is True
+
+    def test_disk_list_multi_host(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('disk list'.split() + hostnames)
+        assert args.host == hostnames
+
+    def test_disk_zap_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('disk zap --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy disk zap' in out
+
+    def test_disk_zap_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('disk zap'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_disk_zap_single_host(self):
+        args = self.parser.parse_args('disk zap host1 /dev/sdb'.split())
+        assert args.disk[0] == '/dev/sdb'
+        assert args.host == 'host1'
+        assert args.debug is False
+
+    def test_disk_zap_multi_host(self):
+        host = 'host1'
+        disks = ['/dev/sda1', '/dev/sda2']
+        args = self.parser.parse_args(['disk', 'zap', host] + disks)
+        assert args.disk == disks
+
+    def test_disk_zap_debug_true(self):
+        args = \
+            self.parser.parse_args('disk zap --debug host1 /dev/sdb'.split())
+        assert args.disk[0] == '/dev/sdb'
+        assert args.host == 'host1'
+        assert args.debug is True
diff --git a/ceph_deploy/tests/parser/test_gatherkeys.py b/ceph_deploy/tests/parser/test_gatherkeys.py
new file mode 100644 (file)
index 0000000..1dcafcc
--- /dev/null
@@ -0,0 +1,33 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserGatherKeys(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_gather_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('gatherkeys --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy gatherkeys' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_gatherkeys_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('gatherkeys'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_gatherkeys_one_host(self):
+        args = self.parser.parse_args('gatherkeys host1'.split())
+        assert args.mon == ['host1']
+
+    def test_gatherkeys_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['gatherkeys'] + hostnames)
+        assert args.mon == hostnames
diff --git a/ceph_deploy/tests/parser/test_install.py b/ceph_deploy/tests/parser/test_install.py
new file mode 100644 (file)
index 0000000..cb6284e
--- /dev/null
@@ -0,0 +1,158 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+COMP_FLAGS = [
+    'mon', 'mds', 'rgw', 'osd', 'common', 'all'
+]
+
+
+class TestParserInstall(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_install_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('install --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy install' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_install_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('install'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_install_one_host(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.host == ['host1']
+
+    def test_install_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['install'] + hostnames)
+        assert frozenset(args.host) == frozenset(hostnames)
+
+    def test_install_release_default_is_none(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.release is None
+        assert args.version_kind == "stable"
+
+    def test_install_release(self):
+        args = self.parser.parse_args('install --release hammer host1'.split())
+        assert args.release == "hammer"
+        assert args.version_kind == "stable"
+
+    @pytest.mark.skipif(reason="No release name sanity checking yet")
+    def test_install_release_bad_codename(self):
+        args = self.parser.parse_args('install --release cephalopod host1'.split())
+        assert args.release != "cephalopod"
+
+    def test_install_testing_default_is_none(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.testing is None
+        assert args.version_kind == "stable"
+
+    def test_install_testing_true(self):
+        args = self.parser.parse_args('install --testing host1'.split())
+        assert len(args.testing) == 0
+        assert args.version_kind == "testing"
+
+    def test_install_dev_disabled_by_default(self):
+        args = self.parser.parse_args('install host1'.split())
+        # dev defaults to master, but version_kind nullifies it
+        assert args.dev == "master"
+        assert args.version_kind == "stable"
+
+    def test_install_dev_custom_version(self):
+        args = self.parser.parse_args('install --dev v0.80.8 host1'.split())
+        assert args.dev == "v0.80.8"
+        assert args.version_kind == "dev"
+
+    @pytest.mark.skipif(reason="test reflects desire, but not code reality")
+    def test_install_dev_option_default_is_master(self):
+        # I don't think this is the way argparse works.
+        args = self.parser.parse_args('install --dev host1'.split())
+        assert args.dev == "master"
+        assert args.version_kind == "dev"
+
+    def test_install_release_testing_mutex(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('install --release hammer --testing host1'.split())
+        out, err = capsys.readouterr()
+        assert 'not allowed with argument' in err
+
+    def test_install_release_dev_mutex(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('install --release hammer --dev master host1'.split())
+        out, err = capsys.readouterr()
+        assert 'not allowed with argument' in err
+
+    def test_install_testing_dev_mutex(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('install --testing --dev master host1'.split())
+        out, err = capsys.readouterr()
+        assert 'not allowed with argument' in err
+
+    @pytest.mark.parametrize('comp', COMP_FLAGS)
+    def test_install_component_default_is_false(self, comp):
+        args = self.parser.parse_args('install host1'.split())
+        assert getattr(args, 'install_%s' % comp) is False
+
+    @pytest.mark.parametrize('comp', COMP_FLAGS)
+    def test_install_component_true(self, comp):
+        args = self.parser.parse_args(('install --%s host1' % comp).split())
+        assert getattr(args, 'install_%s' % comp) is True
+
+    def test_install_multi_component(self):
+        args = self.parser.parse_args(('install --mon --rgw host1').split())
+        assert args.install_mon
+        assert args.install_rgw
+
+    def test_install_adjust_repos_default_is_true(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.adjust_repos
+
+    def test_install_adjust_repos_false(self):
+        args = self.parser.parse_args('install --no-adjust-repos host1'.split())
+        assert not args.adjust_repos
+
+    def test_install_adjust_repos_false_with_custom_release(self):
+        args = self.parser.parse_args('install --release firefly --no-adjust-repos host1'.split())
+        assert args.release == "firefly"
+        assert not args.adjust_repos
+
+    def test_install_repo_default_is_false(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert not args.repo
+
+    def test_install_repo_true(self):
+        args = self.parser.parse_args('install --repo host1'.split())
+        assert args.repo
+
+    def test_install_local_mirror_default_is_none(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.local_mirror is None
+
+    def test_install_local_mirror_custom_path(self):
+        args = self.parser.parse_args('install --local-mirror /mnt/mymirror host1'.split())
+        assert args.local_mirror == "/mnt/mymirror"
+
+    def test_install_repo_url_default_is_none(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.repo_url is None
+
+    def test_install_repo_url_custom_path(self):
+        args = self.parser.parse_args('install --repo-url https://ceph.com host1'.split())
+        assert args.repo_url == "https://ceph.com"
+
+    def test_install_gpg_url_default_is_none(self):
+        args = self.parser.parse_args('install host1'.split())
+        assert args.gpg_url is None
+
+    def test_install_gpg_url_custom_path(self):
+        args = self.parser.parse_args('install --gpg-url https://ceph.com/key host1'.split())
+        assert args.gpg_url == "https://ceph.com/key"
diff --git a/ceph_deploy/tests/parser/test_main.py b/ceph_deploy/tests/parser/test_main.py
new file mode 100644 (file)
index 0000000..ac7186e
--- /dev/null
@@ -0,0 +1,100 @@
+import pytest
+
+import ceph_deploy
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+SUBCMDS_WITH_ARGS = [
+    'new', 'install', 'rgw', 'mds', 'mon', 'gatherkeys', 'disk', 'osd',
+    'admin', 'config', 'uninstall', 'purgedata', 'purge', 'pkg'
+]
+SUBCMDS_WITHOUT_ARGS = ['forgetkeys']
+
+
+class TestParserMain(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_verbose_true(self):
+        args = self.parser.parse_args('--verbose forgetkeys'.split())
+        assert args.verbose
+
+    def test_verbose_default_is_false(self):
+        args = self.parser.parse_args('forgetkeys'.split())
+        assert not args.verbose
+
+    def test_quiet_true(self):
+        args = self.parser.parse_args('--quiet forgetkeys'.split())
+        assert args.quiet
+
+    def test_quiet_default_is_false(self):
+        args = self.parser.parse_args('forgetkeys'.split())
+        assert not args.quiet
+
+    def test_verbose_quiet_are_mutually_exclusive(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('--verbose --quiet forgetkeys'.split())
+        out, err = capsys.readouterr()
+        assert 'not allowed with argument' in err
+
+    def test_version(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('--version'.split())
+        out, err = capsys.readouterr()
+        assert ceph_deploy.__version__ in (out.strip(), err.strip())
+
+    def test_custom_username(self):
+        args = self.parser.parse_args('--username trhoden forgetkeys'.split())
+        assert args.username == 'trhoden'
+
+    def test_default_username_is_none(self):
+        args = self.parser.parse_args('forgetkeys'.split())
+        assert args.username is None
+
+    def test_overwrite_conf_default_false(self):
+        args = self.parser.parse_args('forgetkeys'.split())
+        assert not args.overwrite_conf
+
+    def test_overwrite_conf_true(self):
+        args = self.parser.parse_args('--overwrite-conf forgetkeys'.split())
+        assert args.overwrite_conf
+
+    def test_default_cluster_name(self):
+        args = self.parser.parse_args('forgetkeys'.split())
+        assert args.cluster == 'ceph'
+
+    def test_default_ceph_conf_is_none(self):
+        args = self.parser.parse_args('forgetkeys'.split())
+        assert args.ceph_conf is None
+
+    def test_custom_ceph_conf(self):
+        args = self.parser.parse_args('--ceph-conf /tmp/ceph.conf forgetkeys'.split())
+        assert args.ceph_conf == '/tmp/ceph.conf'
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITH_ARGS)
+    def test_valid_subcommands_with_args(self, cmd, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args(['%s' % cmd])
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+        assert 'invalid choice' not in err
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITHOUT_ARGS)
+    def test_valid_subcommands_without_args(self, cmd, capsys):
+        self.parser.parse_args(['%s' % cmd])
+
+    def test_invalid_subcommand(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('bork'.split())
+        out, err = capsys.readouterr()
+        assert 'invalid choice' in err
+
+    def test_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('--help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy' in out
+        assert 'optional arguments:' in out
+        assert 'commands:' in out
diff --git a/ceph_deploy/tests/parser/test_mds.py b/ceph_deploy/tests/parser/test_mds.py
new file mode 100644 (file)
index 0000000..0b81c38
--- /dev/null
@@ -0,0 +1,35 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserMDS(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_mds_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mds --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy mds' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_mds_create_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mds create'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_mds_create_one_host(self):
+        args = self.parser.parse_args('mds create host1'.split())
+        assert args.mds[0][0] == 'host1'
+
+    def test_mds_create_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['mds', 'create'] + hostnames)
+        # args.mds is a list of tuples, and tuple[0] is the hostname
+        hosts = [x[0] for x in args.mds]
+        assert frozenset(hosts) == frozenset(hostnames)
diff --git a/ceph_deploy/tests/parser/test_mon.py b/ceph_deploy/tests/parser/test_mon.py
new file mode 100644 (file)
index 0000000..4c03101
--- /dev/null
@@ -0,0 +1,122 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+
+SUBCMDS_WITH_ARGS = ['add', 'destroy', 'create']
+SUBCMDS_WITHOUT_ARGS = ['create', 'create-initial']
+
+
+class TestParserMON(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_mon_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy mon' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITH_ARGS)
+    def test_mon_valid_subcommands_with_args(self, cmd, capsys):
+        args = self.parser.parse_args(['mon'] + ['%s' % cmd] + ['host1'])
+        assert args.subcommand == cmd
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITHOUT_ARGS)
+    def test_mon_valid_subcommands_without_args(self, cmd, capsys):
+        args = self.parser.parse_args(['mon'] + ['%s' % cmd])
+        assert args.subcommand == cmd
+
+    def test_mon_invalid_subcommand(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon bork'.split())
+        out, err = capsys.readouterr()
+        assert 'invalid choice' in err
+
+    def test_mon_create_initial_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon create-initial --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy mon create-initial' in out
+
+    def test_mon_create_initial_keyrings_default_none(self):
+        args = self.parser.parse_args('mon create-initial'.split())
+        assert args.keyrings is None
+
+    def test_mon_create_initial_keyrings_custom_dir(self):
+        args = self.parser.parse_args('mon create-initial --keyrings /tmp/keys'.split())
+        assert args.keyrings == "/tmp/keys"
+
+    def test_mon_create_initial_keyrings_host_raises_err(self):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon create-initial test1'.split())
+
+    def test_mon_create_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon create --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy mon create' in out
+
+    def test_mon_create_keyrings_default_none(self):
+        args = self.parser.parse_args('mon create'.split())
+        assert args.keyrings is None
+
+    def test_mon_create_keyrings_custom_dir(self):
+        args = self.parser.parse_args('mon create --keyrings /tmp/keys'.split())
+        assert args.keyrings == "/tmp/keys"
+
+    def test_mon_create_single_host(self):
+        args = self.parser.parse_args('mon create test1'.split())
+        assert args.mon == ['test1']
+
+    def test_mon_create_multi_host(self):
+        hosts = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('mon create'.split() + hosts)
+        assert args.mon == hosts
+
+    def test_mon_add_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon add --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy mon add' in out
+
+    def test_mon_add_address_default_none(self):
+        args = self.parser.parse_args('mon add test1'.split())
+        assert args.address is None
+
+    def test_mon_add_address_custom_addr(self):
+        args = self.parser.parse_args('mon add test1 --address 10.10.0.1'.split())
+        assert args.address == '10.10.0.1'
+
+    def test_mon_add_no_host_raises_err(self):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon add'.split())
+
+    def test_mon_add_one_host_okay(self):
+        args = self.parser.parse_args('mon add test1'.split())
+        assert args.mon == ["test1"]
+
+    def test_mon_add_multi_host_raises_err(self):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon add test1 test2'.split())
+
+    def test_mon_destroy_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon destroy --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy mon destroy' in out
+
+    def test_mon_destroy_no_host_raises_err(self):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('mon destroy'.split())
+
+    def test_mon_destroy_one_host_okay(self):
+        args = self.parser.parse_args('mon destroy test1'.split())
+        assert args.mon == ["test1"]
+
+    def test_mon_destroy_multi_host(self):
+        hosts = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('mon destroy'.split() + hosts)
+        assert args.mon == hosts
diff --git a/ceph_deploy/tests/parser/test_new.py b/ceph_deploy/tests/parser/test_new.py
new file mode 100644 (file)
index 0000000..9395bab
--- /dev/null
@@ -0,0 +1,84 @@
+import pytest
+from mock import patch
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.fakes import fake_arg_val_hostname
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+@patch('ceph_deploy.util.arg_validators.Hostname.__call__', fake_arg_val_hostname)
+class TestParserNew(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('new --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy new' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_new_copykey_true_by_default(self):
+        args = self.parser.parse_args('new host1'.split())
+        assert args.ssh_copykey
+
+    def test_new_copykey_false(self):
+        args = self.parser.parse_args('new --no-ssh-copykey host1'.split())
+        assert not args.ssh_copykey
+
+    def test_new_fsid_none_by_default(self):
+        args = self.parser.parse_args('new host1'.split())
+        assert args.fsid is None
+
+    def test_new_fsid_custom_fsid(self):
+        args = self.parser.parse_args('new --fsid bc50d015-65c9-457a-bfed-e37b92756527 host1'.split())
+        assert args.fsid == 'bc50d015-65c9-457a-bfed-e37b92756527'
+
+    @pytest.mark.skipif(reason="no UUID validation yet")
+    def test_new_fsid_custom_fsid_bad(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('new --fsid bc50d015-65c9-457a-bfed-e37'.split())
+        out, err = capsys.readouterr()
+        #TODO check for correct error string in err
+
+    def test_new_networks_none_by_default(self):
+        args = self.parser.parse_args('new host1'.split())
+        assert args.public_network is None
+        assert args.cluster_network is None
+
+    def test_new_public_network_custom(self):
+        args = self.parser.parse_args('new --public-network 10.10.0.0/16 host1'.split())
+        assert args.public_network == "10.10.0.0/16"
+
+    def test_new_cluster_network_custom(self):
+        args = self.parser.parse_args('new --cluster-network 10.10.0.0/16 host1'.split())
+        assert args.cluster_network == "10.10.0.0/16"
+
+    def test_new_public_network_custom_bad(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('new --public-network 10.10.0.0'.split())
+        out, err = capsys.readouterr()
+        assert "error: subnet must" in err
+
+    def test_new_cluster_network_custom_bad(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('new --cluster-network 10.10.0.0'.split())
+        out, err = capsys.readouterr()
+        assert "error: subnet must" in err
+
+    def test_new_mon_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('new'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_new_one_mon(self):
+        hostnames = ['test1']
+        args = self.parser.parse_args(['new'] + hostnames)
+        assert args.mon == hostnames
+
+    def test_new_multiple_mons(self):
+        hostnames = ['test1', 'test2', 'test3']
+        args = self.parser.parse_args(['new'] + hostnames)
+        assert frozenset(args.mon) == frozenset(hostnames)
diff --git a/ceph_deploy/tests/parser/test_osd.py b/ceph_deploy/tests/parser/test_osd.py
new file mode 100644 (file)
index 0000000..3b983c2
--- /dev/null
@@ -0,0 +1,101 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+SUBCMDS_WITH_ARGS = ['list', 'create']
+
+
+class TestParserOSD(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_osd_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('osd --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy osd' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    @pytest.mark.parametrize('cmd', SUBCMDS_WITH_ARGS)
+    def test_osd_valid_subcommands_with_args(self, cmd):
+        self.parser.parse_args(['osd'] + ['%s' % cmd] + ['host1'])
+
+    def test_osd_invalid_subcommand(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('osd bork'.split())
+        out, err = capsys.readouterr()
+        assert 'invalid choice' in err
+
+    def test_osd_list_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('osd list --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy osd list' in out
+
+    def test_osd_list_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('osd list'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_osd_list_single_host(self):
+        args = self.parser.parse_args('osd list host1'.split())
+        assert args.host[0] == 'host1'
+
+    def test_osd_list_multi_host(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('osd list'.split() + hostnames)
+        assert args.host == hostnames
+
+    def test_osd_create_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('osd create --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy osd create' in out
+
+    def test_osd_create_single_host(self):
+        args = self.parser.parse_args('osd create host1 --data /dev/sdb'.split())
+        assert args.host == 'host1'
+        assert args.data == '/dev/sdb'
+
+    def test_osd_create_zap_default_false(self):
+        args = self.parser.parse_args('osd create host1 --data /dev/sdb'.split())
+        assert args.zap_disk is False
+
+    def test_osd_create_zap_true(self):
+        args = self.parser.parse_args('osd create --zap-disk host1 --data /dev/sdb'.split())
+        assert args.zap_disk is True
+
+    def test_osd_create_fstype_default_xfs(self):
+        args = self.parser.parse_args('osd create host1 --data /dev/sdb'.split())
+        assert args.fs_type == "xfs"
+
+    def test_osd_create_fstype_btrfs(self):
+        args = self.parser.parse_args('osd create --fs-type btrfs host1 --data /dev/sdb'.split())
+        assert args.fs_type == "btrfs"
+
+    def test_osd_create_fstype_invalid(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('osd create --fs-type bork host1 --data /dev/sdb'.split())
+        out, err = capsys.readouterr()
+        assert 'invalid choice' in err
+
+    def test_osd_create_dmcrypt_default_false(self):
+        args = self.parser.parse_args('osd create host1 --data /dev/sdb'.split())
+        assert args.dmcrypt is False
+
+    def test_osd_create_dmcrypt_true(self):
+        args = self.parser.parse_args('osd create --dmcrypt host1 --data /dev/sdb'.split())
+        assert args.dmcrypt is True
+
+    def test_osd_create_dmcrypt_key_dir_default(self):
+        args = self.parser.parse_args('osd create host1 --data /dev/sdb'.split())
+        assert args.dmcrypt_key_dir == "/etc/ceph/dmcrypt-keys"
+
+    def test_osd_create_dmcrypt_key_dir_custom(self):
+        args = self.parser.parse_args('osd create --dmcrypt --dmcrypt-key-dir /tmp/keys host1 --data /dev/sdb'.split())
+        assert args.dmcrypt_key_dir == "/tmp/keys"
+
diff --git a/ceph_deploy/tests/parser/test_pkg.py b/ceph_deploy/tests/parser/test_pkg.py
new file mode 100644 (file)
index 0000000..9061a68
--- /dev/null
@@ -0,0 +1,66 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserPkg(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_pkg_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('pkg --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy pkg' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_pkg_install_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('pkg --install pkg1'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_pkg_install_one_host(self):
+        args = self.parser.parse_args('pkg --install pkg1 host1'.split())
+        assert args.hosts == ['host1']
+        assert args.install == "pkg1"
+
+    def test_pkg_install_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('pkg --install pkg1'.split() + hostnames)
+        assert args.hosts == hostnames
+        assert args.install == "pkg1"
+
+    def test_pkg_install_muliple_pkgs(self):
+        args = self.parser.parse_args('pkg --install pkg1,pkg2 host1'.split())
+        assert args.install == "pkg1,pkg2"
+
+    def test_pkg_remove_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('pkg --remove pkg1'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_pkg_remove_one_host(self):
+        args = self.parser.parse_args('pkg --remove pkg1 host1'.split())
+        assert args.hosts == ['host1']
+        assert args.remove == "pkg1"
+
+    def test_pkg_remove_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args('pkg --remove pkg1'.split() + hostnames)
+        assert args.hosts == hostnames
+        assert args.remove == "pkg1"
+
+    def test_pkg_remove_muliple_pkgs(self):
+        args = self.parser.parse_args('pkg --remove pkg1,pkg2 host1'.split())
+        assert args.remove == "pkg1,pkg2"
+
+    def test_pkg_install_remove_are_mutex(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('pkg --install pkg2 --remove pkg1 host1'.split())
+        out, err = capsys.readouterr()
+        assert "argument --remove: not allowed with argument --install" in err
diff --git a/ceph_deploy/tests/parser/test_purge.py b/ceph_deploy/tests/parser/test_purge.py
new file mode 100644 (file)
index 0000000..8dcb348
--- /dev/null
@@ -0,0 +1,33 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserPurge(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_purge_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('purge --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy purge' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_purge_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('purge'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_purge_one_host(self):
+        args = self.parser.parse_args('purge host1'.split())
+        assert args.host == ['host1']
+
+    def test_purge_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['purge'] + hostnames)
+        assert frozenset(args.host) == frozenset(hostnames)
diff --git a/ceph_deploy/tests/parser/test_purgedata.py b/ceph_deploy/tests/parser/test_purgedata.py
new file mode 100644 (file)
index 0000000..dadef2b
--- /dev/null
@@ -0,0 +1,33 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserPurgeData(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_purgedata_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('purgedata --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy purgedata' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_purgedata_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('purgedata'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_purgedata_one_host(self):
+        args = self.parser.parse_args('purgedata host1'.split())
+        assert args.host == ['host1']
+
+    def test_purgedata_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['purgedata'] + hostnames)
+        assert frozenset(args.host) == frozenset(hostnames)
diff --git a/ceph_deploy/tests/parser/test_repo.py b/ceph_deploy/tests/parser/test_repo.py
new file mode 100644 (file)
index 0000000..a84901e
--- /dev/null
@@ -0,0 +1,71 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserRepo(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_repo_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('repo --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy repo' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_repo_name_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('repo'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_repo_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('repo ceph'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_repo_one_host(self):
+        args = self.parser.parse_args('repo ceph host1'.split())
+        assert args.host == ['host1']
+
+    def test_repo_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['repo', 'ceph'] + hostnames)
+        assert frozenset(args.host) == frozenset(hostnames)
+
+    def test_repo_name(self):
+        args = self.parser.parse_args('repo ceph host1'.split())
+        assert args.repo_name == 'ceph'
+
+    def test_repo_remove_default_is_false(self):
+        args = self.parser.parse_args('repo ceph host1'.split())
+        assert not args.remove
+
+    def test_repo_remove_set_true(self):
+        args = self.parser.parse_args('repo ceph --remove host1'.split())
+        assert args.remove
+
+    def test_repo_remove_delete_alias(self):
+        args = self.parser.parse_args('repo ceph --delete host1'.split())
+        assert args.remove
+
+    def test_repo_url_default_is_none(self):
+        args = self.parser.parse_args('repo ceph host1'.split())
+        assert args.repo_url is None
+
+    def test_repo_url_custom_path(self):
+        args = self.parser.parse_args('repo ceph --repo-url https://ceph.com host1'.split())
+        assert args.repo_url == "https://ceph.com"
+
+    def test_repo_gpg_url_default_is_none(self):
+        args = self.parser.parse_args('repo ceph host1'.split())
+        assert args.gpg_url is None
+
+    def test_repo_gpg_url_custom_path(self):
+        args = self.parser.parse_args('repo ceph --gpg-url https://ceph.com/key host1'.split())
+        assert args.gpg_url == "https://ceph.com/key"
diff --git a/ceph_deploy/tests/parser/test_rgw.py b/ceph_deploy/tests/parser/test_rgw.py
new file mode 100644 (file)
index 0000000..5cbf0a0
--- /dev/null
@@ -0,0 +1,35 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserRGW(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_rgw_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('rgw --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy rgw' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_rgw_create_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('rgw create'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_rgw_create_one_host(self):
+        args = self.parser.parse_args('rgw create host1'.split())
+        assert args.rgw[0][0] == 'host1'
+
+    def test_rgw_create_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['rgw', 'create'] + hostnames)
+        # args.rgw is a list of tuples, and tuple[0] is the hostname
+        hosts = [x[0] for x in args.rgw]
+        assert frozenset(hosts) == frozenset(hostnames)
diff --git a/ceph_deploy/tests/parser/test_uninstall.py b/ceph_deploy/tests/parser/test_uninstall.py
new file mode 100644 (file)
index 0000000..30cd918
--- /dev/null
@@ -0,0 +1,33 @@
+import pytest
+
+from ceph_deploy.cli import get_parser
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+class TestParserUninstall(object):
+
+    def setup(self):
+        self.parser = get_parser()
+
+    def test_uninstall_help(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('uninstall --help'.split())
+        out, err = capsys.readouterr()
+        assert 'usage: ceph-deploy uninstall' in out
+        assert 'positional arguments:' in out
+        assert 'optional arguments:' in out
+
+    def test_uninstall_host_required(self, capsys):
+        with pytest.raises(SystemExit):
+            self.parser.parse_args('uninstall'.split())
+        out, err = capsys.readouterr()
+        assert_too_few_arguments(err)
+
+    def test_uninstall_one_host(self):
+        args = self.parser.parse_args('uninstall host1'.split())
+        assert args.host == ['host1']
+
+    def test_uninstall_multiple_hosts(self):
+        hostnames = ['host1', 'host2', 'host3']
+        args = self.parser.parse_args(['uninstall'] + hostnames)
+        assert frozenset(args.host) == frozenset(hostnames)
diff --git a/ceph_deploy/tests/test_cli_admin.py b/ceph_deploy/tests/test_cli_admin.py
new file mode 100644 (file)
index 0000000..06a95dc
--- /dev/null
@@ -0,0 +1,60 @@
+import os
+import subprocess
+
+import pytest
+from mock import patch, MagicMock, Mock
+
+from ceph_deploy.cli import _main as main
+from ceph_deploy.hosts import remotes
+from ceph_deploy.tests.directory import directory
+
+
+def test_bad_no_conf(tmpdir, cli):
+    with pytest.raises(cli.Failed) as err:
+        with cli(
+            args=['ceph-deploy', 'admin', 'host1'],
+            stderr=subprocess.PIPE,
+            ) as p:
+            result = p.stderr.read().decode('utf-8')
+    assert 'No such file or directory: \'ceph.conf\'' in result
+    assert err.value.status == 1
+
+
+def test_bad_no_key(tmpdir, cli):
+    with tmpdir.join('ceph.conf').open('w'):
+        pass
+    with pytest.raises(cli.Failed) as err:
+        with cli(
+            args=['ceph-deploy', 'admin', 'host1'],
+            stderr=subprocess.PIPE,
+            ) as p:
+            result = p.stderr.read().decode('utf-8')
+    assert 'ceph.client.admin.keyring not found' in result
+    assert err.value.status == 1
+
+
+def test_write_keyring(tmpdir):
+    with tmpdir.join('ceph.conf').open('w'):
+        pass
+    with tmpdir.join('ceph.client.admin.keyring').open('wb'):
+        pass
+
+    etc_ceph = os.path.join(str(tmpdir), 'etc', 'ceph')
+    os.makedirs(etc_ceph)
+
+    distro = MagicMock()
+    distro.conn = MagicMock()
+    remotes.write_file.__defaults__ = (0o644, str(tmpdir), -1, -1)
+    distro.conn.remote_module = remotes
+    distro.conn.remote_module.write_conf = Mock()
+
+    with patch('ceph_deploy.admin.hosts'):
+        with patch('ceph_deploy.admin.hosts.get', MagicMock(return_value=distro)):
+            with directory(str(tmpdir)):
+                main(args=['admin', 'host1'])
+
+    keyring_file = os.path.join(etc_ceph, 'ceph.client.admin.keyring')
+    assert os.path.exists(keyring_file)
+
+    file_mode = oct(os.stat(keyring_file).st_mode & 0o777)
+    assert file_mode == oct(0o600)
diff --git a/ceph_deploy/tests/test_cli_mon.py b/ceph_deploy/tests/test_cli_mon.py
new file mode 100644 (file)
index 0000000..9948681
--- /dev/null
@@ -0,0 +1,56 @@
+import subprocess
+
+import pytest
+from mock import Mock, patch
+
+from ceph_deploy.cli import _main as main
+from ceph_deploy.tests.directory import directory
+from ceph_deploy.tests.util import assert_too_few_arguments
+
+
+#TODO: This test does check that things fail if the .conf file is missing
+def test_bad_no_conf(tmpdir, cli):
+    with pytest.raises(cli.Failed) as err:
+        with cli(
+            args=['ceph-deploy', 'mon'],
+            stderr=subprocess.PIPE,
+            ) as p:
+            result = p.stderr.read().decode('utf-8')
+    assert 'usage: ceph-deploy' in result
+    assert_too_few_arguments(result)
+    assert err.value.status == 2
+
+
+def make_fake_connection(platform_information=None):
+    get_connection = Mock()
+    get_connection.return_value = get_connection
+    get_connection.remote_module.platform_information = Mock(
+        return_value=platform_information)
+    return get_connection
+
+
+def test_new(tmpdir, capsys):
+    with tmpdir.join('ceph.conf').open('w') as f:
+        f.write("""\
+[global]
+fsid = 6ede5564-3cf1-44b5-aa96-1c77b0c3e1d0
+mon initial members = host1
+""")
+
+    fake_ip_addresses = lambda x: ['10.0.0.1']
+    try:
+        with patch('ceph_deploy.new.net.ip_addresses', fake_ip_addresses):
+            with patch('ceph_deploy.new.net.get_nonlocal_ip', lambda x: '10.0.0.1'):
+                with patch('ceph_deploy.new.arg_validators.Hostname', lambda: lambda x: x):
+                    with patch('ceph_deploy.new.hosts'):
+                        with directory(str(tmpdir)):
+                            main(['-v', 'new', '--no-ssh-copykey', 'host1'])
+    except SystemExit as e:
+        raise AssertionError('Unexpected exit: %s', e)
+    out, err = capsys.readouterr()
+    err = err.lower()
+    assert 'creating new cluster named ceph' in err
+    assert 'monitor host1 at 10.0.0.1' in err
+    assert 'resolving host host1' in err
+    assert "monitor initial members are ['host1']" in err
+    assert "monitor addrs are ['10.0.0.1']" in err
diff --git a/ceph_deploy/tests/test_cli_new.py b/ceph_deploy/tests/test_cli_new.py
new file mode 100644 (file)
index 0000000..056bc78
--- /dev/null
@@ -0,0 +1,71 @@
+import re
+import uuid
+
+from mock import patch
+
+from ceph_deploy import conf
+from ceph_deploy.cli import _main as main
+from ceph_deploy.tests.directory import directory
+import pytest
+
+
+def test_write_global_conf_section(tmpdir):
+    fake_ip_addresses = lambda x: ['10.0.0.1']
+
+    with patch('ceph_deploy.new.hosts'):
+        with patch('ceph_deploy.new.net.ip_addresses', fake_ip_addresses):
+            with patch('ceph_deploy.new.net.get_nonlocal_ip', lambda x: '10.0.0.1'):
+                with patch('ceph_deploy.new.arg_validators.Hostname', lambda: lambda x: x):
+                    with directory(str(tmpdir)):
+                        main(args=['new', 'host1'])
+    with tmpdir.join('ceph.conf').open() as f:
+        cfg = conf.ceph.parse(f)
+    assert cfg.sections() == ['global']
+
+
+@pytest.fixture
+def newcfg(request, tmpdir):
+    fake_ip_addresses = lambda x: ['10.0.0.1']
+
+    def new(*args):
+        with patch('ceph_deploy.new.net.ip_addresses', fake_ip_addresses):
+            with patch('ceph_deploy.new.hosts'):
+                with patch('ceph_deploy.new.net.get_nonlocal_ip', lambda x: '10.0.0.1'):
+                    with patch('ceph_deploy.new.arg_validators.Hostname', lambda: lambda x: x):
+                        with directory(str(tmpdir)):
+                            main(args=['new'] + list(args))
+                            with tmpdir.join('ceph.conf').open() as f:
+                                cfg = conf.ceph.parse(f)
+                            return cfg
+    return new
+
+
+def test_uuid(newcfg):
+    cfg = newcfg('host1')
+    fsid = cfg.get('global', 'fsid')
+    # make sure it's a valid uuid
+    uuid.UUID(hex=fsid)
+    # make sure it looks pretty, too
+    UUID_RE = re.compile(
+        r'^[0-9a-f]{8}-'
+        + r'[0-9a-f]{4}-'
+        # constant 4 here, we want to enforce randomness and not leak
+        # MACs or time
+        + r'4[0-9a-f]{3}-'
+        + r'[0-9a-f]{4}-'
+        + r'[0-9a-f]{12}$',
+        )
+    assert UUID_RE.match(fsid)
+
+
+def test_mons(newcfg):
+    cfg = newcfg('node01', 'node07', 'node34')
+    mon_initial_members = cfg.get('global', 'mon_initial_members')
+    assert mon_initial_members == 'node01, node07, node34'
+
+
+def test_defaults(newcfg):
+    cfg = newcfg('host1')
+    assert cfg.get('global', 'auth cluster required') == 'cephx'
+    assert cfg.get('global', 'auth service required') == 'cephx'
+    assert cfg.get('global', 'auth client required') == 'cephx'
diff --git a/ceph_deploy/tests/test_cli_rgw.py b/ceph_deploy/tests/test_cli_rgw.py
new file mode 100644 (file)
index 0000000..b0b86c4
--- /dev/null
@@ -0,0 +1,11 @@
+import ceph_deploy.rgw as rgw
+
+
+def test_rgw_prefix_auto():
+    daemon = rgw.colon_separated("hostname")
+    assert daemon == ("hostname", "rgw.hostname")
+
+
+def test_rgw_prefix_custom():
+    daemon = rgw.colon_separated("hostname:mydaemon")
+    assert daemon == ("hostname", "rgw.mydaemon")
diff --git a/ceph_deploy/tests/test_conf.py b/ceph_deploy/tests/test_conf.py
new file mode 100644 (file)
index 0000000..8d435df
--- /dev/null
@@ -0,0 +1,86 @@
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
+from ceph_deploy import conf
+
+
+def test_simple():
+    f = StringIO("""\
+[foo]
+bar = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar') == 'baz'
+
+
+def test_indent_space():
+    f = StringIO("""\
+[foo]
+        bar = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar') == 'baz'
+
+
+def test_indent_tab():
+    f = StringIO("""\
+[foo]
+\tbar = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar') == 'baz'
+
+
+def test_words_underscore():
+    f = StringIO("""\
+[foo]
+bar_thud = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar_thud') == 'baz'
+    assert cfg.get('foo', 'bar thud') == 'baz'
+
+
+def test_words_space():
+    f = StringIO("""\
+[foo]
+bar thud = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar_thud') == 'baz'
+    assert cfg.get('foo', 'bar thud') == 'baz'
+
+
+def test_words_many():
+    f = StringIO("""\
+[foo]
+bar__ thud   quux = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar_thud_quux') == 'baz'
+    assert cfg.get('foo', 'bar thud quux') == 'baz'
+
+
+def test_write_words_underscore():
+    cfg = conf.ceph.CephConf()
+    cfg.add_section('foo')
+    cfg.set('foo', 'bar thud quux', 'baz')
+    f = StringIO()
+    cfg.write(f)
+    f.seek(0)
+    assert f.readlines() == ['[foo]\n', 'bar_thud_quux = baz\n','\n']
+
+
+def test_section_repeat():
+    f = StringIO("""\
+[foo]
+bar = bez
+thud = quux
+
+[foo]
+bar = baz
+""")
+    cfg = conf.ceph.parse(f)
+    assert cfg.get('foo', 'bar') == 'baz'
+    assert cfg.get('foo', 'thud') == 'quux'
diff --git a/ceph_deploy/tests/test_gather_keys.py b/ceph_deploy/tests/test_gather_keys.py
new file mode 100644 (file)
index 0000000..37f5b1c
--- /dev/null
@@ -0,0 +1,141 @@
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import mock
+import pytest
+import tempfile
+import os
+import shutil
+
+
+def get_key_static(keytype, key_path):
+    with open(key_path, 'w') as f:
+        f.write("[%s]\n" % (gatherkeys.keytype_identity(keytype)))
+        f.write("key=fred\n")
+
+
+def get_key_dynamic(keytype, key_path):
+    with open(key_path, 'w', 0o600) as f:
+        f.write("[%s]\n" % (gatherkeys.keytype_identity(keytype)))
+        f.write("key='%s'" % (new.generate_auth_key()))
+
+
+def mock_time_strftime(time_format):
+    return "20160412144231"
+
+
+def mock_get_keys_fail(args, host, dest_dir):
+    return False
+
+
+def mock_get_keys_sucess_static(args, host, dest_dir):
+    for keytype in ["admin", "mon", "osd", "mds", "mgr", "rgw"]:
+        keypath = gatherkeys.keytype_path_to(args, keytype)
+        path = "%s/%s" % (dest_dir, keypath)
+        get_key_static(keytype, path)
+    return True
+
+
+def mock_get_keys_sucess_dynamic(args, host, dest_dir):
+    for keytype in ["admin", "mon", "osd", "mds", "mgr", "rgw"]:
+        keypath = gatherkeys.keytype_path_to(args, keytype)
+        path = "%s/%s" % (dest_dir, keypath)
+        get_key_dynamic(keytype, path)
+    return True
+
+
+class TestGatherKeys(object):
+    """
+    Since we are testing things that effect the content of the current working
+    directory we should test in a clean empty directory.
+    """
+    def setup(self):
+        """
+        Make temp directory for tests and set as current working directory
+        """
+        self.orginaldir = os.getcwd()
+        self.test_dir = tempfile.mkdtemp()
+        os.chdir(self.test_dir)
+
+
+    def teardown(self):
+        """
+        Set current working directory to old value
+        Remove temp directory and content
+        """
+        os.chdir(self.orginaldir)
+        shutil.rmtree(self.test_dir)
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_with_mon', mock_get_keys_fail)
+    def test_gatherkeys_fail(self):
+        """
+        Test 'gatherkeys' fails when connecting to mon fails.
+        """
+        args = mock.Mock()
+        args.cluster = "ceph"
+        args.mon = ['host1']
+        with pytest.raises(RuntimeError):
+            gatherkeys.gatherkeys(args)
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_with_mon', mock_get_keys_sucess_static)
+    def test_gatherkeys_success(self):
+        """
+        Test 'gatherkeys' succeeds when getinig keys that are always the same.
+        Test 'gatherkeys' does not backup identical keys
+        """
+        args = mock.Mock()
+        args.cluster = "ceph"
+        args.mon = ['host1']
+        gatherkeys.gatherkeys(args)
+        dir_content = os.listdir(self.test_dir)
+        assert "ceph.client.admin.keyring" in dir_content
+        assert "ceph.bootstrap-mds.keyring" in dir_content
+        assert "ceph.bootstrap-mgr.keyring" in dir_content
+        assert "ceph.mon.keyring" in dir_content
+        assert "ceph.bootstrap-osd.keyring" in dir_content
+        assert "ceph.bootstrap-rgw.keyring" in dir_content
+        assert len(dir_content) == 6
+        # Now we repeat as no new keys are generated
+        gatherkeys.gatherkeys(args)
+        dir_content = os.listdir(self.test_dir)
+        assert len(dir_content) == 6
+
+
+    @mock.patch('ceph_deploy.gatherkeys.time.strftime', mock_time_strftime)
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_with_mon', mock_get_keys_sucess_dynamic)
+    def test_gatherkeys_backs_up(self):
+        """
+        Test 'gatherkeys' succeeds when getting keys that are always different.
+        Test 'gatherkeys' does backup keys that are not identical.
+        """
+        args = mock.Mock()
+        args.cluster = "ceph"
+        args.mon = ['host1']
+        gatherkeys.gatherkeys(args)
+        dir_content = os.listdir(self.test_dir)
+        assert "ceph.client.admin.keyring" in dir_content
+        assert "ceph.bootstrap-mds.keyring" in dir_content
+        assert "ceph.bootstrap-mgr.keyring" in dir_content
+        assert "ceph.mon.keyring" in dir_content
+        assert "ceph.bootstrap-osd.keyring" in dir_content
+        assert "ceph.bootstrap-rgw.keyring" in dir_content
+        assert len(dir_content) == 6
+        # Now we repeat as new keys are generated and old
+        # are backed up
+        gatherkeys.gatherkeys(args)
+        dir_content = os.listdir(self.test_dir)
+        mocked_time = mock_time_strftime(None)
+        assert "ceph.client.admin.keyring" in dir_content
+        assert "ceph.bootstrap-mds.keyring" in dir_content
+        assert "ceph.bootstrap-mgr.keyring" in dir_content
+        assert "ceph.mon.keyring" in dir_content
+        assert "ceph.bootstrap-osd.keyring" in dir_content
+        assert "ceph.bootstrap-rgw.keyring" in dir_content
+        assert "ceph.client.admin.keyring-%s" % (mocked_time) in dir_content
+        assert "ceph.bootstrap-mds.keyring-%s" % (mocked_time) in dir_content
+        assert "ceph.bootstrap-mgr.keyring-%s" % (mocked_time) in dir_content
+        assert "ceph.mon.keyring-%s" % (mocked_time) in dir_content
+        assert "ceph.bootstrap-osd.keyring-%s" % (mocked_time) in dir_content
+        assert "ceph.bootstrap-rgw.keyring-%s" % (mocked_time) in dir_content
+        assert len(dir_content) == 12
diff --git a/ceph_deploy/tests/test_gather_keys_missing.py b/ceph_deploy/tests/test_gather_keys_missing.py
new file mode 100644 (file)
index 0000000..0369aa8
--- /dev/null
@@ -0,0 +1,179 @@
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import mock
+import tempfile
+import shutil
+import os
+import pytest
+
+
+class mock_conn(object):
+    def __init__(self):
+        pass
+
+class mock_distro(object):
+    def __init__(self):
+        self.conn = mock_conn()
+
+class mock_rlogger(object):
+    def error(self, *arg):
+        return
+
+    def debug(self, *arg):
+        return
+
+
+def mock_remoto_process_check_success(conn, args):
+    secret = new.generate_auth_key()
+    out = '[mon.]\nkey = %s\ncaps mon = allow *\n' % secret
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+def mock_remoto_process_check_rc_error(conn, args):
+    return [b""], [b"this failed\n"], 1
+
+
+class TestGatherKeysMissing(object):
+    """
+    Since we are testing things that effect the content a directory we should
+    test in a clean empty directory.
+    """
+
+    def setup(self):
+        """
+        Make temp directory for tests.
+        """
+        self.args = mock.Mock()
+        self.distro = mock_distro()
+        self.test_dir = tempfile.mkdtemp()
+        self.rlogger = mock_rlogger()
+        self.keypath_remote = "some_path"
+
+    def teardown(self):
+        """
+        Remove temp directory and content
+        """
+        shutil.rmtree(self.test_dir)
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_success_admin(self):
+        keytype = 'admin'
+        rc = gatherkeys.gatherkeys_missing(
+            self.args,
+            self.distro,
+            self.rlogger,
+            self.keypath_remote,
+            keytype,
+            self.test_dir
+            )
+        assert rc is True
+        keyname = gatherkeys.keytype_path_to(self.args, keytype)
+        keypath_gen = os.path.join(self.test_dir, keyname)
+        assert os.path.isfile(keypath_gen)
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_success_mds(self):
+        keytype = 'mds'
+        rc = gatherkeys.gatherkeys_missing(
+            self.args,
+            self.distro,
+            self.rlogger,
+            self.keypath_remote,
+            keytype,
+            self.test_dir
+            )
+        assert rc is True
+        keyname = gatherkeys.keytype_path_to(self.args, keytype)
+        keypath_gen = os.path.join(self.test_dir, keyname)
+        assert os.path.isfile(keypath_gen)
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_success_mgr(self):
+        keytype = 'mgr'
+        rc = gatherkeys.gatherkeys_missing(
+            self.args,
+            self.distro,
+            self.rlogger,
+            self.keypath_remote,
+            keytype,
+            self.test_dir
+            )
+        assert rc is True
+        keyname = gatherkeys.keytype_path_to(self.args, keytype)
+        keypath_gen = os.path.join(self.test_dir, keyname)
+        assert os.path.isfile(keypath_gen)
+        
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_success_osd(self):
+        keytype = 'osd'
+        rc = gatherkeys.gatherkeys_missing(
+            self.args,
+            self.distro,
+            self.rlogger,
+            self.keypath_remote,
+            keytype,
+            self.test_dir
+            )
+        assert rc is True
+        keyname = gatherkeys.keytype_path_to(self.args, keytype)
+        keypath_gen = os.path.join(self.test_dir, keyname)
+        assert os.path.isfile(keypath_gen)
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_success_rgw(self):
+        keytype = 'rgw'
+        rc = gatherkeys.gatherkeys_missing(
+            self.args,
+            self.distro,
+            self.rlogger,
+            self.keypath_remote,
+            keytype,
+            self.test_dir
+            )
+        assert rc is True
+        keyname = gatherkeys.keytype_path_to(self.args, keytype)
+        keypath_gen = os.path.join(self.test_dir, keyname)
+        assert os.path.isfile(keypath_gen)
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_rc_error)
+    def test_remoto_process_check_rc_error(self):
+        keytype = 'admin'
+        rc = gatherkeys.gatherkeys_missing(
+            self.args,
+            self.distro,
+            self.rlogger,
+            self.keypath_remote,
+            keytype,
+            self.test_dir
+            )
+        assert rc is False
+        keyname = gatherkeys.keytype_path_to(self.args, keytype)
+        keypath_gen = os.path.join(self.test_dir, keyname)
+        assert not os.path.isfile(keypath_gen)
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_fail_identity_missing(self):
+        keytype = 'silly'
+        with pytest.raises(RuntimeError):
+            gatherkeys.gatherkeys_missing(
+                self.args,
+                self.distro,
+                self.rlogger,
+                self.keypath_remote,
+                keytype,
+                self.test_dir
+                )
+
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    def test_fail_capabilities_missing(self):
+        keytype = 'mon'
+        with pytest.raises(RuntimeError):
+            gatherkeys.gatherkeys_missing(
+                self.args,
+                self.distro,
+                self.rlogger,
+                self.keypath_remote,
+                keytype,
+                self.test_dir
+                )
+
diff --git a/ceph_deploy/tests/test_gather_keys_with_mon.py b/ceph_deploy/tests/test_gather_keys_with_mon.py
new file mode 100644 (file)
index 0000000..805cc17
--- /dev/null
@@ -0,0 +1,219 @@
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import mock
+import json
+import copy
+
+
+remoto_process_check_success_output = {
+        "name": "ceph-node1",
+        "rank": 0,
+        "state": "leader",
+        "election_epoch": 6,
+        "quorum": [
+            0,
+            1,
+            2
+        ],
+        "outside_quorum": [],
+        "extra_probe_peers": [
+            "192.168.99.125:6789\/0",
+            "192.168.99.126:6789\/0"
+        ],
+        "sync_provider": [],
+        "monmap": {
+            "epoch": 1,
+            "fsid": "4dbee7eb-929b-4f3f-ad23-8a4e47235e40",
+            "modified": "2016-04-11 05:35:21.665220",
+            "created": "2016-04-11 05:35:21.665220",
+            "mons": [
+                {
+                    "rank": 0,
+                    "name": "host0",
+                    "addr": "192.168.99.124:6789\/0"
+                },
+                {
+                    "rank": 1,
+                    "name": "host1",
+                    "addr": "192.168.99.125:6789\/0"
+                },
+                {
+                    "rank": 2,
+                    "name": "host2",
+                    "addr": "192.168.99.126:6789\/0"
+                }
+            ]
+        }
+    }
+
+
+class mock_remote_module(object):
+    def get_file(self, path):
+        return self.get_file_result
+
+    def shortname(self):
+        hostname_split = self.longhostname.split('.')
+        return hostname_split[0]
+
+class mock_conn(object):
+    def __init__(self):
+        self.remote_module = mock_remote_module()
+
+
+class mock_distro(object):
+    def __init__(self):
+        self.conn = mock_conn()
+        
+
+def mock_hosts_get_file_key_content(host, **kwargs):
+    output = mock_distro()
+    mon_keyring = '[mon.]\nkey = %s\ncaps mon = allow *\n' % new.generate_auth_key()
+    output.conn.remote_module.get_file_result = mon_keyring.encode('utf-8')
+    output.conn.remote_module.longhostname = host
+    return output
+
+
+def mock_hosts_get_file_key_content_none(host, **kwargs):
+    output = mock_distro()
+    output.conn.remote_module.get_file_result = None
+    output.conn.remote_module.longhostname = host
+    return output
+
+
+def mock_gatherkeys_missing_success(args, distro, rlogger, path_keytype_mon, keytype, dest_dir):
+    return True
+
+
+def mock_gatherkeys_missing_fail(args, distro, rlogger, path_keytype_mon, keytype, dest_dir):
+    return False
+
+
+def mock_remoto_process_check_success(conn, args):
+    out = json.dumps(remoto_process_check_success_output,sort_keys=True, indent=4)
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+def mock_remoto_process_check_rc_error(conn, args):
+    return [b""], [b"this failed\n"], 1
+
+
+def mock_remoto_process_check_out_not_json(conn, args):
+    return [b"}bad output{"], [b""], 0
+
+
+def mock_remoto_process_check_out_missing_quorum(conn, args):
+    outdata = copy.deepcopy(remoto_process_check_success_output)
+    del outdata["quorum"]
+    out = json.dumps(outdata,sort_keys=True, indent=4)
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+def mock_remoto_process_check_out_missing_quorum_1(conn, args):
+    outdata = copy.deepcopy(remoto_process_check_success_output)
+    del outdata["quorum"][1]
+    out = json.dumps(outdata,sort_keys=True, indent=4)
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+def mock_remoto_process_check_out_missing_monmap(conn, args):
+    outdata = copy.deepcopy(remoto_process_check_success_output)
+    del outdata["monmap"]
+    out = json.dumps(outdata,sort_keys=True, indent=4)
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+def mock_remoto_process_check_out_missing_mons(conn, args):
+    outdata = copy.deepcopy(remoto_process_check_success_output)
+    del outdata["monmap"]["mons"]
+    out = json.dumps(outdata,sort_keys=True, indent=4)
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+def mock_remoto_process_check_out_missing_monmap_host1(conn, args):
+    outdata = copy.deepcopy(remoto_process_check_success_output)
+    del outdata["monmap"]["mons"][1]
+    out = json.dumps(outdata,sort_keys=True, indent=4)
+    return out.encode('utf-8').split(b'\n'), [], 0
+
+
+class TestGatherKeysWithMon(object):
+    """
+    Test gatherkeys_with_mon function
+    """
+    def setup(self):
+        self.args = mock.Mock()
+        self.args.cluster = "ceph"
+        self.args.mon = ['host1']
+        self.host = 'host1'
+        self.test_dir = '/tmp'
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_success(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is True
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content_none)
+    def test_monkey_none(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_fail)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_success)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_missing_fail(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_rc_error)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_remoto_process_check_rc_error(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_not_json)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_remoto_process_check_out_not_json(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_quorum)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_remoto_process_check_out_missing_quorum(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_quorum_1)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_remoto_process_check_out_missing_quorum_1(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_mons)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_remoto_process_check_out_missing_mon(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
+
+
+    @mock.patch('ceph_deploy.gatherkeys.gatherkeys_missing', mock_gatherkeys_missing_success)
+    @mock.patch('ceph_deploy.lib.remoto.process.check', mock_remoto_process_check_out_missing_monmap_host1)
+    @mock.patch('ceph_deploy.hosts.get', mock_hosts_get_file_key_content)
+    def test_remoto_process_check_out_missing_monmap_host1(self):
+        rc = gatherkeys.gatherkeys_with_mon(self.args, self.host, self.test_dir)
+        assert rc is False
diff --git a/ceph_deploy/tests/test_install.py b/ceph_deploy/tests/test_install.py
new file mode 100644 (file)
index 0000000..4a47f9d
--- /dev/null
@@ -0,0 +1,149 @@
+from mock import Mock
+
+from ceph_deploy import install
+
+
+class TestSanitizeArgs(object):
+
+    def setup(self):
+        self.args = Mock()
+        # set the default behavior we set in cli.py
+        self.args.default_release = False
+        self.args.stable = None
+
+    def test_args_release_not_specified(self):
+        self.args.release = None
+        result = install.sanitize_args(self.args)
+        # XXX
+        # we should get `args.release` to be the latest release
+        # but we don't want to be updating this test every single
+        # time there is a new default value, and we can't programatically
+        # change that. Future improvement: make the default release a
+        # variable in `ceph_deploy/__init__.py`
+        assert result.default_release is True
+
+    def test_args_release_is_specified(self):
+        self.args.release = 'dumpling'
+        result = install.sanitize_args(self.args)
+        assert result.default_release is False
+
+    def test_args_release_stable_is_used(self):
+        self.args.stable = 'dumpling'
+        result = install.sanitize_args(self.args)
+        assert result.release == 'dumpling'
+
+    def test_args_stable_is_not_used(self):
+        self.args.release = 'dumpling'
+        result = install.sanitize_args(self.args)
+        assert result.stable is None
+
+
+class TestDetectComponents(object):
+
+    def setup(self):
+        self.args = Mock()
+        # default values for install_* flags
+        self.args.install_all = False
+        self.args.install_mds = False
+        self.args.install_mgr = False
+        self.args.install_mon = False
+        self.args.install_osd = False
+        self.args.install_rgw = False
+        self.args.install_tests = False
+        self.args.install_common = False
+        self.args.repo = False
+        self.distro = Mock()
+
+    def test_install_with_repo_option_returns_no_packages(self):
+        self.args.repo = True
+        result = install.detect_components(self.args, self.distro)
+        assert result == []
+
+    def test_install_all_returns_all_packages_deb(self):
+        self.args.install_all = True
+        self.distro.is_rpm = False
+        self.distro.is_deb = True
+        self.distro.is_pkgtarxz = False
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph-osd', 'ceph-mds', 'ceph', 'ceph-mon', 'radosgw'
+        ])
+
+    def test_install_all_with_other_options_returns_all_packages_deb(self):
+        self.distro.is_rpm = False
+        self.distro.is_deb = True
+        self.distro.is_pkgtarxz = False
+        self.args.install_all = True
+        self.args.install_mds = True
+        self.args.install_mgr = True
+        self.args.install_mon = True
+        self.args.install_osd = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph-osd', 'ceph-mds', 'ceph', 'ceph-mon', 'radosgw'
+        ])
+
+    def test_install_all_returns_all_packages_rpm(self):
+        self.args.install_all = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph-osd', 'ceph-mds', 'ceph', 'ceph-mon', 'ceph-radosgw'
+        ])
+
+    def test_install_all_with_other_options_returns_all_packages_rpm(self):
+        self.args.install_all = True
+        self.args.install_mds = True
+        self.args.install_mon = True
+        self.args.install_mgr = True
+        self.args.install_osd = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph-osd', 'ceph-mds', 'ceph', 'ceph-mon', 'ceph-radosgw'
+        ])
+
+    def test_install_all_returns_all_packages_pkgtarxz(self):
+        self.args.install_all = True
+        self.distro.is_rpm = False
+        self.distro.is_deb = False
+        self.distro.is_pkgtarxz = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph',
+        ])
+
+    def test_install_all_with_other_options_returns_all_packages_pkgtarxz(self):
+        self.distro.is_rpm = False
+        self.distro.is_deb = False
+        self.distro.is_pkgtarxz = True
+        self.args.install_all = True
+        self.args.install_mds = True
+        self.args.install_mgr = True
+        self.args.install_mon = True
+        self.args.install_osd = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph',
+        ])
+
+    def test_install_only_one_component(self):
+        self.args.install_osd = True
+        result = install.detect_components(self.args, self.distro)
+        assert result == ['ceph-osd']
+
+    def test_install_a_couple_of_components(self):
+        self.args.install_osd = True
+        self.args.install_mds = True
+        self.args.install_mgr = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted(['ceph-osd', 'ceph-mds', 'ceph-mgr'])
+
+    def test_install_tests(self):
+        self.args.install_tests = True
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted(['ceph-test'])
+
+    def test_install_all_should_be_default_when_no_options_passed(self):
+        result = sorted(install.detect_components(self.args, self.distro))
+        assert result == sorted([
+            'ceph-osd', 'ceph-mds', 'ceph', 'ceph-mon', 'ceph-radosgw'
+        ])
diff --git a/ceph_deploy/tests/test_keys_equivalent.py b/ceph_deploy/tests/test_keys_equivalent.py
new file mode 100644 (file)
index 0000000..c748baf
--- /dev/null
@@ -0,0 +1,171 @@
+from ceph_deploy import gatherkeys
+from ceph_deploy import new
+import tempfile
+import shutil
+import pytest
+
+
+def write_key_mon_with_caps(path, secret):
+    mon_keyring = '[mon.]\nkey = %s\ncaps mon = allow *\n' % secret
+    with open(path, 'w', 0o600) as f:
+        f.write(mon_keyring)
+
+
+def write_key_mon_with_caps_with_tab(path, secret):
+    mon_keyring = '[mon.]\n\tkey = %s\n\tcaps mon = allow *\n' % secret
+    with open(path, 'w', 0o600) as f:
+        f.write(mon_keyring)
+
+
+def write_key_mon_with_caps_with_tab_quote(path, secret):
+    mon_keyring = '[mon.]\n\tkey = %s\n\tcaps mon = "allow *"\n' % secret
+    with open(path, 'w', 0o600) as f:
+        f.write(mon_keyring)
+
+
+def write_key_mon_without_caps(path, secret):
+    mon_keyring = '[mon.]\nkey = %s\n' % secret
+    with open(path, 'w', 0o600) as f:
+        f.write(mon_keyring)
+
+
+class TestKeysEquivalent(object):
+    """
+    Since we are testing things that effect the content of the current working
+    directory we should test in a clean empty directory.
+    """
+    def setup(self):
+        """
+        Make temp directory for tests.
+        """
+        self.test_dir = tempfile.mkdtemp()
+
+
+    def teardown(self):
+        """
+        Remove temp directory and content
+        """
+        shutil.rmtree(self.test_dir)
+
+
+    def test_identical_with_caps(self):
+        secret_01 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps(key_path_01, secret_01)
+        write_key_mon_with_caps(key_path_02, secret_01)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is True
+
+
+    def test_different_with_caps(self):
+        secret_01 = new.generate_auth_key()
+        secret_02 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps(key_path_01, secret_01)
+        write_key_mon_with_caps(key_path_02, secret_02)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is False
+
+
+    def test_identical_without_caps(self):
+        secret_01 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_without_caps(key_path_01, secret_01)
+        write_key_mon_without_caps(key_path_02, secret_01)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is True
+
+
+    def test_different_without_caps(self):
+        secret_01 = new.generate_auth_key()
+        secret_02 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_without_caps(key_path_01, secret_01)
+        write_key_mon_without_caps(key_path_02, secret_02)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is False
+
+
+    def test_identical_mixed_caps(self):
+        secret_01 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps(key_path_01, secret_01)
+        write_key_mon_without_caps(key_path_02, secret_01)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is True
+
+
+    def test_different_mixed_caps(self):
+        secret_01 = new.generate_auth_key()
+        secret_02 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps(key_path_01, secret_01)
+        write_key_mon_without_caps(key_path_02, secret_02)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is False
+
+
+    def test_identical_caps_mixed_tabs(self):
+        secret_01 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps(key_path_01, secret_01)
+        write_key_mon_with_caps_with_tab(key_path_02, secret_01)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is True
+
+
+    def test_different_caps_mixed_tabs(self):
+        secret_01 = new.generate_auth_key()
+        secret_02 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps(key_path_01, secret_01)
+        write_key_mon_with_caps_with_tab(key_path_02, secret_02)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is False
+
+
+    def test_identical_caps_mixed_quote(self):
+        secret_01 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps_with_tab(key_path_01, secret_01)
+        write_key_mon_with_caps_with_tab_quote(key_path_02, secret_01)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is True
+
+
+    def test_different_caps_mixed_quote(self):
+        secret_01 = new.generate_auth_key()
+        secret_02 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps_with_tab(key_path_01, secret_01)
+        write_key_mon_with_caps_with_tab_quote(key_path_02, secret_02)
+        same = gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+        assert same is False
+
+
+    def test_missing_key_1(self):
+        secret_02 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps_with_tab_quote(key_path_02, secret_02)
+        with pytest.raises(IOError):
+            gatherkeys._keyring_equivalent(key_path_01, key_path_02)
+
+
+    def test_missing_key_2(self):
+        secret_01 = new.generate_auth_key()
+        key_path_01 = self.test_dir + "/01.keyring"
+        key_path_02 = self.test_dir + "/02.keyring"
+        write_key_mon_with_caps_with_tab_quote(key_path_01, secret_01)
+        with pytest.raises(IOError):
+            gatherkeys._keyring_equivalent(key_path_01, key_path_02)
diff --git a/ceph_deploy/tests/test_mon.py b/ceph_deploy/tests/test_mon.py
new file mode 100644 (file)
index 0000000..585aa41
--- /dev/null
@@ -0,0 +1,93 @@
+from ceph_deploy import exc, mon
+from ceph_deploy.conf.ceph import CephConf
+from mock import Mock
+import pytest
+
+
+def make_fake_conf():
+    return CephConf()
+
+# NOTE: If at some point we re-use this helper, move it out
+# and make it even more generic
+
+def make_fake_conn(receive_returns=None):
+    receive_returns = receive_returns or ([b'{}'], [], 0)
+    conn = Mock()
+    conn.return_value = conn
+    conn.execute = conn
+    conn.receive = Mock(return_value=receive_returns)
+    conn.gateway.remote_exec = conn.receive
+    conn.result = Mock(return_value=conn)
+    conn.cmd = lambda x: x
+    return conn
+
+
+class TestGetMonInitialMembers(object):
+
+    def test_assert_if_mon_none_and_empty_True(self):
+        cfg = make_fake_conf()
+        with pytest.raises(exc.NeedHostError):
+            mon.get_mon_initial_members(Mock(), True, cfg)
+
+    def test_return_if_mon_none_and_empty_false(self):
+        cfg = make_fake_conf()
+        mon_initial_members = mon.get_mon_initial_members(Mock(), False, cfg)
+        assert mon_initial_members is None
+
+    def test_single_item_if_mon_not_none(self):
+        cfg = make_fake_conf()
+        cfg.add_section('global')
+        cfg.set('global', 'mon initial members', 'AAAA')
+        mon_initial_members = mon.get_mon_initial_members(Mock(), False, cfg)
+        assert set(mon_initial_members) == set(['AAAA'])
+
+    def test_multiple_item_if_mon_not_none(self):
+        cfg = make_fake_conf()
+        cfg.add_section('global')
+        cfg.set('global', 'mon initial members', 'AAAA, BBBB')
+        mon_initial_members = mon.get_mon_initial_members(Mock(), False, cfg)
+        assert set(mon_initial_members) == set(['AAAA', 'BBBB'])
+
+
+class TestCatchCommonErrors(object):
+
+    def setup(self):
+        self.logger = Mock()
+
+    def assert_logger_message(self, logger, msg):
+        calls = logger.call_args_list
+        for log_call in calls:
+            if msg in log_call[0][0]:
+                return True
+        raise AssertionError('"%s" was not found in any of %s' % (msg, calls))
+
+    def test_warn_if_no_intial_members(self):
+        fake_conn = make_fake_conn()
+        cfg = make_fake_conf()
+        mon.catch_mon_errors(fake_conn, self.logger, 'host', cfg, Mock())
+        expected_msg = 'is not defined in `mon initial members`'
+        self.assert_logger_message(self.logger.warning, expected_msg)
+
+    def test_warn_if_host_not_in_intial_members(self):
+        fake_conn = make_fake_conn()
+        cfg = make_fake_conf()
+        cfg.add_section('global')
+        cfg.set('global', 'mon initial members', 'AAAA')
+        mon.catch_mon_errors(fake_conn, self.logger, 'host', cfg, Mock())
+        expected_msg = 'is not defined in `mon initial members`'
+        self.assert_logger_message(self.logger.warning, expected_msg)
+
+    def test_warn_if_not_mon_in_monmap(self):
+        fake_conn = make_fake_conn()
+        cfg = make_fake_conf()
+        mon.catch_mon_errors(fake_conn, self.logger, 'host', cfg, Mock())
+        expected_msg = 'does not exist in monmap'
+        self.assert_logger_message(self.logger.warning, expected_msg)
+
+    def test_warn_if_not_public_addr_and_not_public_netw(self):
+        fake_conn = make_fake_conn()
+        cfg = make_fake_conf()
+        cfg.add_section('global')
+        mon.catch_mon_errors(fake_conn, self.logger, 'host', cfg, Mock())
+        expected_msg = 'neither `public_addr` nor `public_network`'
+        self.assert_logger_message(self.logger.warning, expected_msg)
diff --git a/ceph_deploy/tests/test_remotes.py b/ceph_deploy/tests/test_remotes.py
new file mode 100644 (file)
index 0000000..4c7ac03
--- /dev/null
@@ -0,0 +1,255 @@
+from mock import patch
+from ceph_deploy.hosts import remotes
+from ceph_deploy.hosts.remotes import platform_information, parse_os_release
+
+class FakeExists(object):
+
+    def __init__(self, existing_paths):
+        self.existing_paths = existing_paths
+
+    def __call__(self, path):
+        for existing_path in self.existing_paths:
+            if path == existing_path:
+                return path
+
+
+class TestWhich(object):
+
+    def setup(self):
+        self.exists_module = 'ceph_deploy.hosts.remotes.os.path.exists'
+
+    def test_finds_absolute_paths(self):
+        exists = FakeExists(['/bin/ls'])
+        with patch(self.exists_module, exists):
+            path = remotes.which('ls')
+        assert path == '/bin/ls'
+
+    def test_does_not_find_executable(self):
+        exists = FakeExists(['/bin/foo'])
+        with patch(self.exists_module, exists):
+            path = remotes.which('ls')
+        assert path is None
+
+class TestPlatformInformation(object):
+    """ tests various inputs that remotes.platform_information handles
+
+    you can test your OS string by comparing the results with the output from:
+      python -c "import platform; print platform.linux_distribution()"
+    """
+
+    def setup(self):
+        pass
+
+    def test_handles_deb_version_num(self):
+        def fake_distro(): return ('debian', '8.4', '')
+        distro, release, codename = platform_information(fake_distro)
+        assert distro == 'debian'
+        assert release == '8.4'
+        assert codename == 'jessie'
+
+    def test_handles_deb_version_slash(self):
+        def fake_distro(): return ('debian', 'wheezy/something', '')
+        distro, release, codename = platform_information(fake_distro)
+        assert distro == 'debian'
+        assert release == 'wheezy/something'
+        assert codename == 'wheezy'
+
+    def test_handles_deb_version_slash_sid(self):
+        def fake_distro(): return ('debian', 'jessie/sid', '')
+        distro, release, codename = platform_information(fake_distro)
+        assert distro == 'debian'
+        assert release == 'jessie/sid'
+        assert codename == 'sid'
+
+    def test_handles_no_codename(self):
+        def fake_distro(): return ('SlaOS', '99.999', '')
+        distro, release, codename = platform_information(fake_distro)
+        assert distro == 'SlaOS'
+        assert release == '99.999'
+        assert codename == ''
+
+    # Normal distro strings
+    def test_hanles_centos_64(self):
+        def fake_distro(): return ('CentOS', '6.4', 'Final')
+        distro, release, codename = platform_information(fake_distro)
+        assert distro == 'CentOS'
+        assert release == '6.4'
+        assert codename == 'Final'
+
+
+    def test_handles_ubuntu_percise(self):
+        def fake_distro(): return ('Ubuntu', '12.04', 'precise')
+        distro, release, codename = platform_information(fake_distro)
+        assert distro == 'Ubuntu'
+        assert release == '12.04'
+        assert codename == 'precise'
+
+class TestParseOsRelease(object):
+    """ test various forms of /etc/os-release """
+
+    def setup(self):
+        pass
+
+    def test_handles_centos_7(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME="CentOS Linux"
+VERSION="7 (Core)"
+ID="centos"
+ID_LIKE="rhel fedora"
+VERSION_ID="7"
+PRETTY_NAME="CentOS Linux 7 (Core)"
+ANSI_COLOR="0;31"
+CPE_NAME="cpe:/o:centos:centos:7"
+HOME_URL="https://www.centos.org/"
+BUG_REPORT_URL="https://bugs.centos.org/"
+
+CENTOS_MANTISBT_PROJECT="CentOS-7"
+CENTOS_MANTISBT_PROJECT_VERSION="7"
+REDHAT_SUPPORT_PRODUCT="centos"
+REDHAT_SUPPORT_PRODUCT_VERSION="7"
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'centos'
+        assert release == '7'
+        assert codename == 'core'
+
+
+    def test_handles_debian_stretch(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
+NAME="Debian GNU/Linux"
+VERSION_ID="9"
+VERSION="9 (stretch)"
+ID=debian
+HOME_URL="https://www.debian.org/"
+SUPPORT_URL="https://www.debian.org/support"
+BUG_REPORT_URL="https://bugs.debian.org/"
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'debian'
+        assert release == '9'
+        assert codename == 'stretch'
+
+    def test_handles_fedora_26(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME=Fedora
+VERSION="26 (Twenty Six)"
+ID=fedora
+VERSION_ID=26
+PRETTY_NAME="Fedora 26 (Twenty Six)"
+ANSI_COLOR="0;34"
+CPE_NAME="cpe:/o:fedoraproject:fedora:26"
+HOME_URL="https://fedoraproject.org/"
+BUG_REPORT_URL="https://bugzilla.redhat.com/"
+REDHAT_BUGZILLA_PRODUCT="Fedora"
+REDHAT_BUGZILLA_PRODUCT_VERSION=26
+REDHAT_SUPPORT_PRODUCT="Fedora"
+REDHAT_SUPPORT_PRODUCT_VERSION=26
+PRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'fedora'
+        assert release == '26'
+        assert codename == 'twenty six'
+
+    def test_handles_opensuse_leap_42_2(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME="openSUSE Leap"
+VERSION="42.2"
+ID=opensuse
+ID_LIKE="suse"
+VERSION_ID="42.2"
+PRETTY_NAME="openSUSE Leap 42.2"
+ANSI_COLOR="0;32"
+CPE_NAME="cpe:/o:opensuse:leap:42.2"
+BUG_REPORT_URL="https://bugs.opensuse.org"
+HOME_URL="https://www.opensuse.org/"
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'opensuse'
+        assert release == '42.2'
+        assert codename == '42.2'
+
+    def test_handles_opensuse_tumbleweed(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME="openSUSE Tumbleweed"
+# VERSION="20170502"
+ID=opensuse
+ID_LIKE="suse"
+VERSION_ID="20170502"
+PRETTY_NAME="openSUSE Tumbleweed"
+ANSI_COLOR="0;32"
+CPE_NAME="cpe:/o:opensuse:tumbleweed:20170502"
+BUG_REPORT_URL="https://bugs.opensuse.org"
+HOME_URL="https://www.opensuse.org/"
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'opensuse'
+        assert release == '20170502'
+        assert codename == 'tumbleweed'
+
+    def test_handles_sles_12_sp3(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME="SLES"
+VERSION="12-SP3"
+VERSION_ID="12.3"
+PRETTY_NAME="SUSE Linux Enterprise Server 12 SP3"
+ID="sles"
+ANSI_COLOR="0;32"
+CPE_NAME="cpe:/o:suse:sles:12:sp3"
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'sles'
+        assert release == '12.3'
+        assert codename == '12-SP3'
+
+    def test_handles_ubuntu_xenial(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME="Ubuntu"
+VERSION="16.04 LTS (Xenial Xerus)"
+ID=ubuntu
+ID_LIKE=debian
+PRETTY_NAME="Ubuntu 16.04 LTS"
+VERSION_ID="16.04"
+HOME_URL="http://www.ubuntu.com/"
+SUPPORT_URL="http://help.ubuntu.com/"
+BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
+UBUNTU_CODENAME=xenial
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'ubuntu'
+        assert release == '16.04'
+        assert codename == 'xenial'
+
+    def test_handles_alt_8_2(self, tmpdir):
+        path = str(tmpdir.join('os_release'))
+        with open(path, 'w') as os_release:
+            os_release.write("""
+NAME="ALT"
+VERSION="8.2 "
+ID=altlinux
+VERSION_ID=8.2
+PRETTY_NAME="ALT Workstation K 8.2  (Centaurea Ruthenica)"
+ANSI_COLOR="1;33"
+CPE_NAME="cpe:/o:alt:kworkstation:8.2"
+HOME_URL="http://www.basealt.ru"
+BUG_REPORT_URL="https://bugs.altlinux.org/"
+""")
+        distro, release, codename = parse_os_release(path)
+        assert distro == 'altlinux'
+        assert release == '8.2'
+        assert codename == '8.2'
diff --git a/ceph_deploy/tests/unit/hosts/test_altlinux.py b/ceph_deploy/tests/unit/hosts/test_altlinux.py
new file mode 100644 (file)
index 0000000..cc65fde
--- /dev/null
@@ -0,0 +1,10 @@
+from ceph_deploy.hosts.alt.install import map_components, NON_SPLIT_PACKAGES
+
+
+class TestALTMapComponents(object):
+    def test_valid(self):
+        pkgs = map_components(NON_SPLIT_PACKAGES, ['ceph-osd', 'ceph-common', 'ceph-radosgw'])
+        assert 'ceph' in pkgs
+        assert 'ceph-common' in pkgs
+        assert 'ceph-radosgw' in pkgs
+        assert 'ceph-osd' not in pkgs
diff --git a/ceph_deploy/tests/unit/hosts/test_centos.py b/ceph_deploy/tests/unit/hosts/test_centos.py
new file mode 100644 (file)
index 0000000..187582c
--- /dev/null
@@ -0,0 +1,64 @@
+from ceph_deploy.hosts import centos
+from ceph_deploy import hosts
+from mock import Mock, patch
+
+
+def pytest_generate_tests(metafunc):
+    # called once per each test function
+    try:
+        funcarglist = metafunc.cls.params[metafunc.function.__name__]
+    except AttributeError:
+        return
+    argnames = list(funcarglist[0])
+    metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
+                                    for funcargs in funcarglist])
+
+
+class TestCentosRepositoryUrlPart(object):
+
+    params= {
+        'test_repository_url_part': [
+            dict(distro="CentOS Linux", release='4.3', codename="Foo", output='el6'),
+            dict(distro="CentOS Linux", release='6.5', codename="Final", output='el6'),
+            dict(distro="CentOS Linux", release='7.0', codename="Core", output='el7'),
+            dict(distro="CentOS Linux", release='7.0.1406', codename="Core", output='el7'),
+            dict(distro="CentOS Linux", release='10.4.000', codename="Core", output='el10'),
+            dict(distro="RedHat", release='4.3', codename="Foo", output='el6'),
+            dict(distro="Red Hat Enterprise Linux Server", release='5.8', codename="Tikanga", output="el6"),
+            dict(distro="Red Hat Enterprise Linux Server", release='6.5', codename="Santiago", output='rhel6'),
+            dict(distro="RedHat", release='7.0.1406', codename="Core", output='rhel7'),
+            dict(distro="RedHat", release='10.999.12', codename="Core", output='rhel10'),
+            ],
+        'test_rpm_dist': [
+            dict(distro="CentOS Linux", release='4.3', codename="Foo", output='el6'),
+            dict(distro="CentOS Linux", release='6.5', codename="Final", output='el6'),
+            dict(distro="CentOS Linux", release='7.0', codename="Core", output='el7'),
+            dict(distro="CentOS Linux", release='7.0.1406', codename="Core", output='el7'),
+            dict(distro="CentOS Linux", release='10.10.9191', codename="Core", output='el10'),
+            dict(distro="RedHat", release='4.3', codename="Foo", output='el6'),
+            dict(distro="Red Hat Enterprise Linux Server", release='5.8', codename="Tikanga", output="el6"),
+            dict(distro="Red Hat Enterprise Linux Server", release='6.5', codename="Santiago", output='el6'),
+            dict(distro="RedHat", release='7.0', codename="Core", output='el7'),
+            dict(distro="RedHat", release='7.0.1406', codename="Core", output='el7'),
+            dict(distro="RedHat", release='10.9.8765', codename="Core", output='el10'),
+        ]
+    }
+
+    def make_fake_connection(self, platform_information=None):
+        get_connection = Mock()
+        get_connection.return_value = get_connection
+        get_connection.remote_module.platform_information = Mock(
+            return_value=platform_information)
+        return get_connection
+
+    def test_repository_url_part(self, distro, release, codename, output):
+        fake_get_connection = self.make_fake_connection((distro, release, codename))
+        with patch('ceph_deploy.hosts.get_connection', fake_get_connection):
+            self.module = hosts.get('testhost')
+        assert centos.repository_url_part(self.module) == output
+
+    def test_rpm_dist(self, distro, release, codename, output):
+        fake_get_connection = self.make_fake_connection((distro, release, codename))
+        with patch('ceph_deploy.hosts.get_connection', fake_get_connection):
+            self.module = hosts.get('testhost')
+        assert centos.rpm_dist(self.module) == output
diff --git a/ceph_deploy/tests/unit/hosts/test_common.py b/ceph_deploy/tests/unit/hosts/test_common.py
new file mode 100644 (file)
index 0000000..278d09e
--- /dev/null
@@ -0,0 +1,24 @@
+from ceph_deploy.hosts.common import map_components
+
+
+class TestMapComponents(object):
+
+    def test_map_components_all_split(self):
+        components = ['ceph-mon', 'ceph-osd']
+        packages = map_components([], components)
+        assert set(packages) == set(components)
+
+    def test_map_components_mds_not_split(self):
+        components = ['ceph-mon', 'ceph-osd', 'ceph-mds']
+        packages = map_components(['ceph-mds'], components)
+        assert set(packages) == set(['ceph-mon', 'ceph-osd', 'ceph'])
+
+    def test_map_components_no_duplicates(self):
+        components = ['ceph-mon', 'ceph-osd', 'ceph-mds']
+        packages = map_components(['ceph-mds', 'ceph-osd'], components)
+        assert set(packages) == set(['ceph-mon', 'ceph'])
+        assert len(packages) == len(set(['ceph-mon', 'ceph']))
+
+    def test_map_components_no_components(self):
+        packages = map_components(['ceph-mon'], [])
+        assert len(packages) == 0
diff --git a/ceph_deploy/tests/unit/hosts/test_hosts.py b/ceph_deploy/tests/unit/hosts/test_hosts.py
new file mode 100644 (file)
index 0000000..dbc448c
--- /dev/null
@@ -0,0 +1,433 @@
+from pytest import raises
+from mock import Mock, patch
+
+from ceph_deploy import exc
+from ceph_deploy import hosts
+
+
+class TestNormalized(object):
+
+    def test_get_debian(self):
+        result = hosts._normalized_distro_name('Debian')
+        assert result == 'debian'
+
+    def test_get_centos(self):
+        result = hosts._normalized_distro_name('CentOS Linux')
+        assert result == 'centos'
+
+    def test_get_ubuntu(self):
+        result = hosts._normalized_distro_name('Ubuntu')
+        assert result == 'ubuntu'
+
+    def test_get_mint(self):
+        result = hosts._normalized_distro_name('LinuxMint')
+        assert result == 'ubuntu'
+
+    def test_get_suse(self):
+        result = hosts._normalized_distro_name('SUSE LINUX')
+        assert result == 'suse'
+
+    def test_get_redhat(self):
+        result = hosts._normalized_distro_name('RedHatEnterpriseLinux')
+        assert result == 'redhat'
+
+    def test_get_virtuozzo(self):
+        result = hosts._normalized_distro_name('Virtuozzo Linux')
+        assert result == 'virtuozzo'
+
+    def test_get_arch(self):
+        result = hosts._normalized_distro_name('Arch Linux')
+        assert result == 'arch'
+
+
+class TestNormalizeRelease(object):
+
+    def test_int_single_version(self):
+        result = hosts._normalized_release('1')
+        assert result.int_major == 1
+        assert result.int_minor == 0
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_single_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1')
+        assert result.int_major == 1
+        assert result.int_minor == 0
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_single_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01')
+        assert result.int_major == 1
+        assert result.int_minor == 0
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_minor_version(self):
+        result = hosts._normalized_release('1.8')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_minor_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1.8')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_minor_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01.08')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_patch_version(self):
+        result = hosts._normalized_release('1.8.1234')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 0
+
+    def test_int_patch_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1.8.1234')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 0
+
+    def test_int_patch_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01.08.01234')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 0
+
+    def test_int_garbage_version(self):
+        result = hosts._normalized_release('1.8.1234.1')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 1
+
+    def test_int_garbage_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1.8.1234.1')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 1
+
+    def test_int_garbage_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01.08.01234.1')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 1
+
+    def test_int_single_version_rc(self):
+        result = hosts._normalized_release('1rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 0
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_single_version_with_trailing_space_rc(self):
+        result = hosts._normalized_release(' 1rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 0
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_single_version_with_prepended_zero_rc(self):
+        result = hosts._normalized_release('01rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 0
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_minor_version_rc(self):
+        result = hosts._normalized_release('1.8rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_minor_version_with_trailing_space_rc(self):
+        result = hosts._normalized_release(' 1.8rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_minor_version_with_prepended_zero_rc(self):
+        result = hosts._normalized_release('01.08rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 0
+        assert result.int_garbage == 0
+
+    def test_int_patch_version_rc(self):
+        result = hosts._normalized_release('1.8.1234rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 0
+
+    def test_int_patch_version_with_trailing_space_rc(self):
+        result = hosts._normalized_release(' 1.8.1234rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 0
+
+    def test_int_patch_version_with_prepended_zero_rc(self):
+        result = hosts._normalized_release('01.08.01234rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 0
+
+    def test_int_garbage_version_rc(self):
+        result = hosts._normalized_release('1.8.1234.1rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 1
+
+    def test_int_garbage_version_with_trailing_space_rc(self):
+        result = hosts._normalized_release(' 1.8.1234.1rc-123')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 1
+
+    def test_int_garbage_version_with_prepended_zero_rc(self):
+        result = hosts._normalized_release('01.08.01234.1rc-1')
+        assert result.int_major == 1
+        assert result.int_minor == 8
+        assert result.int_patch == 1234
+        assert result.int_garbage == 1
+
+    # with non ints
+
+    def test_single_version(self):
+        result = hosts._normalized_release('1')
+        assert result.major == "1"
+        assert result.minor == "0"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+    def test_single_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1')
+        assert result.major == "1"
+        assert result.minor == "0"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+    def test_single_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01')
+        assert result.major == "01"
+        assert result.minor == "0"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+    def test_minor_version(self):
+        result = hosts._normalized_release('1.8')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+    def test_minor_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1.8')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+    def test_minor_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01.08')
+        assert result.major == "01"
+        assert result.minor == "08"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+    def test_patch_version(self):
+        result = hosts._normalized_release('1.8.1234')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234"
+        assert result.garbage == "0"
+
+    def test_patch_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1.8.1234')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234"
+        assert result.garbage == "0"
+
+    def test_patch_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01.08.01234')
+        assert result.major == "01"
+        assert result.minor == "08"
+        assert result.patch == "01234"
+        assert result.garbage == "0"
+
+    def test_garbage_version(self):
+        result = hosts._normalized_release('1.8.1234.1')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234"
+        assert result.garbage == "1"
+
+    def test_garbage_version_with_trailing_space(self):
+        result = hosts._normalized_release(' 1.8.1234.1')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234"
+        assert result.garbage == "1"
+
+    def test_garbage_version_with_prepended_zero(self):
+        result = hosts._normalized_release('01.08.01234.1')
+        assert result.major == "01"
+        assert result.minor == "08"
+        assert result.patch == "01234"
+        assert result.garbage == "1"
+
+    def test_patch_version_rc(self):
+        result = hosts._normalized_release('1.8.1234rc-123')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234rc-123"
+        assert result.garbage == "0"
+
+    def test_patch_version_with_trailing_space_rc(self):
+        result = hosts._normalized_release(' 1.8.1234rc-123')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234rc-123"
+        assert result.garbage == "0"
+
+    def test_patch_version_with_prepended_zero_rc(self):
+        result = hosts._normalized_release('01.08.01234.1rc-123')
+        assert result.major == "01"
+        assert result.minor == "08"
+        assert result.patch == "01234"
+        assert result.garbage == "1rc-123"
+
+    def test_garbage_version_rc(self):
+        result = hosts._normalized_release('1.8.1234.1rc-123')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234"
+        assert result.garbage == "1rc-123"
+
+    def test_garbage_version_with_trailing_space_rc(self):
+        result = hosts._normalized_release(' 1.8.1234.1rc-123')
+        assert result.major == "1"
+        assert result.minor == "8"
+        assert result.patch == "1234"
+        assert result.garbage == "1rc-123"
+
+    def test_garbage_version_with_prepended_zero_rc(self):
+        result = hosts._normalized_release('01.08.01234.1rc-1')
+        assert result.major == "01"
+        assert result.minor == "08"
+        assert result.patch == "01234"
+        assert result.garbage == "1rc-1"
+
+    def test_garbage_version_with_no_numbers(self):
+        result = hosts._normalized_release('sid')
+        assert result.major == "sid"
+        assert result.minor == "0"
+        assert result.patch == "0"
+        assert result.garbage == "0"
+
+
+class TestHostGet(object):
+
+    def make_fake_connection(self, platform_information=None):
+        get_connection = Mock()
+        get_connection.return_value = get_connection
+        get_connection.remote_module.platform_information = Mock(
+            return_value=platform_information)
+        return get_connection
+
+    def test_get_unsupported(self):
+        fake_get_connection = self.make_fake_connection(('Solaris Enterprise', '', ''))
+        with patch('ceph_deploy.hosts.get_connection', fake_get_connection):
+            with raises(exc.UnsupportedPlatform):
+                hosts.get('myhost')
+
+    def test_get_unsupported_message(self):
+        fake_get_connection = self.make_fake_connection(('Solaris Enterprise', '', ''))
+        with patch('ceph_deploy.hosts.get_connection', fake_get_connection):
+            with raises(exc.UnsupportedPlatform) as error:
+                hosts.get('myhost')
+
+        assert error.value.__str__() == 'Platform is not supported: Solaris Enterprise  '
+
+    def test_get_unsupported_message_release(self):
+        fake_get_connection = self.make_fake_connection(('Solaris', 'Tijuana', '12'))
+        with patch('ceph_deploy.hosts.get_connection', fake_get_connection):
+            with raises(exc.UnsupportedPlatform) as error:
+                hosts.get('myhost')
+
+        assert error.value.__str__() == 'Platform is not supported: Solaris 12 Tijuana'
+
+
+class TestGetDistro(object):
+
+    def test_get_debian(self):
+        result = hosts._get_distro('Debian')
+        assert result.__name__.endswith('debian')
+
+    def test_get_ubuntu(self):
+        # Ubuntu imports debian stuff
+        result = hosts._get_distro('Ubuntu')
+        assert result.__name__.endswith('debian')
+
+    def test_get_centos(self):
+        result = hosts._get_distro('CentOS')
+        assert result.__name__.endswith('centos')
+
+    def test_get_scientific(self):
+        result = hosts._get_distro('Scientific')
+        assert result.__name__.endswith('centos')
+
+    def test_get_oracle(self):
+        result = hosts._get_distro('Oracle Linux Server')
+        assert result.__name__.endswith('centos')
+
+    def test_get_redhat(self):
+        result = hosts._get_distro('RedHat')
+        assert result.__name__.endswith('centos')
+
+    def test_get_redhat_whitespace(self):
+        result = hosts._get_distro('Red Hat Enterprise Linux')
+        assert result.__name__.endswith('centos')
+
+    def test_get_uknown(self):
+        assert hosts._get_distro('Solaris') is None
+
+    def test_get_fallback(self):
+        result = hosts._get_distro('Solaris', 'Debian')
+        assert result.__name__.endswith('debian')
+
+    def test_get_mint(self):
+        result = hosts._get_distro('LinuxMint')
+        assert result.__name__.endswith('debian')
+
+    def test_get_virtuozzo(self):
+        result = hosts._get_distro('Virtuozzo Linux')
+        assert result.__name__.endswith('centos')
+
+    def test_get_arch(self):
+        result = hosts._get_distro('Arch Linux')
+        assert result.__name__.endswith('arch')
+
+    def test_get_altlinux(self):
+        result = hosts._get_distro('ALT Linux')
+        assert result.__name__.endswith('alt')
diff --git a/ceph_deploy/tests/unit/hosts/test_remotes.py b/ceph_deploy/tests/unit/hosts/test_remotes.py
new file mode 100644 (file)
index 0000000..69ee4f7
--- /dev/null
@@ -0,0 +1,37 @@
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
+
+from ceph_deploy.hosts import remotes
+
+
+class TestObjectGrep(object):
+
+    def setup(self):
+        self.file_object = StringIO('foo\n')
+        self.file_object.seek(0)
+
+    def test_finds_term(self):
+        assert remotes.object_grep('foo', self.file_object)
+
+    def test_does_not_find_anything(self):
+        assert remotes.object_grep('bar', self.file_object) is False
+
+
+class TestWhich(object):
+
+    def test_executable_is_a_directory(self, monkeypatch):
+        monkeypatch.setattr(remotes.os.path, 'exists', lambda x: True)
+        monkeypatch.setattr(remotes.os.path, 'isfile', lambda x: False)
+        assert remotes.which('foo') is None
+
+    def test_executable_does_not_exist(self, monkeypatch):
+        monkeypatch.setattr(remotes.os.path, 'exists', lambda x: False)
+        monkeypatch.setattr(remotes.os.path, 'isfile', lambda x: True)
+        assert remotes.which('foo') is None
+
+    def test_executable_exists_as_file(self, monkeypatch):
+        monkeypatch.setattr(remotes.os.path, 'exists', lambda x: True)
+        monkeypatch.setattr(remotes.os.path, 'isfile', lambda x: True)
+        assert remotes.which('foo') == '/usr/local/bin/foo'
diff --git a/ceph_deploy/tests/unit/hosts/test_suse.py b/ceph_deploy/tests/unit/hosts/test_suse.py
new file mode 100644 (file)
index 0000000..d3b3415
--- /dev/null
@@ -0,0 +1,34 @@
+from ceph_deploy.hosts import suse 
+from ceph_deploy.hosts.suse.install import map_components, NON_SPLIT_PACKAGES
+
+class TestSuseInit(object):
+    def setup(self):
+        self.host = suse
+
+    def test_choose_init_default(self):
+        self.host.release = None
+        init_type = self.host.choose_init(self.host)
+        assert init_type == "systemd"
+
+    def test_choose_init_SLE_11(self):
+        self.host.release = '11'
+        init_type = self.host.choose_init(self.host)
+        assert init_type == "sysvinit"
+
+    def test_choose_init_SLE_12(self):
+        self.host.release = '12'
+        init_type = self.host.choose_init(self.host)
+        assert init_type == "systemd"
+
+    def test_choose_init_openSUSE_13_1(self):
+        self.host.release = '13.1'
+        init_type = self.host.choose_init(self.host)
+        assert init_type == "systemd"
+
+class TestSuseMapComponents(object):
+    def test_valid(self):
+        pkgs = map_components(NON_SPLIT_PACKAGES, ['ceph-osd', 'ceph-common', 'ceph-radosgw'])
+        assert 'ceph' in pkgs
+        assert 'ceph-common' in pkgs
+        assert 'ceph-radosgw' in pkgs
+        assert 'ceph-osd' not in pkgs
diff --git a/ceph_deploy/tests/unit/hosts/test_util.py b/ceph_deploy/tests/unit/hosts/test_util.py
new file mode 100644 (file)
index 0000000..c4a5947
--- /dev/null
@@ -0,0 +1,29 @@
+from ceph_deploy.hosts import util
+from mock import Mock
+
+
+class TestInstallYumPriorities(object):
+
+    def setup(self):
+        self.distro = Mock()
+        self.patch_path = 'ceph_deploy.hosts.centos.install.pkg_managers.yum'
+        self.yum = Mock()
+
+    def test_centos_six(self):
+        self.distro.release = ('6', '0')
+        self.distro.normalized_name = 'centos'
+        util.install_yum_priorities(self.distro, _yum=self.yum)
+        assert self.yum.call_args[0][1] == 'yum-plugin-priorities'
+
+    def test_centos_five(self):
+        self.distro.release = ('5', '0')
+        self.distro.normalized_name = 'centos'
+        util.install_yum_priorities(self.distro, _yum=self.yum)
+        assert self.yum.call_args[0][1] == 'yum-priorities'
+
+    def test_fedora(self):
+        self.distro.release = ('20', '0')
+        self.distro.normalized_name = 'fedora'
+        util.install_yum_priorities(self.distro, _yum=self.yum)
+        assert self.yum.call_args[0][1] == 'yum-plugin-priorities'
+
diff --git a/ceph_deploy/tests/unit/test_cli.py b/ceph_deploy/tests/unit/test_cli.py
new file mode 100644 (file)
index 0000000..fff75fc
--- /dev/null
@@ -0,0 +1,46 @@
+from ceph_deploy import cli
+from ceph_deploy.tests import util
+
+
+class FakeLogger(object):
+
+    def __init__(self):
+        self._calls = []
+        self._info = []
+
+    def _output(self):
+        return '\n'.join(self._calls)
+
+    def _record(self, level, message):
+        self._calls.append(message)
+        method = getattr(self, '_%s' % level)
+        method.append(message)
+
+    def info(self, message):
+        self._record('info', message)
+
+
+class TestLogFlags(object):
+
+    def setup(self):
+        self.logger = FakeLogger()
+
+    def test_logs_multiple_object_attributes(self):
+        args = util.Empty(verbose=True, adjust_repos=False)
+        cli.log_flags(args, logger=self.logger)
+        result = self.logger._output()
+        assert ' verbose ' in result
+        assert ' adjust_repos ' in result
+
+    def test_attributes_are_logged_with_values(self):
+        args = util.Empty(verbose=True)
+        cli.log_flags(args, logger=self.logger)
+        result = self.logger._output()
+        assert ' verbose ' in result
+        assert ' : True' in result
+
+    def test_private_attributes_are_not_logged(self):
+        args = util.Empty(verbose=True, _private='some value')
+        cli.log_flags(args, logger=self.logger)
+        result = self.logger._output()
+        assert ' _private ' not in result
diff --git a/ceph_deploy/tests/unit/test_conf.py b/ceph_deploy/tests/unit/test_conf.py
new file mode 100644 (file)
index 0000000..c0a3521
--- /dev/null
@@ -0,0 +1,192 @@
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
+from textwrap import dedent
+import pytest
+from mock import Mock, patch, mock_open
+from ceph_deploy import conf
+
+
+class TestLocateOrCreate(object):
+
+    def setup(self):
+        self.fake_file = mock_open()
+
+    def test_no_conf(self):
+        fake_path = Mock()
+        fake_path.exists = Mock(return_value=False)
+        with patch('ceph_deploy.conf.cephdeploy.open', self.fake_file, create=True):
+            with patch('ceph_deploy.conf.cephdeploy.path', fake_path):
+                conf.cephdeploy.location()
+
+        assert self.fake_file.called is True
+        assert self.fake_file.call_args[0][0].endswith('/.cephdeploy.conf')
+
+    def test_cwd_conf_exists(self):
+        fake_path = Mock()
+        fake_path.join = Mock(return_value='/srv/cephdeploy.conf')
+        fake_path.exists = Mock(return_value=True)
+        with patch('ceph_deploy.conf.cephdeploy.path', fake_path):
+            result = conf.cephdeploy.location()
+
+        assert result == '/srv/cephdeploy.conf'
+
+    def test_home_conf_exists(self):
+        fake_path = Mock()
+        fake_path.expanduser = Mock(return_value='/home/alfredo/.cephdeploy.conf')
+        fake_path.exists = Mock(side_effect=[False, True])
+        with patch('ceph_deploy.conf.cephdeploy.path', fake_path):
+            result = conf.cephdeploy.location()
+
+        assert result == '/home/alfredo/.cephdeploy.conf'
+
+
+class TestConf(object):
+
+    def test_has_repos(self):
+        cfg = conf.cephdeploy.Conf()
+        cfg.sections = lambda: ['foo']
+        assert cfg.has_repos is True
+
+    def test_has_no_repos(self):
+        cfg = conf.cephdeploy.Conf()
+        cfg.sections = lambda: ['ceph-deploy-install']
+        assert cfg.has_repos is False
+
+    def test_get_repos_is_empty(self):
+        cfg = conf.cephdeploy.Conf()
+        cfg.sections = lambda: ['ceph-deploy-install']
+        assert cfg.get_repos() == []
+
+    def test_get_repos_is_not_empty(self):
+        cfg = conf.cephdeploy.Conf()
+        cfg.sections = lambda: ['ceph-deploy-install', 'foo']
+        assert cfg.get_repos() == ['foo']
+
+    def test_get_safe_not_empty(self):
+        cfg = conf.cephdeploy.Conf()
+        cfg.get = lambda section, key: True
+        assert cfg.get_safe(1, 2) is True
+
+    def test_get_safe_empty(self):
+        cfg = conf.cephdeploy.Conf()
+        assert cfg.get_safe(1, 2) is None
+
+
+class TestConfGetList(object):
+
+    def test_get_list_empty(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        key =
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_list('foo', 'key') == ['']
+
+    def test_get_list_empty_when_no_key(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_list('foo', 'key') == []
+
+    def test_get_list_if_value_is_one_item(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        key = 1
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_list('foo', 'key') == ['1']
+
+    def test_get_list_with_mutltiple_items(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        key = 1, 3, 4
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_list('foo', 'key') == ['1', '3', '4']
+
+    def test_get_rid_of_comments(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        key = 1, 3, 4 # this is a wonderful comment y'all
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_list('foo', 'key') == ['1', '3', '4']
+
+    def test_get_rid_of_whitespace(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        key = 1,   3     ,        4
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_list('foo', 'key') == ['1', '3', '4']
+
+    def test_get_default_repo(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        default = True
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_default_repo() == 'foo'
+
+    def test_get_default_repo_fails_non_truthy(self):
+        cfg = conf.cephdeploy.Conf()
+        conf_file = StringIO(dedent("""
+        [foo]
+        default = 0
+        """))
+        cfg.readfp(conf_file)
+        assert cfg.get_default_repo() is False
+
+
+truthy_values = ['yes', 'true', 'on']
+falsy_values = ['no', 'false', 'off']
+
+
+class TestSetOverrides(object):
+
+    def setup(self):
+        self.args = Mock()
+        self.args.func.__name__ = 'foo'
+        self.conf = Mock()
+
+    def test_override_global(self):
+        self.conf.sections = Mock(return_value=['ceph-deploy-global'])
+        self.conf.items = Mock(return_value=(('foo', 1),))
+        arg_obj = conf.cephdeploy.set_overrides(self.args, self.conf)
+        assert arg_obj.foo == 1
+
+    def test_override_foo_section(self):
+        self.conf.sections = Mock(
+            return_value=['ceph-deploy-global', 'ceph-deploy-foo']
+        )
+        self.conf.items = Mock(return_value=(('bar', 1),))
+        arg_obj = conf.cephdeploy.set_overrides(self.args, self.conf)
+        assert arg_obj.bar == 1
+
+    @pytest.mark.parametrize('value', truthy_values)
+    def test_override_truthy_values(self, value):
+        self.conf.sections = Mock(
+            return_value=['ceph-deploy-global', 'ceph-deploy-install']
+        )
+        self.conf.items = Mock(return_value=(('bar', value),))
+        arg_obj = conf.cephdeploy.set_overrides(self.args, self.conf)
+        assert arg_obj.bar is True
+
+    @pytest.mark.parametrize('value', falsy_values)
+    def test_override_falsy_values(self, value):
+        self.conf.sections = Mock(
+            return_value=['ceph-deploy-global', 'ceph-deploy-install']
+        )
+        self.conf.items = Mock(return_value=(('bar', value),))
+        arg_obj = conf.cephdeploy.set_overrides(self.args, self.conf)
+        assert arg_obj.bar is False
diff --git a/ceph_deploy/tests/unit/test_exc.py b/ceph_deploy/tests/unit/test_exc.py
new file mode 100644 (file)
index 0000000..cd38686
--- /dev/null
@@ -0,0 +1,16 @@
+from pytest import raises
+from ceph_deploy import exc
+
+
+class TestExecutableNotFound(object):
+
+    def test_executable_is_used(self):
+        with raises(exc.DeployError) as error:
+            raise exc.ExecutableNotFound('vim', 'node1')
+        assert "'vim'" in str(error)
+
+    def test_host_is_used(self):
+        with raises(exc.DeployError) as error:
+            raise exc.ExecutableNotFound('vim', 'node1')
+        assert "node1" in str(error)
+
diff --git a/ceph_deploy/tests/unit/test_mon.py b/ceph_deploy/tests/unit/test_mon.py
new file mode 100644 (file)
index 0000000..012a6b6
--- /dev/null
@@ -0,0 +1,224 @@
+import sys
+import py.test
+from mock import Mock, patch
+# the below import of mock again is to workaround a py.test issue:
+# https://github.com/pytest-dev/pytest/issues/1035
+import mock
+from ceph_deploy import mon
+from ceph_deploy.hosts.common import mon_create
+from ceph_deploy.misc import mon_hosts, remote_shortname
+
+
+def path_exists(target_paths=None):
+    """
+    A quick helper that enforces a check for the existence of a path. Since we
+    are dealing with fakes, we allow to pass in a list of paths that are OK to
+    return True, otherwise return False.
+    """
+    target_paths = target_paths or []
+
+    def exists(path):
+        return path in target_paths
+    return exists
+
+
+@py.test.mark.skipif(reason='failing due to removal of pushy')
+class TestCreateMon(object):
+
+    def setup(self):
+        # this setup is way more verbose than normal
+        # but we are forced to because this function needs a lot
+        # passed in for remote execution. No other way around it.
+        self.socket = Mock()
+        self.socket.gethostname.return_value = 'hostname'
+        self.fake_file = mock.mock_open()
+        self.distro = Mock()
+        self.sprocess = Mock()
+        self.paths = Mock()
+        self.paths.mon.path = Mock(return_value='/cluster-hostname')
+        self.logger = Mock()
+        self.logger.info = self.logger.debug = lambda x: sys.stdout.write(str(x) + "\n")
+
+    def test_create_mon_tmp_path_if_nonexistent(self):
+        self.distro.sudo_conn.modules.os.path.exists = Mock(
+            side_effect=path_exists(['/cluster-hostname']))
+        self.paths.mon.constants.tmp_path = '/var/lib/ceph/tmp'
+        args = Mock(return_value=['cluster', '1234', 'initd'])
+        args.cluster = 'cluster'
+        with patch('ceph_deploy.hosts.common.conf.load'):
+            mon_create(self.distro, args, Mock(), 'hostname')
+
+        result = self.distro.conn.remote_module.create_mon_path.call_args_list[-1]
+        assert result ==mock.call('/var/lib/ceph/mon/cluster-hostname')
+
+    def test_write_keyring(self):
+        self.distro.sudo_conn.modules.os.path.exists = Mock(
+            side_effect=path_exists(['/']))
+        args = Mock(return_value=['cluster', '1234', 'initd'])
+        args.cluster = 'cluster'
+        with patch('ceph_deploy.hosts.common.conf.load'):
+            with patch('ceph_deploy.hosts.common.remote') as fake_remote:
+                mon_create(self.distro, self.logger, args, Mock(), 'hostname')
+
+        # the second argument to `remote()` should be the write func
+        result = fake_remote.call_args_list[1][0][-1].__name__
+        assert result == 'write_monitor_keyring'
+
+    def test_write_done_path(self):
+        self.distro.sudo_conn.modules.os.path.exists = Mock(
+            side_effect=path_exists(['/']))
+        args = Mock(return_value=['cluster', '1234', 'initd'])
+        args.cluster = 'cluster'
+
+        with patch('ceph_deploy.hosts.common.conf.load'):
+            with patch('ceph_deploy.hosts.common.remote') as fake_remote:
+                mon_create(self.distro, self.logger, args, Mock(), 'hostname')
+
+        # the second to last argument to `remote()` should be the done path
+        # write
+        result = fake_remote.call_args_list[-2][0][-1].__name__
+        assert result == 'create_done_path'
+
+    def test_write_init_path(self):
+        self.distro.sudo_conn.modules.os.path.exists = Mock(
+            side_effect=path_exists(['/']))
+        args = Mock(return_value=['cluster', '1234', 'initd'])
+        args.cluster = 'cluster'
+
+        with patch('ceph_deploy.hosts.common.conf.load'):
+            with patch('ceph_deploy.hosts.common.remote') as fake_remote:
+                mon_create(self.distro, self.logger, args, Mock(), 'hostname')
+
+        result = fake_remote.call_args_list[-1][0][-1].__name__
+        assert result == 'create_init_path'
+
+    def test_mon_hosts(self):
+        hosts = Mock()
+        for (name, host) in mon_hosts(('name1', 'name2.localdomain',
+                    'name3:1.2.3.6', 'name4:localhost.localdomain')):
+            hosts.get(name, host)
+
+        expected = [mock.call.get('name1', 'name1'),
+                   mock.call.get('name2', 'name2.localdomain'),
+                   mock.call.get('name3', '1.2.3.6'),
+                   mock.call.get('name4', 'localhost.localdomain')]
+        result = hosts.mock_calls
+        assert result == expected
+
+    def test_remote_shortname_fqdn(self):
+        socket = Mock()
+        socket.gethostname.return_value = 'host.f.q.d.n'
+        assert remote_shortname(socket) == 'host'
+
+    def test_remote_shortname_host(self):
+        socket = Mock()
+        socket.gethostname.return_value = 'host'
+        assert remote_shortname(socket) == 'host'
+
+
+@py.test.mark.skipif(reason='failing due to removal of pushy')
+class TestIsRunning(object):
+
+    def setup(self):
+        self.fake_popen = Mock()
+        self.fake_popen.return_value = self.fake_popen
+
+    def test_is_running_centos(self):
+        centos_out = ['', "mon.mire094: running {'version': '0.6.15'}"]
+        self.fake_popen.communicate = Mock(return_value=centos_out)
+        with patch('ceph_deploy.mon.subprocess.Popen', self.fake_popen):
+            result = mon.is_running(['ceph', 'status'])
+        assert result is True
+
+    def test_is_not_running_centos(self):
+        centos_out = ['', "mon.mire094: not running {'version': '0.6.15'}"]
+        self.fake_popen.communicate = Mock(return_value=centos_out)
+        with patch('ceph_deploy.mon.subprocess.Popen', self.fake_popen):
+            result = mon.is_running(['ceph', 'status'])
+        assert result is False
+
+    def test_is_dead_centos(self):
+        centos_out = ['', "mon.mire094: dead {'version': '0.6.15'}"]
+        self.fake_popen.communicate = Mock(return_value=centos_out)
+        with patch('ceph_deploy.mon.subprocess.Popen', self.fake_popen):
+            result = mon.is_running(['ceph', 'status'])
+        assert result is False
+
+    def test_is_running_ubuntu(self):
+        ubuntu_out = ['', "ceph-mon (ceph/mira103) start/running, process 5866"]
+        self.fake_popen.communicate = Mock(return_value=ubuntu_out)
+        with patch('ceph_deploy.mon.subprocess.Popen', self.fake_popen):
+            result = mon.is_running(['ceph', 'status'])
+        assert result is True
+
+    def test_is_not_running_ubuntu(self):
+        ubuntu_out = ['', "ceph-mon (ceph/mira103) start/dead, process 5866"]
+        self.fake_popen.communicate = Mock(return_value=ubuntu_out)
+        with patch('ceph_deploy.mon.subprocess.Popen', self.fake_popen):
+            result = mon.is_running(['ceph', 'status'])
+        assert result is False
+
+    def test_is_dead_ubuntu(self):
+        ubuntu_out = ['', "ceph-mon (ceph/mira103) stop/not running, process 5866"]
+        self.fake_popen.communicate = Mock(return_value=ubuntu_out)
+        with patch('ceph_deploy.mon.subprocess.Popen', self.fake_popen):
+            result = mon.is_running(['ceph', 'status'])
+        assert result is False
+
+
+class TestKeyringParser(object):
+
+    def test_line_ends_with_newline_char(self, tmpdir):
+        keyring = tmpdir.join('foo.mon.keyring')
+        keyring.write('[section]\nasdfasdf\nkey = value')
+        result = mon.keyring_parser(keyring.strpath)
+
+        assert result == ['section']
+
+    def test_line_does_not_end_with_newline_char(self, tmpdir):
+        keyring = tmpdir.join('foo.mon.keyring')
+        keyring.write('[section]asdfasdf\nkey = value')
+        result = mon.keyring_parser(keyring.strpath)
+
+        assert result == []
+
+
+class TestConcatenateKeyrings(object):
+
+    def setup(self):
+        self.args = Mock()
+
+    def make_keyring(self, tmpdir, name, contents):
+        keyring = tmpdir.join(name)
+        keyring.write(contents)
+        return keyring
+
+    def test_multiple_keyrings_work(self, tmpdir):
+        self.make_keyring(tmpdir, 'foo.keyring', '[mon.1]\nkey = value\n')
+        self.make_keyring(tmpdir, 'bar.keyring', '[mon.2]\nkey = value\n')
+        self.make_keyring(tmpdir, 'fez.keyring', '[mon.3]\nkey = value\n')
+        self.args.keyrings = tmpdir.strpath
+        result = mon.concatenate_keyrings(self.args).split('\n')
+        assert '[mon.2]' in result
+        assert 'key = value' in result
+        assert '[mon.3]' in result
+        assert 'key = value' in result
+        assert '[mon.1]' in result
+        assert 'key = value' in result
+
+    def test_skips_duplicate_content(self, tmpdir):
+        self.make_keyring(tmpdir, 'foo.keyring', '[mon.1]\nkey = value\n')
+        self.make_keyring(tmpdir, 'bar.keyring', '[mon.2]\nkey = value\n')
+        self.make_keyring(tmpdir, 'fez.keyring', '[mon.3]\nkey = value\n')
+        self.make_keyring(tmpdir, 'dupe.keyring', '[mon.3]\nkey = value\n')
+        self.args.keyrings = tmpdir.strpath
+        result = mon.concatenate_keyrings(self.args).split('\n')
+        assert result.count('[mon.3]') == 1
+        assert result.count('[mon.2]') == 1
+        assert result.count('[mon.1]') == 1
+
+    def test_errors_when_no_keyrings(self, tmpdir):
+        self.args.keyrings = tmpdir.strpath
+
+        with py.test.raises(RuntimeError):
+            mon.concatenate_keyrings(self.args)
diff --git a/ceph_deploy/tests/unit/test_new.py b/ceph_deploy/tests/unit/test_new.py
new file mode 100644 (file)
index 0000000..a32b2ea
--- /dev/null
@@ -0,0 +1,28 @@
+from ceph_deploy import new
+from ceph_deploy.tests import util
+import pytest
+
+
+class TestValidateHostIp(object):
+
+    def test_for_all_subnets_all_ips_match(self):
+        ips = util.generate_ips("10.0.0.1", "10.0.0.40")
+        ips.extend(util.generate_ips("10.0.1.1", "10.0.1.40"))
+        subnets = ["10.0.0.1/16", "10.0.1.1/16"]
+        assert new.validate_host_ip(ips, subnets) is None
+
+    def test_all_subnets_have_one_matching_ip(self):
+        ips = util.generate_ips("10.0.0.1", "10.0.0.40")
+        ips.extend(util.generate_ips("10.0.1.1", "10.0.1.40"))
+        # regardless of extra IPs that may not match. The requirement
+        # is already satisfied
+        ips.extend(util.generate_ips("10.1.2.1", "10.1.2.40"))
+        subnets = ["10.0.0.1/16", "10.0.1.1/16"]
+        assert new.validate_host_ip(ips, subnets) is None
+
+    def test_not_all_subnets_have_one_matching_ip(self):
+        ips = util.generate_ips("10.0.0.1", "10.0.0.40")
+        ips.extend(util.generate_ips("10.0.1.1", "10.0.1.40"))
+        subnets = ["10.0.0.1/16", "10.1.1.1/16"]
+        with pytest.raises(RuntimeError):
+            new.validate_host_ip(ips, subnets)
diff --git a/ceph_deploy/tests/unit/util/test_arg_validators.py b/ceph_deploy/tests/unit/util/test_arg_validators.py
new file mode 100644 (file)
index 0000000..8acb712
--- /dev/null
@@ -0,0 +1,128 @@
+import socket
+from mock import Mock
+from argparse import ArgumentError
+from pytest import raises
+
+from ceph_deploy.util import arg_validators
+
+
+class TestRegexMatch(object):
+
+    def test_match_raises(self):
+        validator = arg_validators.RegexMatch(r'\d+')
+        with raises(ArgumentError):
+            validator('1')
+
+    def test_match_passes(self):
+        validator = arg_validators.RegexMatch(r'\d+')
+        assert validator('foo') == 'foo'
+
+    def test_default_error_message(self):
+        validator = arg_validators.RegexMatch(r'\d+')
+        with raises(ArgumentError) as error:
+            validator('1')
+        message = error.value.message
+        assert message == 'must match pattern \d+'
+
+    def test_custom_error_message(self):
+        validator = arg_validators.RegexMatch(r'\d+', 'wat')
+        with raises(ArgumentError) as error:
+            validator('1')
+        message = error.value.message
+        assert message == 'wat'
+
+
+class TestHostName(object):
+
+    def setup(self):
+        self.fake_sock = Mock()
+        self.fake_sock.gaierror = socket.gaierror
+        self.fake_sock.getaddrinfo.side_effect = socket.gaierror
+
+    def test_hostname_is_not_resolvable(self):
+        hostname = arg_validators.Hostname(self.fake_sock)
+        with raises(ArgumentError) as error:
+            hostname('unresolvable')
+        message = error.value.message
+        assert 'is not resolvable' in message
+
+    def test_hostname_with_name_is_not_resolvable(self):
+        hostname = arg_validators.Hostname(self.fake_sock)
+        with raises(ArgumentError) as error:
+            hostname('name:foo')
+        message = error.value.message
+        assert 'foo is not resolvable' in message
+
+    def test_ip_is_allowed_when_paired_with_host(self):
+        self.fake_sock = Mock()
+        self.fake_sock.gaierror = socket.gaierror
+
+        def side_effect(*args):
+                # First call passes, second call raises socket.gaierror
+                self.fake_sock.getaddrinfo.side_effect = socket.gaierror
+
+        self.fake_sock.getaddrinfo.side_effect = side_effect
+        hostname = arg_validators.Hostname(self.fake_sock)
+        result = hostname('name:192.168.1.111')
+        assert result == 'name:192.168.1.111'
+
+    def test_ipv6_is_allowed_when_paired_with_host(self):
+        self.fake_sock = Mock()
+        self.fake_sock.gaierror = socket.gaierror
+
+        def side_effect(*args):
+                # First call passes, second call raises socket.gaierror
+                self.fake_sock.getaddrinfo.side_effect = socket.gaierror
+
+        self.fake_sock.getaddrinfo.side_effect = side_effect
+        hostname = arg_validators.Hostname(self.fake_sock)
+        result = hostname('name:2001:0db8:85a3:0000:0000:8a2e:0370:7334')
+        assert result == 'name:2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+
+    def test_host_is_resolvable(self):
+        self.fake_sock = Mock()
+        self.fake_sock.gaierror = socket.gaierror
+
+        def side_effect(*args):
+                # First call passes, second call raises socket.gaierror
+                self.fake_sock.getaddrinfo.side_effect = socket.gaierror
+
+        self.fake_sock.getaddrinfo.side_effect = side_effect
+        hostname = arg_validators.Hostname(self.fake_sock)
+        result = hostname('name:example.com')
+        assert result == 'name:example.com'
+
+    def test_hostname_must_be_an_ip(self):
+        self.fake_sock.getaddrinfo = Mock()
+        hostname = arg_validators.Hostname(self.fake_sock)
+        with raises(ArgumentError) as error:
+            hostname('0')
+        message = error.value.message
+        assert '0 must be a hostname' in message
+
+
+class TestSubnet(object):
+
+    def test_subnet_has_less_than_four_numbers(self):
+        validator = arg_validators.Subnet()
+
+        with raises(ArgumentError) as error:
+            validator('3.3.3/12')
+        message = error.value.message
+        assert 'at least 4 numbers' in message
+
+    def test_subnet_has_non_digits(self):
+        validator = arg_validators.Subnet()
+
+        with raises(ArgumentError) as error:
+            validator('3.3.3.a/12')
+        message = error.value.message
+        assert 'have digits separated by dots' in message
+
+    def test_subnet_missing_slash(self):
+        validator = arg_validators.Subnet()
+
+        with raises(ArgumentError) as error:
+            validator('3.3.3.3')
+        message = error.value.message
+        assert 'must contain a slash' in message
diff --git a/ceph_deploy/tests/unit/util/test_constants.py b/ceph_deploy/tests/unit/util/test_constants.py
new file mode 100644 (file)
index 0000000..ce32a57
--- /dev/null
@@ -0,0 +1,16 @@
+from ceph_deploy.util import constants
+
+
+class TestPaths(object):
+
+    def test_mon_path(self):
+        assert constants.mon_path.startswith('/')
+        assert constants.mon_path.endswith('/mon')
+
+    def test_mds_path(self):
+        assert constants.mds_path.startswith('/')
+        assert constants.mds_path.endswith('/mds')
+
+    def test_tmp_path(self):
+        assert constants.tmp_path.startswith('/')
+        assert constants.tmp_path.endswith('/tmp')
diff --git a/ceph_deploy/tests/unit/util/test_net.py b/ceph_deploy/tests/unit/util/test_net.py
new file mode 100644 (file)
index 0000000..9c71bad
--- /dev/null
@@ -0,0 +1,53 @@
+try:
+    from urllib.error import HTTPError
+except ImportError:
+    from urllib2 import HTTPError
+
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
+
+from ceph_deploy.util import net
+from ceph_deploy.tests import util
+import pytest
+
+
+# The following class adds about 1900 tests via py.test generation
+
+class TestIpInSubnet(object):
+
+    @pytest.mark.parametrize('ip', util.generate_ips("10.0.0.1", "10.0.0.255"))
+    def test_correct_for_10_0_0_255(self, ip):
+        assert net.ip_in_subnet(ip, "10.0.0.0/16")
+
+    @pytest.mark.parametrize('ip', util.generate_ips("10.0.0.1", "10.0.0.255"))
+    def test_false_for_10_0_0_255(self, ip):
+        assert net.ip_in_subnet(ip, "10.2.0.0/24") is False
+
+    @pytest.mark.parametrize('ip', util.generate_ips("255.255.255.1", "255.255.255.255"))
+    def test_false_for_255_addresses(self, ip):
+        assert net.ip_in_subnet(ip, "10.9.1.0/16") is False
+
+    @pytest.mark.parametrize('ip', util.generate_ips("172.7.1.1", "172.7.1.255"))
+    def test_false_for_172_addresses(self, ip):
+        assert net.ip_in_subnet(ip, "172.3.0.0/16") is False
+
+    @pytest.mark.parametrize('ip', util.generate_ips("10.9.8.0", "10.9.8.255"))
+    def test_true_for_16_subnets(self, ip):
+        assert net.ip_in_subnet(ip, "10.9.1.0/16") is True
+
+    @pytest.mark.parametrize('ip', util.generate_ips("10.9.8.0", "10.9.8.255"))
+    def test_false_for_24_subnets(self, ip):
+        assert net.ip_in_subnet(ip, "10.9.1.0/24") is False
+
+
+class TestGetRequest(object):
+
+    def test_urlopen_fails(self, monkeypatch):
+        def bad_urlopen(url):
+            raise HTTPError('url', 500, 'error', '', StringIO())
+
+        monkeypatch.setattr(net, 'urlopen', bad_urlopen)
+        with pytest.raises(RuntimeError):
+            net.get_request('https://example.ceph.com')
diff --git a/ceph_deploy/tests/unit/util/test_packages.py b/ceph_deploy/tests/unit/util/test_packages.py
new file mode 100644 (file)
index 0000000..73cc3e8
--- /dev/null
@@ -0,0 +1,43 @@
+from mock import Mock, patch
+from ceph_deploy.exc import ExecutableNotFound
+from ceph_deploy.util import packages
+
+
+class TestCephIsInstalled(object):
+
+    def test_installed(self):
+        with patch('ceph_deploy.util.packages.system'):
+            c = packages.Ceph(Mock())
+            assert c.installed is True
+
+    def test_not_installed(self):
+        with patch('ceph_deploy.util.packages.system') as fsystem:
+            bad_executable = Mock(
+                side_effect=ExecutableNotFound('host', 'ceph')
+            )
+            fsystem.executable_path = bad_executable
+            c = packages.Ceph(Mock())
+            assert c.installed is False
+
+
+class TestCephVersion(object):
+
+    def test_executable_not_found(self):
+        with patch('ceph_deploy.util.packages.system') as fsystem:
+            bad_executable = Mock(
+                side_effect=ExecutableNotFound('host', 'ceph')
+            )
+            fsystem.executable_path = bad_executable
+            c = packages.Ceph(Mock())
+            assert c._get_version_output() == ''
+
+    def test_output_is_unusable(self):
+        _check = Mock(return_value=(b'', b'', 1))
+        c = packages.Ceph(Mock(), _check=_check)
+        assert c._get_version_output() == ''
+
+    def test_output_usable(self):
+        version = b'ceph version 9.0.1-kjh234h123hd (asdf78asdjh234)'
+        _check = Mock(return_value=(version, b'', 1))
+        c = packages.Ceph(Mock(), _check=_check)
+        assert c._get_version_output() == '9.0.1-kjh234h123hd'
diff --git a/ceph_deploy/tests/unit/util/test_paths.py b/ceph_deploy/tests/unit/util/test_paths.py
new file mode 100644 (file)
index 0000000..64ff906
--- /dev/null
@@ -0,0 +1,50 @@
+from ceph_deploy.util import paths
+
+
+class TestMonPaths(object):
+
+    def test_base_path(self):
+        result = paths.mon.base('mycluster')
+        assert result.endswith('/mycluster-')
+
+    def test_path(self):
+        result = paths.mon.path('mycluster', 'myhostname')
+        assert result.startswith('/')
+        assert result.endswith('/mycluster-myhostname')
+
+    def test_done(self):
+        result = paths.mon.done('mycluster', 'myhostname')
+        assert result.startswith('/')
+        assert result.endswith('mycluster-myhostname/done')
+
+    def test_init(self):
+        result = paths.mon.init('mycluster', 'myhostname', 'init')
+        assert result.startswith('/')
+        assert result.endswith('mycluster-myhostname/init')
+
+    def test_keyring(self):
+        result = paths.mon.keyring('mycluster', 'myhostname')
+        assert result.startswith('/')
+        assert result.endswith('tmp/mycluster-myhostname.mon.keyring')
+
+    def test_asok(self):
+        result = paths.mon.asok('mycluster', 'myhostname')
+        assert result.startswith('/')
+        assert result.endswith('mycluster-mon.myhostname.asok')
+
+    def test_monmap(self):
+        result = paths.mon.monmap('mycluster', 'myhostname')
+        assert result.startswith('/')
+        assert result.endswith('tmp/mycluster.myhostname.monmap')
+
+    def test_gpg_url_release(self):
+        result = paths.gpg.url('release')
+        assert result == "https://download.ceph.com/keys/release.asc"
+
+    def test_gpg_url_autobuild(self):
+        result = paths.gpg.url('autobuild')
+        assert result == "https://download.ceph.com/keys/autobuild.asc"
+
+    def test_gpg_url_http(self):
+        result = paths.gpg.url('release', protocol="http")
+        assert result == "http://download.ceph.com/keys/release.asc"
diff --git a/ceph_deploy/tests/unit/util/test_pkg_managers.py b/ceph_deploy/tests/unit/util/test_pkg_managers.py
new file mode 100644 (file)
index 0000000..5f06ad0
--- /dev/null
@@ -0,0 +1,195 @@
+from mock import patch, Mock
+from ceph_deploy.util import pkg_managers
+
+
+class TestApt(object):
+
+    def setup(self):
+        self.to_patch = 'ceph_deploy.util.pkg_managers.remoto.process.run'
+
+    def test_install_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Apt(Mock()).install('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_install_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Apt(Mock()).install(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+    def test_remove_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Apt(Mock()).remove('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_remove_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Apt(Mock()).remove(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+
+class TestYum(object):
+
+    def setup(self):
+        self.to_patch = 'ceph_deploy.util.pkg_managers.remoto.process.run'
+
+    def test_install_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Yum(Mock()).install('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_install_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Yum(Mock()).install(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+    def test_remove_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Yum(Mock()).remove('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_remove_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Yum(Mock()).remove(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+
+class TestZypper(object):
+
+    def setup(self):
+        self.to_patch = 'ceph_deploy.util.pkg_managers.remoto.process.run'
+        self.to_check = 'ceph_deploy.util.pkg_managers.remoto.process.check'
+
+    def test_install_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Zypper(Mock()).install('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_install_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.Zypper(Mock()).install(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+    def test_remove_single_package(self):
+        fake_check = Mock()
+        fake_check.return_value = '', '', 0
+        with patch(self.to_check, fake_check):
+            pkg_managers.Zypper(Mock()).remove('vim')
+            result = fake_check.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_remove_multiple_packages(self):
+        fake_check = Mock()
+        fake_check.return_value = '', '', 0
+        with patch(self.to_check, fake_check):
+            pkg_managers.Zypper(Mock()).remove(['vim', 'zsh'])
+            result = fake_check.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+
+class TestDNF(object):
+
+    def setup(self):
+        self.to_patch = 'ceph_deploy.util.pkg_managers.remoto.process.run'
+
+    def test_install_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.DNF(Mock()).install('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_install_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.DNF(Mock()).install(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+    def test_remove_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.DNF(Mock()).remove('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_remove_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.DNF(Mock()).remove(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+
+
+class TestAtpRpm(object):
+
+    def setup(self):
+        self.to_patch = 'ceph_deploy.util.pkg_managers.remoto.process.run'
+
+    def test_install_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.AptRpm(Mock()).install('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_install_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.AptRpm(Mock()).install(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'install' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
+    def test_remove_single_package(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.AptRpm(Mock()).remove('vim')
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-1] == 'vim'
+
+    def test_remove_multiple_packages(self):
+        fake_run = Mock()
+        with patch(self.to_patch, fake_run):
+            pkg_managers.AptRpm(Mock()).remove(['vim', 'zsh'])
+            result = fake_run.call_args_list[-1]
+        assert 'remove' in result[0][-1]
+        assert result[0][-1][-2:] == ['vim', 'zsh']
+
diff --git a/ceph_deploy/tests/unit/util/test_system.py b/ceph_deploy/tests/unit/util/test_system.py
new file mode 100644 (file)
index 0000000..73ca546
--- /dev/null
@@ -0,0 +1,57 @@
+from mock import Mock
+from pytest import raises
+from ceph_deploy.util import system
+from ceph_deploy import exc
+
+
+class TestExecutablePath(object):
+
+    def test_returns_path(self):
+        fake_conn = Mock()
+        fake_conn.remote_module.which = Mock(return_value='/path')
+        result = system.executable_path(fake_conn, 'foo')
+        assert result == '/path'
+
+    def test_cannot_find_executable(self):
+        fake_conn = Mock()
+        fake_conn.remote_module.which = Mock(return_value=None)
+        with raises(exc.ExecutableNotFound):
+            system.executable_path(fake_conn, 'foo')
+
+
+class TestIsUpstart(object):
+
+    def test_it_is_actually_systemd(self):
+        fake_conn = Mock()
+        fake_conn.remote_module.grep = Mock(return_value=True)
+        result = system.is_upstart(fake_conn)
+        assert result is False
+
+    def test_no_initctl(self):
+        fake_conn = Mock()
+        fake_conn.remote_module.grep = Mock(return_value=False)
+        fake_conn.remote_module.which = Mock(return_value=None)
+        result = system.is_upstart(fake_conn)
+        assert result is False
+
+    def test_initctl_version_says_upstart(self, monkeypatch):
+        fake_conn = Mock()
+        fake_conn.remote_module.grep = Mock(return_value=False)
+        fake_conn.remote_module.which = Mock(return_value='/bin/initctl')
+        fake_stdout = ([b'init', b'(upstart 1.12.1)'], [], 0)
+        fake_check = Mock(return_value=fake_stdout)
+        monkeypatch.setattr("ceph_deploy.util.system.remoto.process.check", lambda *a: fake_check())
+
+        result = system.is_upstart(fake_conn)
+        assert result is True
+
+    def test_initctl_version_says_something_else(self, monkeypatch):
+        fake_conn = Mock()
+        fake_conn.remote_module.grep = Mock(return_value=False)
+        fake_conn.remote_module.which = Mock(return_value='/bin/initctl')
+        fake_stdout = ([b'nosh', b'version', b'1.14'], [], 0)
+        fake_check = Mock(return_value=fake_stdout)
+        monkeypatch.setattr("ceph_deploy.util.system.remoto.process.check", lambda *a: fake_check())
+
+        result = system.is_upstart(fake_conn)
+        assert result is False
diff --git a/ceph_deploy/tests/unit/util/test_templates.py b/ceph_deploy/tests/unit/util/test_templates.py
new file mode 100644 (file)
index 0000000..36c39cd
--- /dev/null
@@ -0,0 +1,29 @@
+from textwrap import dedent
+from ceph_deploy.util import templates
+
+
+class TestCustomRepo(object):
+
+    def test_only_repo_name(self):
+        result = templates.custom_repo(reponame='foo')
+        assert result == '[foo]'
+
+    def test_second_line_with_good_value(self):
+        result = templates.custom_repo(reponame='foo', enabled=0)
+        assert result == '[foo]\nenabled=0'
+
+    def test_mixed_values(self):
+        result = templates.custom_repo(
+            reponame='foo',
+            enabled=0,
+            gpgcheck=1,
+            baseurl='example.org')
+        assert result == dedent("""\
+                        [foo]
+                        baseurl=example.org
+                        enabled=0
+                        gpgcheck=1""")
+
+    def test_allow_invalid_options(self):
+        result = templates.custom_repo(reponame='foo', bar='bar')
+        assert result == '[foo]'
diff --git a/ceph_deploy/tests/util.py b/ceph_deploy/tests/util.py
new file mode 100644 (file)
index 0000000..50932da
--- /dev/null
@@ -0,0 +1,33 @@
+
+
+def generate_ips(start_ip, end_ip):
+    start = list(map(int, start_ip.split(".")))
+    end = list(map(int, end_ip.split(".")))
+    temp = start
+    ip_range = []
+
+    ip_range.append(start_ip)
+    while temp != end:
+        start[3] += 1
+        for i in (3, 2, 1):
+            if temp[i] == 256:
+                temp[i] = 0
+                temp[i-1] += 1
+        ip_range.append(".".join(map(str, temp)))
+
+    return ip_range
+
+
+class Empty(object):
+    """
+    A bare class, with explicit behavior for key/value items to be set at
+    instantiation.
+    """
+    def __init__(self, **kw):
+        for k, v in kw.items():
+            setattr(self, k, v)
+
+
+def assert_too_few_arguments(err):
+    assert ("error: too few arguments" in err or
+            "error: the following argument" in err)
diff --git a/ceph_deploy/util/__init__.py b/ceph_deploy/util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ceph_deploy/util/arg_validators.py b/ceph_deploy/util/arg_validators.py
new file mode 100644 (file)
index 0000000..e61fcd1
--- /dev/null
@@ -0,0 +1,83 @@
+import socket
+import argparse
+import re
+
+
+class RegexMatch(object):
+    """
+    Performs regular expression match on value.
+    If the regular expression pattern matches it will it will return an error
+    message that will work with argparse.
+    """
+
+    def __init__(self, pattern, statement=None):
+        self.string_pattern = pattern
+        self.pattern = re.compile(pattern)
+        self.statement = statement
+        if not self.statement:
+            self.statement = "must match pattern %s" % self.string_pattern
+
+    def __call__(self, string):
+        match = self.pattern.search(string)
+        if match:
+            raise argparse.ArgumentError(None, self.statement)
+        return string
+
+
+class Hostname(object):
+    """
+    Checks wether a given hostname is resolvable in DNS, otherwise raising and
+    argparse error.
+    """
+
+    def __init__(self, _socket=None):
+        self.socket = _socket or socket  # just used for testing
+
+    def __call__(self, string):
+        parts = string.split(':', 1)
+        name = parts[0]
+        host = parts[-1]
+        try:
+            self.socket.getaddrinfo(host, 0)
+        except self.socket.gaierror:
+            msg = "hostname: %s is not resolvable" % host
+            raise argparse.ArgumentError(None, msg)
+
+        try:
+            self.socket.getaddrinfo(name, 0, 0, 0, 0, self.socket.AI_NUMERICHOST)
+        except self.socket.gaierror:
+            return string  # not an IP
+        else:
+            msg = '%s must be a hostname not an IP' % name
+            raise argparse.ArgumentError(None, msg)
+
+        return string
+
+
+class Subnet(object):
+    """
+    A really dumb validator to ensure that we are receiving a subnet (or
+    something that actually looks like a subnet).
+
+    It doesn't enforce at all the constraints of proper validation as that has
+    its own set of caveats that are difficult to implement given that
+    ceph-deploy doesn't (should not) include third party dependencies.
+    """
+
+    def __call__(self, string):
+        ip = string.split('/')[0]
+        ip_parts = ip.split('.')
+
+        if len(ip_parts) != 4:
+            err = "subnet must have at least 4 numbers separated by dots like x.x.x.x/xx, but got: %s" % string
+            raise argparse.ArgumentError(None, err)
+
+        if [i for i in ip_parts[:4] if i.isalpha()]:  # only numbers
+            err = "subnet must have digits separated by dots like x.x.x.x/xx, but got: %s" % string
+            raise argparse.ArgumentError(None, err)
+
+        if len(string.split('/')) != 2:
+            err = "subnet must contain a slash, like x.x.x.x/xx, but got: %s" % string
+            raise argparse.ArgumentError(None, err)
+
+        return string
diff --git a/ceph_deploy/util/constants.py b/ceph_deploy/util/constants.py
new file mode 100644 (file)
index 0000000..a76dcd6
--- /dev/null
@@ -0,0 +1,36 @@
+from os.path import join
+from collections import namedtuple
+
+# Base Path for ceph
+base_path = '/var/lib/ceph'
+
+# Base run Path
+base_run_path = '/var/run/ceph'
+
+tmp_path = join(base_path, 'tmp')
+
+mon_path = join(base_path, 'mon')
+
+mgr_path = join(base_path, 'mgr')
+
+mds_path = join(base_path, 'mds')
+
+osd_path = join(base_path, 'osd')
+
+# Default package components to install
+_base_components = [
+    'ceph',
+    'ceph-osd',
+    'ceph-mds',
+    'ceph-mon',
+]
+
+default_components = namedtuple('DefaultComponents', ['rpm', 'deb', 'pkgtarxz'])
+
+# the difference here is because RPMs currently name the radosgw differently than DEBs.
+# TODO: This needs to get unified once the packaging naming gets consistent
+default_components.rpm = tuple(_base_components + ['ceph-radosgw'])
+default_components.deb = tuple(_base_components + ['radosgw'])
+default_components.pkgtarxz = tuple(['ceph'])
+
+gpg_key_base_url = "download.ceph.com/keys/"
diff --git a/ceph_deploy/util/decorators.py b/ceph_deploy/util/decorators.py
new file mode 100644 (file)
index 0000000..70e002a
--- /dev/null
@@ -0,0 +1,112 @@
+import logging
+import sys
+import traceback
+from functools import wraps
+
+
+def catches(catch=None, handler=None, exit=True, handle_all=False):
+    """
+    Very simple decorator that tries any of the exception(s) passed in as
+    a single exception class or tuple (containing multiple ones) returning the
+    exception message and optionally handling the problem if it raises with the
+    handler if it is provided.
+
+    So instead of doing something like this::
+
+        def bar():
+            try:
+                some_call()
+                print "Success!"
+            except TypeError, exc:
+                print "Error while handling some call: %s" % exc
+                sys.exit(1)
+
+    You would need to decorate it like this to have the same effect::
+
+        @catches(TypeError)
+        def bar():
+            some_call()
+            print "Success!"
+
+    If multiple exceptions need to be caught they need to be provided as a
+    tuple::
+
+        @catches((TypeError, AttributeError))
+        def bar():
+            some_call()
+            print "Success!"
+
+    If adding a handler, it should accept a single argument, which would be the
+    exception that was raised, it would look like::
+
+        def my_handler(exc):
+            print 'Handling exception %s' % str(exc)
+            raise SystemExit
+
+        @catches(KeyboardInterrupt, handler=my_handler)
+        def bar():
+            some_call()
+
+    Note that the handler needs to raise its SystemExit if it wants to halt
+    execution, otherwise the decorator would continue as a normal try/except
+    block.
+
+
+    :param catch: A tuple with one (or more) Exceptions to catch
+    :param handler: Optional handler to have custom handling of exceptions
+    :param exit: Raise a ``SystemExit`` after handling exceptions
+    :param handle_all: Handle all other exceptions via logging.
+    """
+    catch = catch or Exception
+    logger = logging.getLogger('ceph_deploy')
+
+    def decorate(f):
+
+        @wraps(f)
+        def newfunc(*a, **kw):
+            exit_from_catch = False
+            try:
+                return f(*a, **kw)
+            except catch as e:
+                if handler:
+                    return handler(e)
+                else:
+                    logger.error(make_exception_message(e))
+
+                    if exit:
+                        exit_from_catch = True
+                        sys.exit(1)
+            except Exception:  # anything else, no need to save the exception as a variable
+                if handle_all is False:  # re-raise if we are not supposed to handle everything
+                    raise
+                # Make sure we don't spit double tracebacks if we are raising
+                # SystemExit from the `except catch` block
+
+                if exit_from_catch:
+                    sys.exit(1)
+
+                str_failure = traceback.format_exc()
+                for line in str_failure.split('\n'):
+                    logger.error("%s" % line)
+                sys.exit(1)
+
+        return newfunc
+
+    return decorate
+
+#
+# Decorator helpers
+#
+
+
+def make_exception_message(exc):
+    """
+    An exception is passed in and this function
+    returns the proper string depending on the result
+    so it is readable enough.
+    """
+    if str(exc):
+        return '%s: %s\n' % (exc.__class__.__name__, exc)
+    else:
+        return '%s\n' % (exc.__class__.__name__)
+
diff --git a/ceph_deploy/util/files.py b/ceph_deploy/util/files.py
new file mode 100644 (file)
index 0000000..6770596
--- /dev/null
@@ -0,0 +1,5 @@
+
+
+def read_file(path):
+    with open(path, 'rb') as f:
+        return f.read()
diff --git a/ceph_deploy/util/help_formatters.py b/ceph_deploy/util/help_formatters.py
new file mode 100644 (file)
index 0000000..2cb562d
--- /dev/null
@@ -0,0 +1,33 @@
+import argparse
+
+
+class ToggleRawTextHelpFormatter(argparse.HelpFormatter):
+    """ArgParse help formatter that allows raw text in individual help strings
+
+        Inspired by the SmartFormatter at
+           https://bitbucket.org/ruamel/std.argparse
+
+       Normally to include newlines in the help output of argparse, you have
+       use argparse.RawDescriptionHelpFormatter. But this means raw text is enabled
+       everywhere, and not just for specific help entries where you might need it.
+
+       This help formatter allows for you to optional enable/toggle raw text on
+       individual menu items by prefixing the help string with 'R|'.
+
+       Example:
+
+       parser.formatter_class = ToggleRawTextHelpFormatter
+       parser.add_argument('--verbose', action=store_true,
+                           help='Enable verbose mode')
+       #Above help is formatted just as default argparse.HelpFormatter
+
+       parser.add_argument('--complex-arg', action=store_true,
+                           help=('R|This help description use '
+                                 'newlines and tabs and they will be preserved in'
+                                 'the help output.\n\n'
+                                 '\tHow cool is that?'))
+    """
+    def _split_lines(self, text, width):
+        if text.startswith('R|'):
+            return text[2:].splitlines()  
+        return argparse.HelpFormatter._split_lines(self, text, width)
diff --git a/ceph_deploy/util/log.py b/ceph_deploy/util/log.py
new file mode 100644 (file)
index 0000000..c72303c
--- /dev/null
@@ -0,0 +1,67 @@
+import logging
+import sys
+
+BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
+
+COLORS = {
+    'WARNING': YELLOW,
+    'INFO': WHITE,
+    'DEBUG': BLUE,
+    'CRITICAL': RED,
+    'ERROR': RED,
+    'FATAL': RED,
+}
+
+RESET_SEQ = "\033[0m"
+COLOR_SEQ = "\033[1;%dm"
+BOLD_SEQ = "\033[1m"
+
+BASE_COLOR_FORMAT = "[$BOLD%(name)s$RESET][%(color_levelname)-17s] %(message)s"
+BASE_FORMAT = "[%(name)s][%(levelname)-6s] %(message)s"
+FILE_FORMAT = "[%(asctime)s]" + BASE_FORMAT
+
+def supports_color():
+    """
+    Returns True if the running system's terminal supports color, and False
+    otherwise.
+    """
+    unsupported_platform = (sys.platform in ('win32', 'Pocket PC'))
+    # isatty is not always implemented, #6223.
+    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
+    if unsupported_platform or not is_a_tty:
+        return False
+    return True
+
+
+def color_message(message):
+    message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
+    return message
+
+
+class ColoredFormatter(logging.Formatter):
+    """
+    A very basic logging formatter that not only applies color to the levels of
+    the ouput but will also truncate the level names so that they do not alter
+    the visuals of logging when presented on the terminal.
+    """
+
+    def __init__(self, msg):
+        logging.Formatter.__init__(self, msg)
+
+    def format(self, record):
+        levelname = record.levelname
+        truncated_level = record.levelname[:6]
+        levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + truncated_level + RESET_SEQ
+        record.color_levelname = levelname_color
+        return logging.Formatter.format(self, record)
+
+
+def color_format():
+    """
+    Main entry point to get a colored formatter, it will use the
+    BASE_FORMAT by default and fall back to no colors if the system
+    does not support it
+    """
+    str_format = BASE_COLOR_FORMAT if supports_color() else BASE_FORMAT
+    color_format = color_message(str_format)
+    return ColoredFormatter(color_format)
diff --git a/ceph_deploy/util/net.py b/ceph_deploy/util/net.py
new file mode 100644 (file)
index 0000000..73c3914
--- /dev/null
@@ -0,0 +1,399 @@
+try:
+    from urllib.request import urlopen
+    from urllib.error import HTTPError
+except ImportError:
+    from urllib2 import urlopen, HTTPError
+
+from ceph_deploy import exc
+import logging
+import re
+import socket
+from ceph_deploy.lib import remoto
+
+
+LOG = logging.getLogger(__name__)
+
+
+# TODO: at some point, it might be way more accurate to do this in the actual
+# host where we need to get IPs from. SaltStack does this by calling `ip` and
+# parsing the output, which is probably the one true way of dealing with it.
+
+def get_nonlocal_ip(host, subnet=None):
+    """
+    Search result of getaddrinfo() for a non-localhost-net address
+    """
+    try:
+        ailist = socket.getaddrinfo(host, None)
+    except socket.gaierror:
+        raise exc.UnableToResolveError(host)
+    for ai in ailist:
+        # an ai is a 5-tuple; the last element is (ip, port)
+        ip = ai[4][0]
+        if subnet and ip_in_subnet(ip, subnet):
+            LOG.info('found ip (%s) for host (%s) to be in cluster subnet (%s)' % (
+                ip,
+                host,
+                subnet,)
+            )
+
+            return ip
+
+        if not ip.startswith('127.'):
+            if subnet:
+                LOG.warning('could not match ip (%s) for host (%s) for cluster subnet (%s)' % (
+                    ip,
+                    host,
+                    subnet,)
+                )
+            return ip
+    raise exc.UnableToResolveError(host)
+
+
+def ip_in_subnet(ip, subnet):
+    """Does IP exists in a given subnet utility. Returns a boolean"""
+    ipaddr = int(''.join(['%02x' % int(x) for x in ip.split('.')]), 16)
+    netstr, bits = subnet.split('/')
+    netaddr = int(''.join(['%02x' % int(x) for x in netstr.split('.')]), 16)
+    mask = (0xffffffff << (32 - int(bits))) & 0xffffffff
+    return (ipaddr & mask) == (netaddr & mask)
+
+
+def in_subnet(cidr, addrs=None):
+    """
+    Returns True if host is within specified subnet, otherwise False
+    """
+    for address in addrs:
+        if ip_in_subnet(address, cidr):
+            return True
+    return False
+
+
+def ip_addresses(conn, interface=None, include_loopback=False):
+    """
+    Returns a list of IPv4/IPv6 addresses assigned to the host. 127.0.0.1/::1 is
+    ignored, unless 'include_loopback=True' is indicated. If 'interface' is
+    provided, then only IP addresses from that interface will be returned.
+
+    Example output looks like::
+
+        >>> ip_addresses(conn)
+        >>> ['192.168.1.111', '10.0.1.12', '2001:db8::100']
+
+    """
+    ret = set()
+    ifaces = linux_interfaces(conn)
+    if interface is None:
+        target_ifaces = ifaces
+    else:
+        target_ifaces = dict((k, v) for k, v in ifaces.items()
+                             if k == interface)
+        if not target_ifaces:
+            LOG.error('Interface {0} not found.'.format(interface))
+    for info in target_ifaces.values():
+        for ipv4 in info.get('inet', []):
+            loopback = in_subnet('127.0.0.0/8', [ipv4.get('address')]) or ipv4.get('label') == 'lo'
+            if not loopback or include_loopback:
+                ret.add(ipv4['address'])
+        for secondary in info.get('secondary', []):
+            addr = secondary.get('address')
+            if addr and secondary.get('type') == 'inet':
+                if include_loopback or (not include_loopback and not in_subnet('127.0.0.0/8', [addr])):
+                    ret.add(addr)
+        for ipv6 in info.get('inet6', []):
+            # When switching to Python 3 the IPAddress module can do all this work for us
+            if ipv6.get('address').startswith('fe80::'):
+                continue
+
+            if not include_loopback and '::1' == ipv6.get('address'):
+                continue
+
+            ret.add(ipv6['address'])
+    if ret:
+        conn.logger.debug('IP addresses found: %s' % str(list(ret)))
+    return sorted(list(ret))
+
+
+def linux_interfaces(conn):
+    """
+    Obtain interface information for *NIX/BSD variants in remote servers.
+
+    Example output from a remote node with a couple of interfaces::
+
+        {'eth0': {'hwaddr': '08:00:27:08:c2:e4',
+                  'inet': [{'address': '10.0.2.15',
+                            'broadcast': '10.0.2.255',
+                            'label': 'eth0',
+                            'netmask': '255.255.255.0'}],
+                  'inet6': [{'address': 'fe80::a00:27ff:fe08:c2e4',
+                             'prefixlen': '64'}],
+                  'up': True},
+         'eth1': {'hwaddr': '08:00:27:70:06:f1',
+                  'inet': [{'address': '192.168.111.101',
+                            'broadcast': '192.168.111.255',
+                            'label': 'eth1',
+                            'netmask': '255.255.255.0'}],
+                  'inet6': [{'address': 'fe80::a00:27ff:fe70:6f1',
+                             'prefixlen': '64'}],
+                  'up': True},
+         'lo': {'hwaddr': '00:00:00:00:00:00',
+                'inet': [{'address': '127.0.0.1',
+                          'broadcast': None,
+                          'label': 'lo',
+                          'netmask': '255.0.0.0'}],
+                'inet6': [{'address': '::1', 'prefixlen': '128'}],
+                'up': True}}
+
+    :param conn: A connection object to a remote node
+    """
+    ifaces = dict()
+    ip_path = conn.remote_module.which('ip')
+    ifconfig_path = None if ip_path else conn.remote_module.which('ifconfig')
+    if ip_path:
+        cmd1, _, _ = remoto.process.check(
+            conn,
+            [
+                '{0}'.format(ip_path),
+                'link',
+                'show',
+            ],
+        )
+        cmd2, _, _ = remoto.process.check(
+            conn,
+            [
+                '{0}'.format(ip_path),
+                'addr',
+                'show',
+            ],
+        )
+        ifaces = _interfaces_ip(b'\n'.join(cmd1).decode('utf-8') + '\n' +
+                                b'\n'.join(cmd2).decode('utf-8'))
+    elif ifconfig_path:
+        cmd, _, _ = remoto.process.check(
+            conn,
+            [
+                '{0}'.format(ifconfig_path),
+                '-a',
+            ]
+        )
+        ifaces = _interfaces_ifconfig('\n'.join(cmd))
+    return ifaces
+
+
+def _interfaces_ip(out):
+    """
+    Uses ip to return a dictionary of interfaces with various information about
+    each (up/down state, ip address, netmask, and hwaddr)
+    """
+    ret = dict()
+
+    def parse_network(value, cols):
+        """
+        Return a tuple of ip, netmask, broadcast
+        based on the current set of cols
+        """
+        brd = None
+        if '/' in value:  # we have a CIDR in this address
+            ip, cidr = value.split('/')  # pylint: disable=C0103
+        else:
+            ip = value  # pylint: disable=C0103
+            cidr = 32
+
+        if type_ == 'inet':
+            mask = cidr_to_ipv4_netmask(int(cidr))
+            if 'brd' in cols:
+                brd = cols[cols.index('brd') + 1]
+        elif type_ == 'inet6':
+            mask = cidr
+        return (ip, mask, brd)
+
+    groups = re.compile('\r?\n\\d').split(out)
+    for group in groups:
+        iface = None
+        data = dict()
+
+        for line in group.splitlines():
+            if ' ' not in line:
+                continue
+            match = re.match(r'^\d*:\s+([\w.\-]+)(?:@)?([\w.\-]+)?:\s+<(.+)>', line)
+            if match:
+                iface, parent, attrs = match.groups()
+                if 'UP' in attrs.split(','):
+                    data['up'] = True
+                else:
+                    data['up'] = False
+                if parent:
+                    data['parent'] = parent
+                continue
+
+            cols = line.split()
+            if len(cols) >= 2:
+                type_, value = tuple(cols[0:2])
+                iflabel = cols[-1:][0]
+                if type_ in ('inet', 'inet6'):
+                    if 'secondary' not in cols:
+                        ipaddr, netmask, broadcast = parse_network(value, cols)
+                        if type_ == 'inet':
+                            if 'inet' not in data:
+                                data['inet'] = list()
+                            addr_obj = dict()
+                            addr_obj['address'] = ipaddr
+                            addr_obj['netmask'] = netmask
+                            addr_obj['broadcast'] = broadcast
+                            addr_obj['label'] = iflabel
+                            data['inet'].append(addr_obj)
+                        elif type_ == 'inet6':
+                            if 'inet6' not in data:
+                                data['inet6'] = list()
+                            addr_obj = dict()
+                            addr_obj['address'] = ipaddr
+                            addr_obj['prefixlen'] = netmask
+                            data['inet6'].append(addr_obj)
+                    else:
+                        if 'secondary' not in data:
+                            data['secondary'] = list()
+                        ip_, mask, brd = parse_network(value, cols)
+                        data['secondary'].append({
+                            'type': type_,
+                            'address': ip_,
+                            'netmask': mask,
+                            'broadcast': brd,
+                            'label': iflabel,
+                        })
+                        del ip_, mask, brd
+                elif type_.startswith('link'):
+                    data['hwaddr'] = value
+        if iface:
+            ret[iface] = data
+            del iface, data
+    return ret
+
+
+def _interfaces_ifconfig(out):
+    """
+    Uses ifconfig to return a dictionary of interfaces with various information
+    about each (up/down state, ip address, netmask, and hwaddr)
+    """
+    ret = dict()
+
+    piface = re.compile(r'^([^\s:]+)')
+    pmac = re.compile('.*?(?:HWaddr|ether|address:|lladdr) ([0-9a-fA-F:]+)')
+    pip = re.compile(r'.*?(?:inet addr:|inet )(.*?)\s')
+    pip6 = re.compile('.*?(?:inet6 addr: (.*?)/|inet6 )([0-9a-fA-F:]+)')
+    pmask = re.compile(r'.*?(?:Mask:|netmask )(?:((?:0x)?[0-9a-fA-F]{8})|([\d\.]+))')
+    pmask6 = re.compile(r'.*?(?:inet6 addr: [0-9a-fA-F:]+/(\d+)|prefixlen (\d+)).*')
+    pupdown = re.compile('UP')
+    pbcast = re.compile(r'.*?(?:Bcast:|broadcast )([\d\.]+)')
+
+    groups = re.compile('\r?\n(?=\\S)').split(out)
+    for group in groups:
+        data = dict()
+        iface = ''
+        updown = False
+        for line in group.splitlines():
+            miface = piface.match(line)
+            mmac = pmac.match(line)
+            mip = pip.match(line)
+            mip6 = pip6.match(line)
+            mupdown = pupdown.search(line)
+            if miface:
+                iface = miface.group(1)
+            if mmac:
+                data['hwaddr'] = mmac.group(1)
+            if mip:
+                if 'inet' not in data:
+                    data['inet'] = list()
+                addr_obj = dict()
+                addr_obj['address'] = mip.group(1)
+                mmask = pmask.match(line)
+                if mmask:
+                    if mmask.group(1):
+                        mmask = _number_of_set_bits_to_ipv4_netmask(
+                            int(mmask.group(1), 16))
+                    else:
+                        mmask = mmask.group(2)
+                    addr_obj['netmask'] = mmask
+                mbcast = pbcast.match(line)
+                if mbcast:
+                    addr_obj['broadcast'] = mbcast.group(1)
+                data['inet'].append(addr_obj)
+            if mupdown:
+                updown = True
+            if mip6:
+                if 'inet6' not in data:
+                    data['inet6'] = list()
+                addr_obj = dict()
+                addr_obj['address'] = mip6.group(1) or mip6.group(2)
+                mmask6 = pmask6.match(line)
+                if mmask6:
+                    addr_obj['prefixlen'] = mmask6.group(1) or mmask6.group(2)
+                data['inet6'].append(addr_obj)
+        data['up'] = updown
+        ret[iface] = data
+        del data
+    return ret
+
+
+def _number_of_set_bits_to_ipv4_netmask(set_bits):  # pylint: disable=C0103
+    """
+    Returns an IPv4 netmask from the integer representation of that mask.
+
+    Ex. 0xffffff00 -> '255.255.255.0'
+    """
+    return cidr_to_ipv4_netmask(_number_of_set_bits(set_bits))
+
+
+def _number_of_set_bits(x):
+    """
+    Returns the number of bits that are set in a 32bit int
+    """
+    #  Taken from http://stackoverflow.com/a/4912729. Many thanks!
+    x -= (x >> 1) & 0x55555555
+    x = ((x >> 2) & 0x33333333) + (x & 0x33333333)
+    x = ((x >> 4) + x) & 0x0f0f0f0f
+    x += x >> 8
+    x += x >> 16
+    return x & 0x0000003f
+
+
+def cidr_to_ipv4_netmask(cidr_bits):
+    """
+    Returns an IPv4 netmask
+    """
+    try:
+        cidr_bits = int(cidr_bits)
+        if not 1 <= cidr_bits <= 32:
+            return ''
+    except ValueError:
+        return ''
+
+    netmask = ''
+    for idx in range(4):
+        if idx:
+            netmask += '.'
+        if cidr_bits >= 8:
+            netmask += '255'
+            cidr_bits -= 8
+        else:
+            netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))
+            cidr_bits = 0
+    return netmask
+
+
+def get_request(url):
+    try:
+        return urlopen(url)
+    except HTTPError as err:
+        LOG.error('repository might not be available yet')
+        raise RuntimeError('%s, failed to fetch %s' % (err, url))
+
+
+def get_chacra_repo(shaman_url):
+    """
+    From a Shaman URL, get the chacra url for a repository, read the
+    contents that point to the repo and return it as a string.
+    """
+    shaman_response = get_request(shaman_url)
+    chacra_url = shaman_response.geturl()
+    chacra_response = get_request(chacra_url)
+
+    return chacra_response.read()
diff --git a/ceph_deploy/util/packages.py b/ceph_deploy/util/packages.py
new file mode 100644 (file)
index 0000000..a998264
--- /dev/null
@@ -0,0 +1,74 @@
+from ceph_deploy.exc import ExecutableNotFound
+from ceph_deploy.util import system, versions
+from ceph_deploy.lib import remoto
+
+
+class Ceph(object):
+    """
+    Determine different aspects of the Ceph package, like ``version`` and path
+    ``executable``. Although mostly provide a version object that helps for
+    parsing and comparing.
+    """
+
+    def __init__(self, conn, _check=None):
+        self.conn = conn
+        self._check = _check or remoto.process.check
+
+    @property
+    def installed(self):
+        """
+        If the ``ceph`` executable exists, then Ceph is installed. Should
+        probably be revisited if different components do not have the ``ceph``
+        executable (this is currently provided by ``ceph-common``).
+        """
+        return bool(self.executable)
+
+    @property
+    def executable(self):
+        try:
+            return system.executable_path(self.conn, 'ceph')
+        except ExecutableNotFound:
+            return None
+
+    def _get_version_output(self):
+        """
+        Ignoring errors, call `ceph --version` and return only the version
+        portion of the output. For example, output like::
+
+            ceph version 9.0.1-1234kjd (asdflkj2k3jh234jhg)
+
+        Would return::
+
+            9.0.1-1234kjd
+        """
+        if not self.executable:
+            return ''
+        command = [self.executable, '--version']
+        out, _, _ = self._check(self.conn, command)
+        try:
+            return out.decode('utf-8').split()[2]
+        except IndexError:
+            return ''
+
+    @property
+    def version(self):
+        """
+        Return a version object (see
+        :mod:``ceph_deploy.util.versions.NormalizedVersion``)
+        """
+        return versions.parse_version(self._get_version_output)
+
+
+# callback helpers
+
+def ceph_is_installed(module):
+    """
+    A helper callback to be executed after the connection is made to ensure
+    that Ceph is installed.
+    """
+    ceph_package = Ceph(module.conn)
+    if not ceph_package.installed:
+        host = module.conn.hostname
+        raise RuntimeError(
+            'ceph needs to be installed in remote host: %s' % host
+        )
diff --git a/ceph_deploy/util/paths/__init__.py b/ceph_deploy/util/paths/__init__.py
new file mode 100644 (file)
index 0000000..287a551
--- /dev/null
@@ -0,0 +1,3 @@
+from . import mon # noqa
+from . import osd # noqa
+from . import gpg # noqa
diff --git a/ceph_deploy/util/paths/gpg.py b/ceph_deploy/util/paths/gpg.py
new file mode 100644 (file)
index 0000000..d9d950b
--- /dev/null
@@ -0,0 +1,8 @@
+from ceph_deploy.util import constants
+
+def url(key_type, protocol="https"):
+    return "{protocol}://{url}{key_type}.asc".format(
+        protocol=protocol,
+        url=constants.gpg_key_base_url,
+        key_type=key_type
+    )
diff --git a/ceph_deploy/util/paths/mon.py b/ceph_deploy/util/paths/mon.py
new file mode 100644 (file)
index 0000000..0c252d5
--- /dev/null
@@ -0,0 +1,84 @@
+"""
+Common paths for mon, based on the constant file paths defined in
+``ceph_deploy.util.constants``.
+All functions return a string representation of the absolute path
+construction.
+"""
+from os.path import join
+
+from ceph_deploy.util import constants
+
+
+def base(cluster):
+    cluster = "%s-" % cluster
+    return join(constants.mon_path, cluster)
+
+
+def path(cluster, hostname):
+    """
+    Example usage::
+
+        >>> from ceph_deploy.util.paths import mon
+        >>> mon.path('mycluster', 'hostname')
+        /var/lib/ceph/mon/mycluster-myhostname
+    """
+    return "%s%s" % (base(cluster), hostname)
+
+
+def done(cluster, hostname):
+    """
+    Example usage::
+
+        >>> from ceph_deploy.util.paths import mon
+        >>> mon.done('mycluster', 'hostname')
+        /var/lib/ceph/mon/mycluster-myhostname/done
+    """
+    return join(path(cluster, hostname), 'done')
+
+
+def init(cluster, hostname, init):
+    """
+    Example usage::
+
+        >>> from ceph_deploy.util.paths import mon
+        >>> mon.init('mycluster', 'hostname', 'init')
+        /var/lib/ceph/mon/mycluster-myhostname/init
+    """
+    return join(path(cluster, hostname), init)
+
+
+def keyring(cluster, hostname):
+    """
+    Example usage::
+
+        >>> from ceph_deploy.util.paths import mon
+        >>> mon.keyring('mycluster', 'myhostname')
+        /var/lib/ceph/tmp/mycluster-myhostname.mon.keyring
+    """
+    keyring_file = '%s-%s.mon.keyring' % (cluster, hostname)
+    return join(constants.tmp_path, keyring_file)
+
+
+def asok(cluster, hostname):
+    """
+    Example usage::
+
+        >>> from ceph_deploy.util.paths import mon
+        >>> mon.asok('mycluster', 'myhostname')
+        /var/run/ceph/mycluster-mon.myhostname.asok
+    """
+    asok_file = '%s-mon.%s.asok' % (cluster, hostname)
+    return join(constants.base_run_path, asok_file)
+
+
+def monmap(cluster, hostname):
+    """
+    Example usage::
+
+        >>> from ceph_deploy.util.paths import mon
+        >>> mon.monmap('mycluster', 'myhostname')
+        /var/lib/ceph/tmp/mycluster.myhostname.monmap
+    """
+    monmap
+    mon_map_file = '%s.%s.monmap' % (cluster, hostname)
+    return join(constants.tmp_path, mon_map_file)
diff --git a/ceph_deploy/util/paths/osd.py b/ceph_deploy/util/paths/osd.py
new file mode 100644 (file)
index 0000000..18d7502
--- /dev/null
@@ -0,0 +1,13 @@
+"""
+Comosd paths for osd, based on the constant file paths defined in
+``ceph_deploy.util.constants``.
+All functions return a string representation of the absolute path
+construction.
+"""
+from os.path import join
+from ceph_deploy.util import constants
+
+
+def base(cluster):
+    cluster = "%s-" % cluster
+    return join(constants.osd_path, cluster)
diff --git a/ceph_deploy/util/pkg_managers.py b/ceph_deploy/util/pkg_managers.py
new file mode 100644 (file)
index 0000000..feb167f
--- /dev/null
@@ -0,0 +1,429 @@
+import os
+try:
+    from urllib.parse import urlparse
+except ImportError:
+    from urlparse import urlparse
+
+from ceph_deploy.lib import remoto
+from ceph_deploy.util import templates
+
+
+class PackageManager(object):
+    """
+    Base class for all Package Managers
+    """
+
+    def __init__(self, remote_conn):
+        self.remote_info = remote_conn
+        self.remote_conn = remote_conn.conn
+
+    def _run(self, cmd, **kw):
+        return remoto.process.run(
+            self.remote_conn,
+            cmd,
+            **kw
+        )
+
+    def _check(self, cmd, **kw):
+        return remoto.process.check(
+            self.remote_conn,
+            cmd,
+            **kw
+        )
+
+    def install(self, packages, **kw):
+        """Install packages on remote node"""
+        raise NotImplementedError()
+
+    def remove(self, packages, **kw):
+        """Uninstall packages on remote node"""
+        raise NotImplementedError()
+
+    def clean(self):
+        """Clean metadata/cache"""
+        raise NotImplementedError()
+
+    def add_repo_gpg_key(self, url):
+        """Add given GPG key for repo verification"""
+        raise NotImplementedError()
+
+    def add_repo(self, name, url, **kw):
+        """Add/rewrite a repo file"""
+        raise NotImplementedError()
+
+    def remove_repo(self, name):
+        """Remove a repo definition"""
+        raise NotImplementedError()
+
+
+class RPMManagerBase(PackageManager):
+    """
+    Base class to hold common pieces of Yum and DNF
+    """
+
+    executable = None
+    name = None
+
+    def install(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_install_flags', None)
+        cmd = [
+            self.executable,
+            '-y',
+            'install',
+        ]
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def remove(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_remove_flags', None)
+        cmd = [
+            self.executable,
+            '-y',
+            '-q',
+            'remove',
+        ]
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def clean(self, item=None):
+        item = item or 'all'
+        cmd = [
+            self.executable,
+            'clean',
+            item,
+        ]
+
+        return self._run(cmd)
+
+    def add_repo_gpg_key(self, url):
+        cmd = ['rpm', '--import', url]
+        self._run(cmd)
+
+    def add_repo(self, name, url, **kw):
+        gpg_url = kw.pop('gpg_url', None)
+        if gpg_url:
+            self.add_repo_gpg_key(gpg_url)
+            gpgcheck=1
+        else:
+            gpgcheck=0
+
+        # RPM repo defaults
+        description = kw.pop('description', '%s repo' % name)
+        enabled = kw.pop('enabled', 1)
+        proxy = kw.pop('proxy', '') # will get ignored if empty
+        _type = 'repo-md'
+        baseurl = url.strip('/')  # Remove trailing slashes
+
+        ceph_repo_content = templates.custom_repo(
+            reponame=name,
+            name=description,
+            baseurl=baseurl,
+            enabled=enabled,
+            gpgcheck=gpgcheck,
+            _type=_type,
+            gpgkey=gpg_url,
+            proxy=proxy,
+            **kw
+        )
+
+        self.remote_conn.remote_module.write_yum_repo(
+            ceph_repo_content,
+            '%s.repo' % name
+        )
+
+    def remove_repo(self, name):
+        filename = os.path.join(
+            '/etc/yum.repos.d',
+            '%s.repo' % name
+        )
+        self.remote_conn.remote_module.unlink(filename)
+
+
+class DNF(RPMManagerBase):
+    """
+    The DNF Package manager
+    """
+
+    executable = 'dnf'
+    name = 'dnf'
+
+    def install(self, packages, **kw):
+        extra_install_flags = kw.pop('extra_install_flags', [])
+        if '--best' not in extra_install_flags:
+            extra_install_flags.append('--best')
+        super(DNF, self).install(
+            packages,
+            extra_install_flags=extra_install_flags,
+            **kw
+        )
+
+
+class Yum(RPMManagerBase):
+    """
+    The Yum Package manager
+    """
+
+    executable = 'yum'
+    name = 'yum'
+
+
+class Apt(PackageManager):
+    """
+    Apt package management
+    """
+
+    executable = [
+        'env',
+        'DEBIAN_FRONTEND=noninteractive',
+        'DEBIAN_PRIORITY=critical',
+        'apt-get',
+        '--assume-yes',
+        '-q',
+    ]
+    name = 'apt'
+
+    def install(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_install_flags', None)
+        cmd = self.executable + [
+            '--no-install-recommends',
+            'install'
+        ]
+
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def remove(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_remove_flags', None)
+        cmd = self.executable + [
+            '-f',
+            '--force-yes',
+            'remove'
+        ]
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def clean(self):
+        cmd = self.executable + ['update']
+        return self._run(cmd)
+
+    def add_repo_gpg_key(self, url):
+        gpg_path = url.split('file://')[-1]
+        if not url.startswith('file://'):
+            cmd = ['wget', '-O', 'release.asc', url ]
+            self._run(cmd, stop_on_nonzero=False)
+        gpg_file = 'release.asc' if not url.startswith('file://') else gpg_path
+        cmd = ['apt-key', 'add', gpg_file]
+        self._run(cmd)
+
+    def add_repo(self, name, url, **kw):
+        gpg_url = kw.pop('gpg_url', None)
+        if gpg_url:
+            self.add_repo_gpg_key(gpg_url)
+
+        safe_filename = '%s.list' % name.replace(' ', '-')
+        mode = 0o644
+        if urlparse(url).password:
+            mode = 0o600
+            self.remote_conn.logger.info(
+                "Creating repo file with mode 0600 due to presence of password"
+            )
+        self.remote_conn.remote_module.write_sources_list(
+            url,
+            self.remote_info.codename,
+            safe_filename,
+            mode
+        )
+
+        # Add package pinning for this repo
+        fqdn = urlparse(url).hostname
+        self.remote_conn.remote_module.set_apt_priority(fqdn)
+
+    def remove_repo(self, name):
+        safe_filename = '%s.list' % name.replace(' ', '-')
+        filename = os.path.join(
+            '/etc/apt/sources.list.d',
+            safe_filename
+        )
+        self.remote_conn.remote_module.unlink(filename)
+
+
+class Zypper(PackageManager):
+    """
+    Zypper package management
+    """
+
+    executable = [
+        'zypper',
+        '--non-interactive',
+        '--quiet'
+    ]
+    name = 'zypper'
+
+    def install(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_install_flags', None)
+        cmd = self.executable + ['install']
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def remove(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_remove_flags', None)
+        cmd = self.executable + ['--ignore-unknown', 'remove']
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+        cmd.extend(packages)
+        stdout, stderr, exitrc = self._check(
+            cmd,
+            **kw
+        )
+        # exitrc is 104 when package(s) not installed.
+        if not exitrc in [0, 104]:
+            raise RuntimeError("Failed to execute command: %s" % " ".join(cmd))
+        return
+
+    def clean(self):
+        cmd = self.executable + ['refresh']
+        return self._run(cmd)
+
+
+class Pacman(PackageManager):
+    """
+    Pacman package management
+    """
+
+    executable = [
+        'pacman',
+        '--noconfirm',
+    ]
+    name = 'pacman'
+
+    def install(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_install_flags', None)
+        cmd = self.executable + [
+            '-Sy',
+        ]
+
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def remove(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_remove_flags', None)
+        cmd = self.executable + [
+            '-R'
+        ]
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def clean(self):
+        cmd = self.executable + ['-Syy']
+        return self._run(cmd)
+
+    def add_repo_gpg_key(self, url):
+        cmd = ['pacman-key', '-a', url]
+        self._run(cmd)
+
+
+class AptRpm(PackageManager):
+    """
+    Apt-Rpm package management
+    """
+
+    executable = [
+        'apt-get',
+        '-y',
+        '-q',
+        '-V',
+    ]
+    name = 'apt'
+
+    def install(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+        extra_flags = kw.pop('extra_install_flags', None)
+        cmd = self.executable + ['install']
+
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def remove(self, packages, **kw):
+        if isinstance(packages, str):
+            packages = [packages]
+
+
+        extra_flags = kw.pop('extra_remove_flags', None)
+        cmd = self.executable + [
+            '-y',
+            'remove'
+        ]
+        if extra_flags:
+            if isinstance(extra_flags, str):
+                extra_flags = [extra_flags]
+            cmd.extend(extra_flags)
+
+        cmd.extend(packages)
+        return self._run(cmd)
+
+    def clean(self):
+        cmd = self.executable + ['update']
+        return self._run(cmd)
diff --git a/ceph_deploy/util/ssh.py b/ceph_deploy/util/ssh.py
new file mode 100644 (file)
index 0000000..5576371
--- /dev/null
@@ -0,0 +1,32 @@
+import logging
+from ceph_deploy.lib import remoto
+from ceph_deploy.connection import get_local_connection
+
+
+def can_connect_passwordless(hostname):
+    """
+    Ensure that current host can SSH remotely to the remote
+    host using the ``BatchMode`` option to prevent a password prompt.
+
+    That attempt will error with an exit status of 255 and a ``Permission
+    denied`` message or a``Host key verification failed`` message.
+    """
+    # Ensure we are not doing this for local hosts
+    if not remoto.backends.needs_ssh(hostname):
+        return True
+
+    logger = logging.getLogger(hostname)
+    with get_local_connection(logger) as conn:
+        # Check to see if we can login, disabling password prompts
+        command = ['ssh', '-CT', '-o', 'BatchMode=yes', hostname, 'true']
+        out, err, retval = remoto.process.check(conn, command, stop_on_error=False)
+        permission_denied_error = 'Permission denied '
+        host_key_verify_error = 'Host key verification failed.'
+        has_key_error = False
+        for line in err:
+            if permission_denied_error in line or host_key_verify_error in line:
+                has_key_error = True
+
+        if retval == 255 and has_key_error:
+            return False
+    return True
diff --git a/ceph_deploy/util/system.py b/ceph_deploy/util/system.py
new file mode 100644 (file)
index 0000000..cff1305
--- /dev/null
@@ -0,0 +1,180 @@
+from ceph_deploy.exc import ExecutableNotFound
+from ceph_deploy.lib import remoto
+
+
+def executable_path(conn, executable):
+    """
+    Remote validator that accepts a connection object to ensure that a certain
+    executable is available returning its full path if so.
+
+    Otherwise an exception with thorough details will be raised, informing the
+    user that the executable was not found.
+    """
+    executable_path = conn.remote_module.which(executable)
+    if not executable_path:
+        raise ExecutableNotFound(executable, conn.hostname)
+    return executable_path
+
+
+def is_systemd(conn):
+    """
+    Attempt to detect if a remote system is a systemd one or not
+    by looking into ``/proc`` just like the ceph init script does::
+
+        # detect systemd
+        # SYSTEMD=0
+        grep -qs systemd /proc/1/comm && SYSTEMD=1
+    """
+    return conn.remote_module.grep(
+        'systemd',
+        '/proc/1/comm'
+    )
+
+
+def is_upstart(conn):
+    """
+    This helper should only used as a fallback (last resort) as it is not
+    guaranteed that it will be absolutely correct.
+    """
+    # it may be possible that we may be systemd and the caller never checked
+    # before so lets do that
+    if is_systemd(conn):
+        return False
+
+    # get the initctl executable, if it doesn't exist we can't proceed so we
+    # are probably not upstart
+    initctl = conn.remote_module.which('initctl')
+    if not initctl:
+        return False
+
+    # finally, try and get output from initctl that might hint this is an upstart
+    # system. On a Ubuntu 14.04.2 system this would look like:
+    # $ initctl version
+    # init (upstart 1.12.1)
+    stdout, stderr, _ = remoto.process.check(
+        conn,
+        [initctl, 'version'],
+    )
+    result_string = b' '.join(stdout)
+    if b'upstart' in result_string:
+        return True
+    return False
+
+
+def enable_service(conn, service='ceph'):
+    """
+    Enable a service on a remote host depending on the type of init system.
+    Obviously, this should be done for RHEL/Fedora/CentOS systems.
+
+    This function does not do any kind of detection.
+    """
+    if is_systemd(conn):
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'enable',
+                '{service}'.format(service=service),
+            ]
+        )
+    else:
+        remoto.process.run(
+            conn,
+            [
+                'chkconfig',
+                '{service}'.format(service=service),
+                'on',
+            ]
+        )
+
+
+def disable_service(conn, service='ceph'):
+    """
+    Disable a service on a remote host depending on the type of init system.
+    Obviously, this should be done for RHEL/Fedora/CentOS systems.
+
+    This function does not do any kind of detection.
+    """
+    if is_systemd(conn):
+        # Without the check, an error is raised trying to disable an
+        # already disabled service
+        if is_systemd_service_enabled(conn, service):
+            remoto.process.run(
+                conn,
+                [
+                    'systemctl',
+                    'disable',
+                    '{service}'.format(service=service),
+                ]
+            )
+
+
+def stop_service(conn, service='ceph'):
+    """
+    Stop a service on a remote host depending on the type of init system.
+    Obviously, this should be done for RHEL/Fedora/CentOS systems.
+
+    This function does not do any kind of detection.
+    """
+    if is_systemd(conn):
+        # Without the check, an error is raised trying to stop an
+        # already stopped service
+        if is_systemd_service_active(conn, service):
+            remoto.process.run(
+                conn,
+                [
+                    'systemctl',
+                    'stop',
+                    '{service}'.format(service=service),
+                ]
+            )
+
+
+def start_service(conn, service='ceph'):
+    """
+    Stop a service on a remote host depending on the type of init system.
+    Obviously, this should be done for RHEL/Fedora/CentOS systems.
+
+    This function does not do any kind of detection.
+    """
+    if is_systemd(conn):
+        remoto.process.run(
+            conn,
+            [
+                'systemctl',
+                'start',
+                '{service}'.format(service=service),
+            ]
+        )
+
+
+def is_systemd_service_active(conn, service='ceph'):
+    """
+    Detects if a systemd service is active or not.
+    """
+    _, _, returncode = remoto.process.check(
+        conn,
+        [
+            'systemctl',
+            'is-active',
+            '--quiet',
+            '{service}'.format(service=service),
+        ]
+    )
+    return returncode == 0
+
+
+def is_systemd_service_enabled(conn, service='ceph'):
+    """
+    Detects if a systemd service is enabled or not.
+    """
+    _, _, returncode = remoto.process.check(
+        conn,
+        [
+            'systemctl',
+            'is-enabled',
+            '--quiet',
+            '{service}'.format(service=service),
+        ]
+    )
+    return returncode == 0
diff --git a/ceph_deploy/util/templates.py b/ceph_deploy/util/templates.py
new file mode 100644 (file)
index 0000000..b54f7ac
--- /dev/null
@@ -0,0 +1,94 @@
+
+
+ceph_repo = """[ceph]
+name=Ceph packages for $basearch
+baseurl={repo_url}/$basearch
+enabled=1
+gpgcheck={gpgcheck}
+priority=1
+type=rpm-md
+gpgkey={gpg_url}
+
+[ceph-noarch]
+name=Ceph noarch packages
+baseurl={repo_url}/noarch
+enabled=1
+gpgcheck={gpgcheck}
+priority=1
+type=rpm-md
+gpgkey={gpg_url}
+
+[ceph-source]
+name=Ceph source packages
+baseurl={repo_url}/SRPMS
+enabled=0
+gpgcheck={gpgcheck}
+type=rpm-md
+gpgkey={gpg_url}
+"""
+
+zypper_repo = """[ceph]
+name=Ceph packages
+type=rpm-md
+baseurl={repo_url}
+gpgcheck={gpgcheck}
+gpgkey={gpg_url}
+enabled=1
+"""
+
+
+def custom_repo(**kw):
+    """
+    Repo files need special care in that a whole line should not be present
+    if there is no value for it. Because we were using `format()` we could
+    not conditionally add a line for a repo file. So the end result would
+    contain a key with a missing value (say if we were passing `None`).
+
+    For example, it could look like::
+
+        [ceph repo]
+        name= ceph repo
+        proxy=
+        gpgcheck=
+
+    Which breaks. This function allows us to conditionally add lines,
+    preserving an order and be more careful.
+
+    Previously, and for historical purposes, this is how the template used
+    to look::
+
+        custom_repo =
+        [{repo_name}]
+        name={name}
+        baseurl={baseurl}
+        enabled={enabled}
+        gpgcheck={gpgcheck}
+        type={_type}
+        gpgkey={gpgkey}
+        proxy={proxy}
+
+    """
+    lines = []
+
+    # by using tuples (vs a dict) we preserve the order of what we want to
+    # return, like starting with a [repo name]
+    tmpl = (
+        ('reponame', '[%s]'),
+        ('name', 'name=%s'),
+        ('baseurl', 'baseurl=%s'),
+        ('enabled', 'enabled=%s'),
+        ('gpgcheck', 'gpgcheck=%s'),
+        ('_type', 'type=%s'),
+        ('gpgkey', 'gpgkey=%s'),
+        ('proxy', 'proxy=%s'),
+        ('priority', 'priority=%s'),
+    )
+
+    for line in tmpl:
+        tmpl_key, tmpl_value = line  # key values from tmpl
+
+        # ensure that there is an actual value (not None nor empty string)
+        if tmpl_key in kw and kw.get(tmpl_key) not in (None, ''):
+            lines.append(tmpl_value % kw.get(tmpl_key))
+
+    return '\n'.join(lines)
diff --git a/ceph_deploy/util/versions.py b/ceph_deploy/util/versions.py
new file mode 100644 (file)
index 0000000..810eb38
--- /dev/null
@@ -0,0 +1,47 @@
+
+
+class NormalizedVersion(object):
+    """
+    A class to provide a clean interface for setting/retrieving distinct
+    version parts divided into major, minor, and patch (following convnetions
+    from semver (see http://semver.org/)
+
+    Since a lot of times version parts need to be compared, it provides for
+    `int` representations of their string counterparts, with some sanitization
+    processing.
+
+    Defaults to '0' or 0 (int) values when values are not set or parsing fails.
+    """
+
+    def __init__(self, raw_version):
+        self.raw_version = raw_version.strip()
+        self.major = '0'
+        self.minor = '0'
+        self.patch = '0'
+        self.garbage = ''
+        self.int_major = 0
+        self.int_minor = 0
+        self.int_patch = 0
+        self._version_map = {}
+        self._set_versions()
+
+    def _set_int_versions(self):
+        version_map = dict(
+            major=self.major,
+            minor=self.minor,
+            patch=self.patch,
+            garbage=self.garbage)
+
+        # safe int versions that remove non-numerical chars
+        # for example 'rc1' in a version like '1-rc1
+        for name, value in version_map.items():
+            if '-' in value:  # get rid of garbage like -dev1 or -rc1
+                value = value.split('-')[0]
+            value = float(''.join(c for c in value if c.isdigit()) or 0)
+            int_name = "int_%s" % name
+            setattr(self, int_name, value)
+
+    def _set_versions(self):
+        split_version = (self.raw_version.split('.') + ["0"]*4)[:4]
+        self.major, self.minor, self.patch, self.garbage = split_version
+        self._set_int_versions()
diff --git a/ceph_deploy/validate.py b/ceph_deploy/validate.py
new file mode 100644 (file)
index 0000000..8ef5e73
--- /dev/null
@@ -0,0 +1,16 @@
+import argparse
+import re
+
+
+ALPHANUMERIC_RE = re.compile(r'^[a-zA-Z][a-zA-Z0-9]*$')
+
+
+def alphanumeric(s):
+    """
+    Enforces string to be alphanumeric with leading alpha.
+    """
+    if not ALPHANUMERIC_RE.match(s):
+        raise argparse.ArgumentTypeError(
+            'argument must start with a letter and contain only letters and numbers',
+            )
+    return s
diff --git a/debian/ceph-deploy.install b/debian/ceph-deploy.install
new file mode 100644 (file)
index 0000000..cec4ab6
--- /dev/null
@@ -0,0 +1 @@
+./scripts/ceph-deploy  /usr/bin
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..e63c544
--- /dev/null
@@ -0,0 +1,371 @@
+ceph-deploy (2.0.1) stable; urgency=medium
+
+  * New upstream release
+
+ -- Ceph Release Team <ceph-maintainers@ceph.com>  Tue, 19 Jun 2018 17:54:36 +0000
+
+ceph-deploy (2.0.0) stable; urgency=medium
+
+  * New upstream release
+
+ -- Ceph Release Team <ceph-maintainers@ceph.com>  Wed, 17 Jan 2018 13:17:46 +0000
+
+ceph-deploy (1.5.39) stable; urgency=medium
+
+  * New upstream release
+
+ -- Ceph Release Team <ceph-maintainers@ceph.com>  Fri, 01 Sep 2017 11:45:54 +0000
+
+ceph-deploy (1.5.38) stable; urgency=medium
+
+  * New upstream release
+
+ -- Ceph Release Team <ceph-maintainers@ceph.com>  Thu, 25 May 2017 12:35:46 +0000
+
+ceph-deploy (1.5.37) stable; urgency=medium
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Tue, 03 Jan 2017 21:19:14 +0000
+
+ceph-deploy (1.5.36) stable; urgency=medium
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Tue, 30 Aug 2016 11:47:41 +0000
+
+ceph-deploy (1.5.35) stable; urgency=medium
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Mon, 15 Aug 2016 13:15:02 +0000
+
+ceph-deploy (1.5.34) stable; urgency=medium
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Tue, 07 Jun 2016 17:06:26 +0000
+
+ceph-deploy (1.5.33) stable; urgency=medium
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Fri, 22 Apr 2016 12:36:09 +0000
+
+ceph-deploy (1.5.32) stable; urgency=medium
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Wed, 13 Apr 2016 14:21:57 +0000
+
+ceph-deploy (1.5.31) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Mon, 04 Jan 2016 18:46:26 +0000
+
+ceph-deploy (1.5.30) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Fri, 11 Dec 2015 21:09:05 +0000
+
+ceph-deploy (1.5.29) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <adeza@redhat.com>  Wed, 02 Dec 2015 18:21:15 +0000
+
+ceph-deploy (1.5.28) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Wed, 26 Aug 2015 11:25:15 -0700
+
+ceph-deploy (1.5.27) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Wed, 05 Aug 2015 15:51:53 -0700
+
+ceph-deploy (1.5.26) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Mon, 20 Jul 2015 11:09:38 -0700
+
+ceph-deploy (1.5.25) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Tue, 26 May 2015 10:38:53 -0700
+
+ceph-deploy (1.5.24) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Mon, 18 May 2015 13:35:00 -0700
+
+ceph-deploy (1.5.23) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Tue, 07 Apr 2015 17:06:35 -0700
+
+ceph-deploy (1.5.22) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Mon, 09 Mar 2015 08:14:20 -0700
+
+ceph-deploy (1.5.21) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Wed, 10 Dec 2014 07:05:42 -0800
+
+ceph-deploy (1.5.20) stable; urgency=low
+
+  * New upstream release
+
+ -- Travis Rhoden <trhoden@redhat.com>  Thu, 13 Nov 2014 08:08:46 -0800
+
+ceph-deploy (1.5.19) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Wed, 29 Oct 2014 07:19:41 -0700
+
+ceph-deploy (1.5.18) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Thu, 09 Oct 2014 10:37:06 -0700
+
+ceph-deploy (1.5.18) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Thu, 09 Oct 2014 09:38:44 -0700
+
+ceph-deploy (1.5.17) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Mon, 06 Oct 2014 09:15:34 -0700
+
+ceph-deploy (1.5.16) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Tue, 30 Sep 2014 07:25:13 -0700
+
+ceph-deploy (1.5.15) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Fri, 12 Sep 2014 12:30:50 -0700
+
+ceph-deploy (1.5.14) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Tue, 09 Sep 2014 13:51:23 -0700
+
+ceph-deploy (1.5.13) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Wed, 03 Sep 2014 05:38:57 -0700
+
+ceph-deploy (1.5.12) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Mon, 25 Aug 2014 13:04:17 -0700
+
+ceph-deploy (1.5.12) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Mon, 25 Aug 2014 12:40:48 -0700
+
+ceph-deploy (1.5.11) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Wed, 13 Aug 2014 05:29:28 -0700
+
+ceph-deploy (1.5.10) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Thu, 31 Jul 2014 10:45:11 -0700
+
+ceph-deploy (1.5.9) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Mon, 14 Jul 2014 10:12:18 -0700
+
+ceph-deploy (1.5.8) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Wed, 09 Jul 2014 15:51:46 +0000
+
+ceph-deploy (1.5.7) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Tue, 01 Jul 2014 20:54:52 +0000
+
+ceph-deploy (1.5.6) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Tue, 01 Jul 2014 15:22:02 +0000
+
+ceph-deploy (1.5.5) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Tue, 10 Jun 2014 14:12:23 +0000
+
+ceph-deploy (1.5.4) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Fri, 06 Jun 2014 15:45:10 +0000
+
+ceph-deploy (1.5.3) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Fri, 30 May 2014 12:56:14 +0000
+
+ceph-deploy (1.5.2) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Wed, 07 May 2014 18:09:23 +0000
+
+ceph-deploy (1.5.1) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Thu, 01 May 2014 16:09:56 +0000
+
+ceph-deploy (1.5.0) stable; urgency=low
+
+  * New upstream release
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Fri, 25 Apr 2014 20:15:18 +0000
+
+ceph-deploy (1.4.0-1) UNRELEASED; urgency=low
+
+  * New upstream release 
+
+ -- Alfredo Deza <alfredo.deza@inktank.com>  Wed, 19 Mar 2014 14:32:28 +0000
+
+ceph-deploy (1.3.5-1) stable; urgency=low
+
+  * New upstream release
+
+ -- Ken Dreyer <ken.dreyer@inktank.com>  Wed, 05 Feb 2014 19:56:18 +0000
+
+ceph-deploy (1.3.4-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Thu, 02 Jan 2014 17:01:21 -0800
+
+ceph-deploy (1.3.3-1) stable; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Tue, 26 Nov 2013 19:21:04 +0000
+
+ceph-deploy (1.3.2-1) stable; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <glowell@jenkins.front.sepia.ceph.com>  Wed, 13 Nov 2013 00:22:12 +0000
+
+ceph-deploy (1.3.1-1) stable; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Wed, 06 Nov 2013 20:02:54 +0000
+
+ceph-deploy (1.3-1) stable; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Fri, 01 Nov 2013 05:28:02 +0000
+
+ceph-deploy (1.2.7) stable; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Mon, 07 Oct 2013 18:33:45 +0000
+
+ceph-deploy (1.2.6-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <glowell@pudgy.ops.newdream.net>  Wed, 18 Sep 2013 09:26:57 -0700
+
+ceph-deploy (1.2.5-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Tue, 17 Sep 2013 19:25:43 -0700
+
+ceph-deploy (1.2.4-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <glowell@pudgy.ops.newdream.net>  Tue, 17 Sep 2013 11:19:59 -0700
+
+ceph-deploy (1.2.3) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Thu, 29 Aug 2013 15:20:22 -0700
+
+ceph-deploy (1.2.2) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Thu, 22 Aug 2013 12:26:56 -0700
+
+ceph-deploy (1.2.1-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Thu, 15 Aug 2013 15:19:33 -0700
+
+ceph-deploy (1.2-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Mon, 12 Aug 2013 16:59:09 -0700
+
+ceph-deploy (1.1-1) precise; urgency=low
+
+  * New upstream release 
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Tue, 18 Jun 2013 11:07:00 -0700
+
+ceph-deploy (1.0-1) stable; urgency=low
+
+  * New upstream release
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Fri, 24 May 2013 11:57:40 +0800
+
+ceph-deploy (0.0.1-1) unstable; urgency=low
+
+  * Initial release.
+
+ -- Gary Lowell <gary.lowell@inktank.com>  Mon, 10 Mar 2013 18:38:40 +0800
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7f8f011
--- /dev/null
@@ -0,0 +1 @@
+7
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..ee3c694
--- /dev/null
@@ -0,0 +1,26 @@
+Source: ceph-deploy
+Maintainer: Sage Weil <sage@newdream.net>
+Uploaders: Sage Weil <sage@newdream.net>
+Section: admin
+Priority: optional
+Build-Depends: debhelper (>= 7), python-setuptools, git
+X-Python-Version: >= 2.6
+Standards-Version: 3.9.2
+Homepage: http://ceph.com/
+Vcs-Git: git://github.com/ceph/ceph-deploy.git
+Vcs-Browser: https://github.com/ceph/ceph-deploy
+
+Package: ceph-deploy
+Architecture: all
+Depends: python,
+         python-argparse,
+         python-setuptools,
+         python-remoto,
+         ${misc:Depends},
+         ${python:Depends}
+Description:  Ceph-deploy is an easy to use configuration tool
+ for the Ceph distributed storage system.
+ .
+ This package includes the programs and libraries to support
+ simple ceph cluster deployment.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..93bc530
--- /dev/null
@@ -0,0 +1,3 @@
+Files: *
+Copyright: (c) 2004-2012 by Sage Weil <sage@newdream.net>
+License: LGPL2.1 (see /usr/share/common-licenses/LGPL-2.1)
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..3b877fa
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+export DH_VERBOSE=1
+export DEB_PYTHON_INSTALL_ARGS_ALL += --install-lib=/usr/share/ceph-deploy
+
+%:
+       dh $@ --buildsystem python_distutils --with python2
+
+override_dh_clean:
+       rm -rf ceph_deploy/lib/remoto
+       dh_clean
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..f8e0867
--- /dev/null
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# 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 <target>' where <target> 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)"
+
+clean:
+       rm -rf $(BUILDDIR)/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+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/ceph-deploy.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ceph-deploy.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/ceph-deploy"
+       @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ceph-deploy"
+       @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."
diff --git a/docs/source/_static/.empty b/docs/source/_static/.empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Book.eot b/docs/source/_themes/ceph/static/font/ApexSans-Book.eot
new file mode 100644 (file)
index 0000000..332c8cb
Binary files /dev/null and b/docs/source/_themes/ceph/static/font/ApexSans-Book.eot differ
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Book.svg b/docs/source/_themes/ceph/static/font/ApexSans-Book.svg
new file mode 100644 (file)
index 0000000..8af9af2
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" ><svg version="1.1" xmlns="http://www.w3.org/2000/svg"><defs><font horiz-adv-x="0" id="font"><font-face ascent="750" bbox="-51.4776 -260 1280 1185" cap-height="750" descent="-250" font-family="Apex Sans Book" font-stretch="normal" font-style="normal" font-weight="500" units-per-em="1000" x-height="500" /><missing-glyph d="M151 632V118L408 375ZM759 118V632L502 375ZM193 66H717L455 328ZM455 422 717 684H193ZM80 750H830V0H80Z" horiz-adv-x="910" /><glyph d="M212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="A" horiz-adv-x="670" unicode="A" /><glyph d="M520 422V664L365 422ZM12 0 504 750H955V684H591V422H920V356H591V66H955V0H520V356H323L95 0Z" glyph-name="AE" horiz-adv-x="1020" unicode="&#198;" /><glyph d="M320 825 400 1000H495L350 825ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Aacute" horiz-adv-x="670" unicode="&#193;" /><glyph d="M335 835Q299 835 271 843.5Q243 852 223 871Q203 890 192 921.5Q181 953 180 1000H221Q223 946 250 920.5Q277 895 335 895Q393 895 420 920.5Q447 946 449 1000H490Q489 953 478 921.5Q467 890 447 871Q427 852 399 843.5Q371 835 335 835ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Abreve" horiz-adv-x="670" unicode="&#258;" /><glyph d="M470 825 335 941 200 825H170L295 1000H375L500 825ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Acircumflex" horiz-adv-x="670" unicode="&#194;" /><glyph d="M490 935Q490 912 474 896Q458 880 435 880Q412 880 396 896Q380 912 380 935Q380 958 396 974Q412 990 435 990Q458 990 474 974Q490 958 490 935ZM235 880Q212 880 196 896Q180 912 180 935Q180 958 196 974Q212 990 235 990Q258 990 274 974Q290 958 290 935Q290 912 274 896Q258 880 235 880ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Adieresis" horiz-adv-x="670" unicode="&#196;" /><glyph d="M320 825 175 1000H270L350 825ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Agrave" horiz-adv-x="670" unicode="&#192;" /><glyph d="M198 930V995H472V930ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Amacron" horiz-adv-x="670" unicode="&#256;" /><glyph d="M212 272H458L335 647ZM625 0Q576 -20 547.5 -51.5Q519 -83 519 -122Q519 -145 531.5 -162Q544 -179 573 -179Q590 -179 604.5 -176.5Q619 -174 630 -171V-219Q617 -224 599 -227Q581 -230 559 -230Q514 -230 486 -205Q458 -180 458 -135Q458 -46 546 1L478 210H192L123 0H45L310 760H360Z" glyph-name="Aogonek" horiz-adv-x="670" unicode="&#260;" /><glyph d="M383 912Q383 940 370.5 950.5Q358 961 335 961Q312 961 299 950.5Q286 940 286 912Q286 885 299 874.5Q312 864 335 864Q358 864 370.5 874.5Q383 885 383 912ZM433 912Q433 861 406.5 838Q380 815 335 815Q290 815 263 838Q236 861 236 912Q236 964 263 987Q290 1010 335 1010Q380 1010 406.5 987Q433 964 433 912ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Aring" horiz-adv-x="670" unicode="&#197;" /><glyph d="M410 909Q429 909 439 914.5Q449 920 453.5 927.5Q458 935 458.5 944Q459 953 459 961L516 960Q520 907 490 878.5Q460 850 406 850Q375 850 356 860.5Q337 871 322.5 883Q308 895 294 905.5Q280 916 259 916Q240 916 230.5 910.5Q221 905 216.5 897.5Q212 890 211.5 880.5Q211 871 211 864L154 865Q150 918 179.5 946.5Q209 975 263 975Q294 975 313 964.5Q332 954 346.5 942Q361 930 375 919.5Q389 909 410 909ZM212 272H458L335 647ZM547 0 478 210H192L123 0H45L310 760H360L625 0Z" glyph-name="Atilde" horiz-adv-x="670" unicode="&#195;" /><glyph d="M481 552Q481 577 475.5 600.5Q470 624 454 642.5Q438 661 409 672.5Q380 684 332 684H161V422H340Q382 422 409.5 433.5Q437 445 453 463Q469 481 475 504.5Q481 528 481 552ZM161 66H338Q391 66 424 78.5Q457 91 475 111.5Q493 132 499.5 158Q506 184 506 211Q506 287 469 321.5Q432 356 340 356H161ZM90 750H327Q393 750 437.5 734.5Q482 719 509 692Q536 665 548 629Q560 593 560 552Q560 503 533.5 459Q507 415 458 394Q490 385 514 367Q538 349 553.5 324Q569 299 577 270Q585 241 585 211Q585 167 572 128.5Q559 90 529.5 61.5Q500 33 452 16.5Q404 0 334 0H90Z" glyph-name="B" horiz-adv-x="645" unicode="B" /><glyph d="M75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q439 760 486.5 749.5Q534 739 565 725V657Q539 667 516 674.5Q493 682 470 687Q447 692 422.5 694Q398 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q398 54 422.5 56Q447 58 470 63Q493 68 516 75Q539 82 565 93V25Q534 11 486.5 0.5Q439 -10 370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Z" glyph-name="C" horiz-adv-x="640" unicode="C" /><glyph d="M356 825 436 1000H531L386 825ZM75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q439 760 486.5 749.5Q534 739 565 725V657Q539 667 516 674.5Q493 682 470 687Q447 692 422.5 694Q398 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q398 54 422.5 56Q447 58 470 63Q493 68 516 75Q539 82 565 93V25Q534 11 486.5 0.5Q439 -10 370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Z" glyph-name="Cacute" horiz-adv-x="640" unicode="&#262;" /><glyph d="M525 1000 400 825H320L195 1000H245L360 884L475 1000ZM75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q439 760 486.5 749.5Q534 739 565 725V657Q539 667 516 674.5Q493 682 470 687Q447 692 422.5 694Q398 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q398 54 422.5 56Q447 58 470 63Q493 68 516 75Q539 82 565 93V25Q534 11 486.5 0.5Q439 -10 370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Z" glyph-name="Ccaron" horiz-adv-x="640" unicode="&#268;" /><glyph d="M310 -185Q320 -176 329 -165Q338 -154 342.5 -142.5Q347 -131 345.5 -121.5Q344 -112 334 -106Q316 -96 310 -83.5Q304 -71 305.5 -57.5Q307 -44 314 -31Q321 -18 328 -8Q253 -2 203 30Q153 62 122 113Q91 164 78 231.5Q65 299 65 375Q65 455 79.5 525Q94 595 128.5 647.5Q163 700 219.5 730Q276 760 360 760Q429 760 476.5 749.5Q524 739 555 725V657Q529 667 506 674.5Q483 682 460 687Q437 692 412.5 694Q388 696 360 696Q298 696 256 671.5Q214 647 188 603.5Q162 560 150.5 501.5Q139 443 139 375Q139 307 150.5 248.5Q162 190 188 146.5Q214 103 256 78.5Q298 54 360 54Q388 54 412.5 56Q437 58 460 63Q483 68 506 75Q529 82 555 93V25Q526 12 481 2Q436 -8 372 -10Q361 -25 359.5 -41Q358 -57 381 -71Q406 -85 413 -101Q420 -117 417 -133Q414 -149 404.5 -162.5Q395 -176 386 -185Z" glyph-name="Ccedilla" horiz-adv-x="620" unicode="&#199;" /><glyph d="M195 825 320 1000H400L525 825H475L360 941L245 825ZM75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q439 760 486.5 749.5Q534 739 565 725V657Q539 667 516 674.5Q493 682 470 687Q447 692 422.5 694Q398 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q398 54 422.5 56Q447 58 470 63Q493 68 516 75Q539 82 565 93V25Q534 11 486.5 0.5Q439 -10 370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Z" glyph-name="Ccircumflex" horiz-adv-x="640" unicode="&#264;" /><glyph d="M370 880Q347 880 331 896Q315 912 315 935Q315 958 331 974Q347 990 370 990Q393 990 409 974Q425 958 425 935Q425 912 409 896Q393 880 370 880ZM75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q439 760 486.5 749.5Q534 739 565 725V657Q539 667 516 674.5Q493 682 470 687Q447 692 422.5 694Q398 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q398 54 422.5 56Q447 58 470 63Q493 68 516 75Q539 82 565 93V25Q534 11 486.5 0.5Q439 -10 370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Z" glyph-name="Cdotaccent" horiz-adv-x="640" unicode="&#266;" /><glyph d="M161 66H298Q365 66 410.5 89Q456 112 483 153Q510 194 521.5 250.5Q533 307 533 375Q533 442 521.5 499Q510 556 483 597Q456 638 410.5 661Q365 684 298 684H161ZM90 0V750H294Q386 750 447 721.5Q508 693 544 642.5Q580 592 595 523.5Q610 455 610 375Q610 295 595 226.5Q580 158 544 107.5Q508 57 447 28.5Q386 0 294 0Z" glyph-name="D" horiz-adv-x="685" unicode="D" /><glyph d="M507 1000 382 825H302L177 1000H207L342 884L477 1000ZM161 66H298Q365 66 410.5 89Q456 112 483 153Q510 194 521.5 250.5Q533 307 533 375Q533 442 521.5 499Q510 556 483 597Q456 638 410.5 661Q365 684 298 684H161ZM90 0V750H294Q386 750 447 721.5Q508 693 544 642.5Q580 592 595 523.5Q610 455 610 375Q610 295 595 226.5Q580 158 544 107.5Q508 57 447 28.5Q386 0 294 0Z" glyph-name="Dcaron" horiz-adv-x="685" unicode="&#270;" /><glyph d="M191 416H371V361H191V66H328Q395 66 440.5 89Q486 112 513 153Q540 194 551.5 250.5Q563 307 563 375Q563 442 551.5 499Q540 556 513 597Q486 638 440.5 661Q395 684 328 684H191ZM120 416V750H324Q416 750 477 721.5Q538 693 574 642.5Q610 592 625 523.5Q640 455 640 375Q640 295 625 226.5Q610 158 574 107.5Q538 57 477 28.5Q416 0 324 0H120V361H30V416Z" glyph-name="Dcroat" horiz-adv-x="715" unicode="&#272;" /><glyph d="M70 250Q70 304 90.5 351.5Q111 399 146 434Q181 469 228.5 489.5Q276 510 330 510Q384 510 431.5 489.5Q479 469 514 434Q549 399 569.5 351.5Q590 304 590 250Q590 196 569.5 148.5Q549 101 514 66Q479 31 431.5 10.5Q384 -10 330 -10Q276 -10 228.5 10.5Q181 31 146 66Q111 101 90.5 148.5Q70 196 70 250Z" glyph-name="Delta" horiz-adv-x="660" unicode="&#8710;" /><glyph d="M490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="E" horiz-adv-x="600" unicode="E" /><glyph d="M293 825 373 1000H468L323 825ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Eacute" horiz-adv-x="600" unicode="&#201;" /><glyph d="M300 835Q264 835 236 843.5Q208 852 188 871Q168 890 157 921.5Q146 953 145 1000H186Q188 946 215 920.5Q242 895 300 895Q358 895 385 920.5Q412 946 414 1000H455Q454 953 443 921.5Q432 890 412 871Q392 852 364 843.5Q336 835 300 835ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Ebreve" horiz-adv-x="600" unicode="&#276;" /><glyph d="M173 1000 308 884 443 1000H473L348 825H268L143 1000ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Ecaron" horiz-adv-x="600" unicode="&#282;" /><glyph d="M443 825 308 941 173 825H143L268 1000H348L473 825ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Ecircumflex" horiz-adv-x="600" unicode="&#202;" /><glyph d="M463 935Q463 912 447 896Q431 880 408 880Q385 880 369 896Q353 912 353 935Q353 958 369 974Q385 990 408 990Q431 990 447 974Q463 958 463 935ZM208 880Q185 880 169 896Q153 912 153 935Q153 958 169 974Q185 990 208 990Q231 990 247 974Q263 958 263 935Q263 912 247 896Q231 880 208 880ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Edieresis" horiz-adv-x="600" unicode="&#203;" /><glyph d="M300 880Q277 880 261 896Q245 912 245 935Q245 958 261 974Q277 990 300 990Q323 990 339 974Q355 958 355 935Q355 912 339 896Q323 880 300 880ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Edotaccent" horiz-adv-x="600" unicode="&#278;" /><glyph d="M293 825 148 1000H243L323 825ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Egrave" horiz-adv-x="600" unicode="&#200;" /><glyph d="M163 930V995H437V930ZM490 356H161V66H525V0H90V750H525V684H161V422H490Z" glyph-name="Emacron" horiz-adv-x="600" unicode="&#274;" /><glyph d="M610 -96Q610 -135 592.5 -165Q575 -195 547 -215.5Q519 -236 484.5 -247Q450 -258 416 -260V-201Q438 -198 461 -190.5Q484 -183 502.5 -169.5Q521 -156 533 -136Q545 -116 545 -87V21L156 608L158 0H90V750H141L544 142L542 750H610Z" glyph-name="Eng" horiz-adv-x="700" unicode="&#330;" /><glyph d="M490 356H161V66H525V0Q476 -20 447.5 -51.5Q419 -83 419 -122Q419 -145 431.5 -162Q444 -179 473 -179Q490 -179 504.5 -176.5Q519 -174 530 -171V-219Q517 -224 499 -227Q481 -230 459 -230Q414 -230 386 -205Q358 -180 358 -135Q358 -47 445 0H90V750H525V684H161V422H490Z" glyph-name="Eogonek" horiz-adv-x="600" unicode="&#280;" /><glyph d="M191 416H371V361H191V66H328Q395 66 440.5 89Q486 112 513 153Q540 194 551.5 250.5Q563 307 563 375Q563 442 551.5 499Q540 556 513 597Q486 638 440.5 661Q395 684 328 684H191ZM120 416V750H324Q416 750 477 721.5Q538 693 574 642.5Q610 592 625 523.5Q640 455 640 375Q640 295 625 226.5Q610 158 574 107.5Q538 57 477 28.5Q416 0 324 0H120V361H30V416Z" glyph-name="Eth" horiz-adv-x="715" unicode="&#208;" /><glyph d="M60 418V484H124Q129 548 144 599Q159 650 188 686Q217 722 262.5 741Q308 760 375 760Q412 760 456.5 753Q501 746 534 732V672Q505 682 467 690Q429 698 375 698Q328 698 295.5 683Q263 668 242 640Q221 612 209.5 572.5Q198 533 194 484H473L450 418H190V332H420L397 266H194Q198 217 209.5 177.5Q221 138 242 110Q263 82 295.5 67Q328 52 375 52Q429 52 467 60Q505 68 534 78V18Q501 4 456.5 -3Q412 -10 375 -10Q308 -10 262.5 9Q217 28 188 63.5Q159 99 144 150Q129 201 124 266H60V332H120V418Z" glyph-name="Euro" horiz-adv-x="600" unicode="&#8364;" /><glyph d="M25 349H96Q115 424 169 467Q223 510 321 510Q358 510 397.5 504.5Q437 499 470 485V425Q441 435 407 441.5Q373 448 320 448Q257 448 221.5 420.5Q186 393 170 349H398L375 283H156Q155 275 155 266.5Q155 258 155 250Q155 242 155 233.5Q155 225 156 217H352L329 151H170Q186 106 221.5 79Q257 52 320 52Q373 52 407 58.5Q441 65 470 75V15Q437 1 397.5 -4.5Q358 -10 321 -10Q223 -10 169 33Q115 76 96 151H25V217H86Q85 225 85 233Q85 241 85 250Q85 258 85 266.5Q85 275 86 283H25Z" glyph-name="Euro.OP" horiz-adv-x="530" /><glyph d="M480 356H161V0H90V750H515V684H161V422H480Z" glyph-name="F" horiz-adv-x="580" unicode="F" /><glyph d="M370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q448 760 498.5 747Q549 734 580 718V649Q551 661 526 670Q501 679 476.5 684.5Q452 690 426 693Q400 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q391 54 414 55Q437 56 458 58.5Q479 61 497.5 65.5Q516 70 529 76V328H375V394H600V31Q562 13 505.5 1.5Q449 -10 370 -10Z" glyph-name="G" horiz-adv-x="690" unicode="G" /><glyph d="M370 835Q334 835 306 843.5Q278 852 258 871Q238 890 227 921.5Q216 953 215 1000H256Q258 946 285 920.5Q312 895 370 895Q428 895 455 920.5Q482 946 484 1000H525Q524 953 513 921.5Q502 890 482 871Q462 852 434 843.5Q406 835 370 835ZM370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q448 760 498.5 747Q549 734 580 718V649Q551 661 526 670Q501 679 476.5 684.5Q452 690 426 693Q400 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q391 54 414 55Q437 56 458 58.5Q479 61 497.5 65.5Q516 70 529 76V328H375V394H600V31Q562 13 505.5 1.5Q449 -10 370 -10Z" glyph-name="Gbreve" horiz-adv-x="690" unicode="&#286;" /><glyph d="M505 825 370 941 235 825H205L330 1000H410L535 825ZM370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q448 760 498.5 747Q549 734 580 718V649Q551 661 526 670Q501 679 476.5 684.5Q452 690 426 693Q400 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q391 54 414 55Q437 56 458 58.5Q479 61 497.5 65.5Q516 70 529 76V328H375V394H600V31Q562 13 505.5 1.5Q449 -10 370 -10Z" glyph-name="Gcircumflex" horiz-adv-x="690" unicode="&#284;" /><glyph d="M330 -250 348 -170Q332 -164 323.5 -150.5Q315 -137 315 -120Q315 -97 331 -81Q347 -65 370 -65Q393 -65 409 -81Q425 -97 425 -120Q425 -135 419 -147L372 -250ZM370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q448 760 498.5 747Q549 734 580 718V649Q551 661 526 670Q501 679 476.5 684.5Q452 690 426 693Q400 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q391 54 414 55Q437 56 458 58.5Q479 61 497.5 65.5Q516 70 529 76V328H375V394H600V31Q562 13 505.5 1.5Q449 -10 370 -10Z" glyph-name="Gcommaaccent" horiz-adv-x="690" unicode="&#290;" /><glyph d="M370 880Q347 880 331 896Q315 912 315 935Q315 958 331 974Q347 990 370 990Q393 990 409 974Q425 958 425 935Q425 912 409 896Q393 880 370 880ZM370 -10Q286 -10 229.5 20Q173 50 138.5 102.5Q104 155 89.5 225Q75 295 75 375Q75 455 89.5 525Q104 595 138.5 647.5Q173 700 229.5 730Q286 760 370 760Q448 760 498.5 747Q549 734 580 718V649Q551 661 526 670Q501 679 476.5 684.5Q452 690 426 693Q400 696 370 696Q308 696 266 671.5Q224 647 198 603.5Q172 560 160.5 501.5Q149 443 149 375Q149 307 160.5 248.5Q172 190 198 146.5Q224 103 266 78.5Q308 54 370 54Q391 54 414 55Q437 56 458 58.5Q479 61 497.5 65.5Q516 70 529 76V328H375V394H600V31Q562 13 505.5 1.5Q449 -10 370 -10Z" glyph-name="Gdotaccent" horiz-adv-x="690" unicode="&#288;" /><glyph d="M529 0V356H161V0H90V750H161V422H529V750H600V0Z" glyph-name="H" horiz-adv-x="690" unicode="H" /><glyph d="M529 422V540H161V422ZM529 0V356H161V0H90V750H161V606H529V750H600V0Z" glyph-name="Hbar" horiz-adv-x="690" unicode="&#294;" /><glyph d="M480 825 345 941 210 825H180L305 1000H385L510 825ZM529 0V356H161V0H90V750H161V422H529V750H600V0Z" glyph-name="Hcircumflex" horiz-adv-x="690" unicode="&#292;" /><glyph d="M112 0V750H183V0Z" glyph-name="I" horiz-adv-x="295" unicode="I" /><glyph d="M295 61Q313 56 327.5 54.5Q342 53 365 53Q414 53 444 71Q474 89 474 138V750H545V140Q545 68 502 29Q459 -10 365 -10Q349 -10 329.5 -8.5Q310 -7 295 -3ZM112 0V750H183V0Z" glyph-name="IJ" horiz-adv-x="655" unicode="&#306;" /><glyph d="M133 825 213 1000H308L163 825ZM112 0V750H183V0Z" glyph-name="Iacute" horiz-adv-x="295" unicode="&#205;" /><glyph d="M148 835Q112 835 84 843.5Q56 852 36 871Q16 890 5 921.5Q-6 953 -7 1000H34Q36 946 63 920.5Q90 895 148 895Q206 895 233 920.5Q260 946 262 1000H303Q302 953 291 921.5Q280 890 260 871Q240 852 212 843.5Q184 835 148 835ZM112 0V750H183V0Z" glyph-name="Ibreve" horiz-adv-x="295" unicode="&#300;" /><glyph d="M283 825 148 941 13 825H-17L108 1000H188L313 825ZM112 0V750H183V0Z" glyph-name="Icircumflex" horiz-adv-x="295" unicode="&#206;" /><glyph d="M303 935Q303 912 287 896Q271 880 248 880Q225 880 209 896Q193 912 193 935Q193 958 209 974Q225 990 248 990Q271 990 287 974Q303 958 303 935ZM48 880Q25 880 9 896Q-7 912 -7 935Q-7 958 9 974Q25 990 48 990Q71 990 87 974Q103 958 103 935Q103 912 87 896Q71 880 48 880ZM112 0V750H183V0Z" glyph-name="Idieresis" horiz-adv-x="295" unicode="&#207;" /><glyph d="M148 880Q125 880 109 896Q93 912 93 935Q93 958 109 974Q125 990 148 990Q171 990 187 974Q203 958 203 935Q203 912 187 896Q171 880 148 880ZM112 0V750H183V0Z" glyph-name="Idotaccent" horiz-adv-x="295" unicode="&#304;" /><glyph d="M133 825 -12 1000H83L163 825ZM112 0V750H183V0Z" glyph-name="Igrave" horiz-adv-x="295" unicode="&#204;" /><glyph d="M11 930V995H285V930ZM112 0V750H183V0Z" glyph-name="Imacron" horiz-adv-x="295" unicode="&#298;" /><glyph d="M183 0Q170 -10 156.5 -23Q143 -36 132 -51.5Q121 -67 114 -85Q107 -103 107 -122Q107 -145 119.5 -162Q132 -179 161 -179Q178 -179 192.5 -176.5Q207 -174 218 -171V-219Q205 -224 187 -227Q169 -230 147 -230Q102 -230 74 -205Q46 -180 46 -135Q46 -90 66 -56Q86 -22 112 1V750H183Z" glyph-name="Iogonek" horiz-adv-x="295" unicode="&#302;" /><glyph d="M222 909Q241 909 251 914.5Q261 920 265.5 927.5Q270 935 270.5 944Q271 953 271 961L328 960Q332 907 302 878.5Q272 850 218 850Q187 850 168 860.5Q149 871 134.5 883Q120 895 106 905.5Q92 916 71 916Q52 916 42.5 910.5Q33 905 28.5 897.5Q24 890 23.5 880.5Q23 871 23 864L-34 865Q-38 918 -8.5 946.5Q21 975 75 975Q106 975 125 964.5Q144 954 158.5 942Q173 930 187 919.5Q201 909 222 909ZM112 0V750H183V0Z" glyph-name="Itilde" horiz-adv-x="295" unicode="&#296;" /><glyph d="M50 61Q68 56 82.5 54.5Q97 53 120 53Q169 53 199 71Q229 89 229 138V750H300V140Q300 68 257 29Q214 -10 120 -10Q104 -10 84.5 -8.5Q65 -7 50 -3Z" glyph-name="J" horiz-adv-x="410" unicode="J" /><glyph d="M400 825 265 941 130 825H100L225 1000H305L430 825ZM50 61Q68 56 82.5 54.5Q97 53 120 53Q169 53 199 71Q229 89 229 138V750H300V140Q300 68 257 29Q214 -10 120 -10Q104 -10 84.5 -8.5Q65 -7 50 -3Z" glyph-name="Jcircumflex" horiz-adv-x="410" unicode="&#308;" /><glyph d="M159 288V0H90V750H159V376L468 750H560L268 414L590 0H497L224 363Z" glyph-name="K" horiz-adv-x="625" unicode="K" /><glyph d="M264 -250 282 -170Q266 -164 257.5 -150.5Q249 -137 249 -120Q249 -97 265 -81Q281 -65 304 -65Q327 -65 343 -81Q359 -97 359 -120Q359 -135 353 -147L306 -250ZM159 288V0H90V750H159V376L468 750H560L268 414L590 0H497L224 363Z" glyph-name="Kcommaaccent" horiz-adv-x="625" unicode="&#310;" /><glyph d="M161 66H505V0H90V750H161Z" glyph-name="L" horiz-adv-x="560" unicode="L" /><glyph d="M111 825 191 1000H286L141 825ZM161 66H505V0H90V750H161Z" glyph-name="Lacute" horiz-adv-x="560" unicode="&#313;" /><glyph d="M335 750 287 550H241L257 750ZM161 66H505V0H90V750H161Z" glyph-name="Lcaron" horiz-adv-x="560" unicode="&#317;" /><glyph d="M258 -251 276 -171Q260 -165 251.5 -151.5Q243 -138 243 -121Q243 -98 259 -82Q275 -66 298 -66Q321 -66 337 -82Q353 -98 353 -121Q353 -136 347 -148L300 -251ZM161 66H505V0H90V750H161Z" glyph-name="Lcommaaccent" horiz-adv-x="560" unicode="&#315;" /><glyph d="M339 375Q339 396 353.5 410.5Q368 425 389 425Q410 425 424.5 410.5Q439 396 439 375Q439 354 424.5 339.5Q410 325 389 325Q368 325 353.5 339.5Q339 354 339 375ZM161 66H505V0H90V750H161Z" glyph-name="Ldot" horiz-adv-x="560" unicode="&#319;" /><glyph d="M191 386 395 510V447L191 323V66H535V0H120V280L20 219V282L120 343V750H191Z" glyph-name="Lslash" horiz-adv-x="590" unicode="&#321;" /><glyph d="M463 115H407L154 661L158 0H90V750H181L435 205L689 750H780V0H712L716 661Z" glyph-name="M" horiz-adv-x="870" unicode="M" /><glyph d="M559 0 156 608 158 0H90V750H141L544 142L542 750H610V0Z" glyph-name="N" horiz-adv-x="700" unicode="N" /><glyph d="M323 825 403 1000H498L373 825ZM559 0 156 608 158 0H90V750H141L544 142L542 750H610V0Z" glyph-name="Nacute" horiz-adv-x="700" unicode="&#323;" /><glyph d="M515 1000 390 825H310L185 1000H215L350 884L485 1000ZM559 0 156 608 158 0H90V750H141L544 142L542 750H610V0Z" glyph-name="Ncaron" horiz-adv-x="700" unicode="&#327;" /><glyph d="M310 -251 328 -171Q312 -165 303.5 -151.5Q295 -138 295 -121Q295 -98 311 -82Q327 -66 350 -66Q373 -66 389 -82Q405 -98 405 -121Q405 -136 399 -148L352 -251ZM559 0 156 608 158 0H90V750H141L544 142L542 750H610V0Z" glyph-name="Ncommaaccent" horiz-adv-x="700" unicode="&#325;" /><glyph d="M425 909Q444 909 454 914.5Q464 920 468.5 927.5Q473 935 473.5 944Q474 953 474 961L531 960Q535 907 505 878.5Q475 850 421 850Q390 850 371 860.5Q352 871 337.5 883Q323 895 309 905.5Q295 916 274 916Q255 916 245.5 910.5Q236 905 231.5 897.5Q227 890 226.5 880.5Q226 871 226 864L169 865Q165 918 194.5 946.5Q224 975 278 975Q309 975 328 964.5Q347 954 361.5 942Q376 930 390 919.5Q404 909 425 909ZM559 0 156 608 158 0H90V750H141L544 142L542 750H610V0Z" glyph-name="Ntilde" horiz-adv-x="700" unicode="&#209;" /><glyph d="M561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="O" horiz-adv-x="710" unicode="O" /><glyph d="M439 684H377Q309 684 264 661Q219 638 192 597Q165 556 153.5 499Q142 442 142 375Q142 307 153.5 250.5Q165 194 192 153Q219 112 264 89Q309 66 377 66H439ZM800 356H510V66H835V0H381Q289 0 228 28.5Q167 57 131 107.5Q95 158 80 226.5Q65 295 65 375Q65 455 80 523.5Q95 592 131 642.5Q167 693 228 721.5Q289 750 381 750H835V684H510V422H800Z" glyph-name="OE" horiz-adv-x="900" unicode="&#338;" /><glyph d="M340 825 420 1000H515L370 825ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Oacute" horiz-adv-x="710" unicode="&#211;" /><glyph d="M355 835Q319 835 291 843.5Q263 852 243 871Q223 890 212 921.5Q201 953 200 1000H241Q243 946 270 920.5Q297 895 355 895Q413 895 440 920.5Q467 946 469 1000H510Q509 953 498 921.5Q487 890 467 871Q447 852 419 843.5Q391 835 355 835ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Obreve" horiz-adv-x="710" unicode="&#334;" /><glyph d="M490 825 355 941 220 825H190L315 1000H395L520 825ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Ocircumflex" horiz-adv-x="710" unicode="&#212;" /><glyph d="M510 935Q510 912 494 896Q478 880 455 880Q432 880 416 896Q400 912 400 935Q400 958 416 974Q432 990 455 990Q478 990 494 974Q510 958 510 935ZM255 880Q232 880 216 896Q200 912 200 935Q200 958 216 974Q232 990 255 990Q278 990 294 974Q310 958 310 935Q310 912 294 896Q278 880 255 880ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Odieresis" horiz-adv-x="710" unicode="&#214;" /><glyph d="M340 825 195 1000H290L370 825ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Ograve" horiz-adv-x="710" unicode="&#210;" /><glyph d="M265 825 345 1000H440L295 825ZM415 825 495 1000H590L445 825ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Ohungarumlaut" horiz-adv-x="710" unicode="&#336;" /><glyph d="M218 930V995H492V930ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Omacron" horiz-adv-x="710" unicode="&#332;" /><glyph d="M211 423Q245 423 267.5 426.5Q290 430 311 435V376Q285 370 261 367.5Q237 365 211 365Q120 365 77.5 415.5Q35 466 35 563Q35 660 77.5 710Q120 760 211 760Q237 760 261 757.5Q285 755 311 749V690Q290 695 267.5 698.5Q245 702 211 702Q154 702 125.5 670Q97 638 97 563Q97 488 125.5 455.5Q154 423 211 423Z" glyph-name="Omega" horiz-adv-x="336" unicode="&#8486;" /><glyph d="M561 375Q561 441 554 499.5Q547 558 525 602L219 100Q242 78 275 66Q308 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM149 375Q149 309 155.5 250.5Q162 192 185 147L491 649Q468 672 434.5 684Q401 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375ZM143 78Q122 106 108.5 140Q95 174 87.5 212Q80 250 77.5 291.5Q75 333 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q412 760 454.5 747Q497 734 528 711L552 750H615L567 672Q588 644 601.5 610Q615 576 622.5 538Q630 500 632.5 458.5Q635 417 635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q297 -10 255 3Q213 16 182 39L158 0H95Z" glyph-name="Oslash" horiz-adv-x="690" unicode="&#216;" /><glyph d="M430 909Q449 909 459 914.5Q469 920 473.5 927.5Q478 935 478.5 944Q479 953 479 961L536 960Q540 907 510 878.5Q480 850 426 850Q395 850 376 860.5Q357 871 342.5 883Q328 895 314 905.5Q300 916 279 916Q260 916 250.5 910.5Q241 905 236.5 897.5Q232 890 231.5 880.5Q231 871 231 864L174 865Q170 918 199.5 946.5Q229 975 283 975Q314 975 333 964.5Q352 954 366.5 942Q381 930 395 919.5Q409 909 430 909ZM561 375Q561 440 554.5 498.5Q548 557 526 601Q504 645 463.5 670.5Q423 696 355 696Q287 696 246.5 670.5Q206 645 184 601Q162 557 155.5 498.5Q149 440 149 375Q149 310 155.5 251.5Q162 193 184 149Q206 105 246.5 79.5Q287 54 355 54Q423 54 463.5 79.5Q504 105 526 149Q548 193 554.5 251.5Q561 310 561 375ZM635 375Q635 301 626 232Q617 163 587.5 109Q558 55 503 22.5Q448 -10 355 -10Q263 -10 208 22.5Q153 55 123.5 109Q94 163 84.5 232Q75 301 75 375Q75 448 84.5 517.5Q94 587 123.5 641Q153 695 208 727.5Q263 760 355 760Q448 760 503 727.5Q558 695 587.5 641Q617 587 626 517.5Q635 448 635 375Z" glyph-name="Otilde" horiz-adv-x="710" unicode="&#213;" /><glyph d="M161 366H323Q376 366 409 380.5Q442 395 460 418.5Q478 442 484.5 470Q491 498 491 525Q491 552 484.5 580Q478 608 460 631.5Q442 655 409 669.5Q376 684 323 684H161ZM161 0H90V750H319Q389 750 437 731.5Q485 713 514.5 681.5Q544 650 557 609.5Q570 569 570 525Q570 481 557 440.5Q544 400 514.5 368.5Q485 337 437 318.5Q389 300 319 300H161Z" glyph-name="P" horiz-adv-x="620" unicode="P" /><glyph d="M558 375Q558 445 551.5 504Q545 563 523.5 605.5Q502 648 462 671.5Q422 695 355 695Q288 695 248 671.5Q208 648 186.5 605.5Q165 563 158.5 504Q152 445 152 375Q152 304 158.5 245.5Q165 187 186.5 144.5Q208 102 248 78.5Q288 55 355 55Q422 55 462 78.5Q502 102 523.5 144.5Q545 187 551.5 245.5Q558 304 558 375ZM438 6 725 -52V-124L263 0Q201 16 164 52.5Q127 89 107.5 139Q88 189 81.5 249.5Q75 310 75 375Q75 455 85 525.5Q95 596 125 648Q155 700 210 730Q265 760 355 760Q446 760 501 730Q556 700 586 648Q616 596 625.5 525.5Q635 455 635 375Q635 305 629 243.5Q623 182 602.5 134Q582 86 543 53Q504 20 438 7Z" glyph-name="Q" horiz-adv-x="710" unicode="Q" /><glyph d="M161 394H339Q389 394 419 405.5Q449 417 465.5 436.5Q482 456 487 482.5Q492 509 492 539Q492 569 487 595.5Q482 622 465.5 641.5Q449 661 419 672.5Q389 684 339 684H161ZM161 0H90V750H325Q395 750 442 735Q489 720 517.5 692.5Q546 665 558 626Q570 587 570 539Q570 464 539.5 412Q509 360 432 340L611 0H521L358 329Q350 328 341.5 328Q333 328 325 328H161Z" glyph-name="R" horiz-adv-x="651" unicode="R" /><glyph d="M300 825 380 1000H475L330 825ZM161 394H339Q389 394 419 405.5Q449 417 465.5 436.5Q482 456 487 482.5Q492 509 492 539Q492 569 487 595.5Q482 622 465.5 641.5Q449 661 419 672.5Q389 684 339 684H161ZM161 0H90V750H325Q395 750 442 735Q489 720 517.5 692.5Q546 665 558 626Q570 587 570 539Q570 464 539.5 412Q509 360 432 340L611 0H521L358 329Q350 328 341.5 328Q333 328 325 328H161Z" glyph-name="Racute" horiz-adv-x="651" unicode="&#340;" /><glyph d="M480 1000 355 825H275L150 1000H180L315 884L450 1000ZM161 394H339Q389 394 419 405.5Q449 417 465.5 436.5Q482 456 487 482.5Q492 509 492 539Q492 569 487 595.5Q482 622 465.5 641.5Q449 661 419 672.5Q389 684 339 684H161ZM161 0H90V750H325Q395 750 442 735Q489 720 517.5 692.5Q546 665 558 626Q570 587 570 539Q570 464 539.5 412Q509 360 432 340L611 0H521L358 329Q350 328 341.5 328Q333 328 325 328H161Z" glyph-name="Rcaron" horiz-adv-x="651" unicode="&#344;" /><glyph d="M275 -250 293 -170Q277 -164 268.5 -150.5Q260 -137 260 -120Q260 -97 276 -81Q292 -65 315 -65Q338 -65 354 -81Q370 -97 370 -120Q370 -135 364 -147L317 -250ZM161 394H339Q389 394 419 405.5Q449 417 465.5 436.5Q482 456 487 482.5Q492 509 492 539Q492 569 487 595.5Q482 622 465.5 641.5Q449 661 419 672.5Q389 684 339 684H161ZM161 0H90V750H325Q395 750 442 735Q489 720 517.5 692.5Q546 665 558 626Q570 587 570 539Q570 464 539.5 412Q509 360 432 340L611 0H521L358 329Q350 328 341.5 328Q333 328 325 328H161Z" glyph-name="Rcommaaccent" horiz-adv-x="651" unicode="&#342;" /><glyph d="M320 696Q264 696 229 684.5Q194 673 174.5 653Q155 633 147.5 606.5Q140 580 140 550Q140 512 152 489Q164 466 187 453Q210 440 244.5 434Q279 428 323 422Q378 415 421.5 401.5Q465 388 495 364.5Q525 341 540.5 305Q556 269 556 216Q556 178 544 138Q532 98 501 65Q470 32 416.5 11Q363 -10 279 -10Q253 -10 223.5 -6.5Q194 -3 165.5 2.5Q137 8 112 15Q87 22 69 30V96Q87 90 107.5 82.5Q128 75 153.5 69Q179 63 210 58.5Q241 54 280 54Q338 54 377 66.5Q416 79 439 101Q462 123 471.5 152.5Q481 182 481 216Q481 254 471.5 278.5Q462 303 441.5 318Q421 333 388.5 341.5Q356 350 309 356Q243 364 197 378.5Q151 393 122.5 416Q94 439 81.5 472Q69 505 69 551Q69 584 79 620.5Q89 657 116.5 688Q144 719 193 739.5Q242 760 321 760Q390 760 437.5 749.5Q485 739 516 725V657Q465 679 420 687.5Q375 696 320 696Z" glyph-name="S" horiz-adv-x="625" unicode="S" /><glyph d="M302 825 382 1000H477L332 825ZM320 696Q264 696 229 684.5Q194 673 174.5 653Q155 633 147.5 606.5Q140 580 140 550Q140 512 152 489Q164 466 187 453Q210 440 244.5 434Q279 428 323 422Q378 415 421.5 401.5Q465 388 495 364.5Q525 341 540.5 305Q556 269 556 216Q556 178 544 138Q532 98 501 65Q470 32 416.5 11Q363 -10 279 -10Q253 -10 223.5 -6.5Q194 -3 165.5 2.5Q137 8 112 15Q87 22 69 30V96Q87 90 107.5 82.5Q128 75 153.5 69Q179 63 210 58.5Q241 54 280 54Q338 54 377 66.5Q416 79 439 101Q462 123 471.5 152.5Q481 182 481 216Q481 254 471.5 278.5Q462 303 441.5 318Q421 333 388.5 341.5Q356 350 309 356Q243 364 197 378.5Q151 393 122.5 416Q94 439 81.5 472Q69 505 69 551Q69 584 79 620.5Q89 657 116.5 688Q144 719 193 739.5Q242 760 321 760Q390 760 437.5 749.5Q485 739 516 725V657Q465 679 420 687.5Q375 696 320 696Z" glyph-name="Sacute" horiz-adv-x="625" unicode="&#346;" /><glyph d="M482 1000 357 825H277L152 1000H202L317 884L432 1000ZM320 696Q264 696 229 684.5Q194 673 174.5 653Q155 633 147.5 606.5Q140 580 140 550Q140 512 152 489Q164 466 187 453Q210 440 244.5 434Q279 428 323 422Q378 415 421.5 401.5Q465 388 495 364.5Q525 341 540.5 305Q556 269 556 216Q556 178 544 138Q532 98 501 65Q470 32 416.5 11Q363 -10 279 -10Q253 -10 223.5 -6.5Q194 -3 165.5 2.5Q137 8 112 15Q87 22 69 30V96Q87 90 107.5 82.5Q128 75 153.5 69Q179 63 210 58.5Q241 54 280 54Q338 54 377 66.5Q416 79 439 101Q462 123 471.5 152.5Q481 182 481 216Q481 254 471.5 278.5Q462 303 441.5 318Q421 333 388.5 341.5Q356 350 309 356Q243 364 197 378.5Q151 393 122.5 416Q94 439 81.5 472Q69 505 69 551Q69 584 79 620.5Q89 657 116.5 688Q144 719 193 739.5Q242 760 321 760Q390 760 437.5 749.5Q485 739 516 725V657Q465 679 420 687.5Q375 696 320 696Z" glyph-name="Scaron" horiz-adv-x="625" unicode="&#352;" /><glyph d="M322 -8Q311 -24 308.5 -40.5Q306 -57 330 -71Q355 -85 362 -101Q369 -117 366 -133Q363 -149 353.5 -162.5Q344 -176 335 -185H259Q269 -176 278 -165Q287 -154 291.5 -142.5Q296 -131 294.5 -121.5Q293 -112 283 -106Q265 -96 259 -83.5Q253 -71 254.5 -58Q256 -45 262.5 -32.5Q269 -20 276 -10Q250 -10 221 -6.5Q192 -3 164 2.5Q136 8 111 15Q86 22 69 30V96Q87 90 107.5 82.5Q128 75 153.5 69Q179 63 210 58.5Q241 54 280 54Q338 54 377 66.5Q416 79 439 101Q462 123 471.5 152.5Q481 182 481 216Q481 254 471.5 278.5Q462 303 441.5 318Q421 333 388.5 341.5Q356 350 309 356Q243 364 197 378.5Q151 393 122.5 416Q94 439 81.5 472Q69 505 69 551Q69 584 79 620.5Q89 657 116.5 688Q144 719 193 739.5Q242 760 321 760Q390 760 437.5 749.5Q485 739 516 725V657Q465 679 420 687.5Q375 696 320 696Q264 696 229 684.5Q194 673 174.5 653Q155 633 147.5 606.5Q140 580 140 550Q140 512 152 489Q164 466 187 453Q210 440 244.5 434Q279 428 323 422Q378 415 421.5 401.5Q465 388 495 364.5Q525 341 540.5 305Q556 269 556 216Q556 180 545.5 142.5Q535 105 509 73.5Q483 42 437.5 19.5Q392 -3 322 -8Z" glyph-name="Scedilla" horiz-adv-x="625" unicode="&#350;" /><glyph d="M452 825 317 941 182 825H152L277 1000H357L482 825ZM320 696Q264 696 229 684.5Q194 673 174.5 653Q155 633 147.5 606.5Q140 580 140 550Q140 512 152 489Q164 466 187 453Q210 440 244.5 434Q279 428 323 422Q378 415 421.5 401.5Q465 388 495 364.5Q525 341 540.5 305Q556 269 556 216Q556 178 544 138Q532 98 501 65Q470 32 416.5 11Q363 -10 279 -10Q253 -10 223.5 -6.5Q194 -3 165.5 2.5Q137 8 112 15Q87 22 69 30V96Q87 90 107.5 82.5Q128 75 153.5 69Q179 63 210 58.5Q241 54 280 54Q338 54 377 66.5Q416 79 439 101Q462 123 471.5 152.5Q481 182 481 216Q481 254 471.5 278.5Q462 303 441.5 318Q421 333 388.5 341.5Q356 350 309 356Q243 364 197 378.5Q151 393 122.5 416Q94 439 81.5 472Q69 505 69 551Q69 584 79 620.5Q89 657 116.5 688Q144 719 193 739.5Q242 760 321 760Q390 760 437.5 749.5Q485 739 516 725V657Q465 679 420 687.5Q375 696 320 696Z" glyph-name="Scircumflex" horiz-adv-x="625" unicode="&#348;" /><glyph d="M248 -250 266 -170Q250 -164 241.5 -150.5Q233 -137 233 -120Q233 -97 249 -81Q265 -65 288 -65Q311 -65 327 -81Q343 -97 343 -120Q343 -135 337 -147L290 -250ZM320 696Q264 696 229 684.5Q194 673 174.5 653Q155 633 147.5 606.5Q140 580 140 550Q140 512 152 489Q164 466 187 453Q210 440 244.5 434Q279 428 323 422Q378 415 421.5 401.5Q465 388 495 364.5Q525 341 540.5 305Q556 269 556 216Q556 178 544 138Q532 98 501 65Q470 32 416.5 11Q363 -10 279 -10Q253 -10 223.5 -6.5Q194 -3 165.5 2.5Q137 8 112 15Q87 22 69 30V96Q87 90 107.5 82.5Q128 75 153.5 69Q179 63 210 58.5Q241 54 280 54Q338 54 377 66.5Q416 79 439 101Q462 123 471.5 152.5Q481 182 481 216Q481 254 471.5 278.5Q462 303 441.5 318Q421 333 388.5 341.5Q356 350 309 356Q243 364 197 378.5Q151 393 122.5 416Q94 439 81.5 472Q69 505 69 551Q69 584 79 620.5Q89 657 116.5 688Q144 719 193 739.5Q242 760 321 760Q390 760 437.5 749.5Q485 739 516 725V657Q465 679 420 687.5Q375 696 320 696Z" glyph-name="Scommaaccent" horiz-adv-x="625" unicode="&#536;" /><glyph d="M328 684V0H257V684H50V750H535V684Z" glyph-name="T" horiz-adv-x="585" unicode="T" /><glyph d="M475 361H328V0H257V361H110V416H257V684H50V750H535V684H328V416H475Z" glyph-name="Tbar" horiz-adv-x="585" unicode="&#358;" /><glyph d="M457 1000 332 825H252L127 1000H157L292 884L427 1000ZM328 684V0H257V684H50V750H535V684Z" glyph-name="Tcaron" horiz-adv-x="585" unicode="&#356;" /><glyph d="M322 1185Q332 1177 343 1159.5Q354 1142 354 1113Q354 1090 342.5 1068Q331 1046 313.5 1028Q296 1010 274 997Q252 984 231 977V1000Q243 1004 253 1016Q263 1028 270.5 1044.5Q278 1061 281.5 1079Q285 1097 284 1114Q282 1140 269.5 1158.5Q257 1177 246 1185ZM253 -250 271 -170Q255 -164 246.5 -150.5Q238 -137 238 -120Q238 -97 254 -81Q270 -65 293 -65Q316 -65 332 -81Q348 -97 348 -120Q348 -135 342 -147L295 -250ZM328 684V0H257V684H50V750H535V684Z" glyph-name="Tcommaaccent" horiz-adv-x="585" unicode="&#354;" /><glyph d="M151 216H313Q366 216 399 230.5Q432 245 450 268.5Q468 292 474.5 320Q481 348 481 375Q481 402 474.5 430Q468 458 450 481.5Q432 505 399 519.5Q366 534 313 534H151ZM151 0H80V750H151V600H309Q379 600 427 581.5Q475 563 504.5 531.5Q534 500 547 459.5Q560 419 560 375Q560 331 547 290.5Q534 250 504.5 218.5Q475 187 427 168.5Q379 150 309 150H151Z" glyph-name="Thorn" horiz-adv-x="600" unicode="&#222;" /><glyph d="M350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="U" horiz-adv-x="700" unicode="U" /><glyph d="M335 825 415 1000H510L365 825ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Uacute" horiz-adv-x="700" unicode="&#218;" /><glyph d="M350 835Q314 835 286 843.5Q258 852 238 871Q218 890 207 921.5Q196 953 195 1000H236Q238 946 265 920.5Q292 895 350 895Q408 895 435 920.5Q462 946 464 1000H505Q504 953 493 921.5Q482 890 462 871Q442 852 414 843.5Q386 835 350 835ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Ubreve" horiz-adv-x="700" unicode="&#364;" /><glyph d="M485 825 350 941 215 825H185L310 1000H390L515 825ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Ucircumflex" horiz-adv-x="700" unicode="&#219;" /><glyph d="M505 935Q505 912 489 896Q473 880 450 880Q427 880 411 896Q395 912 395 935Q395 958 411 974Q427 990 450 990Q473 990 489 974Q505 958 505 935ZM250 880Q227 880 211 896Q195 912 195 935Q195 958 211 974Q227 990 250 990Q273 990 289 974Q305 958 305 935Q305 912 289 896Q273 880 250 880ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Udieresis" horiz-adv-x="700" unicode="&#220;" /><glyph d="M335 825 190 1000H285L365 825ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Ugrave" horiz-adv-x="700" unicode="&#217;" /><glyph d="M260 825 340 1000H435L290 825ZM410 825 490 1000H585L440 825ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Uhungarumlaut" horiz-adv-x="700" unicode="&#368;" /><glyph d="M213 930V995H487V930ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Umacron" horiz-adv-x="700" unicode="&#362;" /><glyph d="M350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 176 599 135Q583 94 557.5 66.5Q532 39 500 23Q468 7 436 0Q387 -20 358.5 -51.5Q330 -83 330 -122Q330 -145 342.5 -162Q355 -179 384 -179Q401 -179 415.5 -176.5Q430 -174 441 -171V-219Q428 -224 410 -227Q392 -230 370 -230Q325 -230 297 -205Q269 -180 269 -135Q269 -56 339 -10Q316 -9 288 -5Q260 -1 232 9Q204 19 177.5 36.5Q151 54 130.5 81Q110 108 97.5 146Q85 184 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Uogonek" horiz-adv-x="700" unicode="&#370;" /><glyph d="M398 912Q398 940 385.5 950.5Q373 961 350 961Q327 961 314 950.5Q301 940 301 912Q301 885 314 874.5Q327 864 350 864Q373 864 385.5 874.5Q398 885 398 912ZM448 912Q448 861 421.5 838Q395 815 350 815Q305 815 278 838Q251 861 251 912Q251 964 278 987Q305 1010 350 1010Q395 1010 421.5 987Q448 964 448 912ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Uring" horiz-adv-x="700" unicode="&#366;" /><glyph d="M425 909Q444 909 454 914.5Q464 920 468.5 927.5Q473 935 473.5 944Q474 953 474 961L531 960Q535 907 505 878.5Q475 850 421 850Q390 850 371 860.5Q352 871 337.5 883Q323 895 309 905.5Q295 916 274 916Q255 916 245.5 910.5Q236 905 231.5 897.5Q227 890 226.5 880.5Q226 871 226 864L169 865Q165 918 194.5 946.5Q224 975 278 975Q309 975 328 964.5Q347 954 361.5 942Q376 930 390 919.5Q404 909 425 909ZM350 55Q375 55 408.5 60Q442 65 472 84Q502 103 523 141.5Q544 180 544 248V750H615V235Q615 182 601.5 143Q588 104 566 77Q544 50 516 33Q488 16 458.5 6.5Q429 -3 400.5 -6.5Q372 -10 350 -10Q316 -10 270.5 -1.5Q225 7 183.5 33Q142 59 113.5 107Q85 155 85 235V750H156V248Q156 180 177 141.5Q198 103 228 84Q258 65 291.5 60Q325 55 350 55Z" glyph-name="Utilde" horiz-adv-x="700" unicode="&#360;" /><glyph d="M625 750 360 -10H310L45 750H123L335 103L547 750Z" glyph-name="V" horiz-adv-x="670" unicode="V" /><glyph d="M493 750 661 103 788 750H865L699 -10H629L455 645L281 -10H211L45 750H122L249 103L417 750Z" glyph-name="W" horiz-adv-x="910" unicode="W" /><glyph d="M590 825 455 941 320 825H290L415 1000H495L620 825ZM493 750 661 103 788 750H865L699 -10H629L455 645L281 -10H211L45 750H122L249 103L417 750Z" glyph-name="Wcircumflex" horiz-adv-x="910" unicode="&#372;" /><glyph d="M528 750H615L373 380L620 0H530L330 329L130 0H40L287 380L45 750H132L330 429Z" glyph-name="X" horiz-adv-x="660" unicode="X" /><glyph d="M385 310V0H315V310L45 750H131L350 373L569 750H655Z" glyph-name="Y" horiz-adv-x="700" unicode="Y" /><glyph d="M336 825 416 1000H511L366 825ZM385 310V0H315V310L45 750H131L350 373L569 750H655Z" glyph-name="Yacute" horiz-adv-x="700" unicode="&#221;" /><glyph d="M485 825 350 941 215 825H185L310 1000H390L515 825ZM385 310V0H315V310L45 750H131L350 373L569 750H655Z" glyph-name="Ycircumflex" horiz-adv-x="700" unicode="&#374;" /><glyph d="M506 935Q506 912 490 896Q474 880 451 880Q428 880 412 896Q396 912 396 935Q396 958 412 974Q428 990 451 990Q474 990 490 974Q506 958 506 935ZM251 880Q228 880 212 896Q196 912 196 935Q196 958 212 974Q228 990 251 990Q274 990 290 974Q306 958 306 935Q306 912 290 896Q274 880 251 880ZM385 310V0H315V310L45 750H131L350 373L569 750H655Z" glyph-name="Ydieresis" horiz-adv-x="700" unicode="&#376;" /><glyph d="M55 0V46L473 684H75V750H570V704L152 66H585V0Z" glyph-name="Z" horiz-adv-x="640" unicode="Z" /><glyph d="M298 825 378 1000H473L328 825ZM55 0V46L473 684H75V750H570V704L152 66H585V0Z" glyph-name="Zacute" horiz-adv-x="640" unicode="&#377;" /><glyph d="M478 1000 353 825H273L148 1000H198L313 884L428 1000ZM55 0V46L473 684H75V750H570V704L152 66H585V0Z" glyph-name="Zcaron" horiz-adv-x="640" unicode="&#381;" /><glyph d="M313 880Q290 880 274 896Q258 912 258 935Q258 958 274 974Q290 990 313 990Q336 990 352 974Q368 958 368 935Q368 912 352 896Q336 880 313 880ZM55 0V46L473 684H75V750H570V704L152 66H585V0Z" glyph-name="Zdotaccent" horiz-adv-x="640" unicode="&#379;" /><glyph d="M236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="a" horiz-adv-x="525" unicode="a" /><glyph d="M183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="a.smcp" horiz-adv-x="560" /><glyph d="M241 575 321 750H416L271 575ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="aacute" horiz-adv-x="525" unicode="&#225;" /><glyph d="M265 575 345 750H440L295 575ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="aacute.smcp" horiz-adv-x="560" /><glyph d="M256 585Q220 585 192 593.5Q164 602 144 621Q124 640 113 671.5Q102 703 101 750H142Q144 696 171 670.5Q198 645 256 645Q314 645 341 670.5Q368 696 370 750H411Q410 703 399 671.5Q388 640 368 621Q348 602 320 593.5Q292 585 256 585ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="abreve" horiz-adv-x="525" unicode="&#259;" /><glyph d="M280 565Q244 565 216 575Q188 585 168 607Q148 629 137 664.5Q126 700 125 750H176Q177 716 182.5 692Q188 668 200 653Q212 638 231.5 631.5Q251 625 280 625Q338 625 360 653.5Q382 682 384 750H435Q434 700 423 664.5Q412 629 392 607Q372 585 344 575Q316 565 280 565ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="abreve.smcp" horiz-adv-x="560" /><glyph d="M391 575 256 691 121 575H91L216 750H296L421 575ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="acircumflex" horiz-adv-x="525" unicode="&#226;" /><glyph d="M415 575 280 691 145 575H115L240 750H320L445 575ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="acircumflex.smcp" horiz-adv-x="560" /><glyph d="M286 575 366 750H461L316 575Z" glyph-name="acute" horiz-adv-x="540" unicode="&#180;" /><glyph d="M411 685Q411 662 395 646Q379 630 356 630Q333 630 317 646Q301 662 301 685Q301 708 317 724Q333 740 356 740Q379 740 395 724Q411 708 411 685ZM156 630Q133 630 117 646Q101 662 101 685Q101 708 117 724Q133 740 156 740Q179 740 195 724Q211 708 211 685Q211 662 195 646Q179 630 156 630ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="adieresis" horiz-adv-x="525" unicode="&#228;" /><glyph d="M435 685Q435 662 419 646Q403 630 380 630Q357 630 341 646Q325 662 325 685Q325 708 341 724Q357 740 380 740Q403 740 419 724Q435 708 435 685ZM180 630Q157 630 141 646Q125 662 125 685Q125 708 141 724Q157 740 180 740Q203 740 219 724Q235 708 235 685Q235 662 219 646Q203 630 180 630ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="adieresis.smcp" horiz-adv-x="560" /><glyph d="M605 448Q562 448 535.5 434Q509 420 494 397Q479 374 472.5 344Q466 314 465 281H725Q725 313 722 343Q719 373 707 396.5Q695 420 671 434Q647 448 605 448ZM244 224Q212 224 191 218Q170 212 158 201Q146 190 141 174.5Q136 159 136 141Q136 124 141 108Q146 92 159.5 79.5Q173 67 196 59.5Q219 52 256 52Q298 52 336 64.5Q374 77 397 97L414 112Q398 160 395 223V224ZM395 343Q395 398 365.5 423Q336 448 264 448Q212 448 173.5 440.5Q135 433 106 423V483Q139 497 183 503.5Q227 510 264 510Q336 510 378.5 491.5Q421 473 441 441Q465 473 504.5 491.5Q544 510 605 510Q669 510 706.5 489.5Q744 469 763.5 434Q783 399 789 351.5Q795 304 795 250V224H465V218Q466 185 472.5 155Q479 125 494 102Q509 79 536 65.5Q563 52 605 52Q659 52 696 58Q733 64 770 80V20Q728 1 685 -4.5Q642 -10 605 -10Q545 -10 505.5 8Q466 26 442 58L411 34Q377 9 334.5 -0.5Q292 -10 254 -10Q203 -10 167.5 1.5Q132 13 109 33.5Q86 54 75.5 81.5Q65 109 65 141Q65 170 73 195.5Q81 221 101.5 240Q122 259 157.5 270Q193 281 249 281H395Z" glyph-name="ae" horiz-adv-x="865" unicode="&#230;" /><glyph d="M365 287V421L280 287ZM24 0 354 500H700V439H431V287H665V226H431V61H700V0H365V226H242L99 0Z" glyph-name="ae.smcp" horiz-adv-x="765" /><glyph d="M1140 308H60V374H1140Z" glyph-name="afii00208" horiz-adv-x="1200" unicode="&#8213;" /><glyph d="M241 575 96 750H191L271 575ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="agrave" horiz-adv-x="525" unicode="&#224;" /><glyph d="M265 575 120 750H215L295 575ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="agrave.smcp" horiz-adv-x="560" /><glyph d="M119 680V745H393V680ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="amacron" horiz-adv-x="525" unicode="&#257;" /><glyph d="M143 640H417V575H143ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="amacron.smcp" horiz-adv-x="560" /><glyph d="M335 52Q358 52 382.5 55Q407 58 431 65Q350 118 293.5 196Q237 274 205 360Q173 343 149 311.5Q125 280 125 225Q125 179 140.5 146.5Q156 114 184 93Q212 72 250.5 62Q289 52 335 52ZM418 590Q421 599 423 610.5Q425 622 425 638Q425 649 422 660Q419 671 410 679.5Q401 688 384.5 693Q368 698 340 698Q307 698 286 686.5Q265 675 253 657Q241 639 237 617Q233 595 233 575Q233 525 248 458.5Q263 392 296 324.5Q329 257 380.5 196.5Q432 136 505 99Q549 127 577 172.5Q605 218 605 285Q605 328 595.5 357Q586 386 569.5 404Q553 422 530 430Q507 438 480 438Q459 438 442.5 437Q426 436 412.5 434Q399 432 386.5 428.5Q374 425 360 421V481Q398 493 424 496.5Q450 500 480 500Q522 500 557.5 487.5Q593 475 619.5 448.5Q646 422 660.5 381.5Q675 341 675 285Q675 213 647.5 160.5Q620 108 575 71Q627 56 690 56V-10Q589 -10 504 27Q463 8 420 -1Q377 -10 335 -10Q275 -10 223.5 3Q172 16 134.5 44Q97 72 76 117Q55 162 55 225Q55 262 65.5 293Q76 324 94 349Q112 374 135.5 392.5Q159 411 185 423Q164 501 164 575Q164 605 172.5 638Q181 671 201.5 698Q222 725 256 742.5Q290 760 340 760Q385 760 414 750.5Q443 741 460 724.5Q477 708 483.5 685.5Q490 663 490 638Q490 626 487 613Q484 600 480 590Z" glyph-name="ampersand" horiz-adv-x="735" unicode="&amp;" /><glyph d="M236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0Q396 -20 367.5 -51.5Q339 -83 339 -122Q339 -145 351.5 -162Q364 -179 393 -179Q410 -179 424.5 -176.5Q439 -174 450 -171V-219Q437 -224 419 -227Q401 -230 379 -230Q334 -230 306 -205Q278 -180 278 -135Q278 -113 283.5 -92.5Q289 -72 302.5 -53Q316 -34 339.5 -16Q363 2 398 19L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="aogonek" horiz-adv-x="525" unicode="&#261;" /><glyph d="M183 172H376L280 427ZM515 0Q466 -20 437.5 -51.5Q409 -83 409 -122Q409 -145 421.5 -162Q434 -179 463 -179Q480 -179 494.5 -176.5Q509 -174 520 -171V-219Q507 -224 489 -227Q471 -230 449 -230Q404 -230 376 -205Q348 -180 348 -135Q348 -47 440 3L396 120H163L118 0H45L255 510H305Z" glyph-name="aogonek.smcp" horiz-adv-x="560" /><glyph d="M175 142 283 250 175 358 222 405 330 297 438 405 485 358 377 250 485 142 438 95 330 203 222 95ZM146 61H514V439H146ZM80 500H580V0H80Z" glyph-name="approxequal" horiz-adv-x="660" unicode="&#8776;" /><glyph d="M304 662Q304 690 291.5 700.5Q279 711 256 711Q233 711 220 700.5Q207 690 207 662Q207 635 220 624.5Q233 614 256 614Q279 614 291.5 624.5Q304 635 304 662ZM354 662Q354 611 327.5 588Q301 565 256 565Q211 565 184 588Q157 611 157 662Q157 714 184 737Q211 760 256 760Q301 760 327.5 737Q354 714 354 662ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="aring" horiz-adv-x="525" unicode="&#229;" /><glyph d="M328 662Q328 690 315.5 700.5Q303 711 280 711Q257 711 244 700.5Q231 690 231 662Q231 635 244 624.5Q257 614 280 614Q303 614 315.5 624.5Q328 635 328 662ZM378 662Q378 611 351.5 588Q325 565 280 565Q235 565 208 588Q181 611 181 662Q181 714 208 737Q235 760 280 760Q325 760 351.5 737Q378 714 378 662ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="aring.smcp" horiz-adv-x="560" /><glyph d="M417 500 280 681 143 500H80L247 750H313L480 500Z" glyph-name="asciicircum" horiz-adv-x="540" unicode="^" /><glyph d="M335 340Q316 354 298.5 360.5Q281 367 260 367Q241 367 226.5 357Q212 347 197 335Q182 323 163 313Q144 303 116 303Q90 303 68.5 315Q47 327 35 339V391Q54 377 71.5 370.5Q89 364 110 364Q129 364 143.5 374Q158 384 173 396Q188 408 207 418Q226 428 254 428Q280 428 301.5 416Q323 404 335 392Z" glyph-name="asciitilde" horiz-adv-x="370" unicode="~" /><glyph d="M150 564 9 588 33 661 161 601ZM225 601 354 662 376 588 236 564ZM174 609 154 750H232L212 609ZM186 528 118 400 57 446 154 550ZM232 550 331 445 268 402 200 528Z" glyph-name="asterisk" horiz-adv-x="385" unicode="*" /><glyph d="M586 62Q600 66 613.5 71.5Q627 77 640 84V416Q609 433 578.5 440.5Q548 448 500 448Q455 448 428.5 431Q402 414 387.5 386Q373 358 369 322.5Q365 287 365 250Q365 213 369 177.5Q373 142 387.5 114Q402 86 428.5 69Q455 52 500 52Q553 52 586 62ZM55 250Q55 317 62 381Q69 445 88 501.5Q107 558 140 605.5Q173 653 225.5 687.5Q278 722 351.5 741Q425 760 525 760Q625 760 698 746Q771 732 822 706.5Q873 681 905 644Q937 607 954.5 560.5Q972 514 978.5 460Q985 406 985 345Q985 265 973.5 201Q962 137 931.5 92.5Q901 48 847.5 24Q794 0 710 0H671L656 41Q617 16 579 3Q541 -10 500 -10Q440 -10 400 10.5Q360 31 337 66Q314 101 304.5 148.5Q295 196 295 250Q295 304 304.5 351.5Q314 399 337 434Q360 469 400 489.5Q440 510 500 510Q540 510 580.5 497Q621 484 656 458L671 500H710V62Q771 62 810.5 81Q850 100 872 136Q894 172 902.5 224.5Q911 277 911 345Q911 398 906.5 444.5Q902 491 888.5 530Q875 569 849 599.5Q823 630 780 651.5Q737 673 674.5 684.5Q612 696 525 696Q438 696 374.5 679Q311 662 267 632Q223 602 196 560.5Q169 519 154 469.5Q139 420 134 364Q129 308 129 250Q129 192 134 136Q139 80 154 30.5Q169 -19 196 -60.5Q223 -102 267 -132Q311 -162 374.5 -179Q438 -196 525 -196Q613 -196 675 -184.5Q737 -173 780 -152L781 -224Q733 -241 670 -250.5Q607 -260 525 -260Q425 -260 351.5 -241Q278 -222 225.5 -187.5Q173 -153 140 -105.5Q107 -58 88 -1.5Q69 55 62 119Q55 183 55 250Z" glyph-name="at" horiz-adv-x="1040" unicode="@" /><glyph d="M337 659Q356 659 366 664.5Q376 670 380.5 677.5Q385 685 385.5 694Q386 703 386 711L443 710Q447 657 417 628.5Q387 600 333 600Q302 600 283 610.5Q264 621 249.5 633Q235 645 221 655.5Q207 666 186 666Q167 666 157.5 660.5Q148 655 143.5 647.5Q139 640 138.5 630.5Q138 621 138 614L81 615Q77 668 106.5 696.5Q136 725 190 725Q221 725 240 714.5Q259 704 273.5 692Q288 680 302 669.5Q316 659 337 659ZM236 253Q204 250 184.5 240.5Q165 231 154 217Q143 203 139.5 186Q136 169 136 151Q136 134 139.5 116.5Q143 99 154.5 84.5Q166 70 187 61Q208 52 244 52Q285 52 314 62.5Q343 73 375 92V266ZM375 343Q375 370 369.5 390Q364 410 350.5 423Q337 436 314 442Q291 448 256 448Q205 448 170 440.5Q135 433 106 423V483Q139 497 179.5 503.5Q220 510 256 510Q312 510 348.5 498Q385 486 406.5 463.5Q428 441 436.5 410Q445 379 445 342V0H406L391 40Q357 15 318.5 2.5Q280 -10 242 -10Q193 -10 159 3.5Q125 17 104 39Q83 61 74 90Q65 119 65 151Q65 179 72 205Q79 231 98 252Q117 273 151.5 287.5Q186 302 241 307L375 315Z" glyph-name="atilde" horiz-adv-x="525" unicode="&#227;" /><glyph d="M355 659Q374 659 384 664.5Q394 670 398.5 677.5Q403 685 403.5 694Q404 703 404 711L461 710Q465 657 435 628.5Q405 600 351 600Q320 600 301 610.5Q282 621 267.5 633Q253 645 239 655.5Q225 666 204 666Q185 666 175.5 660.5Q166 655 161.5 647.5Q157 640 156.5 630.5Q156 621 156 614L99 615Q95 668 124.5 696.5Q154 725 208 725Q239 725 258 714.5Q277 704 291.5 692Q306 680 320 669.5Q334 659 355 659ZM183 172H376L280 427ZM442 0 396 120H163L118 0H45L255 510H305L515 0Z" glyph-name="atilde.smcp" horiz-adv-x="560" /><glyph d="M204 438Q170 427 150 416V84Q179 68 210 60Q241 52 290 52Q335 52 361.5 69Q388 86 402.5 114Q417 142 421 177.5Q425 213 425 250Q425 287 421 322.5Q417 358 402.5 386Q388 414 361.5 431Q335 448 290 448Q237 448 204 438ZM290 510Q350 510 390 489.5Q430 469 453 434Q476 399 485.5 351.5Q495 304 495 250Q495 196 485.5 148.5Q476 101 453 66Q430 31 390 10.5Q350 -10 290 -10Q249 -10 211 3Q173 16 134 41L119 0H80V750H150V469Q182 490 218.5 500Q255 510 290 510Z" glyph-name="b" horiz-adv-x="565" unicode="b" /><glyph d="M302 302Q326 302 341 307Q356 312 364.5 321Q373 330 375.5 342.5Q378 355 378 370Q378 385 375 398Q372 411 363 420Q354 429 338 434Q322 439 296 439H156V302ZM398 151Q398 169 394.5 185.5Q391 202 380.5 214.5Q370 227 350.5 234Q331 241 300 241H156V61H300Q331 61 350.5 68Q370 75 380.5 87.5Q391 100 394.5 116.5Q398 133 398 151ZM90 500H288Q375 500 410 466.5Q445 433 445 370Q445 341 431 315Q417 289 394 277Q429 263 449 227Q469 191 469 151Q469 119 461 91Q453 63 433.5 43Q414 23 382.5 11.5Q351 0 305 0H90Z" glyph-name="b.smcp" horiz-adv-x="529" /><glyph d="M347 -206 0 816H73L420 -206Z" glyph-name="backslash" horiz-adv-x="420" unicode="\" /><glyph d="M102 -206V816H173V-206Z" glyph-name="bar" horiz-adv-x="275" unicode="|" /><glyph d="M43 338Q66 338 76 347.5Q86 357 85 377L67 707Q64 765 95.5 790.5Q127 816 180 816H195V750H180Q136 750 138 711L156 381Q157 358 148.5 338.5Q140 319 126 305Q140 291 148.5 271.5Q157 252 156 229L138 -101Q136 -140 180 -140H195V-206H180Q127 -206 95.5 -180.5Q64 -155 67 -97L85 233Q87 269 48 272H33V338Z" glyph-name="braceleft" horiz-adv-x="240" unicode="{" /><glyph d="M207 338V272H192Q153 269 155 233L173 -97Q176 -155 144.5 -180.5Q113 -206 60 -206H45V-140H60Q104 -140 102 -101L84 229Q83 252 93 273Q103 294 117 305Q103 316 93 337Q83 358 84 381L102 711Q104 750 60 750H45V816H60Q113 816 144.5 790.5Q176 765 173 707L155 377Q153 338 197 338Z" glyph-name="braceright" horiz-adv-x="240" unicode="}" /><glyph d="M151 -140H215V-206H80V816H215V750H151Z" glyph-name="bracketleft" horiz-adv-x="260" unicode="[" /><glyph d="M109 750H45V816H180V-206H45V-140H109Z" glyph-name="bracketright" horiz-adv-x="260" unicode="]" /><glyph d="M280 565Q244 565 216 575Q188 585 168 607Q148 629 137 664.5Q126 700 125 750H176Q177 716 182.5 692Q188 668 200 653Q212 638 231.5 631.5Q251 625 280 625Q338 625 360 653.5Q382 682 384 750H435Q434 700 423 664.5Q412 629 392 607Q372 585 344 575Q316 565 280 565Z" glyph-name="breve" horiz-adv-x="602" unicode="&#728;" /><glyph d="M173 338H102V815H173ZM102 -206V272H173V-206Z" glyph-name="brokenbar" horiz-adv-x="275" unicode="&#166;" /><glyph d="M65 375Q65 403 75.5 427.5Q86 452 104.5 470.5Q123 489 147.5 499.5Q172 510 200 510Q228 510 252.5 499.5Q277 489 295.5 470.5Q314 452 324.5 427.5Q335 403 335 375Q335 347 324.5 322.5Q314 298 295.5 279.5Q277 261 252.5 250.5Q228 240 200 240Q172 240 147.5 250.5Q123 261 104.5 279.5Q86 298 75.5 322.5Q65 347 65 375Z" glyph-name="bullet" horiz-adv-x="400" unicode="&#8226;" /><glyph d="M70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510Q318 510 357.5 504.5Q397 499 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q397 1 357.5 -4.5Q318 -10 281 -10Q218 -10 177 10.5Q136 31 112 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="c" horiz-adv-x="490" unicode="c" /><glyph d="M75 250Q75 304 88 351.5Q101 399 129.5 434Q158 469 203 489.5Q248 510 311 510Q348 510 387.5 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Q363 52 397 58.5Q431 65 460 75V15Q427 1 387.5 -4.5Q348 -10 311 -10Q248 -10 203 10.5Q158 31 129.5 66Q101 101 88 148.5Q75 196 75 250Z" glyph-name="c.smcp" horiz-adv-x="535" /><glyph d="M248 575 328 750H423L298 575ZM70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510Q318 510 357.5 504.5Q397 499 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q397 1 357.5 -4.5Q318 -10 281 -10Q218 -10 177 10.5Q136 31 112 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="cacute" horiz-adv-x="490" unicode="&#263;" /><glyph d="M276 575 356 750H451L306 575ZM75 250Q75 304 88 351.5Q101 399 129.5 434Q158 469 203 489.5Q248 510 311 510Q348 510 387.5 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Q363 52 397 58.5Q431 65 460 75V15Q427 1 387.5 -4.5Q348 -10 311 -10Q248 -10 203 10.5Q158 31 129.5 66Q101 101 88 148.5Q75 196 75 250Z" glyph-name="cacute.smcp" horiz-adv-x="535" /><glyph d="M145 750 280 634 415 750H445L320 575H240L115 750Z" glyph-name="caron" horiz-adv-x="540" unicode="&#711;" /><glyph d="M339 750 268 575H221L245 750Z" glyph-name="caronSlovak" horiz-adv-x="270" /><glyph d="M436 750 311 575H231L106 750H156L271 634L386 750ZM70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510Q318 510 357.5 504.5Q397 499 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q397 1 357.5 -4.5Q318 -10 281 -10Q218 -10 177 10.5Q136 31 112 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="ccaron" horiz-adv-x="490" unicode="&#269;" /><glyph d="M160 750 295 634 430 750H460L335 575H255L130 750ZM75 250Q75 304 88 351.5Q101 399 129.5 434Q158 469 203 489.5Q248 510 311 510Q348 510 387.5 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Q363 52 397 58.5Q431 65 460 75V15Q427 1 387.5 -4.5Q348 -10 311 -10Q248 -10 203 10.5Q158 31 129.5 66Q101 101 88 148.5Q75 196 75 250Z" glyph-name="ccaron.smcp" horiz-adv-x="535" /><glyph d="M70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510Q318 510 357.5 504.5Q397 499 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q399 2 363.5 -3.5Q328 -9 293 -10Q282 -25 280.5 -41Q279 -57 302 -71Q327 -85 334 -101Q341 -117 338 -133Q335 -149 325.5 -162.5Q316 -176 307 -185H231Q241 -176 250 -165Q259 -154 263.5 -142.5Q268 -131 266.5 -121.5Q265 -112 255 -106Q237 -96 231 -83.5Q225 -71 226.5 -57.5Q228 -44 235 -31Q242 -18 249 -8Q195 -3 160.5 19Q126 41 106 75.5Q86 110 78 154.5Q70 199 70 250Z" glyph-name="ccedilla" horiz-adv-x="490" unicode="&#231;" /><glyph d="M254 -185Q264 -176 273 -165Q282 -154 286.5 -142.5Q291 -131 289.5 -121.5Q288 -112 278 -106Q260 -96 254 -83.5Q248 -71 250 -57.5Q252 -44 259 -31Q266 -18 273 -8Q220 -2 182.5 20.5Q145 43 121 77.5Q97 112 86 156Q75 200 75 250Q75 304 88 351.5Q101 399 129.5 434Q158 469 203 489.5Q248 510 311 510Q348 510 387.5 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Q363 52 397 58.5Q431 65 460 75V15Q428 1 390 -4Q352 -9 316 -10Q305 -25 303.5 -41Q302 -57 325 -71Q350 -85 357 -101Q364 -117 361 -133Q358 -149 348.5 -162.5Q339 -176 330 -185Z" glyph-name="ccedilla.smcp" horiz-adv-x="535" /><glyph d="M106 575 231 750H311L436 575H386L271 691L156 575ZM70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510Q318 510 357.5 504.5Q397 499 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q397 1 357.5 -4.5Q318 -10 281 -10Q218 -10 177 10.5Q136 31 112 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="ccircumflex" horiz-adv-x="490" unicode="&#265;" /><glyph d="M430 575 295 691 160 575H130L255 750H335L460 575ZM75 250Q75 304 88 351.5Q101 399 129.5 434Q158 469 203 489.5Q248 510 311 510Q348 510 387.5 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Q363 52 397 58.5Q431 65 460 75V15Q427 1 387.5 -4.5Q348 -10 311 -10Q248 -10 203 10.5Q158 31 129.5 66Q101 101 88 148.5Q75 196 75 250Z" glyph-name="ccircumflex.smcp" horiz-adv-x="535" /><glyph d="M271 630Q248 630 232 646Q216 662 216 685Q216 708 232 724Q248 740 271 740Q294 740 310 724Q326 708 326 685Q326 662 310 646Q294 630 271 630ZM70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510Q318 510 357.5 504.5Q397 499 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q397 1 357.5 -4.5Q318 -10 281 -10Q218 -10 177 10.5Q136 31 112 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="cdotaccent" horiz-adv-x="490" unicode="&#267;" /><glyph d="M295 630Q272 630 256 646Q240 662 240 685Q240 708 256 724Q272 740 295 740Q318 740 334 724Q350 708 350 685Q350 662 334 646Q318 630 295 630ZM75 250Q75 304 88 351.5Q101 399 129.5 434Q158 469 203 489.5Q248 510 311 510Q348 510 387.5 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Q363 52 397 58.5Q431 65 460 75V15Q427 1 387.5 -4.5Q348 -10 311 -10Q248 -10 203 10.5Q158 31 129.5 66Q101 101 88 148.5Q75 196 75 250Z" glyph-name="cdotaccent.smcp" horiz-adv-x="535" /><glyph d="M231 -185Q241 -176 250 -165Q259 -154 263.5 -142.5Q268 -131 266.5 -121.5Q265 -112 255 -106Q235 -95 230 -80.5Q225 -66 228.5 -51Q232 -36 240.5 -22.5Q249 -9 256 0H301Q294 -9 288.5 -18Q283 -27 281.5 -36Q280 -45 284 -54Q288 -63 302 -71Q327 -85 334 -101Q341 -117 338 -133Q335 -149 325.5 -162.5Q316 -176 307 -185Z" glyph-name="cedilla" horiz-adv-x="602" unicode="&#184;" /><glyph d="M414 630Q441 626 468 619.5Q495 613 515 605V545Q486 555 442.5 564Q399 573 345 573Q250 573 195 521.5Q140 470 140 375Q140 280 195 228.5Q250 177 345 177Q399 177 442.5 186Q486 195 515 205V145Q498 138 476.5 132.5Q455 127 432 123Q409 119 386.5 117Q364 115 346 115Q338 115 330 115Q322 115 314 116L275 0H200L248 126Q164 149 117 211Q70 273 70 375Q70 439 89.5 487.5Q109 536 145 569Q181 602 232 618.5Q283 635 346 635L385 750H460Z" glyph-name="cent" horiz-adv-x="600" unicode="&#162;" /><glyph d="M70 250Q70 304 79 351.5Q88 399 112 434Q136 469 177 489.5Q218 510 281 510L320 625H395L349 505Q391 501 430 485V425Q401 435 367 441.5Q333 448 280 448Q233 448 205 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 205 69Q233 52 280 52Q333 52 367 58.5Q401 65 430 75V15Q397 1 357.5 -4.5Q318 -10 281 -10Q273 -10 265 -10Q257 -10 249 -9L210 -125H135L185 6Q151 20 129 44Q107 68 94 100Q81 132 75.5 170Q70 208 70 250Z" glyph-name="cent.OP" horiz-adv-x="490" /><glyph d="M415 575 280 691 145 575H115L240 750H320L445 575Z" glyph-name="circumflex" horiz-adv-x="540" unicode="&#710;" /><glyph d="M55 375Q55 398 71 414Q87 430 110 430Q133 430 149 414Q165 398 165 375Q165 352 149 336Q133 320 110 320Q87 320 71 336Q55 352 55 375ZM55 45Q55 68 71 84Q87 100 110 100Q133 100 149 84Q165 68 165 45Q165 22 149 6Q133 -10 110 -10Q87 -10 71 6Q55 22 55 45Z" glyph-name="colon" horiz-adv-x="220" unicode=":" /><glyph d="M63 -114 88 -5Q72 1 63.5 14.5Q55 28 55 45Q55 68 71 84Q87 100 110 100Q133 100 149 84Q165 68 165 45Q165 30 159 18L98 -114Z" glyph-name="comma" horiz-adv-x="220" unicode="," /><glyph d="M241 -250 259 -170Q243 -164 234.5 -150.5Q226 -137 226 -120Q226 -97 242 -81Q258 -65 281 -65Q304 -65 320 -81Q336 -97 336 -120Q336 -135 330 -147L283 -250Z" glyph-name="commaaccent" horiz-adv-x="602" unicode="&#63171;" /><glyph d="M337 159Q361 159 385 162.5Q409 166 426 171V110Q406 105 381.5 102Q357 99 337 99Q269 99 233.5 132.5Q198 166 198 250Q198 334 233.5 367.5Q269 401 337 401Q357 401 381.5 398Q406 395 426 390V329Q409 334 385 337.5Q361 341 337 341Q317 341 303 337Q289 333 280 323Q271 313 266.5 295.5Q262 278 262 250Q262 222 266.5 204.5Q271 187 280 177Q289 167 303 163Q317 159 337 159ZM335 52Q378 52 414.5 59Q451 66 477 87Q503 108 518 147Q533 186 533 250Q533 314 518 353Q503 392 477 413Q451 434 414.5 441Q378 448 335 448Q292 448 255.5 441Q219 434 193 413Q167 392 152 353Q137 314 137 250Q137 186 152 147Q167 108 193 87Q219 66 255.5 59Q292 52 335 52ZM335 -10Q282 -10 234 0.5Q186 11 149.5 39.5Q113 68 91.5 119Q70 170 70 250Q70 331 91.5 381.5Q113 432 149.5 460.5Q186 489 234 499.5Q282 510 335 510Q388 510 436 499.5Q484 489 520.5 460.5Q557 432 578.5 381.5Q600 331 600 250Q600 170 578.5 119Q557 68 520.5 39.5Q484 11 436 0.5Q388 -10 335 -10Z" glyph-name="copyright" horiz-adv-x="680" unicode="&#169;" /><glyph d="M60 418V484H124Q129 548 144 599Q159 650 188 686Q217 722 262.5 741Q308 760 375 760Q412 760 456.5 753Q501 746 534 732V672Q505 682 467 690Q429 698 375 698Q328 698 295.5 683Q263 668 242 640Q221 612 209.5 572.5Q198 533 194 484H473L450 418H190V332H420L397 266H194Q198 217 209.5 177.5Q221 138 242 110Q263 82 295.5 67Q328 52 375 52Q429 52 467 60Q505 68 534 78V18Q501 4 456.5 -3Q412 -10 375 -10Q308 -10 262.5 9Q217 28 188 63.5Q159 99 144 150Q129 201 124 266H60V332H120V418Z" glyph-name="currency" horiz-adv-x="600" unicode="&#164;" /><glyph d="M361 438Q328 448 275 448Q230 448 203.5 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 203.5 69Q230 52 275 52Q321 52 352 59.5Q383 67 415 84V416Q402 423 388.5 428Q375 433 361 438ZM275 510Q344 510 415 469V750H485V0H446L431 41Q392 16 354 3Q316 -10 275 -10Q215 -10 175 10.5Q135 31 112 66Q89 101 79.5 148.5Q70 196 70 250Q70 304 79.5 351.5Q89 399 112 434Q135 469 175 489.5Q215 510 275 510Z" glyph-name="d" horiz-adv-x="565" unicode="d" /><glyph d="M260 61Q310 61 343 74.5Q376 88 396 113Q416 138 424.5 173Q433 208 433 250Q433 292 424.5 327Q416 362 396 387Q376 412 343 425.5Q310 439 260 439H156V61ZM90 0V500H258Q322 500 368 482.5Q414 465 443.5 432.5Q473 400 486.5 353.5Q500 307 500 250Q500 193 486.5 146.5Q473 100 443.5 67.5Q414 35 368 17.5Q322 0 258 0Z" glyph-name="d.smcp" horiz-adv-x="575" /><glyph d="M222 599 363 619V541L222 561ZM164 561 23 541V619L164 599ZM174 609 154 750H232L212 609ZM212 551 243 301H143L174 551Z" glyph-name="dagger" horiz-adv-x="385" unicode="&#8224;" /><glyph d="M212 551 222 480 212 410H174L164 480L174 551ZM164 561 23 541V619L164 599ZM164 362 23 342V420L164 400ZM222 400 363 420V342L222 362ZM174 609 154 750H232L212 609ZM222 599 363 619V541L222 561ZM212 352 243 102H143L174 352Z" glyph-name="daggerdbl" horiz-adv-x="385" unicode="&#8225;" /><glyph d="M660 750 589 575H542L566 750ZM361 438Q328 448 275 448Q230 448 203.5 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 203.5 69Q230 52 275 52Q321 52 352 59.5Q383 67 415 84V416Q402 423 388.5 428Q375 433 361 438ZM275 510Q344 510 415 469V750H485V0H446L431 41Q392 16 354 3Q316 -10 275 -10Q215 -10 175 10.5Q135 31 112 66Q89 101 79.5 148.5Q70 196 70 250Q70 304 79.5 351.5Q89 399 112 434Q135 469 175 489.5Q215 510 275 510Z" glyph-name="dcaron" horiz-adv-x="565" unicode="&#271;" /><glyph d="M140 750 275 634 410 750H440L315 575H235L110 750ZM260 61Q310 61 343 74.5Q376 88 396 113Q416 138 424.5 173Q433 208 433 250Q433 292 424.5 327Q416 362 396 387Q376 412 343 425.5Q310 439 260 439H156V61ZM90 0V500H258Q322 500 368 482.5Q414 465 443.5 432.5Q473 400 486.5 353.5Q500 307 500 250Q500 193 486.5 146.5Q473 100 443.5 67.5Q414 35 368 17.5Q322 0 258 0Z" glyph-name="dcaron.smcp" horiz-adv-x="575" /><glyph d="M361 438Q328 448 275 448Q230 448 203.5 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 203.5 69Q230 52 275 52Q321 52 352 59.5Q383 67 415 84V416Q402 423 388.5 428Q375 433 361 438ZM275 510Q344 510 415 469V578H325V629H415V750H485V629H575V578H485V0H446L431 41Q392 16 354 3Q316 -10 275 -10Q215 -10 175 10.5Q135 31 112 66Q89 101 79.5 148.5Q70 196 70 250Q70 304 79.5 351.5Q89 399 112 434Q135 469 175 489.5Q215 510 275 510Z" glyph-name="dcroat" horiz-adv-x="565" unicode="&#273;" /><glyph d="M300 61Q350 61 383 74.5Q416 88 436 113Q456 138 464.5 173Q473 208 473 250Q473 292 464.5 327Q456 362 436 387Q416 412 383 425.5Q350 439 300 439H196V277H316V222H196V61ZM298 500Q362 500 408 482.5Q454 465 483.5 432.5Q513 400 526.5 353.5Q540 307 540 250Q540 193 526.5 146.5Q513 100 483.5 67.5Q454 35 408 17.5Q362 0 298 0H130V222H40V277H130V500Z" glyph-name="dcroat.smcp" horiz-adv-x="615" /><glyph d="M183 705Q147 705 125.5 687Q104 669 104 625Q104 581 125.5 563Q147 545 183 545Q219 545 240.5 563Q262 581 262 625Q262 669 240.5 687Q219 705 183 705ZM183 490Q157 490 132.5 496.5Q108 503 88.5 518.5Q69 534 57 560Q45 586 45 625Q45 665 57 691Q69 717 88.5 732.5Q108 748 132.5 754Q157 760 183 760Q208 760 233 754Q258 748 277.5 732.5Q297 717 309 691Q321 665 321 625Q321 586 309 560Q297 534 277.5 518.5Q258 503 233 496.5Q208 490 183 490Z" glyph-name="degree" horiz-adv-x="366" unicode="&#176;" /><glyph d="M435 685Q435 662 419 646Q403 630 380 630Q357 630 341 646Q325 662 325 685Q325 708 341 724Q357 740 380 740Q403 740 419 724Q435 708 435 685ZM180 630Q157 630 141 646Q125 662 125 685Q125 708 141 724Q157 740 180 740Q203 740 219 724Q235 708 235 685Q235 662 219 646Q203 630 180 630Z" glyph-name="dieresis" horiz-adv-x="540" unicode="&#168;" /><glyph d="M205 496Q205 519 221 535Q237 551 260 551Q283 551 299 535Q315 519 315 496Q315 473 299 457Q283 441 260 441Q237 441 221 457Q205 473 205 496ZM205 186Q205 209 221 225Q237 241 260 241Q283 241 299 225Q315 209 315 186Q315 163 299 147Q283 131 260 131Q237 131 221 147Q205 163 205 186ZM460 374V308H60V374Z" glyph-name="divide" horiz-adv-x="520" unicode="&#247;" /><glyph d="M129 483Q129 460 142 447Q155 434 178 427.5Q201 421 233.5 418Q266 415 304 412Q360 407 403 400Q446 393 475.5 378Q505 363 520 338Q535 313 535 271Q535 193 469.5 154Q404 115 279 115H259L220 0H145L191 121Q155 126 122 132Q89 138 75 145V203Q86 198 109 193.5Q132 189 160.5 184.5Q189 180 220 177.5Q251 175 280 175Q322 175 357 179.5Q392 184 417 195.5Q442 207 455.5 225Q469 243 469 270Q469 295 457 310Q445 325 423 334Q401 343 369.5 347Q338 351 299 354Q242 359 198 365.5Q154 372 124.5 385.5Q95 399 80 422Q65 445 65 483Q65 565 130 600Q195 635 311 635Q317 635 325 635Q333 635 341 634L380 750H455L409 629Q438 625 465.5 620Q493 615 510 607V549Q495 554 472 558.5Q449 563 422 567Q395 571 366 573Q337 575 310 575Q269 575 236 571.5Q203 568 179 557.5Q155 547 142 529Q129 511 129 483Z" glyph-name="dollar" horiz-adv-x="600" unicode="$" /><glyph d="M133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 115 413 87.5Q403 60 379.5 38Q356 16 317.5 3Q279 -10 222 -10L183 -125H108L154 -5Q130 -1 108.5 4Q87 9 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510L293 625H368L322 505Q342 502 361 497.5Q380 493 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Z" glyph-name="dollar.OP" horiz-adv-x="490" /><glyph d="M280 630Q257 630 241 646Q225 662 225 685Q225 708 241 724Q257 740 280 740Q303 740 319 724Q335 708 335 685Q335 662 319 646Q303 630 280 630Z" glyph-name="dotaccent" horiz-adv-x="540" unicode="&#729;" /><glyph d="M281 -175Q258 -175 242 -159Q226 -143 226 -120Q226 -97 242 -81Q258 -65 281 -65Q304 -65 320 -81Q336 -97 336 -120Q336 -143 320 -159Q304 -175 281 -175Z" glyph-name="dotbelow" horiz-adv-x="602" /><glyph d="M206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="dotlessi" horiz-adv-x="270" unicode="&#305;" /><glyph d="M382 575 257 750H352L432 575ZM232 575 107 750H202L282 575Z" glyph-name="doublegrave" horiz-adv-x="540" /><glyph d="M280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="e" horiz-adv-x="540" unicode="e" /><glyph d="M420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="e.smcp" horiz-adv-x="530" /><glyph d="M265 575 345 750H440L295 575ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="eacute" horiz-adv-x="540" unicode="&#233;" /><glyph d="M255 575 335 750H430L285 575ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="eacute.smcp" horiz-adv-x="530" /><glyph d="M280 585Q244 585 216 593.5Q188 602 168 621Q148 640 137 671.5Q126 703 125 750H166Q168 696 195 670.5Q222 645 280 645Q338 645 365 670.5Q392 696 394 750H435Q434 703 423 671.5Q412 640 392 621Q372 602 344 593.5Q316 585 280 585ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="ebreve" horiz-adv-x="540" unicode="&#277;" /><glyph d="M275 565Q239 565 211 575Q183 585 163 607Q143 629 132 664.5Q121 700 120 750H171Q172 716 177.5 692Q183 668 195 653Q207 638 226.5 631.5Q246 625 275 625Q333 625 355 653.5Q377 682 379 750H430Q429 700 418 664.5Q407 629 387 607Q367 585 339 575Q311 565 275 565ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="ebreve.smcp" horiz-adv-x="530" /><glyph d="M445 750 320 575H240L115 750H145L280 634L415 750ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="ecaron" horiz-adv-x="540" unicode="&#283;" /><glyph d="M140 750 275 634 410 750H440L315 575H235L110 750ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="ecaron.smcp" horiz-adv-x="530" /><glyph d="M415 575 280 691 145 575H115L240 750H320L445 575ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="ecircumflex" horiz-adv-x="540" unicode="&#234;" /><glyph d="M410 575 275 691 140 575H110L235 750H315L440 575ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="ecircumflex.smcp" horiz-adv-x="530" /><glyph d="M435 685Q435 662 419 646Q403 630 380 630Q357 630 341 646Q325 662 325 685Q325 708 341 724Q357 740 380 740Q403 740 419 724Q435 708 435 685ZM180 630Q157 630 141 646Q125 662 125 685Q125 708 141 724Q157 740 180 740Q203 740 219 724Q235 708 235 685Q235 662 219 646Q203 630 180 630ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="edieresis" horiz-adv-x="540" unicode="&#235;" /><glyph d="M430 685Q430 662 414 646Q398 630 375 630Q352 630 336 646Q320 662 320 685Q320 708 336 724Q352 740 375 740Q398 740 414 724Q430 708 430 685ZM175 630Q152 630 136 646Q120 662 120 685Q120 708 136 724Q152 740 175 740Q198 740 214 724Q230 708 230 685Q230 662 214 646Q198 630 175 630ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="edieresis.smcp" horiz-adv-x="530" /><glyph d="M280 630Q257 630 241 646Q225 662 225 685Q225 708 241 724Q257 740 280 740Q303 740 319 724Q335 708 335 685Q335 662 319 646Q303 630 280 630ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="edotaccent" horiz-adv-x="540" unicode="&#279;" /><glyph d="M420 226H156V61H455V0H90V500H455V439H156V287H420ZM225 650Q225 671 239.5 685.5Q254 700 275 700Q296 700 310.5 685.5Q325 671 325 650Q325 629 310.5 614.5Q296 600 275 600Q254 600 239.5 614.5Q225 629 225 650Z" glyph-name="edotaccent.smcp" horiz-adv-x="530" /><glyph d="M265 575 120 750H215L295 575ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="egrave" horiz-adv-x="540" unicode="&#232;" /><glyph d="M280 575 135 750H230L310 575ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="egrave.smcp" horiz-adv-x="530" /><glyph d="M300 420Q342 420 371.5 431.5Q401 443 419.5 462Q438 481 446 504.5Q454 528 454 552Q454 577 448 603Q442 629 425 650Q408 671 378 684.5Q348 698 300 698Q252 698 222 684.5Q192 671 175 650Q158 629 152 603Q146 577 146 552Q146 528 154 504.5Q162 481 180.5 462Q199 443 228.5 431.5Q258 420 300 420ZM300 358Q212 358 166.5 322.5Q121 287 121 211Q121 184 129 156Q137 128 157.5 104.5Q178 81 212.5 66.5Q247 52 300 52Q353 52 387.5 66.5Q422 81 442.5 104.5Q463 128 471 156Q479 184 479 211Q479 287 433.5 322.5Q388 358 300 358ZM300 -10Q230 -10 181.5 8Q133 26 103 56.5Q73 87 60 127Q47 167 47 211Q47 239 54.5 268Q62 297 77 322Q92 347 115.5 366.5Q139 386 171 395Q122 418 97 460.5Q72 503 72 552Q72 593 83.5 630.5Q95 668 122 697Q149 726 192.5 743Q236 760 300 760Q364 760 407.5 743Q451 726 478 697Q505 668 516.5 630.5Q528 593 528 552Q528 503 503 460.5Q478 418 429 395Q461 386 484.5 366.5Q508 347 523 322Q538 297 545.5 268Q553 239 553 211Q553 167 540 127Q527 87 497 56.5Q467 26 418.5 8Q370 -10 300 -10Z" glyph-name="eight" horiz-adv-x="600" unicode="8" /><glyph d="M300 420Q342 420 371.5 431.5Q401 443 419.5 462Q438 481 446 504.5Q454 528 454 552Q454 577 448 603Q442 629 425 650Q408 671 378 684.5Q348 698 300 698Q252 698 222 684.5Q192 671 175 650Q158 629 152 603Q146 577 146 552Q146 528 154 504.5Q162 481 180.5 462Q199 443 228.5 431.5Q258 420 300 420ZM300 358Q212 358 166.5 322.5Q121 287 121 211Q121 184 129 156Q137 128 157.5 104.5Q178 81 212.5 66.5Q247 52 300 52Q353 52 387.5 66.5Q422 81 442.5 104.5Q463 128 471 156Q479 184 479 211Q479 287 433.5 322.5Q388 358 300 358ZM300 -10Q230 -10 181.5 8Q133 26 103 56.5Q73 87 60 127Q47 167 47 211Q47 239 54.5 268Q62 297 77 322Q92 347 115.5 366.5Q139 386 171 395Q122 418 97 460.5Q72 503 72 552Q72 593 83.5 630.5Q95 668 122 697Q149 726 192.5 743Q236 760 300 760Q364 760 407.5 743Q451 726 478 697Q505 668 516.5 630.5Q528 593 528 552Q528 503 503 460.5Q478 418 429 395Q461 386 484.5 366.5Q508 347 523 322Q538 297 545.5 268Q553 239 553 211Q553 167 540 127Q527 87 497 56.5Q467 26 418.5 8Q370 -10 300 -10Z" glyph-name="eight.LP" horiz-adv-x="600" /><glyph d="M300 420Q342 420 371.5 431.5Q401 443 419.5 462Q438 481 446 504.5Q454 528 454 552Q454 577 448 603Q442 629 425 650Q408 671 378 684.5Q348 698 300 698Q252 698 222 684.5Q192 671 175 650Q158 629 152 603Q146 577 146 552Q146 528 154 504.5Q162 481 180.5 462Q199 443 228.5 431.5Q258 420 300 420ZM300 358Q212 358 166.5 322.5Q121 287 121 211Q121 184 129 156Q137 128 157.5 104.5Q178 81 212.5 66.5Q247 52 300 52Q353 52 387.5 66.5Q422 81 442.5 104.5Q463 128 471 156Q479 184 479 211Q479 287 433.5 322.5Q388 358 300 358ZM300 -10Q230 -10 181.5 8Q133 26 103 56.5Q73 87 60 127Q47 167 47 211Q47 239 54.5 268Q62 297 77 322Q92 347 115.5 366.5Q139 386 171 395Q122 418 97 460.5Q72 503 72 552Q72 593 83.5 630.5Q95 668 122 697Q149 726 192.5 743Q236 760 300 760Q364 760 407.5 743Q451 726 478 697Q505 668 516.5 630.5Q528 593 528 552Q528 503 503 460.5Q478 418 429 395Q461 386 484.5 366.5Q508 347 523 322Q538 297 545.5 268Q553 239 553 211Q553 167 540 127Q527 87 497 56.5Q467 26 418.5 8Q370 -10 300 -10Z" glyph-name="eight.OP" horiz-adv-x="600" /><glyph d="M770 45Q770 68 786 84Q802 100 825 100Q848 100 864 84Q880 68 880 45Q880 22 864 6Q848 -10 825 -10Q802 -10 786 6Q770 22 770 45ZM440 45Q440 68 456 84Q472 100 495 100Q518 100 534 84Q550 68 550 45Q550 22 534 6Q518 -10 495 -10Q472 -10 456 6Q440 22 440 45ZM110 45Q110 68 126 84Q142 100 165 100Q188 100 204 84Q220 68 220 45Q220 22 204 6Q188 -10 165 -10Q142 -10 126 6Q110 22 110 45Z" glyph-name="ellipsis" horiz-adv-x="990" unicode="&#8230;" /><glyph d="M143 680V745H417V680ZM280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20Q403 1 360 -4.5Q317 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="emacron" horiz-adv-x="540" unicode="&#275;" /><glyph d="M138 640H412V575H138ZM420 226H156V61H455V0H90V500H455V439H156V287H420Z" glyph-name="emacron.smcp" horiz-adv-x="530" /><glyph d="M940 308H60V374H940Z" glyph-name="emdash" horiz-adv-x="1000" unicode="&#8212;" /><glyph glyph-name="emspace" horiz-adv-x="1000" unicode="&#8195;" /><glyph d="M440 308H60V374H440Z" glyph-name="endash" horiz-adv-x="500" unicode="&#8211;" /><glyph d="M285 448Q236 448 207 440Q178 432 150 416V0H80V500H119L134 458Q204 510 285 510Q375 510 415 471Q455 432 455 360V-96Q455 -135 437 -165Q419 -195 390.5 -215.5Q362 -236 326.5 -247Q291 -258 257 -260V-201Q279 -198 302 -190.5Q325 -183 343.5 -169.5Q362 -156 373.5 -136Q385 -116 385 -87V363Q385 412 358.5 430Q332 448 285 448Z" glyph-name="eng" horiz-adv-x="530" unicode="&#331;" /><glyph d="M510 -96Q510 -135 492 -165Q474 -195 445.5 -215.5Q417 -236 381.5 -247Q346 -258 312 -260V-201Q334 -198 358 -190.5Q382 -183 401.5 -169Q421 -155 433.5 -134.5Q446 -114 446 -85V24L155 380L156 0H90V500H134L445 120L444 500H510Z" glyph-name="eng.smcp" horiz-adv-x="600" /><glyph glyph-name="enspace" horiz-adv-x="500" unicode="&#8194;" /><glyph d="M280 448Q236 448 209.5 433.5Q183 419 168 395Q153 371 147 340Q141 309 140 275H400Q400 308 397.5 339.5Q395 371 383 395Q371 419 347 433.5Q323 448 280 448ZM70 250Q70 304 79 351.5Q88 399 111.5 434Q135 469 176 489.5Q217 510 280 510Q344 510 381.5 489.5Q419 469 438.5 434Q458 399 464 351.5Q470 304 470 250V218H140Q141 185 147.5 155Q154 125 169 102Q184 79 211 65.5Q238 52 280 52Q334 52 371 58Q408 64 445 80V20L435 15Q370 -16 343.5 -50Q317 -84 317 -122Q317 -145 329.5 -162Q342 -179 371 -179Q388 -179 402.5 -176.5Q417 -174 428 -171V-219Q415 -224 397 -227Q379 -230 357 -230Q312 -230 284 -205Q256 -180 256 -135Q256 -54 329 -8Q316 -9 303.5 -9.5Q291 -10 280 -10Q217 -10 176 10.5Q135 31 111.5 66Q88 101 79 148.5Q70 196 70 250Z" glyph-name="eogonek" horiz-adv-x="540" unicode="&#281;" /><glyph d="M420 226H156V61H455V0Q406 -20 377.5 -51.5Q349 -83 349 -122Q349 -145 361.5 -162Q374 -179 403 -179Q420 -179 434.5 -176.5Q449 -174 460 -171V-219Q447 -224 429 -227Q411 -230 389 -230Q344 -230 316 -205Q288 -180 288 -135Q288 -47 375 0H90V500H455V439H156V287H420Z" glyph-name="eogonek.smcp" horiz-adv-x="530" /><glyph d="M460 242H60V308H460ZM460 374H60V440H460Z" glyph-name="equal" horiz-adv-x="520" unicode="=" /><glyph d="M425 250Q425 292 420 328.5Q415 365 399 391.5Q383 418 353.5 433Q324 448 275 448Q225 448 195.5 433Q166 418 150.5 391.5Q135 365 130 328.5Q125 292 125 250Q125 208 130 171.5Q135 135 150.5 108.5Q166 82 195.5 67Q225 52 275 52Q324 52 353.5 67Q383 82 399 108.5Q415 135 420 171.5Q425 208 425 250ZM425 691 348 652Q375 623 400.5 584.5Q426 546 446.5 497Q467 448 480 386.5Q493 325 495 250Q495 193 485 145Q475 97 449.5 62.5Q424 28 381.5 9Q339 -10 275 -10Q211 -10 168.5 9Q126 28 101 62.5Q76 97 65.5 145Q55 193 55 250Q55 307 65.5 355Q76 403 101 437.5Q126 472 168.5 491Q211 510 275 510Q306 510 332 503Q358 496 381 484Q364 524 340 558Q316 592 288 621L135 543V602L247 659Q212 689 176.5 711.5Q141 734 112 750H226Q242 740 263.5 725.5Q285 711 308 690L425 750Z" glyph-name="eth" horiz-adv-x="550" unicode="&#240;" /><glyph d="M300 61Q350 61 383 74.5Q416 88 436 113Q456 138 464.5 173Q473 208 473 250Q473 292 464.5 327Q456 362 436 387Q416 412 383 425.5Q350 439 300 439H196V277H316V222H196V61ZM298 500Q362 500 408 482.5Q454 465 483.5 432.5Q513 400 526.5 353.5Q540 307 540 250Q540 193 526.5 146.5Q513 100 483.5 67.5Q454 35 408 17.5Q362 0 298 0H130V222H40V277H130V500Z" glyph-name="eth.smcp" horiz-adv-x="615" /><glyph d="M55 45Q55 68 71 84Q87 100 110 100Q133 100 149 84Q165 68 165 45Q165 22 149 6Q133 -10 110 -10Q87 -10 71 6Q55 22 55 45ZM90 210 55 750H165L130 210Z" glyph-name="exclam" horiz-adv-x="220" unicode="!" /><glyph d="M165 455Q165 432 149 416Q133 400 110 400Q87 400 71 416Q55 432 55 455Q55 478 71 494Q87 510 110 510Q133 510 149 494Q165 478 165 455ZM130 290 165 -250H55L90 290Z" glyph-name="exclamdown" horiz-adv-x="220" unicode="&#161;" /><glyph d="M387 690Q372 694 356 696Q340 698 317 698Q268 698 236 678.5Q204 659 204 610V500H362V445H204V0H137V445H37V476L137 505V610Q137 682 180 721Q223 760 317 760Q333 760 352.5 758.5Q372 757 387 753Z" glyph-name="f" horiz-adv-x="407" unicode="f" /><glyph d="M410 226H156V0H90V500H445V439H156V287H410Z" glyph-name="f.smcp" horiz-adv-x="510" /><glyph d="M613 -10Q559 -10 531 15Q503 40 503 100V500H571V92Q571 66 580.5 56.5Q590 47 613 47ZM387 690Q372 694 356 696Q340 698 317 698Q268 698 236 678.5Q204 659 204 610V500H362V445H204V0H137V445H37V476L137 505V610Q137 682 180 721Q223 760 317 760Q333 760 352.5 758.5Q372 757 387 753Z" glyph-name="f_dotlessi" horiz-adv-x="677" /><glyph d="M482 685Q482 708 498 724Q514 740 537 740Q560 740 576 724Q592 708 592 685Q592 662 576 646Q560 630 537 630Q514 630 498 646Q482 662 482 685ZM613 -10Q559 -10 531 15Q503 40 503 100V500H571V92Q571 66 580.5 56.5Q590 47 613 47ZM387 690Q372 694 356 696Q340 698 317 698Q268 698 236 678.5Q204 659 204 610V500H362V445H204V0H137V445H37V476L137 505V610Q137 682 180 721Q223 760 317 760Q333 760 352.5 758.5Q372 757 387 753Z" glyph-name="fi" horiz-adv-x="677" unicode="&#64257;" /><glyph d="M540 308H60V374H540Z" glyph-name="figuredash" horiz-adv-x="600" unicode="&#8210;" /><glyph glyph-name="figurespace" horiz-adv-x="600" unicode="&#8199;" /><glyph d="M239 52Q270 52 306 58Q342 64 372 83.5Q402 103 422 138.5Q442 174 442 233Q442 290 424.5 320.5Q407 351 379 365Q351 379 315.5 381Q280 383 244 383H104V750H499V688H172V443Q195 445 221 446.5Q247 448 267 448Q312 448 356.5 441.5Q401 435 436 412.5Q471 390 492.5 347.5Q514 305 514 233Q514 152 487 104Q460 56 419 30.5Q378 5 329.5 -2.5Q281 -10 238 -10Q183 -10 141 -0.5Q99 9 73 22V84Q101 73 141 62.5Q181 52 239 52Z" glyph-name="five" horiz-adv-x="600" unicode="5" /><glyph d="M239 52Q270 52 306 58Q342 64 372 83.5Q402 103 422 138.5Q442 174 442 233Q442 290 424.5 320.5Q407 351 379 365Q351 379 315.5 381Q280 383 244 383H104V750H499V688H172V443Q195 445 221 446.5Q247 448 267 448Q312 448 356.5 441.5Q401 435 436 412.5Q471 390 492.5 347.5Q514 305 514 233Q514 152 487 104Q460 56 419 30.5Q378 5 329.5 -2.5Q281 -10 238 -10Q183 -10 141 -0.5Q99 9 73 22V84Q101 73 141 62.5Q181 52 239 52Z" glyph-name="five.LP" horiz-adv-x="600" /><glyph d="M219 -198Q250 -198 286 -192Q322 -186 352 -166.5Q382 -147 402 -111.5Q422 -76 422 -17Q422 40 404.5 70.5Q387 101 359 115Q331 129 295.5 131Q260 133 224 133H84V500H479V438H152V193Q175 195 201 196.5Q227 198 247 198Q292 198 336.5 191.5Q381 185 416 162.5Q451 140 472.5 97.5Q494 55 494 -17Q494 -98 467 -146Q440 -194 399 -219.5Q358 -245 309.5 -252.5Q261 -260 218 -260Q163 -260 121 -250.5Q79 -241 53 -228V-166Q81 -177 121 -187.5Q161 -198 219 -198Z" glyph-name="five.OP" horiz-adv-x="530" /><glyph d="M613 -10Q559 -10 531 15Q503 40 503 100V750H571V92Q571 66 580.5 56.5Q590 47 613 47ZM387 690Q372 694 356 696Q340 698 317 698Q268 698 236 678.5Q204 659 204 610V500H362V445H204V0H137V445H37V476L137 505V610Q137 682 180 721Q223 760 317 760Q333 760 352.5 758.5Q372 757 387 753Z" glyph-name="fl" horiz-adv-x="677" unicode="&#64258;" /><glyph d="M310 760Q347 760 387 754.5Q427 749 460 735V675Q431 685 397 691.5Q363 698 310 698Q261 698 229 672Q197 646 197 595V500H435V445H197V0H130V445H30V476L130 505V595Q130 671 173 715.5Q216 760 310 760Z" glyph-name="florin" horiz-adv-x="530" unicode="&#402;" /><glyph d="M310 510Q347 510 387 504.5Q427 499 460 485V425Q431 435 397 441.5Q363 448 310 448Q261 448 229 422Q197 396 197 345V250H435V195H197V-250H130V195H30V226L130 255V345Q130 421 173 465.5Q216 510 310 510Z" glyph-name="florin.OP" horiz-adv-x="530" /><glyph d="M380 312V641L141 312ZM448 312H555V250H448V0H380V250H55V292L395 760H448Z" glyph-name="four" horiz-adv-x="600" unicode="4" /><glyph d="M380 312V641L141 312ZM448 312H555V250H448V0H380V250H55V292L395 760H448Z" glyph-name="four.LP" horiz-adv-x="600" /><glyph d="M380 62V391L141 62ZM448 62H555V0H448V-250H380V0H55V42L395 510H448Z" glyph-name="four.OP" horiz-adv-x="600" /><glyph glyph-name="fourperemspace" horiz-adv-x="250" unicode="&#8197;" /><glyph d="M-50 0 480 750H557L27 0Z" glyph-name="fraction" horiz-adv-x="507" unicode="&#8260;" /><glyph d="M257 196Q301 196 326.5 206.5Q352 217 365.5 234.5Q379 252 383 274.5Q387 297 387 322Q387 347 383 369.5Q379 392 365.5 409.5Q352 427 326.5 437.5Q301 448 257 448Q213 448 187.5 437.5Q162 427 148.5 409.5Q135 392 131 369.5Q127 347 127 322Q127 297 131 274.5Q135 252 148.5 234.5Q162 217 187.5 206.5Q213 196 257 196ZM220 5Q196 -1 175 -10Q154 -19 137.5 -32Q121 -45 111.5 -63Q102 -81 102 -105Q102 -133 111.5 -151Q121 -169 140.5 -179.5Q160 -190 189 -194Q218 -198 257 -198Q294 -198 328.5 -193Q363 -188 389.5 -176Q416 -164 431.5 -143Q447 -122 447 -90Q447 -52 429.5 -34.5Q412 -17 366 -12ZM257 134Q219 134 191 140Q183 132 177.5 122Q172 112 172 103Q172 68 210 64L380 44Q451 36 484 6Q517 -24 517 -90Q517 -136 496 -168Q475 -200 439 -220.5Q403 -241 356 -250.5Q309 -260 257 -260Q152 -260 92.5 -224Q33 -188 33 -105Q33 -80 41.5 -60Q50 -40 64 -24.5Q78 -9 97 2.5Q116 14 136 23V24Q122 34 113 49.5Q104 65 104 90Q104 111 115 128Q126 145 142 158Q97 183 79.5 226Q62 269 62 322Q62 361 71 395Q80 429 102.5 454.5Q125 480 162.5 495Q200 510 257 510Q280 510 300 507.5Q320 505 337 500H507V462L423 438Q439 414 445.5 384.5Q452 355 452 322Q452 283 443 249Q434 215 411.5 189.5Q389 164 351.5 149Q314 134 257 134Z" glyph-name="g" horiz-adv-x="540" unicode="g" /><glyph d="M310 52Q345 52 373 55Q401 58 424 63V198H315V259H490V23Q475 15 453.5 9Q432 3 408 -1Q384 -5 358.5 -7.5Q333 -10 310 -10Q247 -10 202 10.5Q157 31 129 66Q101 101 88 148.5Q75 196 75 250Q75 304 88 351.5Q101 399 129 434Q157 469 202 489.5Q247 510 310 510Q332 510 356 508Q380 506 402.5 502.5Q425 499 445 493Q465 487 480 480V420Q451 431 411 439.5Q371 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Z" glyph-name="g.smcp" horiz-adv-x="580" /><glyph d="M257 585Q221 585 193 593.5Q165 602 145 621Q125 640 114 671.5Q103 703 102 750H143Q145 696 172 670.5Q199 645 257 645Q315 645 342 670.5Q369 696 371 750H412Q411 703 400 671.5Q389 640 369 621Q349 602 321 593.5Q293 585 257 585ZM257 196Q301 196 326.5 206.5Q352 217 365.5 234.5Q379 252 383 274.5Q387 297 387 322Q387 347 383 369.5Q379 392 365.5 409.5Q352 427 326.5 437.5Q301 448 257 448Q213 448 187.5 437.5Q162 427 148.5 409.5Q135 392 131 369.5Q127 347 127 322Q127 297 131 274.5Q135 252 148.5 234.5Q162 217 187.5 206.5Q213 196 257 196ZM220 5Q196 -1 175 -10Q154 -19 137.5 -32Q121 -45 111.5 -63Q102 -81 102 -105Q102 -133 111.5 -151Q121 -169 140.5 -179.5Q160 -190 189 -194Q218 -198 257 -198Q294 -198 328.5 -193Q363 -188 389.5 -176Q416 -164 431.5 -143Q447 -122 447 -90Q447 -52 429.5 -34.5Q412 -17 366 -12ZM257 134Q219 134 191 140Q183 132 177.5 122Q172 112 172 103Q172 68 210 64L380 44Q451 36 484 6Q517 -24 517 -90Q517 -136 496 -168Q475 -200 439 -220.5Q403 -241 356 -250.5Q309 -260 257 -260Q152 -260 92.5 -224Q33 -188 33 -105Q33 -80 41.5 -60Q50 -40 64 -24.5Q78 -9 97 2.5Q116 14 136 23V24Q122 34 113 49.5Q104 65 104 90Q104 111 115 128Q126 145 142 158Q97 183 79.5 226Q62 269 62 322Q62 361 71 395Q80 429 102.5 454.5Q125 480 162.5 495Q200 510 257 510Q280 510 300 507.5Q320 505 337 500H507V462L423 438Q439 414 445.5 384.5Q452 355 452 322Q452 283 443 249Q434 215 411.5 189.5Q389 164 351.5 149Q314 134 257 134Z" glyph-name="gbreve" horiz-adv-x="540" unicode="&#287;" /><glyph d="M300 565Q264 565 236 575Q208 585 188 607Q168 629 157 664.5Q146 700 145 750H196Q197 716 202.5 692Q208 668 220 653Q232 638 251.5 631.5Q271 625 300 625Q358 625 380 653.5Q402 682 404 750H455Q454 700 443 664.5Q432 629 412 607Q392 585 364 575Q336 565 300 565ZM310 52Q345 52 373 55Q401 58 424 63V198H315V259H490V23Q475 15 453.5 9Q432 3 408 -1Q384 -5 358.5 -7.5Q333 -10 310 -10Q247 -10 202 10.5Q157 31 129 66Q101 101 88 148.5Q75 196 75 250Q75 304 88 351.5Q101 399 129 434Q157 469 202 489.5Q247 510 310 510Q332 510 356 508Q380 506 402.5 502.5Q425 499 445 493Q465 487 480 480V420Q451 431 411 439.5Q371 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Z" glyph-name="gbreve.smcp" horiz-adv-x="580" /><glyph d="M392 575 257 691 122 575H92L217 750H297L422 575ZM257 196Q301 196 326.5 206.5Q352 217 365.5 234.5Q379 252 383 274.5Q387 297 387 322Q387 347 383 369.5Q379 392 365.5 409.5Q352 427 326.5 437.5Q301 448 257 448Q213 448 187.5 437.5Q162 427 148.5 409.5Q135 392 131 369.5Q127 347 127 322Q127 297 131 274.5Q135 252 148.5 234.5Q162 217 187.5 206.5Q213 196 257 196ZM220 5Q196 -1 175 -10Q154 -19 137.5 -32Q121 -45 111.5 -63Q102 -81 102 -105Q102 -133 111.5 -151Q121 -169 140.5 -179.5Q160 -190 189 -194Q218 -198 257 -198Q294 -198 328.5 -193Q363 -188 389.5 -176Q416 -164 431.5 -143Q447 -122 447 -90Q447 -52 429.5 -34.5Q412 -17 366 -12ZM257 134Q219 134 191 140Q183 132 177.5 122Q172 112 172 103Q172 68 210 64L380 44Q451 36 484 6Q517 -24 517 -90Q517 -136 496 -168Q475 -200 439 -220.5Q403 -241 356 -250.5Q309 -260 257 -260Q152 -260 92.5 -224Q33 -188 33 -105Q33 -80 41.5 -60Q50 -40 64 -24.5Q78 -9 97 2.5Q116 14 136 23V24Q122 34 113 49.5Q104 65 104 90Q104 111 115 128Q126 145 142 158Q97 183 79.5 226Q62 269 62 322Q62 361 71 395Q80 429 102.5 454.5Q125 480 162.5 495Q200 510 257 510Q280 510 300 507.5Q320 505 337 500H507V462L423 438Q439 414 445.5 384.5Q452 355 452 322Q452 283 443 249Q434 215 411.5 189.5Q389 164 351.5 149Q314 134 257 134Z" glyph-name="gcircumflex" horiz-adv-x="540" unicode="&#285;" /><glyph d="M435 575 300 691 165 575H135L260 750H340L465 575ZM310 52Q345 52 373 55Q401 58 424 63V198H315V259H490V23Q475 15 453.5 9Q432 3 408 -1Q384 -5 358.5 -7.5Q333 -10 310 -10Q247 -10 202 10.5Q157 31 129 66Q101 101 88 148.5Q75 196 75 250Q75 304 88 351.5Q101 399 129 434Q157 469 202 489.5Q247 510 310 510Q332 510 356 508Q380 506 402.5 502.5Q425 499 445 493Q465 487 480 480V420Q451 431 411 439.5Q371 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Z" glyph-name="gcircumflex.smcp" horiz-adv-x="580" /><glyph d="M297 750 279 670Q295 664 303.5 650.5Q312 637 312 620Q312 597 296 581Q280 565 257 565Q234 565 218 581Q202 597 202 620Q202 635 208 647L255 750ZM257 196Q301 196 326.5 206.5Q352 217 365.5 234.5Q379 252 383 274.5Q387 297 387 322Q387 347 383 369.5Q379 392 365.5 409.5Q352 427 326.5 437.5Q301 448 257 448Q213 448 187.5 437.5Q162 427 148.5 409.5Q135 392 131 369.5Q127 347 127 322Q127 297 131 274.5Q135 252 148.5 234.5Q162 217 187.5 206.5Q213 196 257 196ZM220 5Q196 -1 175 -10Q154 -19 137.5 -32Q121 -45 111.5 -63Q102 -81 102 -105Q102 -133 111.5 -151Q121 -169 140.5 -179.5Q160 -190 189 -194Q218 -198 257 -198Q294 -198 328.5 -193Q363 -188 389.5 -176Q416 -164 431.5 -143Q447 -122 447 -90Q447 -52 429.5 -34.5Q412 -17 366 -12ZM257 134Q219 134 191 140Q183 132 177.5 122Q172 112 172 103Q172 68 210 64L380 44Q451 36 484 6Q517 -24 517 -90Q517 -136 496 -168Q475 -200 439 -220.5Q403 -241 356 -250.5Q309 -260 257 -260Q152 -260 92.5 -224Q33 -188 33 -105Q33 -80 41.5 -60Q50 -40 64 -24.5Q78 -9 97 2.5Q116 14 136 23V24Q122 34 113 49.5Q104 65 104 90Q104 111 115 128Q126 145 142 158Q97 183 79.5 226Q62 269 62 322Q62 361 71 395Q80 429 102.5 454.5Q125 480 162.5 495Q200 510 257 510Q280 510 300 507.5Q320 505 337 500H507V462L423 438Q439 414 445.5 384.5Q452 355 452 322Q452 283 443 249Q434 215 411.5 189.5Q389 164 351.5 149Q314 134 257 134Z" glyph-name="gcommaaccent" horiz-adv-x="540" unicode="&#291;" /><glyph d="M258 -250 276 -170Q260 -164 251.5 -150.5Q243 -137 243 -120Q243 -97 259 -81Q275 -65 298 -65Q321 -65 337 -81Q353 -97 353 -120Q353 -135 347 -147L300 -250ZM310 52Q345 52 373 55Q401 58 424 63V198H315V259H490V23Q475 15 453.5 9Q432 3 408 -1Q384 -5 358.5 -7.5Q333 -10 310 -10Q247 -10 202 10.5Q157 31 129 66Q101 101 88 148.5Q75 196 75 250Q75 304 88 351.5Q101 399 129 434Q157 469 202 489.5Q247 510 310 510Q332 510 356 508Q380 506 402.5 502.5Q425 499 445 493Q465 487 480 480V420Q451 431 411 439.5Q371 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Z" glyph-name="gcommaaccent.smcp" horiz-adv-x="580" /><glyph d="M257 630Q234 630 218 646Q202 662 202 685Q202 708 218 724Q234 740 257 740Q280 740 296 724Q312 708 312 685Q312 662 296 646Q280 630 257 630ZM257 196Q301 196 326.5 206.5Q352 217 365.5 234.5Q379 252 383 274.5Q387 297 387 322Q387 347 383 369.5Q379 392 365.5 409.5Q352 427 326.5 437.5Q301 448 257 448Q213 448 187.5 437.5Q162 427 148.5 409.5Q135 392 131 369.5Q127 347 127 322Q127 297 131 274.5Q135 252 148.5 234.5Q162 217 187.5 206.5Q213 196 257 196ZM220 5Q196 -1 175 -10Q154 -19 137.5 -32Q121 -45 111.5 -63Q102 -81 102 -105Q102 -133 111.5 -151Q121 -169 140.5 -179.5Q160 -190 189 -194Q218 -198 257 -198Q294 -198 328.5 -193Q363 -188 389.5 -176Q416 -164 431.5 -143Q447 -122 447 -90Q447 -52 429.5 -34.5Q412 -17 366 -12ZM257 134Q219 134 191 140Q183 132 177.5 122Q172 112 172 103Q172 68 210 64L380 44Q451 36 484 6Q517 -24 517 -90Q517 -136 496 -168Q475 -200 439 -220.5Q403 -241 356 -250.5Q309 -260 257 -260Q152 -260 92.5 -224Q33 -188 33 -105Q33 -80 41.5 -60Q50 -40 64 -24.5Q78 -9 97 2.5Q116 14 136 23V24Q122 34 113 49.5Q104 65 104 90Q104 111 115 128Q126 145 142 158Q97 183 79.5 226Q62 269 62 322Q62 361 71 395Q80 429 102.5 454.5Q125 480 162.5 495Q200 510 257 510Q280 510 300 507.5Q320 505 337 500H507V462L423 438Q439 414 445.5 384.5Q452 355 452 322Q452 283 443 249Q434 215 411.5 189.5Q389 164 351.5 149Q314 134 257 134Z" glyph-name="gdotaccent" horiz-adv-x="540" unicode="&#289;" /><glyph d="M300 630Q277 630 261 646Q245 662 245 685Q245 708 261 724Q277 740 300 740Q323 740 339 724Q355 708 355 685Q355 662 339 646Q323 630 300 630ZM310 52Q345 52 373 55Q401 58 424 63V198H315V259H490V23Q475 15 453.5 9Q432 3 408 -1Q384 -5 358.5 -7.5Q333 -10 310 -10Q247 -10 202 10.5Q157 31 129 66Q101 101 88 148.5Q75 196 75 250Q75 304 88 351.5Q101 399 129 434Q157 469 202 489.5Q247 510 310 510Q332 510 356 508Q380 506 402.5 502.5Q425 499 445 493Q465 487 480 480V420Q451 431 411 439.5Q371 448 310 448Q263 448 231 431Q199 414 180 386Q161 358 153 322.5Q145 287 145 250Q145 213 153 177.5Q161 142 180 114Q199 86 231 69Q263 52 310 52Z" glyph-name="gdotaccent.smcp" horiz-adv-x="580" /><glyph d="M319 698Q241 698 203.5 668.5Q166 639 166 595V0H99V594Q99 630 112 660Q125 690 152 712.5Q179 735 220.5 747.5Q262 760 319 760Q376 760 418 747.5Q460 735 487 712.5Q514 690 527 660Q540 630 540 594Q540 562 533 538Q526 514 511 495.5Q496 477 471.5 463.5Q447 450 411 441Q364 429 344.5 413Q325 397 325 362Q325 345 330 333Q335 321 348 312Q361 303 383 297Q405 291 439 287Q492 281 526.5 269.5Q561 258 580.5 240.5Q600 223 607.5 199Q615 175 615 144Q615 115 605 87.5Q595 60 571.5 38Q548 16 509.5 3Q471 -10 414 -10Q396 -10 375 -8Q354 -6 333.5 -2.5Q313 1 295 5.5Q277 10 265 15V75Q288 67 324.5 59.5Q361 52 415 52Q454 52 479.5 59.5Q505 67 520 80Q535 93 541 109Q547 125 547 143Q547 160 543.5 174.5Q540 189 528 200Q516 211 493.5 218.5Q471 226 434 229Q380 234 345.5 245.5Q311 257 292 274.5Q273 292 266 314Q259 336 259 362Q259 390 265 411Q271 432 287 448.5Q303 465 329 477Q355 489 396 499Q422 505 437 515Q452 525 460 538Q468 551 470 565.5Q472 580 472 595Q472 639 434.5 668.5Q397 698 319 698Z" glyph-name="germandbls" horiz-adv-x="680" unicode="&#223;" /><glyph d="M679 358Q679 339 686.5 327Q694 315 710.5 307Q727 299 752.5 294.5Q778 290 814 287Q867 282 903.5 272Q940 262 962.5 245.5Q985 229 995 204.5Q1005 180 1005 146Q1005 114 994 85.5Q983 57 957.5 36Q932 15 890.5 2.5Q849 -10 789 -10Q770 -10 746.5 -8Q723 -6 699.5 -2Q676 2 656.5 7Q637 12 625 17V75Q648 66 691 58Q734 50 790 50Q873 50 906 74.5Q939 99 939 145Q939 186 911 204.5Q883 223 809 229Q755 234 717.5 243Q680 252 657.5 267Q635 282 625 304Q615 326 615 358Q615 392 626 420Q637 448 661.5 468Q686 488 725.5 499Q765 510 821 510Q859 510 903 504.5Q947 499 980 485V427Q951 437 912 443.5Q873 450 820 450Q741 450 710 428Q679 406 679 358ZM139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 114 454 85.5Q443 57 417.5 36Q392 15 350.5 2.5Q309 -10 249 -10Q230 -10 206.5 -8Q183 -6 159.5 -2Q136 2 116.5 7Q97 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Z" glyph-name="germandbls.smcp" horiz-adv-x="1080" /><glyph d="M286 575 141 750H236L316 575Z" glyph-name="grave" horiz-adv-x="540" unicode="`" /><glyph d="M55 204 236 341 55 478V541L305 374V308L55 141Z" glyph-name="greater" horiz-adv-x="365" unicode="&gt;" /><glyph d="M810 356 299 -10V76L673 329L694 342H60V408H694L673 421L299 674V760L810 394Z" glyph-name="greaterequal" horiz-adv-x="860" unicode="&#8805;" /><glyph d="M245 283 435 423V347L314 250L435 153V76L245 217ZM55 283 245 423V347L124 250L245 153V76L55 217Z" glyph-name="guillemotleft" horiz-adv-x="495" unicode="&#171;" /><glyph d="M250 217 60 76V153L181 250L60 347V423L250 283ZM440 217 250 76V153L371 250L250 347V423L440 283Z" glyph-name="guillemotright" horiz-adv-x="495" unicode="&#187;" /><glyph d="M55 283 245 423V347L124 250L245 153V76L55 217Z" glyph-name="guilsinglleft" horiz-adv-x="305" unicode="&#8249;" /><glyph d="M250 217 60 76V153L181 250L60 347V423L250 283Z" glyph-name="guilsinglright" horiz-adv-x="305" unicode="&#8250;" /><glyph d="M385 363Q385 412 358.5 430Q332 448 285 448Q236 448 206 440Q176 432 148 416V0H80V750H148V469Q180 490 215 500Q250 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="h" horiz-adv-x="530" unicode="h" /><glyph d="M444 0V226H156V0H90V500H156V287H444V500H510V0Z" glyph-name="h.smcp" horiz-adv-x="600" /><glyph glyph-name="hairspace" horiz-adv-x="50" unicode="&#8202;" /><glyph d="M80 654V750H148V654H285V603H148V469Q180 490 215 500Q250 510 285 510Q375 510 415 471Q455 432 455 360V0H385V363Q385 412 358.5 430Q332 448 285 448Q236 448 206 440Q176 432 148 416V0H80V603H-10V654Z" glyph-name="hbar" horiz-adv-x="530" unicode="&#295;" /><glyph d="M444 287V362H156V287ZM444 0V226H156V0H90V500H156V417H444V500H510V0Z" glyph-name="hbar.smcp" horiz-adv-x="600" /><glyph d="M249 825 114 941 -21 825H-51L74 1000H154L279 825ZM385 363Q385 412 358.5 430Q332 448 285 448Q236 448 206 440Q176 432 148 416V0H80V750H148V469Q180 490 215 500Q250 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="hcircumflex" horiz-adv-x="530" unicode="&#293;" /><glyph d="M435 575 300 691 165 575H135L260 750H340L465 575ZM444 0V226H156V0H90V500H156V287H444V500H510V0Z" glyph-name="hcircumflex.smcp" horiz-adv-x="600" /><glyph d="M170 575 250 750H345L220 575ZM320 575 400 750H495L370 575Z" glyph-name="hungarumlaut" horiz-adv-x="540" unicode="&#733;" /><glyph d="M310 308H60V374H310Z" glyph-name="hyphen" horiz-adv-x="370" unicode="-" /><glyph d="M75 685Q75 708 91 724Q107 740 130 740Q153 740 169 724Q185 708 185 685Q185 662 169 646Q153 630 130 630Q107 630 91 646Q75 662 75 685ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="i" horiz-adv-x="270" unicode="i" /><glyph d="M112 0V500H178V0Z" glyph-name="i.smcp" horiz-adv-x="290" /><glyph d="M115 575 195 750H290L145 575ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="iacute" horiz-adv-x="270" unicode="&#237;" /><glyph d="M130 575 210 750H305L160 575ZM112 0V500H178V0Z" glyph-name="iacute.smcp" horiz-adv-x="290" /><glyph d="M130 585Q94 585 66 593.5Q38 602 18 621Q-2 640 -13 671.5Q-24 703 -25 750H16Q18 696 45 670.5Q72 645 130 645Q188 645 215 670.5Q242 696 244 750H285Q284 703 273 671.5Q262 640 242 621Q222 602 194 593.5Q166 585 130 585ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="ibreve" horiz-adv-x="270" unicode="&#301;" /><glyph d="M145 565Q109 565 81 575Q53 585 33 607Q13 629 2 664.5Q-9 700 -10 750H41Q42 716 47.5 692Q53 668 65 653Q77 638 96.5 631.5Q116 625 145 625Q203 625 225 653.5Q247 682 249 750H300Q299 700 288 664.5Q277 629 257 607Q237 585 209 575Q181 565 145 565ZM112 0V500H178V0Z" glyph-name="ibreve.smcp" horiz-adv-x="290" /><glyph d="M265 575 130 691 -5 575H-35L90 750H170L295 575ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="icircumflex" horiz-adv-x="270" unicode="&#238;" /><glyph d="M280 575 145 691 10 575H-20L105 750H185L310 575ZM112 0V500H178V0Z" glyph-name="icircumflex.smcp" horiz-adv-x="290" /><glyph d="M285 685Q285 662 269 646Q253 630 230 630Q207 630 191 646Q175 662 175 685Q175 708 191 724Q207 740 230 740Q253 740 269 724Q285 708 285 685ZM30 630Q7 630 -9 646Q-25 662 -25 685Q-25 708 -9 724Q7 740 30 740Q53 740 69 724Q85 708 85 685Q85 662 69 646Q53 630 30 630ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="idieresis" horiz-adv-x="270" unicode="&#239;" /><glyph d="M300 685Q300 662 284 646Q268 630 245 630Q222 630 206 646Q190 662 190 685Q190 708 206 724Q222 740 245 740Q268 740 284 724Q300 708 300 685ZM45 630Q22 630 6 646Q-10 662 -10 685Q-10 708 6 724Q22 740 45 740Q68 740 84 724Q100 708 100 685Q100 662 84 646Q68 630 45 630ZM112 0V500H178V0Z" glyph-name="idieresis.smcp" horiz-adv-x="290" /><glyph d="M75 685Q75 708 91 724Q107 740 130 740Q153 740 169 724Q185 708 185 685Q185 662 169 646Q153 630 130 630Q107 630 91 646Q75 662 75 685ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="idotaccent" horiz-adv-x="270" /><glyph d="M145 630Q122 630 106 646Q90 662 90 685Q90 708 106 724Q122 740 145 740Q168 740 184 724Q200 708 200 685Q200 662 184 646Q168 630 145 630ZM112 0V500H178V0Z" glyph-name="idotaccent.smcp" horiz-adv-x="290" /><glyph d="M115 575 -30 750H65L145 575ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="igrave" horiz-adv-x="270" unicode="&#236;" /><glyph d="M130 575 -15 750H80L160 575ZM112 0V500H178V0Z" glyph-name="igrave.smcp" horiz-adv-x="290" /><glyph d="M345 685Q345 708 361 724Q377 740 400 740Q423 740 439 724Q455 708 455 685Q455 662 439 646Q423 630 400 630Q377 630 361 646Q345 662 345 685ZM238 -201Q260 -198 283 -190.5Q306 -183 324.5 -169.5Q343 -156 354.5 -136Q366 -116 366 -87V500H434V-96Q434 -135 416 -165Q398 -195 370 -215.5Q342 -236 307 -247Q272 -258 238 -260ZM75 685Q75 708 91 724Q107 740 130 740Q153 740 169 724Q185 708 185 685Q185 662 169 646Q153 630 130 630Q107 630 91 646Q75 662 75 685ZM206 -10Q152 -10 124 15Q96 40 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="ij" horiz-adv-x="530" unicode="&#307;" /><glyph d="M340 55Q358 50 372.5 48.5Q387 47 410 47Q459 47 489 65Q519 83 519 132V500H585V140Q585 68 544.5 29Q504 -10 410 -10Q394 -10 374.5 -8.5Q355 -7 340 -3ZM112 0V500H178V0Z" glyph-name="ij.smcp" horiz-adv-x="695" /><glyph d="M-7 680V745H267V680ZM201 -10Q147 -10 119 15Q91 40 91 100V500H159V92Q159 66 168.5 56.5Q178 47 201 47Z" glyph-name="imacron" horiz-adv-x="270" unicode="&#299;" /><glyph d="M8 640H282V575H8ZM112 0V500H178V0Z" glyph-name="imacron.smcp" horiz-adv-x="290" /><glyph d="M52 681Q52 662 68 654.5Q84 647 120 644Q174 639 195 622Q216 605 216 571Q216 539 192 516Q168 493 107 493Q88 493 62.5 496.5Q37 500 25 505V534Q37 530 58.5 526.5Q80 523 108 523Q150 523 166.5 535Q183 547 183 570Q183 591 169 601Q155 611 117 614Q63 619 41.5 634Q20 649 20 681Q20 715 43.5 736Q67 757 124 757Q143 757 164.5 754.5Q186 752 203 745V716Q189 721 169 724Q149 727 123 727Q83 727 67.5 716Q52 705 52 681ZM386 532 301 705 303 500H270V750H313L400 568L488 750H530V500H498L500 705L415 532Z" glyph-name="infinity" horiz-adv-x="575" unicode="&#8734;" /><glyph d="M151 66H759V684H151ZM80 750H830V0H80Z" glyph-name="integral" horiz-adv-x="910" unicode="&#8747;" /><glyph d="M280 760Q351 760 392 717.5Q433 675 435 575H384Q382 643 360 671.5Q338 700 280 700Q251 700 231.5 693.5Q212 687 200 672Q188 657 182.5 633Q177 609 176 575H125Q127 675 168 717.5Q209 760 280 760Z" glyph-name="invertedbreve" horiz-adv-x="602" /><glyph d="M75 685Q75 708 91 724Q107 740 130 740Q153 740 169 724Q185 708 185 685Q185 662 169 646Q153 630 130 630Q107 630 91 646Q75 662 75 685ZM206 -10Q187 -16 170 -26.5Q153 -37 139.5 -51Q126 -65 118 -82Q110 -99 110 -119Q110 -145 124 -160Q138 -175 172 -175Q187 -175 200.5 -173Q214 -171 225 -168Q237 -165 248 -161V-213Q240 -218 217 -225Q194 -232 153 -232Q132 -232 112.5 -226Q93 -220 77 -207.5Q61 -195 51.5 -176Q42 -157 42 -131Q42 -93 66.5 -56.5Q91 -20 135 6Q96 30 96 100V500H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="iogonek" horiz-adv-x="270" unicode="&#303;" /><glyph d="M178 0Q165 -10 151.5 -23Q138 -36 127 -51.5Q116 -67 109 -85Q102 -103 102 -122Q102 -145 114.5 -162Q127 -179 156 -179Q173 -179 187.5 -176.5Q202 -174 213 -171V-219Q200 -224 182 -227Q164 -230 142 -230Q97 -230 69 -205Q41 -180 41 -135Q41 -92 59 -59Q77 -26 112 6V500H178Z" glyph-name="iogonek.smcp" horiz-adv-x="290" /><glyph d="M205 659Q224 659 234 664.5Q244 670 248.5 677.5Q253 685 253.5 694Q254 703 254 711L311 710Q315 657 285 628.5Q255 600 201 600Q170 600 151 610.5Q132 621 117.5 633Q103 645 89 655.5Q75 666 54 666Q35 666 25.5 660.5Q16 655 11.5 647.5Q7 640 6.5 630.5Q6 621 6 614L-51 615Q-55 668 -25.5 696.5Q4 725 58 725Q89 725 108 714.5Q127 704 141.5 692Q156 680 170 669.5Q184 659 205 659ZM201 -10Q147 -10 119 15Q91 40 91 100V500H159V92Q159 66 168.5 56.5Q178 47 201 47Z" glyph-name="itilde" horiz-adv-x="270" unicode="&#297;" /><glyph d="M220 659Q239 659 249 664.5Q259 670 263.5 677.5Q268 685 268.5 694Q269 703 269 711L326 710Q330 657 300 628.5Q270 600 216 600Q185 600 166 610.5Q147 621 132.5 633Q118 645 104 655.5Q90 666 69 666Q50 666 40.5 660.5Q31 655 26.5 647.5Q22 640 21.5 630.5Q21 621 21 614L-36 615Q-40 668 -10.5 696.5Q19 725 73 725Q104 725 123 714.5Q142 704 156.5 692Q171 680 185 669.5Q199 659 220 659ZM112 0V500H178V0Z" glyph-name="itilde.smcp" horiz-adv-x="290" /><glyph d="M75 685Q75 708 91 724Q107 740 130 740Q153 740 169 724Q185 708 185 685Q185 662 169 646Q153 630 130 630Q107 630 91 646Q75 662 75 685ZM-32 -201Q-10 -198 13 -190.5Q36 -183 54.5 -169.5Q73 -156 84.5 -136Q96 -116 96 -87V500H164V-96Q164 -135 146 -165Q128 -195 100 -215.5Q72 -236 37 -247Q2 -258 -32 -260Z" glyph-name="j" horiz-adv-x="260" unicode="j" /><glyph d="M50 55Q68 50 82.5 48.5Q97 47 120 47Q169 47 199 65Q229 83 229 132V500H295V140Q295 68 254.5 29Q214 -10 120 -10Q104 -10 84.5 -8.5Q65 -7 50 -3Z" glyph-name="j.smcp" horiz-adv-x="405" /><glyph d="M265 575 130 691 -5 575H-35L90 750H170L295 575ZM-32 -201Q-10 -198 13 -190.5Q36 -183 54.5 -169.5Q73 -156 84.5 -136Q96 -116 96 -87V500H164V-96Q164 -135 146 -165Q128 -195 100 -215.5Q72 -236 37 -247Q2 -258 -32 -260Z" glyph-name="jcircumflex" horiz-adv-x="260" unicode="&#309;" /><glyph d="M397 575 262 691 127 575H97L222 750H302L427 575ZM50 55Q68 50 82.5 48.5Q97 47 120 47Q169 47 199 65Q229 83 229 132V500H295V140Q295 68 254.5 29Q214 -10 120 -10Q104 -10 84.5 -8.5Q65 -7 50 -3Z" glyph-name="jcircumflex.smcp" horiz-adv-x="405" /><glyph d="M253 294 495 0H408L211 254L148 194V0H80V750H148V276L374 500H468Z" glyph-name="k" horiz-adv-x="530" unicode="k" /><glyph d="M261 294 503 0H416L219 254L156 194V0H90V500H156V273L382 500H476Z" glyph-name="k.smcp" horiz-adv-x="545" /><glyph d="M213 -251 231 -171Q215 -165 206.5 -151.5Q198 -138 198 -121Q198 -98 214 -82Q230 -66 253 -66Q276 -66 292 -82Q308 -98 308 -121Q308 -136 302 -148L255 -251ZM253 294 495 0H408L211 254L148 194V0H80V750H148V276L374 500H468Z" glyph-name="kcommaaccent" horiz-adv-x="530" unicode="&#311;" /><glyph d="M238 -250 256 -170Q240 -164 231.5 -150.5Q223 -137 223 -120Q223 -97 239 -81Q255 -65 278 -65Q301 -65 317 -81Q333 -97 333 -120Q333 -135 327 -147L280 -250ZM261 294 503 0H416L219 254L156 194V0H90V500H156V273L382 500H476Z" glyph-name="kcommaaccent.smcp" horiz-adv-x="545" /><glyph d="M253 294 495 0H408L211 254L148 194V0H80V500H148V276L374 500H468Z" glyph-name="kgreenlandic" horiz-adv-x="530" unicode="&#312;" /><glyph d="M206 -10Q152 -10 124 15Q96 40 96 100V750H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="l" horiz-adv-x="270" unicode="l" /><glyph d="M156 61H455V0H90V500H156Z" glyph-name="l.smcp" horiz-adv-x="510" /><glyph d="M115 825 195 1000H290L145 825ZM206 -10Q152 -10 124 15Q96 40 96 100V750H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="lacute" horiz-adv-x="270" unicode="&#314;" /><glyph d="M108 575 188 750H283L138 575ZM156 61H455V0H90V500H156Z" glyph-name="lacute.smcp" horiz-adv-x="510" /><glyph d="M339 750 268 575H221L245 750ZM206 -10Q152 -10 124 15Q96 40 96 100V750H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="lcaron" horiz-adv-x="270" unicode="&#318;" /><glyph d="M381 600 310 425H263L287 600ZM156 61H455V0H90V500H156Z" glyph-name="lcaron.smcp" horiz-adv-x="510" /><glyph d="M90 -251 108 -171Q92 -165 83.5 -151.5Q75 -138 75 -121Q75 -98 91 -82Q107 -66 130 -66Q153 -66 169 -82Q185 -98 185 -121Q185 -136 179 -148L132 -251ZM206 -10Q152 -10 124 15Q96 40 96 100V750H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="lcommaaccent" horiz-adv-x="270" unicode="&#316;" /><glyph d="M233 -250 251 -170Q235 -164 226.5 -150.5Q218 -137 218 -120Q218 -97 234 -81Q250 -65 273 -65Q296 -65 312 -81Q328 -97 328 -120Q328 -135 322 -147L275 -250ZM156 61H455V0H90V500H156Z" glyph-name="lcommaaccent.smcp" horiz-adv-x="510" /><glyph d="M260 300Q260 321 274.5 335.5Q289 350 310 350Q331 350 345.5 335.5Q360 321 360 300Q360 279 345.5 264.5Q331 250 310 250Q289 250 274.5 264.5Q260 279 260 300ZM206 -10Q152 -10 124 15Q96 40 96 100V750H164V92Q164 66 173.5 56.5Q183 47 206 47Z" glyph-name="ldot" horiz-adv-x="350" unicode="&#320;" /><glyph d="M320 275Q320 296 334.5 310.5Q349 325 370 325Q391 325 405.5 310.5Q420 296 420 275Q420 254 405.5 239.5Q391 225 370 225Q349 225 334.5 239.5Q320 254 320 275ZM156 61H455V0H90V500H156Z" glyph-name="ldot.smcp" horiz-adv-x="510" /><glyph d="M310 141 60 308V374L310 541V478L129 341L310 204Z" glyph-name="less" horiz-adv-x="365" unicode="&lt;" /><glyph d="M50 394 561 760V674L187 421L166 408H800V342H166L187 329L561 76V-10L50 356Z" glyph-name="lessequal" horiz-adv-x="860" unicode="&#8804;" /><glyph d="M80 0V500H580V0Z" glyph-name="logicalnot" horiz-adv-x="660" unicode="&#172;" /><glyph d="M227 100 180 147 408 375 180 603 227 650 455 422 683 650 730 603 502 375 730 147 683 100 455 328ZM151 66H759V684H151ZM80 750H830V0H80Z" glyph-name="lozenge" horiz-adv-x="910" unicode="&#9674;" /><glyph d="M164 92Q164 66 173.5 56.5Q183 47 206 47V-10Q152 -10 124 15Q96 40 96 100V330L6 273V337L96 394V750H164V438L264 502V438L164 374Z" glyph-name="lslash" horiz-adv-x="270" unicode="&#322;" /><glyph d="M196 189V61H495V0H130V152L40 101V164L130 215V500H196V252L378 355V292Z" glyph-name="lslash.smcp" horiz-adv-x="550" /><glyph d="M670 363Q670 412 644 430Q618 448 575 448Q552 448 533.5 446Q515 444 499 439.5Q483 435 468.5 429Q454 423 439 415Q445 391 445 363V0H375V363Q375 412 349 430Q323 448 280 448Q236 448 207.5 440.5Q179 433 150 418V0H80V500H119L134 459Q170 485 207.5 497.5Q245 510 280 510Q381 510 420 453Q459 480 498 495Q537 510 575 510Q656 510 698 472.5Q740 435 740 360V0H670Z" glyph-name="m" horiz-adv-x="815" unicode="m" /><glyph d="M321 65 151 409 155 0H90V500H175L350 137L525 500H610V0H545L549 409L379 65Z" glyph-name="m.smcp" horiz-adv-x="700" /><glyph d="M143 640H417V575H143Z" glyph-name="macron" horiz-adv-x="602" unicode="&#175;" /><glyph d="M60 308V374H460V308Z" glyph-name="minus" horiz-adv-x="520" unicode="&#8722;" /><glyph d="M148 -250H80V500H150V134Q150 85 176.5 67Q203 49 250 49Q299 49 328 57Q357 65 385 81V500H455V0H416L401 42Q331 -10 250 -10Q218 -10 190.5 -4Q163 2 148 13Z" glyph-name="mu" horiz-adv-x="535" unicode="&#181;" /><glyph d="M95 223 213 341 95 459 142 506 260 388 378 506 425 459 307 341 425 223 378 176 260 294 142 176Z" glyph-name="multiply" horiz-adv-x="520" unicode="&#215;" /><glyph d="M385 363Q385 412 358.5 430Q332 448 285 448Q236 448 207 440Q178 432 150 416V0H80V500H119L134 458Q204 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="n" horiz-adv-x="530" unicode="n" /><glyph d="M466 0 155 380 156 0H90V500H134L445 120L444 500H510V0Z" glyph-name="n.smcp" horiz-adv-x="600" /><glyph d="M249 575 329 750H424L299 575ZM385 363Q385 412 358.5 430Q332 448 285 448Q236 448 207 440Q178 432 150 416V0H80V500H119L134 458Q204 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="nacute" horiz-adv-x="530" unicode="&#324;" /><glyph d="M284 575 364 750H459L314 575ZM466 0 155 380 156 0H90V500H134L445 120L444 500H510V0Z" glyph-name="nacute.smcp" horiz-adv-x="600" /><glyph d="M433 750 308 575H228L103 750H133L268 634L403 750ZM385 363Q385 412 358.5 430Q332 448 285 448Q236 448 207 440Q178 432 150 416V0H80V500H119L134 458Q204 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="ncaron" horiz-adv-x="530" unicode="&#328;" /><glyph d="M465 750 340 575H260L135 750H185L300 634L415 750ZM466 0 155 380 156 0H90V500H134L445 120L444 500H510V0Z" glyph-name="ncaron.smcp" horiz-adv-x="600" /><glyph d="M228 -251 246 -171Q230 -165 221.5 -151.5Q213 -138 213 -121Q213 -98 229 -82Q245 -66 268 -66Q291 -66 307 -82Q323 -98 323 -121Q323 -136 317 -148L270 -251ZM385 363Q385 412 358.5 430Q332 448 285 448Q236 448 207 440Q178 432 150 416V0H80V500H119L134 458Q204 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="ncommaaccent" horiz-adv-x="530" unicode="&#326;" /><glyph d="M359 -75 288 -250H241L265 -75ZM466 0 155 380 156 0H90V500H134L445 120L444 500H510V0Z" glyph-name="ncommaaccent.smcp" horiz-adv-x="600" /><glyph d="M466 519Q466 549 459.5 580.5Q453 612 435 638.5Q417 665 383.5 681.5Q350 698 296 698Q245 698 214 679.5Q183 661 166.5 634Q150 607 145 575.5Q140 544 140 519Q140 493 144 462Q148 431 164 404Q180 377 211 358.5Q242 340 296 340Q349 340 382 357Q415 374 434 400Q453 426 459.5 458Q466 490 466 519ZM265 54Q324 54 364.5 78Q405 102 429.5 144.5Q454 187 465 246Q476 305 476 376Q467 359 452 341.5Q437 324 414.5 310Q392 296 362.5 287Q333 278 296 278Q219 278 174 301.5Q129 325 106 360.5Q83 396 76.5 438.5Q70 481 70 519Q70 557 78 599.5Q86 642 110 677.5Q134 713 178.5 736.5Q223 760 296 760Q377 760 425 734.5Q473 709 498.5 662.5Q524 616 532 550.5Q540 485 540 405Q540 313 528 236.5Q516 160 485 105Q454 50 400.5 20Q347 -10 265 -10Q192 -10 150.5 1.5Q109 13 90 24V90Q118 76 161.5 65Q205 54 265 54Z" glyph-name="nine" horiz-adv-x="600" unicode="9" /><glyph d="M466 519Q466 549 459.5 580.5Q453 612 435 638.5Q417 665 383.5 681.5Q350 698 296 698Q245 698 214 679.5Q183 661 166.5 634Q150 607 145 575.5Q140 544 140 519Q140 493 144 462Q148 431 164 404Q180 377 211 358.5Q242 340 296 340Q349 340 382 357Q415 374 434 400Q453 426 459.5 458Q466 490 466 519ZM265 54Q324 54 364.5 78Q405 102 429.5 144.5Q454 187 465 246Q476 305 476 376Q467 359 452 341.5Q437 324 414.5 310Q392 296 362.5 287Q333 278 296 278Q219 278 174 301.5Q129 325 106 360.5Q83 396 76.5 438.5Q70 481 70 519Q70 557 78 599.5Q86 642 110 677.5Q134 713 178.5 736.5Q223 760 296 760Q377 760 425 734.5Q473 709 498.5 662.5Q524 616 532 550.5Q540 485 540 405Q540 313 528 236.5Q516 160 485 105Q454 50 400.5 20Q347 -10 265 -10Q192 -10 150.5 1.5Q109 13 90 24V90Q118 76 161.5 65Q205 54 265 54Z" glyph-name="nine.LP" horiz-adv-x="600" /><glyph d="M466 269Q466 299 459.5 330.5Q453 362 435 388.5Q417 415 383.5 431.5Q350 448 296 448Q245 448 214 429.5Q183 411 166.5 384Q150 357 145 325.5Q140 294 140 269Q140 243 144 212Q148 181 164 154Q180 127 211 108.5Q242 90 296 90Q349 90 382 107Q415 124 434 150Q453 176 459.5 208Q466 240 466 269ZM265 -196Q324 -196 364.5 -172Q405 -148 429.5 -105.5Q454 -63 465 -4Q476 55 476 126Q467 109 452 91.5Q437 74 414.5 60Q392 46 362.5 37Q333 28 296 28Q219 28 174 51.5Q129 75 106 110.5Q83 146 76.5 188.5Q70 231 70 269Q70 307 78 349.5Q86 392 110 427.5Q134 463 178.5 486.5Q223 510 296 510Q377 510 425 484.5Q473 459 498.5 412.5Q524 366 532 300.5Q540 235 540 155Q540 63 528 -13.5Q516 -90 485 -145Q454 -200 400.5 -230Q347 -260 265 -260Q192 -260 150.5 -248.5Q109 -237 90 -226V-160Q118 -174 161.5 -185Q205 -196 265 -196Z" glyph-name="nine.OP" horiz-adv-x="605" /><glyph d="M80 0V750H830V0Z" glyph-name="notequal" horiz-adv-x="910" unicode="&#8800;" /><glyph d="M355 659Q374 659 384 664.5Q394 670 398.5 677.5Q403 685 403.5 694Q404 703 404 711L461 710Q465 657 435 628.5Q405 600 351 600Q320 600 301 610.5Q282 621 267.5 633Q253 645 239 655.5Q225 666 204 666Q185 666 175.5 660.5Q166 655 161.5 647.5Q157 640 156.5 630.5Q156 621 156 614L99 615Q95 668 124.5 696.5Q154 725 208 725Q239 725 258 714.5Q277 704 291.5 692Q306 680 320 669.5Q334 659 355 659ZM385 363Q385 412 358.5 430Q332 448 285 448Q236 448 207 440Q178 432 150 416V0H80V500H119L134 458Q204 510 285 510Q375 510 415 471Q455 432 455 360V0H385Z" glyph-name="ntilde" horiz-adv-x="530" unicode="&#241;" /><glyph d="M375 659Q394 659 404 664.5Q414 670 418.5 677.5Q423 685 423.5 694Q424 703 424 711L481 710Q485 657 455 628.5Q425 600 371 600Q340 600 321 610.5Q302 621 287.5 633Q273 645 259 655.5Q245 666 224 666Q205 666 195.5 660.5Q186 655 181.5 647.5Q177 640 176.5 630.5Q176 621 176 614L119 615Q115 668 144.5 696.5Q174 725 228 725Q259 725 278 714.5Q297 704 311.5 692Q326 680 340 669.5Q354 659 375 659ZM466 0 155 380 156 0H90V500H134L445 120L444 500H510V0Z" glyph-name="ntilde.smcp" horiz-adv-x="600" /><glyph d="M192 442H343L388 559H237ZM318 376H167L119 250H52L100 376H15L35 442H125L170 559H85L105 625H195L243 750H310L262 625H413L461 750H528L480 625H570L550 559H455L410 442H500L480 376H385L337 250H270Z" glyph-name="numbersign" horiz-adv-x="585" unicode="#" /><glyph d="M192 192H343L388 309H237ZM318 126H167L119 0H52L100 126H15L35 192H125L170 309H85L105 375H195L243 500H310L262 375H413L461 500H528L480 375H570L550 309H455L410 192H500L480 126H385L337 0H270Z" glyph-name="numbersign.OP" horiz-adv-x="585" /><glyph d="M420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="o" horiz-adv-x="560" unicode="o" /><glyph d="M468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="o.smcp" horiz-adv-x="610" /><glyph d="M265 575 345 750H440L295 575ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="oacute" horiz-adv-x="560" unicode="&#243;" /><glyph d="M290 575 370 750H465L320 575ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="oacute.smcp" horiz-adv-x="610" /><glyph d="M280 585Q244 585 216 593.5Q188 602 168 621Q148 640 137 671.5Q126 703 125 750H166Q168 696 195 670.5Q222 645 280 645Q338 645 365 670.5Q392 696 394 750H435Q434 703 423 671.5Q412 640 392 621Q372 602 344 593.5Q316 585 280 585ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="obreve" horiz-adv-x="560" unicode="&#335;" /><glyph d="M305 565Q269 565 241 575Q213 585 193 607Q173 629 162 664.5Q151 700 150 750H201Q202 716 207.5 692Q213 668 225 653Q237 638 256.5 631.5Q276 625 305 625Q363 625 385 653.5Q407 682 409 750H460Q459 700 448 664.5Q437 629 417 607Q397 585 369 575Q341 565 305 565ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="obreve.smcp" horiz-adv-x="610" /><glyph d="M415 575 280 691 145 575H115L240 750H320L445 575ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="ocircumflex" horiz-adv-x="560" unicode="&#244;" /><glyph d="M440 575 305 691 170 575H140L265 750H345L470 575ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="ocircumflex.smcp" horiz-adv-x="610" /><glyph d="M435 685Q435 662 419 646Q403 630 380 630Q357 630 341 646Q325 662 325 685Q325 708 341 724Q357 740 380 740Q403 740 419 724Q435 708 435 685ZM180 630Q157 630 141 646Q125 662 125 685Q125 708 141 724Q157 740 180 740Q203 740 219 724Q235 708 235 685Q235 662 219 646Q203 630 180 630ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="odieresis" horiz-adv-x="560" unicode="&#246;" /><glyph d="M460 685Q460 662 444 646Q428 630 405 630Q382 630 366 646Q350 662 350 685Q350 708 366 724Q382 740 405 740Q428 740 444 724Q460 708 460 685ZM205 630Q182 630 166 646Q150 662 150 685Q150 708 166 724Q182 740 205 740Q228 740 244 724Q260 708 260 685Q260 662 244 646Q228 630 205 630ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="odieresis.smcp" horiz-adv-x="610" /><glyph d="M630 448Q586 448 559.5 433.5Q533 419 518 395Q503 371 497 340Q491 309 490 275H750Q750 308 747.5 339.5Q745 371 733 395Q721 419 697 433.5Q673 448 630 448ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM280 510Q350 510 391.5 488Q433 466 456 426Q479 465 520.5 487.5Q562 510 630 510Q694 510 731.5 489.5Q769 469 788.5 434Q808 399 814 351.5Q820 304 820 250V218H490Q491 185 497.5 155Q504 125 519 102Q534 79 561 65.5Q588 52 630 52Q684 52 721 58Q758 64 795 80V20Q753 1 710 -4.5Q667 -10 630 -10Q562 -10 520.5 12.5Q479 35 456 74Q433 34 391.5 12Q350 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Z" glyph-name="oe" horiz-adv-x="890" unicode="&#339;" /><glyph d="M375 61V439H305Q255 439 222 425.5Q189 412 169 387Q149 362 140.5 327Q132 292 132 250Q132 208 140.5 173Q149 138 169 113Q189 88 222 74.5Q255 61 305 61ZM675 226H441V61H710V0H307Q242 0 196.5 17.5Q151 35 121.5 67.5Q92 100 78.5 146.5Q65 193 65 250Q65 307 78.5 353.5Q92 400 121.5 432.5Q151 465 196.5 482.5Q242 500 307 500H710V439H441V287H675Z" glyph-name="oe.smcp" horiz-adv-x="775" /><glyph d="M445 0Q396 -20 367.5 -51.5Q339 -83 339 -122Q339 -145 351.5 -162Q364 -179 393 -179Q410 -179 424.5 -176.5Q439 -174 450 -171V-219Q437 -224 419 -227Q401 -230 379 -230Q334 -230 306 -205Q278 -180 278 -135Q278 -47 367 1Z" glyph-name="ogonek" horiz-adv-x="602" unicode="&#731;" /><glyph d="M265 575 120 750H215L295 575ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="ograve" horiz-adv-x="560" unicode="&#242;" /><glyph d="M290 575 145 750H240L320 575ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="ograve.smcp" horiz-adv-x="610" /><glyph d="M190 575 270 750H365L220 575ZM340 575 420 750H518L370 575ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="ohungarumlaut" horiz-adv-x="560" unicode="&#337;" /><glyph d="M198 575 278 750H373L248 575ZM348 575 428 750H523L398 575ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="ohungarumlaut.smcp" horiz-adv-x="610" /><glyph d="M143 680V745H417V680ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="omacron" horiz-adv-x="560" unicode="&#333;" /><glyph d="M168 640H442V575H168ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="omacron.smcp" horiz-adv-x="610" /><glyph d="M125 55H260V688L90 673V700L300 760H335V55H470V0H125Z" glyph-name="one" horiz-adv-x="600" unicode="1" /><glyph d="M235 760V0H160V688L25 683V710L200 760Z" glyph-name="one.LP" horiz-adv-x="360" /><glyph d="M235 510V0H160V438L25 433V460L200 510Z" glyph-name="one.OP" horiz-adv-x="360" /><glyph d="M273 308H60V374H273Z" glyph-name="onethirdemdash" horiz-adv-x="333" /><glyph d="M168 556Q133 553 114 535.5Q95 518 95 489Q95 476 98.5 464Q102 452 110.5 442.5Q119 433 133.5 427Q148 421 169 421Q192 421 216.5 428.5Q241 436 261 447V564ZM261 634Q261 669 244 686.5Q227 704 182 704Q150 704 121 698Q92 692 66 683V738Q94 749 125.5 754.5Q157 760 182 760Q225 760 253 751Q281 742 297 725Q313 708 319 684.5Q325 661 325 632V375H283L276 399Q217 365 167 365Q92 365 61 397.5Q30 430 30 489Q30 541 63.5 572.5Q97 604 169 608L261 613Z" glyph-name="ordfeminine" horiz-adv-x="370" unicode="&#170;" /><glyph d="M132 533H238L185 677ZM298 378 258 481H112L72 378H-1L160 760H210L371 378Z" glyph-name="ordfeminine.smcp" horiz-adv-x="370" /><glyph d="M97 563Q97 488 123 455.5Q149 423 206 423Q265 423 294 456.5Q323 490 323 563Q323 636 294 669Q265 702 206 702Q149 702 123 670Q97 638 97 563ZM35 563Q35 660 75 710Q115 760 206 760Q297 760 340.5 710Q384 660 384 563Q384 466 340.5 415.5Q297 365 206 365Q115 365 75 415.5Q35 466 35 563Z" glyph-name="ordmasculine" horiz-adv-x="419" unicode="&#186;" /><glyph d="M109 565Q109 491 134 460.5Q159 430 216 430Q275 430 303 461Q331 492 331 565Q331 638 303 669Q275 700 216 700Q159 700 134 669.5Q109 639 109 565ZM45 565Q45 662 85 711Q125 760 216 760Q307 760 350.5 711Q394 662 394 565Q394 468 350.5 419Q307 370 216 370Q125 370 85 419Q45 468 45 565Z" glyph-name="ordmasculine.smcp" horiz-adv-x="439" /><glyph d="M420 250Q420 290 417 324.5Q414 359 402 385L191 73Q222 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM140 250Q140 209 142.5 174.5Q145 140 157 114L368 426Q352 437 330.5 442.5Q309 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250ZM117 54Q90 89 80 138.5Q70 188 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q321 510 351.5 502Q382 494 405 480L418 500H480L443 445Q470 410 480 360.5Q490 311 490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q201 -10 155 19L142 0H80Z" glyph-name="oslash" horiz-adv-x="560" unicode="&#248;" /><glyph d="M468 250Q468 291 462 326.5Q456 362 439 388L220 65Q255 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM142 250Q142 208 147.5 173Q153 138 170 111L389 434Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250ZM478 445Q509 410 522 360.5Q535 311 535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q268 -10 238.5 -3.5Q209 3 185 14L176 0H95L132 55Q101 90 88 139.5Q75 189 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q341 510 371 503.5Q401 497 424 486L434 500H515Z" glyph-name="oslash.smcp" horiz-adv-x="610" /><glyph d="M355 659Q374 659 384 664.5Q394 670 398.5 677.5Q403 685 403.5 694Q404 703 404 711L461 710Q465 657 435 628.5Q405 600 351 600Q320 600 301 610.5Q282 621 267.5 633Q253 645 239 655.5Q225 666 204 666Q185 666 175.5 660.5Q166 655 161.5 647.5Q157 640 156.5 630.5Q156 621 156 614L99 615Q95 668 124.5 696.5Q154 725 208 725Q239 725 258 714.5Q277 704 291.5 692Q306 680 320 669.5Q334 659 355 659ZM420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q344 510 385 491Q426 472 449.5 437.5Q473 403 481.5 355Q490 307 490 250Z" glyph-name="otilde" horiz-adv-x="560" unicode="&#245;" /><glyph d="M380 659Q399 659 409 664.5Q419 670 423.5 677.5Q428 685 428.5 694Q429 703 429 711L486 710Q490 657 460 628.5Q430 600 376 600Q345 600 326 610.5Q307 621 292.5 633Q278 645 264 655.5Q250 666 229 666Q210 666 200.5 660.5Q191 655 186.5 647.5Q182 640 181.5 630.5Q181 621 181 614L124 615Q120 668 149.5 696.5Q179 725 233 725Q264 725 283 714.5Q302 704 316.5 692Q331 680 345 669.5Q359 659 380 659ZM468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 193 523 145Q511 97 484 62.5Q457 28 413 9Q369 -10 305 -10Q241 -10 197 9Q153 28 126 62.5Q99 97 87 145Q75 193 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="otilde.smcp" horiz-adv-x="610" /><glyph d="M204 62Q237 52 290 52Q335 52 361.5 69Q388 86 402.5 114Q417 142 421 177.5Q425 213 425 250Q425 287 421 322.5Q417 358 402.5 386Q388 414 361.5 431Q335 448 290 448Q241 448 210 440Q179 432 150 416V84Q170 73 204 62ZM290 -10Q255 -10 218.5 0Q182 10 150 31V-250H80V500H119L134 459Q173 484 211 497Q249 510 290 510Q350 510 390 489.5Q430 469 453 434Q476 399 485.5 351.5Q495 304 495 250Q495 196 485.5 148.5Q476 101 453 66Q430 31 390 10.5Q350 -10 290 -10Z" glyph-name="p" horiz-adv-x="565" unicode="p" /><glyph d="M388 339Q388 357 384.5 375Q381 393 370.5 407Q360 421 340.5 430Q321 439 290 439H156V239H290Q321 239 340.5 248Q360 257 370.5 271Q381 285 384.5 303Q388 321 388 339ZM156 178V0H90V500H292Q338 500 370 487Q402 474 422 452Q442 430 450.5 400.5Q459 371 459 339Q459 307 450 277.5Q441 248 421.5 226Q402 204 370 191Q338 178 292 178Z" glyph-name="p.smcp" horiz-adv-x="520" /><glyph d="M459 684H388V366H459ZM129 525Q129 498 135.5 470Q142 442 160 418.5Q178 395 211 380.5Q244 366 297 366H317V684H297Q244 684 211 669.5Q178 655 160 631.5Q142 608 135.5 580Q129 552 129 525ZM317 0V300H301Q231 300 183 318.5Q135 337 105.5 368.5Q76 400 63 440.5Q50 481 50 525Q50 569 63 609.5Q76 650 105.5 681.5Q135 713 183 731.5Q231 750 301 750H530V0H459V300H388V0Z" glyph-name="paragraph" horiz-adv-x="620" unicode="&#182;" /><glyph d="M190 -206Q171 -178 149 -130Q127 -82 108.5 -17Q90 48 77.5 129Q65 210 65 305Q65 400 77.5 481Q90 562 108.5 627Q127 692 149 740Q171 788 190 816H265Q246 788 224 740Q202 692 183 627Q164 562 151.5 481Q139 400 139 305Q139 210 151.5 129Q164 48 183 -17Q202 -82 224 -130Q246 -178 265 -206Z" glyph-name="parenleft" horiz-adv-x="310" unicode="(" /><glyph d="M45 -206Q64 -178 86 -130Q108 -82 127 -17Q146 48 158.5 129Q171 210 171 305Q171 400 158.5 481Q146 562 127 627Q108 692 86 740Q64 788 45 816H120Q139 788 161 740Q183 692 201.5 627Q220 562 232.5 481Q245 400 245 305Q245 210 232.5 129Q220 48 201.5 -17Q183 -82 161 -130Q139 -178 120 -206Z" glyph-name="parenright" horiz-adv-x="310" unicode=")" /><glyph d="M420 250Q420 292 416.5 328.5Q413 365 399 391.5Q385 418 357 433Q329 448 280 448Q230 448 202 433Q174 418 160.5 391.5Q147 365 143.5 328.5Q140 292 140 250Q140 208 143.5 171.5Q147 135 160.5 108.5Q174 82 202 67Q230 52 280 52Q329 52 357 67Q385 82 399 108.5Q413 135 416.5 171.5Q420 208 420 250ZM490 250Q490 193 481.5 145Q473 97 449.5 62.5Q426 28 385 9Q344 -10 280 -10Q216 -10 175 9Q134 28 111 62.5Q88 97 79 145Q70 193 70 250Q70 307 79 355Q88 403 111 437.5Q134 472 175 491Q216 510 280 510Q325 510 357.5 497.5Q390 485 413 463Q401 578 362.5 638Q324 698 240 698Q184 698 144.5 689.5Q105 681 85 671V731Q102 740 138.5 750Q175 760 240 760Q316 760 364.5 727.5Q413 695 441 631Q469 567 479.5 471.5Q490 376 490 250Z" glyph-name="partialdiff" horiz-adv-x="560" unicode="&#8706;" /><glyph d="M236 430Q293 430 318 460.5Q343 491 343 565Q343 639 318 669.5Q293 700 236 700Q179 700 154 669.5Q129 639 129 565Q129 491 154 460.5Q179 430 236 430ZM689 320Q632 320 607 289.5Q582 259 582 185Q582 111 607 80.5Q632 50 689 50Q746 50 771 80.5Q796 111 796 185Q796 259 771 289.5Q746 320 689 320ZM159 0 689 750H766L236 0ZM689 380Q780 380 820 331Q860 282 860 185Q860 88 820 39Q780 -10 689 -10Q598 -10 558 39Q518 88 518 185Q518 282 558 331Q598 380 689 380ZM236 370Q145 370 105 419Q65 468 65 565Q65 662 105 711Q145 760 236 760Q327 760 367 711Q407 662 407 565Q407 468 367 419Q327 370 236 370Z" glyph-name="percent" horiz-adv-x="925" unicode="%" /><glyph d="M55 45Q55 68 71 84Q87 100 110 100Q133 100 149 84Q165 68 165 45Q165 22 149 6Q133 -10 110 -10Q87 -10 71 6Q55 22 55 45Z" glyph-name="period" horiz-adv-x="220" unicode="." /><glyph d="M55 375Q55 398 71 414Q87 430 110 430Q133 430 149 414Q165 398 165 375Q165 352 149 336Q133 320 110 320Q87 320 71 336Q55 352 55 375Z" glyph-name="periodcentered" horiz-adv-x="220" unicode="&#183;" /><glyph d="M689 320Q632 320 607 289.5Q582 259 582 185Q582 111 607 80.5Q632 50 689 50Q746 50 771 80.5Q796 111 796 185Q796 259 771 289.5Q746 320 689 320ZM1109 320Q1052 320 1027 289.5Q1002 259 1002 185Q1002 111 1027 80.5Q1052 50 1109 50Q1166 50 1191 80.5Q1216 111 1216 185Q1216 259 1191 289.5Q1166 320 1109 320ZM236 430Q293 430 318 460.5Q343 491 343 565Q343 639 318 669.5Q293 700 236 700Q179 700 154 669.5Q129 639 129 565Q129 491 154 460.5Q179 430 236 430ZM159 0 689 750H766L236 0ZM1109 380Q1200 380 1240 331Q1280 282 1280 185Q1280 88 1240 39Q1200 -10 1109 -10Q1018 -10 978 39Q938 88 938 185Q938 282 978 331Q1018 380 1109 380ZM689 380Q780 380 820 331Q860 282 860 185Q860 88 820 39Q780 -10 689 -10Q598 -10 558 39Q518 88 518 185Q518 282 558 331Q598 380 689 380ZM236 370Q145 370 105 419Q65 468 65 565Q65 662 105 711Q145 760 236 760Q327 760 367 711Q407 662 407 565Q407 468 367 419Q327 370 236 370Z" glyph-name="perthousand" horiz-adv-x="1345" unicode="&#8240;" /><glyph d="M400 295Q400 309 394.5 320Q389 331 367 331H297V258H367Q389 258 394.5 269.5Q400 281 400 295ZM297 200V111H236V389H366Q393 389 411.5 381.5Q430 374 441.5 361Q453 348 458 331Q463 314 463 295Q463 276 458 259Q453 242 441.5 229Q430 216 411.5 208Q393 200 366 200ZM335 52Q378 52 414.5 59Q451 66 477 87Q503 108 518 147Q533 186 533 250Q533 314 518 353Q503 392 477 413Q451 434 414.5 441Q378 448 335 448Q292 448 255.5 441Q219 434 193 413Q167 392 152 353Q137 314 137 250Q137 186 152 147Q167 108 193 87Q219 66 255.5 59Q292 52 335 52ZM335 -10Q282 -10 234 0.5Q186 11 149.5 39.5Q113 68 91.5 119Q70 170 70 250Q70 331 91.5 381.5Q113 432 149.5 460.5Q186 489 234 499.5Q282 510 335 510Q388 510 436 499.5Q484 489 520.5 460.5Q557 432 578.5 381.5Q600 331 600 250Q600 170 578.5 119Q557 68 520.5 39.5Q484 11 436 0.5Q388 -10 335 -10Z" glyph-name="pi" horiz-adv-x="680" unicode="&#960;" /><glyph d="M227 141V308H60V374H227V541H293V374H460V308H293V141Z" glyph-name="plus" horiz-adv-x="520" unicode="+" /><glyph d="M60 42V108H460V42ZM227 174V341H60V407H227V574H293V407H460V341H293V174Z" glyph-name="plusminus" horiz-adv-x="520" unicode="&#177;" /><glyph d="M381 300Q381 313 375 322Q369 331 346 331H288V269H346Q369 269 375 278Q381 287 381 300ZM288 210V111H227V389H348Q402 389 423.5 363.5Q445 338 445 300Q445 273 436 252Q427 231 404 220L467 111H397L341 210ZM335 52Q378 52 414.5 59Q451 66 477 87Q503 108 518 147Q533 186 533 250Q533 314 518 353Q503 392 477 413Q451 434 414.5 441Q378 448 335 448Q292 448 255.5 441Q219 434 193 413Q167 392 152 353Q137 314 137 250Q137 186 152 147Q167 108 193 87Q219 66 255.5 59Q292 52 335 52ZM335 -10Q282 -10 234 0.5Q186 11 149.5 39.5Q113 68 91.5 119Q70 170 70 250Q70 331 91.5 381.5Q113 432 149.5 460.5Q186 489 234 499.5Q282 510 335 510Q388 510 436 499.5Q484 489 520.5 460.5Q557 432 578.5 381.5Q600 331 600 250Q600 170 578.5 119Q557 68 520.5 39.5Q484 11 436 0.5Q388 -10 335 -10Z" glyph-name="product" horiz-adv-x="680" unicode="&#8719;" /><glyph glyph-name="punctuationspace" horiz-adv-x="220" unicode="&#8200;" /><glyph d="M361 62Q375 66 388.5 71.5Q402 77 415 84V416Q384 433 353.5 440.5Q323 448 275 448Q230 448 203.5 431Q177 414 162.5 386Q148 358 144 322.5Q140 287 140 250Q140 213 144 177.5Q148 142 162.5 114Q177 86 203.5 69Q230 52 275 52Q328 52 361 62ZM275 -10Q215 -10 175 10.5Q135 31 112 66Q89 101 79.5 148.5Q70 196 70 250Q70 304 79.5 351.5Q89 399 112 434Q135 469 175 489.5Q215 510 275 510Q315 510 355.5 497Q396 484 431 458L446 500H485V-250H415V31Q344 -10 275 -10Z" glyph-name="q" horiz-adv-x="565" unicode="q" /><glyph d="M468 250Q468 292 461 329Q454 366 436 393Q418 420 386.5 435.5Q355 451 305 451Q254 451 222.5 435.5Q191 420 173 393Q155 366 148.5 329Q142 292 142 250Q142 208 148.5 171Q155 134 173 107Q191 80 222.5 64.5Q254 49 305 49Q355 49 386.5 64.5Q418 80 436 107Q454 134 461 171Q468 208 468 250ZM535 250Q535 160 505.5 95.5Q476 31 403 5V4L686 -52V-124L232 -2Q188 10 158 32.5Q128 55 109.5 87.5Q91 120 83 161Q75 202 75 250Q75 307 87 355Q99 403 126 437.5Q153 472 197 491Q241 510 305 510Q369 510 413 491Q457 472 484 437.5Q511 403 523 355Q535 307 535 250Z" glyph-name="q.smcp" horiz-adv-x="610" /><glyph d="M128 45Q128 68 144 84Q160 100 183 100Q206 100 222 84Q238 68 238 45Q238 22 222 6Q206 -10 183 -10Q160 -10 144 6Q128 22 128 45ZM195 696Q141 696 102 688.5Q63 681 40 673V735Q52 740 71 744.5Q90 749 111 752.5Q132 756 154 758Q176 760 194 760Q273 760 321 743.5Q369 727 395.5 699.5Q422 672 431 636Q440 600 440 562Q440 515 427 482.5Q414 450 390 426.5Q366 403 332.5 385.5Q299 368 257 351Q230 340 214.5 327Q199 314 192 297Q185 280 183.5 259Q182 238 182 210H141Q135 240 133.5 268.5Q132 297 141 323Q150 349 173.5 371Q197 393 242 410Q267 419 290 429.5Q313 440 331 456Q349 472 359.5 497Q370 522 370 561Q370 592 363.5 617Q357 642 338 659.5Q319 677 284.5 686.5Q250 696 195 696Z" glyph-name="question" horiz-adv-x="500" unicode="?" /><glyph d="M352 455Q352 432 336 416Q320 400 297 400Q274 400 258 416Q242 432 242 455Q242 478 258 494Q274 510 297 510Q320 510 336 494Q352 478 352 455ZM285 -196Q339 -196 378 -188.5Q417 -181 440 -173V-235Q428 -240 409 -244.5Q390 -249 369 -252.5Q348 -256 326 -258Q304 -260 286 -260Q207 -260 159 -243.5Q111 -227 84.5 -199.5Q58 -172 49 -136Q40 -100 40 -62Q40 -15 53 17.5Q66 50 90 73.5Q114 97 147.5 114.5Q181 132 223 149Q250 160 265.5 173Q281 186 288 203Q295 220 296.5 241Q298 262 298 290H339Q345 260 346.5 231.5Q348 203 339 177Q330 151 306.5 129Q283 107 238 90Q213 81 190 70.5Q167 60 149 44Q131 28 120.5 3Q110 -22 110 -61Q110 -92 116.5 -117Q123 -142 142 -159.5Q161 -177 195 -186.5Q229 -196 285 -196Z" glyph-name="questiondown" horiz-adv-x="500" unicode="&#191;" /><glyph d="M231 500 200 750H300L269 500ZM91 500 60 750H160L129 500Z" glyph-name="quotedbl" horiz-adv-x="360" unicode="&quot;" /><glyph d="M183 -160Q196 -130 205 -91Q213 -58 216.5 -12.5Q220 33 212 90H302Q300 31 285 -15Q270 -61 253 -93Q233 -131 207 -160ZM33 -160Q46 -130 55 -91Q63 -58 66.5 -12.5Q70 33 62 90H152Q150 31 135 -15Q120 -61 103 -93Q83 -131 57 -160Z" glyph-name="quotedblbase" horiz-adv-x="310" unicode="&#8222;" /><glyph d="M277 750Q264 720 255 681Q247 648 243.5 602.5Q240 557 248 500H158Q160 558 175 604.5Q190 651 207 683Q227 721 253 750ZM127 750Q114 720 105 681Q97 648 93.5 602.5Q90 557 98 500H8Q10 558 25 604.5Q40 651 57 683Q77 721 103 750Z" glyph-name="quotedblleft" horiz-adv-x="310" unicode="&#8220;" /><glyph d="M183 500Q196 530 205 569Q213 602 216.5 647.5Q220 693 212 750H302Q300 691 285 645Q270 599 253 567Q233 529 207 500ZM33 500Q46 530 55 569Q63 602 66.5 647.5Q70 693 62 750H152Q150 691 135 645Q120 599 103 567Q83 529 57 500Z" glyph-name="quotedblright" horiz-adv-x="310" unicode="&#8221;" /><glyph d="M127 750Q114 720 105 681Q97 648 93.5 602.5Q90 557 98 500H8Q10 558 25 604.5Q40 651 57 683Q77 721 103 750Z" glyph-name="quoteleft" horiz-adv-x="160" unicode="&#8216;" /><glyph d="M33 500Q46 530 55 569Q63 602 66.5 647.5Q70 693 62 750H152Q150 691 135 645Q120 599 103 567Q83 529 57 500Z" glyph-name="quoteright" horiz-adv-x="160" unicode="&#8217;" /><glyph d="M33 -160Q46 -130 55 -91Q63 -58 66.5 -12.5Q70 33 62 90H152Q150 31 135 -15Q120 -61 103 -93Q83 -131 57 -160Z" glyph-name="quotesinglbase" horiz-adv-x="160" unicode="&#8218;" /><glyph d="M91 500 60 750H160L129 500Z" glyph-name="quotesingle" horiz-adv-x="220" unicode="&apos;" /><glyph d="M355 440Q331 448 295 448Q244 448 211 440Q178 432 150 416V0H80V500H119L134 458Q169 484 211 497Q253 510 295 510Q312 510 326.5 508.5Q341 507 355 505Z" glyph-name="r" horiz-adv-x="393" unicode="r" /><glyph d="M398 349Q398 367 394 383.5Q390 400 378.5 412Q367 424 347.5 431.5Q328 439 297 439H156V259H297Q328 259 347.5 266.5Q367 274 378.5 286Q390 298 394 314.5Q398 331 398 349ZM156 198V0H90V500H304Q350 500 382 488.5Q414 477 433 456.5Q452 436 460.5 408.5Q469 381 469 349Q469 298 448 260.5Q427 223 375 207L494 0H416L309 198Z" glyph-name="r.smcp" horiz-adv-x="550" /><glyph d="M203 575 283 750H378L233 575ZM355 440Q331 448 295 448Q244 448 211 440Q178 432 150 416V0H80V500H119L134 458Q169 484 211 497Q253 510 295 510Q312 510 326.5 508.5Q341 507 355 505Z" glyph-name="racute" horiz-adv-x="393" unicode="&#341;" /><glyph d="M235 575 315 750H410L265 575ZM398 349Q398 367 394 383.5Q390 400 378.5 412Q367 424 347.5 431.5Q328 439 297 439H156V259H297Q328 259 347.5 266.5Q367 274 378.5 286Q390 298 394 314.5Q398 331 398 349ZM156 198V0H90V500H304Q350 500 382 488.5Q414 477 433 456.5Q452 436 460.5 408.5Q469 381 469 349Q469 298 448 260.5Q427 223 375 207L494 0H416L309 198Z" glyph-name="racute.smcp" horiz-adv-x="550" /><glyph d="M146 61H514V439H146ZM80 500H580V0H80Z" glyph-name="radical" horiz-adv-x="660" unicode="&#8730;" /><glyph d="M383 750 258 575H178L53 750H83L218 634L353 750ZM355 440Q331 448 295 448Q244 448 211 440Q178 432 150 416V0H80V500H119L134 458Q169 484 211 497Q253 510 295 510Q312 510 326.5 508.5Q341 507 355 505Z" glyph-name="rcaron" horiz-adv-x="393" unicode="&#345;" /><glyph d="M130 750 265 634 400 750H430L305 575H225L100 750ZM398 349Q398 367 394 383.5Q390 400 378.5 412Q367 424 347.5 431.5Q328 439 297 439H156V259H297Q328 259 347.5 266.5Q367 274 378.5 286Q390 298 394 314.5Q398 331 398 349ZM156 198V0H90V500H304Q350 500 382 488.5Q414 477 433 456.5Q452 436 460.5 408.5Q469 381 469 349Q469 298 448 260.5Q427 223 375 207L494 0H416L309 198Z" glyph-name="rcaron.smcp" horiz-adv-x="550" /><glyph d="M75 -251 93 -171Q77 -165 68.5 -151.5Q60 -138 60 -121Q60 -98 76 -82Q92 -66 115 -66Q138 -66 154 -82Q170 -98 170 -121Q170 -136 164 -148L117 -251ZM355 440Q331 448 295 448Q244 448 211 440Q178 432 150 416V0H80V500H119L134 458Q169 484 211 497Q253 510 295 510Q312 510 326.5 508.5Q341 507 355 505Z" glyph-name="rcommaaccent" horiz-adv-x="393" unicode="&#343;" /><glyph d="M238 -250 256 -170Q240 -164 231.5 -150.5Q223 -137 223 -120Q223 -97 239 -81Q255 -65 278 -65Q301 -65 317 -81Q333 -97 333 -120Q333 -135 327 -147L280 -250ZM398 349Q398 367 394 383.5Q390 400 378.5 412Q367 424 347.5 431.5Q328 439 297 439H156V259H297Q328 259 347.5 266.5Q367 274 378.5 286Q390 298 394 314.5Q398 331 398 349ZM156 198V0H90V500H304Q350 500 382 488.5Q414 477 433 456.5Q452 436 460.5 408.5Q469 381 469 349Q469 298 448 260.5Q427 223 375 207L494 0H416L309 198Z" glyph-name="rcommaaccent.smcp" horiz-adv-x="550" /><glyph d="M195 651Q195 658 192 662.5Q189 667 177 667H147V635H177Q189 635 192 639.5Q195 644 195 651ZM147 604V553H115V697H179Q207 697 218 684Q229 671 229 651Q229 620 208 610L240 553H204L175 604ZM172 728Q150 728 131 724.5Q112 721 98.5 709.5Q85 698 77.5 678Q70 658 70 625Q70 592 77.5 572Q85 552 98.5 540.5Q112 529 131 525.5Q150 522 172 522Q194 522 213 525.5Q232 529 245.5 540.5Q259 552 266.5 572Q274 592 274 625Q274 658 266.5 678Q259 698 245.5 709.5Q232 721 213 724.5Q194 728 172 728ZM172 760Q199 760 224 754.5Q249 749 268 734.5Q287 720 298 693.5Q309 667 309 625Q309 583 298 557Q287 531 268 516Q249 501 224 495.5Q199 490 172 490Q145 490 120 495.5Q95 501 76 516Q57 531 46 557Q35 583 35 625Q35 667 46 693.5Q57 720 76 734.5Q95 749 120 754.5Q145 760 172 760Z" glyph-name="registered" horiz-adv-x="344" unicode="&#174;" /><glyph d="M328 662Q328 690 315.5 700.5Q303 711 280 711Q257 711 244 700.5Q231 690 231 662Q231 635 244 624.5Q257 614 280 614Q303 614 315.5 624.5Q328 635 328 662ZM378 662Q378 611 351.5 588Q325 565 280 565Q235 565 208 588Q181 611 181 662Q181 714 208 737Q235 760 280 760Q325 760 351.5 737Q378 714 378 662Z" glyph-name="ring" horiz-adv-x="602" unicode="&#730;" /><glyph d="M133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 115 413 87.5Q403 60 379.5 38Q356 16 317.5 3Q279 -10 222 -10Q204 -10 183 -8Q162 -6 141.5 -2.5Q121 1 103 5.5Q85 10 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510Q289 510 327 504.5Q365 499 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Z" glyph-name="s" horiz-adv-x="490" unicode="s" /><glyph d="M139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 114 454 85.5Q443 57 417.5 36Q392 15 350.5 2.5Q309 -10 249 -10Q230 -10 206.5 -8Q183 -6 159.5 -2Q136 2 116.5 7Q97 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Z" glyph-name="s.smcp" horiz-adv-x="540" /><glyph d="M230 575 310 750H405L260 575ZM133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 115 413 87.5Q403 60 379.5 38Q356 16 317.5 3Q279 -10 222 -10Q204 -10 183 -8Q162 -6 141.5 -2.5Q121 1 103 5.5Q85 10 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510Q289 510 327 504.5Q365 499 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Z" glyph-name="sacute" horiz-adv-x="490" unicode="&#347;" /><glyph d="M255 575 335 750H430L285 575ZM139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 114 454 85.5Q443 57 417.5 36Q392 15 350.5 2.5Q309 -10 249 -10Q230 -10 206.5 -8Q183 -6 159.5 -2Q136 2 116.5 7Q97 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Z" glyph-name="sacute.smcp" horiz-adv-x="540" /><glyph d="M410 750 285 575H205L80 750H130L245 634L360 750ZM133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 115 413 87.5Q403 60 379.5 38Q356 16 317.5 3Q279 -10 222 -10Q204 -10 183 -8Q162 -6 141.5 -2.5Q121 1 103 5.5Q85 10 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510Q289 510 327 504.5Q365 499 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Z" glyph-name="scaron" horiz-adv-x="490" unicode="&#353;" /><glyph d="M135 750 270 634 405 750H435L310 575H230L105 750ZM139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 114 454 85.5Q443 57 417.5 36Q392 15 350.5 2.5Q309 -10 249 -10Q230 -10 206.5 -8Q183 -6 159.5 -2Q136 2 116.5 7Q97 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Z" glyph-name="scaron.smcp" horiz-adv-x="540" /><glyph d="M192 -185Q202 -176 211 -165Q220 -154 224.5 -142.5Q229 -131 227.5 -121.5Q226 -112 216 -106Q198 -96 192 -83.5Q186 -71 187.5 -58Q189 -45 195.5 -32.5Q202 -20 209 -10Q191 -9 172 -7Q153 -5 134.5 -1.5Q116 2 100 6Q84 10 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510Q289 510 327 504.5Q365 499 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 117 414.5 91.5Q406 66 386.5 45Q367 24 334.5 10Q302 -4 255 -8Q244 -24 241.5 -40.5Q239 -57 263 -71Q288 -85 295 -101Q302 -117 299 -133Q296 -149 286.5 -162.5Q277 -176 268 -185Z" glyph-name="scedilla" horiz-adv-x="490" unicode="&#351;" /><glyph d="M217 -185Q227 -176 236 -165Q245 -154 249.5 -142.5Q254 -131 252.5 -121.5Q251 -112 241 -106Q223 -96 217 -83.5Q211 -71 212.5 -58Q214 -45 220.5 -32.5Q227 -20 234 -10Q215 -9 193 -6.5Q171 -4 150.5 -0.5Q130 3 112.5 7.5Q95 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 116 455.5 89.5Q446 63 424 42Q402 21 366.5 7.5Q331 -6 280 -9Q269 -25 266.5 -41Q264 -57 288 -71Q313 -85 320 -101Q327 -117 324 -133Q321 -149 311.5 -162.5Q302 -176 293 -185Z" glyph-name="scedilla.smcp" horiz-adv-x="540" /><glyph d="M380 575 245 691 110 575H80L205 750H285L410 575ZM133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 115 413 87.5Q403 60 379.5 38Q356 16 317.5 3Q279 -10 222 -10Q204 -10 183 -8Q162 -6 141.5 -2.5Q121 1 103 5.5Q85 10 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510Q289 510 327 504.5Q365 499 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Z" glyph-name="scircumflex" horiz-adv-x="490" unicode="&#349;" /><glyph d="M405 575 270 691 135 575H105L230 750H310L435 575ZM139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 114 454 85.5Q443 57 417.5 36Q392 15 350.5 2.5Q309 -10 249 -10Q230 -10 206.5 -8Q183 -6 159.5 -2Q136 2 116.5 7Q97 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Z" glyph-name="scircumflex.smcp" horiz-adv-x="540" /><glyph d="M304 -75 233 -250H186L210 -75ZM133 362Q133 345 137.5 332.5Q142 320 154 311Q166 302 188.5 296Q211 290 247 287Q300 282 334.5 270.5Q369 259 388.5 241Q408 223 415.5 199Q423 175 423 144Q423 115 413 87.5Q403 60 379.5 38Q356 16 317.5 3Q279 -10 222 -10Q204 -10 183 -8Q162 -6 141.5 -2.5Q121 1 103 5.5Q85 10 73 15V75Q96 67 132.5 59.5Q169 52 223 52Q262 52 287.5 59.5Q313 67 328 80Q343 93 349 109Q355 125 355 143Q355 160 351.5 174.5Q348 189 336 200Q324 211 301.5 218.5Q279 226 242 229Q188 234 153.5 245.5Q119 257 100 274.5Q81 292 74 314Q67 336 67 362Q67 389 76.5 415.5Q86 442 107.5 463Q129 484 165 497Q201 510 254 510Q289 510 327 504.5Q365 499 398 485V425Q369 435 336 441.5Q303 448 253 448Q216 448 192.5 441.5Q169 435 156 423.5Q143 412 138 396Q133 380 133 362Z" glyph-name="scommaaccent" horiz-adv-x="490" unicode="&#537;" /><glyph d="M223 -250 241 -170Q225 -164 216.5 -150.5Q208 -137 208 -120Q208 -97 224 -81Q240 -65 263 -65Q286 -65 302 -81Q318 -97 318 -120Q318 -135 312 -147L265 -250ZM139 358Q139 339 146.5 327Q154 315 170.5 307Q187 299 212.5 294.5Q238 290 274 287Q327 282 363.5 272Q400 262 422.5 245.5Q445 229 455 204.5Q465 180 465 146Q465 114 454 85.5Q443 57 417.5 36Q392 15 350.5 2.5Q309 -10 249 -10Q230 -10 206.5 -8Q183 -6 159.5 -2Q136 2 116.5 7Q97 12 85 17V75Q108 66 151 58Q194 50 250 50Q333 50 366 74.5Q399 99 399 145Q399 186 371 204.5Q343 223 269 229Q215 234 177.5 243Q140 252 117.5 267Q95 282 85 304Q75 326 75 358Q75 392 86 420Q97 448 121.5 468Q146 488 185.5 499Q225 510 281 510Q319 510 363 504.5Q407 499 440 485V427Q411 437 372 443.5Q333 450 280 450Q201 450 170 428Q139 406 139 358Z" glyph-name="scommaaccent.smcp" horiz-adv-x="540" /><glyph d="M524 228Q524 267 514 289Q504 311 483.5 323.5Q463 336 432 343Q401 350 358 358Q307 366 268 376.5Q229 387 203 401Q158 382 141.5 348Q125 314 125 272Q125 233 135 211Q145 189 165.5 176Q186 163 217 156.5Q248 150 291 142Q341 133 380 123Q419 113 445 99Q490 118 507 152Q524 186 524 228ZM114 555Q114 588 124 624Q134 660 162 690Q190 720 239.5 740Q289 760 368 760Q437 760 484.5 749.5Q532 739 563 725V657Q512 679 467 687.5Q422 696 367 696Q311 696 276 685Q241 674 220.5 654.5Q200 635 192.5 609.5Q185 584 185 554Q185 516 197.5 494Q210 472 234 458.5Q258 445 292 437.5Q326 430 369 422Q436 410 479.5 395.5Q523 381 548.5 359Q574 337 584.5 305Q595 273 595 227Q595 206 590.5 182.5Q586 159 575.5 136.5Q565 114 547.5 93Q530 72 503 56Q535 15 535 -55Q535 -88 525 -124Q515 -160 487 -190Q459 -220 409.5 -240Q360 -260 281 -260Q212 -260 164.5 -249.5Q117 -239 86 -225V-157Q111 -168 134.5 -175.5Q158 -183 181.5 -187.5Q205 -192 229.5 -194Q254 -196 282 -196Q337 -196 372.5 -185Q408 -174 428.5 -154.5Q449 -135 456.5 -109.5Q464 -84 464 -54Q464 -17 451.5 5.5Q439 28 415 41.5Q391 55 357 62.5Q323 70 280 78Q213 90 169.5 104.5Q126 119 100.5 141Q75 163 64.5 194.5Q54 226 54 273Q54 294 58.5 317.5Q63 341 73.5 363.5Q84 386 101.5 407Q119 428 146 444Q129 464 121.5 491.5Q114 519 114 555Z" glyph-name="section" horiz-adv-x="649" unicode="&#167;" /><glyph d="M55 375Q55 398 71 414Q87 430 110 430Q133 430 149 414Q165 398 165 375Q165 352 149 336Q133 320 110 320Q87 320 71 336Q55 352 55 375ZM63 -114 88 -5Q72 1 63.5 14.5Q55 28 55 45Q55 68 71 84Q87 100 110 100Q133 100 149 84Q165 68 165 45Q165 30 159 18L98 -114Z" glyph-name="semicolon" horiz-adv-x="220" unicode=";" /><glyph d="M115 0 480 688H50V750H550V702L203 0Z" glyph-name="seven" horiz-adv-x="600" unicode="7" /><glyph d="M115 0 480 688H50V750H550V702L203 0Z" glyph-name="seven.LP" horiz-adv-x="600" /><glyph d="M81 -250 373 438H25V500H440V452L158 -250Z" glyph-name="seven.OP" horiz-adv-x="465" /><glyph d="M144 231Q144 201 150.5 169.5Q157 138 175 111.5Q193 85 226.5 68.5Q260 52 314 52Q365 52 395.5 70.5Q426 89 442.5 116Q459 143 464.5 174Q470 205 470 231Q470 256 466 287.5Q462 319 446 346Q430 373 399 391.5Q368 410 314 410Q261 410 228 393Q195 376 176 349.5Q157 323 150.5 291.5Q144 260 144 231ZM345 696Q286 696 245.5 672Q205 648 180 605.5Q155 563 144.5 503.5Q134 444 134 374Q143 391 158 408.5Q173 426 195.5 440Q218 454 247 463Q276 472 314 472Q391 472 436 448.5Q481 425 504 389.5Q527 354 533.5 311.5Q540 269 540 231Q540 193 532 150.5Q524 108 500 72.5Q476 37 431.5 13.5Q387 -10 314 -10Q233 -10 185 15.5Q137 41 111.5 87.5Q86 134 78 199.5Q70 265 70 345Q70 437 82 513.5Q94 590 125 644.5Q156 699 209 729.5Q262 760 345 760Q418 760 459.5 748.5Q501 737 520 726V660Q492 674 448.5 685Q405 696 345 696Z" glyph-name="six" horiz-adv-x="600" unicode="6" /><glyph d="M144 231Q144 201 150.5 169.5Q157 138 175 111.5Q193 85 226.5 68.5Q260 52 314 52Q365 52 395.5 70.5Q426 89 442.5 116Q459 143 464.5 174Q470 205 470 231Q470 256 466 287.5Q462 319 446 346Q430 373 399 391.5Q368 410 314 410Q261 410 228 393Q195 376 176 349.5Q157 323 150.5 291.5Q144 260 144 231ZM345 696Q286 696 245.5 672Q205 648 180 605.5Q155 563 144.5 503.5Q134 444 134 374Q143 391 158 408.5Q173 426 195.5 440Q218 454 247 463Q276 472 314 472Q391 472 436 448.5Q481 425 504 389.5Q527 354 533.5 311.5Q540 269 540 231Q540 193 532 150.5Q524 108 500 72.5Q476 37 431.5 13.5Q387 -10 314 -10Q233 -10 185 15.5Q137 41 111.5 87.5Q86 134 78 199.5Q70 265 70 345Q70 437 82 513.5Q94 590 125 644.5Q156 699 209 729.5Q262 760 345 760Q418 760 459.5 748.5Q501 737 520 726V660Q492 674 448.5 685Q405 696 345 696Z" glyph-name="six.LP" horiz-adv-x="600" /><glyph d="M144 231Q144 201 150.5 169.5Q157 138 175 111.5Q193 85 226.5 68.5Q260 52 314 52Q365 52 395.5 70.5Q426 89 442.5 116Q459 143 464.5 174Q470 205 470 231Q470 256 466 287.5Q462 319 446 346Q430 373 399 391.5Q368 410 314 410Q261 410 228 393Q195 376 176 349.5Q157 323 150.5 291.5Q144 260 144 231ZM345 696Q286 696 245.5 672Q205 648 180 605.5Q155 563 144.5 503.5Q134 444 134 374Q143 391 158 408.5Q173 426 195.5 440Q218 454 247 463Q276 472 314 472Q391 472 436 448.5Q481 425 504 389.5Q527 354 533.5 311.5Q540 269 540 231Q540 193 532 150.5Q524 108 500 72.5Q476 37 431.5 13.5Q387 -10 314 -10Q233 -10 185 15.5Q137 41 111.5 87.5Q86 134 78 199.5Q70 265 70 345Q70 437 82 513.5Q94 590 125 644.5Q156 699 209 729.5Q262 760 345 760Q418 760 459.5 748.5Q501 737 520 726V660Q492 674 448.5 685Q405 696 345 696Z" glyph-name="six.OP" horiz-adv-x="605" /><glyph glyph-name="sixperemspace" horiz-adv-x="167" unicode="&#8198;" /><glyph d="M0 -206 347 816H420L73 -206Z" glyph-name="slash" horiz-adv-x="420" unicode="/" /><glyph glyph-name="space" horiz-adv-x="200" unicode=" " /><glyph d="M191 422H470V361H191V61H560V0H41V61H121V361H41V422H121V562Q121 605 134.5 641Q148 677 176 703.5Q204 730 248 745Q292 760 352 760Q370 760 395.5 757.5Q421 755 447.5 751Q474 747 498.5 741Q523 735 540 728V668Q525 673 503.5 678Q482 683 457 687.5Q432 692 405 695Q378 698 352 698Q266 698 228.5 666.5Q191 635 191 563Z" glyph-name="sterling" horiz-adv-x="600" unicode="&#163;" /><glyph d="M171 287H390V226H171V61H480V0H31V61H101V226H31V287H101V342Q101 379 110.5 410Q120 441 143.5 463.5Q167 486 205.5 498Q244 510 302 510Q339 510 383 503.5Q427 497 460 483V423Q431 433 392.5 440.5Q354 448 302 448Q230 448 200.5 423Q171 398 171 343Z" glyph-name="sterling.OP" horiz-adv-x="545" /><glyph d="M70 375Q70 455 100 525Q130 595 182.5 647.5Q235 700 305 730Q375 760 455 760Q535 760 605 730Q675 700 727.5 647.5Q780 595 810 525Q840 455 840 375Q840 295 810 225Q780 155 727.5 102.5Q675 50 605 20Q535 -10 455 -10Q375 -10 305 20Q235 50 182.5 102.5Q130 155 100 225Q70 295 70 375Z" glyph-name="summation" horiz-adv-x="910" unicode="&#8721;" /><glyph d="M342 -7Q322 -10 303 -10Q272 -12 242.5 -5.5Q213 1 190 17Q167 33 152.5 58.5Q138 84 137 121V445H37V476L137 505V640L207 660V500H362V445H207V123Q210 77 234 61Q258 45 303 45Q313 45 322.5 45.5Q332 46 342 47Z" glyph-name="t" horiz-adv-x="407" unicode="t" /><glyph d="M298 439V0H227V439H60V500H465V439Z" glyph-name="t.smcp" horiz-adv-x="525" /><glyph d="M332 250H207V123Q210 77 234 61Q258 45 303 45Q313 45 322.5 45.5Q332 46 342 47V-7Q322 -10 303 -10Q272 -12 242.5 -5.5Q213 1 190 17Q167 33 152.5 58.5Q138 84 137 121V250H37V305H137V445H37V476L137 505V640L207 660V500H362V445H207V305H332Z" glyph-name="tbar" horiz-adv-x="407" unicode="&#359;" /><glyph d="M298 439V284H400V229H298V0H227V229H125V284H227V439H60V500H465V439Z" glyph-name="tbar.smcp" horiz-adv-x="525" /><glyph d="M327 575H280L304 750H398ZM342 -7Q322 -10 303 -10Q272 -12 242.5 -5.5Q213 1 190 17Q167 33 152.5 58.5Q138 84 137 121V445H37V476L137 505V640L207 660V500H362V445H207V123Q210 77 234 61Q258 45 303 45Q313 45 322.5 45.5Q332 46 342 47Z" glyph-name="tcaron" horiz-adv-x="407" unicode="&#357;" /><glyph d="M128 750 263 634 398 750H428L303 575H223L98 750ZM298 439V0H227V439H60V500H465V439Z" glyph-name="tcaron.smcp" horiz-adv-x="525" /><glyph d="M206 -250H159L183 -75H277ZM342 -7Q322 -10 303 -10Q272 -12 242.5 -5.5Q213 1 190 17Q167 33 152.5 58.5Q138 84 137 121V445H37V476L137 505V640L207 660V500H362V445H207V123Q210 77 234 61Q258 45 303 45Q313 45 322.5 45.5Q332 46 342 47Z" glyph-name="tcommaaccent" horiz-adv-x="407" unicode="&#355;" /><glyph d="M225 -250 243 -170Q227 -164 218.5 -150.5Q210 -137 210 -120Q210 -97 226 -81Q242 -65 265 -65Q288 -65 304 -81Q320 -97 320 -120Q320 -135 314 -147L267 -250ZM298 439V0H227V439H60V500H465V439Z" glyph-name="tcommaaccent.smcp" horiz-adv-x="525" /><glyph glyph-name="thinspace" horiz-adv-x="100" unicode="&#8201;" /><glyph d="M204 438Q170 427 150 416V84Q179 68 210 60Q241 52 290 52Q335 52 361.5 69Q388 86 402.5 114Q417 142 421 177.5Q425 213 425 250Q425 287 421 322.5Q417 358 402.5 386Q388 414 361.5 431Q335 448 290 448Q237 448 204 438ZM150 750V469Q182 490 218.5 500Q255 510 290 510Q350 510 390 489.5Q430 469 453 434Q476 399 485.5 351.5Q495 304 495 250Q495 196 485.5 148.5Q476 101 453 66Q430 31 390 10.5Q350 -10 290 -10Q253 -10 219 0.5Q185 11 150 31V-250H80V750Z" glyph-name="thorn" horiz-adv-x="565" unicode="&#254;" /><glyph d="M388 250Q388 268 384.5 286Q381 304 370.5 318Q360 332 340.5 341Q321 350 290 350H156V150H290Q321 150 340.5 159Q360 168 370.5 182Q381 196 384.5 214Q388 232 388 250ZM156 89V0H90V500H156V411H292Q338 411 370 398Q402 385 422 363Q442 341 450.5 311.5Q459 282 459 250Q459 218 450 188.5Q441 159 421.5 137Q402 115 370 102Q338 89 292 89Z" glyph-name="thorn.smcp" horiz-adv-x="520" /><glyph d="M261 54Q324 54 364 67Q404 80 427 101.5Q450 123 458.5 151.5Q467 180 467 211Q467 290 423 323Q379 356 286 356H150V422H286Q328 422 358 434Q388 446 406.5 465.5Q425 485 433.5 509Q442 533 442 558Q442 587 434 612Q426 637 405 656Q384 675 347 685.5Q310 696 253 696Q227 696 204 694Q181 692 158.5 687.5Q136 683 113 675.5Q90 668 65 657V725Q96 739 141.5 749.5Q187 760 252 760Q328 760 379 744.5Q430 729 460.5 701.5Q491 674 503.5 637Q516 600 516 558Q516 531 507.5 505.5Q499 480 484 458.5Q469 437 448.5 420Q428 403 404 394Q436 387 461.5 370Q487 353 504.5 328.5Q522 304 531.5 274Q541 244 541 211Q541 166 527.5 125.5Q514 85 481.5 55Q449 25 395 7.5Q341 -10 260 -10Q195 -10 145.5 0.5Q96 11 65 25V93Q116 71 162 62.5Q208 54 261 54Z" glyph-name="three" horiz-adv-x="600" unicode="3" /><glyph d="M261 54Q324 54 364 67Q404 80 427 101.5Q450 123 458.5 151.5Q467 180 467 211Q467 290 423 323Q379 356 286 356H150V422H286Q328 422 358 434Q388 446 406.5 465.5Q425 485 433.5 509Q442 533 442 558Q442 587 434 612Q426 637 405 656Q384 675 347 685.5Q310 696 253 696Q227 696 204 694Q181 692 158.5 687.5Q136 683 113 675.5Q90 668 65 657V725Q96 739 141.5 749.5Q187 760 252 760Q328 760 379 744.5Q430 729 460.5 701.5Q491 674 503.5 637Q516 600 516 558Q516 531 507.5 505.5Q499 480 484 458.5Q469 437 448.5 420Q428 403 404 394Q436 387 461.5 370Q487 353 504.5 328.5Q522 304 531.5 274Q541 244 541 211Q541 166 527.5 125.5Q514 85 481.5 55Q449 25 395 7.5Q341 -10 260 -10Q195 -10 145.5 0.5Q96 11 65 25V93Q116 71 162 62.5Q208 54 261 54Z" glyph-name="three.LP" horiz-adv-x="600" /><glyph d="M223 448Q177 448 136 438.5Q95 429 65 418V482Q79 488 98.5 493Q118 498 140 501.5Q162 505 183.5 507.5Q205 510 223 510Q289 510 333.5 494Q378 478 405 450Q432 422 444 385.5Q456 349 456 308Q456 284 449 259Q442 234 429 212Q416 190 397 172Q378 154 354 144Q386 135 410 117Q434 99 449.5 74Q465 49 473 20Q481 -9 481 -39Q481 -83 468 -123Q455 -163 425.5 -193.5Q396 -224 348 -242Q300 -260 230 -260Q175 -260 133 -250.5Q91 -241 65 -228V-166Q93 -177 132.5 -187.5Q172 -198 230 -198Q284 -198 318.5 -184.5Q353 -171 372.5 -148.5Q392 -126 399.5 -97.5Q407 -69 407 -39Q407 37 367.5 71.5Q328 106 236 106H125V172H236Q278 172 306.5 184Q335 196 351.5 215.5Q368 235 375 259.5Q382 284 382 308Q382 336 375.5 361Q369 386 352 405.5Q335 425 303.5 436.5Q272 448 223 448Z" glyph-name="three.OP" horiz-adv-x="535" /><glyph glyph-name="threeperemspace" horiz-adv-x="333" unicode="&#8196;" /><glyph d="M690 308H60V374H690Z" glyph-name="threequarteremdash" horiz-adv-x="750" /><glyph d="M355 659Q374 659 384 664.5Q394 670 398.5 677.5Q403 685 403.5 694Q404 703 404 711L461 710Q465 657 435 628.5Q405 600 351 600Q320 600 301 610.5Q282 621 267.5 633Q253 645 239 655.5Q225 666 204 666Q185 666 175.5 660.5Q166 655 161.5 647.5Q157 640 156.5 630.5Q156 621 156 614L99 615Q95 668 124.5 696.5Q154 725 208 725Q239 725 258 714.5Q277 704 291.5 692Q306 680 320 669.5Q334 659 355 659Z" glyph-name="tilde" horiz-adv-x="602" unicode="&#732;" /><glyph d="M134 720V500H98V720H15V750H217V720ZM386 532 301 705 303 500H270V750H313L400 568L488 750H530V500H498L500 705L415 532Z" glyph-name="trademark" horiz-adv-x="575" unicode="&#8482;" /><glyph d="M465 560Q465 589 458.5 614Q452 639 433.5 657Q415 675 382 685.5Q349 696 297 696Q271 696 248 694Q225 692 202.5 687.5Q180 683 157 675.5Q134 668 109 657V725Q140 739 185.5 749.5Q231 760 296 760Q375 760 422 740.5Q469 721 494.5 691.5Q520 662 528 626.5Q536 591 536 560Q536 534 530.5 512.5Q525 491 513.5 470.5Q502 450 485 428.5Q468 407 444 383L140 65H536V0H50V46L418 447Q438 469 451.5 497.5Q465 526 465 560Z" glyph-name="two" horiz-adv-x="600" unicode="2" /><glyph d="M465 560Q465 589 458.5 614Q452 639 433.5 657Q415 675 382 685.5Q349 696 297 696Q271 696 248 694Q225 692 202.5 687.5Q180 683 157 675.5Q134 668 109 657V725Q140 739 185.5 749.5Q231 760 296 760Q375 760 422 740.5Q469 721 494.5 691.5Q520 662 528 626.5Q536 591 536 560Q536 534 530.5 512.5Q525 491 513.5 470.5Q502 450 485 428.5Q468 407 444 383L140 65H536V0H50V46L418 447Q438 469 451.5 497.5Q465 526 465 560Z" glyph-name="two.LP" horiz-adv-x="600" /><glyph d="M454 349Q454 302 430 265.5Q406 229 345 188L158 62H474V0H56V46L340 257Q366 277 376 299.5Q386 322 386 350Q386 404 351.5 426Q317 448 249 448Q201 448 160.5 438.5Q120 429 90 418V482Q104 488 123.5 493Q143 498 165 501.5Q187 505 208.5 507.5Q230 510 248 510Q306 510 345.5 497.5Q385 485 409 463Q433 441 443.5 412Q454 383 454 349Z" glyph-name="two.OP" horiz-adv-x="530" /><glyph d="M145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="u" horiz-adv-x="530" unicode="u" /><glyph d="M300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="u.smcp" horiz-adv-x="600" /><glyph d="M248 575 328 750H423L278 575ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="uacute" horiz-adv-x="530" unicode="&#250;" /><glyph d="M285 575 365 750H460L315 575ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="uacute.smcp" horiz-adv-x="600" /><glyph d="M263 585Q227 585 199 593.5Q171 602 151 621Q131 640 120 671.5Q109 703 108 750H149Q151 696 178 670.5Q205 645 263 645Q321 645 348 670.5Q375 696 377 750H418Q417 703 406 671.5Q395 640 375 621Q355 602 327 593.5Q299 585 263 585ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="ubreve" horiz-adv-x="530" unicode="&#365;" /><glyph d="M300 565Q264 565 236 575Q208 585 188 607Q168 629 157 664.5Q146 700 145 750H196Q197 716 202.5 692Q208 668 220 653Q232 638 251.5 631.5Q271 625 300 625Q358 625 380 653.5Q402 682 404 750H455Q454 700 443 664.5Q432 629 412 607Q392 585 364 575Q336 565 300 565ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="ubreve.smcp" horiz-adv-x="600" /><glyph d="M398 575 263 691 128 575H98L223 750H303L428 575ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="ucircumflex" horiz-adv-x="530" unicode="&#251;" /><glyph d="M435 575 300 691 165 575H135L260 750H340L465 575ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="ucircumflex.smcp" horiz-adv-x="600" /><glyph d="M418 685Q418 662 402 646Q386 630 363 630Q340 630 324 646Q308 662 308 685Q308 708 324 724Q340 740 363 740Q386 740 402 724Q418 708 418 685ZM163 630Q140 630 124 646Q108 662 108 685Q108 708 124 724Q140 740 163 740Q186 740 202 724Q218 708 218 685Q218 662 202 646Q186 630 163 630ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="udieresis" horiz-adv-x="530" unicode="&#252;" /><glyph d="M455 685Q455 662 439 646Q423 630 400 630Q377 630 361 646Q345 662 345 685Q345 708 361 724Q377 740 400 740Q423 740 439 724Q455 708 455 685ZM200 630Q177 630 161 646Q145 662 145 685Q145 708 161 724Q177 740 200 740Q223 740 239 724Q255 708 255 685Q255 662 239 646Q223 630 200 630ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="udieresis.smcp" horiz-adv-x="600" /><glyph d="M248 575 103 750H198L278 575ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="ugrave" horiz-adv-x="530" unicode="&#249;" /><glyph d="M285 575 140 750H235L315 575ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="ugrave.smcp" horiz-adv-x="600" /><glyph d="M178 575 258 750H353L208 575ZM328 575 408 750H503L358 575ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="uhungarumlaut" horiz-adv-x="530" unicode="&#369;" /><glyph d="M193 575 273 750H368L243 575ZM343 575 423 750H518L393 575ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="uhungarumlaut.smcp" horiz-adv-x="600" /><glyph d="M126 680V745H400V680ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="umacron" horiz-adv-x="530" unicode="&#363;" /><glyph d="M163 640H437V575H163ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="umacron.smcp" horiz-adv-x="600" /><glyph d="M320 -250H0V-184H320Z" glyph-name="underscore" horiz-adv-x="320" unicode="_" /><glyph d="M310 308H60V374H310Z" glyph-name="uni00AD" horiz-adv-x="370" unicode="&#173;" /><glyph d="M145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0Q401 -20 372.5 -51.5Q344 -83 344 -122Q344 -145 356.5 -162Q369 -179 398 -179Q415 -179 429.5 -176.5Q444 -174 455 -171V-219Q442 -224 424 -227Q406 -230 384 -230Q339 -230 311 -205Q283 -180 283 -135Q283 -91 308 -53Q333 -15 404 19L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="uogonek" horiz-adv-x="530" unicode="&#371;" /><glyph d="M515 160Q515 97 483.5 56Q452 15 385 -1Q337 -21 309.5 -52.5Q282 -84 282 -122Q282 -145 294.5 -162Q307 -179 336 -179Q353 -179 367.5 -176.5Q382 -174 393 -171V-219Q380 -224 362 -227Q344 -230 322 -230Q277 -230 249 -205Q221 -180 221 -135Q221 -56 291 -10Q183 -8 134 37Q85 82 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515Z" glyph-name="uogonek.smcp" horiz-adv-x="600" /><glyph d="M311 662Q311 690 298.5 700.5Q286 711 263 711Q240 711 227 700.5Q214 690 214 662Q214 635 227 624.5Q240 614 263 614Q286 614 298.5 624.5Q311 635 311 662ZM361 662Q361 611 334.5 588Q308 565 263 565Q218 565 191 588Q164 611 164 662Q164 714 191 737Q218 760 263 760Q308 760 334.5 737Q361 714 361 662ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="uring" horiz-adv-x="530" unicode="&#367;" /><glyph d="M348 662Q348 690 335.5 700.5Q323 711 300 711Q277 711 264 700.5Q251 690 251 662Q251 635 264 624.5Q277 614 300 614Q323 614 335.5 624.5Q348 635 348 662ZM398 662Q398 611 371.5 588Q345 565 300 565Q255 565 228 588Q201 611 201 662Q201 714 228 737Q255 760 300 760Q345 760 371.5 737Q398 714 398 662ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="uring.smcp" horiz-adv-x="600" /><glyph d="M338 659Q357 659 367 664.5Q377 670 381.5 677.5Q386 685 386.5 694Q387 703 387 711L444 710Q448 657 418 628.5Q388 600 334 600Q303 600 284 610.5Q265 621 250.5 633Q236 645 222 655.5Q208 666 187 666Q168 666 158.5 660.5Q149 655 144.5 647.5Q140 640 139.5 630.5Q139 621 139 614L82 615Q78 668 107.5 696.5Q137 725 191 725Q222 725 241 714.5Q260 704 274.5 692Q289 680 303 669.5Q317 659 338 659ZM145 134Q145 85 171.5 67Q198 49 245 49Q294 49 323 57Q352 65 380 81V500H450V0H411L396 42Q326 -10 245 -10Q155 -10 115 29Q75 68 75 140V500H145Z" glyph-name="utilde" horiz-adv-x="530" unicode="&#361;" /><glyph d="M375 659Q394 659 404 664.5Q414 670 418.5 677.5Q423 685 423.5 694Q424 703 424 711L481 710Q485 657 455 628.5Q425 600 371 600Q340 600 321 610.5Q302 621 287.5 633Q273 645 259 655.5Q245 666 224 666Q205 666 195.5 660.5Q186 655 181.5 647.5Q177 640 176.5 630.5Q176 621 176 614L119 615Q115 668 144.5 696.5Q174 725 228 725Q259 725 278 714.5Q297 704 311.5 692Q326 680 340 669.5Q354 659 375 659ZM300 -10Q188 -10 136.5 35Q85 80 85 160V500H155V160Q155 100 194.5 74.5Q234 49 300 49Q366 49 405.5 74.5Q445 100 445 160V500H515V160Q515 80 463.5 35Q412 -10 300 -10Z" glyph-name="utilde.smcp" horiz-adv-x="600" /><glyph d="M112 500 255 90 398 500H475L280 -10H230L35 500Z" glyph-name="v" horiz-adv-x="510" unicode="v" /><glyph d="M515 500 305 -10H255L45 500H120L280 71L440 500Z" glyph-name="v.smcp" horiz-adv-x="560" /><glyph d="M695 500 554 -10H502L365 425L228 -10H176L35 500H110L204 108L328 500H402L526 108L620 500Z" glyph-name="w" horiz-adv-x="730" unicode="w" /><glyph d="M725 500 579 -10H527L385 427L243 -10H191L45 500H122L219 103L348 500H422L551 103L648 500Z" glyph-name="w.smcp" horiz-adv-x="770" /><glyph d="M500 575 365 691 230 575H200L325 750H405L530 575ZM695 500 554 -10H502L365 425L228 -10H176L35 500H110L204 108L328 500H402L526 108L620 500Z" glyph-name="wcircumflex" horiz-adv-x="730" unicode="&#373;" /><glyph d="M520 575 385 691 250 575H220L345 750H425L550 575ZM725 500 579 -10H527L385 427L243 -10H191L45 500H122L219 103L348 500H422L551 103L648 500Z" glyph-name="wcircumflex.smcp" horiz-adv-x="770" /><glyph d="M35 0 220 257 45 500H128L260 304L392 500H475L300 257L485 0H402L260 210L118 0Z" glyph-name="x" horiz-adv-x="520" unicode="x" /><glyph d="M40 0 235 257 50 500H135L275 306L415 500H500L315 257L510 0H425L275 208L125 0Z" glyph-name="x.smcp" horiz-adv-x="550" /><glyph d="M115 500 255 90 395 500H475L187 -250H112L225 13L35 500Z" glyph-name="y" horiz-adv-x="510" unicode="y" /><glyph d="M537 500 325 180V0H258V180L45 500H128L291 230L454 500Z" glyph-name="y.smcp" horiz-adv-x="582" /><glyph d="M240 575 320 750H415L270 575ZM115 500 255 90 395 500H475L187 -250H112L225 13L35 500Z" glyph-name="yacute" horiz-adv-x="510" unicode="&#253;" /><glyph d="M276 575 356 750H451L306 575ZM537 500 325 180V0H258V180L45 500H128L291 230L454 500Z" glyph-name="yacute.smcp" horiz-adv-x="582" /><glyph d="M390 575 255 691 120 575H90L215 750H295L420 575ZM115 500 255 90 395 500H475L187 -250H112L225 13L35 500Z" glyph-name="ycircumflex" horiz-adv-x="510" unicode="&#375;" /><glyph d="M426 575 291 691 156 575H126L251 750H331L456 575ZM537 500 325 180V0H258V180L45 500H128L291 230L454 500Z" glyph-name="ycircumflex.smcp" horiz-adv-x="582" /><glyph d="M410 685Q410 662 394 646Q378 630 355 630Q332 630 316 646Q300 662 300 685Q300 708 316 724Q332 740 355 740Q378 740 394 724Q410 708 410 685ZM155 630Q132 630 116 646Q100 662 100 685Q100 708 116 724Q132 740 155 740Q178 740 194 724Q210 708 210 685Q210 662 194 646Q178 630 155 630ZM115 500 255 90 395 500H475L187 -250H112L225 13L35 500Z" glyph-name="ydieresis" horiz-adv-x="510" unicode="&#255;" /><glyph d="M446 685Q446 662 430 646Q414 630 391 630Q368 630 352 646Q336 662 336 685Q336 708 352 724Q368 740 391 740Q414 740 430 724Q446 708 446 685ZM191 630Q168 630 152 646Q136 662 136 685Q136 708 152 724Q168 740 191 740Q214 740 230 724Q246 708 246 685Q246 662 230 646Q214 630 191 630ZM537 500 325 180V0H258V180L45 500H128L291 230L454 500Z" glyph-name="ydieresis.smcp" horiz-adv-x="582" /><glyph d="M25 750H110L300 365L490 750H575L433 484H545V418H398L352 332H545V266H335V0H265V266H55V332H248L202 418H55V484H167Z" glyph-name="yen" horiz-adv-x="600" unicode="&#165;" /><glyph d="M507 118H325V0H258V118H76V184H255L211 250H76V316H167L45 500H128L291 230L454 500H537L415 316H507V250H371L327 184H507Z" glyph-name="yen.OP" horiz-adv-x="582" /><glyph d="M45 0V45L338 441H60V500H425V455L132 59H435V0Z" glyph-name="z" horiz-adv-x="480" unicode="z" /><glyph d="M60 0V45L380 438H75V500H470V455L150 62H480V0Z" glyph-name="z.smcp" horiz-adv-x="540" /><glyph d="M221 575 301 750H396L251 575ZM45 0V45L338 441H60V500H425V455L132 59H435V0Z" glyph-name="zacute" horiz-adv-x="480" unicode="&#378;" /><glyph d="M250 575 330 750H425L280 575ZM60 0V45L380 438H75V500H470V455L150 62H480V0Z" glyph-name="zacute.smcp" horiz-adv-x="540" /><glyph d="M401 750 276 575H196L71 750H121L236 634L351 750ZM45 0V45L338 441H60V500H425V455L132 59H435V0Z" glyph-name="zcaron" horiz-adv-x="480" unicode="&#382;" /><glyph d="M135 750 270 634 405 750H435L310 575H230L105 750ZM60 0V45L380 438H75V500H470V455L150 62H480V0Z" glyph-name="zcaron.smcp" horiz-adv-x="540" /><glyph d="M236 630Q213 630 197 646Q181 662 181 685Q181 708 197 724Q213 740 236 740Q259 740 275 724Q291 708 291 685Q291 662 275 646Q259 630 236 630ZM45 0V45L338 441H60V500H425V455L132 59H435V0Z" glyph-name="zdotaccent" horiz-adv-x="480" unicode="&#380;" /><glyph d="M270 630Q247 630 231 646Q215 662 215 685Q215 708 231 724Q247 740 270 740Q293 740 309 724Q325 708 325 685Q325 662 309 646Q293 630 270 630ZM60 0V45L380 438H75V500H470V455L150 62H480V0Z" glyph-name="zdotaccent.smcp" horiz-adv-x="540" /><glyph d="M460 375Q460 447 457 506.5Q454 566 438.5 608.5Q423 651 390.5 674.5Q358 698 300 698Q241 698 208.5 674.5Q176 651 161 608.5Q146 566 143 506.5Q140 447 140 375Q140 303 143 243.5Q146 184 161 141.5Q176 99 208.5 75.5Q241 52 300 52Q358 52 390.5 75.5Q423 99 438.5 141.5Q454 184 457 243.5Q460 303 460 375ZM530 375Q530 287 524.5 216Q519 145 496 94.5Q473 44 427 17Q381 -10 300 -10Q220 -10 174 17Q128 44 105 94.5Q82 145 76 216Q70 287 70 375Q70 463 76 534Q82 605 105 655.5Q128 706 174 733Q220 760 300 760Q381 760 427 733Q473 706 496 655.5Q519 605 524.5 534Q530 463 530 375Z" glyph-name="zero" horiz-adv-x="600" unicode="0" /><glyph d="M460 375Q460 447 457 506.5Q454 566 438.5 608.5Q423 651 390.5 674.5Q358 698 300 698Q241 698 208.5 674.5Q176 651 161 608.5Q146 566 143 506.5Q140 447 140 375Q140 303 143 243.5Q146 184 161 141.5Q176 99 208.5 75.5Q241 52 300 52Q358 52 390.5 75.5Q423 99 438.5 141.5Q454 184 457 243.5Q460 303 460 375ZM530 375Q530 287 524.5 216Q519 145 496 94.5Q473 44 427 17Q381 -10 300 -10Q220 -10 174 17Q128 44 105 94.5Q82 145 76 216Q70 287 70 375Q70 463 76 534Q82 605 105 655.5Q128 706 174 733Q220 760 300 760Q381 760 427 733Q473 706 496 655.5Q519 605 524.5 534Q530 463 530 375Z" glyph-name="zero.LP" horiz-adv-x="600" /><glyph d="M295 448Q218 448 180 402.5Q142 357 142 250Q142 143 180 97.5Q218 52 295 52Q372 52 415 97.5Q458 143 458 250Q458 356 415.5 402Q373 448 295 448ZM295 510Q409 510 469.5 447.5Q530 385 530 250Q530 116 469.5 53Q409 -10 295 -10Q181 -10 125.5 53Q70 116 70 250Q70 385 125.5 447.5Q181 510 295 510Z" glyph-name="zero.OP" horiz-adv-x="600" /><glyph glyph-name="zerowidthspace" horiz-adv-x="0" unicode="&#8203;" /></font></defs></svg>
\ No newline at end of file
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Book.ttf b/docs/source/_themes/ceph/static/font/ApexSans-Book.ttf
new file mode 100644 (file)
index 0000000..42a0084
Binary files /dev/null and b/docs/source/_themes/ceph/static/font/ApexSans-Book.ttf differ
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Book.woff b/docs/source/_themes/ceph/static/font/ApexSans-Book.woff
new file mode 100644 (file)
index 0000000..681a70e
Binary files /dev/null and b/docs/source/_themes/ceph/static/font/ApexSans-Book.woff differ
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Medium.eot b/docs/source/_themes/ceph/static/font/ApexSans-Medium.eot
new file mode 100644 (file)
index 0000000..e06fd21
Binary files /dev/null and b/docs/source/_themes/ceph/static/font/ApexSans-Medium.eot differ
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Medium.svg b/docs/source/_themes/ceph/static/font/ApexSans-Medium.svg
new file mode 100644 (file)
index 0000000..6c624ec
--- /dev/null
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" ><svg version="1.1" xmlns="http://www.w3.org/2000/svg"><defs><font horiz-adv-x="0" id="font"><font-face ascent="750" bbox="-58 -263 1350 1250" cap-height="750" descent="-250" font-family="Apex Sans Medium" font-stretch="normal" font-style="normal" font-weight="500" units-per-em="1000" x-height="500" /><missing-glyph d="M151 632V118L408 375ZM759 118V632L502 375ZM193 66H717L455 328ZM455 422 717 684H193ZM80 750H830V0H80Z" horiz-adv-x="910" /><glyph d="M343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="A" horiz-adv-x="685" unicode="A" /><glyph d="M499 438V585L405 438ZM-1 0 492 750H951V650H604V438H917V338H604V100H951V0H499V338H341L124 0Z" glyph-name="AE" horiz-adv-x="1020" unicode="&#198;" /><glyph d="M315 825 395 1000H516L371 825ZM343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Aacute" horiz-adv-x="685" unicode="&#193;" /><glyph d="M343 822Q307 822 277 832.5Q247 843 224.5 864.5Q202 886 189 919.5Q176 953 175 1000H242Q243 973 248 955.5Q253 938 264.5 927.5Q276 917 295 912.5Q314 908 343 908Q372 908 391 912.5Q410 917 421.5 927.5Q433 938 438 955.5Q443 973 444 1000H511Q510 953 497 919.5Q484 886 461.5 864.5Q439 843 409 832.5Q379 822 343 822ZM342 1072Q306 1072 276 1082.5Q246 1093 223.5 1114.5Q201 1136 188 1169.5Q175 1203 174 1250H241Q242 1223 247 1205.5Q252 1188 263.5 1177.5Q275 1167 294 1162.5Q313 1158 342 1158Q371 1158 390 1162.5Q409 1167 420.5 1177.5Q432 1188 437 1205.5Q442 1223 443 1250H510Q509 1203 496 1169.5Q483 1136 460.5 1114.5Q438 1093 408 1082.5Q378 1072 342 1072ZM343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Abreve" horiz-adv-x="685" unicode="&#258;" /><glyph d="M343 590 249 296H435ZM465 825 343 930 221 825H165L290 1000H396L521 825ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Acircumflex" horiz-adv-x="685" unicode="&#194;" /><glyph d="M383 917Q383 946 403.5 966.5Q424 987 453 987Q482 987 502.5 966.5Q523 946 523 917Q523 888 502.5 867.5Q482 847 453 847Q424 847 403.5 867.5Q383 888 383 917ZM163 917Q163 946 183.5 966.5Q204 987 233 987Q262 987 282.5 966.5Q303 946 303 917Q303 888 282.5 867.5Q262 847 233 847Q204 847 183.5 867.5Q163 888 163 917ZM343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Adieresis" horiz-adv-x="685" unicode="&#196;" /><glyph d="M315 825 170 1000H291L371 825ZM343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Agrave" horiz-adv-x="685" unicode="&#192;" /><glyph d="M206 891V982H480V891ZM205 1141V1232H479V1141ZM343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Amacron" horiz-adv-x="685" unicode="&#256;" /><glyph d="M343 590 249 296H435ZM40 0 306 763H379L645 0Q603 -19 573.5 -46Q544 -73 544 -110Q544 -133 558 -144.5Q572 -156 595 -156Q616 -156 632.5 -153Q649 -150 658 -147V-215Q646 -220 625.5 -225Q605 -230 573 -230Q550 -230 529 -224Q508 -218 491.5 -205Q475 -192 465 -172.5Q455 -153 455 -126Q455 -99 470 -66.5Q485 -34 530 -2L466 199H218L155 0Z" glyph-name="Aogonek" horiz-adv-x="685" unicode="&#260;" /><glyph d="M387 912Q387 938 375.5 947.5Q364 957 343 957Q321 957 310 947.5Q299 938 299 912Q299 888 310 878.5Q321 869 343 869Q364 869 375.5 878.5Q387 888 387 912ZM343 590 249 296H435ZM444 912Q444 860 416 836Q388 812 343 812Q297 812 269 836Q241 860 241 912Q241 965 269 989Q297 1013 343 1013Q388 1013 416 989Q444 965 444 912ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Aring" horiz-adv-x="685" unicode="&#197;" /><glyph d="M538 974Q537 834 413 837Q382 837 363 847Q344 857 330 868.5Q316 880 303.5 889.5Q291 899 273 899Q258 899 249 894Q240 889 235.5 881.5Q231 874 229.5 864Q228 854 228 844L148 851Q150 923 180 956.5Q210 990 273 989Q304 988 323 978Q342 968 356 956.5Q370 945 382.5 935.5Q395 926 413 926Q428 926 437 931Q446 936 450.5 944Q455 952 456.5 961.5Q458 971 458 981ZM343 590 249 296H435ZM40 0 306 763H379L645 0H530L466 199H218L155 0Z" glyph-name="Atilde" horiz-adv-x="685" unicode="&#195;" /><glyph d="M457 544Q457 564 452.5 583Q448 602 435 617Q422 632 397.5 641Q373 650 334 650H191V438H342Q376 438 398.5 447Q421 456 434 471Q447 486 452 505Q457 524 457 544ZM191 100H340Q384 100 411.5 110.5Q439 121 454.5 137.5Q470 154 475.5 175Q481 196 481 218Q481 281 450 309.5Q419 338 342 338H191ZM85 750H330Q397 750 443 733.5Q489 717 517.5 689Q546 661 558 623.5Q570 586 570 544Q570 502 551.5 462Q533 422 497 395Q548 370 571 321.5Q594 273 594 218Q594 173 580.5 133Q567 93 537 63.5Q507 34 457.5 17Q408 0 336 0H85Z" glyph-name="B" horiz-adv-x="649" unicode="B" /><glyph d="M70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q438 763 488.5 752Q539 741 574 726V619Q543 632 517 640.5Q491 649 467 654Q443 659 419 661Q395 663 370 663Q317 663 280.5 641Q244 619 221.5 580Q199 541 189 488.5Q179 436 179 375Q179 314 189 261.5Q199 209 221.5 170Q244 131 280.5 109Q317 87 370 87Q395 87 419 89Q443 91 467 96Q491 101 517 109.5Q543 118 574 131V24Q539 9 488.5 -2Q438 -13 370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Z" glyph-name="C" horiz-adv-x="644" unicode="C" /><glyph d="M334 825 414 1000H535L390 825ZM70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q438 763 488.5 752Q539 741 574 726V619Q543 632 517 640.5Q491 649 467 654Q443 659 419 661Q395 663 370 663Q317 663 280.5 641Q244 619 221.5 580Q199 541 189 488.5Q179 436 179 375Q179 314 189 261.5Q199 209 221.5 170Q244 131 280.5 109Q317 87 370 87Q395 87 419 89Q443 91 467 96Q491 101 517 109.5Q543 118 574 131V24Q539 9 488.5 -2Q438 -13 370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Z" glyph-name="Cacute" horiz-adv-x="644" unicode="&#262;" /><glyph d="M540 1000 415 825H309L184 1000H240L362 895L484 1000ZM70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q438 763 488.5 752Q539 741 574 726V619Q543 632 517 640.5Q491 649 467 654Q443 659 419 661Q395 663 370 663Q317 663 280.5 641Q244 619 221.5 580Q199 541 189 488.5Q179 436 179 375Q179 314 189 261.5Q199 209 221.5 170Q244 131 280.5 109Q317 87 370 87Q395 87 419 89Q443 91 467 96Q491 101 517 109.5Q543 118 574 131V24Q539 9 488.5 -2Q438 -13 370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Z" glyph-name="Ccaron" horiz-adv-x="644" unicode="&#268;" /><glyph d="M70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q438 763 488.5 752Q539 741 574 726V619Q543 632 517 640.5Q491 649 467 654Q443 659 419 661Q395 663 370 663Q317 663 280.5 641Q244 619 221.5 580Q199 541 189 488.5Q179 436 179 375Q179 314 189 261.5Q199 209 221.5 170Q244 131 280.5 109Q317 87 370 87Q395 87 419 89Q443 91 467 96Q491 101 517 109.5Q543 118 574 131V24Q542 10 497.5 0Q453 -10 394 -12Q384 -27 383 -42.5Q382 -58 405 -71Q430 -85 437 -101Q444 -117 441 -133Q438 -149 428.5 -162.5Q419 -176 410 -185H308Q318 -176 327 -165Q336 -154 340.5 -142.5Q345 -131 343.5 -121.5Q342 -112 332 -106Q314 -97 308 -84.5Q302 -72 303.5 -58.5Q305 -45 311.5 -32.5Q318 -20 325 -10Q253 -2 204 31Q155 64 125 116Q95 168 82.5 234.5Q70 301 70 375Z" glyph-name="Ccedilla" horiz-adv-x="644" unicode="&#199;" /><glyph d="M484 825 362 930 240 825H184L309 1000H415L540 825ZM70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q438 763 488.5 752Q539 741 574 726V619Q543 632 517 640.5Q491 649 467 654Q443 659 419 661Q395 663 370 663Q317 663 280.5 641Q244 619 221.5 580Q199 541 189 488.5Q179 436 179 375Q179 314 189 261.5Q199 209 221.5 170Q244 131 280.5 109Q317 87 370 87Q395 87 419 89Q443 91 467 96Q491 101 517 109.5Q543 118 574 131V24Q539 9 488.5 -2Q438 -13 370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Z" glyph-name="Ccircumflex" horiz-adv-x="644" unicode="&#264;" /><glyph d="M292 917Q292 946 312.5 966.5Q333 987 362 987Q391 987 411.5 966.5Q432 946 432 917Q432 888 411.5 867.5Q391 847 362 847Q333 847 312.5 867.5Q292 888 292 917ZM70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q438 763 488.5 752Q539 741 574 726V619Q543 632 517 640.5Q491 649 467 654Q443 659 419 661Q395 663 370 663Q317 663 280.5 641Q244 619 221.5 580Q199 541 189 488.5Q179 436 179 375Q179 314 189 261.5Q199 209 221.5 170Q244 131 280.5 109Q317 87 370 87Q395 87 419 89Q443 91 467 96Q491 101 517 109.5Q543 118 574 131V24Q539 9 488.5 -2Q438 -13 370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Z" glyph-name="Cdotaccent" horiz-adv-x="644" unicode="&#266;" /><glyph d="M190 100H302Q360 100 399.5 120.5Q439 141 462.5 177.5Q486 214 496 264.5Q506 315 506 375Q506 435 496 485.5Q486 536 462.5 572.5Q439 609 399.5 629.5Q360 650 302 650H190ZM85 0V750H298Q391 750 452.5 721.5Q514 693 550.5 642.5Q587 592 602 523.5Q617 455 617 375Q617 295 602 226.5Q587 158 550.5 107.5Q514 57 452.5 28.5Q391 0 298 0Z" glyph-name="D" horiz-adv-x="687" unicode="D" /><glyph d="M491 1000 366 825H260L135 1000H191L313 895L435 1000ZM190 100H302Q360 100 399.5 120.5Q439 141 462.5 177.5Q486 214 496 264.5Q506 315 506 375Q506 435 496 485.5Q486 536 462.5 572.5Q439 609 399.5 629.5Q360 650 302 650H190ZM85 0V750H298Q391 750 452.5 721.5Q514 693 550.5 642.5Q587 592 602 523.5Q617 455 617 375Q617 295 602 226.5Q587 158 550.5 107.5Q514 57 452.5 28.5Q391 0 298 0Z" glyph-name="Dcaron" horiz-adv-x="687" unicode="&#270;" /><glyph d="M220 418H355V336H220V100H332Q390 100 429.5 120.5Q469 141 492.5 177.5Q516 214 526 264.5Q536 315 536 375Q536 435 526 485.5Q516 536 492.5 572.5Q469 609 429.5 629.5Q390 650 332 650H220ZM328 750Q421 750 482.5 721.5Q544 693 580.5 642.5Q617 592 632 523.5Q647 455 647 375Q647 295 632 226.5Q617 158 580.5 107.5Q544 57 482.5 28.5Q421 0 328 0H115V336H25V418H115V750Z" glyph-name="Dcroat" horiz-adv-x="717" unicode="&#272;" /><glyph d="M67 250Q67 304 87.5 352Q108 400 144 436Q180 472 228 492.5Q276 513 330 513Q384 513 432 492.5Q480 472 516 436Q552 400 572.5 352Q593 304 593 250Q593 196 572.5 148Q552 100 516 64Q480 28 432 7.5Q384 -13 330 -13Q276 -13 228 7.5Q180 28 144 64Q108 100 87.5 148Q67 196 67 250Z" glyph-name="Delta" horiz-adv-x="660" unicode="&#8710;" /><glyph d="M503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="E" horiz-adv-x="606" unicode="E" /><glyph d="M283 825 363 1000H484L339 825ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Eacute" horiz-adv-x="606" unicode="&#201;" /><glyph d="M311 822Q275 822 245 832.5Q215 843 192.5 864.5Q170 886 157 919.5Q144 953 143 1000H210Q211 973 216 955.5Q221 938 232.5 927.5Q244 917 263 912.5Q282 908 311 908Q340 908 359 912.5Q378 917 389.5 927.5Q401 938 406 955.5Q411 973 412 1000H479Q478 953 465 919.5Q452 886 429.5 864.5Q407 843 377 832.5Q347 822 311 822ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Ebreve" horiz-adv-x="606" unicode="&#276;" /><glyph d="M189 1000 311 895 433 1000H489L364 825H258L133 1000ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Ecaron" horiz-adv-x="606" unicode="&#282;" /><glyph d="M433 825 311 930 189 825H133L258 1000H364L489 825ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Ecircumflex" horiz-adv-x="606" unicode="&#202;" /><glyph d="M131 917Q131 946 151.5 966.5Q172 987 201 987Q230 987 250.5 966.5Q271 946 271 917Q271 888 250.5 867.5Q230 847 201 847Q172 847 151.5 867.5Q131 888 131 917ZM351 917Q351 946 371.5 966.5Q392 987 421 987Q450 987 470.5 966.5Q491 946 491 917Q491 888 470.5 867.5Q450 847 421 847Q392 847 371.5 867.5Q351 888 351 917ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Edieresis" horiz-adv-x="606" unicode="&#203;" /><glyph d="M241 917Q241 946 261.5 966.5Q282 987 311 987Q340 987 360.5 966.5Q381 946 381 917Q381 888 360.5 867.5Q340 847 311 847Q282 847 261.5 867.5Q241 888 241 917ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Edotaccent" horiz-adv-x="606" unicode="&#278;" /><glyph d="M283 825 138 1000H259L339 825ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Egrave" horiz-adv-x="606" unicode="&#200;" /><glyph d="M174 891V982H448V891ZM503 338H190V100H536V0H85V750H536V650H190V438H503Z" glyph-name="Emacron" horiz-adv-x="606" unicode="&#274;" /><glyph d="M397 -168Q418 -165 439 -159.5Q460 -154 477 -143.5Q494 -133 505 -117Q516 -101 516 -79V28L185 544L187 0H85V750H168L517 206L515 750H617V-88Q617 -129 597 -160Q577 -191 545.5 -212.5Q514 -234 474.5 -246Q435 -258 397 -260Z" glyph-name="Eng" horiz-adv-x="702" unicode="&#330;" /><glyph d="M503 338H190V100H536V0Q494 -19 464.5 -46Q435 -73 435 -110Q435 -133 449 -144.5Q463 -156 486 -156Q507 -156 523.5 -153Q540 -150 549 -147V-215Q537 -220 516.5 -225Q496 -230 464 -230Q441 -230 420 -224Q399 -218 382.5 -205Q366 -192 356 -172.5Q346 -153 346 -126Q346 -98 362 -65Q378 -32 425 0H85V750H536V650H190V438H503Z" glyph-name="Eogonek" horiz-adv-x="606" unicode="&#280;" /><glyph d="M220 418H355V336H220V100H332Q390 100 429.5 120.5Q469 141 492.5 177.5Q516 214 526 264.5Q536 315 536 375Q536 435 526 485.5Q516 536 492.5 572.5Q469 609 429.5 629.5Q390 650 332 650H220ZM328 750Q421 750 482.5 721.5Q544 693 580.5 642.5Q617 592 632 523.5Q647 455 647 375Q647 295 632 226.5Q617 158 580.5 107.5Q544 57 482.5 28.5Q421 0 328 0H115V336H25V418H115V750Z" glyph-name="Eth" horiz-adv-x="717" unicode="&#208;" /><glyph d="M35 401V500H96Q103 564 120 613.5Q137 663 168 696Q199 729 246 746Q293 763 360 763Q395 763 436.5 758Q478 753 517 736V639Q480 651 443.5 658Q407 665 360 665Q286 665 250 623.5Q214 582 203 500H463L430 401H196V349H413L380 250H203Q214 167 250 126Q286 85 360 85Q407 85 443.5 92Q480 99 517 111V14Q478 -3 436.5 -8Q395 -13 360 -13Q293 -13 246 4Q199 21 168 54Q137 87 120 136Q103 185 96 250H35V349H90V401Z" glyph-name="Euro" horiz-adv-x="546" unicode="&#8364;" /><glyph d="M35 262V361H106Q128 428 181.5 470.5Q235 513 328 513Q363 513 404.5 508Q446 503 485 486V392Q448 404 411 411.5Q374 419 327 419Q286 419 259.5 403Q233 387 218 361H426L393 262H192V238H383L350 139H218Q233 113 259.5 97Q286 81 327 81Q374 81 411 88.5Q448 96 485 108V14Q446 -3 404.5 -8Q363 -13 328 -13Q235 -13 181.5 29Q128 71 106 139H35V238H90V262Z" glyph-name="Euro.OP" horiz-adv-x="546" /><glyph d="M494 338H191V0H85V750H528V650H191V438H494Z" glyph-name="F" horiz-adv-x="588" unicode="F" /><glyph d="M370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q446 763 500 749Q554 735 589 718V612Q555 626 527 635.5Q499 645 473 651Q447 657 422 660Q397 663 370 663Q316 663 279.5 641Q243 619 220.5 580Q198 541 188 488.5Q178 436 178 375Q178 314 188 261.5Q198 209 220.5 170Q243 131 279.5 109Q316 87 370 87Q403 87 440 90.5Q477 94 502 102V312H355V412H607V33Q564 13 506 0Q448 -13 370 -13Z" glyph-name="G" horiz-adv-x="692" unicode="G" /><glyph d="M366 822Q330 822 300 832.5Q270 843 247.5 864.5Q225 886 212 919.5Q199 953 198 1000H265Q266 973 271 955.5Q276 938 287.5 927.5Q299 917 318 912.5Q337 908 366 908Q395 908 414 912.5Q433 917 444.5 927.5Q456 938 461 955.5Q466 973 467 1000H534Q533 953 520 919.5Q507 886 484.5 864.5Q462 843 432 832.5Q402 822 366 822ZM370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q446 763 500 749Q554 735 589 718V612Q555 626 527 635.5Q499 645 473 651Q447 657 422 660Q397 663 370 663Q316 663 279.5 641Q243 619 220.5 580Q198 541 188 488.5Q178 436 178 375Q178 314 188 261.5Q198 209 220.5 170Q243 131 279.5 109Q316 87 370 87Q403 87 440 90.5Q477 94 502 102V312H355V412H607V33Q564 13 506 0Q448 -13 370 -13Z" glyph-name="Gbreve" horiz-adv-x="692" unicode="&#286;" /><glyph d="M488 825 366 930 244 825H188L313 1000H419L544 825ZM370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q446 763 500 749Q554 735 589 718V612Q555 626 527 635.5Q499 645 473 651Q447 657 422 660Q397 663 370 663Q316 663 279.5 641Q243 619 220.5 580Q198 541 188 488.5Q178 436 178 375Q178 314 188 261.5Q198 209 220.5 170Q243 131 279.5 109Q316 87 370 87Q403 87 440 90.5Q477 94 502 102V312H355V412H607V33Q564 13 506 0Q448 -13 370 -13Z" glyph-name="Gcircumflex" horiz-adv-x="692" unicode="&#284;" /><glyph d="M323 -250 337 -184Q318 -176 307 -159Q296 -142 296 -120Q296 -91 316.5 -70.5Q337 -50 366 -50Q395 -50 415.5 -70.5Q436 -91 436 -120Q436 -138 428 -154L384 -250ZM370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q446 763 500 749Q554 735 589 718V612Q555 626 527 635.5Q499 645 473 651Q447 657 422 660Q397 663 370 663Q316 663 279.5 641Q243 619 220.5 580Q198 541 188 488.5Q178 436 178 375Q178 314 188 261.5Q198 209 220.5 170Q243 131 279.5 109Q316 87 370 87Q403 87 440 90.5Q477 94 502 102V312H355V412H607V33Q564 13 506 0Q448 -13 370 -13Z" glyph-name="Gcommaaccent" horiz-adv-x="692" unicode="&#290;" /><glyph d="M296 917Q296 946 316.5 966.5Q337 987 366 987Q395 987 415.5 966.5Q436 946 436 917Q436 888 415.5 867.5Q395 847 366 847Q337 847 316.5 867.5Q296 888 296 917ZM370 -13Q284 -13 226.5 17.5Q169 48 134.5 101Q100 154 85 224.5Q70 295 70 375Q70 455 85 525.5Q100 596 134.5 649Q169 702 226.5 732.5Q284 763 370 763Q446 763 500 749Q554 735 589 718V612Q555 626 527 635.5Q499 645 473 651Q447 657 422 660Q397 663 370 663Q316 663 279.5 641Q243 619 220.5 580Q198 541 188 488.5Q178 436 178 375Q178 314 188 261.5Q198 209 220.5 170Q243 131 279.5 109Q316 87 370 87Q403 87 440 90.5Q477 94 502 102V312H355V412H607V33Q564 13 506 0Q448 -13 370 -13Z" glyph-name="Gdotaccent" horiz-adv-x="692" unicode="&#288;" /><glyph d="M504 0V338H190V0H85V750H190V438H504V750H609V0Z" glyph-name="H" horiz-adv-x="694" unicode="H" /><glyph d="M504 438V540H190V438ZM504 0V338H190V0H85V750H190V638H504V750H609V0Z" glyph-name="Hbar" horiz-adv-x="694" unicode="&#294;" /><glyph d="M469 825 347 930 225 825H169L294 1000H400L525 825ZM504 0V338H190V0H85V750H190V438H504V750H609V0Z" glyph-name="Hcircumflex" horiz-adv-x="694" unicode="&#292;" /><glyph d="M107 0V750H212V0Z" glyph-name="I" horiz-adv-x="319" unicode="I" /><glyph d="M364 98Q392 91 409.5 88.5Q427 86 450 86Q488 86 511.5 99Q535 112 535 147V750H640V149Q640 71 595 29Q550 -13 450 -13Q434 -13 411.5 -10Q389 -7 364 -1ZM107 0V750H212V0Z" glyph-name="IJ" horiz-adv-x="747" unicode="&#306;" /><glyph d="M132 825 212 1000H333L188 825ZM107 0V750H212V0Z" glyph-name="Iacute" horiz-adv-x="319" unicode="&#205;" /><glyph d="M160 822Q124 822 94 832.5Q64 843 41.5 864.5Q19 886 6 919.5Q-7 953 -8 1000H59Q60 973 65 955.5Q70 938 81.5 927.5Q93 917 112 912.5Q131 908 160 908Q189 908 208 912.5Q227 917 238.5 927.5Q250 938 255 955.5Q260 973 261 1000H328Q327 953 314 919.5Q301 886 278.5 864.5Q256 843 226 832.5Q196 822 160 822ZM107 0V750H212V0Z" glyph-name="Ibreve" horiz-adv-x="319" unicode="&#300;" /><glyph d="M282 825 160 930 38 825H-18L107 1000H213L338 825ZM107 0V750H212V0Z" glyph-name="Icircumflex" horiz-adv-x="319" unicode="&#206;" /><glyph d="M-20 917Q-20 946 0.5 966.5Q21 987 50 987Q79 987 99.5 966.5Q120 946 120 917Q120 888 99.5 867.5Q79 847 50 847Q21 847 0.5 867.5Q-20 888 -20 917ZM200 917Q200 946 220.5 966.5Q241 987 270 987Q299 987 319.5 966.5Q340 946 340 917Q340 888 319.5 867.5Q299 847 270 847Q241 847 220.5 867.5Q200 888 200 917ZM107 0V750H212V0Z" glyph-name="Idieresis" horiz-adv-x="319" unicode="&#207;" /><glyph d="M90 917Q90 946 110.5 966.5Q131 987 160 987Q189 987 209.5 966.5Q230 946 230 917Q230 888 209.5 867.5Q189 847 160 847Q131 847 110.5 867.5Q90 888 90 917ZM107 0V750H212V0Z" glyph-name="Idotaccent" horiz-adv-x="319" unicode="&#304;" /><glyph d="M132 825 -13 1000H108L188 825ZM107 0V750H212V0Z" glyph-name="Igrave" horiz-adv-x="319" unicode="&#204;" /><glyph d="M23 891V982H297V891ZM107 0V750H212V0Z" glyph-name="Imacron" horiz-adv-x="319" /><glyph d="M212 0Q199 -9 184.5 -20.5Q170 -32 158 -46Q146 -60 138.5 -76Q131 -92 131 -110Q131 -133 145 -144.5Q159 -156 182 -156Q203 -156 219.5 -153Q236 -150 245 -147V-215Q233 -220 212.5 -225Q192 -230 160 -230Q137 -230 116 -224Q95 -218 78.5 -205Q62 -192 52 -172.5Q42 -153 42 -126Q42 -113 44 -97.5Q46 -82 52.5 -66.5Q59 -51 72 -34.5Q85 -18 107 -2V750H212Z" glyph-name="Iogonek" horiz-adv-x="319" unicode="&#302;" /><glyph d="M355 974Q354 834 230 837Q199 837 180 847Q161 857 147 868.5Q133 880 120.5 889.5Q108 899 90 899Q75 899 66 894Q57 889 52.5 881.5Q48 874 46.5 864Q45 854 45 844L-35 851Q-33 923 -3 956.5Q27 990 90 989Q121 988 140 978Q159 968 173 956.5Q187 945 199.5 935.5Q212 926 230 926Q245 926 254 931Q263 936 267.5 944Q272 952 273.5 961.5Q275 971 275 981ZM107 0V750H212V0Z" glyph-name="Itilde" horiz-adv-x="319" unicode="&#296;" /><glyph d="M45 98Q73 91 90.5 88.5Q108 86 131 86Q169 86 192.5 99Q216 112 216 147V750H321V149Q321 71 276 29Q231 -13 131 -13Q115 -13 92.5 -10Q70 -7 45 -1Z" glyph-name="J" horiz-adv-x="428" unicode="J" /><glyph d="M391 825 269 930 147 825H91L216 1000H322L447 825ZM45 98Q73 91 90.5 88.5Q108 86 131 86Q169 86 192.5 99Q216 112 216 147V750H321V149Q321 71 276 29Q231 -13 131 -13Q115 -13 92.5 -10Q70 -7 45 -1Z" glyph-name="Jcircumflex" horiz-adv-x="428" unicode="&#308;" /><glyph d="M188 277V0H85V750H188V413L453 750H591L298 406L618 0H480L230 326Z" glyph-name="K" horiz-adv-x="648" unicode="K" /><glyph d="M276 -250 290 -184Q271 -176 260 -159Q249 -142 249 -120Q249 -91 269.5 -70.5Q290 -50 319 -50Q348 -50 368.5 -70.5Q389 -91 389 -120Q389 -138 381 -154L337 -250ZM188 277V0H85V750H188V413L453 750H591L298 406L618 0H480L230 326Z" glyph-name="Kcommaaccent" horiz-adv-x="648" unicode="&#310;" /><glyph d="M190 100H518V0H85V750H190Z" glyph-name="L" horiz-adv-x="568" unicode="L" /><glyph d="M110 825 190 1000H311L166 825ZM190 100H518V0H85V750H190Z" glyph-name="Lacute" horiz-adv-x="568" unicode="&#313;" /><glyph d="M364 750 316 550H260L276 750ZM190 100H518V0H85V750H190Z" glyph-name="Lcaron" horiz-adv-x="568" unicode="&#317;" /><glyph d="M256 -250 270 -184Q251 -176 240 -159Q229 -142 229 -120Q229 -91 249.5 -70.5Q270 -50 299 -50Q328 -50 348.5 -70.5Q369 -91 369 -120Q369 -138 361 -154L317 -250ZM190 100H518V0H85V750H190Z" glyph-name="Lcommaaccent" horiz-adv-x="568" unicode="&#315;" /><glyph d="M352 375Q352 404 372.5 424.5Q393 445 422 445Q451 445 471.5 424.5Q492 404 492 375Q492 346 471.5 325.5Q451 305 422 305Q393 305 372.5 325.5Q352 346 352 375ZM190 100H518V0H85V750H190Z" glyph-name="Ldot" horiz-adv-x="568" unicode="&#319;" /><glyph d="M220 297V100H548V0H115V244L25 198V287L115 333V750H220V386L442 500V411Z" glyph-name="Lslash" horiz-adv-x="598" unicode="&#321;" /><glyph d="M472 100H392L183 583L188 0H85V750H212L432 236L652 750H779V0H676L681 583Z" glyph-name="M" horiz-adv-x="864" unicode="M" /><glyph d="M534 0 185 544 187 0H85V750H168L517 206L515 750H617V0Z" glyph-name="N" horiz-adv-x="702" unicode="N" /><glyph d="M323 825 403 1000H524L379 825ZM534 0 185 544 187 0H85V750H168L517 206L515 750H617V0Z" glyph-name="Nacute" horiz-adv-x="702" unicode="&#323;" /><glyph d="M529 1000 404 825H298L173 1000H229L351 895L473 1000ZM534 0 185 544 187 0H85V750H168L517 206L515 750H617V0Z" glyph-name="Ncaron" horiz-adv-x="702" unicode="&#327;" /><glyph d="M308 -250 322 -184Q303 -176 292 -159Q281 -142 281 -120Q281 -91 301.5 -70.5Q322 -50 351 -50Q380 -50 400.5 -70.5Q421 -91 421 -120Q421 -138 413 -154L369 -250ZM534 0 185 544 187 0H85V750H168L517 206L515 750H617V0Z" glyph-name="Ncommaaccent" horiz-adv-x="702" unicode="&#325;" /><glyph d="M546 974Q545 834 421 837Q390 837 371 847Q352 857 338 868.5Q324 880 311.5 889.5Q299 899 281 899Q266 899 257 894Q248 889 243.5 881.5Q239 874 237.5 864Q236 854 236 844L156 851Q158 923 188 956.5Q218 990 281 989Q312 988 331 978Q350 968 364 956.5Q378 945 390.5 935.5Q403 926 421 926Q436 926 445 931Q454 936 458.5 944Q463 952 464.5 961.5Q466 971 466 981ZM534 0 185 544 187 0H85V750H168L517 206L515 750H617V0Z" glyph-name="Ntilde" horiz-adv-x="702" unicode="&#209;" /><glyph d="M532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="O" horiz-adv-x="710" unicode="O" /><glyph d="M447 650H385Q326 650 287 629.5Q248 609 224.5 572.5Q201 536 191 485.5Q181 435 181 375Q181 315 191 264.5Q201 214 224.5 177.5Q248 141 287 120.5Q326 100 385 100H447ZM825 338H552V100H858V0H389Q296 0 234.5 28.5Q173 57 136.5 107.5Q100 158 85 226.5Q70 295 70 375Q70 455 85 523.5Q100 592 136.5 642.5Q173 693 234.5 721.5Q296 750 389 750H858V650H552V438H825Z" glyph-name="OE" horiz-adv-x="928" unicode="&#338;" /><glyph d="M327 825 407 1000H528L383 825ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Oacute" horiz-adv-x="710" unicode="&#211;" /><glyph d="M355 822Q319 822 289 832.5Q259 843 236.5 864.5Q214 886 201 919.5Q188 953 187 1000H254Q255 973 260 955.5Q265 938 276.5 927.5Q288 917 307 912.5Q326 908 355 908Q384 908 403 912.5Q422 917 433.5 927.5Q445 938 450 955.5Q455 973 456 1000H523Q522 953 509 919.5Q496 886 473.5 864.5Q451 843 421 832.5Q391 822 355 822ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Obreve" horiz-adv-x="710" unicode="&#334;" /><glyph d="M477 825 355 930 233 825H177L302 1000H408L533 825ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Ocircumflex" horiz-adv-x="710" unicode="&#212;" /><glyph d="M395 917Q395 946 415.5 966.5Q436 987 465 987Q494 987 514.5 966.5Q535 946 535 917Q535 888 514.5 867.5Q494 847 465 847Q436 847 415.5 867.5Q395 888 395 917ZM175 917Q175 946 195.5 966.5Q216 987 245 987Q274 987 294.5 966.5Q315 946 315 917Q315 888 294.5 867.5Q274 847 245 847Q216 847 195.5 867.5Q175 888 175 917ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Odieresis" horiz-adv-x="710" unicode="&#214;" /><glyph d="M327 825 182 1000H303L383 825ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Ograve" horiz-adv-x="710" unicode="&#210;" /><glyph d="M413 825 493 1000H614L469 825ZM236 825 316 1000H438L293 825ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Ohungarumlaut" horiz-adv-x="710" unicode="&#336;" /><glyph d="M218 891V982H492V891ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Omacron" horiz-adv-x="710" unicode="&#332;" /><glyph d="M229 459Q260 459 288 463.5Q316 468 343 475V382Q309 374 280 370.5Q251 367 229 367Q135 367 94 417.5Q53 468 53 565Q53 662 94 712.5Q135 763 229 763Q251 763 280 759.5Q309 756 343 748V655Q316 662 288 666.5Q260 671 229 671Q185 671 167 647Q149 623 149 565Q149 507 167 483Q185 459 229 459Z" glyph-name="Omega" horiz-adv-x="396" unicode="&#8486;" /><glyph d="M532 375Q532 416 529.5 453.5Q527 491 519 524L261 110Q298 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM178 375Q178 332 181 292.5Q184 253 192 218L453 636Q434 649 410 656Q386 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375ZM592 642Q622 587 631 517.5Q640 448 640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q307 -13 270 -4.5Q233 4 204 20L192 0H56L120 103Q88 158 79 229Q70 300 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q404 763 442.5 753.5Q481 744 510 727L524 750H660Z" glyph-name="Oslash" horiz-adv-x="710" unicode="&#216;" /><glyph d="M550 974Q549 834 425 837Q394 837 375 847Q356 857 342 868.5Q328 880 315.5 889.5Q303 899 285 899Q270 899 261 894Q252 889 247.5 881.5Q243 874 241.5 864Q240 854 240 844L160 851Q162 923 192 956.5Q222 990 285 989Q316 988 335 978Q354 968 368 956.5Q382 945 394.5 935.5Q407 926 425 926Q440 926 449 931Q458 936 462.5 944Q467 952 468.5 961.5Q470 971 470 981ZM532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM640 375Q640 301 631 231.5Q622 162 592 107.5Q562 53 505.5 20Q449 -13 355 -13Q261 -13 204.5 20Q148 53 118 107.5Q88 162 79 231.5Q70 301 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Z" glyph-name="Otilde" horiz-adv-x="710" unicode="&#213;" /><glyph d="M191 385H326Q371 385 398.5 397.5Q426 410 441 429Q456 448 461.5 471.5Q467 495 467 518Q467 540 461.5 563.5Q456 587 441 606.5Q426 626 398.5 638Q371 650 326 650H191ZM191 0H85V750H323Q395 750 444.5 731Q494 712 524 679.5Q554 647 567.5 605Q581 563 581 518Q581 472 567.5 430Q554 388 524 355.5Q494 323 444.5 304Q395 285 323 285H191Z" glyph-name="P" horiz-adv-x="626" unicode="P" /><glyph d="M532 375Q532 433 526 485.5Q520 538 501.5 577.5Q483 617 448 640Q413 663 355 663Q297 663 262 640Q227 617 208.5 577.5Q190 538 184 485.5Q178 433 178 375Q178 317 184 264.5Q190 212 208.5 172.5Q227 133 262 110Q297 87 355 87Q413 87 448 110Q483 133 501.5 172.5Q520 212 526 264.5Q532 317 532 375ZM514 26 738 -14V-124L265 -1Q201 16 162.5 53Q124 90 103.5 140Q83 190 76.5 250.5Q70 311 70 375Q70 448 79 518Q88 588 118 642.5Q148 697 204.5 730Q261 763 355 763Q449 763 505.5 730Q562 697 592 642.5Q622 588 631 518Q640 448 640 375Q640 321 635.5 269Q631 217 617.5 171Q604 125 579 88Q554 51 514 27Z" glyph-name="Q" horiz-adv-x="710" unicode="Q" /><glyph d="M190 412H341Q382 412 407 421.5Q432 431 445.5 447Q459 463 463 485Q467 507 467 532Q467 556 463 577.5Q459 599 445.5 615Q432 631 407 640.5Q382 650 341 650H190ZM190 0H85V750H327Q399 750 448 735Q497 720 526 691.5Q555 663 567.5 622.5Q580 582 580 532Q580 458 553 406.5Q526 355 456 330Q477 291 499.5 249Q522 207 544.5 164.5Q567 122 589 80Q611 38 631 0H501L348 312H190Z" glyph-name="R" horiz-adv-x="671" unicode="R" /><glyph d="M287 825 367 1000H488L343 825ZM190 412H341Q382 412 407 421.5Q432 431 445.5 447Q459 463 463 485Q467 507 467 532Q467 556 463 577.5Q459 599 445.5 615Q432 631 407 640.5Q382 650 341 650H190ZM190 0H85V750H327Q399 750 448 735Q497 720 526 691.5Q555 663 567.5 622.5Q580 582 580 532Q580 458 553 406.5Q526 355 456 330Q477 291 499.5 249Q522 207 544.5 164.5Q567 122 589 80Q611 38 631 0H501L348 312H190Z" glyph-name="Racute" horiz-adv-x="671" unicode="&#340;" /><glyph d="M493 1000 368 825H262L137 1000H193L315 895L437 1000ZM190 412H341Q382 412 407 421.5Q432 431 445.5 447Q459 463 463 485Q467 507 467 532Q467 556 463 577.5Q459 599 445.5 615Q432 631 407 640.5Q382 650 341 650H190ZM190 0H85V750H327Q399 750 448 735Q497 720 526 691.5Q555 663 567.5 622.5Q580 582 580 532Q580 458 553 406.5Q526 355 456 330Q477 291 499.5 249Q522 207 544.5 164.5Q567 122 589 80Q611 38 631 0H501L348 312H190Z" glyph-name="Rcaron" horiz-adv-x="671" unicode="&#344;" /><glyph d="M292 -250 306 -184Q287 -176 276 -159Q265 -142 265 -120Q265 -91 285.5 -70.5Q306 -50 335 -50Q364 -50 384.5 -70.5Q405 -91 405 -120Q405 -138 397 -154L353 -250ZM190 412H341Q382 412 407 421.5Q432 431 445.5 447Q459 463 463 485Q467 507 467 532Q467 556 463 577.5Q459 599 445.5 615Q432 631 407 640.5Q382 650 341 650H190ZM190 0H85V750H327Q399 750 448 735Q497 720 526 691.5Q555 663 567.5 622.5Q580 582 580 532Q580 458 553 406.5Q526 355 456 330Q477 291 499.5 249Q522 207 544.5 164.5Q567 122 589 80Q611 38 631 0H501L348 312H190Z" glyph-name="Rcommaaccent" horiz-adv-x="671" unicode="&#342;" /><glyph d="M323 663Q276 663 246 653Q216 643 199.5 626.5Q183 610 177 588Q171 566 171 542Q171 512 181.5 493.5Q192 475 212 464.5Q232 454 261 448.5Q290 443 328 438Q385 431 429 417.5Q473 404 504 379.5Q535 355 551 317Q567 279 567 224Q567 184 554.5 142Q542 100 510.5 65.5Q479 31 423.5 9Q368 -13 283 -13Q257 -13 227.5 -9.5Q198 -6 168.5 0Q139 6 111.5 14.5Q84 23 64 32V135Q90 125 112 116Q134 107 158.5 100.5Q183 94 213 90.5Q243 87 284 87Q334 87 367.5 98Q401 109 421 127.5Q441 146 449 171Q457 196 457 224Q457 254 449 274Q441 294 423.5 306.5Q406 319 377.5 326Q349 333 309 338H310Q242 347 195.5 362Q149 377 119.5 401Q90 425 77 460Q64 495 64 543Q64 577 74 615.5Q84 654 112.5 687Q141 720 191.5 741.5Q242 763 323 763Q391 763 442 751Q493 739 528 722V616Q497 630 471 639Q445 648 420.5 653.5Q396 659 372 661Q348 663 323 663Z" glyph-name="S" horiz-adv-x="631" unicode="S" /><glyph d="M290 825 370 1000H491L346 825ZM323 663Q276 663 246 653Q216 643 199.5 626.5Q183 610 177 588Q171 566 171 542Q171 512 181.5 493.5Q192 475 212 464.5Q232 454 261 448.5Q290 443 328 438Q385 431 429 417.5Q473 404 504 379.5Q535 355 551 317Q567 279 567 224Q567 184 554.5 142Q542 100 510.5 65.5Q479 31 423.5 9Q368 -13 283 -13Q257 -13 227.5 -9.5Q198 -6 168.5 0Q139 6 111.5 14.5Q84 23 64 32V135Q90 125 112 116Q134 107 158.5 100.5Q183 94 213 90.5Q243 87 284 87Q334 87 367.5 98Q401 109 421 127.5Q441 146 449 171Q457 196 457 224Q457 254 449 274Q441 294 423.5 306.5Q406 319 377.5 326Q349 333 309 338H310Q242 347 195.5 362Q149 377 119.5 401Q90 425 77 460Q64 495 64 543Q64 577 74 615.5Q84 654 112.5 687Q141 720 191.5 741.5Q242 763 323 763Q391 763 442 751Q493 739 528 722V616Q497 630 471 639Q445 648 420.5 653.5Q396 659 372 661Q348 663 323 663Z" glyph-name="Sacute" horiz-adv-x="631" unicode="&#346;" /><glyph d="M496 1000 371 825H265L140 1000H196L318 895L440 1000ZM323 663Q276 663 246 653Q216 643 199.5 626.5Q183 610 177 588Q171 566 171 542Q171 512 181.5 493.5Q192 475 212 464.5Q232 454 261 448.5Q290 443 328 438Q385 431 429 417.5Q473 404 504 379.5Q535 355 551 317Q567 279 567 224Q567 184 554.5 142Q542 100 510.5 65.5Q479 31 423.5 9Q368 -13 283 -13Q257 -13 227.5 -9.5Q198 -6 168.5 0Q139 6 111.5 14.5Q84 23 64 32V135Q90 125 112 116Q134 107 158.5 100.5Q183 94 213 90.5Q243 87 284 87Q334 87 367.5 98Q401 109 421 127.5Q441 146 449 171Q457 196 457 224Q457 254 449 274Q441 294 423.5 306.5Q406 319 377.5 326Q349 333 309 338H310Q242 347 195.5 362Q149 377 119.5 401Q90 425 77 460Q64 495 64 543Q64 577 74 615.5Q84 654 112.5 687Q141 720 191.5 741.5Q242 763 323 763Q391 763 442 751Q493 739 528 722V616Q497 630 471 639Q445 648 420.5 653.5Q396 659 372 661Q348 663 323 663Z" glyph-name="Scaron" horiz-adv-x="631" unicode="&#352;" /><glyph d="M239 -185Q249 -176 258 -165Q267 -154 271.5 -142.5Q276 -131 274.5 -121.5Q273 -112 263 -106Q246 -97 240 -84.5Q234 -72 235 -59.5Q236 -47 242 -34.5Q248 -22 255 -12Q231 -10 204.5 -6Q178 -2 152.5 3.5Q127 9 104.5 16.5Q82 24 64 32V135Q90 125 112 116Q134 107 158.5 100.5Q183 94 213 90.5Q243 87 284 87Q334 87 367.5 98Q401 109 421 127.5Q441 146 449 171Q457 196 457 224Q457 254 449 274Q441 294 423.5 306.5Q406 319 377.5 326Q349 333 309 338H310Q242 347 195.5 362Q149 377 119.5 401Q90 425 77 460Q64 495 64 543Q64 577 74 615.5Q84 654 112.5 687Q141 720 191.5 741.5Q242 763 323 763Q391 763 442 751Q493 739 528 722V616Q497 630 471 639Q445 648 420.5 653.5Q396 659 372 661Q348 663 323 663Q276 663 246 653Q216 643 199.5 626.5Q183 610 177 588Q171 566 171 542Q171 512 181.5 493.5Q192 475 212 464.5Q232 454 261 448.5Q290 443 328 438Q385 431 429 417.5Q473 404 504 379.5Q535 355 551 317Q567 279 567 224Q567 187 556.5 147.5Q546 108 518.5 75Q491 42 444.5 18.5Q398 -5 326 -11Q321 -19 318 -26.5Q315 -34 315 -41.5Q315 -49 320 -56.5Q325 -64 336 -71Q361 -85 368 -101Q375 -117 372 -133Q369 -149 359.5 -162.5Q350 -176 341 -185Z" glyph-name="Scedilla" horiz-adv-x="631" unicode="&#350;" /><glyph d="M440 825 318 930 196 825H140L265 1000H371L496 825ZM323 663Q276 663 246 653Q216 643 199.5 626.5Q183 610 177 588Q171 566 171 542Q171 512 181.5 493.5Q192 475 212 464.5Q232 454 261 448.5Q290 443 328 438Q385 431 429 417.5Q473 404 504 379.5Q535 355 551 317Q567 279 567 224Q567 184 554.5 142Q542 100 510.5 65.5Q479 31 423.5 9Q368 -13 283 -13Q257 -13 227.5 -9.5Q198 -6 168.5 0Q139 6 111.5 14.5Q84 23 64 32V135Q90 125 112 116Q134 107 158.5 100.5Q183 94 213 90.5Q243 87 284 87Q334 87 367.5 98Q401 109 421 127.5Q441 146 449 171Q457 196 457 224Q457 254 449 274Q441 294 423.5 306.5Q406 319 377.5 326Q349 333 309 338H310Q242 347 195.5 362Q149 377 119.5 401Q90 425 77 460Q64 495 64 543Q64 577 74 615.5Q84 654 112.5 687Q141 720 191.5 741.5Q242 763 323 763Q391 763 442 751Q493 739 528 722V616Q497 630 471 639Q445 648 420.5 653.5Q396 659 372 661Q348 663 323 663Z" glyph-name="Scircumflex" horiz-adv-x="631" unicode="&#348;" /><glyph d="M241 -250 255 -184Q236 -176 225 -159Q214 -142 214 -120Q214 -91 234.5 -70.5Q255 -50 284 -50Q313 -50 333.5 -70.5Q354 -91 354 -120Q354 -138 346 -154L302 -250ZM323 663Q276 663 246 653Q216 643 199.5 626.5Q183 610 177 588Q171 566 171 542Q171 512 181.5 493.5Q192 475 212 464.5Q232 454 261 448.5Q290 443 328 438Q385 431 429 417.5Q473 404 504 379.5Q535 355 551 317Q567 279 567 224Q567 184 554.5 142Q542 100 510.5 65.5Q479 31 423.5 9Q368 -13 283 -13Q257 -13 227.5 -9.5Q198 -6 168.5 0Q139 6 111.5 14.5Q84 23 64 32V135Q90 125 112 116Q134 107 158.5 100.5Q183 94 213 90.5Q243 87 284 87Q334 87 367.5 98Q401 109 421 127.5Q441 146 449 171Q457 196 457 224Q457 254 449 274Q441 294 423.5 306.5Q406 319 377.5 326Q349 333 309 338H310Q242 347 195.5 362Q149 377 119.5 401Q90 425 77 460Q64 495 64 543Q64 577 74 615.5Q84 654 112.5 687Q141 720 191.5 741.5Q242 763 323 763Q391 763 442 751Q493 739 528 722V616Q497 630 471 639Q445 648 420.5 653.5Q396 659 372 661Q348 663 323 663Z" glyph-name="Scommaaccent" horiz-adv-x="631" unicode="&#536;" /><glyph d="M345 650V0H240V650H42V750H543V650Z" glyph-name="T" horiz-adv-x="585" unicode="T" /><glyph d="M471 339H345V0H240V339H114V437H240V650H42V750H543V650H345V437H471Z" glyph-name="Tbar" horiz-adv-x="585" unicode="&#358;" /><glyph d="M470 1000 345 825H239L114 1000H170L292 895L414 1000ZM345 650V0H240V650H42V750H543V650Z" glyph-name="Tcaron" horiz-adv-x="585" unicode="&#356;" /><glyph d="M250 -250 264 -184Q245 -176 234 -159Q223 -142 223 -120Q223 -91 243.5 -70.5Q264 -50 293 -50Q322 -50 342.5 -70.5Q363 -91 363 -120Q363 -138 355 -154L311 -250ZM345 650V0H240V650H42V750H543V650Z" glyph-name="Tcommaaccent" horiz-adv-x="585" unicode="&#354;" /><glyph d="M191 242H326Q371 242 398.5 254.5Q426 267 441 286Q456 305 461.5 328.5Q467 352 467 375Q467 397 461.5 420.5Q456 444 441 463.5Q426 483 398.5 495Q371 507 326 507H191ZM191 0H85V750H191V607H323Q395 607 444.5 588Q494 569 524 536.5Q554 504 567.5 462Q581 420 581 375Q581 329 567.5 287Q554 245 524 212.5Q494 180 444.5 161Q395 142 323 142H191Z" glyph-name="Thorn" horiz-adv-x="626" unicode="&#222;" /><glyph d="M351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="U" horiz-adv-x="702" unicode="U" /><glyph d="M323 825 403 1000H524L379 825ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Uacute" horiz-adv-x="702" unicode="&#218;" /><glyph d="M351 822Q315 822 285 832.5Q255 843 232.5 864.5Q210 886 197 919.5Q184 953 183 1000H250Q251 973 256 955.5Q261 938 272.5 927.5Q284 917 303 912.5Q322 908 351 908Q380 908 399 912.5Q418 917 429.5 927.5Q441 938 446 955.5Q451 973 452 1000H519Q518 953 505 919.5Q492 886 469.5 864.5Q447 843 417 832.5Q387 822 351 822ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Ubreve" horiz-adv-x="702" unicode="&#364;" /><glyph d="M473 825 351 930 229 825H173L298 1000H404L529 825ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Ucircumflex" horiz-adv-x="702" unicode="&#219;" /><glyph d="M171 917Q171 946 191.5 966.5Q212 987 241 987Q270 987 290.5 966.5Q311 946 311 917Q311 888 290.5 867.5Q270 847 241 847Q212 847 191.5 867.5Q171 888 171 917ZM391 917Q391 946 411.5 966.5Q432 987 461 987Q490 987 510.5 966.5Q531 946 531 917Q531 888 510.5 867.5Q490 847 461 847Q432 847 411.5 867.5Q391 888 391 917ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Udieresis" horiz-adv-x="702" unicode="&#220;" /><glyph d="M323 825 178 1000H299L379 825ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Ugrave" horiz-adv-x="702" unicode="&#217;" /><glyph d="M409 825 489 1000H610L465 825ZM232 825 312 1000H434L289 825ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Uhungarumlaut" horiz-adv-x="702" unicode="&#368;" /><glyph d="M214 891V982H488V891ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Umacron" horiz-adv-x="702" unicode="&#362;" /><glyph d="M351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 181 607 140Q592 99 567 70.5Q542 42 511 25.5Q480 9 448 0Q406 -19 377 -46Q348 -73 348 -110Q348 -133 362 -144.5Q376 -156 399 -156Q420 -156 436.5 -153Q453 -150 462 -147V-215Q450 -220 429.5 -225Q409 -230 377 -230Q354 -230 333 -224Q312 -218 295.5 -205Q279 -192 269 -172.5Q259 -153 259 -126Q259 -101 271.5 -71.5Q284 -42 322 -12Q286 -9 243.5 2.5Q201 14 165 42Q129 70 104.5 117.5Q80 165 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Uogonek" horiz-adv-x="702" unicode="&#370;" /><glyph d="M395 912Q395 938 383.5 947.5Q372 957 351 957Q329 957 318 947.5Q307 938 307 912Q307 888 318 878.5Q329 869 351 869Q372 869 383.5 878.5Q395 888 395 912ZM452 912Q452 860 424 836Q396 812 351 812Q305 812 277 836Q249 860 249 912Q249 965 277 989Q305 1013 351 1013Q396 1013 424 989Q452 965 452 912ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Uring" horiz-adv-x="702" unicode="&#366;" /><glyph d="M546 974Q545 834 421 837Q390 837 371 847Q352 857 338 868.5Q324 880 311.5 889.5Q299 899 281 899Q266 899 257 894Q248 889 243.5 881.5Q239 874 237.5 864Q236 854 236 844L156 851Q158 923 188 956.5Q218 990 281 989Q312 988 331 978Q350 968 364 956.5Q378 945 390.5 935.5Q403 926 421 926Q436 926 445 931Q454 936 458.5 944Q463 952 464.5 961.5Q466 971 466 981ZM351 88Q372 88 401 92Q430 96 455.5 112.5Q481 129 499 162Q517 195 517 252V750H622V239Q622 184 608.5 144.5Q595 105 572.5 77Q550 49 521 31.5Q492 14 462 4Q432 -6 403 -9.5Q374 -13 351 -13Q328 -13 299 -9.5Q270 -6 240 4Q210 14 181 31.5Q152 49 129.5 77Q107 105 93.5 145Q80 185 80 239V750H185V252Q185 195 203 162Q221 129 246.5 112.5Q272 96 301 92Q330 88 351 88Z" glyph-name="Utilde" horiz-adv-x="702" unicode="&#360;" /><glyph d="M530 750H645L379 -13H306L40 750H155L343 163Z" glyph-name="V" horiz-adv-x="685" unicode="V" /><glyph d="M403 750H504L650 180L755 750H868L702 -13H605L454 580L302 -13H206L40 750H153L258 181Z" glyph-name="W" horiz-adv-x="908" unicode="W" /><glyph d="M576 825 454 930 332 825H276L401 1000H507L632 825ZM403 750H504L650 180L755 750H868L702 -13H605L454 580L302 -13H206L40 750H153L258 181Z" glyph-name="Wcircumflex" horiz-adv-x="908" unicode="&#372;" /><glyph d="M524 750H652L409 380L657 0H526L346 300L166 0H35L283 380L40 750H168L346 457Z" glyph-name="X" horiz-adv-x="692" unicode="X" /><glyph d="M415 309V0H311V309L40 750H166L363 400L560 750H686Z" glyph-name="Y" horiz-adv-x="726" unicode="Y" /><glyph d="M335 825 415 1000H536L391 825ZM415 309V0H311V309L40 750H166L363 400L560 750H686Z" glyph-name="Yacute" horiz-adv-x="726" unicode="&#221;" /><glyph d="M485 825 363 930 241 825H185L310 1000H416L541 825ZM415 309V0H311V309L40 750H166L363 400L560 750H686Z" glyph-name="Ycircumflex" horiz-adv-x="726" unicode="&#374;" /><glyph d="M183 917Q183 946 203.5 966.5Q224 987 253 987Q282 987 302.5 966.5Q323 946 323 917Q323 888 302.5 867.5Q282 847 253 847Q224 847 203.5 867.5Q183 888 183 917ZM403 917Q403 946 423.5 966.5Q444 987 473 987Q502 987 522.5 966.5Q543 946 543 917Q543 888 522.5 867.5Q502 847 473 847Q444 847 423.5 867.5Q403 888 403 917ZM415 309V0H311V309L40 750H166L363 400L560 750H686Z" glyph-name="Ydieresis" horiz-adv-x="726" unicode="&#376;" /><glyph d="M50 0V68L436 650H69V750H578V682L191 100H592V0Z" glyph-name="Z" horiz-adv-x="642" unicode="Z" /><glyph d="M288 825 368 1000H489L344 825ZM50 0V68L436 650H69V750H578V682L191 100H592V0Z" glyph-name="Zacute" horiz-adv-x="642" unicode="&#377;" /><glyph d="M494 1000 369 825H263L138 1000H194L316 895L438 1000ZM50 0V68L436 650H69V750H578V682L191 100H592V0Z" glyph-name="Zcaron" horiz-adv-x="642" unicode="&#381;" /><glyph d="M246 917Q246 946 266.5 966.5Q287 987 316 987Q345 987 365.5 966.5Q386 946 386 917Q386 888 365.5 867.5Q345 847 316 847Q287 847 266.5 867.5Q246 888 246 917ZM50 0V68L436 650H69V750H578V682L191 100H592V0Z" glyph-name="Zdotaccent" horiz-adv-x="642" unicode="&#379;" /><glyph d="M239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="a" horiz-adv-x="525" unicode="a" /><glyph d="M223 197H348L286 362ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="a.smcp" horiz-adv-x="572" /><glyph d="M233 575 313 750H434L289 575ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="aacute" horiz-adv-x="525" unicode="&#225;" /><glyph d="M223 197H348L286 362ZM258 575 338 750H459L314 575ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="aacute.smcp" horiz-adv-x="572" /><glyph d="M261 572Q225 572 195 582.5Q165 593 142.5 614.5Q120 636 107 669.5Q94 703 93 750H160Q161 723 166 705.5Q171 688 182.5 677.5Q194 667 213 662.5Q232 658 261 658Q290 658 309 662.5Q328 667 339.5 677.5Q351 688 356 705.5Q361 723 362 750H429Q428 703 415 669.5Q402 636 379.5 614.5Q357 593 327 582.5Q297 572 261 572ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="abreve" horiz-adv-x="525" unicode="&#259;" /><glyph d="M286 572Q250 572 220 582.5Q190 593 167.5 614.5Q145 636 132 669.5Q119 703 118 750H185Q186 723 191 705.5Q196 688 207.5 677.5Q219 667 238 662.5Q257 658 286 658Q315 658 334 662.5Q353 667 364.5 677.5Q376 688 381 705.5Q386 723 387 750H454Q453 703 440 669.5Q427 636 404.5 614.5Q382 593 352 582.5Q322 572 286 572ZM223 197H348L286 362ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="abreve.smcp" horiz-adv-x="572" /><glyph d="M239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM383 575 261 680 139 575H83L208 750H314L439 575ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="acircumflex" horiz-adv-x="525" unicode="&#226;" /><glyph d="M223 197H348L286 362ZM408 575 286 680 164 575H108L233 750H339L464 575ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="acircumflex.smcp" horiz-adv-x="572" /><glyph d="M252 575 332 750H453L308 575Z" glyph-name="acute" horiz-adv-x="540" unicode="&#180;" /><glyph d="M301 667Q301 696 321.5 716.5Q342 737 371 737Q400 737 420.5 716.5Q441 696 441 667Q441 638 420.5 617.5Q400 597 371 597Q342 597 321.5 617.5Q301 638 301 667ZM81 667Q81 696 101.5 716.5Q122 737 151 737Q180 737 200.5 716.5Q221 696 221 667Q221 638 200.5 617.5Q180 597 151 597Q122 597 101.5 617.5Q81 638 81 667ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="adieresis" horiz-adv-x="525" unicode="&#228;" /><glyph d="M223 197H348L286 362ZM106 667Q106 696 126.5 716.5Q147 737 176 737Q205 737 225.5 716.5Q246 696 246 667Q246 638 225.5 617.5Q205 597 176 597Q147 597 126.5 617.5Q106 638 106 667ZM326 667Q326 696 346.5 716.5Q367 737 396 737Q425 737 445.5 716.5Q466 696 466 667Q466 638 445.5 617.5Q425 597 396 597Q367 597 346.5 617.5Q326 638 326 667ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="adieresis.smcp" horiz-adv-x="572" /><glyph d="M571 416Q540 416 519.5 406.5Q499 397 487 380.5Q475 364 469 342.5Q463 321 460 297H664Q663 321 659.5 343Q656 365 646.5 381Q637 397 619 406.5Q601 416 571 416ZM235 207Q187 207 170 191Q153 175 153 149Q153 136 157 124.5Q161 113 171 104Q181 95 199 89.5Q217 84 246 84Q281 84 313 94.5Q345 105 365 122L372 126Q367 144 363.5 165.5Q360 187 358 207ZM357 336Q357 378 334 397Q311 416 254 416Q207 416 165.5 406.5Q124 397 88 385V479Q126 496 172 503Q218 510 254 510Q317 510 356.5 497.5Q396 485 419 457Q445 483 481 496.5Q517 510 571 510Q637 510 675.5 489.5Q714 469 734 434Q754 399 760 351.5Q766 304 766 250V207H460Q462 182 467.5 160Q473 138 485.5 121Q498 104 518.5 94Q539 84 571 84Q619 84 657.5 92Q696 100 743 120V24Q695 2 650.5 -4Q606 -10 571 -10Q517 -10 479.5 4.5Q442 19 416 45L401 34Q367 10 324.5 0Q282 -10 244 -10Q192 -10 155.5 2Q119 14 95.5 35.5Q72 57 61 86Q50 115 50 149Q50 179 58 206Q66 233 87.5 253.5Q109 274 146 285.5Q183 297 240 297H357Z" glyph-name="ae" horiz-adv-x="826" unicode="&#230;" /><glyph d="M363 303V390L307 303ZM677 209H462V94H710V0H363V209H246L112 0H-1L328 500H710V406H462V303H677Z" glyph-name="ae.smcp" horiz-adv-x="780" /><glyph d="M1140 292H60V390H1140Z" glyph-name="afii00208" horiz-adv-x="1200" unicode="&#8213;" /><glyph d="M233 575 88 750H209L289 575ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="agrave" horiz-adv-x="525" unicode="&#224;" /><glyph d="M223 197H348L286 362ZM258 575 113 750H234L314 575ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="agrave.smcp" horiz-adv-x="572" /><glyph d="M124 641V732H398V641ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="amacron" horiz-adv-x="525" unicode="&#257;" /><glyph d="M149 641V732H423V641ZM223 197H348L286 362ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="amacron.smcp" horiz-adv-x="572" /><glyph d="M335 82Q346 82 358.5 83Q371 84 379 86V87Q316 135 273 199Q230 263 201 332Q182 318 168 291.5Q154 265 154 228Q154 189 167.5 161.5Q181 134 205 116.5Q229 99 262.5 90.5Q296 82 335 82ZM392 574Q397 589 399.5 602Q402 615 402 626Q402 633 400 640Q398 647 391 652.5Q384 658 372 661.5Q360 665 339 665Q313 665 297 655.5Q281 646 272 631Q263 616 259.5 598.5Q256 581 256 565Q256 521 269 461Q282 401 311 339.5Q340 278 385.5 223Q431 168 496 133Q530 156 551.5 195.5Q573 235 573 289Q573 359 544.5 387.5Q516 416 473 416Q433 416 403.5 410.5Q374 405 339 392V490Q381 504 411.5 508.5Q442 513 473 513Q517 513 554.5 500Q592 487 619 460Q646 433 661.5 390.5Q677 348 677 289Q677 229 655.5 180Q634 131 601 96V95Q638 89 691 89V-13Q631 -13 583.5 -4Q536 5 496 21Q416 -13 335 -13Q273 -13 221 0Q169 13 131 42Q93 71 71.5 117Q50 163 50 228Q50 265 59.5 296.5Q69 328 85.5 353.5Q102 379 124 398Q146 417 170 431Q161 468 157 499.5Q153 531 153 565Q153 597 162 632Q171 667 193 696Q215 725 250.5 744Q286 763 339 763Q387 763 418.5 752Q450 741 468.5 722Q487 703 494 678Q501 653 501 626Q501 615 498.5 600Q496 585 492 574Z" glyph-name="ampersand" horiz-adv-x="731" unicode="&amp;" /><glyph d="M239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0Q413 -19 383.5 -46Q354 -73 354 -110Q354 -133 368 -144.5Q382 -156 405 -156Q426 -156 442.5 -153Q459 -150 468 -147V-215Q456 -220 435.5 -225Q415 -230 383 -230Q360 -230 339 -224Q318 -218 301.5 -205Q285 -192 275 -172.5Q265 -153 265 -126Q265 -89 291 -52Q317 -15 380 17L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="aogonek" horiz-adv-x="525" unicode="&#261;" /><glyph d="M223 197H348L286 362ZM532 0Q490 -19 460.5 -46Q431 -73 431 -110Q431 -133 445 -144.5Q459 -156 482 -156Q503 -156 519.5 -153Q536 -150 545 -147V-215Q533 -220 512.5 -225Q492 -230 460 -230Q437 -230 416 -224Q395 -218 378.5 -205Q362 -192 352 -172.5Q342 -153 342 -126Q342 -98 358.5 -65Q375 -32 423 1L381 112H190L148 0H40L251 513H321Z" glyph-name="aogonek.smcp" horiz-adv-x="572" /><glyph d="M204 193 261 250 204 307 273 376 330 319 387 376 456 307 399 250 456 193 387 124 330 181 273 124ZM185 400V100H475V400ZM80 500H580V0H80Z" glyph-name="approxequal" horiz-adv-x="660" unicode="&#8776;" /><glyph d="M305 662Q305 688 293.5 697.5Q282 707 261 707Q239 707 228 697.5Q217 688 217 662Q217 638 228 628.5Q239 619 261 619Q282 619 293.5 628.5Q305 638 305 662ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM362 662Q362 610 334 586Q306 562 261 562Q215 562 187 586Q159 610 159 662Q159 715 187 739Q215 763 261 763Q306 763 334 739Q362 715 362 662ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="aring" horiz-adv-x="525" unicode="&#229;" /><glyph d="M330 662Q330 688 318.5 697.5Q307 707 286 707Q264 707 253 697.5Q242 688 242 662Q242 638 253 628.5Q264 619 286 619Q307 619 318.5 628.5Q330 638 330 662ZM223 197H348L286 362ZM387 662Q387 610 359 586Q331 562 286 562Q240 562 212 586Q184 610 184 662Q184 715 212 739Q240 763 286 763Q331 763 359 739Q387 715 387 662ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="aring.smcp" horiz-adv-x="572" /><glyph d="M333 750 513 500H447L280 677L113 500H47L227 750Z" glyph-name="asciicircum" horiz-adv-x="540" unicode="^" /><glyph d="M335 327Q316 341 298.5 347.5Q281 354 260 354Q241 354 226.5 344Q212 334 197 322Q182 310 163 300Q144 290 116 290Q90 290 68.5 302Q47 314 35 326V404Q54 390 71.5 383.5Q89 377 110 377Q129 377 143.5 387Q158 397 173 409Q188 421 207 431Q226 441 254 441Q280 441 301.5 429Q323 417 335 405Z" glyph-name="asciitilde" horiz-adv-x="370" unicode="~" /><glyph d="M250 617 373 676 404 566 275 543 368 445 274 381 211 497 148 378 57 447 146 543 16 566 51 675 172 617 153 750H269Z" glyph-name="asterisk" horiz-adv-x="420" unicode="*" /><glyph d="M600 394Q578 405 551 411.5Q524 418 487 418Q450 418 428 404.5Q406 391 394 368Q382 345 378 314.5Q374 284 374 250Q374 216 378 185.5Q382 155 394 132Q406 109 428 95.5Q450 82 487 82Q524 82 551 88.5Q578 95 600 106ZM634 0 624 28Q590 9 556.5 -2Q523 -13 487 -13Q424 -13 382 6Q340 25 315 59.5Q290 94 280 142.5Q270 191 270 250Q270 309 280 357.5Q290 406 315 440.5Q340 475 382 494Q424 513 487 513Q523 513 556.5 502Q590 491 624 472L634 500H701V95Q746 98 775 116Q804 134 821 165Q838 196 844.5 239.5Q851 283 851 338Q851 407 841 467Q831 527 796 571.5Q761 616 693.5 641Q626 666 511 666Q434 666 378 649.5Q322 633 283 604Q244 575 220.5 535.5Q197 496 183.5 450Q170 404 165.5 353Q161 302 161 250Q161 198 165.5 147Q170 96 183.5 50Q197 4 220.5 -35.5Q244 -75 283 -104Q322 -133 378 -149.5Q434 -166 511 -166Q591 -166 653 -152.5Q715 -139 766 -114L767 -224Q715 -243 652 -253Q589 -263 511 -263Q414 -263 342.5 -243Q271 -223 220.5 -187.5Q170 -152 137.5 -103.5Q105 -55 87 2Q69 59 62 122Q55 185 55 250Q55 315 62 378Q69 441 87 498Q105 555 137.5 603.5Q170 652 220.5 687.5Q271 723 342.5 743Q414 763 511 763Q608 763 678.5 748Q749 733 798.5 705.5Q848 678 879 639.5Q910 601 927.5 554Q945 507 951 452Q957 397 957 338Q957 261 945 199Q933 137 903.5 92.5Q874 48 823.5 24Q773 0 696 0Z" glyph-name="at" horiz-adv-x="1012" unicode="@" /><glyph d="M461 724Q460 584 336 587Q305 587 286 597Q267 607 253 618.5Q239 630 226.5 639.5Q214 649 196 649Q181 649 172 644Q163 639 158.5 631.5Q154 624 152.5 614Q151 604 151 594L71 601Q73 673 103 706.5Q133 740 196 739Q227 738 246 728Q265 718 279 706.5Q293 695 305.5 685.5Q318 676 336 676Q351 676 360 681Q369 686 373.5 694Q378 702 379.5 711.5Q381 721 381 731ZM239 234Q215 232 199.5 224.5Q184 217 175 206.5Q166 196 162 183.5Q158 171 158 158Q158 145 160.5 131.5Q163 118 171 107Q179 96 195.5 89Q212 82 239 82Q271 82 302 91.5Q333 101 353 113V244ZM353 338Q353 379 334 398.5Q315 418 261 418Q239 418 214.5 415.5Q190 413 167 408.5Q144 404 123.5 398.5Q103 393 90 388V483Q105 490 126.5 495.5Q148 501 172 505Q196 509 219.5 511Q243 513 261 513Q319 513 356.5 500.5Q394 488 416 465Q438 442 446.5 410Q455 378 455 339V0H386L377 26Q346 7 308 -3Q270 -13 237 -13Q185 -13 150 1.5Q115 16 94 40Q73 64 64 94.5Q55 125 55 158Q55 188 63 215Q71 242 92 263.5Q113 285 148.5 299.5Q184 314 240 319L353 328Z" glyph-name="atilde" horiz-adv-x="525" unicode="&#227;" /><glyph d="M481 724Q480 584 356 587Q325 587 306 597Q287 607 273 618.5Q259 630 246.5 639.5Q234 649 216 649Q201 649 192 644Q183 639 178.5 631.5Q174 624 172.5 614Q171 604 171 594L91 601Q93 673 123 706.5Q153 740 216 739Q247 738 266 728Q285 718 299 706.5Q313 695 325.5 685.5Q338 676 356 676Q371 676 380 681Q389 686 393.5 694Q398 702 399.5 711.5Q401 721 401 731ZM223 197H348L286 362ZM424 0 381 112H190L148 0H40L251 513H321L532 0Z" glyph-name="atilde.smcp" horiz-adv-x="572" /><glyph d="M208 408Q184 402 171 395V106Q193 95 220 88.5Q247 82 284 82Q321 82 343 95.5Q365 109 377 132Q389 155 393 185.5Q397 216 397 250Q397 284 393 314.5Q389 345 377 368Q365 391 343 404.5Q321 418 284 418Q260 418 241.5 415Q223 412 208 408ZM70 750H171V486Q201 500 227.5 506.5Q254 513 284 513Q347 513 389 494Q431 475 456 440.5Q481 406 491 357.5Q501 309 501 250Q501 191 491 142.5Q481 94 456 59.5Q431 25 389 6Q347 -13 284 -13Q248 -13 214.5 -2Q181 9 147 28L137 0H70Z" glyph-name="b" horiz-adv-x="561" unicode="b" /><glyph d="M305 315Q335 315 344 327Q353 339 353 360Q353 381 343 393Q333 405 300 405H183V315ZM371 161Q371 190 358.5 208Q346 226 306 226H183V96H306Q346 96 358.5 114Q371 132 371 161ZM85 500H296Q341 500 371 491Q401 482 419 464Q437 446 444.5 420.5Q452 395 452 362Q452 338 442.5 312.5Q433 287 416 278Q444 264 459 229.5Q474 195 474 158Q474 124 465.5 95Q457 66 437 45Q417 24 384 12Q351 0 302 0H85Z" glyph-name="b.smcp" horiz-adv-x="529" /><glyph d="M351 -206 0 816H107L458 -206Z" glyph-name="backslash" horiz-adv-x="458" unicode="\" /><glyph d="M202 -206H97V816H202Z" glyph-name="bar" horiz-adv-x="299" unicode="|" /><glyph d="M185 382Q186 359 178.5 340Q171 321 157 305Q171 288 178.5 269Q186 250 185 228L167 -83Q165 -103 188 -103H222V-206H188Q159 -206 134.5 -199Q110 -192 93 -177Q76 -162 67.5 -138.5Q59 -115 61 -81L78 233Q80 252 60 254H28V356H56Q80 356 78 377L61 691Q59 725 67.5 748.5Q76 772 93 787Q110 802 134.5 809Q159 816 188 816H222V713H188Q165 713 167 693Z" glyph-name="braceleft" horiz-adv-x="262" unicode="{" /><glyph d="M95 693Q97 713 74 713H40V816H74Q103 816 127.5 809Q152 802 169 787Q186 772 194.5 748.5Q203 725 201 691L184 377Q182 356 206 356H234V254H206Q182 254 184 233L201 -81Q203 -115 194.5 -138.5Q186 -162 169 -177Q152 -192 127.5 -199Q103 -206 74 -206H40V-103H74Q97 -103 95 -83L77 228Q75 250 83 269Q91 288 104 305Q91 321 83 340Q75 359 77 382Z" glyph-name="braceright" horiz-adv-x="262" unicode="}" /><glyph d="M180 -106H244V-206H75V816H244V716H180Z" glyph-name="bracketleft" horiz-adv-x="284" unicode="[" /><glyph d="M104 716H40V816H209V-206H40V-106H104Z" glyph-name="bracketright" horiz-adv-x="284" unicode="]" /><glyph d="M270 572Q234 572 204 582.5Q174 593 151.5 614.5Q129 636 116 669.5Q103 703 102 750H169Q170 723 175 705.5Q180 688 191.5 677.5Q203 667 222 662.5Q241 658 270 658Q299 658 318 662.5Q337 667 348.5 677.5Q360 688 365 705.5Q370 723 371 750H438Q437 703 424 669.5Q411 636 388.5 614.5Q366 593 336 582.5Q306 572 270 572Z" glyph-name="breve" horiz-adv-x="540" unicode="&#728;" /><glyph d="M202 -206H97V256H202ZM97 816H202V354H97Z" glyph-name="brokenbar" horiz-adv-x="299" unicode="&#166;" /><glyph d="M65 375Q65 403 75.5 427.5Q86 452 104.5 470.5Q123 489 147.5 499.5Q172 510 200 510Q228 510 252.5 499.5Q277 489 295.5 470.5Q314 452 324.5 427.5Q335 403 335 375Q335 347 324.5 322.5Q314 298 295.5 279.5Q277 261 252.5 250.5Q228 240 200 240Q172 240 147.5 250.5Q123 261 104.5 279.5Q86 298 75.5 322.5Q65 347 65 375Z" glyph-name="bullet" horiz-adv-x="400" unicode="&#8226;" /><glyph d="M164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q405 -1 361.5 -7Q318 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q318 513 361.5 507Q405 501 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Z" glyph-name="c" horiz-adv-x="493" unicode="c" /><glyph d="M70 250Q70 304 83.5 352Q97 400 125.5 435.5Q154 471 199 492Q244 513 308 513Q343 513 384.5 509.5Q426 506 465 489V395Q428 407 391 412.5Q354 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Q354 82 391 87.5Q428 93 465 105V11Q426 -6 384.5 -9.5Q343 -13 308 -13Q244 -13 199 8Q154 29 125.5 64.5Q97 100 83.5 148Q70 196 70 250Z" glyph-name="c.smcp" horiz-adv-x="535" /><glyph d="M249 575 329 750H450L305 575ZM164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q405 -1 361.5 -7Q318 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q318 513 361.5 507Q405 501 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Z" glyph-name="cacute" horiz-adv-x="493" unicode="&#263;" /><glyph d="M272 575 352 750H473L328 575ZM70 250Q70 304 83.5 352Q97 400 125.5 435.5Q154 471 199 492Q244 513 308 513Q343 513 384.5 509.5Q426 506 465 489V395Q428 407 391 412.5Q354 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Q354 82 391 87.5Q428 93 465 105V11Q426 -6 384.5 -9.5Q343 -13 308 -13Q244 -13 199 8Q154 29 125.5 64.5Q97 100 83.5 148Q70 196 70 250Z" glyph-name="cacute.smcp" horiz-adv-x="535" /><glyph d="M458 750 333 575H227L102 750H158L280 645L402 750Z" glyph-name="caron" horiz-adv-x="540" unicode="&#711;" /><glyph d="M331 750 283 550H227L243 750Z" glyph-name="caronSlovak" horiz-adv-x="275" /><glyph d="M455 750 330 575H224L99 750H155L277 645L399 750ZM164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q405 -1 361.5 -7Q318 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q318 513 361.5 507Q405 501 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Z" glyph-name="ccaron" horiz-adv-x="493" unicode="&#269;" /><glyph d="M178 750 300 645 422 750H478L353 575H247L122 750ZM70 250Q70 304 83.5 352Q97 400 125.5 435.5Q154 471 199 492Q244 513 308 513Q343 513 384.5 509.5Q426 506 465 489V395Q428 407 391 412.5Q354 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Q354 82 391 87.5Q428 93 465 105V11Q426 -6 384.5 -9.5Q343 -13 308 -13Q244 -13 199 8Q154 29 125.5 64.5Q97 100 83.5 148Q70 196 70 250Z" glyph-name="ccaron.smcp" horiz-adv-x="535" /><glyph d="M221 -185Q231 -176 240 -165Q249 -154 253.5 -142.5Q258 -131 256.5 -121.5Q255 -112 245 -106Q227 -96 221 -83.5Q215 -71 216.5 -58Q218 -45 224.5 -32.5Q231 -20 238 -10Q188 -4 154 17.5Q120 39 99 72.5Q78 106 69 151Q60 196 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q318 513 361.5 507Q405 501 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q411 2 375 -4Q339 -10 307 -12Q297 -27 296 -42.5Q295 -58 318 -71Q343 -85 350 -101Q357 -117 354 -133Q351 -149 341.5 -162.5Q332 -176 323 -185Z" glyph-name="ccedilla" horiz-adv-x="490" unicode="&#231;" /><glyph d="M245 -185Q255 -176 264 -165Q273 -154 277.5 -142.5Q282 -131 280.5 -121.5Q279 -112 269 -106Q251 -96 245 -83.5Q239 -71 240.5 -58Q242 -45 248.5 -32.5Q255 -20 262 -10Q211 -3 174.5 20Q138 43 115 78Q92 113 81 157Q70 201 70 250Q70 304 83.5 352Q97 400 125.5 435.5Q154 471 199 492Q244 513 308 513Q343 513 384.5 509.5Q426 506 465 489V395Q428 407 391 412.5Q354 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Q354 82 391 87.5Q428 93 465 105V11Q432 -3 397.5 -7.5Q363 -12 331 -13Q321 -27 320 -42.5Q319 -58 342 -71Q367 -85 374 -101Q381 -117 378 -133Q375 -149 365.5 -162.5Q356 -176 347 -185Z" glyph-name="ccedilla.smcp" horiz-adv-x="535" /><glyph d="M399 575 277 680 155 575H99L224 750H330L455 575ZM164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q405 -1 361.5 -7Q318 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q318 513 361.5 507Q405 501 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Z" glyph-name="ccircumflex" horiz-adv-x="493" unicode="&#265;" /><glyph d="M422 575 300 680 178 575H122L247 750H353L478 575ZM70 250Q70 304 83.5 352Q97 400 125.5 435.5Q154 471 199 492Q244 513 308 513Q343 513 384.5 509.5Q426 506 465 489V395Q428 407 391 412.5Q354 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Q354 82 391 87.5Q428 93 465 105V11Q426 -6 384.5 -9.5Q343 -13 308 -13Q244 -13 199 8Q154 29 125.5 64.5Q97 100 83.5 148Q70 196 70 250Z" glyph-name="ccircumflex.smcp" horiz-adv-x="535" /><glyph d="M207 667Q207 696 227.5 716.5Q248 737 277 737Q306 737 326.5 716.5Q347 696 347 667Q347 638 326.5 617.5Q306 597 277 597Q248 597 227.5 617.5Q207 638 207 667ZM164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q405 -1 361.5 -7Q318 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q318 513 361.5 507Q405 501 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Z" glyph-name="cdotaccent" horiz-adv-x="493" unicode="&#267;" /><glyph d="M230 667Q230 696 250.5 716.5Q271 737 300 737Q329 737 349.5 716.5Q370 696 370 667Q370 638 349.5 617.5Q329 597 300 597Q271 597 250.5 617.5Q230 638 230 667ZM70 250Q70 304 83.5 352Q97 400 125.5 435.5Q154 471 199 492Q244 513 308 513Q343 513 384.5 509.5Q426 506 465 489V395Q428 407 391 412.5Q354 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Q354 82 391 87.5Q428 93 465 105V11Q426 -6 384.5 -9.5Q343 -13 308 -13Q244 -13 199 8Q154 29 125.5 64.5Q97 100 83.5 148Q70 196 70 250Z" glyph-name="cdotaccent.smcp" horiz-adv-x="535" /><glyph d="M218 -185Q228 -176 237 -165Q246 -154 250.5 -142.5Q255 -131 253.5 -121.5Q252 -112 242 -106Q222 -95 217 -80.5Q212 -66 215.5 -51Q219 -36 227.5 -22.5Q236 -9 243 0H314Q307 -9 301.5 -18Q296 -27 294.5 -36Q293 -45 297 -54Q301 -63 315 -71Q340 -85 347 -101Q354 -117 351 -133Q348 -149 338.5 -162.5Q329 -176 320 -185Z" glyph-name="cedilla" horiz-adv-x="602" unicode="&#184;" /><glyph d="M60 375Q60 520 132.5 585.5Q205 651 341 651H345L379 750H490L449 642Q475 638 500 632Q525 626 546 618V520Q525 528 498.5 534.5Q472 541 443.5 545Q415 549 387 551Q359 553 335 553Q300 553 269 545Q238 537 214.5 516.5Q191 496 177.5 461.5Q164 427 164 375Q164 322 177.5 288Q191 254 214.5 233.5Q238 213 269 205Q300 197 335 197Q359 197 387 199Q415 201 443.5 205Q472 209 498.5 215.5Q525 222 546 230V132Q523 123 495 116.5Q467 110 439 106Q411 102 385 100.5Q359 99 341 99H317L283 0H172L215 117Q139 142 99.5 205Q60 268 60 375Z" glyph-name="cent" horiz-adv-x="600" unicode="&#162;" /><glyph d="M164 250Q164 216 169 185.5Q174 155 187 132Q200 109 223 95.5Q246 82 283 82Q330 82 366.5 90Q403 98 440 111V16Q405 -1 361.5 -7Q318 -13 283 -13H270L234 -117H125L173 7Q110 36 85 99Q60 162 60 250Q60 308 70.5 356Q81 404 106 438.5Q131 473 172.5 492Q214 511 275 513L311 617H420L377 504Q394 500 410 495.5Q426 491 440 484V389Q403 402 366.5 410Q330 418 283 418Q246 418 223 404.5Q200 391 187 368Q174 345 169 314.5Q164 284 164 250Z" glyph-name="cent.OP" horiz-adv-x="500" /><glyph d="M402 575 280 680 158 575H102L227 750H333L458 575Z" glyph-name="circumflex" horiz-adv-x="540" unicode="&#710;" /><glyph d="M40 375Q40 404 60.5 424.5Q81 445 110 445Q139 445 159.5 424.5Q180 404 180 375Q180 346 159.5 325.5Q139 305 110 305Q81 305 60.5 325.5Q40 346 40 375ZM40 57Q40 86 60.5 106.5Q81 127 110 127Q139 127 159.5 106.5Q180 86 180 57Q180 28 159.5 7.5Q139 -13 110 -13Q81 -13 60.5 7.5Q40 28 40 57Z" glyph-name="colon" horiz-adv-x="220" unicode=":" /><glyph d="M58 -114 81 -7Q62 1 51 18Q40 35 40 57Q40 86 60.5 106.5Q81 127 110 127Q139 127 159.5 106.5Q180 86 180 57Q180 39 172 23L109 -114Z" glyph-name="comma" horiz-adv-x="220" unicode="," /><glyph d="M230 -120Q230 -91 250.5 -70.5Q271 -50 300 -50Q329 -50 349.5 -70.5Q370 -91 370 -120Q370 -149 349.5 -169.5Q329 -190 300 -190Q271 -190 250.5 -169.5Q230 -149 230 -120Z" glyph-name="commaaccent" horiz-adv-x="600" /><glyph d="M340 183Q362 183 387 188Q412 193 432 198V116Q408 111 383.5 108Q359 105 340 105Q274 105 239 137Q204 169 204 250Q204 331 239 363Q274 395 340 395Q359 395 383.5 392Q408 389 432 384V302Q412 307 387 312Q362 317 340 317Q313 317 299.5 304Q286 291 286 250Q286 209 299.5 196Q313 183 340 183ZM340 71Q379 71 412 77.5Q445 84 468.5 103Q492 122 505.5 157.5Q519 193 519 250Q519 307 505.5 342.5Q492 378 468.5 397Q445 416 412 422.5Q379 429 340 429Q301 429 268 422.5Q235 416 211.5 397Q188 378 174.5 342.5Q161 307 161 250Q161 193 174.5 157.5Q188 122 211.5 103Q235 84 268 77.5Q301 71 340 71ZM340 -13Q286 -13 237.5 -2.5Q189 8 152 37Q115 66 93.5 117.5Q72 169 72 250Q72 331 93.5 382.5Q115 434 152 463Q189 492 237.5 502.5Q286 513 340 513Q394 513 442.5 502.5Q491 492 528 463Q565 434 586.5 382.5Q608 331 608 250Q608 169 586.5 117.5Q565 66 528 37Q491 8 442.5 -2.5Q394 -13 340 -13Z" glyph-name="copyright" horiz-adv-x="680" unicode="&#169;" /><glyph d="M35 401V500H96Q103 564 120 613.5Q137 663 168 696Q199 729 246 746Q293 763 360 763Q395 763 436.5 758Q478 753 517 736V639Q480 651 443.5 658Q407 665 360 665Q286 665 250 623.5Q214 582 203 500H463L430 401H196V349H413L380 250H203Q214 167 250 126Q286 85 360 85Q407 85 443.5 92Q480 99 517 111V14Q478 -3 436.5 -8Q395 -13 360 -13Q293 -13 246 4Q199 21 168 54Q137 87 120 136Q103 185 96 250H35V349H90V401Z" glyph-name="currency" horiz-adv-x="546" unicode="&#164;" /><glyph d="M353 408Q338 412 319.5 415Q301 418 277 418Q240 418 218 404.5Q196 391 184 368Q172 345 168 314.5Q164 284 164 250Q164 216 168 185.5Q172 155 184 132Q196 109 218 95.5Q240 82 277 82Q314 82 341 88.5Q368 95 390 106V395Q377 402 353 408ZM491 0H424L414 28Q380 9 346.5 -2Q313 -13 277 -13Q214 -13 172 6Q130 25 105 59.5Q80 94 70 142.5Q60 191 60 250Q60 309 70 357.5Q80 406 105 440.5Q130 475 172 494Q214 513 277 513Q307 513 333.5 506.5Q360 500 390 486V750H491Z" glyph-name="d" horiz-adv-x="561" unicode="d" /><glyph d="M261 94Q302 94 329.5 105Q357 116 373.5 136.5Q390 157 397 186Q404 215 404 250Q404 285 397 314Q390 343 373.5 363.5Q357 384 329.5 395Q302 406 261 406H183V94ZM85 0V500H260Q325 500 371 482.5Q417 465 446.5 432.5Q476 400 489.5 353.5Q503 307 503 250Q503 193 489.5 146.5Q476 100 446.5 67.5Q417 35 371 17.5Q325 0 260 0Z" glyph-name="d.smcp" horiz-adv-x="573" /><glyph d="M240 612 379 631V516L240 535L270 295H134L164 535L25 516V631L163 612L144 750H260Z" glyph-name="dagger" horiz-adv-x="404" unicode="&#8224;" /><glyph d="M240 612 379 631V516L240 535L248 480L240 427L379 446V330L240 350L270 110H134L164 350L25 330V446L163 427L156 480L163 535L25 516V631L163 612L144 750H260Z" glyph-name="daggerdbl" horiz-adv-x="404" unicode="&#8225;" /><glyph d="M640 750 592 550H536L552 750ZM353 408Q338 412 319.5 415Q301 418 277 418Q240 418 218 404.5Q196 391 184 368Q172 345 168 314.5Q164 284 164 250Q164 216 168 185.5Q172 155 184 132Q196 109 218 95.5Q240 82 277 82Q314 82 341 88.5Q368 95 390 106V395Q377 402 353 408ZM491 0H424L414 28Q380 9 346.5 -2Q313 -13 277 -13Q214 -13 172 6Q130 25 105 59.5Q80 94 70 142.5Q60 191 60 250Q60 309 70 357.5Q80 406 105 440.5Q130 475 172 494Q214 513 277 513Q307 513 333.5 506.5Q360 500 390 486V750H491Z" glyph-name="dcaron" horiz-adv-x="561" unicode="&#271;" /><glyph d="M444 750 319 575H213L88 750H144L266 645L388 750ZM261 94Q302 94 329.5 105Q357 116 373.5 136.5Q390 157 397 186Q404 215 404 250Q404 285 397 314Q390 343 373.5 363.5Q357 384 329.5 395Q302 406 261 406H183V94ZM85 0V500H260Q325 500 371 482.5Q417 465 446.5 432.5Q476 400 489.5 353.5Q503 307 503 250Q503 193 489.5 146.5Q476 100 446.5 67.5Q417 35 371 17.5Q325 0 260 0Z" glyph-name="dcaron.smcp" horiz-adv-x="573" /><glyph d="M353 408Q338 412 319.5 415Q301 418 277 418Q240 418 218 404.5Q196 391 184 368Q172 345 168 314.5Q164 284 164 250Q164 216 168 185.5Q172 155 184 132Q196 109 218 95.5Q240 82 277 82Q314 82 341 88.5Q368 95 390 106V395Q377 402 353 408ZM491 659H583V576H491V0H424L414 28Q380 9 346.5 -2Q313 -13 277 -13Q214 -13 172 6Q130 25 105 59.5Q80 94 70 142.5Q60 191 60 250Q60 309 70 357.5Q80 406 105 440.5Q130 475 172 494Q214 513 277 513Q307 513 333.5 506.5Q360 500 390 486V576H275V659H390V750H491Z" glyph-name="dcroat" horiz-adv-x="561" unicode="&#273;" /><glyph d="M291 94Q332 94 359.5 105Q387 116 403.5 136.5Q420 157 427 186Q434 215 434 250Q434 285 427 314Q420 343 403.5 363.5Q387 384 359.5 395Q332 406 291 406H213V294H333V205H213V94ZM25 294H115V500H290Q355 500 401 482.5Q447 465 476.5 432.5Q506 400 519.5 353.5Q533 307 533 250Q533 193 519.5 146.5Q506 100 476.5 67.5Q447 35 401 17.5Q355 0 290 0H115V205H25Z" glyph-name="dcroat.smcp" horiz-adv-x="603" /><glyph d="M183 689Q154 689 137 674.5Q120 660 120 625Q120 590 137 575.5Q154 561 183 561Q212 561 229 575.5Q246 590 246 625Q246 660 229 674.5Q212 689 183 689ZM183 487Q157 487 132 493.5Q107 500 87 516Q67 532 54.5 558.5Q42 585 42 625Q42 665 54.5 692Q67 719 87 734.5Q107 750 132 756.5Q157 763 183 763Q209 763 234 756.5Q259 750 279 734.5Q299 719 311.5 692Q324 665 324 625Q324 585 311.5 558.5Q299 532 279 516Q259 500 234 493.5Q209 487 183 487Z" glyph-name="degree" horiz-adv-x="366" unicode="&#176;" /><glyph d="M320 667Q320 696 340.5 716.5Q361 737 390 737Q419 737 439.5 716.5Q460 696 460 667Q460 638 439.5 617.5Q419 597 390 597Q361 597 340.5 617.5Q320 638 320 667ZM100 667Q100 696 120.5 716.5Q141 737 170 737Q199 737 219.5 716.5Q240 696 240 667Q240 638 219.5 617.5Q199 597 170 597Q141 597 120.5 617.5Q100 638 100 667Z" glyph-name="dieresis" horiz-adv-x="540" unicode="&#168;" /><glyph d="M190 494Q190 523 210.5 543.5Q231 564 260 564Q289 564 309.5 543.5Q330 523 330 494Q330 465 309.5 444.5Q289 424 260 424Q231 424 210.5 444.5Q190 465 190 494ZM190 188Q190 217 210.5 237.5Q231 258 260 258Q289 258 309.5 237.5Q330 217 330 188Q330 159 309.5 138.5Q289 118 260 118Q231 118 210.5 138.5Q190 159 190 188ZM460 390V292H60V390Z" glyph-name="divide" horiz-adv-x="520" unicode="&#247;" /><glyph d="M166 485Q166 466 178.5 456Q191 446 211.5 441Q232 436 257 434.5Q282 433 307 431Q367 426 410 417.5Q453 409 481 392Q509 375 522 347Q535 319 535 275Q535 194 472.5 147Q410 100 287 99L253 0H142L181 105Q149 108 120 113.5Q91 119 74 127V229Q92 221 118.5 215.5Q145 210 174 206.5Q203 203 231.5 201Q260 199 282 199Q312 199 339 202Q366 205 386.5 213Q407 221 419.5 235.5Q432 250 432 274Q432 293 419.5 304Q407 315 387.5 321Q368 327 343.5 330Q319 333 295 335Q236 340 193 347.5Q150 355 121.5 370Q93 385 79 410.5Q65 436 65 478Q65 565 128 608Q191 651 309 651H315L349 750H460L419 644Q445 641 469 635Q493 629 513 621V523Q492 531 465.5 536.5Q439 542 411 545Q383 548 356.5 549.5Q330 551 308 551Q281 551 255 548Q229 545 209.5 537.5Q190 530 178 517.5Q166 505 166 485Z" glyph-name="dollar" horiz-adv-x="600" unicode="$" /><glyph d="M159 354Q159 343 162.5 334.5Q166 326 176 320Q186 314 203.5 310Q221 306 250 303Q305 298 339.5 286Q374 274 394 255Q414 236 421.5 210Q429 184 429 151Q429 122 419.5 93.5Q410 65 388 42Q366 19 329.5 4Q293 -11 240 -13L204 -117H95L138 -4Q95 2 66 15V112Q96 101 136.5 91.5Q177 82 225 82Q255 82 275 88Q295 94 307 103.5Q319 113 324 125Q329 137 329 150Q329 162 326 172.5Q323 183 313.5 191Q304 199 286 204Q268 209 239 212Q183 217 148.5 229.5Q114 242 94.5 260.5Q75 279 68 302.5Q61 326 61 354Q61 381 70 409Q79 437 100.5 459.5Q122 482 157.5 497Q193 512 245 513L281 617H390L346 503Q361 500 376 495Q391 490 406 484V389Q369 401 333 409.5Q297 418 252 418Q223 418 205 413Q187 408 176.5 399.5Q166 391 162.5 379Q159 367 159 354Z" glyph-name="dollar.OP" horiz-adv-x="490" /><glyph d="M210 667Q210 696 230.5 716.5Q251 737 280 737Q309 737 329.5 716.5Q350 696 350 667Q350 638 329.5 617.5Q309 597 280 597Q251 597 230.5 617.5Q210 638 210 667Z" glyph-name="dotaccent" horiz-adv-x="540" unicode="&#729;" /><glyph d="M257 -250 271 -184Q252 -176 241 -159Q230 -142 230 -120Q230 -91 250.5 -70.5Q271 -50 300 -50Q329 -50 349.5 -70.5Q370 -91 370 -120Q370 -138 362 -154L318 -250Z" glyph-name="dotbelow" horiz-adv-x="600" /><glyph d="M202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="dotlessi" horiz-adv-x="275" unicode="&#305;" /><glyph d="M154 575 9 750H130L210 575ZM330 575 185 750H307L387 575Z" glyph-name="doublegrave" horiz-adv-x="540" /><glyph d="M283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="e" horiz-adv-x="542" unicode="e" /><glyph d="M429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="e.smcp" horiz-adv-x="532" /><glyph d="M255 575 335 750H456L311 575ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="eacute" horiz-adv-x="542" unicode="&#233;" /><glyph d="M246 575 326 750H447L302 575ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="eacute.smcp" horiz-adv-x="532" /><glyph d="M283 572Q247 572 217 582.5Q187 593 164.5 614.5Q142 636 129 669.5Q116 703 115 750H182Q183 723 188 705.5Q193 688 204.5 677.5Q216 667 235 662.5Q254 658 283 658Q312 658 331 662.5Q350 667 361.5 677.5Q373 688 378 705.5Q383 723 384 750H451Q450 703 437 669.5Q424 636 401.5 614.5Q379 593 349 582.5Q319 572 283 572ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="ebreve" horiz-adv-x="542" unicode="&#277;" /><glyph d="M274 572Q238 572 208 582.5Q178 593 155.5 614.5Q133 636 120 669.5Q107 703 106 750H173Q174 723 179 705.5Q184 688 195.5 677.5Q207 667 226 662.5Q245 658 274 658Q303 658 322 662.5Q341 667 352.5 677.5Q364 688 369 705.5Q374 723 375 750H442Q441 703 428 669.5Q415 636 392.5 614.5Q370 593 340 582.5Q310 572 274 572ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="ebreve.smcp" horiz-adv-x="532" /><glyph d="M161 750 283 645 405 750H461L336 575H230L105 750ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="ecaron" horiz-adv-x="542" unicode="&#283;" /><glyph d="M452 750 327 575H221L96 750H152L274 645L396 750ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="ecaron.smcp" horiz-adv-x="532" /><glyph d="M283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM405 575 283 680 161 575H105L230 750H336L461 575ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="ecircumflex" horiz-adv-x="542" unicode="&#234;" /><glyph d="M396 575 274 680 152 575H96L221 750H327L452 575ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="ecircumflex.smcp" horiz-adv-x="532" /><glyph d="M323 667Q323 696 343.5 716.5Q364 737 393 737Q422 737 442.5 716.5Q463 696 463 667Q463 638 442.5 617.5Q422 597 393 597Q364 597 343.5 617.5Q323 638 323 667ZM103 667Q103 696 123.5 716.5Q144 737 173 737Q202 737 222.5 716.5Q243 696 243 667Q243 638 222.5 617.5Q202 597 173 597Q144 597 123.5 617.5Q103 638 103 667ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="edieresis" horiz-adv-x="542" unicode="&#235;" /><glyph d="M314 667Q314 696 334.5 716.5Q355 737 384 737Q413 737 433.5 716.5Q454 696 454 667Q454 638 433.5 617.5Q413 597 384 597Q355 597 334.5 617.5Q314 638 314 667ZM94 667Q94 696 114.5 716.5Q135 737 164 737Q193 737 213.5 716.5Q234 696 234 667Q234 638 213.5 617.5Q193 597 164 597Q135 597 114.5 617.5Q94 638 94 667ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="edieresis.smcp" horiz-adv-x="532" /><glyph d="M213 667Q213 696 233.5 716.5Q254 737 283 737Q312 737 332.5 716.5Q353 696 353 667Q353 638 332.5 617.5Q312 597 283 597Q254 597 233.5 617.5Q213 638 213 667ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="edotaccent" horiz-adv-x="542" unicode="&#279;" /><glyph d="M204 667Q204 696 224.5 716.5Q245 737 274 737Q303 737 323.5 716.5Q344 696 344 667Q344 638 323.5 617.5Q303 597 274 597Q245 597 224.5 617.5Q204 638 204 667ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="edotaccent.smcp" horiz-adv-x="532" /><glyph d="M255 575 110 750H231L311 575ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="egrave" horiz-adv-x="542" unicode="&#232;" /><glyph d="M246 575 101 750H222L302 575ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="egrave.smcp" horiz-adv-x="532" /><glyph d="M300 435Q334 435 358 446Q382 457 397 473.5Q412 490 418.5 510.5Q425 531 425 550Q425 570 420 591Q415 612 401 629Q387 646 363 657Q339 668 300 668Q261 668 237 657Q213 646 199 629Q185 612 180 591Q175 570 175 550Q175 531 181.5 510.5Q188 490 203 473.5Q218 457 242 446Q266 435 300 435ZM300 341Q227 341 189.5 307Q152 273 152 212Q152 190 159 167Q166 144 182.5 125Q199 106 227.5 94Q256 82 300 82Q343 82 372 94Q401 106 417.5 125Q434 144 441 167Q448 190 448 212Q448 273 410.5 307Q373 341 300 341ZM300 -13Q229 -13 180.5 5.5Q132 24 102 55Q72 86 59 126.5Q46 167 46 212Q46 237 51.5 264Q57 291 68.5 316Q80 341 97.5 362Q115 383 139 396Q122 409 109 427Q96 445 87 465.5Q78 486 73.5 508Q69 530 69 550Q69 591 81 629.5Q93 668 120 697.5Q147 727 191 745Q235 763 300 763Q364 763 408.5 745Q453 727 480 697.5Q507 668 519 629.5Q531 591 531 550Q531 530 526 508Q521 486 512 465.5Q503 445 490 427Q477 409 461 396Q485 383 502.5 362Q520 341 531.5 316Q543 291 548.5 264Q554 237 554 212Q554 167 540.5 126.5Q527 86 497 55Q467 24 418.5 5.5Q370 -13 300 -13Z" glyph-name="eight" horiz-adv-x="600" unicode="8" /><glyph d="M300 435Q334 435 358 446Q382 457 397 473.5Q412 490 418.5 510.5Q425 531 425 550Q425 570 420 591Q415 612 401 629Q387 646 363 657Q339 668 300 668Q261 668 237 657Q213 646 199 629Q185 612 180 591Q175 570 175 550Q175 531 181.5 510.5Q188 490 203 473.5Q218 457 242 446Q266 435 300 435ZM300 341Q227 341 189.5 307Q152 273 152 212Q152 190 159 167Q166 144 182.5 125Q199 106 227.5 94Q256 82 300 82Q343 82 372 94Q401 106 417.5 125Q434 144 441 167Q448 190 448 212Q448 273 410.5 307Q373 341 300 341ZM300 -13Q229 -13 180.5 5.5Q132 24 102 55Q72 86 59 126.5Q46 167 46 212Q46 237 51.5 264Q57 291 68.5 316Q80 341 97.5 362Q115 383 139 396Q122 409 109 427Q96 445 87 465.5Q78 486 73.5 508Q69 530 69 550Q69 591 81 629.5Q93 668 120 697.5Q147 727 191 745Q235 763 300 763Q364 763 408.5 745Q453 727 480 697.5Q507 668 519 629.5Q531 591 531 550Q531 530 526 508Q521 486 512 465.5Q503 445 490 427Q477 409 461 396Q485 383 502.5 362Q520 341 531.5 316Q543 291 548.5 264Q554 237 554 212Q554 167 540.5 126.5Q527 86 497 55Q467 24 418.5 5.5Q370 -13 300 -13Z" glyph-name="eight.LP" horiz-adv-x="600" /><glyph d="M300 435Q334 435 358 446Q382 457 397 473.5Q412 490 418.5 510.5Q425 531 425 550Q425 570 420 591Q415 612 401 629Q387 646 363 657Q339 668 300 668Q261 668 237 657Q213 646 199 629Q185 612 180 591Q175 570 175 550Q175 531 181.5 510.5Q188 490 203 473.5Q218 457 242 446Q266 435 300 435ZM300 341Q227 341 189.5 307Q152 273 152 212Q152 190 159 167Q166 144 182.5 125Q199 106 227.5 94Q256 82 300 82Q343 82 372 94Q401 106 417.5 125Q434 144 441 167Q448 190 448 212Q448 273 410.5 307Q373 341 300 341ZM300 -13Q229 -13 180.5 5.5Q132 24 102 55Q72 86 59 126.5Q46 167 46 212Q46 237 51.5 264Q57 291 68.5 316Q80 341 97.5 362Q115 383 139 396Q122 409 109 427Q96 445 87 465.5Q78 486 73.5 508Q69 530 69 550Q69 591 81 629.5Q93 668 120 697.5Q147 727 191 745Q235 763 300 763Q364 763 408.5 745Q453 727 480 697.5Q507 668 519 629.5Q531 591 531 550Q531 530 526 508Q521 486 512 465.5Q503 445 490 427Q477 409 461 396Q485 383 502.5 362Q520 341 531.5 316Q543 291 548.5 264Q554 237 554 212Q554 167 540.5 126.5Q527 86 497 55Q467 24 418.5 5.5Q370 -13 300 -13Z" glyph-name="eight.OP" horiz-adv-x="600" /><glyph d="M425 57Q425 86 445.5 106.5Q466 127 495 127Q524 127 544.5 106.5Q565 86 565 57Q565 28 544.5 7.5Q524 -13 495 -13Q466 -13 445.5 7.5Q425 28 425 57ZM755 57Q755 86 775.5 106.5Q796 127 825 127Q854 127 874.5 106.5Q895 86 895 57Q895 28 874.5 7.5Q854 -13 825 -13Q796 -13 775.5 7.5Q755 28 755 57ZM95 57Q95 86 115.5 106.5Q136 127 165 127Q194 127 214.5 106.5Q235 86 235 57Q235 28 214.5 7.5Q194 -13 165 -13Q136 -13 115.5 7.5Q95 28 95 57Z" glyph-name="ellipsis" horiz-adv-x="990" unicode="&#8230;" /><glyph d="M146 641V732H420V641ZM283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q407 -1 363 -7Q319 -13 283 -13Q220 -13 177 6Q134 25 108 59.5Q82 94 71 142.5Q60 191 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="emacron" horiz-adv-x="542" unicode="&#275;" /><glyph d="M137 641V732H411V641ZM429 209H184V94H462V0H85V500H462V406H184V303H429Z" glyph-name="emacron.smcp" horiz-adv-x="532" /><glyph d="M940 292H60V390H940Z" glyph-name="emdash" horiz-adv-x="1000" unicode="&#8212;" /><glyph glyph-name="emspace" horiz-adv-x="1000" unicode="&#8195;" /><glyph d="M440 292H60V390H440Z" glyph-name="endash" horiz-adv-x="500" unicode="&#8211;" /><glyph d="M253 -168Q274 -165 294.5 -159Q315 -153 332 -142.5Q349 -132 359.5 -116Q370 -100 370 -78V354Q370 389 347 403.5Q324 418 289 418Q251 418 222 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216.5 502.5Q253 513 289 513Q382 513 427.5 470.5Q473 428 473 352V-88Q473 -129 453 -160Q433 -191 401.5 -212.5Q370 -234 330.5 -246Q291 -258 253 -260Z" glyph-name="eng" horiz-adv-x="538" unicode="&#331;" /><glyph d="M293 -168Q314 -165 335.5 -158.5Q357 -152 374.5 -141Q392 -130 403.5 -113.5Q415 -97 415 -75V35L183 316L184 0H85V500H154L415 184L414 500H513V-88Q513 -129 493 -160Q473 -191 441.5 -212.5Q410 -234 370.5 -246Q331 -258 293 -260Z" glyph-name="eng.smcp" horiz-adv-x="598" /><glyph glyph-name="enspace" horiz-adv-x="500" unicode="&#8194;" /><glyph d="M283 418Q251 418 229.5 408.5Q208 399 194.5 381.5Q181 364 174.5 341Q168 318 165 291H376Q376 316 373 339Q370 362 360 379.5Q350 397 332 407.5Q314 418 283 418ZM166 202Q169 176 176.5 154Q184 132 197.5 116Q211 100 232 91Q253 82 283 82Q307 82 328 84Q349 86 369.5 90Q390 94 411 101Q432 108 455 118V21Q424 6 394 0Q352 -19 323 -46Q294 -73 294 -110Q294 -133 308 -144.5Q322 -156 345 -156Q366 -156 382.5 -153Q399 -150 408 -147V-215Q396 -220 375.5 -225Q355 -230 323 -230Q300 -230 279 -224Q258 -218 241.5 -205Q225 -192 215 -172.5Q205 -153 205 -126Q205 -101 217.5 -71.5Q230 -42 267 -13Q208 -10 168.5 9.5Q129 29 105 63.5Q81 98 70.5 145.5Q60 193 60 250Q60 309 71 357.5Q82 406 108 440.5Q134 475 177 494Q220 513 283 513Q349 513 388 491.5Q427 470 448 434Q469 398 475.5 350Q482 302 482 250V202Z" glyph-name="eogonek" horiz-adv-x="542" unicode="&#281;" /><glyph d="M429 209H184V94H462V0Q420 -19 390.5 -46Q361 -73 361 -110Q361 -133 375 -144.5Q389 -156 412 -156Q433 -156 449.5 -153Q466 -150 475 -147V-215Q463 -220 442.5 -225Q422 -230 390 -230Q367 -230 346 -224Q325 -218 308.5 -205Q292 -192 282 -172.5Q272 -153 272 -126Q272 -98 288 -65Q304 -32 351 0H85V500H462V406H184V303H429Z" glyph-name="eogonek.smcp" horiz-adv-x="532" /><glyph d="M460 209H60V308H460ZM460 374H60V473H460Z" glyph-name="equal" horiz-adv-x="520" unicode="=" /><glyph d="M387 250Q387 286 384 316.5Q381 347 369 369.5Q357 392 334 405Q311 418 272 418Q232 418 209 405Q186 392 174.5 369.5Q163 347 160 316.5Q157 286 157 250Q157 214 160 183.5Q163 153 174.5 130.5Q186 108 209 95Q232 82 272 82Q311 82 334 95Q357 108 369 130.5Q381 153 384 183.5Q387 214 387 250ZM419 666 357 634Q382 605 405 567.5Q428 530 446 484.5Q464 439 475.5 384Q487 329 488 264Q489 259 489 250Q489 191 480.5 143Q472 95 448.5 60Q425 25 382.5 6Q340 -13 272 -13Q204 -13 162 6Q120 25 96 60Q72 95 63.5 143Q55 191 55 250Q55 308 63.5 356.5Q72 405 96 440Q120 475 162 494Q204 513 272 513Q302 513 329 504Q317 528 302.5 549Q288 570 271 590L129 518V602L216 646Q177 681 137 706.5Q97 732 65 750H220Q236 740 257.5 725.5Q279 711 302 690L419 750Z" glyph-name="eth" horiz-adv-x="544" unicode="&#240;" /><glyph d="M291 94Q332 94 359.5 105Q387 116 403.5 136.5Q420 157 427 186Q434 215 434 250Q434 285 427 314Q420 343 403.5 363.5Q387 384 359.5 395Q332 406 291 406H213V294H333V205H213V94ZM25 294H115V500H290Q355 500 401 482.5Q447 465 476.5 432.5Q506 400 519.5 353.5Q533 307 533 250Q533 193 519.5 146.5Q506 100 476.5 67.5Q447 35 401 17.5Q355 0 290 0H115V205H25Z" glyph-name="eth.smcp" horiz-adv-x="603" /><glyph d="M40 57Q40 86 60.5 106.5Q81 127 110 127Q139 127 159.5 106.5Q180 86 180 57Q180 28 159.5 7.5Q139 -13 110 -13Q81 -13 60.5 7.5Q40 28 40 57ZM78 210 42 750H178L142 210Z" glyph-name="exclam" horiz-adv-x="220" unicode="!" /><glyph d="M40 443Q40 472 60.5 492.5Q81 513 110 513Q139 513 159.5 492.5Q180 472 180 443Q180 414 159.5 393.5Q139 373 110 373Q81 373 60.5 393.5Q40 414 40 443ZM142 290 178 -250H42L78 290Z" glyph-name="exclamdown" horiz-adv-x="220" unicode="&#161;" /><glyph d="M389 657Q372 662 349.5 665Q327 668 306 668Q268 668 244.5 653Q221 638 221 603V500H369V412H221V0H120V412H27V474L120 500V605Q120 681 164 722Q208 763 306 763Q313 763 324 762.5Q335 762 346.5 760.5Q358 759 369.5 757.5Q381 756 389 753Z" glyph-name="f" horiz-adv-x="409" unicode="f" /><glyph d="M420 209H183V0H85V500H452V406H183V303H420Z" glyph-name="f.smcp" horiz-adv-x="512" /><glyph d="M611 -13Q551 -13 520.5 14Q490 41 490 108V500H591V105Q591 91 599.5 86.5Q608 82 630 82V-13ZM389 657Q372 662 349.5 665Q327 668 306 668Q268 668 244.5 653Q221 638 221 603V500H369V412H221V0H120V412H27V474L120 500V605Q120 681 164 722Q208 763 306 763Q313 763 324 762.5Q335 762 346.5 760.5Q358 759 369.5 757.5Q381 756 389 753Z" glyph-name="f_dotlessi" horiz-adv-x="684" /><glyph d="M471 667Q471 696 491.5 716.5Q512 737 541 737Q570 737 590.5 716.5Q611 696 611 667Q611 638 590.5 617.5Q570 597 541 597Q512 597 491.5 617.5Q471 638 471 667ZM611 -13Q551 -13 520.5 14Q490 41 490 108V500H591V105Q591 91 599.5 86.5Q608 82 630 82V-13ZM389 657Q372 662 349.5 665Q327 668 306 668Q268 668 244.5 653Q221 638 221 603V500H369V412H221V0H120V412H27V474L120 500V605Q120 681 164 722Q208 763 306 763Q313 763 324 762.5Q335 762 346.5 760.5Q358 759 369.5 757.5Q381 756 389 753Z" glyph-name="fi" horiz-adv-x="684" unicode="&#64257;" /><glyph d="M540 292H60V390H540Z" glyph-name="figuredash" horiz-adv-x="600" unicode="&#8210;" /><glyph glyph-name="figurespace" horiz-adv-x="600" unicode="&#8199;" /><glyph d="M259 -13Q227 -13 196.5 -10.5Q166 -8 139 -4Q112 0 91 6Q70 12 57 20V119Q71 110 94.5 103Q118 96 146 91.5Q174 87 203.5 84.5Q233 82 260 82Q292 82 324.5 87.5Q357 93 383.5 109.5Q410 126 426.5 156Q443 186 443 235Q443 281 428 305.5Q413 330 388 341Q363 352 330 353.5Q297 355 261 355H79V750H533V655H179V450Q205 452 231.5 452.5Q258 453 290 453Q341 453 388 446Q435 439 470 416.5Q505 394 526 351Q547 308 547 235Q547 153 521.5 104Q496 55 455 29Q414 3 362.5 -5Q311 -13 259 -13Z" glyph-name="five" horiz-adv-x="600" unicode="5" /><glyph d="M259 -13Q227 -13 196.5 -10.5Q166 -8 139 -4Q112 0 91 6Q70 12 57 20V119Q71 110 94.5 103Q118 96 146 91.5Q174 87 203.5 84.5Q233 82 260 82Q292 82 324.5 87.5Q357 93 383.5 109.5Q410 126 426.5 156Q443 186 443 235Q443 281 428 305.5Q413 330 388 341Q363 352 330 353.5Q297 355 261 355H79V750H533V655H179V450Q205 452 231.5 452.5Q258 453 290 453Q341 453 388 446Q435 439 470 416.5Q505 394 526 351Q547 308 547 235Q547 153 521.5 104Q496 55 455 29Q414 3 362.5 -5Q311 -13 259 -13Z" glyph-name="five.LP" horiz-adv-x="600" /><glyph d="M222 -263Q168 -263 128.5 -252Q89 -241 57 -225V-125Q103 -150 141 -159Q179 -168 223 -168Q250 -168 279.5 -162.5Q309 -157 334.5 -140.5Q360 -124 376.5 -94Q393 -64 393 -15Q393 31 378.5 55.5Q364 80 341 91Q318 102 288 103.5Q258 105 228 105H79V500H483V405H179V200Q198 202 216 202.5Q234 203 249 203Q295 203 339.5 196Q384 189 419 166.5Q454 144 475.5 101Q497 58 497 -15Q497 -97 470 -146Q443 -195 402 -221Q361 -247 313 -255Q265 -263 222 -263Z" glyph-name="five.OP" horiz-adv-x="530" /><glyph d="M611 -13Q551 -13 520.5 14Q490 41 490 108V750H591V105Q591 91 599.5 86.5Q608 82 630 82V-13ZM389 657Q372 662 349.5 665Q327 668 306 668Q268 668 244.5 653Q221 638 221 603V500H369V412H221V0H120V412H27V474L120 500V605Q120 681 164 722Q208 763 306 763Q313 763 324 762.5Q335 762 346.5 760.5Q358 759 369.5 757.5Q381 756 389 753Z" glyph-name="fl" horiz-adv-x="684" unicode="&#64258;" /><glyph d="M355 763Q376 763 400.5 761.5Q425 760 450.5 756Q476 752 500 746.5Q524 741 544 732V638Q506 651 457.5 659.5Q409 668 355 668Q334 668 313 661.5Q292 655 275.5 642Q259 629 249 608.5Q239 588 239 561V438H510V350H239V0H139V350H46V411L139 438V561Q139 606 152 643Q165 680 191.5 707Q218 734 258.5 748.5Q299 763 355 763Z" glyph-name="florin" horiz-adv-x="600" unicode="&#402;" /><glyph d="M305 513Q341 513 382.5 507.5Q424 502 464 485V391Q426 404 389.5 411Q353 418 305 418Q268 418 243.5 398.5Q219 379 219 341V271H440V183H219V-250H119V183H26V244L119 271V341Q119 420 163.5 466.5Q208 513 305 513Z" glyph-name="florin.OP" horiz-adv-x="530" /><glyph d="M358 297V561L181 297ZM54 264 388 763H458V297H556V202H458V0H358V202H54Z" glyph-name="four" horiz-adv-x="600" unicode="4" /><glyph d="M358 297V561L181 297ZM54 264 388 763H458V297H556V202H458V0H358V202H54Z" glyph-name="four.LP" horiz-adv-x="600" /><glyph d="M338 95V313L188 95ZM54 62 368 513H438V95H556V0H438V-250H338V0H54Z" glyph-name="four.OP" horiz-adv-x="600" /><glyph glyph-name="fourperemspace" horiz-adv-x="250" unicode="&#8197;" /><glyph d="M-50 0 482 750H601L69 0Z" glyph-name="fraction" horiz-adv-x="551" unicode="&#8260;" /><glyph d="M225 -17Q185 -30 157.5 -49.5Q130 -69 130 -100Q130 -141 161 -154.5Q192 -168 255 -168Q286 -168 314.5 -164Q343 -160 365 -151Q387 -142 400 -126Q413 -110 413 -86Q413 -60 400 -48Q387 -36 355 -32ZM153 318Q153 298 156 280Q159 262 169.5 248.5Q180 235 200 226.5Q220 218 255 218Q289 218 309.5 226.5Q330 235 341 248.5Q352 262 355 280Q358 298 358 318Q358 337 355 355.5Q352 374 341 387.5Q330 401 309.5 409.5Q289 418 255 418Q220 418 200 409.5Q180 401 169.5 387.5Q159 374 156 355.5Q153 337 153 318ZM455 318Q455 278 445.5 242.5Q436 207 413 180.5Q390 154 351.5 138.5Q313 123 255 123Q246 123 237 123Q228 123 220 124Q211 118 203 110Q195 102 195 94Q195 85 199 81.5Q203 78 213 76L372 57Q442 49 479 16.5Q516 -16 516 -86Q516 -133 495 -166.5Q474 -200 438 -221.5Q402 -243 354.5 -253Q307 -263 255 -263Q148 -263 88 -224.5Q28 -186 28 -100Q28 -55 52.5 -26Q77 3 112 21V22Q104 33 99 47Q94 61 94 81Q94 103 104 121Q114 139 129 153Q87 179 71 222.5Q55 266 55 318Q55 358 64.5 393.5Q74 429 97 455.5Q120 482 158.5 497.5Q197 513 255 513Q281 513 303.5 509.5Q326 506 345 500H506V432L438 413Q447 392 451 368Q455 344 455 318Z" glyph-name="g" horiz-adv-x="548" unicode="g" /><glyph d="M307 82Q332 82 353.5 83.5Q375 85 395 89V183H293V277H493V23Q455 3 404 -5Q353 -13 307 -13Q243 -13 198 8Q153 29 124.5 64.5Q96 100 83 148Q70 196 70 250Q70 304 83 352Q96 400 124.5 435.5Q153 471 198 492Q243 513 307 513Q328 513 353.5 511Q379 509 403.5 505.5Q428 502 449 496Q470 490 484 482V388Q455 401 408.5 409.5Q362 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Z" glyph-name="g.smcp" horiz-adv-x="578" /><glyph d="M255 572Q219 572 189 582.5Q159 593 136.5 614.5Q114 636 101 669.5Q88 703 87 750H154Q155 723 160 705.5Q165 688 176.5 677.5Q188 667 207 662.5Q226 658 255 658Q284 658 303 662.5Q322 667 333.5 677.5Q345 688 350 705.5Q355 723 356 750H423Q422 703 409 669.5Q396 636 373.5 614.5Q351 593 321 582.5Q291 572 255 572ZM225 -17Q185 -30 157.5 -49.5Q130 -69 130 -100Q130 -141 161 -154.5Q192 -168 255 -168Q286 -168 314.5 -164Q343 -160 365 -151Q387 -142 400 -126Q413 -110 413 -86Q413 -60 400 -48Q387 -36 355 -32ZM153 318Q153 298 156 280Q159 262 169.5 248.5Q180 235 200 226.5Q220 218 255 218Q289 218 309.5 226.5Q330 235 341 248.5Q352 262 355 280Q358 298 358 318Q358 337 355 355.5Q352 374 341 387.5Q330 401 309.5 409.5Q289 418 255 418Q220 418 200 409.5Q180 401 169.5 387.5Q159 374 156 355.5Q153 337 153 318ZM455 318Q455 278 445.5 242.5Q436 207 413 180.5Q390 154 351.5 138.5Q313 123 255 123Q246 123 237 123Q228 123 220 124Q211 118 203 110Q195 102 195 94Q195 85 199 81.5Q203 78 213 76L372 57Q442 49 479 16.5Q516 -16 516 -86Q516 -133 495 -166.5Q474 -200 438 -221.5Q402 -243 354.5 -253Q307 -263 255 -263Q148 -263 88 -224.5Q28 -186 28 -100Q28 -55 52.5 -26Q77 3 112 21V22Q104 33 99 47Q94 61 94 81Q94 103 104 121Q114 139 129 153Q87 179 71 222.5Q55 266 55 318Q55 358 64.5 393.5Q74 429 97 455.5Q120 482 158.5 497.5Q197 513 255 513Q281 513 303.5 509.5Q326 506 345 500H506V432L438 413Q447 392 451 368Q455 344 455 318Z" glyph-name="gbreve" horiz-adv-x="548" unicode="&#287;" /><glyph d="M299 572Q263 572 233 582.5Q203 593 180.5 614.5Q158 636 145 669.5Q132 703 131 750H198Q199 723 204 705.5Q209 688 220.5 677.5Q232 667 251 662.5Q270 658 299 658Q328 658 347 662.5Q366 667 377.5 677.5Q389 688 394 705.5Q399 723 400 750H467Q466 703 453 669.5Q440 636 417.5 614.5Q395 593 365 582.5Q335 572 299 572ZM307 82Q332 82 353.5 83.5Q375 85 395 89V183H293V277H493V23Q455 3 404 -5Q353 -13 307 -13Q243 -13 198 8Q153 29 124.5 64.5Q96 100 83 148Q70 196 70 250Q70 304 83 352Q96 400 124.5 435.5Q153 471 198 492Q243 513 307 513Q328 513 353.5 511Q379 509 403.5 505.5Q428 502 449 496Q470 490 484 482V388Q455 401 408.5 409.5Q362 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Z" glyph-name="gbreve.smcp" horiz-adv-x="578" /><glyph d="M377 575 255 680 133 575H77L202 750H308L433 575ZM225 -17Q185 -30 157.5 -49.5Q130 -69 130 -100Q130 -141 161 -154.5Q192 -168 255 -168Q286 -168 314.5 -164Q343 -160 365 -151Q387 -142 400 -126Q413 -110 413 -86Q413 -60 400 -48Q387 -36 355 -32ZM153 318Q153 298 156 280Q159 262 169.5 248.5Q180 235 200 226.5Q220 218 255 218Q289 218 309.5 226.5Q330 235 341 248.5Q352 262 355 280Q358 298 358 318Q358 337 355 355.5Q352 374 341 387.5Q330 401 309.5 409.5Q289 418 255 418Q220 418 200 409.5Q180 401 169.5 387.5Q159 374 156 355.5Q153 337 153 318ZM455 318Q455 278 445.5 242.5Q436 207 413 180.5Q390 154 351.5 138.5Q313 123 255 123Q246 123 237 123Q228 123 220 124Q211 118 203 110Q195 102 195 94Q195 85 199 81.5Q203 78 213 76L372 57Q442 49 479 16.5Q516 -16 516 -86Q516 -133 495 -166.5Q474 -200 438 -221.5Q402 -243 354.5 -253Q307 -263 255 -263Q148 -263 88 -224.5Q28 -186 28 -100Q28 -55 52.5 -26Q77 3 112 21V22Q104 33 99 47Q94 61 94 81Q94 103 104 121Q114 139 129 153Q87 179 71 222.5Q55 266 55 318Q55 358 64.5 393.5Q74 429 97 455.5Q120 482 158.5 497.5Q197 513 255 513Q281 513 303.5 509.5Q326 506 345 500H506V432L438 413Q447 392 451 368Q455 344 455 318Z" glyph-name="gcircumflex" horiz-adv-x="548" unicode="&#285;" /><glyph d="M421 575 299 680 177 575H121L246 750H352L477 575ZM307 82Q332 82 353.5 83.5Q375 85 395 89V183H293V277H493V23Q455 3 404 -5Q353 -13 307 -13Q243 -13 198 8Q153 29 124.5 64.5Q96 100 83 148Q70 196 70 250Q70 304 83 352Q96 400 124.5 435.5Q153 471 198 492Q243 513 307 513Q328 513 353.5 511Q379 509 403.5 505.5Q428 502 449 496Q470 490 484 482V388Q455 401 408.5 409.5Q362 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Z" glyph-name="gcircumflex.smcp" horiz-adv-x="578" /><glyph d="M298 750 284 684Q303 676 314 659Q325 642 325 620Q325 591 304.5 570.5Q284 550 255 550Q226 550 205.5 570.5Q185 591 185 620Q185 638 193 654L237 750ZM225 -17Q185 -30 157.5 -49.5Q130 -69 130 -100Q130 -141 161 -154.5Q192 -168 255 -168Q286 -168 314.5 -164Q343 -160 365 -151Q387 -142 400 -126Q413 -110 413 -86Q413 -60 400 -48Q387 -36 355 -32ZM153 318Q153 298 156 280Q159 262 169.5 248.5Q180 235 200 226.5Q220 218 255 218Q289 218 309.5 226.5Q330 235 341 248.5Q352 262 355 280Q358 298 358 318Q358 337 355 355.5Q352 374 341 387.5Q330 401 309.5 409.5Q289 418 255 418Q220 418 200 409.5Q180 401 169.5 387.5Q159 374 156 355.5Q153 337 153 318ZM455 318Q455 278 445.5 242.5Q436 207 413 180.5Q390 154 351.5 138.5Q313 123 255 123Q246 123 237 123Q228 123 220 124Q211 118 203 110Q195 102 195 94Q195 85 199 81.5Q203 78 213 76L372 57Q442 49 479 16.5Q516 -16 516 -86Q516 -133 495 -166.5Q474 -200 438 -221.5Q402 -243 354.5 -253Q307 -263 255 -263Q148 -263 88 -224.5Q28 -186 28 -100Q28 -55 52.5 -26Q77 3 112 21V22Q104 33 99 47Q94 61 94 81Q94 103 104 121Q114 139 129 153Q87 179 71 222.5Q55 266 55 318Q55 358 64.5 393.5Q74 429 97 455.5Q120 482 158.5 497.5Q197 513 255 513Q281 513 303.5 509.5Q326 506 345 500H506V432L438 413Q447 392 451 368Q455 344 455 318Z" glyph-name="gcommaaccent" horiz-adv-x="548" unicode="&#291;" /><glyph d="M257 -250 271 -184Q252 -176 241 -159Q230 -142 230 -120Q230 -91 250.5 -70.5Q271 -50 300 -50Q329 -50 349.5 -70.5Q370 -91 370 -120Q370 -138 362 -154L318 -250ZM307 82Q332 82 353.5 83.5Q375 85 395 89V183H293V277H493V23Q455 3 404 -5Q353 -13 307 -13Q243 -13 198 8Q153 29 124.5 64.5Q96 100 83 148Q70 196 70 250Q70 304 83 352Q96 400 124.5 435.5Q153 471 198 492Q243 513 307 513Q328 513 353.5 511Q379 509 403.5 505.5Q428 502 449 496Q470 490 484 482V388Q455 401 408.5 409.5Q362 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Z" glyph-name="gcommaaccent.smcp" horiz-adv-x="578" /><glyph d="M185 667Q185 696 205.5 716.5Q226 737 255 737Q284 737 304.5 716.5Q325 696 325 667Q325 638 304.5 617.5Q284 597 255 597Q226 597 205.5 617.5Q185 638 185 667ZM225 -17Q185 -30 157.5 -49.5Q130 -69 130 -100Q130 -141 161 -154.5Q192 -168 255 -168Q286 -168 314.5 -164Q343 -160 365 -151Q387 -142 400 -126Q413 -110 413 -86Q413 -60 400 -48Q387 -36 355 -32ZM153 318Q153 298 156 280Q159 262 169.5 248.5Q180 235 200 226.5Q220 218 255 218Q289 218 309.5 226.5Q330 235 341 248.5Q352 262 355 280Q358 298 358 318Q358 337 355 355.5Q352 374 341 387.5Q330 401 309.5 409.5Q289 418 255 418Q220 418 200 409.5Q180 401 169.5 387.5Q159 374 156 355.5Q153 337 153 318ZM455 318Q455 278 445.5 242.5Q436 207 413 180.5Q390 154 351.5 138.5Q313 123 255 123Q246 123 237 123Q228 123 220 124Q211 118 203 110Q195 102 195 94Q195 85 199 81.5Q203 78 213 76L372 57Q442 49 479 16.5Q516 -16 516 -86Q516 -133 495 -166.5Q474 -200 438 -221.5Q402 -243 354.5 -253Q307 -263 255 -263Q148 -263 88 -224.5Q28 -186 28 -100Q28 -55 52.5 -26Q77 3 112 21V22Q104 33 99 47Q94 61 94 81Q94 103 104 121Q114 139 129 153Q87 179 71 222.5Q55 266 55 318Q55 358 64.5 393.5Q74 429 97 455.5Q120 482 158.5 497.5Q197 513 255 513Q281 513 303.5 509.5Q326 506 345 500H506V432L438 413Q447 392 451 368Q455 344 455 318Z" glyph-name="gdotaccent" horiz-adv-x="548" unicode="&#289;" /><glyph d="M229 667Q229 696 249.5 716.5Q270 737 299 737Q328 737 348.5 716.5Q369 696 369 667Q369 638 348.5 617.5Q328 597 299 597Q270 597 249.5 617.5Q229 638 229 667ZM307 82Q332 82 353.5 83.5Q375 85 395 89V183H293V277H493V23Q455 3 404 -5Q353 -13 307 -13Q243 -13 198 8Q153 29 124.5 64.5Q96 100 83 148Q70 196 70 250Q70 304 83 352Q96 400 124.5 435.5Q153 471 198 492Q243 513 307 513Q328 513 353.5 511Q379 509 403.5 505.5Q428 502 449 496Q470 490 484 482V388Q455 401 408.5 409.5Q362 418 307 418Q268 418 242.5 403.5Q217 389 201 365Q185 341 178.5 311Q172 281 172 250Q172 219 178.5 189Q185 159 201 135Q217 111 242.5 96.5Q268 82 307 82Z" glyph-name="gdotaccent.smcp" horiz-adv-x="578" /><glyph d="M304 665Q241 665 210.5 642.5Q180 620 180 588V0H80V587Q80 624 93 655.5Q106 687 133.5 710Q161 733 203.5 746.5Q246 760 304 760Q362 760 404 746.5Q446 733 474 710Q502 687 515 655.5Q528 624 528 587Q528 554 521 528.5Q514 503 498 483.5Q482 464 456.5 450.5Q431 437 394 427Q358 418 343 403.5Q328 389 328 365Q328 343 346 331.5Q364 320 417 314Q472 308 507 295Q542 282 562 262.5Q582 243 590 216.5Q598 190 598 157Q598 127 587.5 97Q577 67 553 43Q529 19 489.5 4.5Q450 -10 392 -10Q374 -10 352.5 -7.5Q331 -5 309.5 -1Q288 3 268 8Q248 13 235 19V115Q265 104 305.5 94.5Q346 85 393 85Q424 85 444 91.5Q464 98 476 108Q488 118 492.5 130.5Q497 143 497 156Q497 168 494 179Q491 190 482 199Q473 208 455.5 214Q438 220 409 223Q353 228 318 240.5Q283 253 263 271.5Q243 290 236 313.5Q229 337 229 365Q229 395 235.5 418.5Q242 442 258 460Q274 478 301.5 491.5Q329 505 371 516Q390 521 401 528.5Q412 536 418 545.5Q424 555 425.5 565.5Q427 576 427 588Q427 620 397 642.5Q367 665 304 665Z" glyph-name="germandbls" horiz-adv-x="645" unicode="&#223;" /><glyph d="M692 355Q692 328 717 318Q742 308 801 303Q855 298 892 287.5Q929 277 952 259.5Q975 242 985 215.5Q995 189 995 153Q995 120 983.5 89.5Q972 59 946.5 36.5Q921 14 878.5 0.5Q836 -13 775 -13Q756 -13 731.5 -11Q707 -9 682 -5Q657 -1 636 3.5Q615 8 604 13V107Q616 102 635.5 98Q655 94 678 90Q701 86 726.5 84Q752 82 776 82Q843 82 869.5 100.5Q896 119 896 152Q896 180 873.5 193.5Q851 207 792 212Q737 217 699.5 226.5Q662 236 638.5 252Q615 268 605 292Q595 316 595 350Q595 386 606.5 415.5Q618 445 643 467Q668 489 708 501Q748 513 805 513Q842 513 887.5 508.5Q933 504 972 487V395Q934 407 893 412.5Q852 418 804 418Q741 418 716.5 402.5Q692 387 692 355ZM162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 120 453.5 89.5Q442 59 416.5 36.5Q391 14 348.5 0.5Q306 -13 245 -13Q226 -13 201.5 -11Q177 -9 152 -5Q127 -1 106 3.5Q85 8 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Z" glyph-name="germandbls.smcp" horiz-adv-x="1060" /><glyph d="M252 575 107 750H228L308 575Z" glyph-name="grave" horiz-adv-x="540" unicode="`" /><glyph d="M50 223 205 341 50 459V562L320 382V300L50 120Z" glyph-name="greater" horiz-adv-x="365" unicode="&gt;" /><glyph d="M810 348 298 -13V113L603 314L624 326H60V424H625L604 436L298 637V763L810 402Z" glyph-name="greaterequal" horiz-adv-x="860" unicode="&#8805;" /><glyph d="M245 296 435 436V349L328 250L435 151V63L245 204ZM55 296 245 436V349L138 250L245 151V63L55 204Z" glyph-name="guillemotleft" horiz-adv-x="495" unicode="&#171;" /><glyph d="M250 204 60 63V151L167 250L60 349V436L250 296ZM440 204 250 63V151L357 250L250 349V436L440 296Z" glyph-name="guillemotright" horiz-adv-x="495" unicode="&#187;" /><glyph d="M55 296 245 436V349L138 250L245 151V63L55 204Z" glyph-name="guilsinglleft" horiz-adv-x="305" unicode="&#8249;" /><glyph d="M250 204 60 63V151L167 250L60 349V436L250 296Z" glyph-name="guilsinglright" horiz-adv-x="305" unicode="&#8250;" /><glyph d="M368 354Q368 389 345 403.5Q322 418 287 418Q249 418 220 411Q191 404 171 394V0H70V750H171V486Q198 500 228 506.5Q258 513 287 513Q380 513 425.5 470.5Q471 428 471 352V0H368Z" glyph-name="h" horiz-adv-x="536" unicode="h" /><glyph d="M414 0V209H184V0H85V500H184V303H414V500H513V0Z" glyph-name="h.smcp" horiz-adv-x="598" /><glyph glyph-name="hairspace" horiz-adv-x="50" unicode="&#8202;" /><glyph d="M368 354Q368 389 345 403.5Q322 418 287 418Q249 418 220 411Q191 404 171 394V0H70V576H-22V659H70V750H171V659H286V576H171V486Q198 500 228 506.5Q258 513 287 513Q380 513 425.5 470.5Q471 428 471 352V0H368Z" glyph-name="hbar" horiz-adv-x="536" unicode="&#295;" /><glyph d="M414 303V355H184V303ZM414 500H513V0H414V209H184V0H85V500H184V438H414Z" glyph-name="hbar.smcp" horiz-adv-x="598" /><glyph d="M243 825 121 930 -1 825H-57L68 1000H174L299 825ZM368 354Q368 389 345 403.5Q322 418 287 418Q249 418 220 411Q191 404 171 394V0H70V750H171V486Q198 500 228 506.5Q258 513 287 513Q380 513 425.5 470.5Q471 428 471 352V0H368Z" glyph-name="hcircumflex" horiz-adv-x="536" unicode="&#293;" /><glyph d="M421 575 299 680 177 575H121L246 750H352L477 575ZM414 0V209H184V0H85V500H184V303H414V500H513V0Z" glyph-name="hcircumflex.smcp" horiz-adv-x="598" /><glyph d="M330 575 410 750H531L386 575ZM153 575 233 750H355L210 575Z" glyph-name="hungarumlaut" horiz-adv-x="540" unicode="&#733;" /><glyph d="M320 292H50V390H320Z" glyph-name="hyphen" horiz-adv-x="370" unicode="-" /><glyph d="M62 667Q62 696 82.5 716.5Q103 737 132 737Q161 737 181.5 716.5Q202 696 202 667Q202 638 181.5 617.5Q161 597 132 597Q103 597 82.5 617.5Q62 638 62 667ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="i" horiz-adv-x="275" unicode="i" /><glyph d="M107 0V500H205V0Z" glyph-name="i.smcp" horiz-adv-x="312" /><glyph d="M104 575 184 750H305L160 575ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="iacute" horiz-adv-x="275" unicode="&#237;" /><glyph d="M129 575 209 750H330L185 575ZM107 0V500H205V0Z" glyph-name="iacute.smcp" horiz-adv-x="312" /><glyph d="M132 572Q96 572 66 582.5Q36 593 13.5 614.5Q-9 636 -22 669.5Q-35 703 -36 750H31Q32 723 37 705.5Q42 688 53.5 677.5Q65 667 84 662.5Q103 658 132 658Q161 658 180 662.5Q199 667 210.5 677.5Q222 688 227 705.5Q232 723 233 750H300Q299 703 286 669.5Q273 636 250.5 614.5Q228 593 198 582.5Q168 572 132 572ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="ibreve" horiz-adv-x="275" unicode="&#301;" /><glyph d="M156 572Q120 572 90 582.5Q60 593 37.5 614.5Q15 636 2 669.5Q-11 703 -12 750H55Q56 723 61 705.5Q66 688 77.5 677.5Q89 667 108 662.5Q127 658 156 658Q185 658 204 662.5Q223 667 234.5 677.5Q246 688 251 705.5Q256 723 257 750H324Q323 703 310 669.5Q297 636 274.5 614.5Q252 593 222 582.5Q192 572 156 572ZM107 0V500H205V0Z" glyph-name="ibreve.smcp" horiz-adv-x="312" /><glyph d="M254 575 132 680 10 575H-46L79 750H185L310 575ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="icircumflex" horiz-adv-x="275" unicode="&#238;" /><glyph d="M279 575 157 680 35 575H-21L104 750H210L335 575ZM107 0V500H205V0Z" glyph-name="icircumflex.smcp" horiz-adv-x="312" /><glyph d="M-48 667Q-48 696 -27.5 716.5Q-7 737 22 737Q51 737 71.5 716.5Q92 696 92 667Q92 638 71.5 617.5Q51 597 22 597Q-7 597 -27.5 617.5Q-48 638 -48 667ZM172 667Q172 696 192.5 716.5Q213 737 242 737Q271 737 291.5 716.5Q312 696 312 667Q312 638 291.5 617.5Q271 597 242 597Q213 597 192.5 617.5Q172 638 172 667ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="idieresis" horiz-adv-x="275" unicode="&#239;" /><glyph d="M-23 667Q-23 696 -2.5 716.5Q18 737 47 737Q76 737 96.5 716.5Q117 696 117 667Q117 638 96.5 617.5Q76 597 47 597Q18 597 -2.5 617.5Q-23 638 -23 667ZM197 667Q197 696 217.5 716.5Q238 737 267 737Q296 737 316.5 716.5Q337 696 337 667Q337 638 316.5 617.5Q296 597 267 597Q238 597 217.5 617.5Q197 638 197 667ZM107 0V500H205V0Z" glyph-name="idieresis.smcp" horiz-adv-x="312" /><glyph d="M62 667Q62 696 82.5 716.5Q103 737 132 737Q161 737 181.5 716.5Q202 696 202 667Q202 638 181.5 617.5Q161 597 132 597Q103 597 82.5 617.5Q62 638 62 667ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="idotaccent" horiz-adv-x="275" /><glyph d="M107 0V500H205V0ZM86 667Q86 696 106.5 716.5Q127 737 156 737Q185 737 205.5 716.5Q226 696 226 667Q226 638 205.5 617.5Q185 597 156 597Q127 597 106.5 617.5Q86 638 86 667Z" glyph-name="idotaccent.smcp" horiz-adv-x="312" /><glyph d="M104 575 -41 750H80L160 575ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="igrave" horiz-adv-x="275" unicode="&#236;" /><glyph d="M129 575 -16 750H105L185 575ZM107 0V500H205V0Z" glyph-name="igrave.smcp" horiz-adv-x="312" /><glyph d="M337 667Q337 696 357.5 716.5Q378 737 407 737Q436 737 456.5 716.5Q477 696 477 667Q477 638 456.5 617.5Q436 597 407 597Q378 597 357.5 617.5Q337 638 337 667ZM237 -168Q258 -165 279 -159.5Q300 -154 317 -143.5Q334 -133 345 -117Q356 -101 356 -79V500H457V-88Q457 -129 437 -160Q417 -191 385.5 -212.5Q354 -234 314.5 -246Q275 -258 237 -260ZM62 667Q62 696 82.5 716.5Q103 737 132 737Q161 737 181.5 716.5Q202 696 202 667Q202 638 181.5 617.5Q161 597 132 597Q103 597 82.5 617.5Q62 638 62 667ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="ij" horiz-adv-x="539" unicode="&#307;" /><glyph d="M357 94Q376 87 397.5 84.5Q419 82 441 82Q478 82 500.5 95Q523 108 523 143V500H622V145Q622 69 580 28Q538 -13 441 -13Q433 -13 422 -12.5Q411 -12 399 -10.5Q387 -9 375.5 -7Q364 -5 357 -3ZM107 0V500H205V0Z" glyph-name="ij.smcp" horiz-adv-x="729" /><glyph d="M-5 641V732H269V641ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="imacron" horiz-adv-x="275" unicode="&#299;" /><glyph d="M19 641V732H293V641ZM107 0V500H205V0Z" glyph-name="imacron.smcp" horiz-adv-x="312" /><glyph d="M78 677Q78 664 91 659.5Q104 655 133 652Q187 647 208.5 630Q230 613 230 575Q230 541 205.5 517.5Q181 494 120 494Q111 494 99 495Q87 496 75 498Q63 500 52 502.5Q41 505 34 508V554Q50 549 73 544.5Q96 540 121 540Q154 540 167.5 548.5Q181 557 181 574Q181 589 169.5 596Q158 603 128 606Q73 611 51.5 625.5Q30 640 30 677Q30 712 54 734Q78 756 135 756Q153 756 175.5 753Q198 750 218 742V696Q200 702 178.5 706Q157 710 134 710Q102 710 90 702Q78 694 78 677ZM372 530 308 658 310 500H261V750H316L391 594L466 750H520V500H472L474 658L410 530Z" glyph-name="infinity" horiz-adv-x="575" unicode="&#8734;" /><glyph d="M185 650V100H725V650ZM80 750H830V0H80Z" glyph-name="integral" horiz-adv-x="910" unicode="&#8747;" /><glyph d="M270 760Q306 760 336 749.5Q366 739 388.5 717.5Q411 696 424 662Q437 628 438 582H371Q370 609 365 626.5Q360 644 348.5 654.5Q337 665 318 669.5Q299 674 270 674Q241 674 222 669.5Q203 665 191.5 654.5Q180 644 175 626.5Q170 609 169 582H102Q103 628 116 662Q129 696 151.5 717.5Q174 739 204 749.5Q234 760 270 760Z" glyph-name="invertedbreve" horiz-adv-x="500" /><glyph d="M62 667Q62 696 82.5 716.5Q103 737 132 737Q161 737 181.5 716.5Q202 696 202 667Q202 638 181.5 617.5Q161 597 132 597Q103 597 82.5 617.5Q62 638 62 667ZM221 -13Q189 -31 167.5 -55Q146 -79 146 -110Q146 -133 160 -144.5Q174 -156 197 -156Q218 -156 234.5 -153Q251 -150 260 -147V-215Q248 -220 227.5 -225Q207 -230 175 -230Q152 -230 131 -224Q110 -218 93.5 -205Q77 -192 67 -172.5Q57 -153 57 -126Q57 -112 60.5 -97Q64 -82 73 -66Q82 -50 97 -33.5Q112 -17 135 -1Q108 11 94.5 37Q81 63 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82Z" glyph-name="iogonek" horiz-adv-x="275" unicode="&#303;" /><glyph d="M205 0Q192 -9 178.5 -20.5Q165 -32 154 -46Q143 -60 136 -76Q129 -92 129 -110Q129 -133 143 -144.5Q157 -156 180 -156Q201 -156 217.5 -153Q234 -150 243 -147V-215Q231 -220 210.5 -225Q190 -230 158 -230Q135 -230 114 -224Q93 -218 76.5 -205Q60 -192 50 -172.5Q40 -153 40 -126Q40 -99 54 -64Q68 -29 107 1V500H205Z" glyph-name="iogonek.smcp" horiz-adv-x="312" /><glyph d="M332 724Q331 584 207 587Q176 587 157 597Q138 607 124 618.5Q110 630 97.5 639.5Q85 649 67 649Q52 649 43 644Q34 639 29.5 631.5Q25 624 23.5 614Q22 604 22 594L-58 601Q-56 673 -26 706.5Q4 740 67 739Q98 738 117 728Q136 718 150 706.5Q164 695 176.5 685.5Q189 676 207 676Q222 676 231 681Q240 686 244.5 694Q249 702 250.5 711.5Q252 721 252 731ZM202 -13Q142 -13 111.5 14Q81 41 81 108V500H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="itilde" horiz-adv-x="275" unicode="&#297;" /><glyph d="M352 724Q351 584 227 587Q196 587 177 597Q158 607 144 618.5Q130 630 117.5 639.5Q105 649 87 649Q72 649 63 644Q54 639 49.5 631.5Q45 624 43.5 614Q42 604 42 594L-38 601Q-36 673 -6 706.5Q24 740 87 739Q118 738 137 728Q156 718 170 706.5Q184 695 196.5 685.5Q209 676 227 676Q242 676 251 681Q260 686 264.5 694Q269 702 270.5 711.5Q272 721 272 731ZM107 0V500H205V0Z" glyph-name="itilde.smcp" horiz-adv-x="312" /><glyph d="M62 667Q62 696 82.5 716.5Q103 737 132 737Q161 737 181.5 716.5Q202 696 202 667Q202 638 181.5 617.5Q161 597 132 597Q103 597 82.5 617.5Q62 638 62 667ZM-38 -168Q-17 -165 4 -159.5Q25 -154 42 -143.5Q59 -133 70 -117Q81 -101 81 -79V500H182V-88Q182 -129 162 -160Q142 -191 110.5 -212.5Q79 -234 39.5 -246Q0 -258 -38 -260Z" glyph-name="j" horiz-adv-x="264" unicode="j" /><glyph d="M45 94Q64 87 85.5 84.5Q107 82 129 82Q166 82 188.5 95Q211 108 211 143V500H310V145Q310 69 268 28Q226 -13 129 -13Q121 -13 110 -12.5Q99 -12 87 -10.5Q75 -9 63.5 -7Q52 -5 45 -3Z" glyph-name="j.smcp" horiz-adv-x="417" /><glyph d="M254 575 132 680 10 575H-46L79 750H185L310 575ZM-38 -168Q-17 -165 4 -159.5Q25 -154 42 -143.5Q59 -133 70 -117Q81 -101 81 -79V500H182V-88Q182 -129 162 -160Q142 -191 110.5 -212.5Q79 -234 39.5 -246Q0 -258 -38 -260Z" glyph-name="jcircumflex" horiz-adv-x="264" unicode="&#309;" /><glyph d="M383 575 261 680 139 575H83L208 750H314L439 575ZM45 94Q64 87 85.5 84.5Q107 82 129 82Q166 82 188.5 95Q211 108 211 143V500H310V145Q310 69 268 28Q226 -13 129 -13Q121 -13 110 -12.5Q99 -12 87 -10.5Q75 -9 63.5 -7Q52 -5 45 -3Z" glyph-name="jcircumflex.smcp" horiz-adv-x="417" /><glyph d="M494 500 275 290 514 0H374L207 225L171 190V0H70V750H171V312L346 500Z" glyph-name="k" horiz-adv-x="528" unicode="k" /><glyph d="M503 500 287 290 523 0H386L219 225L183 190V0H85V500H183V312L358 500Z" glyph-name="k.smcp" horiz-adv-x="553" /><glyph d="M221 -250 235 -184Q216 -176 205 -159Q194 -142 194 -120Q194 -91 214.5 -70.5Q235 -50 264 -50Q293 -50 313.5 -70.5Q334 -91 334 -120Q334 -138 326 -154L282 -250ZM494 500 275 290 514 0H374L207 225L171 190V0H70V750H171V312L346 500Z" glyph-name="kcommaaccent" horiz-adv-x="528" unicode="&#311;" /><glyph d="M503 500 287 290 523 0H386L219 225L183 190V0H85V500H183V312L358 500ZM234 -250 248 -184Q229 -176 218 -159Q207 -142 207 -120Q207 -91 227.5 -70.5Q248 -50 277 -50Q306 -50 326.5 -70.5Q347 -91 347 -120Q347 -138 339 -154L295 -250Z" glyph-name="kcommaaccent.smcp" horiz-adv-x="553" /><glyph d="M494 500 275 290 514 0H374L207 225L171 190V0H70V500H171V312L346 500Z" glyph-name="kgreenlandic" horiz-adv-x="528" unicode="&#312;" /><glyph d="M202 -13Q142 -13 111.5 14Q81 41 81 108V750H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="l" horiz-adv-x="275" unicode="l" /><glyph d="M184 94H462V0H85V500H184Z" glyph-name="l.smcp" horiz-adv-x="512" /><glyph d="M104 825 184 1000H305L160 825ZM202 -13Q142 -13 111.5 14Q81 41 81 108V750H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="lacute" horiz-adv-x="275" unicode="&#314;" /><glyph d="M107 575 187 750H308L163 575ZM184 94H462V0H85V500H184Z" glyph-name="lacute.smcp" horiz-adv-x="512" /><glyph d="M331 750 283 550H227L243 750ZM202 -13Q142 -13 111.5 14Q81 41 81 108V750H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="lcaron" horiz-adv-x="275" unicode="&#318;" /><glyph d="M381 600 333 400H277L293 600ZM184 94H462V0H85V500H184Z" glyph-name="lcaron.smcp" horiz-adv-x="512" /><glyph d="M110 -250 124 -184Q105 -176 94 -159Q83 -142 83 -120Q83 -91 103.5 -70.5Q124 -50 153 -50Q182 -50 202.5 -70.5Q223 -91 223 -120Q223 -138 215 -154L171 -250ZM202 -13Q142 -13 111.5 14Q81 41 81 108V750H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="lcommaaccent" horiz-adv-x="275" unicode="&#316;" /><glyph d="M236 -250 250 -184Q231 -176 220 -159Q209 -142 209 -120Q209 -91 229.5 -70.5Q250 -50 279 -50Q308 -50 328.5 -70.5Q349 -91 349 -120Q349 -138 341 -154L297 -250ZM184 94H462V0H85V500H184Z" glyph-name="lcommaaccent.smcp" horiz-adv-x="512" /><glyph d="M241 300Q241 329 261.5 349.5Q282 370 311 370Q340 370 360.5 349.5Q381 329 381 300Q381 271 360.5 250.5Q340 230 311 230Q282 230 261.5 250.5Q241 271 241 300ZM202 -13Q142 -13 111.5 14Q81 41 81 108V750H182V105Q182 91 190.5 86.5Q199 82 221 82V-13Z" glyph-name="ldot" horiz-adv-x="355" unicode="&#320;" /><glyph d="M318 275Q318 304 338.5 324.5Q359 345 388 345Q417 345 437.5 324.5Q458 304 458 275Q458 246 437.5 225.5Q417 205 388 205Q359 205 338.5 225.5Q318 246 318 275ZM184 94H462V0H85V500H184Z" glyph-name="ldot.smcp" horiz-adv-x="512" /><glyph d="M315 120 45 300V382L315 562V459L160 341L315 223Z" glyph-name="less" horiz-adv-x="365" unicode="&lt;" /><glyph d="M50 402 562 763V637L256 436L235 424H800V326H236L257 314L562 113V-13L50 348Z" glyph-name="lessequal" horiz-adv-x="860" unicode="&#8804;" /><glyph d="M80 0V500H580V0Z" glyph-name="logicalnot" horiz-adv-x="660" unicode="&#172;" /><glyph d="M204 557 273 626 455 444 637 626 706 557 524 375 706 193 637 124 455 306 273 124 204 193 386 375ZM185 650V100H725V650ZM80 750H830V0H80Z" glyph-name="lozenge" horiz-adv-x="910" unicode="&#9674;" /><glyph d="M1 234V328L91 379V750H192V436L292 493V399L192 342V105Q192 91 200.5 86.5Q209 82 231 82V-13H212Q152 -13 121.5 14Q91 41 91 108V285Z" glyph-name="lslash" horiz-adv-x="310" unicode="&#322;" /><glyph d="M214 199V94H492V0H115V154L25 114V203L115 243V500H214V288L375 360V271Z" glyph-name="lslash.smcp" horiz-adv-x="542" /><glyph d="M439 472Q470 492 506 502.5Q542 513 574 513Q668 513 710.5 470Q753 427 753 352V0H650V345Q650 418 574 418Q539 418 511 411Q483 404 463 394L460 392Q463 374 463 352V0H360V345Q360 418 284 418Q249 418 221 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216 502.5Q252 513 284 513Q335 513 369.5 500Q404 487 426 463Z" glyph-name="m" horiz-adv-x="818" unicode="m" /><glyph d="M306 59 179 317 182 0H85V500H194L345 189L496 500H605V0H508L511 317L384 59Z" glyph-name="m.smcp" horiz-adv-x="690" /><glyph d="M133 641V732H407V641Z" glyph-name="macron" horiz-adv-x="540" unicode="&#175;" /><glyph d="M60 390H460V292H60Z" glyph-name="minus" horiz-adv-x="520" unicode="&#8722;" /><glyph d="M65 500H168V142Q169 109 192 95.5Q215 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q204 -13 168 -2V-250H65Z" glyph-name="mu" horiz-adv-x="538" unicode="&#181;" /><glyph d="M84 234 191 341 84 448 153 517 260 410 367 517 436 448 329 341 436 234 367 165 260 272 153 165Z" glyph-name="multiply" horiz-adv-x="520" unicode="&#215;" /><glyph d="M370 354Q370 389 347 403.5Q324 418 289 418Q251 418 222 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216.5 502.5Q253 513 289 513Q382 513 427.5 470.5Q473 428 473 352V0H370Z" glyph-name="n" horiz-adv-x="538" unicode="n" /><glyph d="M444 0 183 316 184 0H85V500H154L415 184L414 500H513V0Z" glyph-name="n.smcp" horiz-adv-x="598" /><glyph d="M254 575 334 750H455L310 575ZM370 354Q370 389 347 403.5Q324 418 289 418Q251 418 222 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216.5 502.5Q253 513 289 513Q382 513 427.5 470.5Q473 428 473 352V0H370Z" glyph-name="nacute" horiz-adv-x="538" unicode="&#324;" /><glyph d="M271 575 351 750H472L327 575ZM444 0 183 316 184 0H85V500H154L415 184L414 500H513V0Z" glyph-name="nacute.smcp" horiz-adv-x="598" /><glyph d="M160 750 282 645 404 750H460L335 575H229L104 750ZM370 354Q370 389 347 403.5Q324 418 289 418Q251 418 222 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216.5 502.5Q253 513 289 513Q382 513 427.5 470.5Q473 428 473 352V0H370Z" glyph-name="ncaron" horiz-adv-x="538" unicode="&#328;" /><glyph d="M477 750 352 575H246L121 750H177L299 645L421 750ZM444 0 183 316 184 0H85V500H154L415 184L414 500H513V0Z" glyph-name="ncaron.smcp" horiz-adv-x="598" /><glyph d="M228 -250 242 -184Q223 -176 212 -159Q201 -142 201 -120Q201 -91 221.5 -70.5Q242 -50 271 -50Q300 -50 320.5 -70.5Q341 -91 341 -120Q341 -138 333 -154L289 -250ZM370 354Q370 389 347 403.5Q324 418 289 418Q251 418 222 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216.5 502.5Q253 513 289 513Q382 513 427.5 470.5Q473 428 473 352V0H370Z" glyph-name="ncommaaccent" horiz-adv-x="538" unicode="&#326;" /><glyph d="M256 -250 270 -184Q251 -176 240 -159Q229 -142 229 -120Q229 -91 249.5 -70.5Q270 -50 299 -50Q328 -50 348.5 -70.5Q369 -91 369 -120Q369 -138 361 -154L317 -250ZM444 0 183 316 184 0H85V500H154L415 184L414 500H513V0Z" glyph-name="ncommaaccent.smcp" horiz-adv-x="598" /><glyph d="M431 520Q431 545 426 571Q421 597 406 618.5Q391 640 363.5 654Q336 668 292 668Q250 668 225 652.5Q200 637 186.5 614.5Q173 592 169 566.5Q165 541 165 520Q165 499 168 473Q171 447 184 424.5Q197 402 222.5 387Q248 372 292 372Q336 372 363.5 386.5Q391 401 406 423Q421 445 426 471Q431 497 431 520ZM85 115Q127 98 171.5 91Q216 84 267 84Q316 84 348 104.5Q380 125 399 159.5Q418 194 427 239.5Q436 285 440 336Q417 311 380 294Q343 277 292 277Q214 277 168.5 301Q123 325 99.5 361Q76 397 69.5 439.5Q63 482 63 520Q63 558 71 600.5Q79 643 103.5 679Q128 715 173 739Q218 763 292 763Q373 763 421.5 738Q470 713 495.5 667Q521 621 529 556.5Q537 492 537 414Q537 324 526.5 246Q516 168 486.5 110.5Q457 53 404.5 20Q352 -13 267 -13Q210 -13 164.5 -5.5Q119 2 85 16Z" glyph-name="nine" horiz-adv-x="600" unicode="9" /><glyph d="M431 520Q431 545 426 571Q421 597 406 618.5Q391 640 363.5 654Q336 668 292 668Q250 668 225 652.5Q200 637 186.5 614.5Q173 592 169 566.5Q165 541 165 520Q165 499 168 473Q171 447 184 424.5Q197 402 222.5 387Q248 372 292 372Q336 372 363.5 386.5Q391 401 406 423Q421 445 426 471Q431 497 431 520ZM85 115Q127 98 171.5 91Q216 84 267 84Q316 84 348 104.5Q380 125 399 159.5Q418 194 427 239.5Q436 285 440 336Q417 311 380 294Q343 277 292 277Q214 277 168.5 301Q123 325 99.5 361Q76 397 69.5 439.5Q63 482 63 520Q63 558 71 600.5Q79 643 103.5 679Q128 715 173 739Q218 763 292 763Q373 763 421.5 738Q470 713 495.5 667Q521 621 529 556.5Q537 492 537 414Q537 324 526.5 246Q516 168 486.5 110.5Q457 53 404.5 20Q352 -13 267 -13Q210 -13 164.5 -5.5Q119 2 85 16Z" glyph-name="nine.LP" horiz-adv-x="600" /><glyph d="M436 270Q436 295 431 321Q426 347 411 368.5Q396 390 368.5 404Q341 418 297 418Q255 418 230 402.5Q205 387 191.5 364.5Q178 342 174 316.5Q170 291 170 270Q170 249 173 223Q176 197 189 174.5Q202 152 227.5 137Q253 122 297 122Q341 122 368.5 136.5Q396 151 411 173Q426 195 431 221Q436 247 436 270ZM90 -135Q132 -152 176.5 -159Q221 -166 272 -166Q321 -166 353 -145.5Q385 -125 404 -90.5Q423 -56 432 -10.5Q441 35 445 86Q422 61 385 44Q348 27 297 27Q219 27 173.5 51Q128 75 104.5 111Q81 147 74.5 189.5Q68 232 68 270Q68 308 76 350.5Q84 393 108.5 429Q133 465 178 489Q223 513 297 513Q378 513 426.5 488Q475 463 500.5 417Q526 371 534 306.5Q542 242 542 164Q542 74 531.5 -4Q521 -82 491.5 -139.5Q462 -197 409.5 -230Q357 -263 272 -263Q215 -263 169.5 -255.5Q124 -248 90 -234Z" glyph-name="nine.OP" horiz-adv-x="605" /><glyph d="M80 0V750H830V0Z" glyph-name="notequal" horiz-adv-x="910" unicode="&#8800;" /><glyph d="M477 724Q476 584 352 587Q321 587 302 597Q283 607 269 618.5Q255 630 242.5 639.5Q230 649 212 649Q197 649 188 644Q179 639 174.5 631.5Q170 624 168.5 614Q167 604 167 594L87 601Q89 673 119 706.5Q149 740 212 739Q243 738 262 728Q281 718 295 706.5Q309 695 321.5 685.5Q334 676 352 676Q367 676 376 681Q385 686 389.5 694Q394 702 395.5 711.5Q397 721 397 731ZM370 354Q370 389 347 403.5Q324 418 289 418Q251 418 222 411Q193 404 173 394V0H70V500H139L149 472Q180 492 216.5 502.5Q253 513 289 513Q382 513 427.5 470.5Q473 428 473 352V0H370Z" glyph-name="ntilde" horiz-adv-x="538" unicode="&#241;" /><glyph d="M494 724Q493 584 369 587Q338 587 319 597Q300 607 286 618.5Q272 630 259.5 639.5Q247 649 229 649Q214 649 205 644Q196 639 191.5 631.5Q187 624 185.5 614Q184 604 184 594L104 601Q106 673 136 706.5Q166 740 229 739Q260 738 279 728Q298 718 312 706.5Q326 695 338.5 685.5Q351 676 369 676Q384 676 393 681Q402 686 406.5 694Q411 702 412.5 711.5Q414 721 414 731ZM444 0 183 316 184 0H85V500H154L415 184L414 500H513V0Z" glyph-name="ntilde.smcp" horiz-adv-x="598" /><glyph d="M226 465H326L354 536H254ZM288 366H189L144 249H41L86 366H10L39 465H124L152 536H75L104 635H190L233 750H336L292 635H393L436 750H539L495 635H575L546 536H457L429 465H510L481 366H392L347 249H245Z" glyph-name="numbersign" horiz-adv-x="585" unicode="#" /><glyph d="M226 215H326L354 286H254ZM288 116H189L144 -1H41L86 116H10L39 215H124L152 286H75L104 385H190L233 500H336L292 385H393L436 500H539L495 385H575L546 286H457L429 215H510L481 116H392L347 -1H245Z" glyph-name="numbersign.OP" horiz-adv-x="585" /><glyph d="M392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="o" horiz-adv-x="554" unicode="o" /><glyph d="M436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="o.smcp" horiz-adv-x="606" /><glyph d="M249 575 329 750H450L305 575ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="oacute" horiz-adv-x="554" unicode="&#243;" /><glyph d="M275 575 355 750H476L331 575ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="oacute.smcp" horiz-adv-x="606" /><glyph d="M277 572Q241 572 211 582.5Q181 593 158.5 614.5Q136 636 123 669.5Q110 703 109 750H176Q177 723 182 705.5Q187 688 198.5 677.5Q210 667 229 662.5Q248 658 277 658Q306 658 325 662.5Q344 667 355.5 677.5Q367 688 372 705.5Q377 723 378 750H445Q444 703 431 669.5Q418 636 395.5 614.5Q373 593 343 582.5Q313 572 277 572ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="obreve" horiz-adv-x="554" unicode="&#335;" /><glyph d="M303 572Q267 572 237 582.5Q207 593 184.5 614.5Q162 636 149 669.5Q136 703 135 750H202Q203 723 208 705.5Q213 688 224.5 677.5Q236 667 255 662.5Q274 658 303 658Q332 658 351 662.5Q370 667 381.5 677.5Q393 688 398 705.5Q403 723 404 750H471Q470 703 457 669.5Q444 636 421.5 614.5Q399 593 369 582.5Q339 572 303 572ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="obreve.smcp" horiz-adv-x="606" /><glyph d="M399 575 277 680 155 575H99L224 750H330L455 575ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="ocircumflex" horiz-adv-x="554" unicode="&#244;" /><glyph d="M425 575 303 680 181 575H125L250 750H356L481 575ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="ocircumflex.smcp" horiz-adv-x="606" /><glyph d="M317 667Q317 696 337.5 716.5Q358 737 387 737Q416 737 436.5 716.5Q457 696 457 667Q457 638 436.5 617.5Q416 597 387 597Q358 597 337.5 617.5Q317 638 317 667ZM97 667Q97 696 117.5 716.5Q138 737 167 737Q196 737 216.5 716.5Q237 696 237 667Q237 638 216.5 617.5Q196 597 167 597Q138 597 117.5 617.5Q97 638 97 667ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="odieresis" horiz-adv-x="554" unicode="&#246;" /><glyph d="M343 667Q343 696 363.5 716.5Q384 737 413 737Q442 737 462.5 716.5Q483 696 483 667Q483 638 462.5 617.5Q442 597 413 597Q384 597 363.5 617.5Q343 638 343 667ZM123 667Q123 696 143.5 716.5Q164 737 193 737Q222 737 242.5 716.5Q263 696 263 667Q263 638 242.5 617.5Q222 597 193 597Q164 597 143.5 617.5Q123 638 123 667ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="odieresis.smcp" horiz-adv-x="606" /><glyph d="M615 418Q583 418 561.5 408.5Q540 399 526.5 381.5Q513 364 506.5 341Q500 318 497 291H708Q708 316 705 339Q702 362 692 379.5Q682 397 664 407.5Q646 418 615 418ZM277 82Q314 82 337 94Q360 106 372 127Q384 148 388 176.5Q392 205 392 238V261Q392 295 388 323.5Q384 352 372 373Q360 394 337 406Q314 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82ZM498 202Q501 176 508.5 154Q516 132 529.5 116Q543 100 564 91Q585 82 615 82Q639 82 660 84Q681 86 701.5 90Q722 94 743 101Q764 108 787 118V21Q739 -1 695 -7Q651 -13 615 -13Q555 -13 514 3.5Q473 20 447 50Q422 20 381 3.5Q340 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q340 513 381 496.5Q422 480 447 449Q473 480 514 496.5Q555 513 615 513Q681 513 720 491.5Q759 470 780 434Q801 398 807.5 350Q814 302 814 250V202Z" glyph-name="oe" horiz-adv-x="874" unicode="&#339;" /><glyph d="M169 250Q169 215 176 186Q183 157 199.5 136.5Q216 116 243.5 105Q271 94 312 94H359V406H312Q271 406 243.5 395Q216 384 199.5 363.5Q183 343 176 314Q169 285 169 250ZM673 209H458V94H706V0H313Q248 0 202 17.5Q156 35 126.5 67.5Q97 100 83.5 146.5Q70 193 70 250Q70 307 83.5 353.5Q97 400 126.5 432.5Q156 465 202 482.5Q248 500 313 500H706V406H458V303H673Z" glyph-name="oe.smcp" horiz-adv-x="776" /><glyph d="M352 0Q310 -19 280.5 -46Q251 -73 251 -110Q251 -133 265 -144.5Q279 -156 302 -156Q323 -156 339.5 -153Q356 -150 365 -147V-215Q353 -220 332.5 -225Q312 -230 280 -230Q257 -230 236 -224Q215 -218 198.5 -205Q182 -192 172 -172.5Q162 -153 162 -126Q162 -110 167.5 -91Q173 -72 186.5 -52.5Q200 -33 224 -13Q248 7 286 26Z" glyph-name="ogonek" horiz-adv-x="526" unicode="&#731;" /><glyph d="M249 575 104 750H225L305 575ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="ograve" horiz-adv-x="554" unicode="&#242;" /><glyph d="M275 575 130 750H251L331 575ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="ograve.smcp" horiz-adv-x="606" /><glyph d="M335 575 415 750H536L391 575ZM158 575 238 750H360L215 575ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="ohungarumlaut" horiz-adv-x="554" unicode="&#337;" /><glyph d="M356 575 436 750H557L412 575ZM179 575 259 750H381L236 575ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="ohungarumlaut.smcp" horiz-adv-x="606" /><glyph d="M140 641V732H414V641ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="omacron" horiz-adv-x="554" unicode="&#333;" /><glyph d="M166 641V732H440V641ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="omacron.smcp" horiz-adv-x="606" /><glyph d="M490 0H105V88H244V659L90 653V704L299 763H351V88H490Z" glyph-name="one" horiz-adv-x="600" unicode="1" /><glyph d="M251 763V0H144V659L17 654V712L199 763Z" glyph-name="one.LP" horiz-adv-x="360" /><glyph d="M251 513V0H144V409L17 404V462L199 513Z" glyph-name="one.OP" horiz-adv-x="360" /><glyph d="M283 292H50V390H283Z" glyph-name="onethirdemdash" horiz-adv-x="333" /><glyph d="M186 540Q137 536 137 499Q137 482 146.5 469.5Q156 457 185 457Q201 457 217.5 461.5Q234 466 249 473V545ZM39 499Q39 555 75 587.5Q111 620 184 625L249 629Q248 652 237 662.5Q226 673 197 673Q169 673 137.5 665.5Q106 658 72 646V737Q104 751 137.5 757Q171 763 197 763Q242 763 271 753Q300 743 316.5 725Q333 707 339.5 682Q346 657 346 626V375H277L272 388Q224 367 183 367Q105 367 72 401.5Q39 436 39 499Z" glyph-name="ordfeminine" horiz-adv-x="400" unicode="&#170;" /><glyph d="M164 518H235L200 612ZM288 375 268 433H131L113 375H5L165 763H235L396 375Z" glyph-name="ordfeminine.smcp" horiz-adv-x="400" /><glyph d="M151 565Q151 507 170 483Q189 459 231 459Q276 459 297.5 483.5Q319 508 319 565Q319 622 297.5 646.5Q276 671 231 671Q189 671 170 647Q151 623 151 565ZM56 565Q56 662 97 712.5Q138 763 231 763Q325 763 369 712.5Q413 662 413 565Q413 468 369 417.5Q325 367 231 367Q138 367 97 417.5Q56 468 56 565Z" glyph-name="ordmasculine" horiz-adv-x="469" unicode="&#186;" /><glyph d="M151 565Q151 507 170 483Q189 459 231 459Q276 459 297.5 483.5Q319 508 319 565Q319 622 297.5 646.5Q276 671 231 671Q189 671 170 647Q151 623 151 565ZM56 565Q56 662 97 712.5Q138 763 231 763Q325 763 369 712.5Q413 662 413 565Q413 468 369 417.5Q325 367 231 367Q138 367 97 417.5Q56 468 56 565Z" glyph-name="ordmasculine.smcp" horiz-adv-x="469" /><glyph d="M392 250Q392 268 391 285.5Q390 303 388 319L228 89Q246 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM162 250Q162 229 162.5 209.5Q163 190 166 173L330 409Q319 413 306 415.5Q293 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250ZM462 425Q480 391 487 347Q494 303 494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q212 -13 169 5L166 0H45L94 70Q74 105 67 150Q60 195 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q344 513 389 493L394 500H515Z" glyph-name="oslash" horiz-adv-x="560" unicode="&#248;" /><glyph d="M170 250Q170 225 172 203Q174 181 180 161L354 411Q343 414 330.5 416Q318 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250ZM436 250Q436 274 433.5 296Q431 318 425 338L251 89Q271 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q270 -13 243 -8Q216 -3 193 6L189 0H68L116 69Q91 104 80.5 149.5Q70 195 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q366 513 412 493L417 500H538L490 431Q514 396 525 350Q536 304 536 250Z" glyph-name="oslash.smcp" horiz-adv-x="606" /><glyph d="M472 724Q471 584 347 587Q316 587 297 597Q278 607 264 618.5Q250 630 237.5 639.5Q225 649 207 649Q192 649 183 644Q174 639 169.5 631.5Q165 624 163.5 614Q162 604 162 594L82 601Q84 673 114 706.5Q144 740 207 739Q238 738 257 728Q276 718 290 706.5Q304 695 316.5 685.5Q329 676 347 676Q362 676 371 681Q380 686 384.5 694Q389 702 390.5 711.5Q392 721 392 731ZM392 250Q392 286 389 316.5Q386 347 374 369.5Q362 392 339 405Q316 418 277 418Q237 418 214 405Q191 392 179.5 369.5Q168 347 165 316.5Q162 286 162 250Q162 214 165 183.5Q168 153 179.5 130.5Q191 108 214 95Q237 82 277 82Q316 82 339 95Q362 108 374 130.5Q386 153 389 183.5Q392 214 392 250ZM494 250Q494 191 485.5 143Q477 95 453.5 60Q430 25 387.5 6Q345 -13 277 -13Q209 -13 167 6Q125 25 101 60Q77 95 68.5 143Q60 191 60 250Q60 308 68.5 356.5Q77 405 101 440Q125 475 167 494Q209 513 277 513Q345 513 387.5 494Q430 475 453.5 440Q477 405 485.5 356.5Q494 308 494 250Z" glyph-name="otilde" horiz-adv-x="554" unicode="&#245;" /><glyph d="M498 724Q497 584 373 587Q342 587 323 597Q304 607 290 618.5Q276 630 263.5 639.5Q251 649 233 649Q218 649 209 644Q200 639 195.5 631.5Q191 624 189.5 614Q188 604 188 594L108 601Q110 673 140 706.5Q170 740 233 739Q264 738 283 728Q302 718 316 706.5Q330 695 342.5 685.5Q355 676 373 676Q388 676 397 681Q406 686 410.5 694Q415 702 416.5 711.5Q418 721 418 731ZM436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 193 524 144.5Q512 96 484.5 61Q457 26 412.5 6.5Q368 -13 303 -13Q238 -13 193.5 6.5Q149 26 121.5 61Q94 96 82 144.5Q70 193 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="otilde.smcp" horiz-adv-x="606" /><glyph d="M208 92Q223 88 241.5 85Q260 82 284 82Q321 82 343 95.5Q365 109 377 132Q389 155 393 185.5Q397 216 397 250Q397 284 393 314.5Q389 345 377 368Q365 391 343 404.5Q321 418 284 418Q247 418 220 411.5Q193 405 171 394V105Q184 98 208 92ZM70 500H137L147 472Q181 491 214.5 502Q248 513 284 513Q347 513 389 494Q431 475 456 440.5Q481 406 491 357.5Q501 309 501 250Q501 191 491 142.5Q481 94 456 59.5Q431 25 389 6Q347 -13 284 -13Q254 -13 227.5 -6.5Q201 0 171 14V-250H70Z" glyph-name="p" horiz-adv-x="561" unicode="p" /><glyph d="M362 332Q362 346 359 359Q356 372 348.5 382.5Q341 393 326.5 399.5Q312 406 290 406H184V257H290Q312 257 326.5 263.5Q341 270 348.5 280.5Q356 291 359 304.5Q362 318 362 332ZM184 164V0H85V500H292Q340 500 373.5 486.5Q407 473 427.5 450Q448 427 457 397Q466 367 466 333Q466 299 457 268.5Q448 238 427 214.5Q406 191 373 177.5Q340 164 292 164Z" glyph-name="p.smcp" horiz-adv-x="511" /><glyph d="M149 518Q149 499 152.5 479Q156 459 166 441.5Q176 424 194 410Q212 396 241 390V645Q212 639 194 625Q176 611 166 593.5Q156 576 152.5 556.5Q149 537 149 518ZM425 650H346V385H425ZM241 0V288Q184 295 144.5 316Q105 337 81 368Q57 399 46 437.5Q35 476 35 518Q35 563 48.5 605Q62 647 92 679.5Q122 712 171.5 731Q221 750 293 750H531V0H425V285H346V0Z" glyph-name="paragraph" horiz-adv-x="606" unicode="&#182;" /><glyph d="M190 -206Q171 -178 149 -130Q127 -82 108.5 -17Q90 48 77.5 129Q65 210 65 305Q65 400 77.5 481Q90 562 108.5 627Q127 692 149 740Q171 788 190 816H299Q280 788 258 740Q236 692 217 627Q198 562 185.5 481Q173 400 173 305Q173 210 185.5 129Q198 48 217 -17Q236 -82 258 -130Q280 -178 299 -206Z" glyph-name="parenleft" horiz-adv-x="344" unicode="(" /><glyph d="M45 -206Q64 -178 86 -130Q108 -82 127 -17Q146 48 158.5 129Q171 210 171 305Q171 400 158.5 481Q146 562 127 627Q108 692 86 740Q64 788 45 816H154Q173 788 195 740Q217 692 235.5 627Q254 562 266.5 481Q279 400 279 305Q279 210 266.5 129Q254 48 235.5 -17Q217 -82 195 -130Q173 -178 154 -206Z" glyph-name="parenright" horiz-adv-x="344" unicode=")" /><glyph d="M389 251Q389 287 386 317.5Q383 348 371.5 370Q360 392 337.5 404.5Q315 417 276 417Q236 417 213.5 404.5Q191 392 179.5 370Q168 348 165.5 317.5Q163 287 163 251Q163 215 165.5 184Q168 153 179.5 130.5Q191 108 213.5 95.5Q236 83 276 83Q315 83 337.5 95.5Q360 108 371.5 130.5Q383 153 386 184Q389 215 389 251ZM492 251Q492 192 483 143.5Q474 95 450 60.5Q426 26 384 6.5Q342 -13 276 -13Q210 -13 168 6.5Q126 26 102 60.5Q78 95 69 143.5Q60 192 60 251Q60 309 69 357Q78 405 102 439.5Q126 474 168 493.5Q210 513 276 513Q308 513 335 505.5Q362 498 379 487Q374 525 364.5 558Q355 591 338.5 615Q322 639 297.5 653Q273 667 239 667Q189 667 145 657Q101 647 74 633V730Q94 741 134 752Q174 763 239 763Q316 763 365 729Q414 695 442.5 629.5Q471 564 481.5 469Q492 374 492 251Z" glyph-name="partialdiff" horiz-adv-x="552" unicode="&#8706;" /><glyph d="M748 301Q704 301 684.5 276.5Q665 252 665 192Q665 132 684.5 107Q704 82 748 82Q793 82 812 107Q831 132 831 192Q831 252 812 276.5Q793 301 748 301ZM237 449Q282 449 301 473.5Q320 498 320 558Q320 618 301 643Q282 668 237 668Q192 668 173 643Q154 618 154 558Q154 498 173 473.5Q192 449 237 449ZM167 0 699 750H818L286 0ZM748 397Q845 397 887.5 345Q930 293 930 192Q930 91 887.5 39Q845 -13 748 -13Q652 -13 609.5 39Q567 91 567 192Q567 292 609.5 344.5Q652 397 748 397ZM237 353Q140 353 97.5 405Q55 457 55 558Q55 659 97.5 711Q140 763 237 763Q333 763 376 710.5Q419 658 419 558Q419 457 376 405Q333 353 237 353Z" glyph-name="percent" horiz-adv-x="985" unicode="%" /><glyph d="M40 57Q40 86 60.5 106.5Q81 127 110 127Q139 127 159.5 106.5Q180 86 180 57Q180 28 159.5 7.5Q139 -13 110 -13Q81 -13 60.5 7.5Q40 28 40 57Z" glyph-name="period" horiz-adv-x="220" unicode="." /><glyph d="M40 375Q40 404 60.5 424.5Q81 445 110 445Q139 445 159.5 424.5Q180 404 180 375Q180 346 159.5 325.5Q139 305 110 305Q81 305 60.5 325.5Q40 346 40 375Z" glyph-name="periodcentered" horiz-adv-x="220" unicode="&#183;" /><glyph d="M748 301Q704 301 684.5 276.5Q665 252 665 192Q665 132 684.5 107Q704 82 748 82Q793 82 812 107Q831 132 831 192Q831 252 812 276.5Q793 301 748 301ZM1168 301Q1124 301 1104.5 276.5Q1085 252 1085 192Q1085 132 1104.5 107Q1124 82 1168 82Q1213 82 1232 107Q1251 132 1251 192Q1251 252 1232 276.5Q1213 301 1168 301ZM237 449Q282 449 301 473.5Q320 498 320 558Q320 618 301 643Q282 668 237 668Q192 668 173 643Q154 618 154 558Q154 498 173 473.5Q192 449 237 449ZM167 0 699 750H818L286 0ZM1168 397Q1265 397 1307.5 345Q1350 293 1350 192Q1350 91 1307.5 39Q1265 -13 1168 -13Q1072 -13 1029.5 39Q987 91 987 192Q987 292 1029.5 344.5Q1072 397 1168 397ZM748 397Q845 397 887.5 345Q930 293 930 192Q930 91 887.5 39Q845 -13 748 -13Q652 -13 609.5 39Q567 91 567 192Q567 292 609.5 344.5Q652 397 748 397ZM237 353Q140 353 97.5 405Q55 457 55 558Q55 659 97.5 711Q140 763 237 763Q333 763 376 710.5Q419 658 419 558Q419 457 376 405Q333 353 237 353Z" glyph-name="perthousand" horiz-adv-x="1405" unicode="&#8240;" /><glyph d="M321 269H366Q377 269 381.5 275Q386 281 386 288Q386 295 381.5 301Q377 307 366 307H321ZM321 194V115H241V385H368Q423 385 445 356.5Q467 328 467 289Q467 270 461.5 253Q456 236 444.5 223Q433 210 414 202Q395 194 368 194ZM340 71Q379 71 411.5 77.5Q444 84 468 103Q492 122 505.5 157.5Q519 193 519 250Q519 307 505.5 342.5Q492 378 468 397Q444 416 411.5 422.5Q379 429 340 429Q301 429 268.5 422.5Q236 416 212 397Q188 378 174.5 342.5Q161 307 161 250Q161 193 174.5 157.5Q188 122 212 103Q236 84 268.5 77.5Q301 71 340 71ZM340 -13Q286 -13 237.5 -2.5Q189 8 152.5 37.5Q116 67 94 118Q72 169 72 250Q72 331 94 382.5Q116 434 152.5 463Q189 492 237.5 502.5Q286 513 340 513Q394 513 442.5 502.5Q491 492 527.5 463Q564 434 586 382.5Q608 331 608 250Q608 169 586 118Q564 67 527.5 37.5Q491 8 442.5 -2.5Q394 -13 340 -13Z" glyph-name="pi" horiz-adv-x="680" unicode="&#960;" /><glyph d="M211 141V292H60V390H211V541H309V390H460V292H309V141Z" glyph-name="plus" horiz-adv-x="520" unicode="+" /><glyph d="M60 43V141H460V43ZM211 191V342H60V440H211V591H309V440H460V342H309V191Z" glyph-name="plusminus" horiz-adv-x="520" unicode="&#177;" /><glyph d="M311 279H351Q359 279 362.5 283.5Q366 288 366 293Q366 298 362.5 302.5Q359 307 351 307H311ZM311 201V115H232V385H350Q406 385 427.5 359Q449 333 449 294Q449 269 442 251Q435 233 418 219L472 115H384L342 201ZM340 71Q379 71 411.5 77.5Q444 84 468 103Q492 122 505.5 157.5Q519 193 519 250Q519 307 505.5 342.5Q492 378 468 397Q444 416 411.5 422.5Q379 429 340 429Q301 429 268.5 422.5Q236 416 212 397Q188 378 174.5 342.5Q161 307 161 250Q161 193 174.5 157.5Q188 122 212 103Q236 84 268.5 77.5Q301 71 340 71ZM340 -13Q286 -13 237.5 -2.5Q189 8 152.5 37.5Q116 67 94 118Q72 169 72 250Q72 331 94 382.5Q116 434 152.5 463Q189 492 237.5 502.5Q286 513 340 513Q394 513 442.5 502.5Q491 492 527.5 463Q564 434 586 382.5Q608 331 608 250Q608 169 586 118Q564 67 527.5 37.5Q491 8 442.5 -2.5Q394 -13 340 -13Z" glyph-name="product" horiz-adv-x="680" unicode="&#8719;" /><glyph glyph-name="punctuationspace" horiz-adv-x="220" unicode="&#8200;" /><glyph d="M353 92Q377 98 390 105V394Q368 405 341 411.5Q314 418 277 418Q240 418 218 404.5Q196 391 184 368Q172 345 168 314.5Q164 284 164 250Q164 216 168 185.5Q172 155 184 132Q196 109 218 95.5Q240 82 277 82Q301 82 319.5 85Q338 88 353 92ZM491 -250H390V14Q360 0 333.5 -6.5Q307 -13 277 -13Q214 -13 172 6Q130 25 105 59.5Q80 94 70 142.5Q60 191 60 250Q60 309 70 357.5Q80 406 105 440.5Q130 475 172 494Q214 513 277 513Q313 513 346.5 502Q380 491 414 472L424 500H491Z" glyph-name="q" horiz-adv-x="561" unicode="q" /><glyph d="M436 250Q436 286 430.5 316.5Q425 347 410.5 369.5Q396 392 370 405Q344 418 303 418Q262 418 236 405Q210 392 195.5 369.5Q181 347 175.5 316.5Q170 286 170 250Q170 214 175.5 183.5Q181 153 195.5 130.5Q210 108 236 95Q262 82 303 82Q344 82 370 95Q396 108 410.5 130.5Q425 153 430.5 183.5Q436 214 436 250ZM536 250Q536 177 516.5 119Q497 61 451 28V27L687 -14V-124L235 -6Q149 17 109.5 78Q70 139 70 250Q70 307 82 355.5Q94 404 121.5 439Q149 474 193.5 493.5Q238 513 303 513Q368 513 412.5 493.5Q457 474 484.5 439Q512 404 524 355.5Q536 307 536 250Z" glyph-name="q.smcp" horiz-adv-x="606" /><glyph d="M110 57Q110 86 130.5 106.5Q151 127 180 127Q209 127 229.5 106.5Q250 86 250 57Q250 28 229.5 7.5Q209 -13 180 -13Q151 -13 130.5 7.5Q110 28 110 57ZM263 345Q242 336 230.5 324.5Q219 313 213 296.5Q207 280 206 258.5Q205 237 205 210H132Q128 231 125 252.5Q122 274 122 291Q122 313 127 333Q132 353 145 371.5Q158 390 180 405.5Q202 421 235 434Q256 442 275 450.5Q294 459 309 471.5Q324 484 333 504Q342 524 342 555Q342 580 336.5 600.5Q331 621 315.5 635.5Q300 650 272 658Q244 666 198 666Q151 666 108 656.5Q65 647 35 636V734Q49 740 69.5 745Q90 750 112.5 754Q135 758 157 760.5Q179 763 197 763Q276 763 324.5 745.5Q373 728 400 698.5Q427 669 436 632Q445 595 445 556Q445 508 432 475Q419 442 395 419Q371 396 337.5 379Q304 362 263 345Z" glyph-name="question" horiz-adv-x="500" unicode="?" /><glyph d="M370 443Q370 414 349.5 393.5Q329 373 300 373Q271 373 250.5 393.5Q230 414 230 443Q230 472 250.5 492.5Q271 513 300 513Q329 513 349.5 492.5Q370 472 370 443ZM217 155Q238 164 249.5 175.5Q261 187 267 203.5Q273 220 274 241.5Q275 263 275 290H348Q352 269 355 247.5Q358 226 358 209Q358 187 353 167Q348 147 335 128.5Q322 110 300 94.5Q278 79 245 66Q224 58 205 49.5Q186 41 171 28.5Q156 16 147 -4Q138 -24 138 -55Q138 -80 143.5 -100.5Q149 -121 164.5 -135.5Q180 -150 208 -158Q236 -166 282 -166Q329 -166 372 -156.5Q415 -147 445 -136V-234Q431 -240 410.5 -245Q390 -250 367.5 -254Q345 -258 323 -260.5Q301 -263 283 -263Q204 -263 155.5 -245.5Q107 -228 80 -198.5Q53 -169 44 -132Q35 -95 35 -56Q35 -8 48 25Q61 58 85 81Q109 104 142.5 121Q176 138 217 155Z" glyph-name="questiondown" horiz-adv-x="500" unicode="&#191;" /><glyph d="M243 500 212 750H348L317 500ZM73 500 42 750H178L147 500Z" glyph-name="quotedbl" horiz-adv-x="390" unicode="&quot;" /><glyph d="M218 -160Q231 -130 240 -91Q248 -58 251.5 -12.5Q255 33 247 90H372Q370 31 354 -15Q338 -61 318 -93Q295 -131 267 -160ZM38 -160Q51 -130 60 -91Q68 -58 71.5 -12.5Q75 33 67 90H192Q190 31 174 -15Q158 -61 138 -93Q115 -131 87 -160Z" glyph-name="quotedblbase" horiz-adv-x="370" unicode="&#8222;" /><glyph d="M332 750Q319 720 310 681Q302 648 298.5 602.5Q295 557 303 500H178Q180 558 196 604.5Q212 651 232 683Q255 721 283 750ZM152 750Q139 720 130 681Q122 648 118.5 602.5Q115 557 123 500H-2Q0 558 16 604.5Q32 651 52 683Q75 721 103 750Z" glyph-name="quotedblleft" horiz-adv-x="370" unicode="&#8220;" /><glyph d="M218 500Q231 530 240 569Q248 602 251.5 647.5Q255 693 247 750H372Q370 691 354 645Q338 599 318 567Q295 529 267 500ZM38 500Q51 530 60 569Q68 602 71.5 647.5Q75 693 67 750H192Q190 691 174 645Q158 599 138 567Q115 529 87 500Z" glyph-name="quotedblright" horiz-adv-x="370" unicode="&#8221;" /><glyph d="M152 750Q139 720 130 681Q122 648 118.5 602.5Q115 557 123 500H-2Q0 558 16 604.5Q32 651 52 683Q75 721 103 750Z" glyph-name="quoteleft" horiz-adv-x="190" unicode="&#8216;" /><glyph d="M38 500Q51 530 60 569Q68 602 71.5 647.5Q75 693 67 750H192Q190 691 174 645Q158 599 138 567Q115 529 87 500Z" glyph-name="quoteright" horiz-adv-x="190" unicode="&#8217;" /><glyph d="M38 -160Q51 -130 60 -91Q68 -58 71.5 -12.5Q75 33 67 90H192Q190 31 174 -15Q158 -61 138 -93Q115 -131 87 -160Z" glyph-name="quotesinglbase" horiz-adv-x="190" unicode="&#8218;" /><glyph d="M73 500 42 750H178L147 500Z" glyph-name="quotesingle" horiz-adv-x="220" unicode="&apos;" /><glyph d="M363 403Q344 410 324 414Q304 418 289 418Q249 418 222 411Q195 404 173 394V0H70V500H139L149 472Q214 513 289 513Q306 513 326 511Q346 509 363 505Z" glyph-name="r" horiz-adv-x="403" unicode="r" /><glyph d="M371 341Q371 354 368.5 366Q366 378 358 387Q350 396 336 401Q322 406 299 406H184V276H299Q322 276 336 281Q350 286 358 295Q366 304 368.5 315.5Q371 327 371 341ZM296 183H184V0H85V500H303Q351 500 384 488Q417 476 437.5 455Q458 434 466.5 405Q475 376 475 342Q475 296 455.5 258Q436 220 395 200L511 0H391Z" glyph-name="r.smcp" horiz-adv-x="551" /><glyph d="M194 575 274 750H395L250 575ZM363 403Q344 410 324 414Q304 418 289 418Q249 418 222 411Q195 404 173 394V0H70V500H139L149 472Q214 513 289 513Q306 513 326 511Q346 509 363 505Z" glyph-name="racute" horiz-adv-x="403" unicode="&#341;" /><glyph d="M238 575 318 750H439L294 575ZM371 341Q371 354 368.5 366Q366 378 358 387Q350 396 336 401Q322 406 299 406H184V276H299Q322 276 336 281Q350 286 358 295Q366 304 368.5 315.5Q371 327 371 341ZM296 183H184V0H85V500H303Q351 500 384 488Q417 476 437.5 455Q458 434 466.5 405Q475 376 475 342Q475 296 455.5 258Q436 220 395 200L511 0H391Z" glyph-name="racute.smcp" horiz-adv-x="551" /><glyph d="M185 400V100H475V400ZM80 500H580V0H80Z" glyph-name="radical" horiz-adv-x="660" unicode="&#8730;" /><glyph d="M400 750 275 575H169L44 750H100L222 645L344 750ZM363 403Q344 410 324 414Q304 418 289 418Q249 418 222 411Q195 404 173 394V0H70V500H139L149 472Q214 513 289 513Q306 513 326 511Q346 509 363 505Z" glyph-name="rcaron" horiz-adv-x="403" unicode="&#345;" /><glyph d="M444 750 319 575H213L88 750H144L266 645L388 750ZM371 341Q371 354 368.5 366Q366 378 358 387Q350 396 336 401Q322 406 299 406H184V276H299Q322 276 336 281Q350 286 358 295Q366 304 368.5 315.5Q371 327 371 341ZM296 183H184V0H85V500H303Q351 500 384 488Q417 476 437.5 455Q458 434 466.5 405Q475 376 475 342Q475 296 455.5 258Q436 220 395 200L511 0H391Z" glyph-name="rcaron.smcp" horiz-adv-x="551" /><glyph d="M79 -250 93 -184Q74 -176 63 -159Q52 -142 52 -120Q52 -91 72.5 -70.5Q93 -50 122 -50Q151 -50 171.5 -70.5Q192 -91 192 -120Q192 -138 184 -154L140 -250ZM363 403Q344 410 324 414Q304 418 289 418Q249 418 222 411Q195 404 173 394V0H70V500H139L149 472Q214 513 289 513Q306 513 326 511Q346 509 363 505Z" glyph-name="rcommaaccent" horiz-adv-x="403" unicode="&#343;" /><glyph d="M241 -250 255 -184Q236 -176 225 -159Q214 -142 214 -120Q214 -91 234.5 -70.5Q255 -50 284 -50Q313 -50 333.5 -70.5Q354 -91 354 -120Q354 -138 346 -154L302 -250ZM371 341Q371 354 368.5 366Q366 378 358 387Q350 396 336 401Q322 406 299 406H184V276H299Q322 276 336 281Q350 286 358 295Q366 304 368.5 315.5Q371 327 371 341ZM296 183H184V0H85V500H303Q351 500 384 488Q417 476 437.5 455Q458 434 466.5 405Q475 376 475 342Q475 296 455.5 258Q436 220 395 200L511 0H391Z" glyph-name="rcommaaccent.smcp" horiz-adv-x="551" /><glyph d="M189 651Q189 656 187 658.5Q185 661 177 661H152V639H177Q185 639 187 642.5Q189 646 189 651ZM152 600V550H112V700H179Q209 700 220.5 686.5Q232 673 232 651Q232 621 213 608L241 550H196L175 600ZM172 721Q151 721 134 717.5Q117 714 104 704Q91 694 84 675Q77 656 77 625Q77 594 84 575Q91 556 104 546Q117 536 134 532.5Q151 529 172 529Q193 529 210 532.5Q227 536 240 546Q253 556 260 575Q267 594 267 625Q267 656 260 675Q253 694 240 704Q227 714 210 717.5Q193 721 172 721ZM172 763Q200 763 225 757.5Q250 752 269.5 737Q289 722 300.5 695Q312 668 312 625Q312 583 300.5 556Q289 529 269.5 513.5Q250 498 225 492.5Q200 487 172 487Q144 487 119 492.5Q94 498 74.5 513.5Q55 529 43.5 556Q32 583 32 625Q32 668 43.5 695Q55 722 74.5 737Q94 752 119 757.5Q144 763 172 763Z" glyph-name="registered" horiz-adv-x="344" unicode="&#174;" /><glyph d="M324 662Q324 688 312.5 697.5Q301 707 280 707Q258 707 247 697.5Q236 688 236 662Q236 638 247 628.5Q258 619 280 619Q301 619 312.5 628.5Q324 638 324 662ZM381 662Q381 610 353 586Q325 562 280 562Q234 562 206 586Q178 610 178 662Q178 715 206 739Q234 763 280 763Q325 763 353 739Q381 715 381 662Z" glyph-name="ring" horiz-adv-x="602" unicode="&#730;" /><glyph d="M157 354Q157 343 160.5 334.5Q164 326 174 320Q184 314 201.5 310Q219 306 248 303Q303 298 337.5 286Q372 274 392 255Q412 236 419.5 210Q427 184 427 151Q427 121 417 91.5Q407 62 383 39Q359 16 319.5 1.5Q280 -13 222 -13Q204 -13 182.5 -10.5Q161 -8 139 -4.5Q117 -1 97.5 4Q78 9 64 15V112Q94 101 134.5 91.5Q175 82 223 82Q253 82 273 88Q293 94 305 103.5Q317 113 322 125Q327 137 327 150Q327 162 324 172.5Q321 183 311.5 191Q302 199 284 204Q266 209 237 212Q181 217 146.5 229.5Q112 242 92.5 260.5Q73 279 66 302.5Q59 326 59 354Q59 382 68.5 410.5Q78 439 100.5 461.5Q123 484 160 498.5Q197 513 251 513Q286 513 325.5 507Q365 501 404 484V389Q367 401 331 409.5Q295 418 250 418Q221 418 203 413Q185 408 174.5 399.5Q164 391 160.5 379Q157 367 157 354Z" glyph-name="s" horiz-adv-x="486" unicode="s" /><glyph d="M162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 120 453.5 89.5Q442 59 416.5 36.5Q391 14 348.5 0.5Q306 -13 245 -13Q226 -13 201.5 -11Q177 -9 152 -5Q127 -1 106 3.5Q85 8 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Z" glyph-name="s.smcp" horiz-adv-x="530" /><glyph d="M215 575 295 750H416L271 575ZM157 354Q157 343 160.5 334.5Q164 326 174 320Q184 314 201.5 310Q219 306 248 303Q303 298 337.5 286Q372 274 392 255Q412 236 419.5 210Q427 184 427 151Q427 121 417 91.5Q407 62 383 39Q359 16 319.5 1.5Q280 -13 222 -13Q204 -13 182.5 -10.5Q161 -8 139 -4.5Q117 -1 97.5 4Q78 9 64 15V112Q94 101 134.5 91.5Q175 82 223 82Q253 82 273 88Q293 94 305 103.5Q317 113 322 125Q327 137 327 150Q327 162 324 172.5Q321 183 311.5 191Q302 199 284 204Q266 209 237 212Q181 217 146.5 229.5Q112 242 92.5 260.5Q73 279 66 302.5Q59 326 59 354Q59 382 68.5 410.5Q78 439 100.5 461.5Q123 484 160 498.5Q197 513 251 513Q286 513 325.5 507Q365 501 404 484V389Q367 401 331 409.5Q295 418 250 418Q221 418 203 413Q185 408 174.5 399.5Q164 391 160.5 379Q157 367 157 354Z" glyph-name="sacute" horiz-adv-x="486" unicode="&#347;" /><glyph d="M229 575 309 750H430L285 575ZM162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 120 453.5 89.5Q442 59 416.5 36.5Q391 14 348.5 0.5Q306 -13 245 -13Q226 -13 201.5 -11Q177 -9 152 -5Q127 -1 106 3.5Q85 8 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Z" glyph-name="sacute.smcp" horiz-adv-x="530" /><glyph d="M421 750 296 575H190L65 750H121L243 645L365 750ZM157 354Q157 343 160.5 334.5Q164 326 174 320Q184 314 201.5 310Q219 306 248 303Q303 298 337.5 286Q372 274 392 255Q412 236 419.5 210Q427 184 427 151Q427 121 417 91.5Q407 62 383 39Q359 16 319.5 1.5Q280 -13 222 -13Q204 -13 182.5 -10.5Q161 -8 139 -4.5Q117 -1 97.5 4Q78 9 64 15V112Q94 101 134.5 91.5Q175 82 223 82Q253 82 273 88Q293 94 305 103.5Q317 113 322 125Q327 137 327 150Q327 162 324 172.5Q321 183 311.5 191Q302 199 284 204Q266 209 237 212Q181 217 146.5 229.5Q112 242 92.5 260.5Q73 279 66 302.5Q59 326 59 354Q59 382 68.5 410.5Q78 439 100.5 461.5Q123 484 160 498.5Q197 513 251 513Q286 513 325.5 507Q365 501 404 484V389Q367 401 331 409.5Q295 418 250 418Q221 418 203 413Q185 408 174.5 399.5Q164 391 160.5 379Q157 367 157 354Z" glyph-name="scaron" horiz-adv-x="486" unicode="&#353;" /><glyph d="M435 750 310 575H204L79 750H135L257 645L379 750ZM162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 120 453.5 89.5Q442 59 416.5 36.5Q391 14 348.5 0.5Q306 -13 245 -13Q226 -13 201.5 -11Q177 -9 152 -5Q127 -1 106 3.5Q85 8 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Z" glyph-name="scaron.smcp" horiz-adv-x="530" /><glyph d="M167 -185Q177 -176 186 -165Q195 -154 199.5 -142.5Q204 -131 202.5 -121.5Q201 -112 191 -106Q173 -97 167 -84.5Q161 -72 162.5 -59Q164 -46 170 -33.5Q176 -21 183 -11Q151 -7 118 -0.5Q85 6 64 15V112Q94 101 134.5 91.5Q175 82 223 82Q253 82 273 88Q293 94 305 103.5Q317 113 322 125Q327 137 327 150Q327 162 324 172.5Q321 183 311.5 191Q302 199 284 204Q266 209 237 212Q181 217 146.5 229.5Q112 242 92.5 260.5Q73 279 66 302.5Q59 326 59 354Q59 382 68.5 410.5Q78 439 100.5 461.5Q123 484 160 498.5Q197 513 251 513Q286 513 325.5 507Q365 501 404 484V389Q367 401 331 409.5Q295 418 250 418Q221 418 203 413Q185 408 174.5 399.5Q164 391 160.5 379Q157 367 157 354Q157 343 160.5 334.5Q164 326 174 320Q184 314 201.5 310Q219 306 248 303Q303 298 337.5 286Q372 274 392 255Q412 236 419.5 210Q427 184 427 151Q427 123 418 96Q409 69 389 46.5Q369 24 336 8.5Q303 -7 254 -11Q243 -26 242 -41.5Q241 -57 264 -71Q289 -85 296 -101Q303 -117 300 -133Q297 -149 287.5 -162.5Q278 -176 269 -185Z" glyph-name="scedilla" horiz-adv-x="486" unicode="&#351;" /><glyph d="M194 -185Q204 -176 213 -165Q222 -154 226.5 -142.5Q231 -131 229.5 -121.5Q228 -112 218 -106Q200 -97 194 -84.5Q188 -72 189 -59Q190 -46 196.5 -33.5Q203 -21 210 -11Q191 -10 171 -7.5Q151 -5 132.5 -2Q114 1 98.5 5Q83 9 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 122 455.5 94Q446 66 424 44Q402 22 367 7.5Q332 -7 281 -11Q270 -26 269 -41.5Q268 -57 291 -71Q316 -85 323 -101Q330 -117 327 -133Q324 -149 314.5 -162.5Q305 -176 296 -185Z" glyph-name="scedilla.smcp" horiz-adv-x="530" /><glyph d="M365 575 243 680 121 575H65L190 750H296L421 575ZM157 354Q157 343 160.5 334.5Q164 326 174 320Q184 314 201.5 310Q219 306 248 303Q303 298 337.5 286Q372 274 392 255Q412 236 419.5 210Q427 184 427 151Q427 121 417 91.5Q407 62 383 39Q359 16 319.5 1.5Q280 -13 222 -13Q204 -13 182.5 -10.5Q161 -8 139 -4.5Q117 -1 97.5 4Q78 9 64 15V112Q94 101 134.5 91.5Q175 82 223 82Q253 82 273 88Q293 94 305 103.5Q317 113 322 125Q327 137 327 150Q327 162 324 172.5Q321 183 311.5 191Q302 199 284 204Q266 209 237 212Q181 217 146.5 229.5Q112 242 92.5 260.5Q73 279 66 302.5Q59 326 59 354Q59 382 68.5 410.5Q78 439 100.5 461.5Q123 484 160 498.5Q197 513 251 513Q286 513 325.5 507Q365 501 404 484V389Q367 401 331 409.5Q295 418 250 418Q221 418 203 413Q185 408 174.5 399.5Q164 391 160.5 379Q157 367 157 354Z" glyph-name="scircumflex" horiz-adv-x="486" unicode="&#349;" /><glyph d="M79 575 204 750H310L435 575H379L257 680L135 575ZM162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 120 453.5 89.5Q442 59 416.5 36.5Q391 14 348.5 0.5Q306 -13 245 -13Q226 -13 201.5 -11Q177 -9 152 -5Q127 -1 106 3.5Q85 8 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Z" glyph-name="scircumflex.smcp" horiz-adv-x="530" /><glyph d="M190 -250 204 -184Q185 -176 174 -159Q163 -142 163 -120Q163 -91 183.5 -70.5Q204 -50 233 -50Q262 -50 282.5 -70.5Q303 -91 303 -120Q303 -138 295 -154L251 -250ZM157 354Q157 343 160.5 334.5Q164 326 174 320Q184 314 201.5 310Q219 306 248 303Q303 298 337.5 286Q372 274 392 255Q412 236 419.5 210Q427 184 427 151Q427 121 417 91.5Q407 62 383 39Q359 16 319.5 1.5Q280 -13 222 -13Q204 -13 182.5 -10.5Q161 -8 139 -4.5Q117 -1 97.5 4Q78 9 64 15V112Q94 101 134.5 91.5Q175 82 223 82Q253 82 273 88Q293 94 305 103.5Q317 113 322 125Q327 137 327 150Q327 162 324 172.5Q321 183 311.5 191Q302 199 284 204Q266 209 237 212Q181 217 146.5 229.5Q112 242 92.5 260.5Q73 279 66 302.5Q59 326 59 354Q59 382 68.5 410.5Q78 439 100.5 461.5Q123 484 160 498.5Q197 513 251 513Q286 513 325.5 507Q365 501 404 484V389Q367 401 331 409.5Q295 418 250 418Q221 418 203 413Q185 408 174.5 399.5Q164 391 160.5 379Q157 367 157 354Z" glyph-name="scommaaccent" horiz-adv-x="486" unicode="&#537;" /><glyph d="M206 -250 220 -184Q201 -176 190 -159Q179 -142 179 -120Q179 -91 199.5 -70.5Q220 -50 249 -50Q278 -50 298.5 -70.5Q319 -91 319 -120Q319 -138 311 -154L267 -250ZM162 355Q162 328 187 318Q212 308 271 303Q325 298 362 287.5Q399 277 422 259.5Q445 242 455 215.5Q465 189 465 153Q465 120 453.5 89.5Q442 59 416.5 36.5Q391 14 348.5 0.5Q306 -13 245 -13Q226 -13 201.5 -11Q177 -9 152 -5Q127 -1 106 3.5Q85 8 74 13V107Q86 102 105.5 98Q125 94 148 90Q171 86 196.5 84Q222 82 246 82Q313 82 339.5 100.5Q366 119 366 152Q366 180 343.5 193.5Q321 207 262 212Q207 217 169.5 226.5Q132 236 108.5 252Q85 268 75 292Q65 316 65 350Q65 386 76.5 415.5Q88 445 113 467Q138 489 178 501Q218 513 275 513Q312 513 357.5 508.5Q403 504 442 487V395Q404 407 363 412.5Q322 418 274 418Q211 418 186.5 402.5Q162 387 162 355Z" glyph-name="scommaaccent.smcp" horiz-adv-x="530" /><glyph d="M354 335Q311 343 276.5 351.5Q242 360 215 371Q179 352 165 326Q151 300 151 271Q151 241 159.5 223Q168 205 186 194.5Q204 184 231 178Q258 172 296 165H295Q380 150 434 129Q469 148 483.5 174Q498 200 498 229Q498 259 489.5 277Q481 295 463 305.5Q445 316 417.5 322Q390 328 353 335ZM606 228Q606 187 588.5 141Q571 95 524 59Q548 18 548 -46Q548 -80 537.5 -118Q527 -156 497.5 -188.5Q468 -221 416 -242Q364 -263 282 -263Q214 -263 162.5 -250.5Q111 -238 74 -222V-114Q105 -128 132 -137.5Q159 -147 184 -152.5Q209 -158 233 -160.5Q257 -163 283 -163Q331 -163 361.5 -154Q392 -145 409.5 -129Q427 -113 433.5 -91.5Q440 -70 440 -45Q440 -15 428.5 3.5Q417 22 396 33Q375 44 345 51Q315 58 277 65H278Q209 77 163.5 92Q118 107 91 130Q64 153 53.5 187.5Q43 222 43 272Q43 292 47 314.5Q51 337 60.5 359Q70 381 85.5 402Q101 423 124 441Q101 480 101 546Q101 580 111.5 618Q122 656 151.5 688.5Q181 721 232.5 742Q284 763 367 763Q435 763 486.5 750.5Q538 738 575 722V614Q544 628 517 637.5Q490 647 465 652.5Q440 658 416 660.5Q392 663 366 663Q318 663 287.5 654Q257 645 239.5 629Q222 613 215.5 591.5Q209 570 209 545Q209 515 220.5 496.5Q232 478 253 466.5Q274 455 304 448.5Q334 442 372 435H371Q440 423 485.5 408Q531 393 558 370Q585 347 595.5 312.5Q606 278 606 228Z" glyph-name="section" horiz-adv-x="649" unicode="&#167;" /><glyph d="M40 375Q40 404 60.5 424.5Q81 445 110 445Q139 445 159.5 424.5Q180 404 180 375Q180 346 159.5 325.5Q139 305 110 305Q81 305 60.5 325.5Q40 346 40 375ZM58 -114 81 -7Q62 1 51 18Q40 35 40 57Q40 86 60.5 106.5Q81 127 110 127Q139 127 159.5 106.5Q180 86 180 57Q180 39 172 23L109 -114Z" glyph-name="semicolon" horiz-adv-x="220" unicode=";" /><glyph d="M110 0 442 655H50V750H550V682L225 0Z" glyph-name="seven" horiz-adv-x="600" unicode="7" /><glyph d="M110 0 442 655H50V750H550V682L225 0Z" glyph-name="seven.LP" horiz-adv-x="600" /><glyph d="M54 -250 336 405H21V500H444V432L169 -250Z" glyph-name="seven.OP" horiz-adv-x="465" /><glyph d="M169 230Q169 205 174 179Q179 153 194 131.5Q209 110 236.5 96Q264 82 308 82Q350 82 375 97Q400 112 413.5 134.5Q427 157 431 183Q435 209 435 230Q435 251 431.5 276.5Q428 302 415.5 324.5Q403 347 377.5 362.5Q352 378 308 378Q264 378 236.5 363.5Q209 349 194 327Q179 305 174 279Q169 253 169 230ZM515 635Q473 652 428.5 659Q384 666 333 666Q284 666 252 645.5Q220 625 201 590.5Q182 556 173 510.5Q164 465 160 414Q183 439 220 456Q257 473 308 473Q386 473 431.5 449Q477 425 500.5 389Q524 353 530.5 310.5Q537 268 537 230Q537 192 529 149.5Q521 107 496.5 71Q472 35 427 11Q382 -13 308 -13Q227 -13 178.5 12Q130 37 104.5 83Q79 129 71 193.5Q63 258 63 336Q63 426 73.5 504Q84 582 113.5 639.5Q143 697 195.5 730Q248 763 333 763Q390 763 435.5 755.5Q481 748 515 734Z" glyph-name="six" horiz-adv-x="600" unicode="6" /><glyph d="M169 230Q169 205 174 179Q179 153 194 131.5Q209 110 236.5 96Q264 82 308 82Q350 82 375 97Q400 112 413.5 134.5Q427 157 431 183Q435 209 435 230Q435 251 431.5 276.5Q428 302 415.5 324.5Q403 347 377.5 362.5Q352 378 308 378Q264 378 236.5 363.5Q209 349 194 327Q179 305 174 279Q169 253 169 230ZM515 635Q473 652 428.5 659Q384 666 333 666Q284 666 252 645.5Q220 625 201 590.5Q182 556 173 510.5Q164 465 160 414Q183 439 220 456Q257 473 308 473Q386 473 431.5 449Q477 425 500.5 389Q524 353 530.5 310.5Q537 268 537 230Q537 192 529 149.5Q521 107 496.5 71Q472 35 427 11Q382 -13 308 -13Q227 -13 178.5 12Q130 37 104.5 83Q79 129 71 193.5Q63 258 63 336Q63 426 73.5 504Q84 582 113.5 639.5Q143 697 195.5 730Q248 763 333 763Q390 763 435.5 755.5Q481 748 515 734Z" glyph-name="six.LP" horiz-adv-x="600" /><glyph d="M174 230Q174 205 179 179Q184 153 199 131.5Q214 110 241.5 96Q269 82 313 82Q355 82 380 97Q405 112 418.5 134.5Q432 157 436 183Q440 209 440 230Q440 251 436.5 276.5Q433 302 420.5 324.5Q408 347 382.5 362.5Q357 378 313 378Q269 378 241.5 363.5Q214 349 199 327Q184 305 179 279Q174 253 174 230ZM520 635Q478 652 433.5 659Q389 666 338 666Q289 666 257 645.5Q225 625 206 590.5Q187 556 178 510.5Q169 465 165 414Q188 439 225 456Q262 473 313 473Q391 473 436.5 449Q482 425 505.5 389Q529 353 535.5 310.5Q542 268 542 230Q542 192 534 149.5Q526 107 501.5 71Q477 35 432 11Q387 -13 313 -13Q232 -13 183.5 12Q135 37 109.5 83Q84 129 76 193.5Q68 258 68 336Q68 426 78.5 504Q89 582 118.5 639.5Q148 697 200.5 730Q253 763 338 763Q395 763 440.5 755.5Q486 748 520 734Z" glyph-name="six.OP" horiz-adv-x="605" /><glyph glyph-name="sixperemspace" horiz-adv-x="167" unicode="&#8198;" /><glyph d="M0 -206 351 816H458L107 -206Z" glyph-name="slash" horiz-adv-x="458" unicode="/" /><glyph glyph-name="space" horiz-adv-x="200" unicode=" " /><glyph d="M218 438H471V344H218V94H555V0H50V94H115V344H50V438H115V568Q115 611 127 646.5Q139 682 166 708Q193 734 236 748.5Q279 763 341 763Q359 763 384.5 761Q410 759 437 754.5Q464 750 490.5 743.5Q517 737 536 729V635Q518 641 494 647Q470 653 443.5 657.5Q417 662 390.5 665Q364 668 341 668Q218 668 218 569Z" glyph-name="sterling" horiz-adv-x="600" unicode="&#163;" /><glyph d="M208 303H411V209H208V94H495V0H40V94H105V209H40V303H105V338Q105 377 115 409Q125 441 149 464.5Q173 488 212.5 500.5Q252 513 311 513Q346 513 392 506Q438 499 476 482V388Q441 400 399 409Q357 418 311 418Q254 418 231 399.5Q208 381 208 339Z" glyph-name="sterling.OP" horiz-adv-x="565" /><glyph d="M67 375Q67 455 97.5 526Q128 597 180.5 649.5Q233 702 304 732.5Q375 763 455 763Q535 763 606 732.5Q677 702 729.5 649.5Q782 597 812.5 526Q843 455 843 375Q843 295 812.5 224Q782 153 729.5 100.5Q677 48 606 17.5Q535 -13 455 -13Q375 -13 304 17.5Q233 48 180.5 100.5Q128 153 97.5 224Q67 295 67 375Z" glyph-name="summation" horiz-adv-x="910" unicode="&#8721;" /><glyph d="M348 -7Q329 -12 310.5 -12.5Q292 -13 285 -13Q254 -13 224.5 -5Q195 3 171.5 20Q148 37 134 63.5Q120 90 120 127V412H27V474L120 500V626L221 654V500H369V412H221V136Q221 104 240.5 93Q260 82 293 82Q307 82 323.5 83Q340 84 348 86Z" glyph-name="t" horiz-adv-x="409" unicode="t" /><glyph d="M303 406V0H200V406H45V500H458V406Z" glyph-name="t.smcp" horiz-adv-x="503" /><glyph d="M348 -7Q329 -12 310.5 -12.5Q292 -13 285 -13Q254 -13 224.5 -5Q195 3 171.5 20Q148 37 134 63.5Q120 90 120 127V236H37V319H120V412H27V474L120 500V626L221 654V500H369V412H221V319H345V236H221V136Q221 104 240.5 93Q260 82 293 82Q307 82 323.5 83Q340 84 348 86Z" glyph-name="tbar" horiz-adv-x="409" unicode="&#359;" /><glyph d="M303 406V298H404V215H303V0H200V215H99V298H200V406H45V500H458V406Z" glyph-name="tbar.smcp" horiz-adv-x="503" /><glyph d="M370 750 322 550H266L282 750ZM348 -7Q329 -12 310.5 -12.5Q292 -13 285 -13Q254 -13 224.5 -5Q195 3 171.5 20Q148 37 134 63.5Q120 90 120 127V412H27V474L120 500V626L221 654V500H369V412H221V136Q221 104 240.5 93Q260 82 293 82Q307 82 323.5 83Q340 84 348 86Z" glyph-name="tcaron" horiz-adv-x="409" unicode="&#357;" /><glyph d="M430 750 305 575H199L74 750H130L252 645L374 750ZM303 406V0H200V406H45V500H458V406Z" glyph-name="tcaron.smcp" horiz-adv-x="503" /><glyph d="M192 -250 206 -184Q187 -176 176 -159Q165 -142 165 -120Q165 -91 185.5 -70.5Q206 -50 235 -50Q264 -50 284.5 -70.5Q305 -91 305 -120Q305 -138 297 -154L253 -250ZM348 -7Q329 -12 310.5 -12.5Q292 -13 285 -13Q254 -13 224.5 -5Q195 3 171.5 20Q148 37 134 63.5Q120 90 120 127V412H27V474L120 500V626L221 654V500H369V412H221V136Q221 104 240.5 93Q260 82 293 82Q307 82 323.5 83Q340 84 348 86Z" glyph-name="tcommaaccent" horiz-adv-x="409" unicode="&#355;" /><glyph d="M209 -250 223 -184Q204 -176 193 -159Q182 -142 182 -120Q182 -91 202.5 -70.5Q223 -50 252 -50Q281 -50 301.5 -70.5Q322 -91 322 -120Q322 -138 314 -154L270 -250ZM303 406V0H200V406H45V500H458V406Z" glyph-name="tcommaaccent.smcp" horiz-adv-x="503" /><glyph glyph-name="thinspace" horiz-adv-x="100" unicode="&#8201;" /><glyph d="M208 92Q223 88 241.5 85Q260 82 284 82Q321 82 343 95.5Q365 109 377 132Q389 155 393 185.5Q397 216 397 250Q397 284 393 314.5Q389 345 377 368Q365 391 343 404.5Q321 418 284 418Q247 418 220 411.5Q193 405 171 394V105Q184 98 208 92ZM70 750H171V484Q199 498 226.5 505.5Q254 513 284 513Q347 513 389 494Q431 475 456 440.5Q481 406 491 357.5Q501 309 501 250Q501 191 491 142.5Q481 94 456 59.5Q431 25 389 6Q347 -13 284 -13Q254 -13 227.5 -6.5Q201 0 171 14V-250H70Z" glyph-name="thorn" horiz-adv-x="561" unicode="&#254;" /><glyph d="M362 250Q362 264 359 277Q356 290 348.5 300.5Q341 311 326.5 317.5Q312 324 290 324H184V175H290Q312 175 326.5 181.5Q341 188 348.5 198.5Q356 209 359 222.5Q362 236 362 250ZM184 82V0H85V500H184V418H292Q340 418 373.5 404.5Q407 391 427.5 368Q448 345 457 315Q466 285 466 251Q466 217 457 186.5Q448 156 427 132.5Q406 109 373 95.5Q340 82 292 82Z" glyph-name="thorn.smcp" horiz-adv-x="511" /><glyph d="M266 668Q242 668 213.5 665.5Q185 663 157 658.5Q129 654 104 648Q79 642 61 636V735Q77 741 102 746.5Q127 752 155.5 755.5Q184 759 212 761Q240 763 261 763Q342 763 392.5 746Q443 729 472 699.5Q501 670 511.5 632Q522 594 522 552Q522 531 517.5 508.5Q513 486 504 464.5Q495 443 481.5 425Q468 407 450 395Q475 384 493 365Q511 346 522.5 322Q534 298 539.5 271Q545 244 545 217Q545 172 534 130.5Q523 89 492 57Q461 25 407 6Q353 -13 268 -13Q202 -13 146 -4Q90 5 61 20V116Q94 104 152 93Q210 82 273 82Q328 82 360.5 94Q393 106 410.5 125.5Q428 145 433.5 169Q439 193 439 217Q439 278 407 308.5Q375 339 299 339H144V437H299Q333 437 355.5 448Q378 459 391.5 475.5Q405 492 410.5 512.5Q416 533 416 552Q416 574 411 595Q406 616 390 632Q374 648 344.5 658Q315 668 266 668Z" glyph-name="three" horiz-adv-x="600" unicode="3" /><glyph d="M266 668Q242 668 213.5 665.5Q185 663 157 658.5Q129 654 104 648Q79 642 61 636V735Q77 741 102 746.5Q127 752 155.5 755.5Q184 759 212 761Q240 763 261 763Q342 763 392.5 746Q443 729 472 699.5Q501 670 511.5 632Q522 594 522 552Q522 531 517.5 508.5Q513 486 504 464.5Q495 443 481.5 425Q468 407 450 395Q475 384 493 365Q511 346 522.5 322Q534 298 539.5 271Q545 244 545 217Q545 172 534 130.5Q523 89 492 57Q461 25 407 6Q353 -13 268 -13Q202 -13 146 -4Q90 5 61 20V116Q94 104 152 93Q210 82 273 82Q328 82 360.5 94Q393 106 410.5 125.5Q428 145 433.5 169Q439 193 439 217Q439 278 407 308.5Q375 339 299 339H144V437H299Q333 437 355.5 448Q378 459 391.5 475.5Q405 492 410.5 512.5Q416 533 416 552Q416 574 411 595Q406 616 390 632Q374 648 344.5 658Q315 668 266 668Z" glyph-name="three.LP" horiz-adv-x="600" /><glyph d="M226 418Q186 418 141 408.5Q96 399 61 386V485Q77 491 98 496.5Q119 502 141.5 505.5Q164 509 186 511Q208 513 226 513Q292 513 337.5 496Q383 479 410.5 449.5Q438 420 450 382Q462 344 462 302Q462 281 457.5 258.5Q453 236 444 214.5Q435 193 421.5 175Q408 157 390 145Q415 134 433 115Q451 96 462.5 72Q474 48 479.5 21Q485 -6 485 -33Q485 -78 472 -119.5Q459 -161 429.5 -193Q400 -225 351.5 -244Q303 -263 233 -263Q179 -263 134.5 -254Q90 -245 61 -230V-134Q94 -146 137.5 -157Q181 -168 233 -168Q278 -168 306.5 -156Q335 -144 351 -124.5Q367 -105 373 -81Q379 -57 379 -33Q379 28 347 58.5Q315 89 239 89H119V187H239Q273 187 295.5 198Q318 209 331.5 225.5Q345 242 350.5 262.5Q356 283 356 302Q356 324 350.5 345Q345 366 331 382Q317 398 291.5 408Q266 418 226 418Z" glyph-name="three.OP" horiz-adv-x="550" /><glyph glyph-name="threeperemspace" horiz-adv-x="333" unicode="&#8196;" /><glyph d="M690 292H60V390H690Z" glyph-name="threequarteremdash" horiz-adv-x="750" /><glyph d="M475 724Q474 584 350 587Q319 587 300 597Q281 607 267 618.5Q253 630 240.5 639.5Q228 649 210 649Q195 649 186 644Q177 639 172.5 631.5Q168 624 166.5 614Q165 604 165 594L85 601Q87 673 117 706.5Q147 740 210 739Q241 738 260 728Q279 718 293 706.5Q307 695 319.5 685.5Q332 676 350 676Q365 676 374 681Q383 686 387.5 694Q392 702 393.5 711.5Q395 721 395 731Z" glyph-name="tilde" horiz-adv-x="540" unicode="&#732;" /><glyph d="M154 704V500H102V704H25V750H230V704ZM372 530 308 658 310 500H261V750H316L390 594L466 750H520V500H472L474 658L410 530Z" glyph-name="trademark" horiz-adv-x="575" unicode="&#8482;" /><glyph d="M426 555Q426 578 420.5 598.5Q415 619 400 634Q385 649 358.5 658Q332 667 289 667Q242 667 198.5 658.5Q155 650 95 625V726Q130 742 178 752.5Q226 763 288 763Q367 763 415 742.5Q463 722 488.5 691Q514 660 522 623.5Q530 587 530 555Q530 504 505 456Q480 408 437 362L190 98H540V0H50V70L388 451Q408 474 417 501Q426 528 426 555Z" glyph-name="two" horiz-adv-x="600" unicode="2" /><glyph d="M426 555Q426 578 420.5 598.5Q415 619 400 634Q385 649 358.5 658Q332 667 289 667Q242 667 198.5 658.5Q155 650 95 625V726Q130 742 178 752.5Q226 763 288 763Q367 763 415 742.5Q463 722 488.5 691Q514 660 522 623.5Q530 587 530 555Q530 504 505 456Q480 408 437 362L190 98H540V0H50V70L388 451Q408 474 417 501Q426 528 426 555Z" glyph-name="two.LP" horiz-adv-x="600" /><glyph d="M422 349Q422 314 404.5 284.5Q387 255 354 225.5Q321 196 273.5 165Q226 134 166 95H443V0H25V78L275 268Q305 291 313.5 309Q322 327 322 350Q322 363 317 375Q312 387 300 396.5Q288 406 268 412Q248 418 218 418Q170 418 129.5 408.5Q89 399 59 388V485Q73 491 92.5 496Q112 501 134 504.5Q156 508 177.5 510.5Q199 513 217 513Q275 513 314.5 500Q354 487 378 464.5Q402 442 412 412.5Q422 383 422 349Z" glyph-name="two.OP" horiz-adv-x="500" /><glyph d="M168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="u" horiz-adv-x="538" unicode="u" /><glyph d="M299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="u.smcp" horiz-adv-x="598" /><glyph d="M239 575 319 750H440L295 575ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="uacute" horiz-adv-x="538" unicode="&#250;" /><glyph d="M271 575 351 750H472L327 575ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="uacute.smcp" horiz-adv-x="598" /><glyph d="M266 572Q230 572 200 582.5Q170 593 147.5 614.5Q125 636 112 669.5Q99 703 98 750H165Q166 723 171 705.5Q176 688 187.5 677.5Q199 667 218 662.5Q237 658 266 658Q295 658 314 662.5Q333 667 344.5 677.5Q356 688 361 705.5Q366 723 367 750H434Q433 703 420 669.5Q407 636 384.5 614.5Q362 593 332 582.5Q302 572 266 572ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="ubreve" horiz-adv-x="538" unicode="&#365;" /><glyph d="M299 572Q263 572 233 582.5Q203 593 180.5 614.5Q158 636 145 669.5Q132 703 131 750H198Q199 723 204 705.5Q209 688 220.5 677.5Q232 667 251 662.5Q270 658 299 658Q328 658 347 662.5Q366 667 377.5 677.5Q389 688 394 705.5Q399 723 400 750H467Q466 703 453 669.5Q440 636 417.5 614.5Q395 593 365 582.5Q335 572 299 572ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="ubreve.smcp" horiz-adv-x="598" /><glyph d="M389 575 267 680 145 575H89L214 750H320L445 575ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="ucircumflex" horiz-adv-x="538" unicode="&#251;" /><glyph d="M421 575 299 680 177 575H121L246 750H352L477 575ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="ucircumflex.smcp" horiz-adv-x="598" /><glyph d="M87 667Q87 696 107.5 716.5Q128 737 157 737Q186 737 206.5 716.5Q227 696 227 667Q227 638 206.5 617.5Q186 597 157 597Q128 597 107.5 617.5Q87 638 87 667ZM307 667Q307 696 327.5 716.5Q348 737 377 737Q406 737 426.5 716.5Q447 696 447 667Q447 638 426.5 617.5Q406 597 377 597Q348 597 327.5 617.5Q307 638 307 667ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="udieresis" horiz-adv-x="538" unicode="&#252;" /><glyph d="M119 667Q119 696 139.5 716.5Q160 737 189 737Q218 737 238.5 716.5Q259 696 259 667Q259 638 238.5 617.5Q218 597 189 597Q160 597 139.5 617.5Q119 638 119 667ZM339 667Q339 696 359.5 716.5Q380 737 409 737Q438 737 458.5 716.5Q479 696 479 667Q479 638 458.5 617.5Q438 597 409 597Q380 597 359.5 617.5Q339 638 339 667ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="udieresis.smcp" horiz-adv-x="598" /><glyph d="M239 575 94 750H215L295 575ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="ugrave" horiz-adv-x="538" unicode="&#249;" /><glyph d="M271 575 126 750H247L327 575ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="ugrave.smcp" horiz-adv-x="598" /><glyph d="M325 575 405 750H526L381 575ZM148 575 228 750H350L205 575ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="uhungarumlaut" horiz-adv-x="538" unicode="&#369;" /><glyph d="M352 575 432 750H553L408 575ZM175 575 255 750H377L232 575ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="uhungarumlaut.smcp" horiz-adv-x="598" /><glyph d="M129 641V732H403V641ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="umacron" horiz-adv-x="538" unicode="&#363;" /><glyph d="M162 641V732H436V641ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="umacron.smcp" horiz-adv-x="598" /><glyph d="M320 -250H0V-184H320Z" glyph-name="underscore" horiz-adv-x="320" unicode="_" /><glyph d="M320 292H50V390H320Z" glyph-name="uni00AD" horiz-adv-x="370" unicode="&#173;" /><glyph d="M168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0Q426 -19 396.5 -46Q367 -73 367 -110Q367 -133 381 -144.5Q395 -156 418 -156Q439 -156 455.5 -153Q472 -150 481 -147V-215Q469 -220 448.5 -225Q428 -230 396 -230Q373 -230 352 -224Q331 -218 314.5 -205Q298 -192 288 -172.5Q278 -153 278 -126Q278 -89 304 -52Q330 -15 393 17L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="uogonek" horiz-adv-x="538" unicode="&#371;" /><glyph d="M518 166Q518 104 489.5 61.5Q461 19 399 0Q357 -19 327.5 -46Q298 -73 298 -110Q298 -133 312 -144.5Q326 -156 349 -156Q370 -156 386.5 -153Q403 -150 412 -147V-215Q400 -220 379.5 -225Q359 -230 327 -230Q304 -230 283 -224Q262 -218 245.5 -205Q229 -192 219 -172.5Q209 -153 209 -126Q209 -101 222 -71.5Q235 -42 272 -12Q172 -7 126 40.5Q80 88 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518Z" glyph-name="uogonek.smcp" horiz-adv-x="598" /><glyph d="M311 662Q311 688 299.5 697.5Q288 707 267 707Q245 707 234 697.5Q223 688 223 662Q223 638 234 628.5Q245 619 267 619Q288 619 299.5 628.5Q311 638 311 662ZM368 662Q368 610 340 586Q312 562 267 562Q221 562 193 586Q165 610 165 662Q165 715 193 739Q221 763 267 763Q312 763 340 739Q368 715 368 662ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="uring" horiz-adv-x="538" unicode="&#367;" /><glyph d="M343 662Q343 688 331.5 697.5Q320 707 299 707Q277 707 266 697.5Q255 688 255 662Q255 638 266 628.5Q277 619 299 619Q320 619 331.5 628.5Q343 638 343 662ZM400 662Q400 610 372 586Q344 562 299 562Q253 562 225 586Q197 610 197 662Q197 715 225 739Q253 763 299 763Q344 763 372 739Q400 715 400 662ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="uring.smcp" horiz-adv-x="598" /><glyph d="M462 724Q461 584 337 587Q306 587 287 597Q268 607 254 618.5Q240 630 227.5 639.5Q215 649 197 649Q182 649 173 644Q164 639 159.5 631.5Q155 624 153.5 614Q152 604 152 594L72 601Q74 673 104 706.5Q134 740 197 739Q228 738 247 728Q266 718 280 706.5Q294 695 306.5 685.5Q319 676 337 676Q352 676 361 681Q370 686 374.5 694Q379 702 380.5 711.5Q382 721 382 731ZM168 146Q168 111 191 96.5Q214 82 249 82Q287 82 316 89Q345 96 365 106V500H468V0H399L389 28Q358 8 321.5 -2.5Q285 -13 249 -13Q156 -13 110.5 29.5Q65 72 65 148V500H168Z" glyph-name="utilde" horiz-adv-x="538" unicode="&#361;" /><glyph d="M494 724Q493 584 369 587Q338 587 319 597Q300 607 286 618.5Q272 630 259.5 639.5Q247 649 229 649Q214 649 205 644Q196 639 191.5 631.5Q187 624 185.5 614Q184 604 184 594L104 601Q106 673 136 706.5Q166 740 229 739Q260 738 279 728Q298 718 312 706.5Q326 695 338.5 685.5Q351 676 369 676Q384 676 393 681Q402 686 406.5 694Q411 702 412.5 711.5Q414 721 414 731ZM299 -13Q185 -13 132.5 35Q80 83 80 166V500H183V166Q183 120 214.5 101Q246 82 299 82Q352 82 383.5 101Q415 120 415 166V500H518V166Q518 83 465.5 35Q413 -13 299 -13Z" glyph-name="utilde.smcp" horiz-adv-x="598" /><glyph d="M375 500H488L292 -13H221L25 500H137L257 139Z" glyph-name="v" horiz-adv-x="513" unicode="v" /><glyph d="M532 500 321 -13H251L40 500H150L286 138L422 500Z" glyph-name="v.smcp" horiz-adv-x="572" /><glyph d="M504 162 579 500H686L545 -13H466L356 369L245 -13H166L25 500H133L209 161L307 500H404Z" glyph-name="w" horiz-adv-x="711" unicode="w" /><glyph d="M720 500 574 -13H496L380 351L264 -13H186L40 500H150L228 176L333 500H427L532 176L610 500Z" glyph-name="w.smcp" horiz-adv-x="760" /><glyph d="M477 575 355 680 233 575H177L302 750H408L533 575ZM504 162 579 500H686L545 -13H466L356 369L245 -13H166L25 500H133L209 161L307 500H404Z" glyph-name="wcircumflex" horiz-adv-x="711" unicode="&#373;" /><glyph d="M502 575 380 680 258 575H202L327 750H433L558 575ZM720 500 574 -13H496L380 351L264 -13H186L40 500H150L228 176L333 500H427L532 176L610 500Z" glyph-name="wcircumflex.smcp" horiz-adv-x="760" /><glyph d="M138 0H15L200 257L25 500H147L260 326L372 500H495L320 257L505 0H381L260 184Z" glyph-name="x" horiz-adv-x="520" unicode="x" /><glyph d="M35 0 232 257 46 500H171L292 335L413 500H538L352 257L548 0H422L292 180L162 0Z" glyph-name="x.smcp" horiz-adv-x="583" /><glyph d="M141 500 258 139 375 500H490L202 -250H90L210 24L25 500Z" glyph-name="y" horiz-adv-x="515" unicode="y" /><glyph d="M568 500 354 178V0H255V177L40 500H163L304 267L445 500Z" glyph-name="y.smcp" horiz-adv-x="608" /><glyph d="M229 575 309 750H430L285 575ZM141 500 258 139 375 500H490L202 -250H90L210 24L25 500Z" glyph-name="yacute" horiz-adv-x="515" unicode="&#253;" /><glyph d="M276 575 356 750H477L332 575ZM568 500 354 178V0H255V177L40 500H163L304 267L445 500Z" glyph-name="yacute.smcp" horiz-adv-x="608" /><glyph d="M380 575 258 680 136 575H80L205 750H311L436 575ZM141 500 258 139 375 500H490L202 -250H90L210 24L25 500Z" glyph-name="ycircumflex" horiz-adv-x="515" unicode="&#375;" /><glyph d="M426 575 304 680 182 575H126L251 750H357L482 575ZM568 500 354 178V0H255V177L40 500H163L304 267L445 500Z" glyph-name="ycircumflex.smcp" horiz-adv-x="608" /><glyph d="M77 667Q77 696 97.5 716.5Q118 737 147 737Q176 737 196.5 716.5Q217 696 217 667Q217 638 196.5 617.5Q176 597 147 597Q118 597 97.5 617.5Q77 638 77 667ZM297 667Q297 696 317.5 716.5Q338 737 367 737Q396 737 416.5 716.5Q437 696 437 667Q437 638 416.5 617.5Q396 597 367 597Q338 597 317.5 617.5Q297 638 297 667ZM141 500 258 139 375 500H490L202 -250H90L210 24L25 500Z" glyph-name="ydieresis" horiz-adv-x="515" unicode="&#255;" /><glyph d="M124 667Q124 696 144.5 716.5Q165 737 194 737Q223 737 243.5 716.5Q264 696 264 667Q264 638 243.5 617.5Q223 597 194 597Q165 597 144.5 617.5Q124 638 124 667ZM344 667Q344 696 364.5 716.5Q385 737 414 737Q443 737 463.5 716.5Q484 696 484 667Q484 638 463.5 617.5Q443 597 414 597Q385 597 364.5 617.5Q344 638 344 667ZM568 500 354 178V0H255V177L40 500H163L304 267L445 500Z" glyph-name="ydieresis.smcp" horiz-adv-x="608" /><glyph d="M55 349H215L188 401H55V500H137L7 750H131L300 405L469 750H593L463 500H545V401H411L384 349H545V250H352V0H248V250H55Z" glyph-name="yen" horiz-adv-x="600" unicode="&#165;" /><glyph d="M510 109H341V0H242V109H72V207H221L205 231H72V330H141L28 501H150L291 268L432 501H554L441 330H510V231H376L360 207H510Z" glyph-name="yen.OP" horiz-adv-x="582" /><glyph d="M35 0V67L290 405H49V500H426V433L171 95H435V0Z" glyph-name="z" horiz-adv-x="470" unicode="z" /><glyph d="M50 0V68L326 405H64V500H469V432L192 95H478V0Z" glyph-name="z.smcp" horiz-adv-x="528" /><glyph d="M203 575 283 750H404L259 575ZM35 0V67L290 405H49V500H426V433L171 95H435V0Z" glyph-name="zacute" horiz-adv-x="470" unicode="&#378;" /><glyph d="M237 575 317 750H438L293 575ZM50 0V68L326 405H64V500H469V432L192 95H478V0Z" glyph-name="zacute.smcp" horiz-adv-x="528" /><glyph d="M409 750 284 575H178L53 750H109L231 645L353 750ZM35 0V67L290 405H49V500H426V433L171 95H435V0Z" glyph-name="zcaron" horiz-adv-x="470" unicode="&#382;" /><glyph d="M50 0V68L326 405H64V500H469V432L192 95H478V0ZM443 750 318 575H212L87 750H143L265 645L387 750Z" glyph-name="zcaron.smcp" horiz-adv-x="528" /><glyph d="M161 667Q161 696 181.5 716.5Q202 737 231 737Q260 737 280.5 716.5Q301 696 301 667Q301 638 280.5 617.5Q260 597 231 597Q202 597 181.5 617.5Q161 638 161 667ZM35 0V67L290 405H49V500H426V433L171 95H435V0Z" glyph-name="zdotaccent" horiz-adv-x="470" unicode="&#380;" /><glyph d="M194 667Q194 696 214.5 716.5Q235 737 264 737Q293 737 313.5 716.5Q334 696 334 667Q334 638 313.5 617.5Q293 597 264 597Q235 597 214.5 617.5Q194 638 194 667ZM50 0V68L326 405H64V500H469V432L192 95H478V0Z" glyph-name="zdotaccent.smcp" horiz-adv-x="528" /><glyph d="M300 668Q251 668 224 646.5Q197 625 184 586.5Q171 548 168.5 494Q166 440 166 375Q166 310 168.5 256Q171 202 184 163.5Q197 125 224 103.5Q251 82 300 82Q349 82 376 103.5Q403 125 416 163.5Q429 202 431.5 256Q434 310 434 375Q434 440 431.5 494Q429 548 416 586.5Q403 625 376 646.5Q349 668 300 668ZM300 -13Q216 -13 168 14.5Q120 42 96 92.5Q72 143 66 214.5Q60 286 60 375Q60 463 66 535Q72 607 96 657.5Q120 708 168 735.5Q216 763 300 763Q384 763 432 735.5Q480 708 504 657.5Q528 607 534 535Q540 463 540 375Q540 286 534 214.5Q528 143 504 92.5Q480 42 432 14.5Q384 -13 300 -13Z" glyph-name="zero" horiz-adv-x="600" unicode="0" /><glyph d="M300 668Q251 668 224 646.5Q197 625 184 586.5Q171 548 168.5 494Q166 440 166 375Q166 310 168.5 256Q171 202 184 163.5Q197 125 224 103.5Q251 82 300 82Q349 82 376 103.5Q403 125 416 163.5Q429 202 431.5 256Q434 310 434 375Q434 440 431.5 494Q429 548 416 586.5Q403 625 376 646.5Q349 668 300 668ZM300 -13Q216 -13 168 14.5Q120 42 96 92.5Q72 143 66 214.5Q60 286 60 375Q60 463 66 535Q72 607 96 657.5Q120 708 168 735.5Q216 763 300 763Q384 763 432 735.5Q480 708 504 657.5Q528 607 534 535Q540 463 540 375Q540 286 534 214.5Q528 143 504 92.5Q480 42 432 14.5Q384 -13 300 -13Z" glyph-name="zero.LP" horiz-adv-x="600" /><glyph d="M300 418Q237 418 202 379Q167 340 167 250Q167 158 202 120Q237 82 300 82Q363 82 398 120Q433 158 433 250Q433 340 398 379Q363 418 300 418ZM300 -13Q185 -13 123.5 49.5Q62 112 62 250Q62 388 123.5 450.5Q185 513 300 513Q415 513 476.5 450.5Q538 388 538 250Q538 112 476.5 49.5Q415 -13 300 -13Z" glyph-name="zero.OP" horiz-adv-x="600" /><glyph glyph-name="zerowidthspace" horiz-adv-x="0" unicode="&#8203;" /></font></defs></svg>
\ No newline at end of file
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Medium.ttf b/docs/source/_themes/ceph/static/font/ApexSans-Medium.ttf
new file mode 100644 (file)
index 0000000..44c281e
Binary files /dev/null and b/docs/source/_themes/ceph/static/font/ApexSans-Medium.ttf differ
diff --git a/docs/source/_themes/ceph/static/font/ApexSans-Medium.woff b/docs/source/_themes/ceph/static/font/ApexSans-Medium.woff
new file mode 100644 (file)
index 0000000..b7c8819
Binary files /dev/null and b/docs/source/_themes/ceph/static/font/ApexSans-Medium.woff differ
diff --git a/docs/source/_themes/ceph/static/nature.css_t b/docs/source/_themes/ceph/static/nature.css_t
new file mode 100644 (file)
index 0000000..394a633
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * nature.css_t
+ * ~~~~~~~~~~~~
+ *
+ * Sphinx stylesheet -- nature theme.
+ *
+ * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+@import url("basic.css");
+/* -- page layout ----------------------------------------------------------- */
+
+@font-face {
+    font-family: 'ApexSansMedium';
+    src: url('font/ApexSans-Medium.eot');
+    src: url('font/ApexSans-Medium.eot?#iefix') format('embedded-opentype'),
+    url('font/ApexSans-Medium.woff') format('woff'),
+    url('font/ApexSans-Medium.ttf') format('truetype'),
+    url('font/ApexSans-Medium.svg#FontAwesome') format('svg');
+    font-weight: normal;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'ApexSansBook';
+    src: url('font/ApexSans-Book.eot');
+    src: url('font/ApexSans-Book.eot?#iefix') format('embedded-opentype'),
+    url('font/ApexSans-Book.woff') format('woff'),
+    url('font/ApexSans-Book.ttf') format('truetype'),
+    url('font/ApexSans-Book.svg#FontAwesome') format('svg');
+    font-weight: normal;
+    font-style: normal;
+}
+body {
+    font: 14px/1.4 Helvetica, Arial, sans-serif;
+    background-color: #E6E8E8;
+    color: #37424A;
+    margin: 0;
+    padding: 0;
+    border-top: 5px solid #F05C56;
+}
+
+div.documentwrapper {
+    float: left;
+    width: 100%;
+}
+
+div.bodywrapper {
+    margin: 0 0 0 330px;
+}
+
+hr {
+    border: 1px solid #B1B4B6;
+}
+div.document {
+    background-color: #ffffff;
+}
+div.body {
+    background-color: #ffffff;
+    color: #3E4349;
+    padding: 0 30px 30px 30px;
+}
+div.footer {
+    color: #222B31;
+    width: 100%;
+    padding: 13px 0;
+    text-align: center;
+    font-size: 75%;
+}
+div.footer a {
+    color: #444;
+    text-decoration: underline;
+}
+div.related {
+    background-color: #80D2DC;
+    line-height: 32px;
+    color: #37424A;
+    // text-shadow: 0px 1px 0 #444;
+    font-size: 100%;
+    border-top: #9C4850 5px solid;
+}
+div.related a {
+    color: #37424A;
+    text-decoration: none;
+}
+
+div.related a:hover {
+    color: #fff;
+    // text-decoration: underline;
+}
+div.sphinxsidebar {
+    // font-size: 100%;
+    line-height: 1.5em;
+    width: 330px;
+}
+
+div.sphinxsidebarwrapper{
+    padding: 20px 0;
+    background-color: #efefef;
+}
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+    font-family: ApexSansMedium;
+    color: #e6e8e8;
+    font-size: 1.2em;
+    font-weight: normal;
+    margin: 0;
+    padding: 5px 10px;
+    background-color: #5e6a71;
+    // text-shadow: 1px 1px 0 white;
+    text-transform: uppercase;
+}
+
+div.sphinxsidebar h4{
+    font-size: 1.1em;
+}
+div.sphinxsidebar h3 a {
+    color: #e6e8e8;
+}
+div.sphinxsidebar p {
+    color: #888;
+    padding: 5px 20px;
+}
+div.sphinxsidebar p.topless {
+}
+div.sphinxsidebar ul {
+    margin: 10px 5px 10px 20px;
+    padding: 0;
+    color: #000;
+}
+div.sphinxsidebar a {
+    color: #444;
+}
+div.sphinxsidebar input {
+    border: 1px solid #ccc;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+div.sphinxsidebar input[type=text]{
+    margin-left: 20px;
+}
+/* -- body styles ----------------------------------------------------------- */
+a {
+    color: #F05C56;
+    text-decoration: none;
+}
+a:hover {
+    color: #F05C56;
+    text-decoration: underline;
+}
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+    // font-family: ApexSansMedium;
+    // background-color: #80D2DC;
+    // font-weight: normal;
+    // color: #37424a;
+    margin: 30px 0px 10px 0px;
+    padding: 5px 0 5px 0px;
+    // text-shadow: 0px 1px 0 white;
+    text-transform: uppercase;
+}
+div.body h1 { font: 20px/2.0 ApexSansBook; color: #37424A; border-top: 20px solid white; margin-top: 0; }
+div.body h2 { font: 18px/1.8 ApexSansMedium; background-color: #5E6A71; color: #E6E8E8; padding: 5px 10px; }
+div.body h3 { font: 16px/1.6 ApexSansMedium; color: #37424A; }
+div.body h4 { font: 14px/1.4 Helvetica, Arial, sans-serif;  color: #37424A; }
+div.body h5 { font: 12px/1.2 Helvetica, Arial, sans-serif;  color: #37424A; }
+div.body h6 { font-size: 100%; color: #37424A; }
+
+// div.body h2 { font-size: 150%; background-color: #E6E8E8; color: #37424A; }
+// div.body h3 { font-size: 120%; background-color: #E6E8E8; color: #37424A; }
+// div.body h4 { font-size: 110%; background-color: #E6E8E8; color: #37424A; }
+// div.body h5 { font-size: 100%; background-color: #E6E8E8; color: #37424A; }
+// div.body h6 { font-size: 100%; background-color: #E6E8E8; color: #37424A; }
+a.headerlink {
+    color: #c60f0f;
+    font-size: 0.8em;
+    padding: 0 4px 0 4px;
+    text-decoration: none;
+}
+a.headerlink:hover {
+    background-color: #c60f0f;
+    color: white;
+}
+div.body p, div.body dd, div.body li {
+    line-height: 1.5em;
+}
+div.admonition p.admonition-title + p {
+    display: inline;
+}
+
+div.highlight{
+    background-color: white;
+}
+
+div.note {
+    background-color: #e6e8e8;
+    border: 1px solid #ccc;
+}
+div.seealso {
+    background-color: #ffc;
+    border: 1px solid #ff6;
+}
+div.topic {
+    background-color: #efefef;
+}
+div.warning {
+    background-color: #F05C56;
+    border: 1px solid #9C4850;
+    color: #fff;
+}
+p.admonition-title {
+    display: inline;
+}
+p.admonition-title:after {
+    content: ":";
+}
+pre {
+    padding: 10px;
+    background-color: White;
+    color: #222;
+    line-height: 1.2em;
+    border: 1px solid #5e6a71;
+    font-size: 1.1em;
+    margin: 1.5em;
+    -webkit-box-shadow: 1px 1px 1px #e6e8e8;
+    -moz-box-shadow: 1px 1px 1px #e6e8e8;
+}
+tt {
+    background-color: #ecf0f3;
+    color: #222;
+    /* padding: 1px 2px; */
+    font-size: 15px;
+    font-family: monospace;
+}
+
+.viewcode-back {
+    font-family: Arial, sans-serif;
+}
+
+div.viewcode-block:target {
+    background-color: #f4debf;
+    border-top: 1px solid #ac9;
+    border-bottom: 1px solid #ac9;
+}
+
+table.docutils {
+    margin: 1.5em;
+}
+
+div.sidebar {
+    border: 1px solid #5E6A71;
+    background-color: #E6E8E8;
+}
+
+div.admonition.tip {
+    background-color: #80D2DC;
+    border: 1px solid #55AEBA;
+}
+
+div.admonition.important {
+    background-color: #F05C56;
+    border: 1px solid #9C4850;
+    color: #fff;
+}
+
+div.tip tt.literal {
+    background-color: #55aeba;
+    color: #fff;
+}
+
+div.important tt.literal {
+    background-color: #9C4850;
+    color: #fff;
+}
+
+h2 .literal {
+    color: #fff;
+    background-color: #37424a;
+}
+
+dl.glossary dt {
+       font-size: 1.0em;
+       padding-top:20px;       
+
+}
\ No newline at end of file
diff --git a/docs/source/_themes/ceph/theme.conf b/docs/source/_themes/ceph/theme.conf
new file mode 100644 (file)
index 0000000..1cc4004
--- /dev/null
@@ -0,0 +1,4 @@
+[theme]
+inherit = basic
+stylesheet = nature.css
+pygments_style = tango
diff --git a/docs/source/admin.rst b/docs/source/admin.rst
new file mode 100644 (file)
index 0000000..8ae7395
--- /dev/null
@@ -0,0 +1,26 @@
+.. _admin:
+
+admin
+=======
+The ``admin`` subcommand provides an interface to add to the cluster's admin
+node.
+
+Example
+-------
+To make a node and admin node run::
+
+  ceph-deploy admin ADMIN [ADMIN..]
+
+This places the the cluster configuration and the admin keyring on the remote
+nodes.
+
+Admin node definition
+---------------------
+
+The definition of an admin node is that both the cluster configuration file
+and the admin keyring. Both of these files are stored in the directory
+/etc/ceph and thier prefix is that of the cluster name.
+
+The default ceph cluster name is "ceph". So with a cluster with a default name
+the admin keyring is named /etc/ceph/ceph.client.admin.keyring while cluster
+configuration file is named /etc/ceph/ceph.conf.
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
new file mode 100644 (file)
index 0000000..13cd4e4
--- /dev/null
@@ -0,0 +1,613 @@
+Changelog
+=========
+
+2.0
+---
+
+2.0.2
+^^^^^
+16-Jul-2018
+
+* Bump the ``remoto`` requirement that fixes the ``expand_env`` bug
+
+
+2.0.1
+^^^^^
+19-Jun-2018
+
+* Add support for archlinux
+* Support IPV6 addresses in monitors
+* Add debug argument when calling disk zap
+* Ensure remote executables are files (vs. possible dirs)
+* Run ``apt-get update`` before installs
+* Default to mimic release
+* Use INFO log levels for disk list
+* Fix ``UnboundLocalError`` when createing mds/mgr with bad hosts
+* Improve distro detection for Arch Linux
+* Add epilog text
+
+
+2.0.0
+^^^^^
+16-Jan-2018
+
+* Backward incompatible API changes for OSD creation - will use ceph-volume and
+  no longer consume ceph-disk.
+* Remove python-distribute dependency
+* Use /etc/os-release as a fallback when ``linux_distribution()`` doesn't work
+* Drop dmcrypt support (unsupported by ceph-volume for now)
+* Allow debug modes for ceph-volume
+
+
+1.5
+---
+
+1.5.39
+^^^^^^
+1-Sep-2017
+
+* Remove ``--cluster`` options, default to ``ceph`` always
+* Add ``--filestore`` since ``ceph-disk`` defaults to bluestore
+* Start testing against Python 3.5
+* Support Debian 9 and 10 intalls
+* Better handling on package conflicts when upgrading/re-installing
+
+
+1.5.38
+^^^^^^
+19-May-2017
+
+* Allow unsigned deb packages from mirrors
+* Detect systemd before sysvinit in centos
+* Fix UnboundLocalError when installing in debian with custom repo flags
+* gatherkeys to give mgr "allow * " permissions
+* specify block.db and block.wal for bluestore
+* be able to install ceph-mgr
+* bootstrap mgr keys
+* cleanup mds key creation
+* Virtuozzo Linux support
+* update osd and mds caps
+
+
+1.5.37
+^^^^^^
+03-Jan-2017
+
+* Use the ``--cluster`` flag on monitor commands (defaulting to 'ceph' if
+  unspecfied)
+* After adding a monitor, ensure it is started regardless of init system
+* Allow Oracle Linux Server to be deployed to.
+* Fix issue when calling gatherkeys where a log argument was missing
+* Use the new development services for installation (from chacra.ceph.com and
+  shaman.ceph.com URLs)
+* Try to decode bytes only on Python 3 when writing files on remote hosts
+
+
+1.5.36
+^^^^^^
+29-Aug-2016
+
+* Prefer to use ``load_raw`` to avoid mangling ceph.conf content.
+* Improve systemd/sysvinit detection for both CentOS and RHEL
+* Gatherkeys should try to get an existing key without caps, in case they don't
+  match
+
+1.5.35
+^^^^^^
+15-Aug-2016
+
+* Add compatibility for bytes/strings with Python 3
+* Fix errors in argparse default behavior (error messages, incomplete commands)
+* Add Python 3.4 to tox
+* Python 3 changes to workaround configparser issues
+* Use the configured username when using rsync to a remote host (local repo
+  support)
+* Install Python 3 with the bootstrap sciprt
+* Bump remoto requirement to 0.0.29
+* Include admin.rst and gatherkeys.rst in the TOC index
+* Handle Ceph package split in Ubuntu
+* Add a ``--nogpgcheck`` option to disable checks on local repos
+* Improve sysvinit/systemd checks by not including 'ceph' in the path
+* Install Diamond when calling ``ceph-deploy calamari connect``
+* Zypper fixes for purging: allows removal of multiple packages
+
+
+07-Jun-2016
+1.5.34
+^^^^^^
+07-Jun-2016
+
+* Do not call partx/partprobe when zapping disks
+* No longer allow using ext4
+* Default to systemd for SUSE
+* Remove usage of rcceph (for SUSE)
+* No longer depend on automatic ``ceph-create-keys``, use the monitors to fetch
+  keys.
+* Use ``0.0.28`` from remoto
+
+1.5.33
+^^^^^^
+22-Apr-2016
+
+* Default to Jewel for releases
+
+1.5.32
+^^^^^^
+13-Apr-2016
+
+* Improve systemd detection for Ubuntu releases.
+* Rename ceph-deploy log to include the cluster name
+* Bluestore support
+* Disable timeouts for pkg install/remove operations (they can take a long
+  time)
+* Remove deprecated ceph.conf configuration "filestore xattr use omap = true"
+
+1.5.31
+^^^^^^
+04-Jan-2016
+
+* Use the new remoto version (0.0.27) that fixes an error when dealing with
+  remote output.
+
+1.5.30
+^^^^^^
+11-Dec-2015
+
+* Default to the "infernalis" release.
+* Fix an issue when trying to destroy/stop monitors on systemd servers
+
+1.5.29
+^^^^^^
+2-Dec-2015
+
+* Add support for ``--dev-commit <sha1>``
+* Add ``--test`` option for installing ceph-test package
+* Enable Ceph on ``osd create``
+* Remove bootstrap-rgw key when forgetkeys is used
+* Prefer systemd over upstart in newer Ubuntu
+* Use download.ceph.com directly
+* Use better examples in default cephdeploy.conf file
+* Cleanup functions for uninstall and purge (simplifying code)
+* Use https for download.cep.com
+* Fix gitbuilder hosts to avoid using https
+* Do not udevadm trigger because ceph-disk does it already
+* Download gpg keys from download.ceph.com
+* Specify a PID location for monitors
+* Fix invalid path for release keys in test
+* Add timestamp to log output
+
+1.5.28
+^^^^^^
+26-Aug-2015
+
+* Fix issue when importing GPG keys on Centos 6 introduced in 1.5.27.
+* Support systemd and sysvinit on RHEL, Fedora, and CentOS, when systemd
+  is present in the Ceph packages.
+* Simplify steps taken when adding a monitor with ``ceph-deploy mon add``.
+  Eliminates a 5-minute hang when moving from 1 monitor to 2.
+* Make sure that Ceph is installed on a remote node before trying to enable
+  a Ceph daemon.
+
+1.5.27
+^^^^^^
+05-Aug-2015
+
+* New ``repo`` top-level command for adding and removing repos.
+* Ability to install subset of ceph packages based on CLI switches like
+  ``--cli``, ``--rgw``, etc.
+* Initial support for systemd.  Ceph on Fedora 22 only.
+* Fixed an issue that prevented package upgrades when using DNF.
+* No longer installs yum-priorities-plugin when using DNF.
+
+1.5.26
+^^^^^^
+20-Jul-2015
+
+* Make parsing of boolean values in config file overrides work.
+* Output value of all ceph-deploy options upon invocation.
+* Point to git.ceph.com for GPG keys.
+* Make GPG key fetching work on Debian Wheezy.
+* Allow ceph-deploy to work on Mint distro.
+* Improved help menu output during subcommand context.
+* Point to SUSE downstream packages by default on SUSE distros since
+  ceph.com does not host packages for SUSE anymore..
+* Some initial groundwork for installing Ceph daemons that will no longer
+  run as root user.
+* Add support for DNF package manager (Fedora >= 22 only).
+* Echo RGW default port number after ``ceph-deploy rgw create``.
+
+1.5.25
+^^^^^^
+26-May-2015
+
+* **CVE-2015-4053**: Make sure that the admin keyring is mode 0600 after being
+  pushed with the ``ceph-deploy admin`` command.
+* Improved SUSE install and purge.
+* Make sure that package name 'ceph-radosgw' is used everywhere for RPM systems
+  instead of 'radosgw'.
+
+1.5.24
+^^^^^^
+18-May-2015
+
+* Use version 0.0.25 of ``remoto`` that fixes an issue where output would be cut
+  (https://github.com/alfredodeza/remoto/issues/15).
+* Automatically prefix custom RGW daemon names with 'rgw.'
+* Log an error message when deploying MDS in RHEL distros fails as it may not
+  be supported.
+* More robust vendor.py script (tries ceph.com and GitHub)
+* Create /var/lib/ceph/radosgw directory on remote host if not present
+* Enable/start ceph-radosgw service on RPM systems instead of radosgw
+* Add flags to support install of specific daemons (OSD, MON, RGW, MDS) only
+  Note that the packaging changes for this in upstream Ceph are still pending
+* removing installation of 'calamari-minions' repo upon
+  'ceph-deploy calamari connect'
+* enable ceph-mds service correctly on systemd
+* Check for sysvinit and custom cluster name on 'ceph-deploy new' command
+
+1.5.23
+^^^^^^
+07-Apr-2015
+
+* Default to Hammer on install.
+* Add ``rgw`` command to easily create rgw instances.
+* Automatically install the radosgw package.
+* Remove unimplemented subcommands from CLI and help.
+* **CVE-2015-3010**: Fix an issue where keyring permissions were
+  world readable (thanks Owen Synge).
+* Fix an issue preventing all but the first host given to
+  ``install --repo`` from being used.
+
+1.5.22
+^^^^^^
+09-Mar-2015
+
+* Enable ``check_obsoletes`` in Yum priorities plugin when deploying
+  upstream Ceph on RPM-based distros.
+* Require ``--release`` flag to install upstream Ceph on RHEL.
+* Uninstall ``ceph-common`` on Fedora.
+
+1.5.21
+^^^^^^
+10-Dec-2014
+
+* Fix distro detection for CentOS and Scientific Linux, which was
+  preventing installation of EPEL repo as a prerequisite.
+* Default to Giant on install.
+* Fix an issue where ``gatherkeys`` did not exit non-zero when
+  keys were not found.
+
+1.5.20
+^^^^^^
+13-Nov-2014
+
+* log stderr and stdout in the same order as they happen remotely.
+
+1.5.19
+^^^^^^
+29-Oct-2014
+
+* Create temporary ceph.conf files in ``/etc/ceph`` to avoid issues with
+  SELinux.
+
+1.5.18
+^^^^^^
+09-Oct-2014
+
+* Fix issue for enabling the OSD service in el-like distros.
+* Create a monitor keyring if it doesn't exist.
+
+1.5.17
+^^^^^^
+06-Oct-2014
+
+* Do not ask twice for passwords when calling ``new``.
+* Ensure priorities are installed and enforced for custom repositories.
+
+1.5.16
+^^^^^^
+30-Sep-2014
+
+* Enable services on ``el`` distros when deploying Ceph daemons.
+* Smarter detection of ``sudo`` need on remote nodes (prevents issues when
+  running ceph-deploy as ``root`` or with ``sudo``.
+* Fix an issue where Debian Sid would break ceph-deploy failing Distro
+  detection.
+
+1.5.15
+^^^^^^
+12-Sep-2014
+
+* If ``wget`` is installed don't try to install it regardless.
+
+1.5.14
+^^^^^^
+09-Sep-2014
+
+* Do not override environment variables on remote hosts, preserve them and
+  extend the ``$PATH`` if not explicitly told not to.
+
+1.5.13
+^^^^^^
+03-Sep-2014
+
+* Fix missing priority plugin in YUM for Fedora when installing
+* Implement --public-network and --cluster-network with remote IP validation
+* Fixed an issue where errors before the logger was setup would be silenced.
+
+1.5.12
+^^^^^^
+25-Aug-2014
+
+* Better traceback reporting with logging.
+* Close stderr/stdout when ceph-deploy completes operations (silences odd
+  tracebacks)
+* Allow to re-use a ceph.conf file with ``--ceph-conf`` global flag
+* Be able to concatenate and seed keyring files with ``--keyrings``
+
+1.5.11
+^^^^^^
+25-Aug-2014
+
+*  Fix a problem where CentOS7 is not matched correctly against repos (Thanks
+   Tom Walsh)
+
+1.5.10
+^^^^^^
+31-Jul-2014
+
+* Use ``ceph-disk`` with high verbosity
+* Don't require ``ceph-common`` on EL distros
+* Use ``ceph-disk zap`` instead of re-implementing it
+* Use proper paths for ``zypper`` (Thanks Owen Synge)
+* More robust ``init`` detection for Ubuntu (Thanks Joao Eduardo Luis)
+* Allow to install repo files only
+* Work with inconsistent repo sections for Emperor when setting priorities
+
+1.5.9
+^^^^^
+14-Jul-2014
+
+* Allow to optionally set the ``fsid`` when calling ``new``
+* Correctly select sysvinit or systemd for Suse versions (Thanks Owen Synge)
+* Use correct version of remoto (``0.0.19``) that holds the ``None`` global fix
+* Fix new naming scheme for CentOS platforms that prevented CentOS 7 installs
+
+1.5.8
+^^^^^
+09-Jul-2014
+
+* Create a flake8/pep8/linting job so that we prevent Undefined errors
+* Add partprobe/partx calls when zapping disks
+* Fix RHEL7 installation issues (url was using el6 incorrectly) (Thanks David Vossel)
+* Warn when an executable is not found
+* Fix an ``AttributeError`` in execnet (see https://github.com/alfredodeza/execnet/issues/1)
+
+1.5.7
+^^^^^
+01-Jul-2014
+
+* Fix ``NameError`` on osd.py from an undefined variable
+* Fix a calamari connect problem when installing on multiple hosts
+
+1.5.6
+^^^^^
+01-Jul-2014
+
+* Optionally avoid vendoring libraries for upstream package maintainers.
+* Fix RHEL7 installation issue that was pulling ``el6`` packages (Thanks David Vossel)
+
+1.5.5
+^^^^^
+10-Jun-2014
+
+* Normalize repo file header calls. Fixes breakage on Calamari repos.
+
+1.5.4
+^^^^^
+10-Jun-2014
+
+* Improve help by adding online doc link
+* allow cephdeploy.conf to set priorities in repos
+* install priorities plugin for yum distros
+* set the right priority for ceph.repo and warn about this
+
+1.5.3
+^^^^^
+30-May-2014
+
+* Another fix for IPV6: write correct ``mon_host`` in ceph.conf
+* Support ``proxy`` settings for repo files in YUM
+* Better error message when ceph.conf is not found
+* Refuse to install custom cluster names on sysvinit systems (not supported)
+* Remove quiet flags from package manager's install calls to avoid timing out
+* Use the correct URL repo when installing for RHEL
+
+1.5.2
+^^^^^
+09-May-2014
+
+* Remove ``--`` from the command to install packages. (Thanks Vincenzo Pii)
+* Default to Firefly as the latest, stable Ceph version
+
+1.5.1
+^^^^^
+01-May-2014
+
+* Fixes a broken ``osd`` command that had the wrong attribute in the conn
+  object
+
+1.5.0
+^^^^^
+28-Apr-2014
+
+* Warn if ``requiretty`` is causing issues
+* Support IPV6 host resolution (Thanks Frode Nordahl)
+* Fix incorrect paths for local cephdeploy.conf
+* Support subcommand overrides defined in cephdeploy.conf
+* When installing on CentOS/RHEL call ``yum clean all``
+* Check OSD status when deploying to catch possible issues
+* Add a ``--local-mirror`` flag for installation that syncs files
+* Implement ``osd list`` to list remote osds
+* Fix install issues on Suse (Thanks Owen Synge)
+
+1.4
+-----
+
+1.4.0
+^^^^^
+* uninstall ceph-release and clean cache in CentOS
+* Add ability to add monitors to an existing cluster
+* Deprecate use of ``--stable`` for releases, introduce ``--release``
+* Eat some tracebacks that may appear when closing remote connections
+* Enable default ceph-deploy configurations for repo handling
+* Fix wrong URL for rpm installs with ``--testing`` flag
+
+1.3
+---
+
+1.3.5
+^^^^^
+* Support Debian SID for installs
+* Error nicely when hosts cannot be resolved
+* Return a non-zero exit status when monitors have not formed quorum
+* Use the new upstream library for remote connections (execnet 1.2)
+* Ensure proper read permissions for ceph.conf when pushing configs
+* clean up color logging for non-tty sessions
+* do not reformat configs when pushing, pushes are now as-is
+* remove dry-run flag that did nothing
+
+1.3.4
+^^^^^
+* ``/etc/ceph`` now gets completely removed when using ``purgedata``.
+* Refuse to perform ``purgedata`` if ceph is installed
+* Add more details when a given platform is not supported
+* Use new Ceph auth settings for ``ceph.conf``
+* Remove old journal size settings from ``ceph.conf``
+* Add a new subcommand: ``pkg`` to install/remove packages from hosts
+
+
+1.3.3
+^^^^^
+* Add repo mirror support with ``--repo-url`` and ``--gpg-url``
+* Remove dependency on the ``which`` command
+* Fix problem when removing ``/var/lib/ceph`` and OSDs are still mounted
+* Make sure all tmp files are closed before moving, fixes issue when creating
+  keyrings and conf files
+* Complete remove the lsb module
+
+
+1.3.2
+^^^^^
+* ``ceph-deploy new`` will now attempt to copy SSH keys if necessary unless it
+  it disabled.
+* Default to Emperor version of ceph when installing.
+
+1.3.1
+^^^^^
+* Use ``shutil.move`` to overwrite files from temporary ones (Thanks Mark
+  Kirkwood)
+* Fix failure to ``wget`` GPG keys on Debian and Debian-based distros when
+  installing
+
+1.3.0
+^^^^^
+* Major refactoring for all the remote connections in ceph-deploy. With global
+  and granular timeouts.
+* Raise the log level for missing keyrings
+* Allow ``--username`` to be used for connecting over SSH
+* Increase verbosity when MDS fails, include the exit code
+* Do not remove ``/etc/ceph``, just the contents
+* Use ``rcceph`` instead of service for SUSE
+* Fix lack of ``--cluster`` usage on monitor error checks
+* ensure we correctly detect Debian releases
+
+1.2
+---
+
+1.2.7
+^^^^^
+* Ensure local calls to ceph-deploy do not attempt to ssh.
+* ``mon create-initial`` command to deploy all defined mons, wait for them to
+  form quorum and finally to gatherkeys.
+* Improve help menu for mon commands.
+* Add ``--fs-type`` option to ``disk`` and ``osd`` commands (Thanks Benoit
+  Knecht)
+* Make sure we are using ``--cluster`` for remote configs when starting ceph
+* Fix broken ``mon destroy`` calls using the new hostname resolution helper
+* Add a helper to catch common monitor errors (reporting the status of a mon)
+* Normalize all configuration options in ceph-deploy (Thanks Andrew Woodward)
+* Use a ``cuttlefish`` compatible ``mon_status`` command
+* Make ``osd activate`` use the new remote connection libraries for improved
+  readability.
+* Make ``disk zap`` also use the new remote connection libraries.
+* Handle any connection errors that may came up when attempting to get into
+  remote hosts.
+
+1.2.6
+^^^^^
+* Fixes a problem witha closed connection for Debian distros when creating
+  a mon.
+
+1.2.5
+^^^^^
+* Fix yet another hanging problem when starting monitors. Closing the
+  connection now before we even start them.
+
+1.2.4
+^^^^^
+* Improve ``osd help`` menu with path information
+* Really discourage the use of ``ceph-deploy new [IP]``
+* Fix hanging remote requests
+* Add ``mon status`` output when creating monitors
+* Fix Debian install issue (wrong parameter order) (Thanks Sayid Munawar)
+* ``osd`` commands will be more verbose when deploying them
+* Issue a warning when provided hosts do not match ``hostname -s`` remotely
+* Create two flags for altering/not-altering source repos at install time:
+  ``--adjust-repos`` and ``--no-adjust-repos``
+* Do not do any ``sudo`` commands if user is root
+* Use ``mon status`` for every ``mon`` deployment and detect problems with
+  monitors.
+* Allow to specify ``host:fqdn/ip`` for all mon commands (Thanks Dmitry
+  Borodaenko)
+* Be consistent for hostname detection (Thanks Dmitry Borodaenko)
+* Fix hanging problem on remote hosts
+
+1.2.3
+^^^^^
+* Fix non-working ``disk list``
+* ``check_call`` utility fixes ``$PATH`` issues.
+* Use proper exit codes from the ``main()`` CLI function
+* Do not error when attempting to add the EPEL repos.
+* Do not complain when using IP:HOST pairs
+* Report nicely when ``HOST:DISK`` is not used when zapping.
+
+1.2.2
+^^^^^
+* Do not force usage of lsb_release, fallback to
+  ``platform.linux_distribution()``
+* Ease installation in CentOS/Scientific by adding the EPEL repo
+  before attempting to install Ceph.
+* Graceful handling of pushy connection issues due to host
+  address resolution
+* Honor the usage of ``--cluster`` when calling osd prepare.
+
+1.2.1
+^^^^^
+* Print the help when no arguments are passed
+* Add a ``--version`` flag
+* Show the version in the help menu
+* Catch ``DeployError`` exceptions nicely with the logger
+* Fix blocked command when calling ``mon create``
+* default to ``dumpling`` for installs
+* halt execution on remote exceptions
+
+1.2.0
+^^^^^
+* Better logging output
+* Remote logging for individual actions for ``install`` and ``mon create``
+* Install ``ca-certificates`` on all Debian-based distros
+* Honor the usage of ``--cluster``
+* Do not ``rm -rf`` monitor logs when destroying
+* Error out when ``ceph-deploy new [IP]`` is used
+* Log the ceph version when installing
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644 (file)
index 0000000..b841234
--- /dev/null
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+#
+# ceph-deploy documentation build configuration file, created by
+# sphinx-quickstart on Mon Oct 21 09:32:42 2013.
+#
+# 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
+import os
+
+# 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.append(os.path.abspath('_themes'))
+sys.path.insert(0, os.path.abspath('..'))
+import ceph_deploy
+
+# -- 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',
+]
+
+# 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 = 'contents'
+
+# General information about the project.
+project = u'ceph-deploy'
+copyright = u'2013, Inktank'
+
+# 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 = ceph_deploy.__version__
+# The full version, including alpha/beta/rc tags.
+release = ceph_deploy.__version__
+
+# 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 = []
+
+# 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 = 'ceph'
+
+# 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 = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['_themes']
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> 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']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# 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'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+html_use_smartypants = False
+
+# 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 <link> 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 = 'ceph-deploydoc'
+
+
+# -- 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, or own class]).
+latex_documents = [
+  ('index', 'ceph-deploy.tex', u'ceph-deploy Documentation',
+   u'Inktank', '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', 'ceph-deploy', u'ceph-deploy Documentation',
+     [u'Inktank'], 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', 'ceph-deploy', u'ceph-deploy Documentation',
+   u'Inktank', 'ceph-deploy', '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
+
+
+# XXX Uncomment when we are ready to link to ceph docs
+# Example configuration for intersphinx: refer to the Python standard library.
+#intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/docs/source/conf.rst b/docs/source/conf.rst
new file mode 100644 (file)
index 0000000..6eee392
--- /dev/null
@@ -0,0 +1,175 @@
+.. _conf:
+
+Ceph Deploy Configuration
+=========================
+Starting with version 1.4, ceph-deploy uses a configuration file that can be
+one of:
+
+* ``cephdeploy.conf`` (in the current directory)
+* ``$HOME/.cephdeploy.conf`` (hidden in the user's home directory)
+
+This configuration file allows for setting certain ceph-deploy behavior that
+would be difficult to set on the command line or that it might be cumbersome to
+do.
+
+The file itself follows the INI style of configurations which means that it
+consists of sections (in brackets) that may contain any number of key/value
+pairs.
+
+If a configuration file is not found in the current working directory nor in
+the user's home dir, ceph-deploy will proceed to create one in the home
+directory.
+
+This is how a default configuration file would look like::
+
+    #
+    # ceph-deploy configuration file
+    #
+
+    [ceph-deploy-global]
+    # Overrides for some of ceph-deploy's global flags, like verbosity or cluster
+    # name
+
+    [ceph-deploy-install]
+    # Overrides for some of ceph-deploy's install flags, like version of ceph to
+    # install
+
+
+    #
+    # Repositories section
+    #
+
+    # yum repos:
+    # [myrepo]
+    # baseurl = https://user:pass@example.org/rhel6
+    # gpgurl = https://example.org/keys/release.asc
+    # default = True
+    # extra-repos = cephrepo  # will install the cephrepo file too
+    #
+    # [cephrepo]
+    # name=ceph repo noarch packages
+    # baseurl=http://ceph.com/rpm-emperor/el6/noarch
+    # enabled=1
+    # gpgcheck=1
+    # type=rpm-md
+    # gpgkey=https://download.ceph.com/keys/release.asc
+
+    # apt repos:
+    # [myrepo]
+    # baseurl = https://user:pass@example.org/
+    # gpgurl = https://example.org/keys/release.asc
+    # default = True
+    # extra-repos = cephrepo  # will install the cephrepo file too
+    #
+    # [cephrepo]
+    # baseurl=http://ceph.com/rpm-emperor/el6/noarch
+    # gpgkey=https://download.ceph.com/keys/release.asc
+
+.. conf_sections:
+
+Sections
+--------
+To work with ceph-deploy configurations, it is important to note that all
+sections that relate to ceph-deploy's flags and state are prefixed with
+``ceph-deploy-`` followed by the subcommand or by ``global`` if it is something
+that belongs to the global flags.
+
+Any other section that is not prefixed with ``ceph-deploy-`` is considered
+a repository.
+
+Repositories can be very complex to describe and most of the time (specially
+for yum repositories) they can be very verbose too.
+
+Setting Default Flags or Values
+-------------------------------
+Because the configuration loading allows specifying the same flags as in the
+CLI it is possible to set defaults. For example, assuming that a user always
+wants to install Ceph the following way (that doesn't create/modify remote repo
+files)::
+
+    ceph-deploy install --no-adjust-repos {nodes}
+
+This can be the default behavior by setting it in the right section in the
+configuration file, which should look like this::
+
+    [ceph-deploy-install]
+    adjust_repos = False
+
+The default for ``adjust_repos`` is ``True``, but because we are changing this
+to ``False`` the CLI will now have this behavior changed without the need to
+pass any flag.
+
+Repository Sections
+-------------------
+Keys will depend on the type of package manager that will use it. Certain keys
+for yum are required (like ``baseurl``) and some others like ``gpgcheck`` are
+optional.
+
+For both yum and apt these would be all the required keys in a repository section:
+
+* baseurl
+* gpgkey
+
+If a required key is not present ceph-deploy will abort the installation
+process with an error identifying the section and key what was missing.
+
+In yum the repository name is taken from the section, so if the section is
+``[foo]``, then the name of the repository will be ``foo repo`` and the
+filename written to ``/etc/yum.repos.d/`` will be ``foo.repo``.
+
+For apt, the same happens except the directory location changes to:
+``/etc/apt/sources.list.d/`` and the file becomes ``foo.list``.
+
+
+Optional values for yum
+-----------------------
+**name**:  A descriptive name for the repository. If not provided ``{repo
+section} repo`` is used
+
+**enabled**: Defaults to ``1``
+
+**gpgcheck**: Defaults to ``1``
+
+**type**: Defaults to ``rpm-md``
+
+**gpgcheck**: Defaults to ``1``
+
+
+Default Repository
+------------------
+For installations where a default repository is needed a key can be added to
+that section to indicate it is the default one::
+
+    [myrepo]
+    default = true
+
+When a default repository is detected it is mentioned in the log output and
+ceph will get install from that one repository at the end.
+
+Extra Repositories
+------------------
+If other repositories need to be installed aside from the main one, a key
+should be added to represent that need with a comma separated value with the
+name of the sections of the other repositories (just like the example
+configuration file demonstrates)::
+
+    [myrepo]
+    baseurl = https://user:pass@example.org/rhel6
+    gpgurl = https://example.org/keys/release.asc
+    default = True
+    extra-repos = cephrepo  # will install the cephrepo file too
+
+    [cephrepo]
+    name=ceph repo noarch packages
+    baseurl=http://ceph.com/rpm-emperor/el6/noarch
+    enabled=1
+    gpgcheck=1
+    type=rpm-md
+    gpgkey=https://download.ceph.com/keys/release.asc
+
+In this case, the repository called ``myrepo`` defines the ``extra-repos`` key
+with just one extra one: ``cephrepo``.
+
+This extra repository must exist as a section in the configuration file. After
+the main one is added all the extra ones defined will follow. Installation of
+Ceph will only happen with the main repository.
diff --git a/docs/source/contents.rst b/docs/source/contents.rst
new file mode 100644 (file)
index 0000000..c80d8cc
--- /dev/null
@@ -0,0 +1,18 @@
+Content Index
+=============
+
+.. toctree::
+   :maxdepth: 2
+
+   index.rst
+   new.rst
+   install.rst
+   mon.rst
+   rgw.rst
+   mds.rst
+   conf.rst
+   pkg.rst
+   repo.rst
+   changelog.rst
+   admin.rst
+   gatherkeys.rst
diff --git a/docs/source/gatherkeys.rst b/docs/source/gatherkeys.rst
new file mode 100644 (file)
index 0000000..6a1bdea
--- /dev/null
@@ -0,0 +1,55 @@
+.. _gatherkeys:
+
+==========
+gatherkeys
+==========
+
+The ``gatherkeys`` subcommand provides an interface to get with a cluster's
+cephx bootstrap keys.
+
+keyrings
+========
+The ``gatherkeys`` subcommand retrieves the following keyrings.
+
+ceph.mon.keyring
+----------------
+This keyring is used by all mon nodes to communicate with other mon nodes.
+
+ceph.client.admin.keyring
+-------------------------
+This keyring is ceph client commands by default to administer the ceph cluster.
+
+ceph.bootstrap-osd.keyring
+--------------------------
+This keyring is used to generate cephx keyrings for OSD instances.
+
+ceph.bootstrap-mds.keyring
+--------------------------
+This keyring is used to generate cephx keyrings for MDS instances.
+
+ceph.bootstrap-rgw.keyring
+--------------------------
+This keyring is used to generate cephx keyrings for RGW instances.
+
+Example
+=======
+The ``gatherkeys`` subcommand contacts the mon and creates or retrieves existing
+keyrings from the mon internal store. To run::
+
+  ceph-deploy gatherkeys MON [MON..]
+
+You can optionally add as many mon nodes to the command line as desired. The
+``gatherkeys`` subcommand will succeed on the first mon to respond successfully
+with all the keyrings.
+
+Backing up of old keyrings
+==========================
+
+If old keyrings exist in the current working directory that do not match the
+retrieved keyrings these old keyrings will be renamed with a time stamp
+extention so you will not loose valuable keyrings.
+
+.. note:: Before version v1.5.33 ceph-deploy relied upon ``ceph-create-keys``
+          and did not backup existing keys. Using ``ceph-create-keys`` produced
+          a side effect of deploying all bootstrap keys on the mon node so
+          making all mon nodes admin nodes.
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644 (file)
index 0000000..3ba0f99
--- /dev/null
@@ -0,0 +1,315 @@
+========================================================
+ ceph-deploy -- Deploy Ceph with minimal infrastructure
+========================================================
+
+``ceph-deploy`` is a way to deploy Ceph relying on just SSH access to
+the servers, ``sudo``, and some Python. It runs fully on your
+workstation, requiring no servers, databases, or anything like that.
+
+If you set up and tear down Ceph clusters a lot, and want minimal
+extra bureaucracy, this is for you.
+
+.. _what this tool is not:
+
+What this tool is not
+---------------------
+It is not a generic deployment system, it is only for Ceph, and is designed
+for users who want to quickly get Ceph running with sensible initial settings
+without the overhead of installing Chef, Puppet or Juju.
+
+It does not handle client configuration beyond pushing the Ceph config file
+and users who want fine-control over security settings, partitions or directory
+locations should use a tool such as Chef or Puppet.
+
+
+Installation
+============
+Depending on what type of usage you are going to have with ``ceph-deploy`` you
+might want to look into the different ways to install it. For automation, you
+might want to ``bootstrap`` directly. Regular users of ``ceph-deploy`` would
+probably install from the OS packages or from the Python Package Index.
+
+Python Package Index
+--------------------
+If you are familiar with Python install tools (like ``pip`` and
+``easy_install``) you can easily install ``ceph-deploy`` like::
+
+    pip install ceph-deploy
+
+
+It should grab all the dependencies for you and install into the current user's
+environment.
+
+We highly recommend using ``virtualenv`` and installing dependencies in
+a contained way.
+
+
+DEB
+---
+All new releases of ``ceph-deploy`` are pushed to all ``ceph`` DEB release
+repos.
+
+The DEB release repos are found at::
+
+     http://ceph.com/debian-{release}
+     http://ceph.com/debian-testing
+
+This means, for example, that installing ``ceph-deploy`` from
+http://ceph.com/debian-giant will install the same version as from
+http://ceph.com/debian-firefly or http://ceph.com/debian-testing.
+
+RPM
+---
+All new releases of ``ceph-deploy`` are pushed to all ``ceph`` RPM release
+repos.
+
+The RPM release repos are found at::
+
+     http://ceph.com/rpm-{release}
+     http://ceph.com/rpm-testing
+
+Make sure you add the proper one for your distribution (i.e. el7 vs rhel7).
+
+This means, for example, that installing ``ceph-deploy`` from
+http://ceph.com/rpm-giant will install the same version as from
+http://ceph.com/rpm-firefly or http://ceph.com/rpm-testing.
+
+bootstrapping
+-------------
+To get the source tree ready for use, run this once::
+
+  ./bootstrap
+
+You can symlink the ``ceph-deploy`` script in this somewhere
+convenient (like ``~/bin``), or add the current directory to ``PATH``,
+or just always type the full path to ``ceph-deploy``.
+
+
+SSH and Remote Connections
+==========================
+``ceph-deploy`` will attempt to connect via SSH to hosts when the hostnames do
+not match the current host's hostname. For example, if you are connecting to
+host ``node1`` it will attempt an SSH connection as long as the current host's
+hostname is *not* ``node1``.
+
+ceph-deploy at a minimum requires that the machine from which the script is
+being run can ssh as root without password into each Ceph node.
+
+To enable this generate a new ssh keypair for the root user with no passphrase
+and place the public key (``id_rsa.pub`` or ``id_dsa.pub``) in::
+
+    /root/.ssh/authorized_keys
+
+and ensure that the following lines are in the sshd config::
+
+    PermitRootLogin yes
+    PermitEmptyPasswords yes
+
+The machine running ceph-deploy does not need to have the Ceph packages
+installed unless it needs to admin the cluster directly using the ``ceph``
+command line tool.
+
+
+usernames
+---------
+When not specified the connection will be done with the same username as the
+one executing ``ceph-deploy``. This is useful if the same username is shared in
+all the nodes but can be cumbersome if that is not the case.
+
+A way to avoid this is to define the correct usernames to connect with in the
+SSH config, but you can also use the ``--username`` flag as well::
+
+    ceph-deploy --username ceph install node1
+
+``ceph-deploy`` then in turn would use ``ceph@node1`` to connect to that host.
+
+This would be the same expectation for any action that warrants a connection to
+a remote host.
+
+
+Managing an existing cluster
+============================
+
+You can use ceph-deploy to provision nodes for an existing cluster.
+To grab a copy of the cluster configuration file (normally
+``ceph.conf``)::
+
+ ceph-deploy config pull HOST
+
+You will usually also want to gather the encryption keys used for that
+cluster::
+
+    ceph-deploy gatherkeys MONHOST
+
+At this point you can skip the steps below that create a new cluster
+(you already have one) and optionally skip installation and/or monitor
+creation, depending on what you are trying to accomplish.
+
+
+Installing packages
+===================
+For detailed information on installation instructions refer to the :ref:`install`
+section.
+
+Proxy or Firewall Installs
+--------------------------
+If attempting to install behind a firewall or through a proxy you can
+use the ``--no-adjust-repos`` that will tell ceph-deploy to skip any changes
+to the distro's repository in order to install the packages and it will go
+straight to package installation.
+
+That will allow an environment without internet access to point to *its own
+repositories*. This means that those repositories will need to be properly
+setup (and mirrored with all the necessary dependencies) before attempting an
+install.
+
+Another alternative is to set the `wget` env variables to point to the right
+hosts, for example::
+
+    http_proxy=http://host:port
+    ftp_proxy=http://host:port
+    https_proxy=http://host:port
+
+
+Creating a new configuration
+============================
+
+To create a new configuration file and secret key, decide what hosts
+will run ``ceph-mon``, and run::
+
+  ceph-deploy new MON [MON..]
+
+For detailed information on new instructions refer to the :ref:`new`
+section.
+
+For detailed information on ``new`` subcommand refer to the
+:ref:`mon` section.
+
+Deploying monitors
+==================
+
+To actually deploy ``ceph-mon`` to the hosts you chose, run::
+
+  ceph-deploy mon create HOST [HOST..]
+
+Without explicit hosts listed, hosts in ``mon_initial_members`` in the
+config file are deployed. That is, the hosts you passed to
+``ceph-deploy new`` are the default value here.
+
+For detailed information on ``mon`` subcommand refer to the
+:ref:`mon` section.
+
+Gather keys
+===========
+
+To gather authentication keys (for administering the cluster and
+bootstrapping new nodes) to the local directory, run::
+
+  ceph-deploy gatherkeys HOST [HOST...]
+
+where ``HOST`` is one of the monitor hosts.
+
+Once these keys are in the local directory, you can provision new OSDs etc.
+
+For detailed information on ``gatherkeys`` subcommand refer to the
+:ref:`gatherkeys` section.
+
+Admin hosts
+===========
+
+To prepare a host with a ``ceph.conf`` and ``ceph.client.admin.keyring``
+keyring so that it can administer the cluster, run::
+
+  ceph-deploy admin HOST [HOST ...]
+
+Older versions of ceph-deploy automatically added the admin keyring to
+all mon nodes making them admin nodes. For detailed information on the
+admin command refer to the :ref:`admin` section.
+
+For detailed information on ``admin`` subcommand refer to the
+:ref:`admin` section.
+
+Deploying OSDs
+==============
+
+To create an OSD on a remote node, run::
+
+  ceph-deploy osd create HOST --data /path/to/device
+
+Alternatively, ``--data`` can accept a logical volume in the format of
+``vg/lv``
+
+After that, the hosts will be running OSDs for the given data disks or logical
+volumes. For other OSD devices like journals (when using ``--filestore``) or
+``block.db``, and ``block.wal``, these need to be logical volumes or GPT
+partitions.
+
+.. note:: Partitions aren't created by this tool, they must be created
+          beforehand
+
+
+Forget keys
+===========
+
+The ``new`` and ``gatherkeys`` put some Ceph authentication keys in keyrings in
+the local directory.  If you are worried about them being there for security
+reasons, run::
+
+  ceph-deploy forgetkeys
+
+and they will be removed.  If you need them again later to deploy additional
+nodes, simply re-run::
+
+  ceph-deploy gatherkeys HOST [HOST...]
+
+and they will be retrieved from an existing monitor node.
+
+Multiple clusters
+=================
+
+All of the above commands take a ``--cluster=NAME`` option, allowing
+you to manage multiple clusters conveniently from one workstation.
+For example::
+
+  ceph-deploy --cluster=us-west new
+  vi us-west.conf
+  ceph-deploy --cluster=us-west mon
+
+FAQ
+===
+
+Before anything
+---------------
+Make sure you have the latest version of ``ceph-deploy``. It is actively
+developed and releases are coming weekly (on average). The most recent versions
+of ``ceph-deploy`` will have a ``--version`` flag you can use, otherwise check
+with your package manager and update if there is anything new.
+
+Why is feature X not implemented?
+---------------------------------
+Usually, features are added when/if it is sensible for someone that wants to
+get started with ceph and said feature would make sense in that context.  If
+you believe this is the case and you've read "`what this tool is not`_" and
+still think feature ``X`` should exist in ceph-deploy, open a feature request
+in the ceph tracker: http://tracker.ceph.com/projects/ceph-deploy/issues
+
+A command gave me an error, what is going on?
+---------------------------------------------
+Most of the commands for ``ceph-deploy`` are meant to be run remotely in a host
+that you have configured when creating the initial config. If a given command
+is not working as expected try to run the command that failed in the remote
+host and assert the behavior there.
+
+If the behavior in the remote host is the same, then it is probably not
+something wrong with ``ceph-deploy`` per-se. Make sure you capture the output
+of both the ``ceph-deploy`` output and the output of the command in the remote
+host.
+
+Issues with monitors
+--------------------
+If your monitors are not starting, make sure that the ``{hostname}`` you used
+when you ran ``ceph-deploy mon create {hostname}`` match the actual ``hostname -s``
+in the remote host.
+
+Newer versions of ``ceph-deploy`` should warn you if the results are different
+but that might prevent the monitors from reaching quorum.
diff --git a/docs/source/install.rst b/docs/source/install.rst
new file mode 100644 (file)
index 0000000..8291f88
--- /dev/null
@@ -0,0 +1,219 @@
+
+.. _install:
+
+install
+===========
+A few different distributions are supported with some flags to allow some
+customization for installing ceph on remote nodes.
+
+Supported distributions:
+
+* Ubuntu
+* Debian
+* Fedora
+* RedHat
+* CentOS
+* Suse
+* Scientific Linux
+* Arch Linux
+
+Before any action is taken, a platform detection call is done to make sure that
+the platform that will get ceph installed is the correct one. If the platform
+is not supported no further actions will proceed and an error message will be
+displayed, similar to::
+
+    [ceph_deploy][ERROR ] UnsupportedPlatform: Platform is not supported: Mandriva
+
+
+.. _install-stable-releases:
+
+
+.. _note:
+    Although ceph-deploy installs some extra dependencies, do note that those
+    are not going to be uninstalled. For example librbd1 and librados which
+    qemu-kvm depends on, and removing it would cause issues for qemu-kvm.
+
+Distribution Notes
+------------------
+
+RPMs
+^^^^
+On RPM-based distributions, ``yum-plugin-priorities`` is installed to make sure
+that upstream ceph.com repos have a higher priority than distro repos.
+
+Because of packaging splits that are present in downstream repos that may not
+be present in ceph.com repos, ``ceph-deploy`` enables the ``check_obsoletes``
+flag for the Yum priorities plugin.
+
+.. versionchanged:: 1.5.22
+   Enable ``check_obsoletes`` by default
+
+RHEL
+^^^^
+When installing packages on systems running Red Hat Enterprise Linux (RHEL),
+``ceph-deploy`` will not install the latest upstream release by default. On other
+distros, running ``ceph-deploy install`` without the ``--release`` flag will
+install the latest upstream release by default (i.e. firefly, giant, etc). On
+RHEL, the ``--release`` flag *must* be used if you wish to use the upstream
+packages hosted on http://ceph.com.
+
+.. versionchanged:: 1.5.22
+   Require ``--release`` flag to get upstream packages on RHEL
+
+Specific Releases
+-----------------
+By default the *latest* release is assumed. This value changes when
+newer versions are available. If you are automating deployments it is better to
+specify exactly what release you need::
+
+    ceph-deploy install --release emperor {host}
+
+
+Note that the ``--stable`` flag for specifying a Ceph release is deprecated and
+should no longer be used starting from version 1.3.6.
+
+.. versionadded:: 1.4.0
+
+.. _install-unstable-releases:
+
+Unstable releases
+-----------------
+If you need to test cutting edge releases or a specific feature of ceph that
+has yet to make it to a stable release you can specify this as well with
+ceph-deploy with a couple of flags.
+
+To get the latest development release::
+
+    ceph-deploy install --testing {host}
+
+For a far more granular approach, you may want to specify a branch or a tag
+from the repository, if none specified it fall backs to the latest commit in
+master::
+
+    ceph-deploy install --dev {branch or tag} {host}
+
+
+.. _install-behind-firewall:
+
+Behind Firewall
+---------------
+For restrictive environments there are a couple of options to be able to
+install ceph.
+
+If hosts have had some customizations with custom repositories and all is
+needed is to proceed with a install of ceph, we can skip altering the source
+repositories like::
+
+    ceph-deploy install --no-adjust-repos {host}
+
+Note that you will need to have working repositories that have all the
+dependencies that ceph needs. In some distributions, other repos (besides the
+ceph repos) will be added, like EPEL for CentOS.
+
+However, if there is a ceph repo mirror already set up you can point to it
+before installation proceeds. For this specific action you will need two
+arguments passed in (or optionally use environment variables).
+
+The repository URL and the GPG URL can be specified like this::
+
+    ceph-deploy install --repo-url {http mirror} --gpg-url {http gpg url} {host}
+
+Optionally, you can use the following environment variables:
+
+* ``CEPH_DEPLOY_REPO_URL``
+* ``CEPH_DEPLOY_GPG_URL``
+
+Those values will be used to write to the ceph ``sources.list`` (in Debian and
+Debian-based distros) or the ``yum.repos`` file for RPM distros and will skip
+trying to compose the right URL for the release being installed.
+
+.. note::
+    It is currently not possible to specify what version/release is to be
+    installed when ``--repo-url`` is used.
+
+It is strongly suggested that both flags be provided. However, the
+``--gpg-url`` will default to the current one in the ceph repository::
+
+    https://download.ceph.com/keys/release.asc
+
+.. versionadded:: 1.3.3
+
+
+Local Mirrors
+-------------
+``ceph-deploy`` supports local mirror installation by syncing a repository to
+remote servers and configuring correctly the remote hosts to install directly
+from those local paths (as opposed to going through the network).
+
+The one requirement for this option to work is to have a ``release.asc`` at the
+top of the directory that holds the repository files.
+
+That file is used by Ceph as the key for its signed packages and it is usually
+retrieved from::
+
+        https://download.ceph.com/keys/release.asc
+
+This is how it would look the process to get Ceph installed from a local
+repository in an admin host::
+
+    $ ceph-deploy install --local-mirror ~/tmp/rpm-mirror/ceph.com/rpm-emperor/el6 node2
+    [ceph_deploy.cli][INFO  ] Invoked (1.4.1): /bin/ceph-deploy install --local-mirror /Users/alfredo/tmp/rpm-mirror/ceph.com/rpm-emperor/el6 node2
+    [ceph_deploy.install][DEBUG ] Installing stable version emperor on cluster ceph hosts node2
+    [ceph_deploy.install][DEBUG ] Detecting platform for host node2 ...
+    [node2][DEBUG ] connected to host: node2
+    [node2][DEBUG ] detect platform information from remote host
+    [node2][DEBUG ] detect machine type
+    [ceph_deploy.install][INFO  ] Distro info: CentOS 6.4 Final
+    [node2][INFO  ] installing ceph on node2
+    [node2][INFO  ] syncing file: noarch/ceph-deploy-1.3-0.noarch.rpm
+    [node2][INFO  ] syncing file: noarch/ceph-deploy-1.3.1-0.noarch.rpm
+    [node2][INFO  ] syncing file: noarch/ceph-deploy-1.3.2-0.noarch.rpm
+    [node2][INFO  ] syncing file: noarch/ceph-release-1-0.el6.noarch.rpm
+    [node2][INFO  ] syncing file: noarch/index.html
+    [node2][INFO  ] syncing file: noarch/index.html?C=D;O=A
+    [node2][INFO  ] syncing file: noarch/index.html?C=D;O=D
+    [node2][INFO  ] syncing file: noarch/index.html?C=M;O=A
+    ...
+    [node2][DEBUG ]
+    [node2][DEBUG ] Installed:
+    [node2][DEBUG ]   ceph.x86_64 0:0.72.1-0.el6
+    [node2][DEBUG ]
+    [node2][DEBUG ] Complete!
+    [node2][INFO  ] Running command: sudo ceph --version
+    [node2][DEBUG ] ceph version 0.72.1
+    (4d923861868f6a15dcb33fef7f50f674997322de)
+
+.. versionadded:: 1.5.0
+
+
+Repo file only
+--------------
+The ``install`` command has a flag that offers flexibility for installing
+"repo files" only, avoiding installation of ceph and its dependencies.
+
+These "repo files" are the configuration files for package managers ("yum" or
+"apt" for example) that point to the right repository information so that
+certain packages become available.
+
+For APT  these files would be `list files` and for YUM they would be `repo
+files`. Regardless of the package manager, ceph-deploy is able to install this
+file correctly so that the Ceph packages are available. This is useful in
+a situation where a massive upgrade is needed and ``ceph-deploy`` would be too
+slow to install sequentially in every host.
+
+Repositories are specified in the ``cephdeploy.conf`` (or
+``$HOME/.cephdeploy.conf``) file. If a specific repository section is needed,
+it can be specified with the ``--release`` flag::
+
+    ceph-deploy install --repo --release firefly {HOSTS}
+
+The above command would install the ``firefly`` repo file in every ``{HOST}``
+specified.
+
+If a repository section exists with the ``default = True`` flag, there is no
+need to specify anything else and the repo file can be installed simply by
+passing in the hosts::
+
+    ceph-deploy install --repo {HOSTS}
+
+.. versionadded:: 1.5.10
diff --git a/docs/source/mds.rst b/docs/source/mds.rst
new file mode 100644 (file)
index 0000000..c7b1b10
--- /dev/null
@@ -0,0 +1,20 @@
+.. _mds:
+
+mds
+=======
+The ``mds`` subcommand provides an interface to interact with a cluster's
+CephFS Metadata servers.
+
+create
+----------
+Deploy MDS instances by specifying directly like::
+
+    ceph-deploy mds create node1 node2 node3
+
+This will create an MDS on the given node(s) and start the
+corresponding service.
+
+The MDS instances will default to having a name corresponding to the hostname
+where it runs.  For example, ``mds.node1``.
+
+.. note:: Removing MDS instances is not yet supported
diff --git a/docs/source/mon.rst b/docs/source/mon.rst
new file mode 100644 (file)
index 0000000..7942500
--- /dev/null
@@ -0,0 +1,106 @@
+.. _mon:
+
+mon
+=======
+The ``mon`` subcommand provides an interface to interact with a cluster's
+monitors. The tool makes a few assumptions that are needed to implement the
+most common scenarios. Monitors are usually very particular in what they need
+to work correctly.
+
+.. note:: Before version v1.5.33 ceph-deploy relied upon ``ceph-create-keys``.
+          Using ``ceph-create-keys`` produced a side effect of deploying all
+          bootstrap keys on the mon node so making all mon nodes admin nodes.
+          This can be recreated by running the admin command on all mon nodes
+          see :ref:`admin` section.
+
+create-initial
+------------------
+Will deploy for monitors defined in ``mon initial members``, wait until
+they form quorum and then ``gatherkeys``, reporting the monitor status along
+the process. If monitors don't form quorum the command will eventually
+time out.
+
+This is the *preferred* way of initially deploying monitors since it will
+compound a few of the steps needed together while looking for possible issues
+along the way.
+
+::
+
+    ceph-deploy mon create-initial
+
+
+create
+----------
+Deploy monitors by specifying directly like::
+
+    ceph-deploy mon create node1 node2 node3
+
+If no hosts are passed it will default to use the `mon initial members`
+defined in the configuration.
+
+Please note that if this is an initial monitor deployment, the preferred way
+is to use ``create-initial``.
+
+
+add
+-------
+Add a monitor to an existing cluster::
+
+    ceph-deploy mon add node1
+
+Since monitor hosts can have different network interfaces, this command allows
+you to specify the interface IP in a few different ways.
+
+**``--address``**: this will explicitly override any configured address for
+that host. Usage::
+
+    ceph-deploy mon add node1 --address 192.168.1.10
+
+
+**ceph.conf**: If a section for the node that is being added exists and it
+defines a ``mon addr`` key. For example::
+
+    [mon.node1]
+    mon addr = 192.168.1.10
+
+**resolving/dns**: if the monitor address is not defined in the configuration file
+nor overridden in the command-line it will fall-back to resolving the address
+of the provided host.
+
+.. warning:: If the monitor host has multiple addresses you should specify
+             the address directly to ensure the right IP is used. Please
+             note, only one node can be added at a time.
+
+.. versionadded:: 1.4.0
+
+
+destroy
+-----------
+Completely remove monitors on a remote host. Requires hostname(s) as
+arguments::
+
+    ceph-deploy mon destroy node1 node2 node3
+
+
+--keyrings
+--------------
+Both ``create`` and ``create-initial`` subcommands can be used with the
+``--keyrings`` flag that accepts a path to search for keyring files.
+
+When this flag is used it will then look into the passed in path for files that
+end with ``.keyring`` and will proceed to concatenate them in memory and seed
+them to the monitor being created in the remote mode.
+
+This is useful when having several different keyring files that are needed at
+initial setup, but normally, ceph-deploy will only use the
+``$cluster.mon.keyring`` file for initial seeding.
+
+To keep things in order, create a directory and use that directory to store all
+the keyring files that are needed. This is how the commands would look like for
+a directory called ``keyrings``::
+
+    ceph-deploy mon --keyrings keyrings create-initial
+
+Or for the ``create`` sub-command::
+
+    ceph-deploy mon --keyrings keyrings create {nodes}
diff --git a/docs/source/new.rst b/docs/source/new.rst
new file mode 100644 (file)
index 0000000..e71d4dd
--- /dev/null
@@ -0,0 +1,75 @@
+.. _new:
+
+new
+=======
+This subcommand is used to generate a working ``ceph.conf`` file that will
+contain important information for provisioning nodes and/or adding them to
+a cluster.
+
+
+SSH Keys
+--------
+Ideally, all nodes will be pre-configured to have their passwordless access
+from the machine executing ``ceph-deploy`` but you can also take advantage of
+automatic detection of this when calling the ``new`` subcommand.
+
+Once called, it will try to establish an SSH connection to the hosts passed
+into the ``new`` subcommand, and determine if it can (or cannot) connect
+without a password prompt.
+
+If it can't proceed, it will try to copy *existing* keys to the remote host, if
+those do not exist, then passwordless ``rsa`` keys will be generated for the
+current user and those will get used.
+
+This feature can be overridden in the ``new`` subcommand like::
+
+    ceph-deploy new --no-ssh-copykey
+
+.. versionadded:: 1.3.2
+
+
+Creating a new configuration
+----------------------------
+
+To create a new configuration file and secret key, decide what hosts
+will run ``ceph-mon``, and run::
+
+  ceph-deploy new MON [MON..]
+
+listing the hostnames of the monitors.  Each ``MON`` can be
+
+ * a simple hostname.  It must be DNS resolvable without the fully
+   qualified domain name.
+ * a fully qualified domain name.  The hostname is assumed to be the
+   leading component up to the first ``.``.
+ * a ``HOST:FQDN`` pair, of both the hostname and a fully qualified
+   domain name or IP address.  For example, ``foo``,
+   ``foo.example.com``, ``foo:something.example.com``, and
+   ``foo:1.2.3.4`` are all valid.  Note, however, that the hostname
+   should match that configured on the host ``foo``.
+
+The above will create a ``ceph.conf`` and ``ceph.mon.keyring`` in your
+current directory.
+
+
+Edit initial cluster configuration
+----------------------------------
+
+You want to review the generated ``ceph.conf`` file and make sure that
+the ``mon_host`` setting contains the IP addresses you would like the
+monitors to bind to.  These are the IPs that clients will initially
+contact to authenticate to the cluster, and they need to be reachable
+both by external client-facing hosts and internal cluster daemons.
+
+
+--cluster-network --public-network
+----------------------------------
+Are used to provide subnets so that nodes can communicate within that
+network. If passed, validation will occur by looking at the remote IP addresses
+and making sure that at least one of those addresses is valid for the given
+subnet.
+
+Those values will also be added to the generated ``ceph.conf``. If IPs are not
+correct (or not in the subnets specified) an error will be raised.
+
+.. versionadded:: 1.5.13
diff --git a/docs/source/pkg.rst b/docs/source/pkg.rst
new file mode 100644 (file)
index 0000000..2f70d08
--- /dev/null
@@ -0,0 +1,58 @@
+
+.. _pkg:
+
+pkg
+=======
+Provides a simple interface to install or remove packages on a remote host (or
+a number of remote hosts).
+
+Packages to install or remove *must* be comma separated when there are more
+than one package in the argument.
+
+.. note::
+    This feature only supports installing on same distributions. You cannot
+    install a given package on different distributions at the same time.
+
+
+.. _pkg-install:
+
+--install
+-------------
+This flag will use the package (or packages) passed in to perform an installation using
+the distribution package manager in a non-interactive way. Package managers
+that tend to ask for confirmation will not prompt.
+
+An example call to install a few packages on 2 hosts (with hostnames like
+``node1`` and ``node2``) would look like::
+
+    ceph-deploy pkg --install vim,zsh node1 node2
+    [ceph_deploy.cli][INFO  ] Invoked (1.3.3): /bin/ceph-deploy pkg --install vim,zsh node1 node2
+    [node1][DEBUG ] connected to host: node1
+    [node1][DEBUG ] detect platform information from remote host
+    [node1][DEBUG ] detect machine type
+    [ceph_deploy.pkg][INFO  ] Distro info: Ubuntu 12.04 precise
+    [node1][INFO  ] installing packages on node1
+    [node1][INFO  ] Running command: sudo env DEBIAN_FRONTEND=noninteractive apt-get -q install --assume-yes vim zsh
+    ...
+
+
+.. _pkg-remove:
+
+--remove
+------------
+This flag will use the package (or packages) passed in to remove them using
+the distribution package manager in a non-interactive way. Package managers
+that tend to ask for confirmation will not prompt.
+
+An example call to remove a few packages on 2 hosts (with hostnames like
+``node1`` and ``node2``) would look like::
+
+
+    [ceph_deploy.cli][INFO  ] Invoked (1.3.3): /bin/ceph-deploy pkg --remove vim,zsh node1 node2
+    [node1][DEBUG ] connected to host: node1
+    [node1][DEBUG ] detect platform information from remote host
+    [node1][DEBUG ] detect machine type
+    [ceph_deploy.pkg][INFO  ] Distro info: Ubuntu 12.04 precise
+    [node1][INFO  ] removing packages from node1
+    [node1][INFO  ] Running command: sudo apt-get -q remove -f -y --force-yes -- vim zsh
+    ...
diff --git a/docs/source/repo.rst b/docs/source/repo.rst
new file mode 100644 (file)
index 0000000..e862048
--- /dev/null
@@ -0,0 +1,77 @@
+.. _repo:
+
+repo
+=====
+Provides a simple interface for installing or removing new Apt or RPM repo files.
+
+Apt repo files are added in ``/etc/apt/sources.list.d``, while RPM repo files
+are added in ``/etc/yum.repos.d``.
+
+.. _repo-install:
+
+Installing repos
+----------------
+
+Repos can be defined through CLI arguments, or they can be defined in cephdeploy.conf
+and referenced by name.
+
+The general format for adding a repo is::
+
+    ceph-deploy repo --repo-url <repo_url> --gpg-url <optional URL to GPG key> <repo-name> <host> [host [host ...]]
+
+As an example of adding the Ceph rpm-hammer repo for EL7::
+
+    ceph-deploy repo --repo-url http://ceph.com/rpm-hammer/el7/x86_64/ --gpg-url 'https://download.ceph.com/keys/release.asc' ceph HOST1
+
+In this example, the repo-name is ``ceph``, and the file ``/etc/yum.repos.d/ceph.repo``
+will be created. Because ``--gpg-url`` was passed, the repo will have ``gpgcheck=1``
+and will reference the given GPG key.  
+
+For APT, the equivalent example would be::
+
+    ceph-deploy repo --repo-url http://ceph.com/debian-hammer --gpg-url 'https://download.ceph.com/keys/release.asc' ceph HOST1
+
+If a repo was defined in cephdeploy.conf, like the following::
+
+    [ceph-mon]
+    name=Ceph-MON
+    baseurl=https://cephmirror.com/hammer/el7/x86_64
+    gpgkey=https://cephmirror.com/release.asc
+    gpgcheck=1
+    proxy=_none_
+
+This could be installed with this command::
+
+    ceph-deploy repo ceph-mon HOST1
+
+``ceph-deploy repo`` will always check to see if a matching repo name exists in
+cephdeploy.conf first.
+
+It is possible that repos may be password protected, and a URL may be structured like so::
+
+    https://<user>:<password>@host.com/...
+
+In this case, Apt repositories will be created with mode ``0600`` to make
+sure the password is not world-readable.  You can also use the
+``CEPH_DEPLOY_REPO_URL`` and ``CEPH_DEPLOY_GPG_URL`` environment variables in lieu
+of ``--repo-url`` and ``--gpg-url`` to avoid placing sensitive credentials on the
+command line (and thus visible in the process table).
+
+.. note::
+    The writing of a repo file as mode 0600 when a password is present is only done
+    for Apt repos currently.
+
+.. _repo-remove:
+
+Removing
+--------
+
+Repos are simply removed by name.  The general format for adding a repo is::
+
+    ceph-deploy repo --remove <repo-name> <host> [host [host...]]
+
+To remove a repo at ``/etc/yum.repos.d/ceph.repo``, do::
+
+    ceph-deploy repo --remove ceph HOST1
+
+.. versionadded:: 1.5.27
diff --git a/docs/source/rgw.rst b/docs/source/rgw.rst
new file mode 100644 (file)
index 0000000..4a060c5
--- /dev/null
@@ -0,0 +1,36 @@
+.. _rgw:
+
+rgw
+=======
+The ``rgw`` subcommand provides an interface to interact with a cluster's
+RADOS Gateway instances.
+
+create
+----------
+Deploy RGW instances by specifying directly like::
+
+    ceph-deploy rgw create node1 node2 node3
+
+This will create an instance of RGW on the given node(s) and start the
+corresponding service. The daemon will listen on the default port of 7480.
+
+The RGW instances will default to having a name corresponding to the hostname
+where it runs.  For example, ``rgw.node1``.
+
+If a custom name is desired for the RGW daemon, it can be specific like::
+
+    ceph-deploy rgw create node1:foo
+
+Custom names are automatically prefixed with "rgw.", so the resulting daemon
+name would be "rgw.foo".
+
+.. note:: If an error is presented about the ``bootstrap-rgw`` keyring not being
+          found, that is because the ``bootstrap-rgw`` only been auto-created on
+          new clusters starting with the Hammer release.
+
+.. versionadded:: 1.5.23
+
+.. note:: Removing RGW instances is not yet supported
+
+.. note:: Changing the port on which RGW will listen at deployment time is not yet
+          supported.
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644 (file)
index 0000000..7d7303e
--- /dev/null
@@ -0,0 +1,3 @@
+pytest >=2.1.3
+tox >=1.2
+mock >=1.0.1
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/scripts/build-debian.sh b/scripts/build-debian.sh
new file mode 100755 (executable)
index 0000000..5b59670
--- /dev/null
@@ -0,0 +1,87 @@
+#! /bin/sh
+
+# Tag tree and update version number in change log and
+# in setup.py before building.
+
+REPO=debian-repo
+COMPONENT=main
+KEYID=${KEYID:-03C3951A}  # default is autobuild keyid
+DEB_DIST="sid wheezy squeeze quantal precise oneiric natty raring"
+DEB_BUILD=$(lsb_release -s -c)
+RELEASE=0
+
+if [ X"$1" = X"--release" ] ; then
+    echo "Release Build"
+    RELEASE=1
+fi
+
+if [ ! -d debian ] ; then
+    echo "Are we in the right directory"
+    exit 1
+fi
+
+if gpg --list-keys 2>/dev/null | grep -q ${KEYID} ; then
+    echo "Signing packages and repo with ${KEYID}"
+else
+    echo "Package signing key (${KEYID}) not found"
+    echo "Have you set \$GNUPGHOME ? "
+    exit 3
+fi
+
+# Clean up any leftover builds
+rm -f ../ceph-deploy*.dsc ../ceph-deploy*.changes ../ceph-deploy*.deb ../ceph-deploy.tgz
+rm -rf ./debian-repo
+
+# Apply backport tag if release build
+# I am going to jump out the window if this is not fixed and removed from the source
+# of this package. There is absolutely **NO** reason why we need to hard code the
+# DEBEMAIL like this.
+if [ $RELEASE -eq 1 ] ; then 
+    DEB_VERSION=$(dpkg-parsechangelog | sed -rne 's,^Version: (.*),\1, p')
+    BP_VERSION=${DEB_VERSION}${BPTAG}
+    DEBEMAIL="alfredo.deza@inktank.com" dch -D $DIST --force-distribution -b -v "$BP_VERSION" "$comment"
+    dpkg-source -b .
+fi
+
+# Build Package
+echo "Building for dist: $DEB_BUILD"
+dpkg-buildpackage -k$KEYID
+if [ $? -ne 0 ] ; then
+    echo "Build failed"
+    exit 2
+fi
+
+# Build Repo
+PKG=../ceph-deploy*.changes
+mkdir -p $REPO/conf
+if [ -e $REPO/conf/distributions ] ; then
+    rm -f $REPO/conf/distributions
+fi
+
+for DIST in  $DEB_DIST ; do
+    cat <<EOF >> $REPO/conf/distributions
+Codename: $DIST
+Suite: stable
+Components: $COMPONENT
+Architectures: amd64 armhf i386 source
+Origin: Inktank
+Description: Ceph distributed file system
+DebIndices: Packages Release . .gz .bz2
+DscIndices: Sources Release .gz .bz2
+Contents: .gz .bz2
+SignWith: $KEYID
+
+EOF
+done
+
+echo "Adding package to repo, dist: $DEB_BUILD ($PKG)"
+reprepro --ask-passphrase -b $REPO -C $COMPONENT --ignore=undefinedtarget --ignore=wrongdistribution include $DEB_BUILD $PKG
+
+#for DIST in $DEB_DIST
+#do
+#    [ "$DIST" = "$DEB_BUILD" ] && continue
+#    echo "Copying package to dist: $DIST"
+#    reprepro -b $REPO --ignore=undefinedtarget --ignore=wrongdistribution copy $DIST $DEB_BUILD ceph-deploy
+#done
+
+echo "Done"
diff --git a/scripts/build-rpm.sh b/scripts/build-rpm.sh
new file mode 100755 (executable)
index 0000000..9b330e4
--- /dev/null
@@ -0,0 +1,59 @@
+#! /bin/sh
+
+# Tag tree and update version number in change log and
+# in setup.py before building.
+
+REPO=rpm-repo
+KEYID=${KEYID:-03C3951A}  # Default is autobuild-key
+BUILDAREA=./rpmbuild
+DIST=el6
+RPM_BUILD=$(lsb_release -s -c)
+
+if [ ! -e setup.py ] ; then
+    echo "Are we in the right directory"
+    exit 1
+fi
+
+if gpg --list-keys 2>/dev/null | grep -q ${KEYID} ; then
+    echo "Signing packages and repo with ${KEYID}"
+else
+    echo "Package signing key (${KEYID}) not found"
+    echo "Have you set \$GNUPGHOME ? "
+    exit 3
+fi
+
+if ! CREATEREPO=`which createrepo` ; then
+    echo "Please install the createrepo package"
+    exit 4
+fi
+
+# Create Tarball
+python setup.py sdist --formats=bztar
+
+# Build RPM
+mkdir -p rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
+BUILDAREA=`readlink -fn ${BUILDAREA}`   ### rpm wants absolute path
+cp ceph-deploy.spec ${BUILDAREA}/SPECS
+cp dist/*.tar.bz2 ${BUILDAREA}/SOURCES
+echo "buildarea is: ${BUILDAREA}"
+rpmbuild -ba --define "_topdir ${BUILDAREA}" --define "_unpackaged_files_terminate_build 0" ${BUILDAREA}/SPECS/ceph-deploy.spec
+
+# create repo
+DEST=${REPO}/${DIST}
+mkdir -p ${REPO}/${DIST}
+cp -r ${BUILDAREA}/*RPMS ${DEST}
+
+# Sign all the RPMs for this release
+rpm_list=`find ${REPO} -name "*.rpm" -print`
+rpm --addsign --define "_gpg_name ${KEYID}" $rpm_list
+
+# Construct repodata
+for dir in ${DEST}/SRPMS ${DEST}/RPMS/*
+do
+    if [ -d $dir ] ; then
+        createrepo $dir
+        gpg --detach-sign --armor -u ${KEYID} $dir/repodata/repomd.xml
+    fi
+done
+
+exit 0
diff --git a/scripts/ceph-deploy b/scripts/ceph-deploy
new file mode 100755 (executable)
index 0000000..cc8dd62
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+import os
+import platform
+import sys
+"""
+ceph-deploy - admin tool for ceph
+"""
+
+if os.path.exists('/usr/share/pyshared/ceph_deploy'):
+    sys.path.insert(0,'/usr/share/pyshared/ceph_deploy')
+elif os.path.exists('/usr/share/ceph-deploy'):
+    sys.path.insert(0,'/usr/share/ceph-deploy')
+elif os.path.exists('/usr/share/pyshared/ceph-deploy'):
+    sys.path.insert(0,'/usr/share/pyshared/ceph-deploy')
+elif os.path.exists('/usr/lib/python2.6/site-packages/ceph_deploy'):
+    sys.path.insert(0,'/usr/lib/python2.6/site-packages/ceph_deploy')
+
+from ceph_deploy.cli import main
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/scripts/jenkins-build b/scripts/jenkins-build
new file mode 100755 (executable)
index 0000000..9d0d212
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+# This is the script that runs inside Jenkins.
+# http://jenkins.ceph.com/job/ceph-deploy/
+
+set -x
+set -e
+
+# Jenkins will set $RELEASE as a parameter in the job configuration.
+if $RELEASE ; then
+        # This is a formal release. Sign it with the release key.
+        export GNUPGHOME=/home/jenkins-build/build/gnupg.ceph-release/
+        export KEYID=17ED316D
+else
+        # This is an automatic build. Sign it with the autobuild key.
+        export GNUPGHOME=/home/jenkins-build/build/gnupg.autobuild/
+        export KEYID=03C3951A
+fi
+
+HOST=$(hostname --short)
+echo "Building on ${HOST}"
+echo "  DIST=${DIST}"
+echo "  BPTAG=${BPTAG}"
+echo "  KEYID=${KEYID}"
+echo "  WS=$WORKSPACE"
+echo "  PWD=$(pwd)"
+echo "  BRANCH=$BRANCH"
+
+case $HOST in
+gitbuilder-*-rpm*)
+        rm -rf rpm-repo dist/* build/rpmbuild
+       ./scripts/build-rpm.sh --release
+        if [ $? -eq 0 ] ; then
+            cd $WORKSPACE
+            mkdir -p dist
+        fi
+        ;;
+gitbuilder-cdep-deb* | tala* | mira*)
+        rm -rf debian-repo
+        rm -rf dist
+        rm -f ../*.changes ../*.dsc ../*.gz ../*.diff
+       ./scripts/build-debian.sh --release
+        if [ $? -eq 0 ] ; then
+            cd $WORKSPACE
+            mkdir -p dist
+            mv ../*.changes ../*.dsc ../*.deb ../*.tar.gz dist/.
+        fi
+        ;;
+*)
+       echo "Can't determine build host type"
+        exit 4
+        ;;
+esac
diff --git a/scripts/jenkins-pull-requests-build b/scripts/jenkins-pull-requests-build
new file mode 100644 (file)
index 0000000..24f2867
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -x
+set -e
+
+# This is the script that runs inside Jenkins for each pull request.
+# http://jenkins.ceph.com/job/ceph-deploy-pull-requests/
+
+virtualenv --version
+virtualenv venv
+. venv/bin/activate
+#venv/bin/python setup.py develop
+venv/bin/pip install tox
+venv/bin/tox -rv
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..b9f9db4
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[tool:pytest]
+norecursedirs = .* _* virtualenv
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..1a465b9
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,68 @@
+from setuptools import setup, find_packages
+import os
+import sys
+import ceph_deploy
+
+
+def read(fname):
+    path = os.path.join(os.path.dirname(__file__), fname)
+    f = open(path)
+    return f.read()
+
+
+install_requires = ['remoto']
+pyversion = sys.version_info[:2]
+if pyversion < (2, 7) or (3, 0) <= pyversion <= (3, 1):
+    install_requires.append('argparse')
+
+
+setup(
+    name='ceph-deploy',
+    version=ceph_deploy.__version__,
+    packages=find_packages(),
+
+    author='Inktank',
+    author_email='ceph-devel@vger.kernel.org',
+    description='Deploy Ceph with minimal infrastructure',
+    long_description=read('README.rst'),
+    license='MIT',
+    keywords='ceph deploy',
+    url="https://github.com/ceph/ceph-deploy",
+
+    install_requires=[
+        'setuptools',
+        ] + install_requires,
+
+    tests_require=[
+        'pytest >=2.1.3',
+        'mock >=1.0b1',
+        ],
+
+    entry_points={
+
+        'console_scripts': [
+            'ceph-deploy = ceph_deploy.cli:main',
+            ],
+
+        'ceph_deploy.cli': [
+            'new = ceph_deploy.new:make',
+            'install = ceph_deploy.install:make',
+            'uninstall = ceph_deploy.install:make_uninstall',
+            'purge = ceph_deploy.install:make_purge',
+            'purgedata = ceph_deploy.install:make_purge_data',
+            'mon = ceph_deploy.mon:make',
+            'gatherkeys = ceph_deploy.gatherkeys:make',
+            'osd = ceph_deploy.osd:make',
+            'disk = ceph_deploy.osd:make_disk',
+            'mds = ceph_deploy.mds:make',
+            'mgr = ceph_deploy.mgr:make',
+            'forgetkeys = ceph_deploy.forgetkeys:make',
+            'config = ceph_deploy.config:make',
+            'admin = ceph_deploy.admin:make',
+            'pkg = ceph_deploy.pkg:make',
+            'rgw = ceph_deploy.rgw:make',
+            'repo = ceph_deploy.repo:make',
+            ],
+
+        },
+    )
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..4d529d5
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,32 @@
+[tox]
+envlist = py27, py35, flake8
+
+[testenv]
+deps=
+  pytest
+  mock==1.0.1
+setenv =
+    CEPH_DEPLOY_TEST = 1
+commands=py.test -v {posargs:ceph_deploy/tests}
+
+[testenv:docs]
+basepython=python
+changedir=docs/source
+deps=sphinx
+commands=
+    sphinx-build -W -b html -d {envtmpdir}/doctrees .  {envtmpdir}/html
+
+[testenv:flake8]
+deps=flake8
+commands=flake8 --select=F,E9 --exclude=vendor {posargs:ceph_deploy}
+
+# Note that ``remoto`` is not added as a dependency here as it is assumed
+# that the tester will have the distro version of remoto installed
+
+[testenv:py26-novendor]
+sitepackages=True
+deps=
+
+[testenv:py27-novendor]
+sitepackages=True
+deps=
diff --git a/vendor.py b/vendor.py
new file mode 100644 (file)
index 0000000..9d49efa
--- /dev/null
+++ b/vendor.py
@@ -0,0 +1,112 @@
+from __future__ import print_function
+
+import subprocess
+import os
+from os import path
+import re
+import traceback
+import sys
+
+
+error_msg = """
+This library depends on sources fetched when packaging that failed to be
+retrieved.
+
+This means that it will *not* work as expected. Errors encountered:
+"""
+
+
+def run(cmd):
+    print('[vendoring] Running command: %s' % ' '.join(cmd))
+    try:
+        result = subprocess.Popen(
+            cmd,
+            stderr=subprocess.PIPE,
+            stdout=subprocess.PIPE
+        )
+    except Exception:
+        # if building with python2.5 this makes it compatible
+        _, error, _ = sys.exc_info()
+        print_error([], traceback.format_exc(error).split('\n'))
+        raise SystemExit(1)
+
+    if result.wait():
+        print_error(result.stdout.readlines(), result.stderr.readlines())
+
+    return result.returncode
+
+
+def print_error(stdout, stderr):
+    print('*'*80)
+    print(error_msg)
+    for line in stdout:
+        print(line)
+    for line in stderr:
+        print(line)
+    print('*'*80)
+
+
+def vendor_library(name, version, cmd=None):
+    this_dir = path.dirname(path.abspath(__file__))
+    vendor_dest = path.join(this_dir, 'ceph_deploy/lib/vendor/%s' % name)
+    vendor_init = path.join(vendor_dest, '__init__.py')
+    vendor_src = path.join(this_dir, name)
+    vendor_module = path.join(vendor_src, name)
+    current_dir = os.getcwd()
+
+    if path.exists(vendor_src):
+        run(['rm', '-rf', vendor_src])
+
+    if path.exists(vendor_init):
+        # The following read/regex is done so that we can parse module metadata without the need
+        # to import it. Module metadata is defined as variables with double underscores. We are
+        # particularly insteresting in the version string, so we look into single or double quoted
+        # values, like:  __version__ = '1.0'
+        module_file = open(vendor_init).read()
+        metadata = dict(re.findall(r"__([a-z]+)__\s*=\s*['\"]([^'\"]*)['\"]", module_file))
+        if metadata.get('version') != version:
+            run(['rm', '-rf', vendor_dest])
+
+    if not path.exists(vendor_dest):
+        rc = run(['git', 'clone', 'git://git.ceph.com/%s' % name])
+        if rc:
+            print("%s: git clone failed using ceph.com url with rc %s, trying github.com" % (path.basename(__file__), rc))
+            run(['git', 'clone', 'https://github.com/ceph/%s.git' % name])
+        os.chdir(vendor_src)
+        run(['git', 'checkout', version])
+        if cmd:
+            run(cmd)
+        run(['mv', vendor_module, vendor_dest])
+    os.chdir(current_dir)
+
+
+def clean_vendor(name):
+    """
+    Ensure that vendored code/dirs are removed, possibly when packaging when
+    the environment flag is set to avoid vendoring.
+    """
+    this_dir = path.dirname(path.abspath(__file__))
+    vendor_dest = path.join(this_dir, 'ceph_deploy/lib/vendor/%s' % name)
+    run(['rm', '-rf', vendor_dest])
+
+
+def vendorize(vendor_requirements):
+    """
+    This is the main entry point for vendorizing requirements. It expects
+    a list of tuples that should contain the name of the library and the
+    version.
+
+    For example, a library ``foo`` with version ``0.0.1`` would look like::
+
+        vendor_requirements = [
+            ('foo', '0.0.1'),
+        ]
+    """
+
+    for library in vendor_requirements:
+        if len(library) == 2:
+            name, version = library
+            cmd = None
+        elif len(library) == 3:  # a possible cmd we need to run
+            name, version, cmd = library
+        vendor_library(name, version, cmd)